#1 2016-09-29 17:45:51

RaXaR
Member

HUD displays wrong stat value.

I have a strange issue. I've re-enabled the HUD display and for some reason it is printing the wrong value to screen. Here's what I did first:

In game.h:
- struct attackinfo {} I set the rail to do 20 damage.
- struct gamestate {} I set maxhealth(100) in the constructor.
- gameent::startgame() I change maxhealth = 1; to health = maxhealth;

In game.cpp:
- drawhudicons() I move "#if 0" down to comment out icons bit. Now the health text prints the value of d->health.

In server.cpp:
- server::servstate::reset() I comment out maxhealth = 1;

----

When I build and run the game, I start a New Map and spawn a bot. When it shoots me with a rail gun, I take 20 damage, the health decreases and the HUD updates correctly.
So far so good.

Here's the problem. When I add "shield" and "shieldMax" properties and then try to decrease it, the drawhudicons() method always shows the initial value. But in the background it decreases correctly when taking damage. Here's what I did second:

----

In game.h:
- struct gamestate{} I add "int shield, shieldMax;" below the health entry.
- in the gamestate constructor I set shieldMax(50).
- gamestate::respawn() I add shield = shieldMax; below the health entry.
- gamestate::dodamage() I replace with:
    int dodamage(int damage)
    {
        if (shield > 0) {
            shield -= damage;
            if (shield < 0)
                shield = 0;
        } else {
            health -= damage;
        }
        return damage;
    }

----

Now when I build -> run -> New Map -> spawn bot -> take damage. The shield value decreases but the HUD does not show the new value. I suspect that in the drawhudicons() method, d->shield is a value retrieved from the server or something, but I'm not sure. Anyone know what I'm missing here?

Offline

#2 2016-09-29 19:07:42

Hypernova^
Member

Re: HUD displays wrong stat value.

All damage is transferred through the server. The server must know about your shield to have correct effect.

Last edited by Hypernova^ (2016-09-29 19:08:30)

Offline

#3 2016-10-02 21:04:17

chasester
Member

Re: HUD displays wrong stat value.

what you are looking for is called a game event. Basically the server is set up with "event based" controls meaning the client tells when where what and the the server processes them in that order so that things look correct, client does prediction between frames that it doesnt get server updates. etc.

this function is the client prediction. We see damage we apply damage, but the server will actually send all the accurate info every frame (player pos, health, direction etc .. oddly not ammo :P). So to fix this you will simply need to write the same code in the server, then send all the info to the player. This means that your buffer (the list of commands and ints etc) will be bigger than in all other "vanilla" instances of the game. Meaning if you have some one having a "vanilla" tesseract it will force them to disconnect after the first frame buffer, with a "Bad data from server" or similar disconnect error.

