What am I doing wrong?

Status
Not open for further replies.

amperbee

thunderdome denizen
Retired Staff
I can't even tell which problems I'm having.
Look at this, it's a medium-sized script:

Code:
local boostlock = 0
local fuel = 0
local boost_fuel_cost = 30
local rings_to_fuel = 10
local fuel_gain = 75

addHook("ThinkFrame", function()
	for player in players.iterate do

		if boost_fuel_cost == nil then
			boost_fuel_cost = server.boost_fuel_cost
			--print("I'm NIL")
		end
		
		-- Jetpack action
		if (player.cmd.buttons & BT_CUSTOM1) and (player.mo ~= nil) then
			if fuel > 0 then
				P_SpawnGhostMobj(player.mo)
				if player.mo.momz < 20*FRACUNIT then
					player.mo.momz = $ + 1*FRACUNIT
				end
				S_StartSound(player.mo, sfx_buzz4)
				fuel = $ - 1
			end
		end
		
		-- Boost action - Like, you press it once to throw yourself upwards.
		if (player.cmd.buttons & BT_CUSTOM2) and (boostlock == 0) and (player.mo ~= nil) then
			if fuel >= boost_fuel_cost then
				boostlock = 1
				P_SpawnGhostMobj(player.mo)
				player.mo.momz = $ + 18*FRACUNIT
				S_StartSound(player.mo, sfx_pogo)
				fuel = $ - boost_fuel_cost
			end
		elseif not (player.cmd.buttons & BT_CUSTOM2) then
			boostlock = 0
		end
		
		if player.spectator then fuel = 0 end
		
	end
end)

COM_AddCommand("ringfuel", function(p, buy)
	if p.mo ~= nil then
		if buy == nil then
			CONS_Printf(p, "\x82[Jetpack]\x80 ringfuel <buy>: Takes some rings and transforms the rings into fuel.")
			CONS_Printf(p, "\x82[Jetpack]\x80 Current price: "..rings_to_fuel.." rings for "..fuel_gain.." fuel.")
			CONS_Printf(p, "\x82[Jetpack]\x80 Type 'ringfuel buy' to exchange rings for fuel at the current price.")
		elseif buy == "buy" then
			if p.mo.health > rings_to_fuel then
				fuel = $ + fuel_gain
				p.mo.health = $ - rings_to_fuel
				p.health = $ - rings_to_fuel
			else
				CONS_Printf(p, "\x82[Jetpack]\x80 You don't have enough rings to buy fuel!")
			end
		else
			CONS_Printf(p, "\x82[Jetpack]\x80 Invalid syntax. Try again.")
		end
	else
		CONS_Printf(p, "\x82[Jetpack]\x80 Get out of spectator before executing this command!")
	end
end, 0)

COM_AddCommand("fuelprice", function(p, ring, gas)
	ring = tonumber(ring)
	gas = tonumber(gas)
	if ring == nil then
		CONS_Printf(p, "\x82[Jetpack]\x80 fuelprice <rings for fuel> <fuel gain>: Sets how much rings do you need for X fuel.")
	end
	if (ring ~= nil) and (gas ~= nil) then
		rings_to_fuel = ring
		fuel_gain = gas
		CONS_Printf(p, "\x82[Jetpack]\x80 New price: "..rings_to_fuel.." rings for "..fuel_gain.." fuel.")
	end
end, 1)

COM_AddCommand("boostprice", function(p, boost)
	boost = tonumber(boost)
	if boost == nil then
		CONS_Printf(p, "\x82[Jetpack]\x80 boostprice <self-descriptive>: Sets how much rings do you need for a quick boost.")
	end
	if (boost ~= nil) then
		boost_fuel_cost = boost
		CONS_Printf(p, "\x82[Jetpack]\x80 Now every boost costs "..boost_fuel_cost.." fuel. ur 3 expensiv bruh")
	end
end, 1)

