51

hmm Maybe add C.update(); before declaring dt? It won't solve the problem, but it might give a clue.
Actually, try C.update() and C.tick(), just to see if any of them work. If both give errors, it doesn't say much, but if one or neither gives an error, then that might give an idea of where the real problem is.
All I can think of is that there's a syntax error of some kind somewhere, but such that the compiler doesn't notice—an out-of-place brace, for example. That doesn't seem quite right, though, because time and delay are the very first things in the class. hmm

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

52

So after adding C.tick() and C.update(), the error about time and delay is gone.  Weird.  I don't understand that.

Thumbs up

53

Yeah, I don't get that, either. I'm tempted to think it might be a bug in BGT itself. When you got the error, where was the dt line? Just to see if I can reproduce the error.

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

54

Ok, dammit to hell, just as soon as I think I have shit ironed out, more shit crops up, lol.  Now, I keep getting unitialized variable messages, but I shouldn't be, because I'm performing legal operations on vectors here:
Line: vector acc((NetF.x/M2), (NetF.y/M2),(NetF.z/M2)*DT);
So I assign each component.  Compiler says:
Error: Use of uninitialized global variable 'NetF'.
NetF looks like this:
vector NetF = Strength+lift-drag;
Again, I thought I could do that legally, i.e.:
vector billow = pillow+willow;
That's all I've done, is assign NetF the combined values of the other vectors.  That should be legal.  I'm having the same issue with my velocity vector:
Line: vector vel = (acc/M2)*DT;
Error: Use of uninitialized global variable 'acc'.
But that's crap:
vector acc((NetF.x/M2), (NetF.y/M2),(NetF.z/M2)*DT);
What the fuck?  From what I understood, all of those operations are legal, since all I am doing is assigning values based on basic math on the other vectors, which is absolutely legal.  I could say:
vector billow = pillow+willow, or pillow/willow, or hell, even (pillow*willow)*power(fluffy,2).
Just as an experiment, I changed the vectors to arbitrary numeric values, i.e.:
vector vel(5, 3, 2);
vector acc(3, 3, 3);
vector NetF(12, -2, -5);
And then it worked.  But why is it not letting me do the legal vector math?  I'm starting to wonder whether something got messed up in the compiler, since yesterday I was getting that clock error message, when time and delay were quite obviously members of clock.

Thumbs up

55

I declared DT at the top of math:  double DT = 0.001*C.time.elapsed;
Which reminds me.  Should I use time.elapsed there, or C.delay?

Thumbs up

56

You should use C.delay.
The uninitialized global variable thing is... weird, but I'm getting the feeling it might be related to the problem with the clock earlier. If I'm right, it's something to do with the order in which the compiler does things, which might have made sense in C 30 years ago but is kinda perplexing now. It isn't about the vector additions directly; you are correct that those are valid.
I could be way off, here, but it use to be required to declare a variable or function separately from assigning it a value. BGT had something like this with member variables in early versions. If it's compiling with that paradigm, it might be that you've found cases where you can reference something after the compiler has declared the variable, but before it's initialized it. That seems unlikely, but that's what is coming to mind, here. hmm
, but keep in mind this is just wild speculation, and I'll have to try to replicate it somehow.

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

57

So I tried this:
vector vel;
then a few lines down:
vel = (acc/M2)*DT;
And the compiler now says:
Line: vector vel = (acc/M2)*DT;
Error: Name conflict. 'vel' is a global property.
So here's what I am going to do.  I'm going to paste the entirety of my math code here, so that you can see exactly how I have things, because maybe I'm losing my mind and doing something really fucking stupid, and I'm just not seeing it.  Here goes, from line one:
double strength = 3;
double CT = 1.2;
float M2 = 0.058;
float x, y, z;
float d;
float roh = 1.151;
float A = 0.0033;
float CD = 0.507;
float FL;
float DX = (CD*roh*A)*power((vel.x/2),2);
float DY = (CD*roh*A)*power((vel.y/2),2);
float DZ = (CD*roh*A)*power((vel.z/2),2);
double theta;
const double pi = 3.14159265358979;
vector pos(0, 0, 0);
vector acc((NetF.x/M2), (NetF.y/M2),(NetF.z/M2));
vector vel = (acc/M2)*DT;
vector drag(DX, DY, DZ);
vector lift(5, 1, 3);  //arbitrary values for testing.
vector Strength(5, 1, 4);  //Arbitrary values for testing.
vector grav(0, 0, 9.81);
vector NetF(Strength+lift-drag);