Keep in mind there are 2 server.cpp files, we want the one that includes game.h not engine.h. This other one is the code for the shell that runs a deicated server (a server that's not attach to a client, ill explain this concept later).

in Server.cpp

So to explain this a little before i tell you how it works.
Struct gameevent is the base class which all events inherent from (well save editevents, but thats a different topic). Basically this is just one forced virtual and 2 optional ones.

  struct gameevent
    {
        virtual ~gameevent() {}

        virtual bool flush(clientinfo *ci, int fmillis);
        virtual void process(clientinfo *ci) {}

        virtual bool keepable() const { return false; }
    };

this is like the container your events are stored in till you process them. So you read the buffer store it in the correct event type and then when its time to process this event you pull it out of the stack process it and either flush it (basically delete it) or if it's keepable you keep it.

Shotevent is inherent of time event which adds one element millis (basically when) cuz some events need to know what order they happen in.

The main two for weapons is shotevent and explode event.

Another element needed will be the servstate which inherents gamestate (which is one of the elements your player inherits from) this just basically outlines basics of the player like health curgun amo ai(or not) and has most the basic functions for base gameplay. This means that the first thing we need to do is add shield into this class as a int (yes an int cuz reasons :P [mainly cuz it will make adding to the buffer easier cuz floats cant be added easily (cuz reasons)]).

So after we add the new variable int sheild we may wanna add max shield as well just incase we have variable player classes or over shield or really a million other reasons.


Next is client info this is a container that holds each player in ther server (wether they are spectating or playing or editing etc) this allows us to add events to it, so we order our events then process them. Not important for this but good to know. Keep in mind it goes clientinfo owns serverstate, and owns a list of events. So ci (which it is mostly called) will be your base container for all items pertaining for a client.

so there is a logic error with your code, in the case that shield < damage then the extra damage does not take away heath. Im gonna assume this is not your intent and fix this with shield depletes first then health. If this is false just fix what I wrote.
ln 2071

void dodamage(clientinfo *target, clientinfo *actor, int damage, int atk, const vec &hitpush = vec(0, 0, 0))
    {
        servstate &ts = target->state;
        ts.dodamage(damage); // this is the function where the damage happens 0.0 odd it has the same name ;P
        if(target!=actor && !isteam(target->team, actor->team)) actor->state.damage += damage;
       sendf(-1, 1, "ri5", N_DAMAGE, target->clientnum, actor->clientnum, damage, ts.health, ts.sheild);
// this is where we sent the info to the client. keep in mind we added a variable here we ill need to fix the buffer, so that the cleint knows to read this data
        if(target==actor) target->setpushed();
...
    }

in game.h
ln 378

int dodamage(int damage)
    {
       shield -= damage;
       health += shield < 0 ?  shield : 0;
       if(shield < 0) shield = 0; // way easier
    }

Now its time to correct the buffer so that it includes shields (so every one is on the same page :)

game.h
ln 251
N_DIED, 5, N_DAMAGE, 6, N_HITPUSH, 7, N_SHOTFX, 10, N_EXPLODEFX, 4,

change N_DAMAGE from a 5 message buffer to a 6 (so now we have enough space
client.cpp
ln 1563
change under

 case N_DAMAGE:
     {
int tcn = getint(p),
                    acn = getint(p),
                    damage = getint(p),
                    health = getint(p),
                    shield = getint(p);
            //add a get to pull an int from the buffer and add it to a temporary variable.
...
target->shield = shield;
//now that we know the buffer is the right size lets add this to the target
// (the player we picked) and add this as their known shield.
...
}

Why do all of this work. Pretty simply. It makes things stay in tune with each other, This will stop players from hacking their health or shield, along with fix any bugs will missing packets. Basically if something goes wrong it will correct there shield back to where it should be after the next frame.

I would also add shield to the buffers for parsestate() and sendstate() under the health this should make it so shield is sent every frame, rather than just when you damage a player. This would allow for more accuracy and lets weird lag errors.

chasester

Ps: as to your hud problem. Ther sever tells the client every frame how much health it has left so its probably resetting the health before your draw function is called :p but you are voluntary killing your self when you think you die (or the server is just accurate and yours is a mirage. If you do that it should fix the hud issue :)

Last edited by chasester (2016-10-02 21:09:23)

Offline

#4 2016-10-03 21:46:58

RaXaR
Member

Re: HUD displays wrong stat value.

Aaah, gosh. I knew it had something to do with the server/client setup. Your explanation has shed some good light on what the hell is going on. Thank you so much! :)

The shield-logic as I wrote it earlier is as intended. Only when the shields are completely depleted does the player take any health damage. (Ignore damage that are more than the remaining shields).  The next two parts to the shield is "delay" and "regen".  I think these two will also be processed server-side. Off the top of my head, here's the strategy I'll try first:

For the "delay" part I'll check the value of each client's shield, if it's zero then start a timer for that client. (provided they don't have a timer running yet.). Once the timer fires then regen is allowed. Haven't decided what/if anything new should be broadcasted. Maybe not, since the servstate has all the info anyway... hmm, I'll ponder this a bit.

