share/hedgewars/Data/Scripts/Multiplayer/Racer.lua
author Wuzzy <almikes@aol.com>
Wed, 13 Apr 2016 14:14:33 +0200
changeset 11930 4ed643879c0f
parent 11929 167de692f2d7
child 11931 a01a890093f3
permissions -rw-r--r--
Racer: Clean up “did not finish” mess


------------------------------------------
-- RACER 0.8
-- map-independant racing script
-- by mikade
-----------------------------------------

-----------------------------------
--0.1: took all the code from crazy racer and scrapped most of it
-----------------------------------

-- Removed tumbler system
-- Removed extra adds like boosters etc
-- Added experimental waypoint placement system
-- More user feedback
-- Reduced race complexity limit to 5 waypoints
-- stop placement at complexity limit reached and end turn
-- guys dont keep racing after dying
-- invulnerable feasibility
-- reverted time keeping method
-- reduced feedback display time
-- colour-coded addcaptions
-- cleaned up code
-- support for more players properly added
-- tardis fix
-- remove airstrikes

-- i think the remainder 0 .456 sec of the tracktime isnt getting reset on newturn

-- update feedback

-------
-- 0.2
-------

-- allow gameflags
-- extend time to 90s
-- remove other air-attack based weps
-- turn off water rise for sd

-------
-- 0.3
-------

-- prevent WP being placed in land
-- prevent waypoints being placed outside border

-------
-- 0.4
-------

-- update user feedback
-- add more sounds

-------
-- 0.5
-------

-- fix ghost disappearing if hog falls in water or somehow dies
-- lengthen ghost tracking interval to improve performance on slower machines
-- increase waypoint limit to 8
-- allow for persistent showmission information

-------
-- 0.6
-------

-- remove hogs from racing area as per request

-------
-- 0.7
-------

-- switch to first available weapon if starting race with no weapon selected

-------
-- 0.8
-------
-- allow different boost directions

-----------------------------
-- SCRIPT BEGINS
-----------------------------

HedgewarsScriptLoad("/Scripts/Locale.lua")
HedgewarsScriptLoad("/Scripts/OfficialChallenges.lua")
HedgewarsScriptLoad("/Scripts/Params.lua")

------------------
-- Got Variables?
------------------

local fMod = 1000000 -- 1
local roundLimit = 3
local roundNumber = 0
local firstClan = 10

local fastX = {}
local fastY = {}
local fastCount = 0
local fastIndex = 0
local fastColour = 0xffffffff

local currX = {}
local currY = {}
local currCount = 0

local specialPointsX = {}
local specialPointsY = {}
local specialPointsCount = 0

local TeamRope = false

--------------------------
-- hog and team tracking variales
--------------------------

local numhhs = 0 -- store number of hedgehogs
local hhs = {} -- store hedgehog gears

local numTeams --  store the number of teams in the game
local teamNameArr = {}  -- store the list of teams
local teamClan = {}
local teamSize = {}     -- store how many hogs per team
local teamIndex = {} -- at what point in the hhs{} does each team begin

local teamComment = {}
local teamScore = {}

-------
-- racer vars
--------

local cGear = nil

local bestClan = 10
local bestTime = 1000000

local gameBegun = false
local gameOver = false
local racerActive = false
local trackTime = 0

local wpCirc = {}
local wpX = {}
local wpY = {}
local wpCol = {}
local wpActive = {}
local wpRad = 450
local wpCount = 0
local wpLimit = 8

local usedWeapons = {}

local roundN
local lastRound
local RoundHasChanged

local boostX = 0
local boostY = 0
local boostValue = 1

-------------------
-- general methods
-------------------

--[[
Parameters syntax:

teamrope=true
--> The team will be colored in the color of the team.

rounds=N
--> The game will be played with N rounds (default: 3)

waypointradius=N
--> The waypoints have a radius of N pixels (default: 450)

]]

function onParameters()
    parseParams()
    if params["teamrope"] ~= nil then
        TeamRope = true
    end
    if params["rounds"] ~= nil then
        roundLimit = math.max(1, math.floor(tonumber(params["rounds"])))
        if type(roundLimit) ~= "number" then
             roundLimit = 3
        end
    end
    if params["waypointradius"] ~= nil then
        wpRad = math.max(40, math.floor(tonumber(params["waypointradius"])))
        if type(roundLimit) ~= "number" then
             wpRad = 450
        end
    end
end