double RTD(double theta)
{
return deg = theta*180/pi;
}

double DTR(double deg)
{
return theta = deg*pi/180;
}

vector power(vector v, double exponent)
{
    vector GetExp(power(v.x, exponent), power(v.y, exponent), power(v.z, exponent));
    return GetExp;
}

float GetDistance(vector v1, vector v2)
{
vector v;
v.x = v1.x - v2.x;
v.y = v1.y - v2.y;
v.z = v1.z - v2.z;
return d = square_root(power(v.x,2)+power(v.y,2)+power(v.z,2));
}

Thumbs up

58

I don't know how I missed this until now sad
The name conflict error happened because you only specify the type of a variable when first creating it. So the second vector vel=... line is trying to create a new variable named vel. If you remove the "vector" from that line, it will interpret it as referring to the original variable, and the conflict is resolved.
I'm not finding any obvious errors in the math code. The only thing that jumped out at me was the two strength variables, but since you capitalized the vector and not the double, there shouldn't be a name conflict. You'd just need to remember which you need in context.
So, yeah, still no idea what the cause of the previous problem is. hmm

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

59

Ok, now I'm getting pissed.  I declared the damn vel vector, just like the compiler wanted, even though I'm not sure that it was necessary:
vector vel;
Then, further down:
vel = (acc/M2)*DT;
That is a simple mathematical calculation on two other variables, then assigning the vel vector that value, which is absolutely fucking legal, i.e., vector pillow = (billow+willow)*WhateverIChoose;.
And this is what the compiler says:
On line: 18 (5)
Line: vel = (acc/M2)*DT;
Error: Expected identifier
On line: 18 (5)
Line: vel = (acc/M2)*DT;
Error: Instead found '='
What...the...fuck?

Thumbs up

60

Are you doing that calculation in global scope, or in a class? Most calculations must be performed inside a function, so if that's not in a function, that's the error you'd get. If that's for the ball class, you might put it in either the constructor, or the updating. If it's for generic use, I'm not really sure. If all else fails, you can create a function specifically for initializations which can't be performed globally, but I doubt that's the best waay, here.

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

61

That calculation is done in my math.bgt file:
double CT = 1.2;
...
double theta;
double deg;
const double pi = 3.14159265358979;
vector vel;
vector pos(0, 0, 0);
vector acc = NetF/M2;
vel = (acc/M2)*DT;
vector drag(DX, DY, DZ);
...
I tried doing this in the ball constructor, remember?  But for whatever reason, my vectors kept getting destroyed.  So I thought if I put them in math, then they could just be held there and called whenever needed from wherever in the code.  If I am doing this wrong, and it would be better to have them in the ball class, then I need to know exactly where to put them so that they're not destroyed, and so that when I perform calculations on them in functions, the calculations actually work.

Thumbs up

62

Ok, so I have finally, finally figured it out mostly.  I created a separate little program to test all of the vectors and associated math, and to see whether the sounds would work correctly, and, yes, it works.  That being said, I see a glaring issue.
I thought that the clock was supposed to make it so that my original timer asynchronicity issue, causing the disparity in movement across devices,  would no longer be a problem.  However, I notice that even with the clock, the ball doesn't move regularly.  I have a single click sound to indicate the movement, and I have it set to only play that sound every 50 frames exactly, so theoretically, that sound should play at an exact rhythm, once a second since I have FPS set to 50.  However, it does not, which means that there are times when 50 frames go by faster, times when they're slower.  Doesn't that mean that I'm still gonna have the same discrepancy across machines that I had before?  If so, then how did the clock and FPS implementation help me?

Thumbs up

63

The original asynchronisity was due to expecting the same amount of time to pass on both machines, so discrepencies would show up. The purpose of the clock is to get them deterministic again, by converting frames into seconds, instead of measuring time directly. So, yeah, sometimes your system will experience enough of a CPU load that frames take longer than they normally would, and this would show up as lag, but you can know exactly where the ball is n frames after a force is applied to it, on both systems. The only synchronisity issue to worry about now is frame-sensitive actions.
As I understand it, the original problem was that the same event would have different outcomes on different machines, because random background events were causing enough lag to alter the amount of time between measurements. If that happens now, the worst case is that the clocks are out of sync, which is something you can compensate for. If you need time-sensitive events sent over the network, such as hitting the ball while it's in motion, you might see some discrepencies, which are generally handled by setting the clocks to match, and the faster machine rolls back to the state it was in at the frame when the event occurred. This means that both players will run into the same amount of lag, but will get identical behavior. In the case of a single moving object with an easily reversed trajectory, this isn't so big a deal. (For a scenario with a dozen objects, four of which are player-controlled, with pseudorandom numbers involved, the deal might be on the bigger side.)
I do wonder exactly how offbeat the test gets. Is there anything else going on, or is it just idly ticking? How badly is it lagging—often or rarely? Large delays (>0.1 seconds or so) or relatively small?

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