The actual regeneration of the shield will be controlled by the server too. Every second or so the server will increment the shield values of all clients. If the cilent has max shield or is in "delay" then ignore that shield and go on to the next in the list. This could be expanded upon, but for simplicity's sake I'll keep the increment to a constant value and interval.

Thanks again for the feedback, every little bit helps more than you realize. :)

Last edited by RaXaR (2016-10-04 08:14:33)

Offline

#5 2016-10-04 08:11:10

RaXaR
Member

Re: HUD displays wrong stat value.

Quick quesiton. How does the "lifesequence" variable work/get used. It's a bit obscure. :P

Offline

#6 2016-10-04 09:58:06

RaXaR
Member

Re: HUD displays wrong stat value.

UPDATE: I got a shield value in and working. There was a few places I had to add the parameter. Also I updated a few sendf() parameters (those "rii5vi" type parameters) to include the new fields. Everything seems to be working stable now.

Next up I'll attempt the "delay" and "regen" parts.
Also, I'm renaming this topic to better reflect what we're talking about. or not, can't seem to edit the topic header. oh  well.

Last edited by RaXaR (2016-10-04 09:59:13)

Offline

#7 2016-10-05 03:54:21

chasester
Member

Re: HUD displays wrong stat value.

Delay should be handeled on the server and the client just varifies so basically do it on both then the server will just override it if the client does it too early or too late (but all the animation and sounds should be handeled regardless if the sheild actually comes back).

I forgot about changing the "rii5vi" My bad, its been a min since i worked on server modding.

Easiest way to handel delay and regen is to do it with time variables. So add shieldmillis as a varible into the gamestate, then add a constant SHEILDREGENPERSEC in the game.h. Then simply right a fuction in the gamestate, regenshield(). This should run every frame cycle so you need to add this into the server main loop (it should be marked in comments). Something like the following:
in game.h

uchar OVERSHIELDDECAY = 5;
gamestate::regensheild()
{
     shield -= shield > maxsheild ?  OVERSHIELDDECAY : shield;
     if(shield >= maxshield) return;
     if(shieldmillis < lastmillis) return; //this mean not enough time has passed to regen shield
     shield +=  min(SHEILDREGENPERSEC, -shield  + maxshield);
      //so either add regen amout or the differents between sheild and maxshield which ever one is less
}
void dodamage(...) //in server.cpp
{
  servstate &ts = target->state;
   ts.sheildmillis = gamemillis + SHIELDREGENTIME; //set the time when the shield should regen 
   //if we get hit again then we just simply keep adding to this time.
  ts.dodamage(damage); 
   ...
}

Note: If you notice a gittering effect this may be due to the server and the client being on different times. You should move this call  to shotevent::process and explodeevent::process then uses shotevent::millis variable in place of gamemillis keep in mind this will only show variance if your lag is more than 100 or 200 ping. Since your server is on the machine you are working form there will be 0 or close to 0 ping so you may want to create artificial lag or just connect through the web rather than a direct lan connect this should raise your ping to at least 40 or 50 and you might be able to tell a difference.

So that handles the server now we need the client side.

in game.cpp

void damaged(...)
{
     if(d->state ....)
     ...
     if(h=d)
     {
         shieldmillis += lastmillis + SHEILDREGENTIME;
         ...
      }
}

That should be most of it you need. You could move the client side to where lasthit is added or a lot of other places. Keep in mind that you need this too look right in spectator too so you will have to take into account hudplayer() not just player1. So when doing all your draw calls just remember to check them in spectator mode as well to make sure they look right.

Last edited by chasester (2016-10-05 03:56:35)

Offline

#8 2016-10-11 09:02:45

RaXaR
Member

Re: HUD displays wrong stat value.

Phew, ok so based on the information you shared I eventually got this up and running in some form. I created a ShieldProp class. In /game/game.h I do

struct gamestate {
    ...
    ShieldProp *shield;
    ...
    gamestate() {
        shield = new ShieldProp(...);
    }
}