function RebuildTeamInfo()


        -- make a list of individual team names
        for i = 0, (TeamsCount-1) do
                teamNameArr[i] = " "
                teamSize[i] = 0
                teamIndex[i] = 0
                teamScore[i] = 1000000
        end
        numTeams = 0

        for i = 0, (numhhs-1) do

                z = 0
                unfinished = true
                while(unfinished == true) do

                        newTeam = true
                        tempHogTeamName = GetHogTeamName(hhs[i]) -- this is the new name

                        if tempHogTeamName == teamNameArr[z] then
                                newTeam = false
                                unfinished = false
                        end

                        z = z + 1

                        if z == TeamsCount then
                                unfinished = false
                                if newTeam == true then
                                        teamNameArr[numTeams] = tempHogTeamName
                                        numTeams = numTeams + 1
                                end
                        end

                end

        end

        -- find out how many hogs per team, and the index of the first hog in hhs
        for i = 0, (numTeams-1) do
                for z = 0, (numhhs-1) do
                        if GetHogTeamName(hhs[z]) == teamNameArr[i] then
                                teamClan[i] = GetHogClan(hhs[z])
                                if teamSize[i] == 0 then
                                        teamIndex[i] = z -- should give starting index
                                end
                                teamSize[i] = teamSize[i] + 1
                                --add a pointer so this hog appears at i in hhs
                        end
                end

        end

end


-----------------
-- RACER METHODS
-----------------

function onLeft()
	boostX = boostX +boostValue
end
function onLeftUp()
	boostX = boostX -boostValue
end
function onRight()
	boostX = boostX -boostValue
end
function onRightUp()
	boostX = boostX +boostValue
end
function onUp()
	boostY = boostY +boostValue
end
function onUpUp()
	boostY = boostY -boostValue
end
function onDown()
	boostY = boostY -boostValue
end
function onDownUp()
	boostY = boostY +boostValue
end

function CheckWaypoints()

        trackFinished = true

        for i = 0, (wpCount-1) do

                g1X, g1Y = GetGearPosition(CurrentHedgehog)
                g2X, g2Y = wpX[i], wpY[i]

                g1X = g1X - g2X
                g1Y = g1Y - g2Y
                dist = (g1X*g1X) + (g1Y*g1Y)

                NR = (48/100*wpRad)/2

                if dist < (NR*NR) then
                        wpCol[i] = GetClanColor(GetHogClan(CurrentHedgehog))
                        SetVisualGearValues(wpCirc[i], wpX[i], wpY[i], 20, 100, 1, 10, 0, wpRad, 5, wpCol[i])

                        wpRem = 0
                        for k = 0, (wpCount-1) do
                                if wpActive[k] == false then
                                        wpRem = wpRem + 1
                                end
                        end

                        if wpActive[i] == false then
                                local wpMessage = ""
                                if wpRem-1 == 0 then
                                         wpMessage = loc("Track completed!")
                                else
                                         wpMessage = string.format(loc("Waypoints remaining: %d"), wpRem-1)
                                end
                                AddCaption(wpMessage, 0xffba00ff, capgrpGameState)
                        end
                        wpActive[i] = true

                end

                if wpActive[i] == false then
                        trackFinished = false
                end

        end

        return(trackFinished)

end

function AdjustScores()
	bestTimeComment = loc("Did not finish")

        newScore = false

        -- update this clan's time if the new track is better
        for i = 0, (numTeams-1) do
                if teamClan[i] == GetHogClan(CurrentHedgehog) then
                        if trackTime < teamScore[i] then
                                teamScore[i] = trackTime
                                newScore = true
                        else
                                newScore = false
                        end
                end
        end

        -- find the best time out of those so far
        for i = 0, (numTeams-1) do
                if teamScore[i] < bestTime then
                        bestTime = teamScore[i]
                        bestClan = teamClan[i]
                end
        end

        if bestTime ~= 1000000 then
                bestTimeComment = string.format(loc("%.1fs"), (bestTime/1000))
        end

        if newScore == true then
                if trackTime == bestTime then -- best time of the race
                        ShowMission(loc("Racer"),
                        loc("Track completed!"),
                        string.format(loc("New race record: %.1fs"), (trackTime/1000)) .. "|" ..
                        string.format(loc("Winning time: %s"), bestTimeComment), 0, 4000)
                        PlaySound(sndHomerun)
                else    -- best time for the clan
                        ShowMission(loc("Racer"),
                        loc("Track completed!"),
                        string.format(loc("New clan record: %.1fs"), (trackTime/1000)) .. "|" ..
                        string.format(loc("Winning time: %s"), bestTimeComment), 4, 4000)
                end
        else -- not any kind of new score
                ShowMission(loc("Racer"),
                loc("Track completed!"),
                string.format(loc("Time: %.1fs"), (trackTime/1000)) .. "|" ..
                string.format(loc("Winning time: %s"), bestTimeComment), -amSkip, 4000)
                PlaySound(sndHellish)
        end


        if bestTime == trackTime then

                fastColour = GetClanColor(GetHogClan(CurrentHedgehog))

                for i = 0, (currCount-1) do
                        fastX[i] = currX[i]
                        fastY[i] = currY[i]
                end

                fastCount = currCount
                fastIndex = 0

        else
                currCount = 0
                fastIndex = 0
        end