local function hud_drawfuel(v, stplyr)	
	for player in players.iterate
		v.drawString(15, 72, "JETPACK", V_SNAPTOLEFT)
		v.drawString(15, 80, "FUEL", V_SNAPTOLEFT)
		v.drawNum(45, 90, fuel, V_SNAPTORIGHT)
	end
end
hud.add(hud_drawfuel)

In singleplayer, it works like a charm.

But in multiplayer, the script goes downhill:
Supposedly, when you hold down the custom button 1 you start draining YOUR fuel to be propelled upwards, but for some reason the entire fuel meter is drained for the entire server (and the one using it gets propelled upwards). If you have 600 fuel, someone else can use every bit of it.
It's like it is being synchronized to everyone.

And if that wasn't enough, the variable that is supposed to lock the custom button 2 to not be held, doesn't lock it and you can easily drain the fuel by holding it.

I'm doing something wrong that I didn't notice?
 
I'm a little rusty with Lua and just skimmed over the script, so bear with me if I'm wrong.

You need to use a MobjThinker + MT_PLAYER for this instead of a ThinkFrame + for player in players.iterate combo.

And for the HUD, you don't need the for player in players.iterate.

for player in players.iterate iterates it for all players in the map, making all variables and functions and whatnot universal.

So instead of your current addHook("ThinkFrame", function() do ... end) setup, you should use addHook("MobjThinker", function(mobj) do local player = mobj.player ... end)

Here's your script with the ThinkFrame hook changed into a MobjThinker as well as the removal of the for player in players.iterate in the HUD code:

Code:
local boostlock = 0
local fuel = 0
local boost_fuel_cost = 30
local rings_to_fuel = 10
local fuel_gain = 75

addHook("MobjThinker", function(mobj)

local player = mobj.player

        if boost_fuel_cost == nil then
            boost_fuel_cost = server.boost_fuel_cost
            --print("I'm NIL")
        end
        
        -- Jetpack action
        if (player.cmd.buttons & BT_CUSTOM1) and (player.mo ~= nil) then
            if fuel > 0 then
                P_SpawnGhostMobj(player.mo)
                if player.mo.momz < 20*FRACUNIT then
                    player.mo.momz = $ + 1*FRACUNIT
                end
                S_StartSound(player.mo, sfx_buzz4)
                fuel = $ - 1
            end
        end
        
        -- Boost action - Like, you press it once to throw yourself upwards.
        if (player.cmd.buttons & BT_CUSTOM2) and (boostlock == 0) and (player.mo ~= nil) then
            if fuel >= boost_fuel_cost then
                boostlock = 1
                P_SpawnGhostMobj(player.mo)
                player.mo.momz = $ + 18*FRACUNIT
                S_StartSound(player.mo, sfx_pogo)
                fuel = $ - boost_fuel_cost
            end
        elseif not (player.cmd.buttons & BT_CUSTOM2) then
            boostlock = 0
        end
        
        if player.spectator then fuel = 0 end
        
    end
end, MT_PLAYER)

COM_AddCommand("ringfuel", function(p, buy)
    if p.mo ~= nil then
        if buy == nil then
            CONS_Printf(p, "\x82[Jetpack]\x80 ringfuel <buy>: Takes some rings and transforms the rings into fuel.")
            CONS_Printf(p, "\x82[Jetpack]\x80 Current price: "..rings_to_fuel.." rings for "..fuel_gain.." fuel.")
            CONS_Printf(p, "\x82[Jetpack]\x80 Type 'ringfuel buy' to exchange rings for fuel at the current price.")
        elseif buy == "buy" then
            if p.mo.health > rings_to_fuel then
                fuel = $ + fuel_gain
                p.mo.health = $ - rings_to_fuel
                p.health = $ - rings_to_fuel
            else
                CONS_Printf(p, "\x82[Jetpack]\x80 You don't have enough rings to buy fuel!")
            end
        else
            CONS_Printf(p, "\x82[Jetpack]\x80 Invalid syntax. Try again.")
        end
    else
        CONS_Printf(p, "\x82[Jetpack]\x80 Get out of spectator before executing this command!")
    end
end, 0)