On the server-side I used /game/server.cpp/serverupdate() and loop over the clients array and run a regen() method.

void serverupdate() {
    ...
    if (shouldstep && !gamepaused) {
        loopv(clients)
            clients[i]->state.shield->regen(gamemillis);
    }
    ...
}

If enough time has passed and regen() incremented the shield, then broadcast a N_REGEN_SHIELD message with two attached values: client->clientnum and "shield amount".

    sendf(-1, 1, "rii2", N_REGEN_SHIELD, clientnum, shieldamount);


Then on the client side I used /game/client/parsemessages(...) to listen for that N_REGEN_SHIELD message. When it's received then getint(p) the client number and getint(p) the shield amount. Then apply the shield amount to the client with clientnum.

So the regen "appears" to work just fine with this setup. However, it seems very inefficient since I use sendf(-1, ....). As far as I can tell, the -1 means "send to all clients" and I really just want to send the N_REGEN_SHIELD message to the relevant client. The others don't need to know about everyone else' regen events.

I figured that I can do sendf(clientnum, ...) but then I'm not sure how to process that in client.cpp/parsemessages().

So here's the last two questions on shields:
1.) Can I use sendf(clientnum, ...) to send a N_REGEN_SHIELD message to one specific client and if so, how do I deal with it on the client-side?
2.) What is the right way to "delete" an instantiated object. In my case the gamestate->shield. I tried to use the ~gamestate() destructor with "delete shield" but that causes an assert. I also tried to use the unique_ptr() method by including "memory.h" in "cube.h" but that didn't work either.

Thanks again for all the information so far, it made things go a lot smoother.

Offline

#9 2016-10-11 15:48:29

chasester
Member

Re: HUD displays wrong stat value.

You should do a N_REGEN_START instead and just send the clientnum so that if you add an effect to shield regen (like say halo where a yellow lighting surounds the player when they start regening) you can see it on all clients.

Then you should send the shield in with the heath in the above sendstate, getstate functions I stated above. This way the shield is always updated so its always accurate on all people screens. Otherwise you may think that said player dies, lag for 500 mm and then hes alive, This could be very aggravating.

What is shieldprop ? do shield do different things based on types? if not I would just stick to an int shield; which links to a:

enum
{
 SHEILD_PROJECTILE  = 1<<0,
 SHIELD_BULLET = 1<<1,
 SHIELD_PLASMA = 1<<2,
 SHIELD_ENVIROMENT = 1<<3,
 SHEILD_MELEE = 1<<4
}
static const NUMOFSHEILDS = 4;

Static const struct shieldinfo { const char *name; int maxshield; int absorptionindex; int maxabsorption; int minabsorption;} shields[NUMOFSHEILDS]
{
       { "Aquashield", 100, SHEILD_PROJECTILE|SHIELD_PLASMA, 100, 120 },
       {"Bulletshield", 140, SHEILD_BULLET|SHIELD_MELEE, 100, 130 },
       {"ThickShield", 200, SHIELD_BULLET|SHIELD_PROJECTILE|SHIELD_PLASMA, 90, 140 },
       {"HandBreakerShield", 200, SHIELD_MELEE|SHIELD_ENVIROMENT, 100, 150 }
};

So basically when you apply damage you could take the type of damage into account. If its one of the "proficient" types then you use maxabosortions else you do minabsorption.

So it would look like

const shieldinfo s = shields[d->shield];
int absorbe;
if(typeofdamge & s.absorptionindex) absorbe = s.maxabsorption;
else absorbe = s.minabsorption;

damage *= absorbe*0.01;

//then apply the damage.

This would basically make some shields take more damage to certain types of damage.  Just an Idea on how you could implement a shield system.

Last edited by chasester (2016-10-11 15:57:37)

Offline

#10 2016-10-11 16:11:02

RaXaR
Member

Re: HUD displays wrong stat value.

The idea behind the ShieldProp class is to keep all things related to the shield in a single class and only expose methods that are actually needed to the rest of the game. Like regen(gamemillis) which internally runs the timers and this method is only called on the server-side.