64

relatively small, yes, I'd say within 0.1-0.5 seconds when it does it.  The ball will be hit while it's in motion, yes.  I have a question.  How do I reverse the trajectory for the opponent machine?  If I have:
pos += vel*DT;
where vel is vel((acc.x/M2)*consine(theta), (acc.y/M2)*sine(theta), grav.z);
How do I reverse that?  Maybe something like:
(acc/M2)*cosine(-theta), or would it be (acc/M2)*-cosine(theta)?  Also, , should it be:
(acc.x/m2)*cosine(theta), or:
(acc.x/m2*cosine(theta))?

Thumbs up

65

Reversing it would be pos-(vel*dt*df), where df is the number of frames. The only thing to worry about is how vel changes if you need to roll back more than one frame. You could also include pos and vel in the network message, which would take away the need for more calculations. (That feels wrong for a reason I don't understand... maybe it's the increased potential for cheating?)
Another possibility would be storing copies of the last few frames of position/velocity data, and restoring to the correspondding record. This would suggest an upper bounds on how far out of sync you're willing to let the players get before correcting, but that's generally a good idea anyway. The trick here would be managing the record as it changes, what datastructure to use, that sort of thing.
Which is best isn't really specified or explained anywhere that I've come across, but I could easily have missed something. The first option means working out the calculations to avoid edge cases (once the ball is on the ground, how do you know how many frames it's been there?); the second method is much more hackable and increases bandwidth usage on the network; the third gives you more work every frame and has no defined behavior for extreme asynchronisity.

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

66 (edited by JLove 2017-09-09 07:06:58)

Ok, I'm confused.  Can you show me an example piece of code?  I think I might understand better if I see it.  Which option would you recommend?  Also, the ball isn't going to move massive amounts of squares each frame, because each frame is of shortd duration.  And how would I determine how many frames a shot would be?  The frame amount is most likely going to vary based on velocity.  I mean, the ball is going to travel further if velocity is higher given a particular shot versus another, i.e., the ball might move 10 squares for a slow shot, or 30 squares for a really hard struck, fast one.  Does that make sense?  For example, let's say my opponent hits a slow, looping ball to me, and I hit a hard, flat shot back, which would mean that my ball has more velocity, thus travelling further than his ball.  I want those calculations to be done on the fly, automatically.  Also, I assume that I am going to have to do something like switch case, for each shot I'll have to change the velocity vector, based on the net force applied to the ball.  The formula, according to the physics research I have read, is acceleration = (NetForce/mass).  Final velocity = initial velocity+acceleration*DT.  If the ball is moving, the formula to calculate the new velocity after a force is applied (in this case, a return shot) is:  (initial velocity + net force/mass)*time of contact).

Thumbs up

67

Example 3 might look like this:

class snapshot [
    vector pos, vel;
    uint frame; // This shouldn't be needed, but I'm including it in case I've overlooked something that will show up later.
    snapshot() {
     frame = C.frame;
    }
    snapshot (vector p, vector v, uint f) {
        pos = 1.0 * p;
        vel = 1.0 * v;
        frame = f;
    }
}

[email protected][] history;

[email protected] history_add (vector p, vector v, uint f=-1) {
    if (f == -1) f=C.frame;
    snapshot ret (p, v, f);
    history.insert_at(0, ret);
    // We don't want to waste RAM or allow rewinding too far, so keep the history from growing too long:
    if(history.length() > 10) history.resize(10);
    return ret;
}

