share/hedgewars/Data/Scripts/SimpleMission.lua
branchhedgeroid
changeset 15515 7030706266df
parent 15096 5c8c729a16ce
equal deleted inserted replaced
7861:bc7b6aa5d67a 15515:7030706266df
       
     1 --[=[
       
     2 = Simple Mission Framework for Hedgewars =
       
     3 
       
     4 This is a simple library intended to make setting up simple missions an
       
     5 easy task for Lua scripters. The entire game logic and coding is
       
     6 abtracted away in a single function which you just need to feed
       
     7 a large definition table in which you define gears, goals, etc.
       
     8 
       
     9 This is ideal for missions in which you set up the entire scenario
       
    10 from the start and don't need any complex in-mission events.
       
    11 BUT! This is NOT suited for missions with scripted events, cut-scenes,
       
    12 branching story, etc.
       
    13 
       
    14 This library has the following features:
       
    15 * Add teams, clans, hogs
       
    16 * Spawn gears
       
    17 * Sensible defaults for almost everything
       
    18 * Set custom goals or use the default one (kill all enemies)
       
    19 * Add non-goals to fail the mission
       
    20 * Checks victory and failure automatically
       
    21 
       
    22 To use this library, you first have to load it and to call SimpleMission once with
       
    23 the appropriate parameters.
       
    24 See the comment of SimpleMission for a specification of all parameters.
       
    25 
       
    26 ]=]
       
    27 
       
    28 HedgewarsScriptLoad("/Scripts/Locale.lua")
       
    29 HedgewarsScriptLoad("/Scripts/Tracker.lua")
       
    30 HedgewarsScriptLoad("/Scripts/Utils.lua")
       
    31 
       
    32 --[[
       
    33 SimpleMission(params)
       
    34 
       
    35 This function sets up the *entire* mission and needs one argument: params.
       
    36 The argument “params” is a table containing fields which describe the mission.
       
    37 
       
    38 	Mandatory fields:
       
    39 	- teams:		Table of teams. There must be 1-8 teams.
       
    40 
       
    41 	Optional fields
       
    42 	- ammoConfig		Table containing basic ammo values (default: infinite skip only)
       
    43 	- initVars		Table where you set up environment parameters such as MinesNum.
       
    44 	- wind			If set, the wind will permanently set to this value (-100..100). Implies gfDisableWind
       
    45 	- gears:		Table of objects.
       
    46 	- girders		Table of girders
       
    47 	- rubbers		Table of rubbers
       
    48 
       
    49 	AMMO
       
    50 	- ammoType		ammo type
       
    51 	- delay			delay (default: 0)
       
    52 	- numberInCrate		ammo per crate (default: 1)
       
    53 	- count			default starter ammo for everyone, 9 for infinite (default: 0)
       
    54 	- probability		probability in crates (default: 0)
       
    55 
       
    56 	TEAM DATA
       
    57 	- isMissionTeam		if true, this is the player's chosen team for this mission (default: false)
       
    58 	- hogs			table of hedgehogs in this team (must contain at least 1 hog)
       
    59 	- clanID		ID of the clan to which this team belongs to. Counting starts at 0.
       
    60 				By default, each team goes into its own clan.
       
    61 				Important: The clan of the player and allies MUST be 0.
       
    62 				Important: You MUST either set the clan ID explicitly for all teams or none of them.
       
    63 	These arguments will be ignored if this is a mission team:
       
    64 	- name			team name
       
    65 	- flag			flag name (default: hedgewars)
       
    66 	- grave			grave name (has default grave for each team)
       
    67 	- fort			fort name (default: Castle)
       
    68 	- voice			voicepack (default: Default_qau)
       
    69 
       
    70 	HEDGEHOG DATA:
       
    71 	- id			optional identifier for goals
       
    72 	- health		hog health (default: 100)
       
    73 	- ammo			table of ammo types
       
    74 	- x, y			hog position (default: spawns randomly on land)
       
    75 	- poisoned		if true, hedgehog starts poisoned with 5 poison damage. Set to a number for other poison damage (default: false)
       
    76 	- frozen		if true, hedgehogs starts frozen (default: false)
       
    77 	- faceLeft		initial facing direction. true=left, false=false (default: false)
       
    78 	These arguments will be ignored if the hog is in a mission team:
       
    79 	- name			hog name
       
    80 	- botLevel		1-5: Bot level (lower=stronger). 0=human player (default: 0)
       
    81 	- hat			hat name (default: NoHat)
       
    82 
       
    83 	GEAR TYPES:
       
    84 	- type			gear type
       
    85 	ALL types:
       
    86 		id		optional identifier for goals
       
    87 		x		x coordinate of starting position (default: 0)
       
    88 		y		y coordinate of starting position (default: 0)
       
    89 		dx		initial x speed (default: 0)
       
    90 		dy		initial y speed (default: 0)
       
    91 	- type=gtMine		Mine
       
    92 		timer 		Mine timer (only for non-duds). Default: MinesTime
       
    93 		isDud		Whether the mine is a dud. default: false
       
    94 		isFrozen	Whether the mine is frozen. If true, it implies being a dud as well. Default: false
       
    95 		health 		Initial health of dud mines. Has no effect if isDud=false. Default: 36
       
    96 	- type=gtSMine		Sticky mine
       
    97 		timer		Timer. Default: 500
       
    98 	- type=gtAirMine	Air mine
       
    99 		timer		Timer. Default: (MinesTime/1000 * 250)
       
   100 	- type=gtExplosives	Barrel
       
   101 		health		Initial health. Default: 60
       
   102 		isFrozen	Whether the barrel is frozen. Default: true with health > 60, false otherwise
       
   103 		isRolling	Whether the barrel starts in “rolling” state. Default: false
       
   104 	- type=gtCase		Crate
       
   105 		crateType	"health": Health crate
       
   106 				"supply": Ammo or utility crate (select crate type automatically)
       
   107 				"supply_ammo_explicit": Ammo crate (not recommened)
       
   108 				"supply_utility_explicit": Utility crate (not recommededn)
       
   109 		ammoType	Contained ammo (only for ammo and utility crates).
       
   110 		health		Contained health (only for health crates). Default: HealthCaseAmount
       
   111 		isFrozen	Whether the crate is frozen. Default: false
       
   112 	- type=gtKnife		Cleaver
       
   113 	- type=gtTarget		Target
       
   114 
       
   115 	GOALS:
       
   116 	Note: If there are at least two opposing teams, a default goal is used, which is to defeat all the enemies of the
       
   117 	player's team. If this is what you want, you can skip this section.
       
   118 
       
   119 	The default goal is overwritten as if customGoals has been set. Set customGoals and other related parameters for
       
   120 	defining your own special goals. In this case, the mission is won if all customGoals are completed.
       
   121 	Note the mission will always fail if the player's hedgehogs and all their allies have been defeated.
       
   122 	If there is only one team (for the player), there is no default goal and one must be set explicitly.
       
   123 	- customGoals		Table of custom goals (see below). All of them must be met to win. Some goal types might fail,
       
   124 				rendering the mission unwinnable and leading to the loss of the mission. An example is
       
   125 				blowing up a crate which you should have collected.ed.
       
   126 	- customNonGoals	Table of non-goals, the player loses if one of them is achieved
       
   127 	- customGoalCheck	When to check goals and non-goals. Values: "instant" (default), "turnStart", "turnEnd"
       
   128 
       
   129 	- missionTitle:		The name of the mission (highly recommended)
       
   130 	- missionIcon:		Icon of the mission panel, see documentation of ShowMission in the Lua API
       
   131 	- goalText:		A short string explaining the goal of the mission (use this if you set custom goals).
       
   132 
       
   133 	GOAL TYPES:
       
   134 	- type			name of goal type
       
   135 	- failText		Optional. For non-goals, this text will be shown in the stats if mission fails due to this non-goal
       
   136 				being completed. For goals which fail, this text will be displayed at failure. Note that
       
   137 				all normal goals have sensible default fail texts.
       
   138 	- type="destroy"	Gear must be destroyed
       
   139 		- id		Gear to destroy
       
   140 	- type="teamDefeat"	Team must be defeated
       
   141 		- teamName	Name of team to defeat
       
   142 	- type="collect"	Crate must be collected
       
   143 		FAIL CONDITION:	Crate taken by enemy, or destroyed
       
   144 		- id		ID of crate gear to collect
       
   145 		- collectors	Optional table of gear IDs, any one of which must collect the gear (but nobody else!).
       
   146 				By default, this is for the player's teams and allies.
       
   147 	- type="turns"		Achieved when a number of turns has been played
       
   148 		- turns 	Number of played turns 
       
   149 	- type="rounds"		Achieved when a number of rounds has been played
       
   150 		- rounds	Number of played rounds
       
   151 	- type="suddenDeath"	Sudden Death has started
       
   152 	- type="inZone"		A gear is within given coordinate bounds. Each of xMin, xMax, yMin and yMax is a sub-goal.
       
   153 				Each sub-goal is only checked if not nil.
       
   154 				You can use this to check if a gear left, right, above or below a given coordinate.
       
   155 				To check if the gear is within a rectangle, just set all 4 sub-goals.
       
   156 		FAIL CONDITION:	Gear destroyed
       
   157 		- id		Gear to watch
       
   158 		- xMin		gear's X coordinate must be lower than this
       
   159 		- xMax		gear's X coordinate must be higher than this
       
   160 		- yMin		gear's Y coordinate must be lower than this
       
   161 		- yMax		gear's Y coordinate must be higher than this
       
   162 	- type="distGearPos"	Distance between a gear and a fixed position
       
   163 		FAIL CONDITION:	Gear destroyed
       
   164 		- distance	goal distance to compare to
       
   165 		- relationship	"greaterThan" or "smallerThan"
       
   166 		- id		gear to watch
       
   167 		- x		x coordinate to reach
       
   168 		- y		y coordinate to reach
       
   169 	- type="distGearGear"	Distance between two gears
       
   170 		FAIL CONDITION:	Any of both gears destroyed
       
   171 		- distance	goal distance to compare to
       
   172 		- relationship	"greaterThan" or "smallerThan"
       
   173 		- id1		first gear to compare
       
   174 		- id2		second gear to compare
       
   175 	- type="damage"		Gear took damage or was destroyed
       
   176 		- id		Gear to watch
       
   177 		- damage	Minimum amount of damage to take at a single blow. Default: 1
       
   178 		- canDestroy	If false, this goal will fail if the gear was destroyed without taking the required damage
       
   179 	- type="drown"		Gear has drowned
       
   180 		FAIL CONDITION:	Gear destroyed by other means
       
   181 		- id		Gear to watch
       
   182 	- type="poison"		Gear must be poisoned
       
   183 		FAIL CONDITION:	Gear destroyed
       
   184 		- id		Gear to be poisoned
       
   185 	- type="cure"		Gear must exist and be free from poisoning
       
   186 		FAIL CONDITION:	Gear destroyed
       
   187 		- id		Gear to check
       
   188 	- type="freeze"		Gear must exist and be frozen
       
   189 		FAIL CONDITION:	Gear destroyed
       
   190 		- id		Gear to be frozen
       
   191 	- type="melt"		Gear must exist and be unfrozen
       
   192 		FAIL CONDITION:	Gear destroyed
       
   193 		- id		Gear to check
       
   194 	- type="waterSkip"	Gear must have skipped over water
       
   195 		FAIL CONDITION:	Gear destroyed before it reached the required number of skips
       
   196 		- id
       
   197 		- skips		Total number of water skips required at least (default: 1)
       
   198 
       
   199 ]]
       
   200 
       
   201 local goals
       
   202 local teamHogs = {}
       
   203 
       
   204 --[[
       
   205 	HELPER VARIABLES
       
   206 ]]
       
   207 
       
   208 local defaultGraves = {
       
   209 	"Grave", "Statue", "pyramid", "Simple", "skull", "Badger", "Duck2", "Flower"
       
   210 }
       
   211 local defaultFlags = {
       
   212 	"hedgewars", "cm_birdy", "cm_eyes", "cm_spider", "cm_kiwi", "cm_scout", "cm_skull", "cm_bars"
       
   213 }
       
   214 
       
   215 -- Utility functions
       
   216 
       
   217 -- Returns value if it is non-nil, otherwise returns default
       
   218 local function def(value, default)
       
   219 	if value == nil then
       
   220 		return default
       
   221 	else
       
   222 		return value
       
   223 	end
       
   224 end
       
   225 
       
   226 local errord = false
       
   227 
       
   228 -- This function generates the mission. See above for the meaning of params.
       
   229 function SimpleMission(params)
       
   230 	if params.missionTitle == nil then
       
   231 		params.missionTitle = loc("Scenario")
       
   232 	end
       
   233 	if params.missionIcon == nil then
       
   234 		params.missionIcon = 1 -- target icon
       
   235 	end
       
   236 	if params.goalText == nil then
       
   237 		params.goalText = loc("Eliminate the enemy.")
       
   238 	end
       
   239 	if params.customGoalCheck == nil and (params.customGoals ~= nil or params.customNonGoals ~= nil) then
       
   240 		params.customGoalCheck = "instant"
       
   241 	end
       
   242 
       
   243 	_G.sm = {}
       
   244 
       
   245 	_G.sm.isInSuddenDeath = false
       
   246 
       
   247 	-- Number of completed turns
       
   248 	_G.sm.gameTurns = 0
       
   249 
       
   250 	_G.sm.goalGears = {}
       
   251 
       
   252 	_G.sm.params = params
       
   253 
       
   254 	_G.sm.gameEnded = false
       
   255 
       
   256 	_G.sm.playerClan = 0
       
   257 
       
   258 	_G.sm.wonVarWritten = false
       
   259 
       
   260 	_G.sm.makeStats = function(winningClan, customAchievements)
       
   261 		for t=0, TeamsCount-1 do
       
   262 			local team = GetTeamName(t)
       
   263 			local stats = GetTeamStats(team)
       
   264 			local clan = GetTeamClan(team)
       
   265 			if clan == winningClan then
       
   266 				SendStat(siPlayerKills, stats.Kills, team)
       
   267 			end
       
   268 		end
       
   269 		for t=0, TeamsCount-1 do
       
   270 			local team = GetTeamName(t)
       
   271 			local stats = GetTeamStats(team)
       
   272 			local clan = GetTeamClan(team)
       
   273 			if clan ~= winningClan then
       
   274 				SendStat(siPlayerKills, stats.Kills, team)
       
   275 			end
       
   276 		end
       
   277 		if customAchievements ~= nil then
       
   278 			for a=1, #customAchievements do
       
   279 				SendStat(siCustomAchievement, customAchievements[a])
       
   280 			end
       
   281 		end
       
   282 	end
       
   283 
       
   284 	_G.sm.criticalGearFailText = function(gearSmid)
       
   285 		local gear = _G.sm.goalGears[gearSmid]
       
   286 		if GetGearType(gear) == gtHedgehog then
       
   287 			return string.format(loc("%s is dead, who was critical to this mission!"), GetHogName(gear))
       
   288 		else
       
   289 			return loc("We have lost an object which was critical to this mission.")
       
   290 		end
       
   291 	end
       
   292 
       
   293 	_G.sm.checkGoal = function(goal)
       
   294 		if goal.type == "destroy" then
       
   295 			return getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed")
       
   296 		elseif goal.type == "collect" then
       
   297 			local collector = getGearValue(_G.sm.goalGears[goal.id], "sm_collected")
       
   298 			if collector then
       
   299 				if not goal.collectors then
       
   300 					if GetHogClan(collector) == _G.sm.playerClan then
       
   301 						return true
       
   302 					else
       
   303 						-- Fail if the crate was collected by enemy
       
   304 						return "fail", loc("The enemy has taken a crate which we really needed!")
       
   305 					end
       
   306 				else
       
   307 					for c=1, #goal.collectors do
       
   308 						if _G.sm.goalGears[goal.collectors[c]] == collector then
       
   309 							return true
       
   310 						end
       
   311 					end
       
   312 					-- Fail if the crate was collected by someone who was not supposed to get it
       
   313 					return "fail", loc("The wrong hedgehog has taken the crate.")
       
   314 				end
       
   315 			else
       
   316 				-- Fail goal if crate was destroyed
       
   317 				if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
       
   318 					return "fail", loc("A crate critical to this mission has been destroyed.")
       
   319 				end
       
   320 				return false
       
   321 			end
       
   322 		elseif goal.type == "turns" then
       
   323 			return sm.gameTurns >= goal.turns
       
   324 		elseif goal.type == "rounds" then
       
   325 			return (TotalRounds) >= goal.rounds
       
   326 		elseif goal.type == "inZone" then
       
   327 			if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
       
   328 				return "fail", _G.sm.criticalGearFailText(goal.id)
       
   329 			end
       
   330 			local gX, gY = GetGearPosition(_G.sm.goalGears[goal.id])
       
   331 			-- 4 sub-goals, each optional
       
   332 			local g1 = (not goal.xMin) or gX >= goal.xMin
       
   333 			local g2 = (not goal.xMax) or gX <= goal.xMax
       
   334 			local g3 = (not goal.yMin) or gY >= goal.yMin
       
   335 			local g4 = (not goal.yMax) or gY <= goal.yMax
       
   336 			return g1 and g2 and g3 and g4
       
   337 		elseif goal.type == "distGearPos" or goal.type == "distGearGear" then
       
   338 			local gX, gY, tX, tY
       
   339 			if goal.type == "distGearPos" then
       
   340 				if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
       
   341 					-- Fail if gear was destroyed
       
   342 					return "fail", _G.sm.criticalGearFailText(goal.id)
       
   343 				end
       
   344 				gX, gY = GetGearPosition(_G.sm.goalGears[goal.id])
       
   345 				tX, tY = goal.x, goal.y
       
   346 			elseif goal.type == "distGearGear" then
       
   347 				-- Fail if one of the gears was destroyed
       
   348 				if getGearValue(_G.sm.goalGears[goal.id1], "sm_destroyed") then
       
   349 					return "fail", _G.sm.criticalGearFailText(goal.id1)
       
   350 				elseif getGearValue(_G.sm.goalGears[goal.id2], "sm_destroyed") then
       
   351 					return "fail", _G.sm.criticalGearFailText(goal.id2)
       
   352 				end
       
   353 				gX, gY = GetGearPosition(_G.sm.goalGears[goal.id1])
       
   354 				tX, tY = GetGearPosition(_G.sm.goalGears[goal.id2])
       
   355 			end
       
   356 
       
   357 			local h = integerHypotenuse(gX - tX, gY - tY)
       
   358 			if goal.relationship == "smallerThan" then
       
   359 				return h < goal.distance
       
   360 			elseif goal.relationship == "greaterThan" then
       
   361 				return h > goal.distance
       
   362 			end
       
   363 			-- Invalid parameters!
       
   364 			error("SimpleMission: Invalid parameters for distGearPos/distGearGear!")
       
   365 			errord = true
       
   366 			return false
       
   367 		elseif goal.type == "suddenDeath" then
       
   368 			return sm.isInSuddenDeath
       
   369 		elseif goal.type == "damage" then
       
   370 			local damage = goal.damage or 1
       
   371 			local gear = _G.sm.goalGears[goal.id]
       
   372 			local tookEnoughDamage = getGearValue(gear, "sm_maxDamage") >= damage
       
   373 			if getGearValue(gear, "sm_destroyed") then
       
   374 				-- Fail if gear was destroyed without taking enough damage first
       
   375 				if not tookEnoughDamage and goal.canDestroy == false then
       
   376 					if GetGearType(gear) == gtHedgehog then
       
   377 						return "fail", string.format(loc("%s has been killed before taking enough damage first."), GetHogName(gear))
       
   378 					else
       
   379 						return "fail", loc("An object has been destroyed before it took enough damage.")
       
   380 					end
       
   381 				else
       
   382 				-- By default, succeed if gear was destroyed
       
   383 					return true
       
   384 				end
       
   385 			end
       
   386 			return tookEnoughDamage
       
   387 		elseif goal.type == "drown" then
       
   388 			local drowned = getGearValue(_G.sm.goalGears[goal.id], "sm_drowned")
       
   389 			-- Fail if gear was destroyed by something other than drowning
       
   390 			if not drowned and getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
       
   391 				return "fail", _G.sm.criticalGearFailText(goal.id)
       
   392 			end
       
   393 			return drowned
       
   394 		elseif goal.type == "poison" then
       
   395 			if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
       
   396 				return "fail", _G.sm.criticalGearFailText(goal.id)
       
   397 			end
       
   398 			return GetEffect(_G.sm.goalGears[goal.id], hePoisoned) >= 1
       
   399 		elseif goal.type == "freeze" then
       
   400 			if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
       
   401 				return "fail", _G.sm.criticalGearFailText(goal.id)
       
   402 			end
       
   403 			return GetEffect(_G.sm.goalGears[goal.id], heFrozen) >= 256
       
   404 		elseif goal.type == "cure" then
       
   405 			if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
       
   406 				return "fail", _G.sm.criticalGearFailText(goal.id)
       
   407 			end
       
   408 			return GetEffect(_G.sm.goalGears[goal.id], hePoisoned) == 0
       
   409 		elseif goal.type == "melt" then
       
   410 			if getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
       
   411 				return "fail", _G.sm.criticalGearFailText(goal.id)
       
   412 			end
       
   413 			return GetEffect(_G.sm.goalGears[goal.id], heFrozen) == 0
       
   414 		elseif goal.type == "waterSkip" then
       
   415 			local skips = goal.skips or 1
       
   416 			local hasEnoughSkips = getGearValue(_G.sm.goalGears[goal.id], "sm_waterSkips") >= skips
       
   417 			-- Fail if gear was destroyed before it got the required number of skips
       
   418 			if not hasEnoughSkips and getGearValue(_G.sm.goalGears[goal.id], "sm_destroyed") then
       
   419 				return "fail", _G.sm.criticalGearFailText(goal.id)
       
   420 			end
       
   421 			return hasEnoughSkips
       
   422 		elseif goal.type == "teamDefeat" then
       
   423 			return #teamHogs[goal.teamName] == 0
       
   424 		else
       
   425 			return false
       
   426 		end
       
   427 	end
       
   428 
       
   429 	--[[ Checks the custom goals.
       
   430 	Returns true when all custom goals are met.
       
   431 	Returns false when not all custom goals are met.
       
   432 	Returns "fail" if any of the goals has failed (i.e. is impossible to complete).
       
   433 	Returns nil when there are no custom goals ]]
       
   434 	_G.sm.checkGoals = function()
       
   435 		if params.customGoals ~= nil and #params.customGoals > 0 then
       
   436 			for key, goal in pairs(params.customGoals) do
       
   437 				local done, defaultFailText = _G.sm.checkGoal(goal)
       
   438 				if done == false or done == "fail" then
       
   439 					local failText
       
   440 					if goal.failText then
       
   441 						failText = goal.failText
       
   442 					else
       
   443 						failText = customFailText
       
   444 					end
       
   445 					return done, failText
       
   446 				end
       
   447 			end
       
   448 			return true
       
   449 		else
       
   450 			return nil
       
   451 		end
       
   452 	end
       
   453 
       
   454 	--[[ Checks the custom non-goals.
       
   455 	Returns true when any non-goal is met.
       
   456 	Returns false otherwise. ]]
       
   457 	_G.sm.checkNonGoals = function()
       
   458 		if params.customNonGoals ~= nil and #params.customNonGoals > 0 then
       
   459 			for key, nonGoal in pairs(params.customNonGoals) do
       
   460 				local done = _G.sm.checkGoal(nonGoal)
       
   461 				if done == true then
       
   462 					return true, nonGoal.failText
       
   463 				end
       
   464 			end
       
   465 		end
       
   466 		return false
       
   467 	end
       
   468 
       
   469 	-- Declare the game ended if all enemy teams are dead and player teams or allies are still alive
       
   470 	_G.sm.checkRegularVictory = function()
       
   471 		local victory = true
       
   472 		for t=0, TeamsCount-1 do
       
   473 			local team = GetTeamName(t)
       
   474 			local defeat = _G.sm.checkGoal({type="teamDefeat", teamName=team})
       
   475 			if not defeat then
       
   476 				-- Deep check, also look at damage of all hogs
       
   477 				local dead = 0
       
   478 				for h=1, #teamHogs[team] do
       
   479 					local _,_,_,_,_,_,_,_,_,_,_,Damage = GetGearValues(teamHogs[team][h])
       
   480 					if Damage >= GetHealth(teamHogs[team][h]) then
       
   481 						dead = dead + 1
       
   482 					end
       
   483 				end
       
   484 				if dead >= #teamHogs[team] then
       
   485 					defeat = true
       
   486 				end
       
   487 			end
       
   488 			if (defeat == true) and (GetTeamClan(team) == _G.sm.playerClan) then
       
   489 				victory = false
       
   490 				break
       
   491 			elseif (defeat == false) and (GetTeamClan(team) ~= _G.sm.playerClan) then
       
   492 				victory = false
       
   493 				break
       
   494 			end
       
   495 		end
       
   496 		if victory then
       
   497 			_G.sm.gameEnded = true
       
   498 		end
       
   499 	end
       
   500 
       
   501 	-- Checks goals and non goals and wins or loses mission
       
   502 	_G.sm.checkWinOrFail = function()
       
   503 		if errord then
       
   504 			return
       
   505 		end
       
   506 		local nonGoalStatus, nonGoalFailText = _G.sm.checkNonGoals()
       
   507 		local goalStatus, goalFailText = _G.sm.checkGoals()
       
   508 		if nonGoalStatus == true then
       
   509 			_G.sm.lose(nonGoalFailText)
       
   510 		elseif goalStatus == "fail" then
       
   511 			_G.sm.lose(goalText)
       
   512 		elseif goalStatus == true then
       
   513 			_G.sm.win()
       
   514 		end
       
   515 	end
       
   516 
       
   517 	_G.sm.win = function()
       
   518 		if not _G.sm.gameEnded then
       
   519 			_G.sm.gameEnded = true
       
   520 			if not _G.sm.wonVarWritten then
       
   521 				SaveMissionVar("Won", "true")
       
   522 				_G.sm.wonVarWritten = true
       
   523 			end
       
   524 			AddCaption(loc("Victory!"), capcolDefault, capgrpGameState)
       
   525 			SendStat(siGameResult, loc("Mission succeeded!"))
       
   526 			_G.sm.makeStats(_G.sm.playerClan)
       
   527 			EndGame()
       
   528 			if GetHogLevel(CurrentHedgehog) == 0 then
       
   529 				for team, hog in pairs(teamHogs[GetHogTeamName(CurrentHedgehog)]) do
       
   530 					SetState(hog, gstWinner)
       
   531 					PlaySound(sndVictory, hog)
       
   532 				end
       
   533 			end
       
   534 		end
       
   535 	end
       
   536 
       
   537 	_G.sm.lose = function(failReason)
       
   538 		if not _G.sm.gameEnded then
       
   539 			_G.sm.gameEnded = true
       
   540 			AddCaption(loc("Mission failed!"), capcolDefault, capgrpGameState)
       
   541 			SendStat(siGameResult, loc("Mission failed!"))
       
   542 			if failReason then
       
   543 				SendStat(siCustomAchievement, failReason)
       
   544 			end
       
   545 			if GetHogLevel(CurrentHedgehog) == 0 then
       
   546 				SetState(CurrentHedgehog, bor(GetState(CurrentHedgehog), gstLoser))
       
   547 				SetState(CurrentHedgehog, band(GetState(CurrentHedgehog), bnot(gstHHDriven)))
       
   548 			end
       
   549 			local clan = ClansCount-1
       
   550 			for t=0, TeamsCount-1 do
       
   551 				local team = GetTeamName(t)
       
   552 				-- Just declare any living team other than the player team the winner
       
   553 				if (_G.sm.checkGoal({type="teamDefeat", teamName=team}) == false) and (GetTeamClan(team) ~= _G.sm.playerClan) then
       
   554 					clan = GetTeamClan(team)
       
   555 					break
       
   556 				end
       
   557 			end
       
   558 			_G.sm.makeStats(clan)
       
   559 			EndGame()
       
   560 		end
       
   561 	end
       
   562 
       
   563 	_G.onSuddenDeath = function()
       
   564 		_G.sm.isInSuddenDeath = true
       
   565 	end
       
   566 
       
   567 	_G.onGearWaterSkip = function(gear)
       
   568 		increaseGearValue(gear, "sm_waterSkips")
       
   569 	end
       
   570 
       
   571 	_G.onGearAdd = function(gear)
       
   572 		if GetGearType(gear) == gtHedgehog then
       
   573 			local team = GetHogTeamName(gear)
       
   574 			if teamHogs[team] == nil then
       
   575 				teamHogs[team] = {}
       
   576 			end
       
   577 			table.insert(teamHogs[GetHogTeamName(gear)], gear)
       
   578 		end
       
   579 		setGearValue(gear, "sm_waterSkips", 0)
       
   580 		setGearValue(gear, "sm_maxDamage", 0)
       
   581 		setGearValue(gear, "sm_drowned", false)
       
   582 		setGearValue(gear, "sm_destroyed", false)
       
   583 	end
       
   584 
       
   585 	_G.onGearResurrect = function(gear)
       
   586 		if GetGearType(gear) == gtHedgehog then
       
   587 			table.insert(teamHogs[GetHogTeamName(gear)], gear)
       
   588 		end
       
   589 		setGearValue(gear, "sm_destroyed", false)
       
   590 	end
       
   591 
       
   592 	_G.onGearDelete = function(gear)
       
   593 		if GetGearType(gear) == gtCase and band(GetGearMessage(gear), gmDestroy) ~= 0 then
       
   594 			-- Set ID of collector
       
   595 			setGearValue(gear, "sm_collected", CurrentHedgehog)
       
   596 		end
       
   597 		if GetGearType(gear) == gtHedgehog then
       
   598 			local team = GetHogTeamName(gear)
       
   599 			local hogList = teamHogs[team]
       
   600 			for h=1, #hogList do
       
   601 				if hogList[h] == gear then
       
   602 					table.remove(hogList, h)
       
   603 					break
       
   604 				end
       
   605 			end
       
   606 		end
       
   607 		if band(GetState(gear), gstDrowning) ~= 0 then
       
   608 			setGearValue(gear, "sm_drowned", true)
       
   609 		end
       
   610 		setGearValue(gear, "sm_destroyed", true)
       
   611 	end
       
   612 
       
   613 	_G.onGearDamage = function(gear, damage)
       
   614 		local currentDamage = getGearValue(gear, "sm_maxDamage")
       
   615 		if damage > currentDamage then
       
   616 			setGearValue(gear, "sm_maxDamage", damage)
       
   617 		end
       
   618 	end
       
   619 
       
   620 	_G.onGameInit = function()
       
   621 		CaseFreq = 0
       
   622 		WaterRise = 0
       
   623 		HealthDecrease = 0
       
   624 		MinesNum = 0
       
   625 		Explosives = 0
       
   626 
       
   627 		for initVarName, initVarValue in pairs(params.initVars) do
       
   628 			if initVarName == "GameFlags" then
       
   629 				EnableGameFlags(initVarValue)
       
   630 			else
       
   631 				_G[initVarName] = initVarValue
       
   632 			end
       
   633 		end
       
   634 		if #params.teams == 1 then
       
   635 			EnableGameFlags(gfOneClanMode)
       
   636 		end
       
   637 		if params.wind then
       
   638 			EnableGameFlags(gfDisableWind)
       
   639 		end
       
   640 
       
   641 		local clanCounter = 0
       
   642 		for teamID, teamData in pairs(params.teams) do
       
   643 			local name, clanID, grave, fort, voice, flag
       
   644 			name = def(teamData.name, string.format(loc("Team %d"), teamID))
       
   645 			if teamData.clanID == nil then
       
   646 				clanID = clanCounter
       
   647 				clanCounter = clanCounter + 1
       
   648 			else
       
   649 				clanID = teamData.clanID
       
   650 			end
       
   651 
       
   652 			local realName
       
   653 			if teamData.isMissionTeam then
       
   654 				realName = AddMissionTeam(-(clanID+1))
       
   655 				_G.sm.playerClan = clanID
       
   656 			else
       
   657 				grave = def(teamData.grave, defaultGraves[math.min(teamID, 8)])
       
   658 				fort = def(teamData.fort, "Castle")
       
   659 				voice = def(teamData.voice, "Default_qau")
       
   660 				flag = def(teamData.flag, defaultFlags[math.min(teamID, 8)])
       
   661 
       
   662 				realName = AddTeam(name, -(clanID+1), grave, fort, voice, flag)
       
   663 			end
       
   664 
       
   665 			-- Update all teamDefeat goals if the real team name differs from the
       
   666 			-- team configuration.
       
   667 			-- (AddTeam might change the name due to naming collisions)
       
   668 			if name ~= realName then
       
   669 				local checks = { params.customGoals, params.customNonGoals }
       
   670 				for c=1, 2 do
       
   671 					if checks[c] then
       
   672 						for k,goal in pairs(checks[c]) do
       
   673 							if goal.type == "teamDefeat" and goal.teamName == name then
       
   674 								goal.teamName = realName
       
   675 							end
       
   676 						end
       
   677 					end
       
   678 				end
       
   679 			end
       
   680 
       
   681 			for hogID, hogData in pairs(teamData.hogs) do
       
   682 				local name, botLevel, health, hat
       
   683 				name = def(hogData.name, string.format(loc("Hog %d"), hogID))
       
   684 				botLevel = def(hogData.botLevel, 0)
       
   685 				health = def(hogData.health, 100)
       
   686 				hat = def(hogData.hat, "NoHat")
       
   687 				local hog
       
   688 				if teamData.isMissionTeam then
       
   689 					hog = AddMissionHog(health)
       
   690 				else
       
   691 					hog = AddHog(name, botLevel, health, hat)
       
   692 				end
       
   693 				if hogData.x ~= nil and hogData.y ~= nil then
       
   694 					SetGearPosition(hog, hogData.x, hogData.y)
       
   695 				end
       
   696 				if hogData.faceLeft then
       
   697 					HogTurnLeft(hog, true)
       
   698 				end
       
   699 				if hogData.poisoned == true then
       
   700 					SetEffect(hog, hePoisoned, 5)
       
   701 				elseif type(hogData.poisoned) == "number" then
       
   702 					SetEffect(hog, hePoisoned, hogData.poisoned)
       
   703 				end
       
   704 				if hogData.frozen then
       
   705 					SetEffect(hog, heFrozen, 199999)
       
   706 				end
       
   707 
       
   708 				if hog ~= nil and hogData.id ~= nil then
       
   709 					_G.sm.goalGears[hogData.id] = hog
       
   710 					setGearValue(hog, "sm_id", hogData.id)
       
   711 				end
       
   712 
       
   713 				-- Remember this hedgehog's gear ID for later use
       
   714 				hogData.gearID = hog
       
   715 			end
       
   716 		end
       
   717 	end
       
   718 
       
   719 	_G.onNewTurn = function()
       
   720 		_G.sm.gameStarted = true
       
   721 
       
   722 		if params.customGoalCheck == "turnStart" then
       
   723 			_G.sm.checkRegularVictory()
       
   724 			_G.sm.checkWinOrFail()
       
   725 		end
       
   726 	end
       
   727 
       
   728 	_G.onEndTurn = function()
       
   729 		_G.sm.gameTurns = _G.sm.gameTurns + 1
       
   730 
       
   731 		if params.customGoalCheck == "turnEnd" then
       
   732 			_G.sm.checkRegularVictory()
       
   733 			_G.sm.checkWinOrFail()
       
   734 		end
       
   735 	end
       
   736 
       
   737 	_G.onGameResult = function(winningClan)
       
   738 		if (params.customGoals == nil) and (not _G.sm.wonVarWritten) and (winningClan == _G.sm.playerClan) then
       
   739 			SendStat(siGameResult, loc("Mission succeeded!"))
       
   740 			SaveMissionVar("Won", "true")
       
   741 			_G.sm.wonVarWritten = true
       
   742 		else
       
   743 			SendStat(siGameResult, loc("Mission failed!"))
       
   744 		end
       
   745 	end
       
   746 
       
   747 	_G.onAmmoStoreInit = function()
       
   748 		local ammoTypesDone = {}
       
   749 		-- Read script's stated ammo wishes
       
   750 		if params.ammoConfig ~= nil then
       
   751 			for ammoType, v in pairs(params.ammoConfig) do
       
   752 				SetAmmo(ammoType, def(v.count, 0), def(v.probability, 0), def(v.delay, 0), def(v.numberInCrate, 1))
       
   753 				ammoTypesDone[ammoType] = true
       
   754 			end
       
   755 		end
       
   756 		-- Apply default values for all ammo types which have not been set
       
   757 		for a=0, AmmoTypeMax do
       
   758 			if a ~= amNothing and ammoTypesDone[a] ~= true then
       
   759 				local count = 0
       
   760 				if a == amSkip then
       
   761 					count = 9
       
   762 				end
       
   763 				SetAmmo(a, count, 0, 0, 1)
       
   764 			end
       
   765 		end
       
   766 	end
       
   767 
       
   768 	_G.onGameStart = function()
       
   769 		-- Mention mines timer
       
   770 		if MinesTime ~= 3000 and MinesTime ~= nil then 
       
   771 			if MinesTime < 0 then
       
   772 				params.goalText = params.goalText .. "|" .. loc("Mines time: 0s-5s")
       
   773 			elseif (MinesTime % 1000) == 0 then
       
   774 				params.goalText = params.goalText .. "|" .. string.format(loc("Mines time: %ds"), MinesTime/1000)
       
   775 			elseif (MinesTime % 100) == 0 then
       
   776 				params.goalText = params.goalText .. "|" .. string.format(loc("Mines time: %.1fs"), MinesTime/1000)
       
   777 			else
       
   778 				params.goalText = params.goalText .. "|" .. string.format(loc("Mines time: %.2fs"), MinesTime/1000)
       
   779 			end
       
   780 		end
       
   781 		if params.wind then
       
   782 			SetWind(params.wind)
       
   783 		end
       
   784 		ShowMission(params.missionTitle, loc("Scenario"), params.goalText, params.missionIcon, 5000) 
       
   785 
       
   786 		-- Spawn objects
       
   787 
       
   788 		if params.gears ~= nil then
       
   789 			for listGearID, gv in pairs(params.gears) do
       
   790 				local timer, state, x, y, dx, dy
       
   791 				local g
       
   792 				state = 0
       
   793 				if gv.type == gtMine then
       
   794 					if gv.isFrozen then
       
   795 						state = gstFrozen
       
   796 					end
       
   797 					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))
       
   798 					if gv.isDud then
       
   799 						SetHealth(g, 0)
       
   800 						if gv.health ~= nil then
       
   801 							SetGearValues(g, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 36 - gv.health)
       
   802 						end
       
   803 					end
       
   804 				elseif gv.type == gtSMine then
       
   805 					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))
       
   806 				elseif gv.type == gtAirMine then
       
   807 					if gv.isFrozen then
       
   808 						state = gstFrozen
       
   809 					end
       
   810 					local timer = def(gv.timer, div(MinesTime, 1000) * 250)
       
   811 					g = AddGear(def(gv.x,0), def(gv.y,0), gv.type, state, def(gv.dx,0), def(gv.dy,0), timer)
       
   812 					SetGearValues(g, nil, nil, timer) -- WDTimer
       
   813 				elseif gv.type == gtExplosives then
       
   814 					if gv.isRolling then
       
   815 						state = gsttmpFlag
       
   816 					end
       
   817 					g = AddGear(def(gv.x,0), def(gv.y,0), gv.type, state, def(gv.dx,0), def(gv.dy,0), 0)
       
   818 					if gv.health then
       
   819 						SetHealth(g, gv.health)
       
   820 					end
       
   821 					if gv.isFrozen ~= nil then
       
   822 						if gv.isFrozen == true then
       
   823 							SetState(g, bor(GetState(g, gstFrozen)))
       
   824 						end
       
   825 					elseif GetHealth(g) > 60 then
       
   826 						SetState(g, bor(GetState(g, gstFrozen)))
       
   827 					end
       
   828 				elseif gv.type == gtCase then
       
   829 					local x, y, spawnTrick
       
   830 					spawnTrick = false
       
   831 					x = def(gv.x, 0)
       
   832 					y = def(gv.y, 0)
       
   833 					if x==0 and y==0 then
       
   834 						x=1
       
   835 						y=1
       
   836 						spawnTrick = true
       
   837 					end
       
   838 					g = AddGear(x, y, gv.type, 0, def(gv.dx,0), def(gv.dy,0), 0)
       
   839 					if spawnTrick then
       
   840 						SetGearPosition(g, 0, 0)
       
   841 					end
       
   842 					if gv.crateType == "supply" then
       
   843 						g = SpawnSupplyCrate(def(gv.x, 0), def(gv.y, 0), gv.ammoType)
       
   844 					elseif gv.crateType == "supply_ammo_explicit" then
       
   845 						g = SpawnAmmoCrate(def(gv.x, 0), def(gv.y, 0), gv.ammoType)
       
   846 					elseif gv.crateType == "supply_utility_explicit" then
       
   847 						g = SpawnUtilityCrate(def(gv.x, 0), def(gv.y, 0), gv.ammoType)
       
   848 					elseif gv.crateType == "health" then
       
   849 						g = SpawnHealthCrate(def(gv.x, 0), def(gv.y, 0))
       
   850 						if gv.health ~= nil then
       
   851 							SetHealth(g, gv.health)
       
   852 						end
       
   853 					end
       
   854 					if gv.isFrozen then
       
   855 						SetState(g, bor(GetState(g, gstFrozen)))
       
   856 					end
       
   857 				elseif gv.type == gtKnife or gv.type == gtTarget then
       
   858 					g = AddGear(def(gv.x,0), def(gv.y,0), gv.type, 0, def(gv.dx,0), def(gv.dy,0), 0)
       
   859 				end
       
   860 				if g ~= nil and gv.id ~= nil then
       
   861 					_G.sm.goalGears[gv.id] = g
       
   862 					setGearValue(g, "sm_id", gv.id)
       
   863 				end
       
   864 			end
       
   865 		end
       
   866 
       
   867 		-- Spawn girders and rubbers
       
   868 		if params.girders ~= nil then
       
   869 			for i, girderData in pairs(params.girders) do
       
   870 				PlaceGirder(girderData.x, girderData.y, girderData.frameIdx)
       
   871 			end
       
   872 		end
       
   873 		if params.rubbers ~= nil then
       
   874 			for i, rubberData in pairs(params.rubbers) do
       
   875 				PlaceSprite(rubberData.x, rubberData.y, sprAmRubber, capcolDefault, rubberData.frameIdx, false, false, false, lfBouncy)
       
   876 			end
       
   877 		end
       
   878 
       
   879 		-- Per-hedgehog ammo loadouts
       
   880 		for teamID, teamData in pairs(params.teams) do
       
   881 			for hogID, hogData in pairs(teamData.hogs) do
       
   882 				if hogData.ammo ~= nil then
       
   883 					for ammoType, count in pairs(hogData.ammo) do
       
   884 						AddAmmo(hogData.gearID, ammoType, count)
       
   885 					end
       
   886 				end
       
   887 			end
       
   888 		end
       
   889 	end
       
   890 
       
   891 	_G.onGameTick20 = function()
       
   892 		if params.customGoalCheck == "instant" then
       
   893 			_G.sm.checkWinOrFail()
       
   894 		end
       
   895 	end
       
   896 
       
   897 end
       
   898