• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Lua] World editor closes when I save the map because of this code.

Status
Not open for further replies.
Level 24
Joined
Jun 26, 2020
Messages
1,852
I tried to update the Lua pure code version of my Bounty Controller but every time I saved it the editor closes and I don't know why, I wrote it in the VSCode with pluggins and never displayed me a syntax error or something like that, to see what is happening here is the code:
Lua:
--The pure Lua version of the Bounty Controller
--GUI version: https://www.hiveworkshop.com/threads/gui-bounty-controller.332114/

do

    Bounty_Controller = nil
    local LocalPlayer = nil
    --selfs can be edited (obviously only valid values)
    local DEF_COLOR_GOLD = "ffcc00"
    local DEF_COLOR_LUMBER = "32cd32"
    local DEF_SIZE = 10
    local DEF_LIFE_SPAN = 3.50
    local DEF_AGE = 0.00
    local DEF_SPEED = 64
    local DEF_DIRECTION = 90
    local DEF_FADE_POINT = 2.50
    local DEF_STATE = "gold"
    local DEF_HEIGHT = 0
    local DEF_SHOW = true
    local DEF_SHOW_NOTHING = false
    local DEF_ALLOW_FRIEND_FIRE = false
    local DEF_EFFECT = "UI\\Feedback\\GoldCredit\\GoldCredit.mdl"
    local DEF_SHOW_EFFECT = true
    local DEF_PERMANENT = false
    local LIMIT_RECURSION = 16 --If a loop caused by recursion is doing in porpouse you can edit the tolerance of how many calls can do
    
    local t1 = CreateTrigger()
    local t2 = CreateTrigger()
        
    local current = nil
    local Bounties0 = {}
    local Bounties1 = {}
    local Bounties2 = {}
    local Recursion = 0
        
    -- This function is runned at the map initialization,  if you wanna use it to your bounties,  you can do it
    local function SetData()
        
        --[[Peasant]] Bounty.Set(FourCC("hpea"), 15, 5, 3)

        for i = 0, PLAYER_NEUTRAL_AGGRESSIVE do
            SetPlayerState(Player(i), PLAYER_STATE_GIVES_BOUNTY, 0)
        end
        
    end
    
    globals(function(_ENV)
        BountyEvent = 0.0
        BountyDeadEvent = 0.0
    end)

    function IsBounty(value)
        return getmetatable(value) == BountyMT
    end

    -- The order it returns (if there is no error) is: number, Bounty
    local function ValidValues(v1, v2)
        if type(v1) == "number" and IsBounty(v2) then
            return v1, v2
        elseif IsBounty(v1) and type(v2) == "number" then
            return v2, v1
        else
            error("Wrong operators", 3)
        end
    end
    
    BountyMT = {
        __index = function (t, k)
            if BountyMT[k] then
                return BountyMT[k]
            elseif k == "Amount" or
                k == "Color" or
                k == "Size" or
                k == "LifeSpan" or
                k == "Age" or
                k == "Speed" or
                k == "Direction" or
                k == "FadePoint" or
                k == "State" or
                k == "Height" or
                k == "Show" or
                k == "ShowNothing" or
                k == "AllowFriendFire" or
                k == "Effect" or
                k == "ShowEff" or
                k == "Permanent" or
                k == "Receiver" or
                k == "UnitPos" or
                k == "LocPos" or
                k == "TextTag" or
                k == "PosX" or
                k == "PosY" or
                k == "KillingUnit" or
                k == "DyingUnit" or
                k == "canSee" then
                return rawget(t, k)
            else
                error("This field don't exist " .. tostring(k), 2)
            end
        end,

        __newindex = function (t, k ,v)
            if BountyMT[k] then
                error("You can't edit this method " .. tostring(k), 2)
            elseif k == "Amount" or
                k == "Color" or
                k == "Size" or
                k == "LifeSpan" or
                k == "Age" or
                k == "Speed" or
                k == "Direction" or
                k == "FadePoint" or
                k == "State" or
                k == "Height" or
                k == "Show" or
                k == "ShowNothing" or
                k == "AllowFriendFire" or
                k == "Effect" or
                k == "ShowEff" or
                k == "Permanent" or
                k == "Receiver" or
                k == "UnitPos" or
                k == "LocPos" or
                k == "TextTag" or
                k == "PosX" or
                k == "PosY" or
                k == "KillingUnit" or
                k == "DyingUnit" or
                k == "canSee" then
                rawset(t, k, v)
            else
                error("You can't add new fields " .. tostring(k), 2)
            end
        end,

        __add = function (v1, v2)
            local num, bounty = ValidValues(v1, v2)
            local new = Bounty.Copy(bounty)
            new.Amount = new.Amount + num
            return new
        end,

        __sub = function (v1, v2)
            local num, bounty = ValidValues(v1, v2)
            local new = Bounty.Copy(bounty)
            new.Amount = new.Amount - num
            return new
        end,

        __mul = function (v1, v2)
            local num, bounty = ValidValues(v1, v2)
            local new = Bounty.Copy(bounty)
            new.Amount = new.Amount * num
            return new
        end,

        __div = function (v1, v2)
            local num, bounty = ValidValues(v1, v2)
            local new = Bounty.Copy(bounty)
            new.Amount = new.Amount / num
            return new
        end,

        __mod = function (v1, v2)
            local num, bounty = ValidValues(v1, v2)
            local new = Bounty.Copy(bounty)
            new.Amount = new.Amount % num
            return new
        end,

        __pow = function (v1, v2)
            local num, bounty = ValidValues(v1, v2)
            local new = Bounty.Copy(bounty)
            new.Amount = new.Amount ^ num
            return new
        end,

        __gc = function (t)
            print(t)
        end,

        __name = "Bounty",
    
        __tostring = function (t)
            return "Bounty: |cff" .. Bounty.Sign(t.Bounty) .. t.Bounty .. t.Color .. "|r"
        end
    }
    
    function BountyMT:destroy()
        if self.LocPos then
            RemoveLocation(self.LocPos)
        end
        self.canSee = nil
        Recursion = Recursion - 1
    end
    function BountyMT:CanSee(p, flag)
        self.canSee[p] = flag
    end

    function BountyMT:Seeing(p)
        return self.canSee[p]
    end

    function BountyMT:Run()
        local what

        if Recursion > LIMIT_RECURSION then --If there is recursion that don't stop soon, the system stops automatically
            warn("There is a recursion with the Bounty system, check if you are not creating a infinite loop.")
            self:destroy()
            return nil
        end
        
        if self.State == "gold" then
            what = PLAYER_STATE_RESOURCE_GOLD
        elseif self.State == "lumber" then
            what = PLAYER_STATE_RESOURCE_LUMBER
        else
            self:destroy()
            return nil --If the state is not valid, the process stop
        end

        if self.Amount == 0 and not self.ShowNothing then
            self.Show = false
            self.ShowEff = false
        end

        AdjustPlayerStateSimpleBJ(self.Receiver, what, self.Amount)

        if not self.Color then
            if self.State == "gold" then
                self.Color = DEF_COLOR_GOLD
            elseif self.State == "lumber" then
                self.Color = DEF_COLOR_LUMBER
            end
        end
    
        if self.LocPos then
            self.PosX = GetLocationX(self.LocPos)
            self.PosY = GetLocationY(self.LocPos)
        elseif self.UnitPos then
            self.PosX = GetUnitX(self.UnitPos)
            self.PosY = GetUnitY(self.UnitPos)
        else
            self.Show = false
            self.ShowEff = false --If there is no position to the text, the text and the effect won't show
        end
        
        self.TextTag = CreateTextTag()
        SetTextTagPermanent(self.TextTag, self.Permanent)
        SetTextTagText(self.TextTag, "|cff" .. self.Color .. Bounty.Sign(self.Amount) .. I2S(self.Amount) .. "|r", TextTagSize2Height(self.Size))
        SetTextTagVisibility(self.TextTag, self:Seeing(LocalPlayer) and self.Show)
        SetTextTagPos(self.TextTag, self.PosX, self.PosY, self.Height)
        SetTextTagFadepoint(self.TextTag, self.FadePoint)
        SetTextTagLifespan(self.TextTag, self.LifeSpan)
        SetTextTagVelocityBJ(self.TextTag, self.Speed, self.Direction)
        SetTextTagAge(self.TextTag, self.Age)

        if self.ShowEff then
            DestroyEffect(AddSpecialEffect(self.Effect, self.PosX, self.PosY))
        end
        
        current = self
        what = self.TextTag
        
        globals.BountyEvent = 0.00
        globals.BountyEvent = 1.00

        current = self

        self:destroy()

        return what
    end

    function BountyMT:Call(bounty, pos, myplayer, addplayer, perm)
        self.Amount = bounty
        self.UnitPos = pos
        self.Receiver = myplayer
        self:CanSee(myplayer, addplayer)
        self.Permanent = perm
        return self:Run()
    end
    function BountyMT:ChangeReceiver(newPlayer, removePrevious, addNew)
        self:CanSee(self.Receiver, not removePrevious)
        self.Receiver = newPlayer
        self:CanSee(newPlayer, addNew)
    end

    Bounty = setmetatable({
    
        --The functions to the bounty stats
        
        SetBase = function(id, base)
            Bounties0[id] = base
        end,
        
        SetDice = function(id, dice)
            Bounties1[id] = dice
        end,
        
        SetSides = function(id, sides)
            Bounties2[id] = sides
        end,
        
        Set = function(id, base, dice, side)
            Bounty.SetBase(id, base)
            Bounty.SetDice(id, dice)
            Bounty.SetSides(id, side)
        end,
        
        --The static functions to get the bounty stats (that you set before)
        
        GetBase = function(id)
            return Bounties0[id] or 0
        end,
        
        GetDice = function(id)
            return Bounties1[id] or 0
        end,
        
        GetSides = function(id)
            return Bounties2[id] or 0
        end,
        
        Get = function(id)
            return Bounty.GetBase(id) + GetRandomInt(0, Bounty.GetDice(id) * Bounty.GetSides(id))
        end,

        GetCurrent = function()
            return current
        end,

        Sign = function (i)
            return i < 0 and "" or "+"
        end,

        Copy = function (bounty)
            if IsBounty(bounty) then
                local new = setmetatable({}, BountyMT)
                for k, v in pairs(bounty) do
                    new[k] = v
                end
                return new
            else
                error("Wrong argument, expected Bounty", 2)
            end
        end,
        
        create = function()
            Recursion = Recursion + 1
            local self = setmetatable({}, BountyMT)
            self.Amount = 0
            self.Size = DEF_SIZE
            self.LifeSpan = DEF_LIFE_SPAN
            self.Age = DEF_AGE
            self.Speed = DEF_SPEED
            self.Direction = DEF_DIRECTION
            self.FadePoint = DEF_FADE_POINT
            self.State = DEF_STATE
            self.Height = DEF_HEIGHT
            self.Show = DEF_SHOW
            self.ShowNothing = DEF_SHOW_NOTHING
            self.Effect = DEF_EFFECT
            self.ShowEff = DEF_SHOW_EFFECT
            self.Permanent = DEF_PERMANENT
            self.AllowFriendFire = DEF_ALLOW_FRIEND_FIRE
            self.PosX = 0.00
            self.PosY = 0.00
            return self
        end
    },{
        __index = function (t, k)
            if k == "SetBase" or
                k == "SetDice" or
                k == "SetSides" or
                k == "Set" or
                k == "GetBase" or
                k == "GetDice" or
                k == "GetSides" or
                k == "Get" or
                k == "GetCurrent" or
                k == "Sign" or
                k == "create" then
                return rawget(t, k)
            else
                error("This field don't exist " .. tostring(k), 2)
            end
        end,

        __newindex = function (t, k ,v)
            if k == "SetBase" or
                k == "SetDice" or
                k == "SetSides" or
                k == "Set" or
                k == "GetBase" or
                k == "GetDice" or
                k == "GetSides" or
                k == "Get" or
                k == "GetCurrent" or
                k == "Sign" or
                k == "create" then
                rawset(t, k, v)
            else
                error("You can't add new fields " .. tostring(k), 2)
            end
        end,

        __name = "Bounty",
    
        __tostring = function (t)
            return "Class: Bounty"
        end
    })

    local oldBlizzard = InitBlizzard
    function InitBlizzard()
        --The trigger that runs when a unit dies
        Bounty_Controller = CreateTrigger()
        TriggerRegisterAnyUnitEventBJ(Bounty_Controller, EVENT_PLAYER_UNIT_DEATH)
        TriggerAddCondition(Bounty_Controller, Condition(function()
            return GetKillingUnit() ~= nil --If there is not killing unit then the process stop
        end))
        TriggerAddAction(Bounty_Controller, function()
            local self = Bounty.create()
            
            self.KillingUnit = GetKillingUnit()
            self.DyingUnit = GetDyingUnit()
            self.Amount = Bounty.Get(GetUnitTypeId(self.DyingUnit))
            
            self.Receiver = GetOwningPlayer(self.KillingUnit)
            self:CanSee(self.Receiver, true)
            self.UnitPos = self.DyingUnit
            
            current = self
            
            globals.BountyDeadEvent=0.00
            globals.BountyDeadEvent=1.00
            
            current = self
            
            if IsUnitEnemy(self.DyingUnit, self.Receiver) or self.AllowFriendFire then
                self:Run()
            else
                self:destroy()
            end
        end)
        --Last details
        TriggerRegisterVariableEvent(t1, "BountyDeadEvent", EQUAL, 1.00)
        TriggerRegisterVariableEvent(t2, "BountyEvent", EQUAL, 1.00)
        LocalPlayer = GetLocalPlayer()
        SetData()

        oldBlizzard()
    end

    function RegisterBountyDeadEvent(func)
        return TriggerAddAction(t1, func)
    end

    function TriggerRegisterBountyDeadEvent(t, func)
        TriggerRegisterVariableEvent(t, "BountyDeadEvent", EQUAL, 1.00)
        return TriggerAddAction(t, func)
    end

    function GetNativeBountyDeadTrigger()
        return t1
    end

    function RegisterBountyEvent(func)
        return TriggerAddAction(t2, func)
    end

    function TriggerRegisterBountyEvent(t, func)
        TriggerRegisterVariableEvent(t, "BountyEvent", EQUAL, 1.00)
        return TriggerAddAction(t, func)
    end

    function GetNativeBountyTrigger()
        return t2
    end

