Version 22 + update readmes
[Hedgewars_Game_Hacks.git] / Game_Hacks_v22.lua
blob09958ddac5cefbac1b1475f257e3ed147b65c505
1 --[[
2 Game Hacks
4 This is a style which allows you to manipulate over 100 different aspects of Hedgewars
5 using the script parameter in the game scheme.
7 Read the Game_Hacks_README_v22.txt file to learn how it works.
9 This file is licensed under the MIT License.
12 HedgewarsScriptLoad("/Scripts/Locale.lua")
13 HedgewarsScriptLoad("/Scripts/Params.lua")
14 HedgewarsScriptLoad("/Scripts/Tracker.lua")
16 -- Save several gears
17 local birdyGear = nil
18 local rcPlaneGear = nil
19 local saucerGear = nil
20 local kamikazeGear = nil
21 local freezerGear = nil
22 local planeGears = {}
23 local beeGears = {}
24 local mineGears = {}
25 local ropeGear = nil
26 local portalGears = {}
27 local teleporterGear = nil
28 local dynaGears = {}
29 local healthCaseGearsTodo = {} -- List of health crates for which the health still needs to be set
30 local hogGears = {}
31 local pickHammerGear = nil
32 local hogsLeftSide = {} --[[ Stores the initial “side” of each hog (true = left, false = right), used for forcefield.
33 In this mode, the hegehogs must stay on their side for the rest of the game. ]]
34 local poisonCloudGears = {}
35 local tardisGears = {}
36 local specialJumpingGear = nil -- Save gear for “no jumping” mode
38 -- Serveral status variables
39 local gameStarted = false
40 local placeHogsPhase = nil -- Currently in hog placement phase?
41 local setAirPlaneHealth = false
42 local hogCounter = 0
43 local turnsCounter = 0 -- Number of turns played
44 local roundWind -- Used for fair wind
45 local lastRound -- TotalRounds value for last turn
46 local sdState = 0 -- Save Sudden Death state (0=not active, 1=active)
47 local girderPlaced -- Used for strategic tools
48 local teams = {} -- Table indexed by team names, values are number of hogs (at start of game)
49 local poisonKillDone = false -- Has the “poison kills” modifier been applied in this turn yet?
50 local donors = {} -- Table of hogs which are about to “donate”
51 local donorBoxes = {} -- Table of donor boxes, values are tables which contain ammo counts
52 local flaggedGrave -- Remember a grave for 1 tick, used by donor boxes
53 local hogNumbers = {}
54 local lastConstruction -- Remember last placed girder or rubber. Used for girder/rubber range
55 local rangeCircle -- Used for seduction and resurrector
56 local manualTimer -- Used to save the last manual timer used by the user
57 local setWeaponMessage
58 local releaseShotInNextTurn -- Temporary variable for max. launching power
59 local stuffBuilt = 0 -- How many constructions, rubbers and landsprays have been used this turn (used for constructionlimit)
60 local extraDamageUsed = false -- Whether extra damage has been used in this turn
61 local landCenter = nil -- x coordinate of the center of the land, used for forcefield
62 local centerBuffer = 12 -- forcefield: Hedgehogs must stay this far away from the center
63 local wdGameTicks = 0 -- Helper variable for gravity reset: Stores GameTime
64 local wdTTL = 0 -- Helper variable for gravity reset: Stores TurnTimeLeft
66 local tmpSwitchCount
67 local tmpSelectedAmmoType
69 -- List of weapons that have a power meter; used by maxPower
70 -- Longterm TODO: Keep this up-to-date for new Hedgewars releases
71 local powerWeapons = { amGrenade, amClusterBomb, amBazooka, amBee, amWatermelon,
72 amHellishBomb, amDrill, amMolotov, amGasBomb, amSMine, amSnowball, amKnife,
73 amAirMine }
75 -- More variables grabbed from the engine
76 local cMaxWindSpeed_QWORD = 1073742
77 local cMaxPower = 1500
79 --[[ Parser helpers ]]
81 local INT_MAX = 2147483647
83 function minmax(n, min, max)
84 if forceLimits then
85 if max and min and max ~= "inf" then
86 return math.min(max, math.max(min, n))
87 elseif max and max ~= "inf" then
88 return math.min(max, n)
89 elseif min then
90 return math.max(min, n)
91 else
92 return n
93 end
94 else
95 -- This happens if the “unreasonable values” protection is off
96 return n
97 end
98 end
100 function parseRange(key, str, unitParser, ...)
101 if str == nil then return nil end
102 local minval, maxval
103 local subMin, subMax = string.match(str, "([^-]+)-([^-]+)")
104 if subMin ~= nil and subMax ~= nil then
105 minval = unitParser(key, subMin, ...)
106 maxval = unitParser(key, subMax, ...)
107 else
108 minval = unitParser(key, str, ...)
109 maxval = minval
111 if minval ~= nil and maxval ~= nil then
112 if minval > maxval then
113 minval = nil
114 maxval = nil
117 return minval, maxval
121 function parsePercentOrNumber(key, str, reference, min, max)
122 if str == nil then return nil end
124 local n
125 min = min or 0
126 max = max or INT_MAX
127 if str == "inf" or str == "i" then
128 return "inf"
129 elseif str == "min" then
130 return min
131 elseif str == "max" then
132 return max
133 elseif string.sub(str, string.len(str), string.len(str)) == "%" then
134 local substr = string.sub(str, 1, string.len(str)-1)
135 n = truncStrNum(substr)
136 if type(n) == "number" then
137 local ret = div(n * reference, 100)
138 return minmax(ret, min, max)
139 else
140 return nil
142 else
143 n = truncStrNum(str)
144 if type(n) == "number" then
145 return minmax(n, min, max)
146 else
147 return nil
152 --[[
153 - Leave original value if last characters are “ms”
154 - Multiply by 1000 if last character is “s”
155 - Leave original value if raw number was supplied
156 - min and max apply to raw number ]]
157 function parseTime(key, str, min, max)
158 if str == nil then return nil end
160 local n
161 local len = string.len(str)
162 min = min or 0
163 max = max or INT_MAX
164 -- infinite time
165 if str == "inf" or str == "i" then
166 return "inf"
167 -- minimum
168 elseif str == "min" then
169 return min
170 -- maximum
171 elseif str == "max" then
172 return max
173 -- milliseconds
174 elseif string.sub(str, len-1, len) == "ms" then
175 local substr = string.sub(str, 1, len-2)
176 n = truncStrNum(substr)
177 -- seconds
178 elseif string.sub(str, len, len) == "s" then
179 local substr = string.sub(str, 1, len-1)
180 local prePoint, postPoint = string.match(substr, "(%d*)%.(%d*)")
181 if postPoint == nil then
182 prePoint = string.match(substr, "(%d*)")
183 postPoint = "000"
185 prePoint = tonumber(prePoint)
187 if prePoint == nil then
188 return nil
191 local postPointN = {}
193 for i=1,3 do
194 local subN = string.sub(postPoint, i, i)
195 if tonumber(subN) ~= nil then
196 postPointN[i] = tonumber(subN)
197 else
198 postPointN[i] = 0
202 n = prePoint * 1000
204 for i=1,3 do
205 n = n + postPointN[i] * math.pow(10, 3-i)
210 if type(n) ~= "number" then return nil end
211 -- raw number (interpreted as milliseconds)
212 else
213 n = truncStrNum(str)
215 if type(n) ~= "number" then
216 return nil
218 return minmax(n, min, max)
221 function parseTimeRange(key, str, min, max)
222 return parseRange(key, str, parseTime, min, max)
225 function parseInt(key, str, min, max)
226 min = min or 0
227 max = max or INT_MAX
228 if str == "inf" or str == "i" then
229 return "inf"
230 elseif str == "min" then
231 return min
232 elseif str == "max" then
233 return max
234 else
235 local n = truncStrNum(str)
236 if n ~= nil then
237 return minmax(n, min, max)
238 else
239 return nil
244 function parseIntRange(key, str, min, max)
245 return parseRange(key, str, parseInt, min, max)
248 function parseHex(key, str, min, max)
249 if str == nil then return nil end
250 local s = string.match(str, "(%x*)")
251 if s == nil then return nil end
252 local n = tonumber("0x"..str)
253 if str == "inf" or str == "i" then
254 return "inf"
255 elseif type(n) == "number" then
256 return minmax(n, min, max)
257 else
258 return nil
262 function parseBool(key, str, default)
263 if str == "true" or str == "t" then
264 return true
265 elseif str == "false" or str == "f" then
266 return false
267 else
268 return default
272 function multiParse(keys, parseFunction, ...)
273 for i=1,#keys do
274 local result = parseFunction(keys[i], params[keys[i]], ...)
275 if result ~= nil then
276 return parseFunction(keys[i], params[keys[i]], ...)
279 return nil
282 function onParameters()
283 parseParams()
285 --[[ DEVELOPER NOTE:
286 Remove a free letter (for the short form) from this list when you introduce a new parameter with a 1-letter short form.
287 Free letters:
288 v, V, i
291 -- Script config parameters first
292 forceLimits = multiParse({"forcelimits", "Z"}, parseBool, true)
293 hedgePot = multiParse({"hedgepot", "H"}, parseBool)
295 -- Now for all the other params
297 if params["minetimer"] == "random" or params["8"] == "random" or params["minetimer"] == "r" or params["8"] == "r" then
298 mineTimerMin = 0
299 mineTimerMax = 5000
300 else
301 mineTimerMin, mineTimerMax = multiParse({"minetimer", "8"}, parseTimeRange, 0)
303 if params["stickyminetimer"] == "random" or params["s"] == "random" or params["stickyminetimer"] == "r" or params["s"] == "r" then
304 stickyMineTimerMin = 0
305 stickyMineTimerMax = 3000
306 else
307 stickyMineTimerMin, stickyMineTimerMax = multiParse({"stickyminetimer", "s"}, parseTimeRange, 0)
309 if params["airminetimer"] == "random" or params["Y"] == "random" or params["airminetimer"] == "r" or params["Y"] == "r" then
310 airMineTimerMin = 0
311 airMineTimerMax = 1300
312 else
313 airMineTimerMin, airMineTimerMax = multiParse({"airminetimer", "Y"}, parseTimeRange, 0)
315 if params["flamemode"] == "off" or params["flamemode"] == "normal" or params["flamemode"] == "sticky" or params["flamemode"] == "short" then
316 flameMode = params["flamemode"]
317 elseif params["ef"] == "off" or params["ef"] == "normal" or params["ef"] == "sticky" or params["ef"] == "short" then
318 flameMode = params["ef"]
321 healthCaseAmountMin, healthCaseAmountMax = multiParse({"healthcratehealth", "eh"}, parseIntRange, 0)
322 maxCrateDrops = multiParse({"maxcratedrops", "eC"}, parseInt, 0, 500)
323 sturdyCrates = multiParse({"sturdycrates", "ec"}, parseBool)
325 gravity = multiParse({"gravity", "g"}, parsePercentOrNumber, 100, 1, 1000)
326 -- FIXME: Health is not set to this value when frozen
327 dudMineHealth = multiParse({"dudminehealth", "wu"}, parsePercentOrNumber, 36, 1)
328 barrelHealthMin, barrelHealthMax = multiParse({"barrelhealth", "b"}, parseIntRange, 1)
329 showMineTimer = multiParse({"showminetimer", "im"}, parseBool)
330 extraTime = multiParse({"extratime", "e"}, parseTime, 1000)
331 saucerFuel = multiParse({"saucerfuel", "f"}, parsePercentOrNumber, 2000, 1, "inf")
332 birdyEnergy = multiParse({"birdyenergy", "B"}, parsePercentOrNumber, 2000, 1, "inf")
333 landsprayFuel = multiParse({"landsprayfuel", "L"}, parsePercentOrNumber, 1000, 1)
334 freezerFuel = multiParse({"freezerfuel", "u"}, parsePercentOrNumber, 1000, 1)
335 flamethrowerFuel = multiParse({"flamethrowerfuel", "F"}, parsePercentOrNumber, 500, 1)
336 rcPlaneTimer = multiParse({"rcplanetimer", "C"}, parseTime, 1, "inf")
337 rcPlaneBombs = multiParse({"rcplanebombs", "R"}, parseInt, 0, "inf")
338 birdyEggs = multiParse({"birdyeggs", "E"}, parseInt, 0, "inf")
339 pianoBounces = multiParse({"pianobounces", "O"}, parseInt, 1)
340 ballGunBalls = multiParse({"ballgunballs", "U"}, parseInt, 1)
341 cakeTimer = multiParse({"caketimer", "c"}, parsePercentOrNumber, 2048, 1)
342 kamikazeRange = multiParse({"kamikazerange", "K"}, parsePercentOrNumber, 2048, 0)
343 kamikazeTrigger = multiParse({"kamikazetrigger", "wk"}, parseBool)
344 blowTorchTimer = multiParse({"blowtorchtimer", "T"}, parseTime, 1, "inf")
345 pickHammerTimer = multiParse({"pickhammertimer", "I"}, parseTime, 1, "inf")
346 planeDrops = multiParse({"planedrops", "4"}, parseInt, 1)
347 airAttackBombs = multiParse({"airattackbombs", "A"}, parseInt, 1) or planeDrops
348 napalmBombs = multiParse({"napalmbombs", "N"}, parseInt, 1) or planeDrops
349 mineStrikeMines = multiParse({"minestrikemines", "M"}, parseInt, 1) or planeDrops
350 drillStrikeDrills = multiParse({"drillstrikedrills", "D"}, parseInt, 1) or planeDrops
351 planeDropGap = multiParse({"planedropgap", "2"}, parsePercentOrNumber, 30, 1)
352 airAttackGap = multiParse({"airattackgap", "wA"}, parsePercentOrNumber, 30, 1) or planeDropGap
353 napalmGap = multiParse({"napalmgap", "wN"}, parsePercentOrNumber, 30, 1) or planeDropGap
354 mineStrikeGap = multiParse({"minestrikegap", "wM"}, parsePercentOrNumber, 30, 1) or planeDropGap
355 drillStrikeGap = multiParse({"drillstrikegap", "wD"}, parsePercentOrNumber, 30, 1) or planeDropGap
356 napalmBombTimer = multiParse({"napalmbombtimer", "tN"}, parseTime, 0)
357 -- Maximum is dynamically determined on generation
358 girders = multiParse({"girders", "G"}, parseInt, 0)
359 ready = multiParse({"ready", "r"}, parseTime, 0, 999000)
360 airFlamesHurt = multiParse({"airflameshurt", "a"}, parseBool)
361 maxWind = multiParse({"maxwind", "w"}, parsePercentOrNumber, 100, 0, 100)
362 lowGravity = multiParse({"lowgravity", "l"}, parsePercentOrNumber, 100, 1, 1000)
363 noJumping = multiParse({"nojump", "j"}, parseBool)
364 noSnow = multiParse({"nosnow", "Q"}, parseBool)
365 -- TODO: Make force field work for more than 2 clan colors
366 -- TODO: Make compatible with wrap world edge
367 -- TODO: Make compatible with Time Box
368 -- FIXME: Hog might get stuck if parachuting into force field
369 forceField = multiParse({"forcefield", "ei"}, parseBool)
370 portalDistance = multiParse({"portaldistance", "tP"}, parseInt, 0, "inf")
371 strategicTools = multiParse({"strategictools", "z"}, parseBool)
372 maxPower = multiParse({"maxpower", "hm"}, parseBool)
373 shoppaBorder = multiParse({"shoppaborder", "o"}, parseBool)
374 poison = multiParse({"poison", "p"}, parseBool)
375 epidemic = multiParse({"epidemic", "he"}, parseBool)
376 poisonDamage = multiParse({"poisondamage", "P"}, parseInt, 1)
377 poisonKills = multiParse({"poisonkills", "n"}, parseBool)
378 teamCure = multiParse({"teamcure", "t"}, parseBool)
379 shareHealth = multiParse({"sharehealth", "h"}, parseBool)
380 davidAndGoliath = multiParse({"davidandgoliath", "d"}, parseBool)
381 donorBox = multiParse({"donorbox", "q"}, parseBool)
382 fairWind = multiParse({"fairwind", "W"}, parseBool)
383 noKnock = multiParse({"noknock", "k"}, parseBool)
384 sdOneHP = multiParse({"sd1hp", "1"}, parseBool)
385 sdPoison = multiParse({"sdpoison", "S"}, parseBool)
386 poisonCloudTimer = multiParse({"poisoncloudtimer", "tp"}, parseTime, 0, 5000)
387 beeTimer1 = multiParse({"beedelay", "x"}, parseTime, 1)
388 beeTimer2 = multiParse({"beetimer", "y"}, parseTime, 1)
389 -- timeboxnotimer is a legacy parameter which is still unofficially supported;
390 -- users should now use timeboxturns instead
391 tardisReturnEmergencyOnly = multiParse({"timeboxnotimer", "uT"}, parseBool)
392 tardisReturnTurns = multiParse({"timeboxturns", "ut"}, parseInt, 0, "inf")
393 -- Fall-back condition for the legacy parameter timeboxnotimer
394 if(tardisReturnTurns == nil and tardisReturnEmergencyOnly ~= nil) then
395 -- legacy timeboxnotimer is the equivalent of timeboxturns=inf
396 tardisReturnTurns = "inf"
398 pickHammerStraightDown = multiParse({"pickhammerstraightdown", "up"}, parseBool)
399 buildLimit = multiParse({"buildlimit", "ub"}, parseInt, 1, "inf")
401 if params["balltimer"] == "m" or params["m"] == "m" or params["balltimer"] == "manual" or params["m"] == "manual" then
402 ballTimer = "manual"
403 else
404 ballTimer = multiParse({"balltimer", "m"}, parseTime, 1)
406 deagleStrength = multiParse({"deaglestrength", "wd"}, parsePercentOrNumber, 50, 0, 100000)
407 sniperStrength = multiParse({"sniperstrength", "ws"}, parsePercentOrNumber, 50, 0, 100000)
408 hammerStrength = multiParse({"hammerstrength", "wh"}, parsePercentOrNumber, 125, 1)
409 if params["hellishtimer"] == "m" or params["6"] == "m" or params["hellishtimer"] == "manual" or params["6"] == "manual" then
410 hhgTimer = "manual"
411 else
412 hhgTimer = multiParse({"hellishtimer", "6"}, parseTime, 1)
414 if params["dynamitetimer"] == "m" or params["5"] == "m" or params["dynamitetimer"] == "manual" or params["5"] == "manual" then
415 dynamiteTimer = "manual"
416 else
417 dynamiteTimer = multiParse({"dynamitetimer", "5"}, parseTime, 0)
419 airMineFriction = multiParse({"airminefriction", "wf"}, parsePercentOrNumber, div(cMaxWindSpeed_QWORD*3, 2), 0)
420 airMineSeekSpeed = multiParse({"airmineseekspeed", "we"}, parsePercentOrNumber, div(cMaxWindSpeed_QWORD, 2), 0)
421 airMineSeekRange = multiParse({"airmineseekrange", "wr"}, parsePercentOrNumber, 175, 0, "inf")
422 airMineDamage = multiParse({"airminedamage", "da"}, parsePercentOrNumber, 30, 0)
423 if params["drillrockettimer"] == "m" or params["3"] == "m" or params["drillrockettimer"] == "manual" or params["3"] == "manual" then
424 drillRocketTimer = "manual"
425 else
426 drillRocketTimer = multiParse({"drillrockettimer", "3"}, parseTime, 0)
429 -- Parameters for 0.9.22 or later
430 if SetGearFriction ~= nil then
431 hogFriction = multiParse({"hogfriction", "0"}, parseInt, -10, 9989)
433 -- 0.9.23 or later
434 if SetGearValues ~= nil then
435 -- Damage
436 mineDamage = multiParse({"minedamage", "d8"}, parsePercentOrNumber, 50, 0, 3000)
437 stickyMineDamage = multiParse({"stickyminedamage", "dt"}, parsePercentOrNumber, 30, 0, 3000)
438 grenadeDamage = multiParse({"grenadedamage", "dg"}, parsePercentOrNumber, 50, 0, 3000)
439 hhgDamage = multiParse({"hellishdamage", "dH"}, parsePercentOrNumber, 90, 0, 3000)
440 clusterBombDamage = multiParse({"clusterbombdamage", "dl"}, parsePercentOrNumber, 20, 0, 3000)
441 clusterDamage = multiParse({"clusterdamage", "dL"}, parsePercentOrNumber, 25, 0, 3000)
442 melonDamage = multiParse({"melondamage", "dm"}, parsePercentOrNumber, 75, 0, 3000)
443 melonPieceDamage = multiParse({"melonpiecedamage", "dM"}, parsePercentOrNumber, 75, 0, 3000)
444 ballDamage = multiParse({"balldamage", "dU"}, parsePercentOrNumber, 40, 0, 3000)
445 bazookaDamage = multiParse({"bazookadamage", "db"}, parsePercentOrNumber, 50, 0, 3000)
446 mortarDamage = multiParse({"mortardamage", "do"}, parsePercentOrNumber, 20, 0, 3000)
447 beeDamage = multiParse({"beedamage", "dx"}, parsePercentOrNumber, 50, 0, 3000)
448 dynamiteDamage = multiParse({"dynamitedamage", "d5"}, parsePercentOrNumber, 75, 0, 3000)
449 cakeDamage = multiParse({"cakedamage", "dc"}, parsePercentOrNumber, 75, 0, 3000)
450 batDamage = multiParse({"batdamage", "dB"}, parsePercentOrNumber, 30, 0)
451 shoryukenDamage = multiParse({"shoryukendamage", "dF"}, parsePercentOrNumber, 30, 0)
452 whipDamage = multiParse({"whipdamage", "dw"}, parsePercentOrNumber, 30, 0)
453 rcPlaneDamage = multiParse({"rcplanedamage", "dr"}, parsePercentOrNumber, 25, 0, 3000)
454 cleaverDamage = multiParse({"cleaverdamage", "dv"}, parsePercentOrNumber, 40000, 0, 3000)
455 eggDamage = multiParse({"eggdamage", "dE"}, parsePercentOrNumber, 10, 0, 3000)
456 pianoDamage = multiParse({"pianodamage", "dp"}, parsePercentOrNumber, 80, 0, 3000)
457 limburgerDamage = multiParse({"limburgerdamage", "di"}, parsePercentOrNumber, 20, 0, 3000)
458 deagleDamage = multiParse({"deagledamage", "dd"}, parsePercentOrNumber, 7, 0)
459 minigunDamage = multiParse({"minigundamage", "dG"}, parsePercentOrNumber, 2, 0)
460 shotgunDamage = multiParse({"shotgundamage", "dO"}, parsePercentOrNumber, 25, 0)
461 sniperDamage = multiParse({"sniperdamage", "ds"}, parsePercentOrNumber, 100000, 0)
462 sineDamage = multiParse({"sinedamage", "dS"}, parsePercentOrNumber, 35, 0)
463 kamikazeDamage = multiParse({"kamikazedamage", "dK"}, parsePercentOrNumber, 30, 0)
464 airBombDamage = multiParse({"airbombdamage", "dA"}, parsePercentOrNumber, 30, 0, 3000)
465 drillRocketDamage = multiParse({"drillrocketdamage", "d3"}, parsePercentOrNumber, 50, 0, 3000)
466 drillStrikeDrillDamage = multiParse({"drillstrikedrilldamage", "dD"}, parsePercentOrNumber, 30, 0, 3000)
467 blowTorchDamage = multiParse({"blowtorchdamage", "dT"}, parsePercentOrNumber, 2, 0)
468 pickHammerDamage = multiParse({"pickhammerdamage", "dI"}, parsePercentOrNumber, 6, 0)
469 hammerDamage = multiParse({"hammerdamage", "dj"}, parseInt, 1)
470 hammerExtraDamage = multiParse({"hammerextradamage", "dJ"}, parseInt, 1)
471 flameDamage = multiParse({"flamedamage", "df"}, parsePercentOrNumber, 2, 0, 500)
472 hogDamage = multiParse({"hogdamage", "dh"}, parsePercentOrNumber, 30, 0, 3000)
473 crateDamage = multiParse({"cratedamage", "dC"}, parsePercentOrNumber, 25, 0, 3000)
474 barrelDamage = multiParse({"barreldamage", "de"}, parsePercentOrNumber, 75, 0, 3000)
476 -- Misc.
477 mudballPush = multiParse({"mudballstrength", "wm"}, parsePercentOrNumber, 200000, 0, 20000000)
478 hogFreeze = multiParse({"hogfreeze", "hf"}, parsePercentOrNumber, 199999, 0)
479 sineStrength = multiParse({"sinestrength", "wS"}, parseInt, 1, 10000)
480 resurrectorRange = multiParse({"resurrectorrange", "9"}, parsePercentOrNumber, 100, 0, 40000)
481 seductionRange = multiParse({"seductionrange", "7"}, parsePercentOrNumber, 250, 0, 40000)
482 ropeOpacity = multiParse({"ropeopacity", "xR"}, parsePercentOrNumber, 255, 0, 255)
483 if params["ropecolor"] == "clan" or params["xr"] == "clan" then
484 ropeColor = "clan"
485 else
486 ropeColor = multiParse({"ropecolor", "xr"}, parseHex, 0x0, 0xFFFFFF)
489 buildRange = multiParse({"buildrange", "J"}, parsePercentOrNumber, 256, 1, "inf")
492 function onGameInit()
493 -- Build desciption text
494 local goalArray = {}
496 -- Activate the hedgepot
497 local hpModWeaps, hpTexts
498 if hedgePot == true then
499 hpModWeaps, hpTexts = hedgepot()
500 -- Text comes later
504 -- Most significant game hacks first
505 if gravity ~= nil and gravity ~= 100 then
506 if gravity > 100 then
507 table.insert(goalArray, string.format(loc("High gravity: Base gravity is %d%%."), math.floor(gravity)))
508 else
509 table.insert(goalArray, string.format(loc("Low gravity: Base gravity is %d%%."), math.floor(gravity)))
511 DisableGameFlags(gfLowGravity)
513 if strategicTools == true then
514 table.insert(goalArray, loc("Strategic tools: Construction, rubber, mud ball, land spray and resurrector ends your turn."))
515 SetAmmoTexts(amGirder, nil, nil, nil, false)
516 SetAmmoTexts(amRubber, nil, nil, nil, false)
517 SetAmmoTexts(amSnowball, nil, nil, nil, false)
518 SetAmmoTexts(amLandGun, nil, nil, nil, false)
519 SetAmmoTexts(amResurrector, nil, nil, nil, false)
520 DisableGameFlags(gfInfAttack)
522 if buildLimit == 1 then
523 table.insert(goalArray, string.format(loc("Slow builder: You may use only one construction, rubber or land spray per turn."), buildLimit))
524 elseif buildLimit ~= nil and buildLimit ~= "inf" then
525 table.insert(goalArray, string.format(loc("Slow builder: You may use construction, rubber and land spray up to %d time(s) per turn."), buildLimit))
527 if forceField == true then
528 table.insert(goalArray, loc("Force field: Hedgehogs can not enter the opposite side of the terrain."))
529 if WorldEdge == weWrap then
530 WorldEdge = weNone
533 if maxPower == true then
534 table.insert(goalArray, loc("Maximum launching power: Weapons will always be fired with full power."))
536 if buildRange == "inf" then
537 if SetMaxBuildDistance ~= nil then
538 SetMaxBuildDistance(0)
540 table.insert(goalArray, loc("Construction site: Girders and rubber can be placed without range limits."))
542 if noJumping == true then
543 table.insert(goalArray, loc("No jumping: Hedgehogs can't jump."))
545 if noKnock == true then
546 table.insert(goalArray, loc("Hogs of Steel: Hedgehogs won't be pushed away by sliding hedgehogs."))
548 if hogFriction ~= nil then
549 if hogFriction >= 9989 then
550 table.insert(goalArray, loc("Extremely sticky hedgehogs: Hedgehogs won't slide at all."))
551 elseif hogFriction > 0 then
552 table.insert(goalArray, loc("Sticky hedgehogs: Hedgehog won't slide as much."))
553 elseif hogFriction < 0 then
554 table.insert(goalArray, loc("Slippery hedgehogs: Hedgehog will slide more easily, watch your step!"))
558 if sturdyCrates == true then
559 table.insert(goalArray, loc("Sturdy crates: Crates can not be destroyed."))
561 if maxCrateDrops ~= nil then
562 MaxCaseDrops = maxCrateDrops
564 if teamCure == true then
565 table.insert(goalArray, loc("TeamCure™: Health crates cure all team mates of poison."))
567 if shareHealth == true then
568 table.insert(goalArray, loc("Health Socialism: Health in crates is shared among team comrades."))
570 if epidemic == true then
571 table.insert(goalArray, loc("Epidemic: Poisoned hedgehogs infect others by touching."))
573 if poisonKills == true then
574 table.insert(goalArray, loc("Lethal poison: Poison can reduce the health to 0."))
576 if flameMode == "off" then
577 table.insert(goalArray, loc("No flames: Flames do not appear"))
578 SetAmmoTexts(amHellishBomb, nil, loc("Forged in the depths of hell"), loc("Unleash hell on your foes by using|this fiendish explosive. Don't get too close to it!|Attack: Hold to shoot with more power"))
579 elseif flameMode == "sticky" then
580 table.insert(goalArray, loc("Long-lived fire: All flames keep burning between turns."))
581 elseif flameMode == "short" then
582 table.insert(goalArray, loc("Short-lived fire: All flames quickly burn out and do not keep burning between turns."))
584 if airFlamesHurt == true and flameMode ~= "off" then
585 table.insert(goalArray, loc("Dangerous falling flames: Flames in mid-air can hurt hedgehogs."))
587 if donorBox == true then
588 if GetGameFlag(gfKing) then
589 table.insert(goalArray, loc("Donor boxes: A dying king leaves a box with all the hog's weapons inside."))
590 else
591 table.insert(goalArray, loc("Donor boxes: The last dying hog of a team leaves a box with all the hog's weapons inside."))
595 -- Weapon/utiliy tweaks later
597 -- Mines timer
598 -- We set MinesTime to 3000 to suppress the engine's message
599 if mineTimerMin == 0 and mineTimerMax == 0 then
600 table.insert(goalArray, loc("Mine timer: Mines explode instantly."))
601 MinesTime = 3000
602 elseif mineTimerMin ~= mineTimerMax then
603 local str
604 if(mineTimerMin % 1000 == 0 and mineTimerMax % 1000 == 0) then
605 str = string.format(loc("Mine timer: Mines explode after %.0f-%.0f seconds."), mineTimerMin/1000, mineTimerMax/1000)
606 else
607 str = string.format(loc("Mine timer: Mines explode after %.1f-%.1f seconds."), mineTimerMin/1000, mineTimerMax/1000)
609 table.insert(goalArray, str)
610 MinesTime = 3000
611 elseif mineTimerMin ~= nil then
612 local str
613 if(mineTimerMin % 1000 == 0 and mineTimerMax % 1000 == 0) then
614 str = string.format(loc("Mine timer: Mines explode after %.0f second(s)."), mineTimerMin/1000)
615 else
616 str = string.format(loc("Mine timer: Mines explode after %.1f second(s)."), mineTimerMin/1000)
618 table.insert(goalArray, str)
619 MinesTime = 3000
621 if stickyMineTimerMin == 0 and stickyMineTimerMax == 0 then
622 table.insert(goalArray, loc("Sticky mine timer: Sticky mines explode instantly."))
623 elseif stickyMineTimerMin ~= stickyMineTimerMax then
624 local str
625 if(stickyMineTimerMin % 1000 == 0 and stickyMineTimerMax % 1000 == 0) then
626 str = string.format(loc("Sticky mine timer: Sticky mines explode after %.0f-%.0f seconds."), stickyMineTimerMin/1000, stickyMineTimerMax/1000)
627 else
628 str = string.format(loc("Sticky mine timer: Sticky mines explode after %.1f-%.1f seconds."), stickyMineTimerMin/1000, stickyMineTimerMax/1000)
630 table.insert(goalArray, str)
631 elseif stickyMineTimerMin ~= nil then
632 local str
633 if(stickyMineTimerMin % 1000 == 0 and stickyMineTimerMax % 1000 == 0) then
634 str = string.format(loc("Sticky mine timer: Sticky mines explode after %.0f second(s)."), stickyMineTimerMin/1000)
635 else
636 str = string.format(loc("Sticky mine timer: Sticky mines explode after %.1f second(s)."), stickyMineTimerMin/1000)
638 table.insert(goalArray, str)
640 if airMineTimerMin == 0 and airMineTimerMax == 0 then
641 table.insert(goalArray, loc("Air mine timer: Air mines explode instantly."))
642 elseif airMineTimerMin ~= airMineTimerMax then
643 local str
644 if(airMineTimerMin % 1000 == 0 and airMineTimerMax % 1000 == 0) then
645 str = string.format(loc("Air mine timer: Air mines explode after %.0f-%.0f seconds."), airMineTimerMin/1000, airMineTimerMax/1000)
646 else
647 str = string.format(loc("Air mine timer: Air mines explode after %.1f-%.1f seconds."), airMineTimerMin/1000, airMineTimerMax/1000)
649 table.insert(goalArray, str)
650 elseif airMineTimerMin ~= nil then
651 local str
652 if(airMineTimerMin % 1000 == 0 and airMineTimerMax % 1000 == 0) then
653 str = string.format(loc("Air mine timer: Air mines explode after %.0f second(s)."), airMineTimerMin/1000)
654 else
655 str = string.format(loc("Air mine timer: Air mines explode after %.1f second(s)."), airMineTimerMin/1000)
657 table.insert(goalArray, str)
659 if kamikazeTrigger then
660 SetAmmoDescriptionAppendix(amKamikaze, loc("Attack: Detonate early"))
662 if saucerFuel == "inf" then
663 SetAmmoDescriptionAppendix(amJetpack, "| |" .. loc("Modifications:") .. " |" .. loc("Infinite fuel."))
664 elseif saucerFuel ~= nil and saucerFuel ~= 2000 then
665 SetAmmoDescriptionAppendix(amJetpack, "| |" .. loc("Modifications:") .. " |" .. string.format(loc("Fuel: %d%%"), round(saucerFuel/20)))
667 if birdyEnergy == "inf" then
668 SetAmmoDescriptionAppendix(amBirdy, "| |" .. loc("Modifications:") .. " |" .. loc("Birdy can fly forever."))
669 elseif birdyEnergy ~= nil then
670 SetAmmoDescriptionAppendix(amBirdy, "| |" .. loc("Modifications:") .. " |" .. string.format(loc("Stamina: %d%%"), round(birdyEnergy/20)))
672 if rcPlaneTimer == "inf" then
673 SetAmmoDescriptionAppendix(amRCPlane, "| |" .. loc("Modifications:") .. " |" .. loc("Infinite fuel."))
674 elseif rcPlaneTimer ~= nil and rcPlaneTimer ~= 15000 then
675 SetAmmoDescriptionAppendix(amRCPlane, "| |" .. loc("Modifications:") .. " |" .. string.format(loc("Flight time: %d second(s)"), round(rcPlaneTimer/1000)))
677 if portalDistance ~= nil and portalDistance ~= "inf" then
678 SetAmmoDescriptionAppendix(amPortalGun, "| |" .. loc("Modifications:") .. " |" .. string.format(loc("Maximum portal distance: %d"), portalDistance))
680 if lowGravity ~= nil and lowGravity ~= 50 then
681 if lowGravity <= 100 then
683 table.insert(goalArray, string.format(loc("Modified low gravity: Low gravity utility modifies the gravity to %d%% of the base gravity."), math.floor(lowGravity)))
684 else
685 table.insert(goalArray, string.format(loc("High gravity utility: Low gravity utility modifies the gravity to %d%% of the base gravity."), math.floor(lowGravity)))
687 DisableGameFlags(gfLowGravity)
689 if extraTime ~= nil and extraTime ~= 30000 then
690 SetAmmoTexts(amExtraTime, nil, nil, string.format(loc("Add %.0f second(s) to your turn time.|Attack: Activate"), extraTime/1000))
693 -- Hedgepot: Random modified property ofbweapon/utility property
694 if hedgePot and hpModWeaps > 0 then
695 -- Assumes that only 1 weapon/utility is changed
696 table.insert(goalArray, string.format(loc("Surprise supplies: A random property was changed: %s"), hpTexts[1]))
699 -- Concatenate the entire array for the final Goals string
700 if #goalArray > 0 then
701 local goalString = ""
702 for i=1,#goalArray do
703 goalString = goalString .. goalArray[i] .. "|"
705 Goals = goalString
708 -- Misc. setup (no messages)
709 if ready ~= nil then
710 Ready = ready
712 if shoppaBorder == true then
713 EnableGameFlags(gfShoppaBorder)
715 if fairWind == true or maxWind ~= nil then
716 -- Disable engine's wind because we want to do it our own way
717 EnableGameFlags(gfDisableWind)
719 if(GetGameFlag(gfPlaceHog)) then
720 placeHogsPhase = true
721 else
722 placeHogsPhase = false
725 -- Set gravity
726 if gravity ~= nil then
727 SetGravity(gravity)
730 trackTeams()
733 function onAmmoStoreInit()
734 for a=0, AmmoTypeMax do
735 -- Ban certain ammos with certain settings
736 if forceField and a == amTardis then
737 SetAmmo(a, 0, 0, 0, 1)
738 elseif flameMode == "off" and (a == amFlamethrower or a == amMolotov or a == amNapalm) then
739 SetAmmo(a, 0, 0, 0, 1)
740 else
741 local count, prob, delay, numberInCrate = GetAmmo(a)
742 SetAmmo(a, count, prob, delay, numberInCrate)
747 function onGearAdd(gear)
748 local gt = GetGearType(gear)
750 -- Gear hacks
751 if gt == gtHedgehog then
752 trackGear(gear)
753 hogGears[gear] = true
755 local teamName = GetHogTeamName(gear)
756 if hogNumbers[teamName] == nil then
757 -- first hog
758 hogNumbers[teamName] = { [1] = gear }
759 else
760 table.insert(hogNumbers[teamName], gear)
762 if teams[teamName] == nil then
763 teams[teamName] = 1
764 else
765 teams[teamName] = teams[teamName] + 1
768 if poison == true then
769 local dmg
770 if poisonDamage == nil then
771 dmg = 5
772 else
773 dmg = poisonDamage
775 SetEffect(gear, hePoisoned, dmg)
778 if noKnocking then
779 disableKnocking(gear)
781 if hogFriction ~= nil then
782 -- This assumes a default friction of 9989
783 local newFriction = GetGearFriction(gear) - hogFriction
784 SetGearFriction(gear, newFriction)
786 if hogDamage ~= nil then
787 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hogDamage)
790 if gt == gtCase then
791 if crateDamage ~= nil then
792 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, crateDamage)
794 -- Health case handling
795 if (healthCaseAmountMin ~= nil and healthCaseAmountMax ~= nil) or (shareHealth) then
796 table.insert(healthCaseGearsTodo, gear)
798 if sturdyCrates then
799 SetState(gear, bor(GetState(gear), gstNoDamage))
802 if gt == gtExplosives then
803 if barrelHealthMin ~= nil and barrelHealthMax ~= nil then
804 local barrelHealth = barrelHealthMin + GetRandom(barrelHealthMax-barrelHealthMin+1)
805 SetHealth(gear, barrelHealth)
806 if barrelHealth > 60 then
807 SetState(gear, bor(GetState(gear), gstFrozen))
810 if barrelDamage ~= nil then
811 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, barrelDamage)
814 if gt == gtShell then
815 if bazookaDamage ~= nil then
816 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, bazookaDamage)
819 if gt == gtMortar then
820 if mortarDamage ~= nil then
821 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mortarDamage)
824 if gt == gtGrenade then
825 if grenadeDamage ~= nil then
826 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, grenadeDamage)
829 if gt == gtClusterBomb then
830 if clusterBombDamage ~= nil then
831 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, clusterBombDamage)
834 if gt == gtCluster then
835 if clusterDamage ~= nil then
836 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, clusterDamage)
839 if gt == gtMine then
840 mineGears[gear] = true
841 if mineTimerMin ~= nil then
842 if mineTimerMin ~= mineTimerMax then
843 SetTimer(gear, mineTimerMin + GetRandom(mineTimerMax-mineTimerMin+1))
844 else
845 SetTimer(gear, mineTimerMin)
848 if mineDamage ~= nil then
849 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mineDamage)
851 if showMineTimer then
852 SetGearValues(gear, nil, nil, nil, nil, nil, 0)
855 if gt == gtSMine then
856 if stickyMineTimerMin ~= nil then
857 if stickyMineTimerMin ~= stickyMineTimerMax then
858 SetTimer(gear, stickyMineTimerMin + GetRandom(stickyMineTimerMax-stickyMineTimerMin+1))
859 else
860 SetTimer(gear, stickyMineTimerMin)
862 elseif stickyMineTimer ~= nil then
863 SetTimer(gear, stickyMineTimer)
865 if stickyMineDamage ~= nil then
866 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, stickyMineDamage)
868 if showMineTimer then
869 SetVisualGearValues(gear, nil, nil, nil, nil, nil, 0)
872 if gt == gtAirMine then
873 if airMineTimerMin ~= nil then
874 local t
875 if airMineTimerMin ~= airMineTimerMax then
876 t = airMineTimerMin + GetRandom(airMineTimerMax-airMineTimerMin+1)
877 else
878 t = airMineTimerMin
880 SetTimer(gear, t)
881 SetGearValues(gear, nil, nil, t) -- WDTimer
883 if airMineSeekSpeed ~= nil or airMineSeekRange ~= nil then
884 local r = airMineSeekRange
885 if airMineSeekRange == "inf" then
886 r = 0xFFFFFFFF
888 SetGearValues(gear, r, airMineSeekSpeed)
890 if airMineFriction ~= nil then
891 SetGearPos(gear, airMineFriction)
893 if airMineDamage ~= nil then
894 SetGearValues(gear, nil, nil, nil, nil, nil, airMineDamage) -- Karma
896 if showMineTimer then
897 SetGearValues(gear, nil, nil, nil, nil, nil, 0)
900 if gt == gtJetpack then
901 saucerGear = gear
902 if type(saucerFuel) == "number" then
903 SetHealth(gear, saucerFuel)
904 elseif saucerFuel == "inf" then
905 SetHealth(gear, JETPACK_FUEL_INFINITE)
908 if gt == gtIceGun then
909 if freezerFuel ~= nil then
910 SetHealth(gear, freezerFuel)
912 freezerGear = gear
914 if gt == gtFlamethrower then
915 if flamethrowerFuel ~= nil then
916 SetHealth(gear, flamethrowerFuel)
919 if gt == gtLandGun then
920 if buildLimit ~= nil and buildLimit ~= "inf" then
921 if stuffBuilt >= buildLimit then
922 -- Destroy land gun immediately if construction limit is exceeded
923 PlaySound(sndDenied)
924 AddCaption(loc("Building limit exceeded!"), 0xFFFFFFFF, capgrpAmmostate)
925 SetHealth(gear, 0)
926 -- This may have reduced ammo by one, so we have to return it
927 local ammo = GetAmmoCount(CurrentHedgehog, amLandGun)
928 if(ammo ~= 100) then
929 AddAmmo(CurrentHedgehog, amLandGun, ammo + 1)
931 else
932 stuffBuilt = stuffBuilt + 1
933 AddCaption(string.format(loc("Buildings left: %d"), buildLimit - stuffBuilt), 0xFFFFFFFF, capgrpAmmostate)
934 if stuffBuilt >= buildLimit then
935 SetMaxBuildDistance(1)
939 if landsprayFuel ~= nil then
940 SetHealth(gear, landsprayFuel)
943 if gt == gtRCPlane then
944 rcPlaneGear = gear
945 if type(rcPlaneTimer) == "number" then
946 SetTimer(gear, rcPlaneTimer)
948 if rcPlaneBombs == "inf" then
949 -- Set RC plane bombs to a arbitrarily large number, but later it will be ensured this stays always above 0
950 SetHealth(gear, 9001)
951 elseif rcPlaneBombs ~= nil then
952 SetHealth(gear, rcPlaneBombs)
954 if rcPlaneDamage ~= nil then
955 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, rcPlaneDamage)
958 if gt == gtAirBomb then
959 if airBombDamage ~= nil then
960 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, airBombDamage)
963 if gt == gtBirdy then
964 birdyGear = gear
965 if type(birdyEnergy) == "number" then
966 SetHealth(gear, birdyEnergy)
967 elseif birdyEnergy == "inf" then
968 SetHealth(gear, BIRDY_ENERGY_INFINITE)
970 if birdyEggs == "inf" then
971 -- Set eggs to a arbitrarily large number, but later it will be ensured this stays always above 0
972 SetFlightTime(gear, 9001)
973 elseif birdyEggs ~= nil then
974 SetFlightTime(gear, birdyEggs)
977 if gt == gtEgg then
978 if eggDamage ~= nil then
979 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, eggDamage)
982 if gt == gtPiano then
983 if pianoBounces ~= nil then
984 SetGearPos(gear, 5 - pianoBounces)
986 if pianoDamage ~= nil then
987 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, pianoDamage)
990 if gt == gtAirAttack then
991 planeGears[gear] = true
992 local planeType = GetState(gear)
993 if planeType == 0 then
994 -- air attack
995 if airAttackBombs ~= nil then
996 SetHealth(gear, airAttackBombs)
998 if airAttackGap ~= nil then
999 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, airAttackGap)
1001 elseif planeType == 1 then
1002 -- mine strike
1003 if mineStrikeMines ~= nil then
1004 SetHealth(gear, mineStrikeMines)
1006 if mineStrikeGap ~= nil then
1007 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mineStrikeGap)
1009 elseif planeType == 2 then
1010 -- napalm
1011 if napalmBombs ~= nil then
1012 SetHealth(gear, napalmBombs)
1014 if napalmGap ~= nil then
1015 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, napalmGap)
1017 elseif planeType == 3 then
1018 -- drill strike
1019 if drillStrikeDrills ~= nil then
1020 SetHealth(gear, drillStrikeDrills)
1022 if drillStrikeGap ~= nil then
1023 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, drillStrikeGap)
1027 if gt == gtNapalmBomb and napalmBombTimer ~= nil then
1028 SetTimer(gear, napalmBombTimer)
1030 if gt == gtBallGun and ballGunBalls ~= nil then
1031 SetTimer(gear, ballGunBalls * 100 - 99)
1033 if gt == gtBall then
1034 if ballTimer ~= nil then
1035 local timer
1036 if type(ballTimer) == "number" then
1037 timer = ballTimer
1038 elseif ballTimer == "manual" then
1039 if manualTimer == nil then
1040 manualTimer = 5
1042 timer = manualTimer*1000
1044 SetTimer(gear, timer)
1046 if ballDamage ~= nil then
1047 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, ballDamage)
1050 if gt == gtSniperRifleShot then
1051 if sniperStrength ~= nil then
1052 SetHealth(gear, sniperStrength)
1054 if sniperDamage ~= nil then
1055 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, sniperDamage)
1058 if gt == gtDEagleShot then
1059 if deagleStrength ~= nil then
1060 SetHealth(gear, deagleStrength)
1062 if deagleDamage ~= nil then
1063 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, deagleDamage)
1066 if gt == gtMinigunBullet then
1067 if minigunDamage ~= nil then
1068 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, minigunDamage)
1071 if gt == gtShotgunShot then
1072 if shotgunDamage ~= nil then
1073 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, shotgunDamage)
1076 if gt == gtHellishBomb then
1077 if hhgTimer ~= nil then
1078 local timer
1079 if type(hhgTimer) == "number" then
1080 timer = hhgTimer
1081 elseif hhgTimer == "manual" then
1082 if manualTimer == nil then
1083 manualTimer = 5
1085 timer = manualTimer*1000
1087 SetTimer(gear, timer)
1089 if hhgDamage ~= nil then
1090 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hhgDamage)
1093 if gt == gtWatermelon then
1094 if melonDamage ~= nil then
1095 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, melonDamage)
1098 if gt == gtMelonPiece then
1099 if melonPieceDamage ~= nil then
1100 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, melonPieceDamage)
1103 if gt == gtDynamite then
1104 if dynamiteTimer ~= nil then
1105 local timer
1106 if type(dynamiteTimer) == "number" then
1107 timer = dynamiteTimer
1108 elseif dynamiteTimer == "manual" then
1109 if manualTimer == nil then
1110 manualTimer = 5
1112 timer = manualTimer*1000
1115 SetTimer(gear, timer)
1116 if timer < 5000 then
1117 SetTag(gear, div(5000-timer, 166))
1120 if dynamiteDamage ~= nil then
1121 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, dynamiteDamage)
1123 dynaGears[gear] = true
1125 if gt == gtGasBomb then
1126 if limburgerDamage ~= nil then
1127 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, limburgerDamage)
1130 if gt == gtPoisonCloud and poisonCloudTimer ~= nil then
1131 if poisonCloudTimer == 0 then
1132 DeleteGear(gear)
1133 else
1134 SetTimer(gear, poisonCloudTimer)
1137 if gt == gtSineGunShot then
1138 if sineStrength ~= nil then
1139 SetGearValues(gear, nil, nil, nil, sineStrength)
1141 if sineDamage ~= nil then
1142 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, sineDamage)
1145 if gt == gtResurrector and resurrectorRange ~= nil then
1146 SetGearValues(gear, nil, nil, nil, resurrectorRange)
1148 if gt == gtSeduction and seductionRange ~= nil then
1149 SetGearValues(gear, nil, nil, nil, seductionRange)
1151 if gt == gtBee then
1152 if beeTimer1 ~= nil then
1153 SetTimer(gear, beeTimer1)
1155 if beeTimer1 ~= nil or beeTimer2 ~= nil then
1156 SetGearPos(gear, 0)
1158 if beeDamage ~= nil then
1159 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, beeDamage)
1161 beeGears[gear] = true
1163 if gt == gtDrill then
1164 -- Is this a normal drill (not from drill strike)?
1165 if band(GetState(gear), gsttmpFlag) == 0 then
1166 if drillRocketTimer ~= nil then
1167 local timer
1168 if type(drillRocketTimer) == "number" then
1169 timer = drillRocketTimer
1170 elseif drillRocketTimer == "manual" then
1171 if manualTimer == nil then
1172 manualTimer = 5
1174 timer = manualTimer*1000
1176 SetTimer(gear, timer)
1178 if drillRocketDamage ~= nil then
1179 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, drillRocketDamage)
1181 else
1182 if drillStrikeDrillDamage ~= nil then
1183 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, drillStrikeDrillDamage)
1187 if gt == gtCake then
1188 if cakeTimer ~= nil then
1189 SetHealth(gear, cakeTimer)
1191 if cakeDamage ~= nil then
1192 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, cakeDamage)
1195 if gt == gtFirePunch then
1196 if shoryukenDamage ~= nil then
1197 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, shoryukenDamage)
1200 if gt == gtWhip then
1201 if whipDamage ~= nil then
1202 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, whipDamage)
1205 if gt == gtShover then
1206 if batDamage ~= nil then
1207 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, batDamage)
1210 if gt == gtKamikaze then
1211 kamikazeGear = gear
1212 if kamikazeRange ~= nil then
1213 SetHealth(gear, kamikazeRange)
1215 if kamikazeDamage ~= nil then
1216 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, kamikazeDamage)
1219 if gt == gtKnife then
1220 if cleaverDamage ~= nil then
1221 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, cleaverDamage)
1224 if gt == gtHammer then
1225 -- Hammer damage
1226 if(extraDamageUsed and hammerExtraDamage ~= nil) then
1227 -- Hammer with Extra Damage
1228 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hammerExtraDamage)
1229 elseif((not extraDamageUsed) and (hammerDamage ~= nil)) then
1230 -- Hammer without Extra Damage
1231 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hammerDamage)
1234 if gt == gtHammerHit and hammerStrength ~= nil then
1235 if hammerStrength == "inf" then
1236 SetTimer(gear, 0)
1237 else
1238 SetTimer(gear, hammerStrength)
1241 if gt == gtPickHammer then
1242 if pickHammerTimer == "inf" then
1243 SetTimer(gear, 0)
1244 elseif pickHammerTimer ~= nil then
1245 SetTimer(gear, pickHammerTimer)
1247 if pickHammerDamage ~= nil then
1248 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, pickHammerDamage)
1250 if pickHammerStraightDown == true then
1251 -- Disallow pickhammer to be moved left and right
1252 SetGearMessage(CurrentHedgehog, band(GetGearMessage(CurrentHedgehog), bnot(gmLeft+gmRight)))
1253 -- Input mask will be restored when pick hammer is deleted
1254 SetInputMask(band(GetInputMask(), bnot(gmLeft+gmRight)))
1256 if forceField == true then
1257 pickHammerGear = gear
1260 if gt == gtBlowTorch then
1261 if blowTorchTimer == "inf" then
1262 SetTimer(gear, 0)
1263 elseif blowTorchTimer ~= nil then
1264 SetTimer(gear, blowTorchTimer)
1266 if blowTorchDamage ~= nil then
1267 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, blowTorchDamage)
1270 if gt == gtGrave and donorBox then
1271 flaggedGrave = gear
1273 if gt == gtGirder then
1274 local ignore = false
1275 if buildLimit ~= nil and buildLimit ~= "inf" then
1276 if stuffBuilt >= buildLimit then
1277 AddCaption(loc("Building limit exceeded!"), 0xFFFFFFFF, capgrpAmmostate)
1278 DeleteGear(gear)
1279 ignore = true
1282 if not ignore then
1283 lastConstruction = gear
1286 if gt == gtFlake and noSnow == true then
1287 -- Get rid of snow flakes
1288 -- But only replace snow flakes, not the flakes from land spray!
1289 if band(GetState(gear), gsttmpFlag) == 0 then
1290 -- Replace snow flake gear with (harmless) visual gear flake
1291 local x, y = GetGearPosition(gear)
1292 local dx, dy = GetGearVelocity(gear)
1293 DeleteGear(gear)
1294 AddVisualGear(x, y, vgtFlake, 0, false)
1297 if gt == gtRope then
1298 -- Rope color and rope opacity
1299 if ropeColor ~= nil or ropeOpacity ~= nil then
1300 -- Mostly just some bitmask stuff (color is in RGBA format)
1301 local opacityMask
1302 if ropeOpacity ~= nil then
1303 opacityMask = bor(0xFFFFFF00, ropeOpacity)
1304 else
1305 opacityMask = 0xFFFFFFFF
1307 local actualColor
1308 if ropeColor == "clan" then
1309 actualColor = band(GetClanColor(GetHogClan(CurrentHedgehog)), opacityMask)
1310 elseif ropeColor ~= nil then
1311 actualColor = band(ropeColor*0x100+0xFF, opacityMask)
1312 else
1313 actualColor = band(0xD8D8D8FF, opacityMask)
1316 -- Set rope to “line” mode (needed for coloization to work)
1317 SetTag(gear, 1)
1318 -- Set rope color
1319 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, actualColor)
1321 ropeGear = gear
1323 if gt == gtPortal then
1324 if forceField then
1325 portalGears[gear] = true
1327 if portalDistance ~= nil and portalDistance ~= "inf" then
1328 trackGear(gear)
1329 setGearValue(gear, "portalDistance", portalDistance)
1332 if gt == gtTardis then
1333 -- Remember the Time Box' gear ID for later use, along with some additional values
1334 tardisGears[gear] = {}
1335 if tardisReturnTurns ~= nil then
1336 tardisGears[gear].turnsLeft = tardisReturnTurns
1339 if gt == gtSnowball then
1340 if strategicTools then
1341 -- Strategic tools: Snowball
1342 strategicToolsEndTurn(3000, false)
1344 if mudballPush ~= nil then
1345 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mudballPush)
1348 if gt == gtTeleport then
1349 if forceField then
1350 teleporterGear = gear
1353 if gt == gtFlame then
1354 -- Remove flames
1355 if flameMode == "off" then
1356 DeleteGear(gear)
1357 else
1358 -- Make all flames sticky
1359 if flameMode == "sticky" then
1360 SetState(gear, bor(GetState(gear), gsttmpFlag))
1361 -- Make all flames non-sticky
1362 elseif flameMode == "short" then
1363 SetState(gear, band(GetState(gear), bnot(gsttmpFlag)))
1365 -- Mid-air flames hurt hedgehogs
1366 if airFlamesHurt == true then
1367 SetFlightTime(gear, 0)
1369 -- Custom flame damage
1370 if flameDamage ~= nil then
1371 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, flameDamage)
1376 -- No jumping mode
1377 if noJumping and (gt == gtJetpack or gt == gtRope or gt == gtParachute) then
1378 specialJumpingGear = gear
1379 SetInputMask(bor(GetInputMask(), gmLJump))
1380 SetInputMask(band(GetInputMask(), bnot(gmHJump)))
1384 function onGearResurrect(gear, vGear)
1385 if forceField then
1386 if(GetGearType(gear) == gtHedgehog) then
1387 if(hogsLeftSide[gear] == true and (GetX(gear) >= landCenter - centerBuffer)) then
1388 FindPlace(gear, false, LeftX, landCenter - centerBuffer)
1389 SetVisualGearValues(vGear, GetX(gear), GetY(gear))
1390 elseif(hogsLeftSide[gear] == false and (GetX(gear) < landCenter + centerBuffer)) then
1391 FindPlace(gear, false, landCenter + centerBuffer, RightX)
1392 SetVisualGearValues(vGear, GetX(gear), GetY(gear))
1398 function onHogRestore(gear)
1399 hogGears[gear] = true
1402 function onGearDelete(gear)
1403 local gt = GetGearType(gear)
1405 -- Clear temp. gear variables
1406 if gt == gtHedgehog then
1407 hogGears[gear] = nil
1408 elseif gt == gtBirdy then
1409 birdyGear = nil
1410 elseif gt == gtRCPlane then
1411 rcPlaneGear = nil
1412 elseif gt == gtJetpack then
1413 saucerGear = nil
1414 elseif gt == gtDynamite then
1415 dynaGears[gear] = nil
1416 elseif gt == gtRope then
1417 ropeGear = nil
1418 elseif gt == gtPortal then
1419 portalGears[gear] = nil
1420 elseif gt == gtAirAttack then
1421 planeGears[gear] = nil
1422 elseif gt == gtTardis then
1423 tardisGears[gear] = nil
1424 elseif gt == gtMine then
1425 mineGears[gear] = nil
1426 elseif gt == gtPickHammer then
1427 pickHammerGear = nil
1428 elseif gt == gtTeleport then
1429 teleporterGear = nil
1430 elseif gt == gtIceGun then
1431 freezerGear = nil
1434 if gt == gtHedgehog then
1435 if donorBox and donors[gear] then
1436 if flaggedGrave ~= nil then
1437 DeleteGear(flaggedGrave)
1438 flaggedGrave = nil
1440 local x, y = GetGearPosition(gear)
1442 local ammos = {}
1443 local ammoTypes = {}
1445 for a=0, AmmoTypeMax do
1446 if a ~= amSkip and a ~= amNothing then
1447 local count = GetAmmoCount(gear, a)
1448 if count > 0 then
1449 ammos[a] = count
1454 local box = SpawnFakeAmmoCrate(x, y, false, false)
1455 donorBoxes[box] = ammos
1457 trackDeletion(gear)
1460 if gt == gtBee and (beeTimer1 ~= nil or beeTimer2 ~= nil) then
1461 beeGears[gear] = nil
1464 -- Collect donor crate and award its contents to the collector
1465 if donorBox and gt == gtCase and donorBoxes[gear] ~= nil then
1466 if band(GetGearMessage(gear), gmDestroy) ~= 0 then
1467 for ammoType, addCount in pairs(donorBoxes[gear]) do
1468 modAmmo(CurrentHedgehog, ammoType, addCount)
1470 PlaySound(sndShotgunReload)
1471 AddCaption(loc("Donor box collected!"), GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
1472 donorBoxes[gear] = nil
1476 -- Health Crate stuff
1477 if (teamCure or shareHealth) and gt == gtCase and band(GetGearPos(gear), 0x2) ~= 0 then
1478 if band(GetGearMessage(gear), gmDestroy) ~= 0 then
1479 -- TeamCure™
1480 if teamCure then
1481 runOnHogsInTeam(function(hog)
1482 SetEffect(hog, hePoisoned, 0)
1483 end, GetHogTeamName(CurrentHedgehog))
1486 -- Health Socialism
1487 if shareHealth then
1488 --[[ Okay, here's how it works:
1489 The health pack is divided by the number of hogs in the
1490 team. The integer result of box health divided by hogs is then
1491 added to each member.
1492 If there is a remainder, it is split up among the hogs this
1493 way: We start counting from the current hedgehog, and give it
1494 another HP. We continue with the next hogs in team order and
1495 give them 1 HP each until the remainder is "used up".
1496 After this algorithm, the entire health pack has been used up.
1498 Examples:
1499 - 50 HP box, 5 team members: 50/10=10, so each gets 10 HP.
1500 - Hog 2 collects a 30 HP box, 4 team members:
1501 30/4 = 7, remainder 2. So:
1502 +8 HP for Hog 2 and Hog 3 (current and next), +7 HP for Hog 1 and Hog 4.
1503 - 1 HP, 8 team members: +1 HP only for current hog.
1507 -- REALLY complicated hack to find out the correct hog order
1508 local teamHogTable = {}
1509 local CurrentHedgehogID
1510 runOnHogsInTeam(function(hog)
1511 -- Skip hidden gears (will not be in hogGears table)
1512 if hogGears[hog] ~= nil then
1513 table.insert(teamHogTable, hog)
1514 if hog == CurrentHedgehog then
1515 CurrentHedgehogID = #teamHogTable
1518 end, GetHogTeamName(CurrentHedgehog))
1520 local newTeamHogTable = { CurrentHedgehog }
1521 for i=#teamHogTable, CurrentHedgehogID+1, -1 do
1522 table.insert(newTeamHogTable, teamHogTable[i])
1524 for i=1, CurrentHedgehogID-1 do
1525 table.insert(newTeamHogTable, teamHogTable[i])
1528 -- Now finally heal some hogs!
1529 -- shareHealth turned all health crates into fake ones,
1530 -- so we do all of the health handling manually.
1532 -- Heal hogs
1533 local boxHealth = GetHealth(gear)
1534 local healthPart = div(boxHealth, #teamHogTable)
1535 local remainder = math.fmod(boxHealth, #teamHogTable)
1537 for i=1, #newTeamHogTable do
1538 local heal
1539 local hog = newTeamHogTable[i]
1540 if i > remainder then
1541 heal = healthPart
1542 else
1543 heal = healthPart + 1
1545 HealHog(hog, heal, false)
1548 -- Sound
1549 PlaySound(sndShotgunReload)
1551 -- Custom health message
1552 local msg
1553 if(boxHealth >= 2 and #teamHogTable >= 2) then
1554 msg = loc("+%d (shared)")
1555 else
1556 msg = loc("+%d")
1558 AddCaption(string.format(msg, boxHealth), GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
1563 -- No jumping mode
1564 if noJumping and (gt == gtJetpack or gt == gtRope or gt == gtParachute) then
1565 specialJumpingGear = nil
1566 SetInputMask(band(GetInputMask(), bnot(gmLJump + gmHJump)))
1569 -- Force pick hammer to dig straight down
1570 if (pickHammerStraightDown or forceField) and gt == gtPickHammer then
1571 -- Enable left and right again for pick hammer
1572 SetInputMask(bor(GetInputMask(), gmLeft+gmRight))
1575 if gt == gtKamikaze then
1576 kamikazeGear = nil
1579 -- Strategic tools: Resurrection and land spray
1580 if strategicTools then
1581 if gt == gtResurrector then
1582 strategicToolsEndTurn(3000, false)
1583 elseif gt == gtLandGun then
1584 strategicToolsEndTurn(4000, false)
1588 -- For some reason using blow torch enables knocking again
1589 if noKnock and gt == gtBlowTorch then
1590 runOnHogs(disableKnocking)
1594 function onHogHide(gear)
1595 hogGears[gear] = nil
1598 function onGameTick20()
1599 if portalDistance ~= nil and portalDistance ~= "inf" then
1600 runOnGears(function(gear)
1601 -- Destroy portal balls after a given distance
1602 local tag = GetTag(gear)
1603 if GetGearType(gear) == gtPortal and tag == 0 or tag == 2 then
1604 local col
1605 if tag == 0 then
1606 col = 0xFAB02AFF
1607 else
1608 col = 0x364DF7FF
1610 local dist = getGearValue(gear, "portalDistance")
1611 dist = dist - 20
1612 if dist <= 0 then
1613 local tempE = AddVisualGear(GetX(gear)+15, GetY(gear), vgtSmoke, 0, true)
1614 SetVisualGearValues(tempE, nil, nil, nil, nil, nil, nil, nil, nil, nil, col)
1616 tempE = AddVisualGear(GetX(gear)-15, GetY(gear), vgtSmoke, 0, true)
1617 SetVisualGearValues(tempE, nil, nil, nil, nil, nil, nil, nil, nil, nil, col)
1619 tempE = AddVisualGear(GetX(gear), GetY(gear)+15, vgtSmoke, 0, true)
1620 SetVisualGearValues(tempE, nil, nil, nil, nil, nil, nil, nil, nil, nil, col)
1622 tempE = AddVisualGear(GetX(gear), GetY(gear)-15, vgtSmoke, 0, true)
1623 SetVisualGearValues(tempE, nil, nil, nil, nil, nil, nil, nil, nil, nil, col)
1625 PlaySound(sndVaporize)
1627 DeleteGear(gear)
1628 else
1629 setGearValue(gear, "portalDistance", dist)
1632 end)
1634 -- Reset saucer/rc plane fuel and birdy energy if infinite
1635 if rcPlaneTimer == "inf" and rcPlaneGear then
1636 SetTimer(rcPlaneGear, 15000)
1639 -- Reset gravity after turn freezes for 15 seconds
1640 if gravity ~= nil or lowGravity ~= nil then
1641 if wdGameTicks + 15000 < GameTime then
1642 SetGravity(100)
1643 else
1644 if(wdTTL ~= TurnTimeLeft) then
1645 wdGameTicks = GameTime
1648 wdTTL = TurnTimeLeft
1651 -- Main bee timer
1652 if beeTimer2 ~= nil then
1653 for bee, _ in pairs(beeGears) do
1654 if GetGearPos(bee) == 0 then
1655 if GetTimer(bee) <= 20 then
1656 SetFlightTime(bee, GetTimer(bee))
1657 SetGearPos(bee, 1)
1659 elseif GetGearPos(bee) == 1 then
1660 SetTimer(bee, beeTimer2)
1661 SetGearPos(bee, 2)
1666 -- Dud mine health
1667 if dudMineHealth ~= nil then
1668 for mine, _ in pairs(mineGears) do
1669 if GetHealth(mine) == 0 then
1670 -- Dud mines explode when Damage is over 35
1671 SetGearValues(mine, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 36 - dudMineHealth) -- Damage
1672 mineGears[mine] = nil
1677 -- Epidemic
1678 if epidemic then
1679 local tdist = 22 -- Touch distance
1680 for hog1, t1 in pairs(hogGears) do
1681 if GetEffect(hog1, hePoisoned) >= 1 and GetHealth(hog1) > 0 then
1682 local x1, y1 = GetGearPosition(hog1)
1683 for hog2, t2 in pairs(hogGears) do
1684 local x2, y2 = GetGearPosition(hog2)
1685 if GetEffect(hog2, hePoisoned) == 0 and GetEffect(hog2, heInvulnerable) == 0 and GetHealth(hog2) > 0 then
1686 if (x2 > x1 - tdist) and (x2 < x1 + tdist) and (y2 > y1 - tdist) and (y2 < y1 + tdist) and GetHealth(hog2) > 0 then
1687 SetEffect(hog2, hePoisoned, GetEffect(hog1, hePoisoned))
1695 -- Custom poison damage
1696 if poisonDamage ~= nil then
1697 runOnHogs(adjustPoisonDamage)
1700 -- Custom freeze level for hogs
1701 if hogFreeze ~= nil then
1702 runOnHogs(adjustFreezeLevel)
1705 -- Kill poisoned
1706 if poisonKills and not poisonKillDone then
1707 if TurnTimeLeft == 0 and CurrentHedgehog ~= nil then
1708 runOnHogs(doPoisonKill)
1709 poisonKillDone = true
1713 -- Time Box timers
1714 if tardisReturnTurns ~= nil then
1715 for tardis, tardisValues in pairs(tardisGears) do
1716 if GetGearPos(tardis) == 4 then -- Time Box is travelling through time
1717 if type(tardisValues.turnsLeft) == "number" and tardisValues.turnsLeft <= 0 then
1718 -- Special case: The turn counter is already at 0, so we can return immediately
1719 SetTimer(tardis, 0)
1720 tardisGears[tardis].turnsLeft = nil
1721 elseif tardisValues.turnsLeft == "inf" then
1722 -- Constantly set the Time Box timer to a high value to make sure it never reaches 0
1723 -- so we can manually trigger the return of the Time Box
1724 SetTimer(tardis, 1000000)
1730 -- Donor boxes
1731 if donorBox then
1732 runOnHogs(checkDonors)
1735 -- Show delayed message for modified weapon timers
1736 if setWeaponMessage then
1737 local curAmmo = GetCurAmmoType()
1738 if (curAmmo == amHellishBomb and hhgTimer == "manual")
1739 or (curAmmo == amDynamite and dynamiteTimer == "manual")
1740 or (curAmmo == amDrill and drillRocketTimer == "manual")
1741 or (curAmmo == amBallgun and ballTimer == "manual") then
1742 if manualTimer == nil then
1743 manualTimer = 5
1745 FakeAmmoinfoCaption(curAmmo)
1746 setWeaponMessage = nil
1750 -- Health crate handling
1751 if healthCaseAmountMin ~= nil or shareHealth then
1752 for h=1, #healthCaseGearsTodo do
1753 local gear = healthCaseGearsTodo[h]
1754 -- Is health crate?
1755 if band(GetGearPos(gear), 0x2) ~= 0 then
1756 -- Random health case contents
1757 if healthCaseAmountMin ~= nil and healthCaseAmountMax ~= nil then
1758 SetHealth(gear, healthCaseAmountMin + GetRandom(healthCaseAmountMax-healthCaseAmountMin+1))
1760 if shareHealth then
1761 -- In shareHealth mode, flag all health crates
1762 -- as fake crate to simplify custom health handling
1763 SetGearPos(gear, bor(GetGearPos(gear), 0x8))
1767 healthCaseGearsTodo = {}
1771 function onGameTick()
1772 if maxPower then
1773 if CurrentHedgehog ~= nil then
1774 if band(GetGearMessage(CurrentHedgehog), gmAttack) ~= 0 then
1775 local at = GetCurAmmoType()
1776 local ok = false
1777 -- Check if this weapon is power-based
1778 for i=1,#powerWeapons do
1779 if at == powerWeapons[i] then
1780 ok = true
1781 break
1784 if ok then
1785 -- Immediately set to full launching power and force-release the attack message in the next tick
1786 SetGearValues(CurrentHedgehog, nil, cMaxPower - 1)
1787 if releaseShotInNextTick then
1788 SetGearMessage(CurrentHedgehog, band(GetGearMessage(CurrentHedgehog), bnot(gmAttack)))
1789 releaseShotInNextTick = false
1790 else
1791 releaseShotInNextTick = true
1798 if strategicTools then
1799 if girderPlaced == true then
1800 girderPlaced = false
1801 strategicToolsEndTurn(4999, true)
1805 if donorBox then
1806 flaggedGrave = nil
1809 -- Force field
1810 if forceField then
1811 if gameStarted and placeHogsPhase == false then
1812 -- Check hedgehogs
1813 for hog, _ in pairs(hogGears) do
1814 local x, y = GetGearPosition(hog)
1815 if(hogsLeftSide[hog] == nil) then
1816 -- Initialize the hogs “side”
1817 hogsLeftSide[hog] = x < landCenter
1818 else
1819 -- Repel hedgehogs
1820 local dx, dy = GetGearVelocity(hog)
1821 if(kamikazeGear ~= nil and hog == CurrentHedgehog) then
1822 -- ... except they do the kamikaze
1823 elseif(hogsLeftSide[hog] == true and x >= landCenter - centerBuffer) then
1824 SetGearPosition(hog, landCenter-centerBuffer - 1, y)
1825 SetGearVelocity(hog, math.min(dx, 0), dy)
1826 elseif(hogsLeftSide[hog] == false and x < landCenter + centerBuffer) then
1827 SetGearPosition(hog, landCenter + centerBuffer, y)
1828 SetGearVelocity(hog, math.max(dx, 0), dy)
1829 if dx < 0 then
1830 HogTurnLeft(hog, true)
1835 -- Check ropes
1836 if ropeGear ~= nil then
1837 -- Destroy ropes going over the center
1838 local x = GetX(ropeGear)
1839 if(hogsLeftSide[CurrentHedgehog] ~= nil and
1840 ((hogsLeftSide[CurrentHedgehog] == true and x >= landCenter - centerBuffer) or
1841 (hogsLeftSide[CurrentHedgehog] == false and x < landCenter + centerBuffer)))
1842 then
1843 SetState(CurrentHedgehog, band(GetState(CurrentHedgehog), bnot(gstAttacking)))
1844 SetGearMessage(CurrentHedgehog, band(GetGearMessage(CurrentHedgehog), bnot(gmAttack)))
1845 DeleteGear(ropeGear)
1849 -- Check portals
1850 for portal, _ in pairs(portalGears) do
1851 -- Destroy all portals going over the center
1852 local x = GetX(portal)
1853 if(hogsLeftSide[CurrentHedgehog] ~= nil and
1854 ((hogsLeftSide[CurrentHedgehog] == true and x >= landCenter - centerBuffer) or
1855 (hogsLeftSide[CurrentHedgehog] == false and x < landCenter + centerBuffer)))
1856 then
1857 DeleteGear(portal)
1861 -- Check pick hammer (not needed if pickhammerstraightdown=true)
1862 if pickHammerGear ~= nil and not pickHammerStraightDown and hogsLeftSide[CurrentHedgehog] ~= nil then
1863 -- Don't let pick hammer dig left/right through the center wall
1864 local x = GetX(pickHammerGear)
1865 if(hogsLeftSide[CurrentHedgehog] == true and x >= landCenter - centerBuffer - 1) then
1866 SetInputMask(band(GetInputMask(), bnot(gmRight)))
1867 SetGearMessage(CurrentHedgehog, band(GetGearMessage(CurrentHedgehog), bnot(gmRight)))
1868 elseif(hogsLeftSide[CurrentHedgehog] == false and x < landCenter + centerBuffer + 1) then
1869 SetInputMask(band(GetInputMask(), bnot(gmLeft)))
1870 SetGearMessage(CurrentHedgehog, band(GetGearMessage(CurrentHedgehog), bnot(gmLeft)))
1871 else
1872 -- Restore input mask when pick hammer moved away from the wall
1873 SetInputMask(bor(GetInputMask(), gmLeft+gmRight))
1876 -- Check teleportation
1877 if teleporterGear ~= nil then
1878 -- Let hogs only teleport within their own half
1879 local x, y = GetGearTarget(teleporterGear)
1880 local hog = CurrentHedgehog
1881 local unsuccess = false
1882 if(hogsLeftSide[hog] == true and x >= landCenter - centerBuffer) then
1883 SetGearTarget(teleporterGear, GetX(hog), GetY(hog))
1884 unsuccess = true
1885 elseif(hogsLeftSide[hog] == false and x < landCenter + centerBuffer) then
1886 SetGearTarget(teleporterGear, GetX(hog), GetY(hog))
1887 unsuccess = true
1889 if unsuccess then
1890 AddCaption(loc("You can not go to the other side!"))
1892 teleporterGear = nil
1896 -- Mark the map center
1897 if(GameTime % 8) == 0 then
1898 local eX, eY, tempE, color
1899 eX = landCenter
1900 eY = GetRandom(LAND_HEIGHT+1)
1901 tempE = AddVisualGear(eX, eY, vgtDust, 0, false)
1902 color = 0x808080FF
1903 if tempE ~= 0 then
1904 SetVisualGearValues(tempE, nil, nil, 0, 0, nil, nil, nil, 1, nil, color)
1909 if dynamiteTimer ~= nil then
1910 for dynaGear, v in pairs(dynaGears) do
1911 if GetTimer(dynaGear) > 5000 then
1912 SetTag(dynaGear, 0)
1917 -- Draw range circles for changed resurrector and seduction ranges
1918 -- TODO: Also remove the original circle as soon the engine permits it (original circles are currently (0.9.21-0.9.22) hardcoded)
1919 local dontDraw = false
1921 if band(GetState(CurrentHedgehog), gstWait+gstAttacking+gstAttacked+gstAnimation+gstHHJumping) == 0 and
1922 band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 and
1923 band(GetGearMessage(CurrentHedgehog), gmLeft+gmRight) == 0 and
1924 ropeGear == nil then
1925 if resurrectorRange ~= nil and GetCurAmmoType() == amResurrector then
1926 if rangeCircle then
1927 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, resurrectorRange, 4, 0xFFFF80EE)
1928 else
1929 rangeCircle = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtCircle, 0, true)
1930 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, resurrectorRange, 4, 0xFFFF80EE)
1932 elseif seductionRange ~= nil and GetCurAmmoType() == amSeduction then
1933 if rangeCircle then
1934 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, seductionRange, 4, 0xFF8080EE)
1935 else
1936 rangeCircle = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtCircle, 0, true)
1937 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, seductionRange, 4, 0xFF8080EE)
1939 else
1940 dontDraw = true
1942 else
1943 dontDraw = true
1945 if dontDraw then
1946 if rangeCircle ~= nil then
1947 DeleteVisualGear(rangeCircle)
1949 rangeCircle = nil
1953 function onGameStart()
1954 -- Place random girders
1955 if girders ~= nil then
1956 local points = {}
1957 -- Will be set to true if we have exceeded the tryLimit once
1958 local dontBotherTrying = false
1959 local buffer = 160
1960 local tryLimit = 500
1961 --[[ The complexitiy of this code is O(n^2) but the algorithm may terminate early
1962 if it fails to validly place a girder ]]
1963 for i=1,girders do
1964 local x, y
1965 -- Try a couple of times to place a girder without being too close to other placed girders.
1966 local tries = 0
1967 while tries < tryLimit do
1968 -- Get a random girder position
1969 tries = tries + 1
1970 x, y = GetRandom(LAND_WIDTH-buffer)+buffer, GetRandom(LAND_HEIGHT-buffer)+buffer
1971 if dontBotherTrying then
1972 --[[ To reduce the complexity, we don't go through the next loop if we have once
1973 exceeded the tryLimit ]]
1974 break
1976 local placementOkay = true
1977 -- Check collisions with gears and land
1978 --[[ NOTE: Remove this line to make girders appear anywhere, even in land and near gears. In combination with
1979 EraseSprite, this can be used to create nice “girder holes”. The HWP file currently contains a legacy sprite
1980 for erasing (custom sprite 1).
1981 This MAY be a nice candidate for another parameter but it might be a bit glitchy ]]
1982 placementOkay = not TestRectForObstacle(x-div(buffer,2), y-div(buffer,2), x+div(buffer,2), y+div(buffer,2), false)
1983 -- Check whether this position it is too close to other girders
1984 if placementOkay then
1985 for p=1,#points do
1986 if math.sqrt(math.pow(math.abs(points[p].x - x), 2) + math.pow(math.abs(points[p].y -y), 2)) < buffer then
1987 placementOkay = false
1988 break
1992 if placementOkay then
1993 break
1996 if tries >= tryLimit then
1997 --[[ If we have once tried too many times, we stop adding girders unless
1998 forceLimits is false. DontBotherTrying is used to kinda reduce the algorithmic
1999 complexity for the following iterations if forceLimits is false ]]
2000 dontBotherTrying = true
2001 if forceLimits then
2002 break
2005 table.insert(points, {x=x, y=y})
2008 -- Place girders
2009 for i=1,#points do
2010 PlaceSprite(points[i].x, points[i].y, sprAmGirder, 0)
2014 -- David and Goliath
2015 if davidAndGoliath then
2016 for teamName, teamMembers in pairs(teams) do
2017 hogCounter = 0
2018 local goliath
2019 local healthForGoliath = 0
2020 runOnHogsInTeam(function(hog)
2021 -- Goliath
2022 if hogCounter == 0 then
2023 goliath = hog
2024 -- David
2025 else
2026 --[[ Remove half of the health (rounded down) ]]
2027 local removedHealth = div(GetHealth(hog), 2)
2028 healthForGoliath = healthForGoliath + removedHealth
2029 SetHealth(hog, GetHealth(hog) - removedHealth)
2031 hogCounter = hogCounter + 1
2032 end, teamName)
2034 -- Add all health to Goliath
2035 SetHealth(goliath, GetHealth(goliath) + healthForGoliath)
2039 -- Prepare no invasion mode
2040 if forceField then
2041 landCenter = div(LAND_WIDTH, 2)
2045 function onHogAttack(ammoType)
2046 if ammoType == amExtraTime then
2047 local n = 30
2048 if extraTime ~= nil then
2049 n = extraTime
2050 SetTurnTimeLeft(math.min(MAX_TURN_TIME, TurnTimeLeft - 30000 + extraTime))
2052 elseif ammoType == amExtraDamage then
2053 extraDamageUsed = true
2054 elseif ammoType == amLowGravity then
2055 if gravity ~= nil or lowGravity ~= nil then
2056 local base = 100
2057 if gravity then
2058 base = gravity
2060 if lowGravity == nil then
2061 lowGravity = 50
2063 SetGravity(div(base * lowGravity, 100))
2068 function FakeAmmoinfoCaption(ammoType)
2069 local ammoCount = GetAmmoCount(CurrentHedgehog, ammoType)
2070 local str
2072 local infStr, finStr
2074 if ammoType == amHellishBomb or ammoType == amDynamite or ammoType == amDrill or ammoType == amBallgun then
2075 finStr = loc("%s (%d), %d sec")
2076 infStr = loc("%s, %d sec")
2079 if ammoCount == 100 then
2080 str = string.format(GetAmmoName(ammoType), infStr, manualTimer)
2081 else
2082 str = string.format(GetAmmoName(ammoType), finStr, GetAmmoCount(CurrentHedgehog, ammoType), manualTimer)
2084 AddCaption(str, GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
2087 function onTimer(timer)
2088 manualTimer = timer
2089 local curAmmo = GetCurAmmoType()
2090 if CurrentHedgehog ~= nil then
2091 if (curAmmo == amHellishBomb and hhgTimer == "manual")
2092 or (curAmmo == amDynamite and dynamiteTimer == "manual")
2093 or (curAmmo == amDrill and drillRocketTimer == "manual")
2094 or (curAmmo == amBallgun and ballTimer == "manual") then
2095 FakeAmmoinfoCaption(curAmmo)
2100 function onSetWeapon()
2101 setWeaponMessage = true
2103 onSlot = onSetWeapon
2105 function onAttack()
2106 if birdyGear ~= nil then
2107 if birdyEggs == "inf" then
2108 -- We kinda “fake” infinity by making sure we never run out of eggs
2109 SetFlightTime(birdyGear, 9001)
2112 if rcPlaneGear ~= nil then
2113 if rcPlaneBombs == "inf" then
2114 -- We kinda “fake” infinity by making sure we never run out of bombs
2115 SetHealth(rcPlaneGear, 9001)
2118 if kamikazeGear ~= nil then
2119 if kamikazeTrigger then
2120 SetHealth(kamikazeGear, 0)
2125 function suddenDeathSpecials()
2126 if sdState ~= 1 then
2127 -- Emulate Sudden Death effect if it is disabled by the game scheme
2128 if HealthDecrease == 0 and WaterRise == 0 and (sdPoison or sdOneHP ) then
2129 AddCaption(loc("Sudden Death!"), capcolDefault, capgrpGameState)
2130 PlaySound(sndSuddenDeath)
2132 if sdPoison then
2133 runOnHogs(function(hog)
2134 if poisonDamage ~= nil then
2135 SetEffect(hog, hePoisoned, poisonDamage)
2136 else
2137 SetEffect(hog, hePoisoned, 5)
2139 end)
2141 if sdOneHP then
2142 runOnHogs(function(hog)
2143 SetHealth(hog, 1)
2144 end)
2146 sdState = 1
2150 onSuddenDeath = suddenDeathSpecials
2152 function onNewTurn()
2153 gameStarted = true
2154 releaseShotInNextTick = false
2155 manualTimer = nil
2156 extraDamageUsed = false
2157 turnsCounter = turnsCounter + 1
2159 if(placeHogsPhase) then
2160 if turnsCounter > #hogGears then
2161 placeHogsPhase = false
2165 if buildLimit ~= nil and buildLimit ~= "inf" then
2166 stuffBuilt = 0
2167 if buildRange == "inf" then
2168 SetMaxBuildDistance(0)
2169 elseif buildRange ~= nil then
2170 SetMaxBuildDistance(buildRange)
2171 else
2172 SetMaxBuildDistance()
2176 -- Set gravity
2177 if gravity ~= nil then
2178 wdGameTicks = GameTime
2179 SetGravity(gravity)
2182 -- No jumping mode
2183 if noJumping then
2184 SetInputMask(band(GetInputMask(), bnot(gmLJump + gmHJump)))
2187 -- Disable knocking
2188 if noKnock then
2189 runOnHogs(disableKnocking)
2192 -- Set gravity
2193 if gravity ~= nil then
2194 SetGravity(gravity)
2197 -- Adjust wind
2198 if fairWind or maxWind ~= nil then
2199 local newWind
2200 if maxWind ~= nil then
2201 if maxWind == 0 then
2202 newWind = 0
2203 else
2204 newWind = GetRandom(maxWind*2+1) - maxWind
2206 else
2207 newWind = GetRandom(201)-100
2210 if fairWind then
2211 if lastRound == nil or lastRound ~= TotalRounds then
2212 SetWind(newWind)
2214 elseif maxWind ~= nil then
2215 SetWind(newWind)
2219 if TotalRounds == SuddenDeathTurns + 1 then
2220 suddenDeathSpecials()
2223 -- Reset “poison kills” modifier for this turn
2224 if poisonKills then
2225 poisonKillDone = false
2228 -- Force-return Tim eBoxes when using turn-based return setting
2229 if tardisReturnTurns ~= nil then
2230 for tardis, tardisValues in pairs(tardisGears) do
2231 if GetGearPos(tardis) == 4 and type(tardisValues.turnsLeft) == "number" then
2232 tardisGears[tardis].turnsLeft = tardisGears[tardis].turnsLeft - 1
2233 if tardisGears[tardis].turnsLeft <= 0 then
2234 SetTimer(tardis, 0)
2235 tardisGears[tardis].turnsLeft = nil
2241 -- Count rounds
2242 lastRound = TotalRounds
2245 function onGirderPlacement(frameIdx, x, y)
2246 if(buildLimit ~= nil and buildLimit ~= "inf") then
2247 stuffBuilt = stuffBuilt + 1
2248 AddCaption(string.format(loc("Buildings left: %d"), buildLimit - stuffBuilt), 0xFFFFFFFF, capgrpAmmostate)
2249 if stuffBuilt >= buildLimit then
2250 SetMaxBuildDistance(1)
2253 if strategicTools then
2254 strategicToolsHandlePlacement()
2258 onRubberPlacement = onGirderPlacement
2260 --[[ Helper functions ]]
2261 function hedgepot()
2262 local default_percent_min = 33
2263 local default_percent_max = 300
2264 local mutators = {
2265 { type = "GameFlag", name = "gfLaserSight" },
2266 { type = "GameFlag", name = "gfInvulnerable" },
2267 { type = "GameFlag", name = "gfResetHealth" },
2268 { type = "GameFlag", name = "gfVampiric" },
2269 { type = "GameFlag", name = "gfKarma" },
2270 { type = "GameFlag", name = "gfSharedAmmo" },
2271 { type = "GameFlag", name = "gfResetWeps" },
2272 { type = "GameFlag", name = "gfPerHogAmmo" },
2273 { type = "GameFlag", name = "gfBottomBorder" },
2274 { type = "GameFlag", name = "gfSwitchHog" },
2275 { type = "CoreNumber", name = "HealthCaseProb", absolute_min = 0, absolute_max = 100 },
2276 { type = "CoreNumber", name = "DamagePercent" },
2277 { type = "CoreNumber", name = "MinesNum" },
2278 { type = "CoreNumber", name = "MineDudPercent", absoulte_min = 0, absolute_max = 100 },
2279 { type = "CoreNumber", name = "Explosives" },
2280 { type = "GameFlag", name = "gfKarma" },
2281 { type = "GameFlag", name = "gfMoreWind" },
2282 { type = "truth", name = "airFlamesHurt" },
2283 { type = "truth", name = "poison" },
2284 { type = "truth", name = "noJump" },
2285 { type = "truth", name = "noKnock" },
2286 { type = "truth", name = "teamCure" },
2287 { type = "truth", name = "shareHealth" },
2288 { type = "truth", name = "davidAndGoliath" },
2289 { type = "truth", name = "strategicTools" },
2290 { type = "truth", name = "sdPoison" },
2291 { type = "truth", name = "sdOneHP" },
2292 { type = "truth", name = "poisonKills" },
2293 { type = "truth", name = "epidemic" },
2294 { type = "truth", name = "maxPower" },
2295 { type = "truth", name = "fairWind" },
2296 { type = "enum", name = "flameMode", enum = { "normal", "sticky", "short", "off" } },
2297 { type = "num", name = "maxWind", absolute_min = 0, absolute_max = 100, default = 100 },
2298 { type = "num", name = "gravity", absolute_min = 10, absolute_max = 200, default = 100 },
2299 { type = "num", name = "poisonDamage", absolute_min = 1, absolute_max = 20, default = 5 },
2300 { type = "num", name = "hogFriction", absolute_min = -10, absolute_max = 10, default = 0 },
2301 { type = "num", name = "buildLimit", absolute_min = 1, absolute_max = 5, default = 2 },
2303 local util_weapons_modifiers =
2305 { type = "truth", name = "kamikazeTrigger" },
2306 { type = "truth", name = "pickHammerStraightDown", text=loc("Pick hammer (no skewing)") },
2307 { type = "enum", name = "MinesTime", enum = { -1000, 0, 1000, 2000, 3000, 4000, 5000 } },
2308 { type = "enum", name = "tardisReturnTurns", enum = { 0, 1, 3, 5, "inf", } },
2309 { type = "set", name = "saucerFuel", value = "inf" },
2310 { type = "set", name = "birdyEnergy", value = "inf" },
2311 { type = "set", name = "birdyEggs", value = "inf", text=loc("Birdy eggs (infinite)") },
2312 { type = "set", name = "rcPlaneBombs", value = "inf", text=loc("RC Plane bombs (infinite)") },
2313 { type = "set", name = "hhgTimer", value = "manual", text=loc("Hellish hand-grenade timer (manual)")},
2314 { type = "set", name = "ballTimer", value = "manual", text=loc("Ball timer (manual)")},
2315 { type = "set", name = "buildRange", value = "inf" },
2316 { type = "num", name = "buildRange", default = 256, text=loc("Construction/Rubber range (%d%%)"), numtype="perc" },
2317 { type = "num", name = "saucerFuel", default = 2000 },
2318 { type = "num", name = "birdyEnergy", default = 2000 },
2319 { type = "num", name = "rcPlaneTimer", default = 15000},
2320 { type = "num", name = "freezerFuel", default = 1000, text=loc("Freezer fuel (%d%%)"), numtype="perc"},
2321 { type = "num", name = "flamethrowerFuel", default = 500, text=loc("Flamethrower fuel (%d%%)"), numtype="perc"},
2322 { type = "num", name = "cakeTimer", default = 2048, text=loc("Cake walking time (%d%%)"), numtype="perc"},
2323 { type = "num", name = "kamikazeRange", default = 2048, text=loc("Kamikaze range (%d%%)"), numtype="perc"},
2324 { type = "num", name = "ballTimer", default = 5000, text=loc("Ball timer (%.1fs)"),numtype="time"},
2325 { type = "num", name = "pickHammerTimer", default = 4000, text=loc("Pickhammer time (%.1fs)"), numtype="time"},
2326 { type = "num", name = "blowTorchTimer", default = 7500, text=loc("Blowtorch time (%.1fs)"), numtype="time"},
2327 { type = "num", name = "birdyEggs", default = 2, text=loc("Birdy eggs (%d)"), numtype="abs"},
2328 { type = "num", name = "rcPlaneBombs", default = 3, text=loc("RC Plane bombs (%d)"), numtype="abs"},
2329 { type = "num", name = "ballGunBalls", default = 51, text=loc("Number of ball gun balls (%d)"), numtype="abs"},
2330 { type = "num", name = "pianoBounces", default = 5, text=loc("Piano terrain bounces (%d)"), numtype="abs"},
2331 { type = "num", name = "airAttackBombs", default = 6, text=loc("Air attack bombs (%d)"), numtype="abs"},
2332 { type = "num", name = "mineStrikeMines", default = 6, text=loc("Mine strike mines (%d)"), numtype="abs"},
2333 { type = "num", name = "drillStrikeDrills", default = 6, text=loc("Drill strike drills (%d)"), numtype="abs"},
2334 { type = "num", name = "deagleStrength", default = 50, text=loc("Desert Eagle digging strength (%d%%)"), numtype="perc"},
2335 { type = "num", name = "sniperStrength", default = 50, text=loc("Sniper rifle digging strength (%d%%)"), numtype="perc"},
2336 { type = "num", name = "hammerStrength", default = 125, text=loc("Hammer digging strength (%d%%)"), numtype="perc"},
2337 { type = "num", name = "extraTime", default = 30000, absolute_min = 15000, absolute_max = 45000 },
2338 { type = "num", name = "lowGravity", default = 50},
2339 { type = "num", name = "drillRocketTimer", default = 5000, text=loc("Drill rocket timer (%.1fs)"), numtype="time"},
2340 { type = "num", name = "hhgTimer", default = 5000, text=loc("Hellish hand-grenade timer (%.1fs)"), numtype="time"},
2342 local conflicts = {
2343 sdPoison = { "sdOneHP" },
2344 sdOneHP = { "sdPoison" },
2345 gfInvulnerable = { "gfVampiric", "gfKarma", "teamCure", "poison", "sdPoison", "shareHealth", "gfResetHealth" },
2346 gfVampiric = { "gfInvulnerable" },
2347 gfKarma = { "gfInvulnerable" },
2348 teamCure = { "gfInvulnerable" },
2349 poison = { "gfInvulnerable" },
2350 sdPoison = { "gfInvulnerable" },
2351 shareHealth = { "gfInvulnverable" },
2352 gfResetHealth = { "gfInulnverable" },
2353 gfArtillery = { "noJump" },
2354 noJump = { "gfArtillery " },
2355 gfSharedAmmo = { "gfPerHogAmmo" },
2356 gfPerHogAmmo = { "gfSharedAmmo" },
2359 local text_counter = 0
2360 local texts = {}
2362 local function roll(reel)
2363 local pickID = GetRandom(#reel)+1
2364 local pick = reel[pickID]
2365 if pick.type == "GameFlag" then
2366 if GetGameFlag(_G[pick.name]) == true then
2367 DisableGameFlags(_G[pick.name])
2368 else
2369 EnableGameFlags(_G[pick.name])
2371 elseif pick.type == "CoreNumber" then
2372 if pick.absolute_min ~= nil and pick.absolute_max ~= nil then
2373 _G[pick.name] = GetRandom(pick.absolute_max - pick.absolute_min + 1) + pick.absolute_min
2374 else
2375 _G[pick.name] = div((GetRandom(default_percent_max - default_percent_min) + default_percent_min) * _G[pick.name], 100)
2377 elseif pick.type == "enum" then
2378 local e = GetRandom(#pick.enum - 1)+1
2379 _G[pick.name] = pick.enum[e]
2380 elseif pick.type == "truth" then
2381 _G[pick.name] = true
2382 elseif pick.type == "num" then
2383 if pick.absolute_min and pick.absolute_max then
2384 _G[pick.name] = GetRandom(pick.absolute_max - pick.absolute_min + 1) + pick.absolute_min
2385 else
2386 _G[pick.name] = div((GetRandom(default_percent_max - default_percent_min) + default_percent_min) * pick.default, 100)
2388 elseif pick.type == "set" then
2389 _G[pick.name] = pick.value
2391 if pick.text ~= nil then
2392 text_counter = text_counter + 1
2393 if pick.type == "num" then
2394 local arg
2395 if pick.numtype == "abs" then
2396 arg = round(_G[pick.name])
2397 elseif pick.numtype == "time" then
2398 arg = _G[pick.name]/1000
2399 else
2400 arg = round(_G[pick.name]/pick.default*100)
2402 table.insert(texts, string.format(pick.text, arg))
2403 elseif pick.type == "set" then
2404 table.insert(texts, string.format(pick.text))
2407 table.remove(reel, pickID)
2410 -- “Roll” the “reels”
2411 local rolls_1 = GetRandom(5)+1
2412 local weapons_mod = GetRandom(100)
2413 local rolls_2 = 0
2415 for i=1,rolls_1 do
2416 roll(mutators)
2418 if weapons_mod < 20 then
2419 roll(util_weapons_modifiers)
2422 return text_counter, texts
2425 -- Functions for strategic tools
2426 function strategicToolsEndTurn(baseRetreatTime, girderUsed)
2427 SetState(CurrentHedgehog, bor(GetState(CurrentHedgehog), gstAttacked))
2428 local retreatTime
2429 retreatTime = div(baseRetreatTime * GetAwayTime, 100)
2430 -- Unselect girder/rubber so the hog can walk again
2431 if girderUsed then
2432 SetWeapon(amNothing)
2434 Retreat(retreatTime)
2437 function strategicToolsHandlePlacement()
2438 girderPlaced = true
2439 strategicToolsEndTurn(5000, true)
2442 -- Handle modified poison damage
2443 function adjustPoisonDamage(hog)
2444 if GetEffect(hog, hePoisoned) > 0 then
2445 if GetEffect(hog, hePoisoned) ~= poisonDamage then
2446 SetEffect(hog, hePoisoned, poisonDamage)
2451 -- Handle modified freeze level for freshly frozen hogs
2452 function adjustFreezeLevel(hog)
2453 if GetEffect(hog, heFrozen) == 199999 and freezerGear == nil then
2454 if getGearValue(hog, "freezeLevelAdjusted") ~= true then
2455 SetEffect(hog, heFrozen, hogFreeze)
2456 setGearValue(hog, "freezeLevelAdjusted", true)
2458 else
2459 setGearValue(hog, "freezeLevelAdjusted", false)
2463 -- Kill poisoned hogs with low health
2464 function doPoisonKill(hog)
2465 local poison = GetEffect(hog, hePoisoned)
2466 local health = GetHealth(hog)
2467 if poison > 0 then
2468 if health - poison <= 0 then
2469 SetHealth(hog, 0)
2474 -- Hogs of Steel mode
2475 function disableKnocking(gear)
2476 SetState(gear, bor(GetState(gear), gstNotKickable))
2479 -- Drop donor box if it was the last hog or the king
2480 function checkDonors(hog)
2481 if band(GetState(hog), gstHHDeath) ~= 0 and band(GetState(hog), gstDrowning) == 0 then
2482 if GetGameFlag(gfKing) and hogNumbers[GetHogTeamName(hog)][1] == hog then
2483 donors[hog] = true
2484 else
2485 hogCounter = 0
2486 runOnHogsInTeam(countHog, GetHogTeamName(hog))
2487 if hogCounter == 1 then
2488 donors[hog] = true
2494 -- Misc.
2495 -- Used for tracking functions to count hogs in a team etc.
2496 function countHog(gear)
2497 hogCounter = hogCounter + 1
2500 function round(num)
2501 if num >= 0 then
2502 return math.floor(num + 0.5)
2503 else
2504 return math.ceil(num - 0.5)
2508 -- Given a time in milliseconds, converts the number into seconds and rounds it up
2509 --[[ Examples:
2510 0 → 0
2511 2000 → 2
2512 500 → 1
2513 999 → 1
2514 1500 → 2
2515 2456 → 3
2517 function timeToNextSec(milliSecs)
2518 return div(milliSecs + 999, 1000)
2521 -- Truncate string input number (safe math.floor(tonumber(x)) alternative)
2522 function truncStrNum(strNum)
2523 if strNum == nil then return nil end
2524 local s = string.match(strNum, "(%d*)")
2525 if s ~= nil then
2526 return tonumber(s)
2527 else
2528 return nil
2532 -- Changes the ammo count of ammoType of the hog
2533 -- It adds addCount to the hog
2534 -- If addCount is 100, the hog gets infinite ammo
2535 -- For finite numbers, the hog can never end up with more than 99 of the same ammo
2536 function modAmmo(hog, ammoType, addCount)
2537 local currentCount = GetAmmoCount(hog, ammoType)
2538 if showMessage == nil then showMessage = true end
2539 if currentCount ~= 100 and addCount ~= 100 then
2540 local newCount
2541 if addCount == 100 then
2542 newCount = 100
2543 else
2544 newCount = math.max(0, math.min(99, currentCount + addCount))
2546 AddAmmo(CurrentHedgehog, ammoType, newCount)