// The following goes in the ball class:
    bool revert(uint f) {
        int df = C.frame-f;
        if(df<0) {
            // We don't want to skip ahead, or at most we might skip a single frame.
            // In this case, I think the correct response is to send a message to the other player's game, since the general rule is that the slower peer takes priority.
            return false;
        }
        else if(df >= history.length() ) {
            // In this case, the other player is too far behind to adjust for with this method.
            // I'd suggest sending a network event telling the other player's game to skip ahead to the latest frame you can revert to.
            // I'd suggest going with df=history.length()-1, since being this far behind suggests there might be more lag to come, and you don't want to stretch things out by the maximum in that case, otherwise you'll be constantly falling into this case.
            return false;
        }
        pos = history[df].pos;
        vel = history[df].vel;
        C.frame = f;

        while(df>0) {
            history.remove_at(0);
            df--;
        }
        return true;
    }

I lef! out the two out-ofbounds responses because they depend on how you're using the network. Those cases return false, so you could just respond to that return value by sending the "way too out of sync" message. However, you'd still need to revert in the too-far-behind case. I left out the implementation there because that's the part you'd probably wind up adjusting based on how it goes in tests.
A forced jump forward would be better executing the skipped frames in sequence, just skipping the Ctick.

I have not compiled or tested the above example; it's intended to illustrate the concept. You can try to use it as written, but it will inevitably need adjusting for details on your end I'm not fully aware of.
hth

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

68

