--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/share/hedgewars/Data/Scripts/SimpleMission.lua Wed Feb 21 17:14:46 2018 +0100
@@ -0,0 +1,795 @@
+--[=[
+= Simple Mission Framework for Hedgewars =
+
+This is a simple library intended to make setting up simple missions an
+easy task for Lua scripters. The entire game logic and coding is
+abtracted away in a single function which you just need to feed
+a large definition table in which you define gears, goals, etc.
+
+This is ideal for missions in which you set up the entire scenario
+from the start and don't need any complex in-mission events.
+BUT! This is NOT suited for missions with scripted events, cut-scenes,
+branching story, etc.
+
+This library has the following features:
+* Add teams, clans, hogs
+* Spawn gears
+* Sensible defaults for almost everything
+* Set custom goals or use the default one (kill all enemies)
+* Add non-goals to fail the mission
+* Checks victory and failure automatically
+
+To use this library, you first have to load it and to call SimpleMission once with
+the appropriate parameters.
+See the comment of SimpleMission for a specification of all parameters.
+
+]=]
+
+HedgewarsScriptLoad("/Scripts/Locale.lua")
+HedgewarsScriptLoad("/Scripts/Tracker.lua")
+
+--[[
+SimpleMission(params)
+
+This function sets up the *entire* mission and needs one argument: params.
+The argument “params” is a table containing fields which describe the mission.
+
+ Mandatory fields:
+ - teams: Table of teams. There must be 1-8 teams.
+
+ Optional fields
+ - ammoConfig Table containing basic ammo values (default: infinite skip only)
+ - initVars Table where you set up environment parameters such as MinesNum.
+ - wind If set, the wind will permanently set to this value (-100..100)
+ - gears: Table of objects.
+ - girders Table of girders
+ - rubbers Table of rubbers
+
+ AMMO
+ - ammoType ammo type
+ - delay delay (default: 0)
+ - numberInCrate ammo per crate (default: 1)
+ - count default starter ammo for everyone, 9 for infinite (default: 0)
+ - probability probability in crates (default: 0)
+
+ TEAM DATA
+ - hogs table of hedgehogs in this team (must contain at least 1 hog)
+ - name team name
+ - clanID ID of the clan to which this team belongs to. Counting starts at 0.
+ By default, each team goes into its own clan.
+ Important: The clan of the player and allies MUST be 0.
+ Important: You MUST either set the clan ID explicitly for all teams or none of them.
+ - flag flag name (default: hedgewars)
+ - grave grave name (has default grave for each team)
+ - fort fort name (default: Castle)
+
+ HEDGEHOG DATA:
+ - id optional identifier for goals
+ - name hog name
+ - x, y hog position (default: spawns randomly on land)
+ - botLevel 1-5: Bot level (lower=stronger). 0=human player (default: 0)
+ - hat hat name (default: NoHat)
+ - health hog health (default: 100)
+ - poisoned if true, hedgehog starts poisoned with 5 poison damage. Set to a number for other poison damage (default: false)
+ - frozen if true, hedgehogs starts frozen (default: false)
+ - faceLeft initial facing direction. true=left, false=false (default: false)
+ - ammo table of ammo types
+
+ GEAR TYPES:
+ - type gear type
+ ALL types:
+ id optional identifier for goals
+ x x coordinate of starting position (default: 0)
+ y y coordinate of starting position (default: 0)
+ dx initial x speed (default: 0)
+ dy initial y speed (default: 0)
+ - type=gtMine Mine
+ timer Mine timer (only for non-duds). Default: MinesTime
+ isDud Whether the mine is a dud. default: false
+ isFrozen Whether the mine is frozen. If true, it implies being a dud as well. Default: false
+ health Initial health of dud mines. Has no effect if isDud=false. Default: 36
+ - type=gtSMine Sticky mine
+ timer Timer. Default: 500
+ - type=gtAirMine Air mine
+ timer Timer. Default: (MinesTime/1000 * 250)
+ - type=gtExplosives Barrel
+ health Initial health. Default: 60
+ isFrozen Whether the barrel is frozen. Default: true with health > 60, false otherwise
+ isRolling Whether the barrel starts in “rolling” state. Default: false
+ - type=gtCase Crate
+ crateType "health": Health crate
+ "supply": Ammo or utility crate (select crate type automatically)
+ "supply_ammo_explicit": Ammo crate (not recommened)
+ "supply_utility_explicit": Utility crate (not recommededn)
+ ammoType Contained ammo (only for ammo and utility crates).
+ health Contained health (only for health crates). Default: HealthCaseAmount
+ isFrozen Whether the crate is frozen. Default: false
+ - type=gtKnife Cleaver
+ - type=gtTarget Target
+
+ GOALS:
+ Note: If there are at least two opposing teams, a default goal is used, which is to defeat all the enemies of the
+ player's team. If this is what you want, you can skip this section.
+
+ The default goal is overwritten as if customGoals has been set. Set customGoals and other related parameters for
+ defining your own special goals. In this case, the mission is won if all customGoals are completed.
+ Note the mission will always fail if the player's hedgehogs and all their allies have been defeated.
+ If there is only one team (for the player), there is no default goal and one must be set explicitly.
+ - customGoals Table of custom goals (see below). All of them must be met to win. Some goal types might fail,
+ rendering the mission unwinnable and leading to the loss of the mission. An example is
+ blowing up a crate which you should have collected.ed.
+ - customNonGoals Table of non-goals, the player loses if one of them is achieved
+ - customGoalCheck When to check goals and non-goals. Values: "instant" (default), "turnStart", "turnEnd"
+
+ - missionTitle: The name of the mission (highly recommended)
+ - customGoalText: A short string explaining the goal of the mission (use this if you set custom goals).
+
+ GOAL TYPES:
+ - type name of goal type
+ - type="destroy" Gear must be destroyed
+ - id Gear to destroy
+ - type="teamDefeat" Team must be defeated
+ - teamName Name of team to defeat
+ - type="collect" Crate must be collected
+ FAIL CONDITION: Crate taken by enemy, or destroyed
+ - id ID of crate gear to collect
+ - collectors Optional table of gear IDs, any one of which must collect the gear (but nobody else!).
+ By default, this is for the player's teams and allies.
+ - type="turns" Achieved when a number of turns has been played
+ - turns Number of played turns
+ - type="rounds" Achieved when a number of rounds has been played
+ - rounds Number of played rounds
+ - type="suddenDeath" Sudden Death has started
+ - type="inZone" A gear is within given coordinate bounds. Each of xMin, xMax, yMin and yMax is a sub-goal.
+ Each sub-goal is only checked if not nil.
+ You can use this to check if a gear left, right, above or below a given coordinate.
+ To check if the gear is within a rectangle, just set all 4 sub-goals.
+ FAIL CONDITION: Gear destroyed
+ - id Gear to watch
+ - xMin gear's X coordinate must be lower than this
+ - xMax gear's X coordinate must be higher than this
+ - yMin gear's Y coordinate must be lower than this
+ - yMax gear's Y coordinate must be higher than this
+ - type="distGearPos" Distance between a gear and a fixed position
+ FAIL CONDITION: Gear destroyed
+ - distance goal distance to compare to
+ - relationship "greaterThan" or "lowerThan"
+ - id gear to watch
+ - x x coordinate to reach
+ - y y coordinate to reach
+ - type="distGearGear" Distance between two gears
+ FAIL CONDITION: Any of both gears destroyed
+ - distance goal distance to compare to
+ - relationship "greaterThan" or "lowerThan"
+ - id1 first gear to compare
+ - id2 second gear to compare
+ - type="damage" Gear took damage or was destroyed
+ - id Gear to watch
+ - damage Minimum amount of damage to take at a single blow. Default: 1
+ - canDestroy If false, this goal will fail if the gear was destroyed without taking the required damage
+ - type="drown" Gear has drowned
+ FAIL CONDITION: Gear destroyed by other means
+ - id Gear to watch
+ - type="poison" Gear must be poisoned
+ FAIL CONDITION: Gear destroyed
+ - id Gear to be poisoned
+ - type="cure" Gear must exist and be free from poisoning
+ FAIL CONDITION: Gear destroyed
+ - id Gear to check
+ - type="freeze" Gear must exist and be frozen
+ FAIL CONDITION: Gear destroyed
+ - id Gear to be frozen
+ - type="melt" Gear must exist and be unfrozen
+ FAIL CONDITION: Gear destroyed
+ - id Gear to check
+ - type="waterSkip" Gear must have skipped over water
+ FAIL CONDITION: Gear destroyed before it reached the required number of skips
+ - id
+ - skips Total number of water skips required at least (default: 1)
+
+]]
+
+local goals
+local teamHogs = {}
+
+--[[
+ HELPER VARIABLES
+]]
+
+local defaultClanColors = {
+ [0] = 0xff0204, -- red
+ [1] = 0x4980c1, -- blue
+ [2] = 0x1de6ba, -- cyan
+ [3] = 0xb541ef, -- purple
+ [4] = 0xe55bb0, -- magenta
+ [5] = 0x20bf00, -- green
+ [6] = 0xfe8b0e, -- orange
+ [7] = 0x5f3605, -- brown
+ [8] = 0xffff01, -- yellow
+}
+local defaultGraves = {
+ "Grave", "Statue", "pyramid", "Simple", "skull", "Badger", "Duck2", "Flower"
+}
+local defaultFlags = {
+ "hedgewars", "cm_birdy", "cm_eyes", "cm_spider", "cm_kiwi", "cm_scout", "cm_skull", "cm_bars"
+}
+
+-- Utility functions
+
+-- Returns value if it is non-nil, otherwise returns default
+local function def(value, default)
+ if value == nil then
+ return default
+ else
+ return value
+ end
+end
+
+-- Get hypotenuse of a triangle with legs x and y
+local function hypot(x, y)
+ local t
+ x = math.abs(x)
+ y = math.abs(y)
+ t = math.min(x, y)
+ x = math.max(x, y)
+ if x == 0 then
+ return 0
+ end
+ t = t / x
+ return x * math.sqrt(1 + t * t)
+end
+
+local errord = false
+
+-- This function generates the mission. See above for the meaning of params.
+function SimpleMission(params)
+ if params.missionTitle == nil then
+ params.missionTitle = loc("Scenario")
+ end
+ if params.goalText == nil then
+ params.goalText = loc("Defeat the enemy!")
+ end
+ if params.customGoalCheck == nil and (params.customGoals ~= nil or params.customNonGoals ~= nil) then
+ params.customGoalCheck = "instant"
+ end
+
+ _G.sm = {}
+
+ _G.sm.isInSuddenDeath = false
+
+ -- Number of completed turns
+ _G.sm.gameTurns = 0
+
+ _G.sm.goalGears = {}
+
+ _G.sm.params = params
+
+ _G.sm.gameEnded = false
+
+ _G.sm.playerClan = 0
+
+ _G.sm.makeStats = function(winningClan, customAchievements)
+ for t=0, TeamsCount-1 do
+ local team = GetTeamName(t)
+ local stats = GetTeamStats(team)
+ local clan = GetTeamClan(team)
+ if clan == winningClan then
+ SendStat(siPlayerKills, stats.Kills, team)
+ end
+ end
+ for t=0, TeamsCount-1 do
+ local team = GetTeamName(t)
+ local stats = GetTeamStats(team)
+ local clan = GetTeamClan(team)
+ if clan ~= winningClan then
+ SendStat(siPlayerKills, stats.Kills, team)
+ end
+ end
+ if customAchievements ~= nil then
+ for a=1, #customAchievements do
+ SendStat(siCustomAchievement, customAchievements[a])
+ end
+ end
+ end
+
+ _G.sm.checkGoal = function(goal)
+ if goal.type == "destroy" then
+ return getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed")
+ elseif goal.type == "collect" then
+ local collector = getGearValue(_G.sm.goalGears[goal.id], "sm_collected")
+ if collector then
+ if not goal.collectors then
+ if GetHogClan(collector) == _G.sm.playerClan then
+ return true
+ else
+ -- Fail if the crate was collected by enemy
+ return "fail"
+ end
+ else
+ for c=1, #goal.collectors do
+ if _G.sm.goalGears[goal.collectors[c]] == collector then
+ return true
+ end
+ end
+ -- Fail if the crate was collected by someone who was not supposed to get it
+ return "fail"
+ end
+ else
+ -- Fail goal if crate was destroyed
+ if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ return "fail"
+ end
+ return false
+ end
+ elseif goal.type == "turns" then
+ return sm.gameTurns >= goal.turns
+ elseif goal.type == "rounds" then
+ return (TotalRounds) >= goal.rounds
+ elseif goal.type == "inZone" then
+ if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ return "fail"
+ end
+ local gX, gY = GetGearPosition(_G.sm.goalGears[goal.id])
+ -- 4 sub-goals, each optional
+ local g1 = (not goal.xMin) or gX >= goal.xMin
+ local g2 = (not goal.xMax) or gX <= goal.xMax
+ local g3 = (not goal.yMin) or gY >= goal.yMin
+ local g4 = (not goal.yMax) or gY <= goal.yMax
+ return g1 and g2 and g3 and g4
+ elseif goal.type == "distGearPos" or goal.type == "distGearGear" then
+ local gX, tY, tX, tY
+ if goal.type == "distGearPos" then
+ if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ -- Fail if gear was destroyed
+ return "fail"
+ end
+ gX, gY = GetGearPosition(_G.sm.goalGears[goal.id])
+ tX, tY = goal.x, goal.y
+ elseif goal.type == "distGearGear" then
+ if getGearValue(_G.sm.goalGears[goal.id1], "sm_destroyed") or getGearValue(_G.sm.goalGears[goal.id2], "sm_destroyed") then
+ -- Fail if one of the gears was destroyed
+ return "fail"
+ end
+ gX, gY = GetGearPosition(_G.sm.goalGears[goal.id1])
+ tX, tY = GetGearPosition(_G.sm.goalGears[goal.id2])
+ end
+
+ local h = hypot(gX - tX, gY - tY)
+ if goal.relationship == "smallerThan" then
+ return h < goal.distance
+ elseif goal.relationship == "greaterThan" then
+ return h > goal.distance
+ end
+ -- Invalid parameters!
+ error("SimpleMission: Invalid parameters for distGearPos/distGearGear!")
+ errord = true
+ return false
+ elseif goal.type == "suddenDeath" then
+ return sm.isInSuddenDeath
+ elseif goal.type == "damage" then
+ local damage = goal.damage or 1
+ local tookEnoughDamage = getGearValue(_G.sm.goalGears[goal.id], "sm_maxDamage") >= damage
+ if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ -- Fail if gear was destroyed without taking enough damage first
+ if not tookEnoughDamage and goal.canDestroy == false then
+ return "fail"
+ else
+ -- By default, succeed if gear was destroyed
+ return true
+ end
+ end
+ return tookEnoughDamage
+ elseif goal.type == "drown" then
+ local drowned = getGearValue(_G.sm.goalGears[goal.id], "sm_drowned")
+ -- Fail if gear was destroyed by something other than drowning
+ if not drowned and getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ return "fail"
+ end
+ return drowned
+ elseif goal.type == "poison" then
+ if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ return "fail"
+ end
+ return GetEffect(_G.sm.goalGears[goal.id], hePoisoned) >= 1
+ elseif goal.type == "freeze" then
+ if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ return "fail"
+ end
+ return GetEffect(_G.sm.goalGears[goal.id], heFrozen) >= 256
+ elseif goal.type == "cure" then
+ if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ return "fail"
+ end
+ return GetEffect(_G.sm.goalGears[goal.id], hePoisoned) == 0
+ elseif goal.type == "melt" then
+ if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ return "fail"
+ end
+ return GetEffect(_G.sm.goalGears[goal.id], heFrozen) == 0
+ elseif goal.type == "waterSkip" then
+ local skips = goal.skips or 1
+ local hasEnoughSkips = getGearValue(_G.sm.goalGears[goal.id], "sm_waterSkips") >= skips
+ -- Fail if gear was destroyed before it got the required number of skips
+ if not hasEnoughSkips and getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
+ return "fail"
+ end
+ return hasEnoughSkips
+ elseif goal.type == "teamDefeat" then
+ return #teamHogs[goal.teamName] == 0
+ else
+ return false
+ end
+ end
+
+ --[[ Checks the custom goals.
+ Returns true when all custom goals are met.
+ Returns false when not all custom goals are met.
+ Returns "fail" if any of the goals has failed (i.e. is impossible to complete).
+ Returns nil when there are no custom goals ]]
+ _G.sm.checkGoals = function()
+ if params.customGoals ~= nil and #params.customGoals > 0 then
+ for key, goal in pairs(params.customGoals) do
+ local done = _G.sm.checkGoal(goal)
+ if done == false or done == "fail" then
+ return done
+ end
+ end
+ return true
+ else
+ return nil
+ end
+ end
+
+ --[[ Checks the custom non-goals.
+ Returns true when any non-goal is met.
+ Returns false otherwise. ]]
+ _G.sm.checkNonGoals = function()
+ if params.customNonGoals ~= nil and #params.customNonGoals > 0 then
+ for key, nonGoal in pairs(params.customNonGoals) do
+ local done = _G.sm.checkGoal(nonGoal)
+ if done == true then
+ return true
+ end
+ end
+ end
+ return false
+ end
+
+ -- Checks goals and non goals and wins or loses mission
+ _G.sm.checkWinOrFail = function()
+ if errord then
+ return
+ end
+ if _G.sm.checkNonGoals() == true or _G.sm.checkGoals() == "fail" then
+ _G.sm.lose()
+ elseif _G.sm.checkGoals() == true then
+ _G.sm.win()
+ end
+ end
+
+ _G.sm.win = function()
+ if not _G.sm.gameEnded then
+ _G.sm.gameEnded = true
+ AddCaption(loc("Victory!"), 0xFFFFFFFF, capgrpGameState)
+ SendStat(siGameResult, loc("You win!"))
+ if GetHogLevel(CurrentHedgehog) == 0 then
+ SetState(CurrentHedgehog, bor(GetState(CurrentHedgehog), gstWinner))
+ SetState(CurrentHedgehog, band(GetState(CurrentHedgehog), bnot(gstHHDriven)))
+ PlaySound(sndVictory, CurrentHedgehog)
+ end
+ _G.sm.makeStats(_G.sm.playerClan)
+ EndGame()
+ end
+ end
+
+ _G.sm.lose = function()
+ if not _G.sm.gameEnded then
+ _G.sm.gameEnded = true
+ AddCaption(loc("Scenario failed!"), 0xFFFFFFFF, capgrpGameState)
+ SendStat(siGameResult, loc("You lose!"))
+ if GetHogLevel(CurrentHedgehog) == 0 then
+ SetState(CurrentHedgehog, bor(GetState(CurrentHedgehog), gstLoser))
+ SetState(CurrentHedgehog, band(GetState(CurrentHedgehog), bnot(gstHHDriven)))
+ end
+ local clan = ClansCount-1
+ for t=0, TeamsCount-1 do
+ local team = GetTeamName(t)
+ -- Just declare any living team other than the player team the winner
+ if (_G.sm.checkGoal({type="teamDefeat", teamName=team}) == false) and (GetTeamClan(team) ~= _G.sm.playerClan) then
+ clan = GetTeamClan(team)
+ break
+ end
+ end
+ _G.sm.makeStats(clan)
+ EndGame()
+ end
+ end
+
+ _G.onSuddenDeath = function()
+ sm.isInSuddenDeath = true
+ end
+
+ _G.onGearWaterSkip = function(gear)
+ increaseGearValue(gear, "sm_waterSkips")
+ end
+
+ _G.onGearAdd = function(gear)
+ if GetGearType(gear) == gtHedgehog then
+ local team = GetHogTeamName(gear)
+ if teamHogs[team] == nil then
+ teamHogs[team] = {}
+ end
+ table.insert(teamHogs[GetHogTeamName(gear)], gear)
+ end
+ setGearValue(gear, "sm_waterSkips", 0)
+ setGearValue(gear, "sm_maxDamage", 0)
+ setGearValue(gear, "sm_drowned", false)
+ setGearValue(gear, "sm_destroyed", false)
+ end
+
+ _G.onGearResurrect = function(gear)
+ if GetGearType(gear) == gtHedgehog then
+ table.insert(teamHogs[GetHogTeamName(gear)], gear)
+ end
+ setGearValue(gear, "sm_destroyed", false)
+ end
+
+ _G.onGearDelete = function(gear)
+ if GetGearType(gear) == gtCase and band(GetGearMessage(gear), gmDestroy) ~= 0 then
+ -- Set ID of collector
+ setGearValue(gear, "sm_collected", CurrentHedgehog)
+ end
+ if GetGearType(gear) == gtHedgehog then
+ local team = GetHogTeamName(gear)
+ local hogList = teamHogs[team]
+ for h=1, #hogList do
+ if hogList[h] == gear then
+ table.remove(hogList, h)
+ break
+ end
+ end
+ end
+ if band(GetState(gear), gstDrowning) ~= 0 then
+ setGearValue(gear, "sm_drowned", true)
+ end
+ setGearValue(gear, "sm_destroyed", true)
+ end
+
+ _G.onGearDamage = function(gear, damage)
+ local currentDamage = getGearValue(gear, "sm_maxDamage")
+ if damage > currentDamage then
+ setGearValue(gear, "sm_maxDamage", damage)
+ end
+ end
+
+ _G.onGameInit = function()
+ CaseFreq = 0
+ WaterRise = 0
+ HealthDecrease = 0
+ MinesNum = 0
+ Explosives = 0
+
+ for initVarName, initVarValue in pairs(params.initVars) do
+ _G[initVarName] = initVarValue
+ end
+ if #params.teams == 1 then
+ EnableGameFlags(gfOneClanMode)
+ end
+
+ local clanCounter = 0
+ for teamID, teamData in pairs(params.teams) do
+ local name, clanID, grave, fort, voice, flag
+ name = def(teamData.name, string.format(loc("Team %d"), teamID))
+ if teamData.clanID == nil then
+ clanID = clanCounter
+ clanCounter = clanCounter + 1
+ else
+ clanID = teamData.clanID
+ end
+ grave = def(teamData.grave, defaultGraves[math.min(teamID, 8)])
+ fort = def(teamData.fort, "Castle")
+ voice = def(teamData.voice, "Default")
+ flag = def(teamData.flag, defaultFlags[math.min(teamID, 8)])
+
+ AddTeam(name, defaultClanColors[clanID], grave, fort, voice, flag)
+
+ for hogID, hogData in pairs(teamData.hogs) do
+ local name, botLevel, health, hat
+ name = def(hogData.name, string.format(loc("Hog %d"), hogID))
+ botLevel = def(hogData.botLevel, 0)
+ health = def(hogData.health, 100)
+ hat = def(hogData.hat, "NoHat")
+ local hog = AddHog(name, botLevel, health, hat)
+ if hogData.x ~= nil and hogData.y ~= nil then
+ SetGearPosition(hog, hogData.x, hogData.y)
+ end
+ if hogData.faceLeft then
+ HogTurnLeft(hog, true)
+ end
+ if hogData.poisoned == true then
+ SetEffect(hog, hePoisoned, 5)
+ elseif type(hogData.poisoned) == "number" then
+ SetEffect(hog, hePoisoned, hogData.poisoned)
+ end
+ if hogData.frozen then
+ SetEffect(hog, heFrozen, 199999)
+ end
+
+ if hog ~= nil and hogData.id ~= nil then
+ _G.sm.goalGears[hogData.id] = hog
+ setGearValue(hog, "sm_id", hogData.id)
+ end
+
+ -- Remember this hedgehog's gear ID for later use
+ hogData.gearID = hog
+ end
+ end
+ end
+
+ _G.onNewTurn = function()
+ if params.wind ~= nil then
+ SetWind(params.wind)
+ end
+ _G.sm.gameStarted = true
+
+ if params.customGoalCheck == "turnStart" then
+ _G.sm.checkWinOrFail()
+ end
+ end
+
+ _G.onEndTurn = function()
+ _G.sm.gameTurns = _G.sm.gameTurns + 1
+
+ if params.customGoalCheck == "turnEnd" then
+ _G.sm.checkWinOrFail()
+ end
+ end
+
+ _G.onAmmoStoreInit = function()
+ local ammoTypesDone = {}
+ -- Read script's stated ammo wishes
+ if params.ammoConfig ~= nil then
+ for ammoType, v in pairs(params.ammoConfig) do
+ SetAmmo(ammoType, def(v.count, 0), def(v.probability, 0), def(v.delay, 0), def(v.numberInCrate, 1))
+ ammoTypesDone[ammoType] = true
+ end
+ end
+ -- Apply default values for all ammo types which have not been set
+ for a=0, AmmoTypeMax do
+ if a ~= amNothing and ammoTypesDone[a] ~= true then
+ local count = 0
+ if a == amSkip then
+ count = 9
+ end
+ SetAmmo(a, count, 0, 0, 1)
+ end
+ end
+ end
+
+ _G.onGameStart = function()
+ -- Mention mines timer
+ if MinesTime ~= 3000 and MinesTime ~= nil then
+ if MinesTime < 0 then
+ params.goalText = params.goalText .. "|" .. loc("Mines time: 0s-5s")
+ elseif (MinesTime % 1000) == 0 then
+ params.goalText = params.goalText .. "|" .. string.format(loc("Mines time: %ds"), MinesTime/1000)
+ elseif (MinesTime % 100) == 0 then
+ params.goalText = params.goalText .. "|" .. string.format(loc("Mines time: %.1fs"), MinesTime/1000)
+ else
+ params.goalText = params.goalText .. "|" .. string.format(loc("Mines time: %.2fs"), MinesTime/1000)
+ end
+ end
+ ShowMission(params.missionTitle, loc("Scenario"), params.goalText, 1, 5000)
+
+ -- Spawn objects
+
+ if params.gears ~= nil then
+ for listGearID, gv in pairs(params.gears) do
+ local timer, state, x, y, dx, dy
+ local g
+ state = 0
+ if gv.type == gtMine then
+ if gv.isFrozen then
+ state = gstFrozen
+ end
+ g = AddGear(def(gv.x,0), def(gv.y,0), gv.type, state, def(gv.dx, 0), def(gv.dy, 0), def(gv.timer, MinesTime))
+ if gv.isDud then
+ SetHealth(g, 0)
+ if gv.health ~= nil then
+ SetGearValues(g, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 36 - gv.health)
+ end
+ end
+ elseif gv.type == gtSMine then
+ g = AddGear(def(gv.x,0), def(gv.y,0), gv.type, 0, def(gv.dx,0), def(gv.dy,0), def(gv.timer, 500))
+ elseif gv.type == gtAirMine then
+ if gv.isFrozen then
+ state = gstFrozen
+ end
+ local timer = def(gv.timer, div(MinesTime, 1000) * 250)
+ g = AddGear(def(gv.x,0), def(gv.y,0), gv.type, state, def(gv.dx,0), def(gv.dy,0), timer)
+ SetGearValues(g, nil, nil, timer) -- WDTimer
+ elseif gv.type == gtExplosives then
+ if gv.isRolling then
+ state = gsttmpFlag
+ end
+ g = AddGear(def(gv.x,0), def(gv.y,0), gv.type, state, def(gv.dx,0), def(gv.dy,0), 0)
+ if gv.health then
+ SetHealth(g, gv.health)
+ end
+ if gv.isFrozen ~= nil then
+ if gv.isFrozen == true then
+ SetState(g, bor(GetState(g, gstFrozen)))
+ end
+ elseif GetHealth(g) > 60 then
+ SetState(g, bor(GetState(g, gstFrozen)))
+ end
+ elseif gv.type == gtCase then
+ local x, y, spawnTrick
+ spawnTrick = false
+ x = def(gv.x, 0)
+ y = def(gv.y, 0)
+ if x==0 and y==0 then
+ x=1
+ y=1
+ spawnTrick = true
+ end
+ g = AddGear(x, y, gv.type, 0, def(gv.dx,0), def(gv.dy,0), 0)
+ if spawnTrick then
+ SetGearPosition(g, 0, 0)
+ end
+ if gv.crateType == "supply" then
+ g = SpawnSupplyCrate(def(gv.x, 0), def(gv.y, 0), gv.ammoType)
+ elseif gv.crateType == "supply_ammo_explicit" then
+ g = SpawnAmmoCrate(def(gv.x, 0), def(gv.y, 0), gv.ammoType)
+ elseif gv.crateType == "supply_utility_explicit" then
+ g = SpawnUtilityCrate(def(gv.x, 0), def(gv.y, 0), gv.ammoType)
+ elseif gv.crateType == "health" then
+ g = SpawnHealthCrate(def(gv.x, 0), def(gv.y, 0))
+ if gv.health ~= nil then
+ SetHealth(g, gv.health)
+ end
+ end
+ if gv.isFrozen then
+ SetState(g, bor(GetState(g, gstFrozen)))
+ end
+ elseif gv.type == gtKnife or gv.type == gtTarget then
+ g = AddGear(def(gv.x,0), def(gv.y,0), gv.type, 0, def(gv.dx,0), def(gv.dy,0), 0)
+ end
+ if g ~= nil and gv.id ~= nil then
+ _G.sm.goalGears[gv.id] = g
+ setGearValue(g, "sm_id", gv.id)
+ end
+ end
+ end
+
+ -- Spawn girders and rubbers
+ if params.girders ~= nil then
+ for i, girderData in pairs(params.girders) do
+ PlaceGirder(girderData.x, girderData.y, girderData.frameIdx)
+ end
+ end
+ if params.rubbers ~= nil then
+ for i, rubberData in pairs(params.rubbers) do
+ PlaceSprite(rubberData.x, rubberData.y, sprAmRubber, 0xFFFFFFFF, rubberData.frameIdx, false, false, false, lfBouncy)
+ end
+ end
+
+ -- Per-hedgehog ammo loadouts
+ for teamID, teamData in pairs(params.teams) do
+ for hogID, hogData in pairs(teamData.hogs) do
+ if hogData.ammo ~= nil then
+ for ammoType, count in pairs(hogData.ammo) do
+ AddAmmo(hogData.gearID, ammoType, count)
+ end
+ end
+ end
+ end
+ end
+
+ _G.onGameTick20 = function()
+ if params.customGoalCheck == "instant" then
+ _G.sm.checkWinOrFail()
+ end
+ end
+end
+