|
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 |