------------------------------------ -- TUMBLER -- v.0.8.0 ------------------------------------ --[[ SCRIPT PARAMETER The script is configured with the script parameter. Additional configuration in the game scheme is permitted. The script parameter is a comma-separated list of key=value pairs. The values are always whole numbers, the keys are listed below. Key Default Description ---------------------------------------------------------------------- spawnbarrels 2 Number of barrels that spawn per turn spawnmines 4 Number of mines that spawn per turn ammoflamer 50 Initial fuel/ammo of Flamer ammobarrel 2 Initial ammo of Barrel Launcher ammomine 1 Initial ammo of Mine Deployer minetimerplaced 1000 Mine timer of mines dropped from Mine Deployer (!) in milliseconds bonustime 25 Bonus time in utility crates, in seconds bonusflames 800 Flamer fuel bonus in ammo crates chanceammo 30 Chance (in %) that an ammo crate will drop before a turn chancetime 50 Chance (in %) that an utility crate (extra time) will drop before a turn EXAMPLES: ammoflamer=800, ammomine=5 --> Starts the game with 800 Flamer fuel and 5 Mine Deployer mines. chancetime=0 --> No clock crates. GAME SCHEME CONFIGURATION The script recognizes most game modifiers and settings, but changing the following game modifiers will have no effect: - Artillery - Tag Team - Shared ammo - Per-hog ammo - Place hogs - Invulnerable - Reset weapons ]] HedgewarsScriptLoad("/Scripts/Locale.lua") HedgewarsScriptLoad("/Scripts/Tracker.lua") HedgewarsScriptLoad("/Scripts/Params.lua") local fMod = 1000000 -- use this for dev and .16+ games local leftOn = false local rightOn = false local upOn = false local downOn = false local preciseOn = false local wep = {} local wepAmmo = {} local wepCol = {} local wepIndex = 0 local wepCount = 0 local fGears = 0 local mineSpawn local barrelSpawn local roundKills = 0 local barrelsEaten = 0 local minesEaten = 0 local moveTimer = 0 local fireTimer = 0 local TimeLeftCounter = 0 local TimeLeft = 0 local stopMovement = false local tumbleStarted = false local vTag = {} local barrelSpawn = 2 local mineSpawn = 4 local initAmmoFlamer = 50 local initAmmoBarrel = 2 local initAmmoMine = 1 local placedMineTime = 1000 local bonusTime = 25 local bonusFlames = 800 local chanceAmmo = 30 local chanceTime = 50 ------------------------ -- version 0.4 ------------------------ -- removed some old code/comments -- removed both shell and mortar as the primary and secondary weapons -- the primary weapon is now an explosive(barrel) -- added support for picking up barrels scattered about the map (backspace) -- added support for dragging around mines (enter toggles on/off) -- added support for primary fire being onAttackUp -- added a trail to indicate when the player has 5s or less left to tumble -- updated showmission to reflect changed controls and options ------------------------ -- version 0.5 ------------------------ -- changed some of the user feedback -- i can't remember?? -- substituted onAttackUp for onPrecise() -- brought in line with new velocity changes ------------------------ -- version 0.6 ------------------------ -- reduced starting "ammo" -- randomly spawn new barrels/mines on new turn -- updated user feedback -- better locs and coloured addcaptions -- added tag for turntime -- removed tractor beam -- added two new weapons and changed ammo handling -- health crates now give tumbler time, and wep/utility give flamer ammo -- explosives AND mines can be picked up to increase their relative ammo -- replaced "no weapon" selected message that hw serves -- modified crate frequencies a bit -- added some simple kill-based achievements, i think ------------------------ -- version 0.7 ------------------------ -- a few code optimisations/performance tweaks -- removed some deprecated code -- fix a potential spawn bug -- improved HUD (now shows ammo counts) -- improved user feedback (less generic messages) -- colour-coded addcaptions to match hud :) -- base tumbling time now equals scheme turntime -- tumbling time extension is now based on the amount of health contained in crate -- new mines per turn based on minesnum -- new barrels per turn based on explosives -- added 2 more achievements: barrel eater and mine eater (like kills, don't do anything atm) -- slightly increased grab distance for explosives/mines -- slightly increased flamer velocity -- slightly decreased flamer volume -- added a flame vaporiser (based on number of flame gears?) -- give tumblers an extra 47 health on the start of their tumble to counter the grenade (exp) -- refocus camera on tumbler on newturn (not on crates, barrels etc) -- increase delay: yes, yes, eat your hearts out -- commit log -- Better HUD -- Allow more user customization -- Bugfix for new gear spawns -- Performance tweaks -- Variety of small gameplay changes ------------------------ -- version 0.7.1 ------------------------ -- redraw HUD on screen resolution change ------------------------ -- version 0.8.0 ------------------------ -- Allow detailed configuration with script parameter (see above) -- Alternative weapon selection with slot keys --- Slot 1: Barrel Launcher --- Slot 2: Mine Deployer --- Slot 3: Flamer -- Add mine/barrel launch sounds -- Improved ammo display -- Denied sound + message when trying to fire empty ammo weapon -- Slightly better mission description --- The old hacks by (ab)using MinesNum, Explosives and HealthCaseAmount have been removed -- Permanently disable some gameflags which currently won't work together with this script (see above) -- Show flamer ammo as fuel everywhere (no more percentage confusion) --------------------------- -- some other ideas/things --------------------------- --[[ -- allow invulnerability mode (currently broken, thus disabled) -- better barrel/minespawn effects -- separate grab distance for mines/barrels -- bug: message color for remaining ammo does not change if two times the same message (but in different desired color) is shown in quick succession (i.e. "Out of ammo!" for all weapons) -- [probably not] make barrels always explode? -- [probably not] persistent ammo? -- [probably not] dont hurt tumblers and restore their health at turn end? ]] ---------------------------------------------------------------- ---------------------------------------------------------------- local flames = {} local fGearValues = {} function runOnflames(func) for k, gear in ipairs(flames) do func(gear) end end function trackFGear(gear) table.insert(flames, gear) end function trackFGearDeletion(gear) fGearValues[gear] = nil for k, g in ipairs(flames) do if g == gear then table.remove(flames, k) break end end end function getFGearValue(gear, key) if fGearValues[gear] ~= nil then return fGearValues[gear][key] end return nil end function setFGearValue(gear, key, value) found = false for id, values in pairs(fGearValues) do if id == gear then values[key] = value found = true end end if not found then fGearValues[gear] = { [key] = value } end end function decreaseFGearValue(gear, key) for id, values in pairs(fGearValues) do if id == gear then values[key] = values[key] - 1 end end end function HandleLife(gear) decreaseFGearValue(gear, "L") if getFGearValue(gear, "L") == 0 then AddVisualGear(GetX(gear), GetY(gear), vgtSmoke, 0, false) DeleteGear(gear) end end ---------------------------------------------------------------- ---------------------------------------------------------------- function HideTags() for i = 0, 3 do SetVisualGearValues(vTag[i],0,0,0,0,0,1,0, 0, 240000, 0xffffff00) end end function DrawTag(i) zoomL = 1.3 xOffset = 40 if i == 0 then yOffset = 40 tCol = 0xffba00ff --0xffed09ff --0xffba00ff tValue = TimeLeft elseif i == 1 then zoomL = 1.1 yOffset = 70 tCol = wepCol[0] tValue = wepAmmo[0] elseif i == 2 then zoomL = 1.1 xOffset = 40 + 35 yOffset = 70 tCol = wepCol[1] tValue = wepAmmo[1] elseif i == 3 then zoomL = 1.1 xOffset = 40 + 70 yOffset = 70 tCol = wepCol[2] tValue = wepAmmo[2] end DeleteVisualGear(vTag[i]) vTag[i] = AddVisualGear(0, 0, vgtHealthTag, 0, false) g1, g2, g3, g4, g5, g6, g7, g8, g9, g10 = GetVisualGearValues(vTag[i]) SetVisualGearValues ( vTag[i], --id -(ScreenWidth/2) + xOffset, --xoffset ScreenHeight - yOffset, --yoffset 0, --dx 0, --dy zoomL, --zoom 1, --~= 0 means align to screen g7, --frameticks tValue, --value 240000, --timer tCol --GetClanColor( GetHogClan(CurrentHedgehog) ) ) end function GetGearDistance(gear) g1X, g1Y = GetGearPosition(gear) g2X, g2Y = GetGearPosition(CurrentHedgehog) q = g1X - g2X w = g1Y - g2Y return( (q*q) + (w*w) ) end -- add to your ammo ***WHEN YOU PUSH A KEY*** near them -- yes that was my justification for a non generic method function CheckProximityToExplosives(gear) if (GetGearDistance(gear) < 1400) then if (GetGearType(gear) == gtExplosives) then wepAmmo[0] = wepAmmo[0] + 1 PlaySound(sndShotgunReload) DeleteGear(gear) AddCaption(loc("+1 barrel!"), wepCol[0], capgrpAmmoinfo ) DrawTag(1) barrelsEaten = barrelsEaten + 1 if barrelsEaten == 5 then AddCaption(loc("Achievement obtained: Barrel Eater!"),0xffba00ff,capgrpMessage2) end elseif (GetGearType(gear) == gtMine) then wepAmmo[1] = wepAmmo[1] + 1 PlaySound(sndShotgunReload) DeleteGear(gear) AddCaption(loc("+1 mine!"), wepCol[1], capgrpAmmoinfo ) DrawTag(2) minesEaten = minesEaten + 1 if minesEaten == 5 then AddCaption(loc("Achievement obtained: Mine Eater!"),0xffba00ff,capgrpMessage2) end end end end -- check proximity on crates function CheckProximity(gear) dist = GetGearDistance(gear) if (dist < 1600) and (GetGearType(gear) == gtCase) then if band(GetGearPos(gear), 0x4) ~= 0 then AddCaption(string.format(loc("+%d seconds!"), bonusTime), 0xffba00ff, capgrpMessage2 ) TimeLeft = TimeLeft + bonusTime DrawTag(0) PlaySound(sndExtraTime) elseif band(GetGearPos(gear), 0x1) ~= 0 then wepAmmo[2] = wepAmmo[2] + bonusFlames PlaySound(sndShotgunReload) AddCaption(string.format(loc("+%d flamer fuel!"), bonusFlames), wepCol[2], capgrpAmmoinfo ) DrawTag(3) end DeleteGear(gear) end end function shotsRemainingMessage() local shotsMsg if wepAmmo[wepIndex] <= 0 then shotsMsg = loc("Out of ammo!") else if wepIndex == 2 then shotsMsg = loc("%d fuel remaining") else shotsMsg = loc("%d shots remaining") end end AddCaption(string.format(shotsMsg, wepAmmo[wepIndex]), wepCol[wepIndex],capgrpMessage2) end function ChangeWeapon(newIndex) if newIndex == nil then wepIndex = wepIndex + 1 if wepIndex == wepCount then wepIndex = 0 end else wepIndex = newIndex end local selText if wepIndex == 0 then selText = loc("Barrel Launcher selected!") elseif wepIndex == 1 then selText = loc("Mine Deployer selected!") else selText = loc("Flamer selected!") end AddCaption(selText, wepCol[wepIndex],capgrpAmmoinfo ) shotsRemainingMessage() end --------------- -- Parse parameters --------------- function parseNum(key, default, min, max) local num = tonumber(params[key]) if type(num) ~= "number" then if default ~= nil then return default else return nil end end if min ~= nil then num = math.max(min, num) end if max ~= nil then num = math.min(max, num) end return num end function onParameters() parseParams() barrelSpawn = parseNum("spawnbarrels", barrelSpawn, 0) mineSpawn = parseNum("spawnmines", mineSpawn, 0) initAmmoFlamer = parseNum("ammoflamer", initAmmoFlamer, 0) initAmmoBarrel = parseNum("ammobarrel", initAmmoBarrel, 0) initAmmoMine = parseNum("ammomine", initAmmoMine, 0) placedMineTime = parseNum("minetimeplaced", placedMineTime, 0, 5000) bonusTime = parseNum("bonustime", bonusTime, 0) bonusFlames = parseNum("bonusflames", bonusFlames, 0) chanceAmmo = parseNum("chanceammo", chanceAmmo, 0, 100) chanceTime = parseNum("chancetime", chanceTime, 0, 100) end --------------- -- action keys --------------- function onPrecise() if (CurrentHedgehog ~= nil) and (stopMovement == false) and (tumbleStarted == true) then if wepAmmo[wepIndex] <= 0 then PlaySound(sndDenied) shotsRemainingMessage() else wepAmmo[wepIndex] = wepAmmo[wepIndex] - 1 shotsRemainingMessage() if wep[wepIndex] == loc("Barrel Launcher") then morte = AddGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), gtExplosives, 0, 0, 0, 1) CopyPV(CurrentHedgehog, morte) -- new addition x,y = GetGearVelocity(morte) x = x*2 y = y*2 SetGearVelocity(morte, x, y) DrawTag(1) PlaySound(sndThrowRelease) elseif wep[wepIndex] == loc("Mine Deployer") then morte = AddGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), gtMine, 0, 0, 0, 0) SetTimer(morte, placedMineTime) DrawTag(2) PlaySound(sndThrowRelease) end end end preciseOn = true end function onPreciseUp() preciseOn = false end onAttack = onPrecise onAttackUp = onPreciseUp function onHJump() -- pick up explosives/mines if nearby them if (CurrentHedgehog ~= nil) and (stopMovement == false) and (tumbleStarted == true) then runOnGears(CheckProximityToExplosives) end end ------------------- -- Weapon selection ------------------- function onLJump() if (CurrentHedgehog ~= nil) and (stopMovement == false) and (tumbleStarted == true) then ChangeWeapon() end end function onSlot(slot) if (CurrentHedgehog ~= nil) and (stopMovement == false) and (tumbleStarted == true) then if slot >= 0 and slot <= 2 then ChangeWeapon(slot) end end end ----------------- -- movement keys ----------------- function onLeft() if (CurrentHedgehog ~= nil) and (stopMovement == false) then leftOn = true end end function onRight() if (CurrentHedgehog ~= nil) and (stopMovement == false) then rightOn = true end end function onUp() if (CurrentHedgehog ~= nil) and (stopMovement == false) then upOn = true end end function onDown() if (CurrentHedgehog ~= nil) and (stopMovement == false) then downOn = true end end function onDownUp() downOn = false end function onUpUp() upOn = false end function onLeftUp() leftOn = false end function onRightUp() rightOn = false end -------------------------- -- other event handlers -------------------------- function onGameInit() CaseFreq = 0 HealthCaseProb = 0 Delay = 1000 for i = 0, 3 do vTag[0] = AddVisualGear(0, 0, vgtHealthTag, 0, false) end HideTags() wep[0] = loc("Barrel Launcher") wep[1] = loc("Mine Deployer") wep[2] = loc("Flamer") wepCol[0] = 0x78818eff wepCol[1] = 0xa12a77ff wepCol[2] = 0xf49318ff wepCount = 3 DisableGameFlags(gfArtillery + gfSharedAmmo + gfPerHogAmmo + gfTagTeam + gfPlaceHog + gfInvulnerable) end function onGameStart() local clockStr local timeStr if chanceTime > 0 then clockStr = loc("Utility crates extend your time.") .. "|" timeStr = string.format(loc("Time extension: %ds"), bonusTime) .. "|" else clockStr = "" timeStr = "" end ShowMission ( "Tumbler", loc("A Hedgewars mini-game"), loc("Fly around and hurl explosives to your enemies.") .."|".. loc("Eliminate the enemy hogs to win.") .. "|" .. " " .. "|" .. string.format(loc("New mines per turn: %d"), mineSpawn) .. "|" .. string.format(loc("New barrels per turn: %d"), barrelSpawn) .. "|" .. timeStr .. " " .. "|" .. loc("Movement: [Up], [Down], [Left], [Right]") .. "|" .. loc("Fire: [Precise]") .. "|" .. loc("Change weapon: [Long jump] or [Slot 1]-[Slot 3]") .. "|" .. loc("Grab mines/barrels: [High jump]") .. "|" .. " " .. "|" .. clockStr .. loc("Ammo is reset at the end of your turn.") .. "|" .. "", 4, 4000 ) end function onScreenResize() -- redraw Tags so that their screen locations are updated if (CurrentHedgehog ~= nil) and (tumbleStarted == true) then for i = 0, 3 do DrawTag(i) end end end function onAmmoStoreInit() -- Remove all conventional weapons for a=0, 56 do SetAmmo(a, 0, 0, 0, 0) end end function onNewTurn() stopMovement = false tumbleStarted = false -- randomly create new barrels mines on the map every turn (can be disabled by setting mine/barrels to 0 in scheme) for i = 0, barrelSpawn-1 do gear = AddGear(100, 100, gtExplosives, 0, 0, 0, 0) SetHealth(gear, 100) if FindPlace(gear, false, 0, LAND_WIDTH, false) ~= nil then tempE = AddVisualGear(GetX(gear), GetY(gear), vgtBigExplosion, 0, false) end end for i = 0, mineSpawn-1 do gear = AddGear(100, 100, gtMine, 0, 0, 0, 0) if FindPlace(gear, false, 0, LAND_WIDTH, false) ~= nil then tempE = AddVisualGear(GetX(gear), GetY(gear), vgtBigExplosion, 0, false) end end -- randomly spawn time extension crates / flamer fuel on the map r = GetRandom(100) if r > 100-chanceTime then gear = SpawnFakeUtilityCrate(0, 0, false, false) end r = GetRandom(100) if r > 100-chanceAmmo then gear = SpawnFakeAmmoCrate(0, 0, false, false) end HideTags() --reset ammo counts wepAmmo[0] = 2 wepAmmo[1] = 1 wepAmmo[2] = 50 -- 50000 -- 50 wepIndex = 2 ChangeWeapon() roundKills = 0 barrelsEaten = 0 minesEaten = 0 FollowGear(CurrentHedgehog) end function DisableTumbler() stopMovement = true upOn = false down = false leftOn = false rightOn = false HideTags() end function onGameTick() -- start the player tumbling with a boom once their turn has actually begun if tumbleStarted == false then if (TurnTimeLeft > 0) and (TurnTimeLeft ~= TurnTime) then tumbleStarted = true TimeLeft = (TurnTime/1000) AddGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), gtGrenade, 0, 0, 0, 1) SetHealth(CurrentHedgehog, GetHealth(CurrentHedgehog) + 47) -- new for i = 0, 3 do DrawTag(i) end end end if (CurrentHedgehog ~= nil) and (tumbleStarted == true) then runOnGears(CheckProximity) -- crates -- Calculate and display turn time TimeLeftCounter = TimeLeftCounter + 1 if TimeLeftCounter == 1000 then TimeLeftCounter = 0 TimeLeft = TimeLeft - 1 if TimeLeft >= 0 then DrawTag(0) end end if TimeLeft == 0 then DisableTumbler() end -- handle movement based on IO moveTimer = moveTimer + 1 if moveTimer == 100 then -- 100 moveTimer = 0 runOnflames(HandleLife) --------------- -- new trail code --------------- -- the trail lets you know you have 5s left to pilot, akin to birdy feathers if (TimeLeft <= 5) and (TimeLeft > 0) then tempE = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtSmoke, 0, false) g1, g2, g3, g4, g5, g6, g7, g8, g9, g10 = GetVisualGearValues(tempE) SetVisualGearValues(tempE, g1, g2, g3, g4, g5, g6, g7, g8, g9, GetClanColor(GetHogClan(CurrentHedgehog)) ) end -------------- dx, dy = GetGearVelocity(CurrentHedgehog) dxlimit = 0.4*fMod dylimit = 0.4*fMod if dx > dxlimit then dx = dxlimit end if dy > dylimit then dy = dylimit end if dx < -dxlimit then dx = -dxlimit end if dy < -dylimit then dy = -dylimit end if leftOn == true then dx = dx - 0.1*fMod end if rightOn == true then dx = dx + 0.1*fMod end if upOn == true then dy = dy - 0.1*fMod end if downOn == true then dy = dy + 0.1*fMod end SetGearVelocity(CurrentHedgehog, dx, dy) end -- --flamer -- fireTimer = fireTimer + 1 if fireTimer == 6 then -- 5 --10 fireTimer = 0 if (wep[wepIndex] == loc("Flamer") ) and (preciseOn == true) and (wepAmmo[wepIndex] > 0) and (stopMovement == false) and (tumbleStarted == true) then wepAmmo[wepIndex] = wepAmmo[wepIndex] - 1 shotsRemainingMessage() DrawTag(3) dx, dy = GetGearVelocity(CurrentHedgehog) shell = AddGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), gtFlame, 0, 0, 0, 0) xdev = 1 + GetRandom(25) --15 xdev = xdev / 100 r = GetRandom(2) if r == 1 then xdev = xdev*-1 end ydev = 1 + GetRandom(25) --15 ydev = ydev / 100 r = GetRandom(2) if r == 1 then ydev = ydev*-1 end --*13 --8 *-4 SetGearVelocity(shell, (dx*4.5)+(xdev*fMod), (dy*4.5)+(ydev*fMod)) --10 end end -- end end function isATrackedGear(gear) if (GetGearType(gear) == gtExplosives) or (GetGearType(gear) == gtMine) or (GetGearType(gear) == gtCase) then return(true) else return(false) end end function onGearAdd(gear) if GetGearType(gear) == gtFlame then trackFGear(gear) fGears = fGears +1 if fGears < 80 then setFGearValue(gear,"L",30) else setFGearValue(gear,"L",5) --3 end elseif isATrackedGear(gear) then trackGear(gear) end end function onGearDelete(gear) if GetGearType(gear) == gtFlame then trackFGearDeletion(gear) fGears = fGears -1 elseif isATrackedGear(gear) then trackDeletion(gear) -- achievements? prototype elseif GetGearType(gear) == gtHedgehog then if GetHogTeamName(gear) ~= GetHogTeamName(CurrentHedgehog) then roundKills = roundKills + 1 if roundKills == 2 then AddCaption(loc("Double Kill!"),0xffba00ff,capgrpMessage2) elseif roundKills == 3 then AddCaption(loc("Killing spree!"),0xffba00ff,capgrpMessage2) elseif roundKills >= 4 then AddCaption(loc("Unstoppable!"),0xffba00ff,capgrpMessage2) end elseif gear == CurrentHedgehog then DisableTumbler() elseif gear ~= CurrentHedgehog then AddCaption(loc("Friendly Fire!"),0xffba00ff,capgrpMessage2) end end if CurrentHedgehog ~= nil then FollowGear(CurrentHedgehog) end end