The network looks like this in CheckEvents at the moment.  Keep in mind that I will have to restructure it because of the implementation of vectors, but you should be able to get the idea:
void CheckEvents()
{
network_event Event;
Event = Server.request();
switch(Event.type)
{
case 1:
speaker.speak("Peer has connected to server from address "+Server.get_peer_address(Event.peer_id)+".");
break;
}
case 2:
if(string_contains(Event.message, "__", 1) > -1)
{
string[] position = string_split(Event.message, "__", true);
opponent.x = string_to_number(position[0]);
opponent.y = string_to_number(position[1]);
env.update_listener_2d(opponent.x, opponent.y);
env.play_2d("sounds/step.ogg", user.x, user.y, opponent.x, opponent.y, false);
}
if(string_contains(Event.message, ",", 1) > -1)
{
string[] NewShot = string_split(Event.message, ",", true);
opponent.NewShot(string_to_number(NewShot[0]), string_to_number(NewShot[1]), true);
}
if(Event.message == "toss")
{
B.x = opponent.x;
B.y = opponent.y;
B.z = 4;
env.play_2d("sounds/toss.ogg", user.x, user.y, opponent.x, opponent.y, false);
}
if(string_contains(Event.message, "::", 1) > -1)
{
string[] serveState = string_split(Event.message, "::", true);
B.x = string_to_number(serveState[0]);
B.y = string_to_number(serveState[1]);
B.z = string_to_number(serveState[2]);
opponent.ShotStrength = string_to_number(serveState[3]);
}
break;
case 3:
speaker.speakInterrupt("Peer "+Server.get_peer_address(Event.peer_id)+" has disconnected.");
break;
}
How does reversing the frames send the ball in the opposite direction?  And I am assuming that where I send the packet, I would say something like:
if(string_contains(Event.message, "::", 1) > -1)
{
string[] ShotState= string_split(Event.message, "::", true);
{
IV = vel (acc*DT);
vel = (IV+NetF/M2)*DT;
B.pos = vel*DT*DF;
}
Not sure I got that totally correct.  In the ball move method, it would be something like:
void move(vector IV, vector acc, vector NetF, double DT)
{
DT = 0.001*C.delay;
pos += vel*DT;
//collision detection code goes here.
server.send_reliable(vector vel, C.frame);
C.tick();
C.update();
}
Am I headed in the right direction?  Not sure how much of that is correct.

Thumbs up

69

It's hard to say for sure just because Im reading on mobile, but it does look like the right direction.
As for reversing direction, flipping the sign is equivalent to moving in the oppisite direction. If you really wanted to flip the direction by using a different value for theta, you'd add or subtract pi. I'd generally just go with −1*vel, since all you have to do is change a + to a −. Or—I haven't thought about this much but the math checks out—if you want to use the move function you already, have, well, if you're technically reversing time, maybe send −1*dt? I don't know how stable that strategy would be, but I suppose that'd be ideal. I still lean toward the history method for a simple system, because entropy.

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

70

Ok.  This looks like it is going to be an extreme pain in the ass to implement.  I really hope that I gget this right.  I'm sure I'm gonna hit snags on the way.What's going to be difficult is testing, since I only have one PC.  I wonder if I can just modify it so that once the height hits 0, the height is reset, and the ball returns to the origin of the position vector, just to see if it works.  Theoretically, I should be able to do that.

Thumbs up

71

Ok, so I have tried implementing this, and I have the following error:
On line: 19 (1)
Information: Compiling [email protected] history_add(vector, vector, uint = - 1)
On line: 20 (11)
Line:     if (f == -1)
Warning: Implicit conversion changed sign of value
Is this going to be an issue, or is it normal?  If it's a problem, how do I fix it?

Thumbs up

72

Ah, that was a mistake on my part. Just change uint f to int f. Realistically it shouldn't be a problem, but better safe.

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

73

Ok, I am noticing something.  The theta value is not having any effect on the direction of movement, as it should be.  I have:
vector vel((acc.x/M2)*cosine(theta), (acc.y/M2)*sine(theta), grav.z);
Then, in move:
void move(double theta)
B.pos += vel*DT;
I have it set to print the cords of the position to a log file for me, solely for testing purposes.  Keep in mind that I have slightly broken convention, in that I use the Z axis for height instead of Y, like I have seen in many cases.  I have read that either way is correct.  If that is wrong, please let me know.  Also, please keep in mind that the numbers are going to look skewed and weird (height of 500 is huge).  Right now I am solely testing to see if movement will even work correctly; the numbers at this point are simply arbitrary.  I wanted to give the ball plenty of height to get a real feel for whether direction was being factored correctly, etc. 
With all that in mind, the first test I ran set theta to 1.  If my math is correct, that would be roughly 57 degrees, so the ball should travel in a slightly sharper diagonal angle.  For the first move, my log file shows:
0.035087719559669, 0, 499.803802490234.
Y is shown at 0 here, which means the ball is travelling totally horizontally along the X axis so far.  I expect X to increment more than Y slightly, given the 57-degree angle.  But here's the problem.  The very last line of the file, final move, shows:
9.47366905212402, 0, 447.026672363281.
Y is still 0 here, which means that the ball isn't travelling at 57 degrees, it's travelling at 90 degrees.  Next, I changed theta to 5, just to see if anything changed.  Theta of 5 is equivalent to roughly 286 degrees, I think, which means that the ball should travel to the left, very sharply diagonally.  I even let it travel longer, just to make sure.  First line of log shows:
0.035087719559669, 0, 499.803802490234.
And last line shows:
26.4209728240967, 0, 352.263275146484.
That means that again, it's travelling strictly horizontally.  So what the hell?  Is:
vector vel((acc.x/M2)*cosine(theta), (acc.y/M2)*sine(theta), grav.z);
incorrect?

Thumbs up

74

Unless your x velocity is also unaffected, the first thing I'd check is acc.y, since that's the only part of that equation which could sed vel.y to 0. The only other possibility is that theta is somehow getting set to 0 before vel can use it, but that looks unlikely based on your code.

Some of my games
Keep up to date by following @Jeqofire on twitter!
Ear Ninja?

Thumbs up

75 (edited by JLove 2017-09-12 19:37:45)

After further testing, where I have everything printed to logs, this is interesting.  I see:
theta is 5, velocity x is 1.75438594818115, velocity y is 0, and velocity z is -9.8100004196167.
Ball is now at 0.035087719559669, 0, 499.803802490234.
theta is 5, velocity x is 1.75438594818115, velocity y is 0, and velocity z is -9.8100004196167.
Ball is now at 0.070175439119339, 0, 499.607604980469.
theta is 5, velocity x is 1.75438594818115, velocity y is 0, and velocity z is -9.8100004196167.
Um, the hell?  It makes no sense to me why vel.y would be 0, since the same math applies to both x and y:
vector vel((acc.x/M2)*cosine(theta), (acc.y/M2)*sine(theta), ...);
As you can see, the same base operation applies:  (acc.x/M2), and (acc.y/M2).  That means that y should never be 0 if acc.y is not 0.  What the hell?  So I tried something.  I removed the *cosine(theta) from x, and the *sine(theta) from y.  New code looks like:
vector vel((acc.x/M2), (acc.y/M2), ...);
Now, the log shows:
theta is 5, velocity x is 1.75438594818115, velocity y is 1.75438594818115, and velocity z is -9.8100004196167.
That means that the ball will now move at exactly 45 degrees.  That makes sense, since both velocities match.  For direction control, I   thought that *cosine(theta) for X, and *sine(theta) for y, was correct?  Again, what the hell?

Thumbs up