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