In my current setup I sendf(-1, ...) the shield value every time the regen() method actually increments the shield. So it waits for 2 seconds, then increments the shield amount and then broadcasts the new value. This way the server isn't constantly spamming the same shield amount. :)

At the moment I don't care about other players perceiving if the shield is regenerating or not. so for this they really don't need to know anything about my shields or I about theirs. The only time it's relevant is when they get hit, again, when they get hit I broadcast that event with the shield amount attached.

Considering the above, this is why I'm wondering how to use sendf() properly?

Offline

#11 2016-10-12 00:39:58

RaXaR
Member

Re: HUD displays wrong stat value.

Update:
Ok I've basically made my peace with sendf() for now.  I'm using it with

sendf(-1, 1, "rii2", N_MY_MESSAGE, clientNum, myvalue);

and things seem to work out okay :P I did notice that in /game/client.cpp: void parsepacketclient(...) uses the channel number and then sets the clientnumber to -1 anyway. I'll revisit this and dig deeper at some point.

Back to Shields and HUD :)
I've read over the feedback above (multiple times) and the picture of how the server/client implementation was done is getting clearer (slowly, but surely). I've decided to not carry the shields amount with the N_DAMAGE event. I feel that N_DAMAGE should be used for one thing only - damage. (I suspect the health property has been included there due to cheaters, but not sure.)

So based on this decision I need two things to happen for correct operation:
1. When the client receives the N_DAMAGE message, it should subtract the 'damage' value from it's current shields amount. By this time the server would have done this already to it's clientinfo object.
2. At some point the client shields will have to be synced with the value in clientinfo on the server.

I already send a N_REGEN_SHIELD message, but this is only sent once every second or more. To remedy this, I added a N_SHIELD_SYNC message. This fires at shorter, regular intervals. When a client receives this message it will overwrite it's shield amount with the amount carried in this packet.

I tested my approach with high lag and everything syncs up nicely so far. There is an occasional bit of jitter but that is to be expected.

@chasester Thank you so much for all the help you've given me with this. Without your feedback I wouldn't have been able to get this far, especially in such a short amount of time.

I would like to give something back but I'm not sure how or what yet. Maybe I can contribute art or a tutorial or something, let's see.

Offline

#12 2016-10-12 02:44:46

chasester
Member

Re: HUD displays wrong stat value.

Ok how I would do it (which is not the way its done but thats cuz, cube has a funny way to do client server stuff).

  1. Client shoots and sends shot info (gun, direction, positon, time)

  2. Server checks client data (check if the gun has ammo, check if the position is close enough to the last udated position and is in the right amount of time (say player moves 10units a second and time is half a second before last update, basically just check to see if that position is reachable, if not correct this position to the correct position), and lastly check the direction against last known direction to make sure they arent aim boting (by foring the yaw and pitch to jump to the target)).

  3. Server checks to see what got hit (based on potions updated by player and moves them based on time between updates and guesses the best postion for said time. (this is interpoling similar to what the client already does).

  4. Apply all the hits and just send back who got hit and which what weapons (just so that damage sounds and blood etc [visual cues] can be applied)

  5. during the sendstate send the health and shield, this way it sends every cycle and is never wrong.

  6. If a player's shield starts to regenerate, send a message with just the clientnumber time it started. Then fix the shield number by sending it in the sendstate command. (this will reduce error cuz its only sent one time so there is no chance for it to jump oddly.

  7. Lastly for the Player (or hudplayer) set up a systems that interpoles the shield over time so that it looks fluid even if you start to lag a little. Then it fixes it as you get an update, slowly changing the hud visuals to the correct amount.

Alright thats all I have to say on this :p and your welcome, ive spent a large amount of time in the game code so I know most all elements, So if you have any question feel free to ask. For the small parts I havent looked at I love digging in and figuring out how it works and how to make it work better :)

chasester

Offline

Board footer