COM_AddCommand("fuelprice", function(p, ring, gas)
    ring = tonumber(ring)
    gas = tonumber(gas)
    if ring == nil then
        CONS_Printf(p, "\x82[Jetpack]\x80 fuelprice <rings for fuel> <fuel gain>: Sets how much rings do you need for X fuel.")
    end
    if (ring ~= nil) and (gas ~= nil) then
        rings_to_fuel = ring
        fuel_gain = gas
        CONS_Printf(p, "\x82[Jetpack]\x80 New price: "..rings_to_fuel.." rings for "..fuel_gain.." fuel.")
    end
end, 1)

COM_AddCommand("boostprice", function(p, boost)
    boost = tonumber(boost)
    if boost == nil then
        CONS_Printf(p, "\x82[Jetpack]\x80 boostprice <self-descriptive>: Sets how much rings do you need for a quick boost.")
    end
    if (boost ~= nil) then
        boost_fuel_cost = boost
        CONS_Printf(p, "\x82[Jetpack]\x80 Now every boost costs "..boost_fuel_cost.." fuel. ur 3 expensiv bruh")
    end
end, 1)

local function hud_drawfuel(v, stplyr)    
    v.drawString(15, 72, "JETPACK", V_SNAPTOLEFT)
    v.drawString(15, 80, "FUEL", V_SNAPTOLEFT)
    v.drawNum(45, 90, fuel, V_SNAPTORIGHT)
end

hud.add(hud_drawfuel)
That should fix your problem.

A little bit extra: lemme give you an explanation about the effect of the players iterate. Again, a little rusty here. It's a command, but it applies to everything.

Code:
-- Example command: "transform" transforms the player that uses it.

COM_AddCommand("transform", function(player)
    if player.mo
        P_DoSuperTransformation(player, true)
    end
end)
versus

Code:
-- Example command: "transform" transforms everyone into their super form.

COM_AddCommand("transform", function(player)
    for player in players.iterate
        if player.mo
            P_DoSuperTransformation(player, true)
        end
    end
end)
Hope this helps.
 
While it is preferred to use a MobjThinker for things that affect specific objects, that's far from the problem here. The problem is the variables declared in the top of the function, which apply to everyone.

SRB2 doesn't do multiplayer by synchronizing positions or anything like that. What it does is collect inputs from everyone, send them out, and run the exact same game on everyone's ends, doing synchronization checks when necessary. It's kind of like how netplay hacks in emulators work. This idea of running the exact same game for each player extends to Lua scripts; if you tell it to alter a local variable, it'll alter that local variable for everyone. (The values aren't synced up on join, though, due to the hook you'd use to selectively do that being broken in current SRB2. :<)

If you want to store the value once per each player, store it as a variable in the player itself. Both player_t structs and mobj_t structs have Lua support to make any arbitrary key in their tables that's only accessible through Lua, and you can take advantage of this to store whatever info you'd like. If you want the info to stay through respawns and (automatic) map changes, put the value in the player struct, like player.fuel. If you want it to reset on each spawn, put it in the mobj struct, like player.mo.fuel. You can do this for either of those (though not for many other data types, though there are solutions for those), and can freely read/write to any variable not already specified by vanilla SRB2. (If you're going to use a bunch of variables per player, I'd recommend declaring a subtable to set them in, like player.giveThisTableAName = {}, and then using player.giveThisTableAName.whatever to set variables.)

For the HUD, you don't want to use a for player in players.iterate loop, unless your specific intent is to render info for everyone. The stplyr argument passed into the HUD function (or whatever you call it) contains the current player the function is being called for, so just remove the lines that encase the loop and replace all mentions of player with stplyr (or rename the argument declaration itself) and it'll work like you want it to.
 