end

function onNewRound()

        roundNumber = roundNumber + 1

        totalComment = ""
        for i = 0, (TeamsCount-1) do
                        if teamNameArr[i] ~= " " and teamScore[i] ~= 1000000 then
                                teamComment[i] = string.format(loc("%s: %.1fs"), teamNameArr[i], (teamScore[i]/1000)) .. "|"
                        else
                                teamComment[i] = string.format(loc("%s: Did not finish"), teamNameArr[i]) .. "|"
                        end
                        totalComment = totalComment .. teamComment[i]
        end

        ShowMission(    loc("Racer"),
                                        loc("Status update"),
                                        string.format(loc("Rounds complete: %d/%d"), roundNumber, roundLimit) .. "|" .. " " .. "|" ..
                                        loc("Best team times: ") .. "|" .. totalComment, 0, 4000)

        -- end game if its at round limit
        if roundNumber >= roundLimit then
                gameOver = true
                TurnTimeLeft = 10000000

                -- Sort the scores for the ranking list
                local unfinishedArray = {}
                local sortedTeams = {}
                local k = 1
                for i = 0, TeamsCount-1 do
                        if teamScore[i] ~= 1000000 and teamNameArr[i] ~= " " then
                               sortedTeams[k] = {}
                               sortedTeams[k].name = teamNameArr[i]
                               sortedTeams[k].score = teamScore[i]
                               k = k + 1
                        else
                               table.insert(unfinishedArray, string.format(loc("%s did not finish the race."), teamNameArr[i]))
                        end
                end
                table.sort(sortedTeams, function(team1, team2) return team1.score < team2.score end)

                -- Write all the stats!

                for i = 1, #sortedTeams do
                        SendStat(siPointType, loc("milliseconds"))
                        SendStat(siPlayerKills, sortedTeams[i].score, sortedTeams[i].name)
                end

                if #sortedTeams >= 1 then
                        SendStat(siGameResult, string.format(loc("%s wins!"), sortedTeams[1].name))
                        SendStat(siGameResult, string.format(loc("%s wins!"), sortedTeams[1].name))
                        SendStat(siCustomAchievement, string.format(loc("%s wins with a best time of %.1fs."), sortedTeams[1].name, (sortedTeams[1].score/1000)))
                        for i=1,#unfinishedArray do
                                 SendStat(siCustomAchievement, unfinishedArray[i])
                        end
                else
                        SendStat(siGameResult, loc("Round draw"))
                        SendStat(siCustomAchievement, loc("Nobody managed to finish the race. What a shame!"))
                        SendStat(siCustomAchievement, loc("Maybe you should try easier waypoints next time."))
                end

                -- Game over
                EndGame()
        end

end

function CheckForNewRound()

        if GetHogClan(CurrentHedgehog) == firstClan then
                onNewRound()
        end

end

function DisableTumbler()
        currCount = 0
        fastIndex = 0
        TurnTimeLeft = 0
        racerActive = false -- newadd
end

function HandleGhost()

        -- get the current xy of the racer at this point
        currX[currCount] = GetX(CurrentHedgehog)
        currY[currCount] = GetY(CurrentHedgehog)
        currCount = currCount + 1

        -- draw a ping of smoke where the fastest player was at this point
        if (fastCount ~= 0) and (fastIndex < fastCount) then

                fastIndex = fastIndex + 1

                tempE = AddVisualGear(fastX[fastIndex], fastY[fastIndex], 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, fastColour )

        end

end

function TryRepositionHogs()

	if MapHasBorder() == true then

		for i = 0, (numhhs-1) do
			if hhs[i] ~= nil then
				SetGearPosition(hhs[i],GetX(hhs[i]), TopY-10)
			end
		end

	end

end

----------------------------------
-- GAME METHODS / EVENT HANDLERS
----------------------------------