end
 
Level 18
Joined
Oct 17, 2012
Messages
821
Well, you have the option of coding outside the World Editor, and I don't mean copy and pasting back and forth. That would be too burdensome.

Compile code externally with Ceres. On the plus side, it includes a decent debugger.

For compiling code in Visual Studio alone, there is this extension, which is similar to what Wurstscript offers.

Both options bring back the functionality that were removed in Wc3 Lua.
 
Last edited:
Level 18
Joined
Oct 17, 2012
Messages
821
Well, first I copied the files for Ceres from GitHub to somewhere easily accessible. I chose an empty folder on my desktop.

Then I created a text file that contains the following:
JASS:
@echo off
cd [Path of the folder that contains the copied files]
ceres build -- --map [Your Map's Name].w3x --output mpq
pause
cd [Path to the folder called target]
[Name of your map's copy].w3x
pause
What this does is first try to access the copied files. Then it tells Ceres to compile your source code files and insert them into a copy of your map.
The rest is just extra fluff that opens the copy in World Editor.

After doing that, I changed the extension of the text file from .txt to .bat.
That way, each time I click on the bat file, Ceres will work out its magic.
You can also bind a keyboard shortcut to the bat file to get stuff done by a press of a button!

Some Notes:
  • You need to place your source code files inside the folder named src and your map inside the folder named maps. Duh!
  • The folder named target contains the end product, that is your map with the code compiled.
  • You can continue to code in Visual Code Studio.
Ceres's API on GitHub
 
Last edited:
Status
Not open for further replies.
Top