Last edited:
Thanks for the explanation! However I don't understand this one part:

For the HUD, you don't want to use a for player in players.iterate loop, unless your specific intent is to render info for everyone. The stplyr argument passed into the HUD function (or whatever you call it) contains the current player the function is being called for, so just remove the lines that encase the loop and replace all mentions of player with stplyr (or rename the argument declaration itself) and it'll work like you want it to.

You mean I should do "hud.add(hud_drawfuel, whoever)"?

I want to draw this part of the HUD to every player
 
Last edited:
No, in the function itself, hud_drawfuel(v, stplyr). The game passes in the display player to the argument currently named stplyr. You can use that to get the player struct of whoever's playing on the screen you're drawing to. HUD rendering functions are executed separately for each player, intended to show only that player's information; it's the only part of the game not run exactly the same for each player. Making the function render only the current player's information is usually what you want.

Also, the V_SNAPTO* flags don't do what you're assuming they do. Numbers align to the right automatically, and string alignment is determined with a fifth parameter that can be "left", "right", "center", or other things. (It defaults to "left" though, so you don't really need to change it.) What the flags actually do is control how the game handles resolutions that aren't an even multiple of 320x200; by default, the game centers this grid on the screen, multiplying it by integer values only to make it as big as possible without being bigger than the screen. V_SNAPTO* flags basically shift this grid to an edge of the screen; V_SNAPTOLEFT shifts it to the left edge, for instance. (These flags don't affect anything in recommended resolutions. Also because of weird handling at the moment, OpenGL just stretches the grid to fill the screen, distorting if needed, so these flags have no effect there either?)

yLfyMmq.png
 
Last edited:
I see.

I discovered flags won't affect the numbers when I already tried these, but I prefer having it as-is so I don't get confused.

Also, based on what you say, you mean that V_SNAPTORIGHT doesn't start "writing" the string from the right? Like arabic text? I always thought that it did that.
 
Nope, it's simply a flag to adjust the grid alignment of the starting point where HUD items are rendered from. The actual string alignment isn't affected by them. The flags do affect how HUD items align in resolutions that aren't green, so don't use them as just a convenience marker.
 
So you're saying that basically the flag (i.e V_SNAPTORIGHT) shifts the (i.e) X origin (or 0, or where X would be 0) to the right, or something like that, but when the resolution of the screen IS NOT one of the green recommended resolutions?

By looking (twice) to the image you posted earlier, I understood that by default the string is set to the center and when used with V_SNAPTORIGHT the origin of X (0) is shifted a bit to the right as if it were the "new center", am I correct?


Sorry for the multiple questions of the same thing, I still don't understand for some reason. English is not my native language.
 
That's basically how it works. Without any flags, 0 would the the upper/left edge of that dark box, and 320/200 would be the lower/right edge of that box. Applying one of those flags would shift the bounds to match the box of the matching color. (And then of course, you can combine a vertical one with a horizontal one to move the bounding box to a corner.)

The way strings work is that the position you give it (after being offset by the flags above) is the "hot spot" of the string. What that is depends on the alignment: by default ("left"), it's the upper-left corner of the string. With "center" alignment, it's the center of the upper edge of the string. And with "right" alignment, it's the upper-right corner of the string. (I'm not sure how well the latter two behave with multi-line strings, though; if you have weird alignment issues then make each line separate or something?)

If you still don't quite get it, write a script that renders some things to the corners of the screen, then make a toggle that toggles applying each of the flags to all of the things being rendered. Then see what toggling each one does at a variety of non-standard resolutions. (I find 800x600 a good resolution to spot this effect, since the grid range is tiny compared to the screen.)
 
Well okay, I'll try.
I might as well draw some coloured dots on Paint, put them in a WAD and force these to be rendered on all the corners as patches.

Thanks Red.
 
Status
Not open for further replies.

Who is viewing this thread (Total: 1, Members: 0, Guests: 1)

Back
Top