function onGameInit()
        EnableGameFlags(gfInfAttack, gfInvulnerable)
        CaseFreq = 0
        TurnTime = 90000
        WaterRise = 0
end


function onGameStart()
        SendHealthStatsOff()

        roundN = 0
        lastRound = TotalRounds
        RoundHasChanged = false

        for i = 0, (specialPointsCount-1) do
                PlaceWayPoint(specialPointsX[i], specialPointsY[i])
        end

        RebuildTeamInfo()

        ShowMission     (
                                loc("Racer"),
                                loc("A Hedgewars mini-game"),

                                loc("Build a track and race.") .. "|" ..
                                string.format(loc("Round limit: %d"), roundLimit) .. "|" ..

                                "", 4, 4000
                                )

        TryRepositionHogs()

end

function PlaceWayPoint(x,y)
    if not racerActive then
        if wpCount == 0 or wpX[wpCount - 1] ~= x or wpY[wpCount - 1] ~= y then

            wpX[wpCount] = x
            wpY[wpCount] = y
            wpCol[wpCount] = 0xffffffff
            wpCirc[wpCount] = AddVisualGear(wpX[wpCount],wpY[wpCount],vgtCircle,0,true)

            SetVisualGearValues(wpCirc[wpCount], wpX[wpCount], wpY[wpCount], 20, 100, 1, 10, 0, wpRad, 5, wpCol[wpCount])

            wpCount = wpCount + 1

            AddCaption(string.format(loc("Waypoint placed. Available points remaining: %d"), wpLimit-wpCount))
        end
    end
end

function onSpecialPoint(x,y,flag)
    if flag == 99 then
        fastX[fastCount] = x
        fastY[fastCount] = y
        fastCount = fastCount + 1
    else
        addHashData(x)
        addHashData(y)
        addHashData(flag)
        specialPointsX[specialPointsCount] = x
        specialPointsY[specialPointsCount] = y
        specialPointsCount = specialPointsCount + 1
    end
end

function onNewTurn()

        CheckForNewRound()
        TryRepositionHogs()

        racerActive = false

        trackTime = 0

        currCount = 0 -- hopefully this solves problem
        AddAmmo(CurrentHedgehog, amAirAttack, 0)
        gTimer = 0

        -- Set the waypoints to unactive on new round
        for i = 0,(wpCount-1) do
                wpActive[i] = false
                wpCol[i] = 0xffffffff
                SetVisualGearValues(wpCirc[i], wpX[i], wpY[i], 20, 100, 1, 10, 0, wpRad, 5, wpCol[i])
        end

        -- Handle Starting Stage of Game
        if (gameOver == false) and (gameBegun == false) then
                if wpCount >= 3 then
                        gameBegun = true
                        roundNumber = 0
                        firstClan = GetHogClan(CurrentHedgehog)
                        ShowMission(loc("Racer"),
                        loc("A Hedgewars mini-game"),
                        loc("Complete the track as fast as you can!"), 2, 4000)
                else
                        ShowMission(loc("Racer"),
                        loc("Waypoint placement phase"),
                        loc("Place more waypoints using the 'Air Attack' weapon."), 2, 4000)
                        AddAmmo(CurrentHedgehog, amAirAttack, 4000)
                        SetWeapon(amAirAttack)
                end
        end

        if gameOver == true then
                gameBegun = false
                racerActive = false -- newadd
        end

        AddAmmo(CurrentHedgehog, amTardis, 0)
        AddAmmo(CurrentHedgehog, amDrillStrike, 0)
        AddAmmo(CurrentHedgehog, amMineStrike, 0)
        AddAmmo(CurrentHedgehog, amNapalm, 0)
        AddAmmo(CurrentHedgehog, amPiano, 0)

end

function onGameTick20()

        -- airstrike detected, convert this into a potential waypoint spot
        if cGear ~= nil then
                x,y = GetGearPosition(cGear)
                if x > -9000 then
                        x,y = GetGearTarget(cGear)


                        if TestRectForObstacle(x-20, y-20, x+20, y+20, true) then
                                AddCaption(loc("Please place the waypoint in the air, within the map boundaries"))
                                PlaySound(sndDenied)
                        elseif (y > WaterLine-50) then
                                AddCaption(loc("Please place the waypoint further away from the waterline"))
                                PlaySound(sndDenied)
                        else
                                PlaceWayPoint(x, y)
                                if wpCount == wpLimit then
                                        AddCaption(loc("Race complexity limit reached"))
                                        DisableTumbler()
                                end
                        end
                else
                        DeleteGear(cGear)
                end
        SetGearPosition(cGear, -10000, 0)
        end


        -- start the player tumbling with a boom once their turn has actually begun
        if racerActive == false then

                if (TurnTimeLeft > 0) and (TurnTimeLeft ~= TurnTime) then

                        -- if the gamehas started put the player in the middle of the first
                        --waypoint that was placed
                        if gameBegun == true then
                                AddCaption(loc("Good to go!"))
                                racerActive = true
                                trackTime = 0

                                SetGearPosition(CurrentHedgehog, wpX[0], wpY[0])
                                AddGear(GetX(CurrentHedgehog)+boostX, GetY(CurrentHedgehog)+boostY, gtGrenade, 0, 0, 0, 1)
                                FollowGear(CurrentHedgehog)

                                HideMission()

                                -- don't start empty-handed
                                if (GetCurAmmoType() == amNothing) then
                                        SetNextWeapon()
                                end
                        else
                                -- still in placement mode
                        end

                end
        end


        -- has the player started his tumbling spree?
        if (CurrentHedgehog ~= nil) then

                --airstrike conversion used to be here

                -- if the RACE has started, show tracktimes and keep tabs on waypoints
                if (racerActive == true) and (gameBegun == true) then

                        --ghost
                        if GameTime%40 == 0 then
                                HandleGhost()
                        end

                        trackTime = trackTime + 20

                        if GameTime%100 == 0 then

                                AddCaption(string.format(loc("Time: %.1fs"), (trackTime/1000)),GetClanColor(GetHogClan(CurrentHedgehog)),capgrpMessage2)

                                if (CheckWaypoints() == true) then
                                        AdjustScores()
                                        DisableTumbler()
                                end

                        end

                end

                -- if the player has expended his tunbling time, stop him tumbling
                if TurnTimeLeft <= 20 then
                        DisableTumbler()
                end

        end

end

function onGearResurrect(gear)

        AddVisualGear(GetX(gear), GetY(gear), vgtBigExplosion, 0, false)

        if gear == CurrentHedgehog then
                DisableTumbler()
        end

end

function onGearAdd(gear)

        if GetGearType(gear) == gtHedgehog then
                hhs[numhhs] = gear
                numhhs = numhhs + 1
                SetEffect(gear, heResurrectable, 1)
        elseif GetGearType(gear) == gtAirAttack then
                cGear = gear
        elseif GetGearType(gear) == gtRope and TeamRope then
            SetTag(gear,1)
            SetGearValues(gear,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,GetClanColor(GetHogClan(CurrentHedgehog)))
        elseif GetGearType(gear) == gtAirMine then
            DeleteGear(gear)
        end
end

function onGearDelete(gear)

        if GetGearType(gear) == gtAirAttack then
                cGear = nil
        end

end

function onAttack()
    at = GetCurAmmoType()

    usedWeapons[at] = 0
end

function onAchievementsDeclaration()
    usedWeapons[amSkip] = nil
    usedWeapons[amExtraTime] = nil

    usedRope = usedWeapons[amRope] ~= nil
    usedPortal = usedWeapons[amPortalGun] ~= nil
    usedSaucer = usedWeapons[amJetpack] ~= nil

    usedWeapons[amNothing] = nil
    usedWeapons[amRope] = nil
    usedWeapons[amPortalGun] = nil
    usedWeapons[amJetpack] = nil

    usedOther = next(usedWeapons) ~= nil

    if usedOther then -- smth besides nothing, skip, rope, portal or saucer used
        raceType = "unknown race"
    elseif usedRope and not usedPortal and not usedSaucer then
        raceType = "rope race"
    elseif not usedRope and usedPortal and not usedSaucer then
        raceType = "portal race"
    elseif not usedRope and not usedPortal and usedSaucer then
        raceType = "saucer race"
    elseif (usedRope or usedPortal or usedSaucer or usedOther) == false then -- no weapons used at all?
        raceType = "no tools race"
    else -- at least two of rope, portal and saucer used
        raceType = "mixed race"
    end

    map = detectMapWithDigest()

    for i = 0, (numTeams-1) do
        if teamScore[i] < 1000000 then
            DeclareAchievement(raceType, teamNameArr[i], map, teamScore[i])
        end
    end

    if map ~= nil and fastCount > 0 then
        StartGhostPoints(fastCount)

        for i = 0, (fastCount - 1) do
            DumpPoint(fastX[i], fastY[i])
        end
    end
end