samples/gui/OpenRider: Fixed deserialization still broken
[sdk] / samples / guiAndGfx / openrider / game.ec
1 import "math"
2
3 define PI=3.14159265358979323846;
4 define BOX_TOLERANCE=0.5;
5
6 define max_version_code_supported=0;
7 define version_name="0.0";
8    //This string should be no longer than 15 characters
9
10 public:
11
12 //Coordinates are in units of pixels
13 //Velocity is in units of pixels per frame
14 //A frame is defined as 1/LOGICAL_FPS seconds, or one iteration of g_frame()
15
16 define LOGICAL_FPS=512;
17    //is really 512, but this is for testing
18 //define DRAWN_FPS=60;
19
20 enum GameLineType {regular,speed,slow,decor};
21 enum GameFloorOrCeiling {floor,ceiling,both};
22
23 enum GameVehicleType {none, ball};
24
25 struct GameLine {
26    double x0,y0,x1,y1;
27    GameLineType type;
28    GameFloorOrCeiling floor_or_ceiling;
29 };
30 class GameVehicle {
31    //complicated vehicles that can't be described by a single location/velocity need something
32    //  so the camera will center on it.
33    Coord2D location;
34         Vector2D velocity;
35    GameVehicleType type;
36
37    void OnSerialize(IOChannel channel)
38    {
39       channel.Put(type);
40       channel.Put(location);
41       channel.Put(velocity);
42    }
43
44    void OnUnserialize(IOChannel channel)
45    {
46       if(!this)
47       {
48          GameVehicleType vtype;
49          channel.Get(vtype);
50          if(vtype == ball)
51          {
52             GameBall::OnUnserialize(class(GameBall), this, channel);
53             return;
54          }
55          else
56             this = GameVehicle { };
57       }
58
59       if(!type)
60          channel.Get(type);
61       channel.Get(location);
62       channel.Get(velocity);
63    }
64
65    virtual void Update(Game game);
66 };
67 class GameBall : GameVehicle {
68    type = ball;
69
70         double radius;
71         double elasticity;
72         double angle;
73         double angle_velocity;
74
75    void OnSerialize(IOChannel channel)
76    {
77       GameVehicle::OnSerialize(channel);
78       channel.Put(radius);
79       channel.Put(elasticity);
80       channel.Put(angle);
81       channel.Put(angle_velocity);
82    }
83
84    void OnUnserialize(IOChannel channel)
85    {
86       this = GameBall { };
87       GameVehicle::OnUnserialize(channel);
88       channel.Get(radius);
89       channel.Get(elasticity);
90       channel.Get(angle);
91       channel.Get(angle_velocity);
92    }
93
94    //support backtracing if ball is within box for line
95    void Update(Game game) {
96       //TODO:  check for collision with other balls
97       double time_left=1.0;
98       BallLineReturn soonest = {INF,{0,0}};
99       GameLine *soonest_line;
100       Vector2D vb,vp;
101       BallLineReturn bl_result;
102       uint max_collisions_per_frame=16;
103       do
104       {
105          double x0 = location.x - radius;
106          double y0 = location.y - radius;
107          double x1 = location.x + radius;
108          double y1 = location.y + radius;
109          uint n=0;
110          if (velocity.x<0)
111             x0 += velocity.x;
112          else
113             x1 += velocity.x;
114          if (velocity.y<0)
115             y0 += velocity.y;
116          else
117             y1 += velocity.y;
118          game.FindLinesInBox(x0,y0,x1,y1, true);
119          soonest_line=null;
120          for (i:game.linesInBox)
121          {
122             double t;
123             n++;
124             //TODO:  check for floor/ceiling/decor
125             if (i.type == decor)
126                continue;
127             vb = {i.x1-i.x0,i.y1-i.y0};
128             vp = {i.x0-location.x,i.y0-location.y};
129
130             BallLineCollide(bl_result,radius,velocity,vb,vp);
131
132             t=bl_result.t;
133             //TODO:  Find a better way to deal with the teleport problem when ball sits in weird situations
134             //  Currently, I'm just filtering out outrageously low negative ts (negative ts don't exactly make sense, but they keep the ball from falling through lines)
135             //if (t<=time_left && t<soonest.t) {
136             if (t>-100.0 && t<=time_left && t<soonest.t) {
137                soonest = bl_result;
138                soonest_line=&i;
139                //printf("Collide with line %ld Q=(%f,%f,%f,%f) L=(%f,%f,%f,%f)\n", soonest_line-game.lines.array, x0,y0,x1,y1, );
140             }
141          }
142          if (soonest_line)
143          {
144             Vector2D v_parallel, v_normal;
145             Vector2D v_parallel_normalized;
146             double dp;
147             double elasticity_cur;
148             bool clockcont = false; //if positive angle velocity contributes to speed toward parallel of ball velocity
149
150             //printf("COLLIDE! (location=(%f, %f), p = <%f, %f>)\n",location.x, location.y, soonest_line->x0-location.x,soonest_line->y0-location.y);
151
152             //if (soonest.t<0.0)
153             //   PrintLn("Negative time ", soonest.t);
154             //if (soonest.t>=0.0 && soonest.t<0.01)
155             //   PrintLn("Negative time after subtract", soonest.t);
156
157             soonest.t-=0.01; //don't fall all the way in, or we'll have multiple collisions after this
158
159             //compensate for the time taken to get to the collision
160             location.x += velocity.x*soonest.t;
161             location.y += velocity.y*soonest.t;
162             time_left -= soonest.t;
163
164             //decompose into parallel and normal vectors (with relation to line of collision)
165             v_parallel = soonest.s;
166             dp = DotProduct(velocity, soonest.s);
167             v_parallel.x *= dp;
168             v_parallel.y *= dp;
169             v_normal.Sub(velocity, v_parallel);
170
171             /* //elastic collision:  v' = vp-e*vn;
172             v_normal.x *= elasticity;
173             v_normal.y *= elasticity;
174             velocity.Sub(v_parallel,v_normal);
175             */
176
177             if (v_normal.y!=0.0 && v_parallel.x!=0.0) {
178                if ((v_normal.y>0 && v_parallel.x>0) || (v_normal.y<0 && v_parallel.x<0))
179                   clockcont = true;
180             } else {
181                if ((v_normal.x<0 && v_parallel.y>0) || (v_normal.x>0 && v_parallel.y<0))
182                   clockcont = true;
183             }
184
185             //we need a reference to this before v_normal gets zeroed if elasticity_cur is 0.
186             v_parallel_normalized = {-v_normal.y, v_normal.x};
187             v_parallel_normalized.Normalize();
188
189             elasticity_cur = -elasticity;
190             if (soonest_line->type != regular)
191                elasticity_cur *= 0.3; //to make speed lines easier to use
192
193             //elastic collision:  multiply the normal by negative elasticity
194             v_normal.x *= elasticity_cur;
195             v_normal.y *= elasticity_cur;
196
197             //an attempt at the effects of angular velocity and friction
198             {
199                double gs; //goal velocity squared
200                double g; //goal velocity
201                double gsfa; //goal velocity contributed by angular velocity
202                bool gneg = false; //g is negative
203                double dampener = 0.01;
204
205                gs = 3*DotProduct(v_parallel,v_parallel);
206                gsfa = 2*angle_velocity*angle_velocity*PI*radius*radius*radius;
207                gsfa *= dampener;
208                if (angle_velocity<0)
209                   gsfa = -gsfa;
210                if (clockcont)
211                   gs += gsfa;
212                else
213                   gs -= gsfa;
214                gs /= 3+(2*PI*radius*dampener);
215                if (gs < 0.0) {
216                   gneg = true;
217                   gs = -gs;
218                }
219                g = sqrt(gs);
220                if (gneg)
221                   g = -g;
222                g *= 0.9993; //energy loss
223                v_parallel = v_parallel_normalized;
224                if (clockcont) {
225                   v_parallel.x = -v_parallel.x;
226                   v_parallel.y = -v_parallel.y;
227                }
228                v_parallel.x *= g;
229                v_parallel.y *= g;
230                if (clockcont)
231                   angle_velocity = g/radius;
232                else
233                   angle_velocity = -g/radius;
234             }
235
236             velocity.Add(v_parallel, v_normal);
237
238             //at last, we'll handle speed/slow lines by simply changing the angular velocity
239             if (soonest_line->type == slow)
240                angle_velocity -= 0.001;
241             else if (soonest_line->type == speed)
242                angle_velocity += 0.001;
243          }
244          else
245             location.Add(location,velocity);
246          if (!max_collisions_per_frame--)
247             break;
248       } while (soonest_line);
249       velocity.Add(velocity, game.gravity);
250       angle += angle_velocity;
251       if (angle<0 || angle>=2*3.1415926535897932384626)
252                    angle = fmod(angle,2*3.1415926535897932384626); //prevents angle from accumulating an insanely high or low value
253    }
254 };
255
256 class Game {
257 public:
258    Instance master;
259    Array<GameLine> lines {};
260    Array<GameLine> linesInBox {};
261    Array<uint> linesInBoxIndices {};
262    Array<GameVehicle> vehicles {};
263    Vector2D gravity;
264
265    uint version_code; //which version of OpenRider to act like
266
267    version_code = max_version_code_supported;
268
269    ~Game() { FreeAll(); }
270    void FreeAll(void) {
271       for (i:vehicles)
272          delete i;
273       //we don't need to worry about the other things because they are automatically freed
274    }
275
276    void OnUnserialize(IOChannel channel)
277    {
278       FreeAll();
279
280       channel.Get(gravity);
281       channel.Get(lines);
282       channel.Get(vehicles);
283    }
284
285    void OnSerialize(IOChannel channel)
286    {
287       channel.Put(gravity);
288       channel.Put(lines);
289       channel.Put(vehicles);
290    }
291
292    void Init(void) {
293       gravity = {0, 0.002};
294       Clear();
295       ResetBall();
296    }
297    void Clear(void) {
298       lines.size = 0;
299       version_code = max_version_code_supported;
300    }
301    void ResetBall(void) {
302       for (i:vehicles)
303          delete i;
304       vehicles.size = 1;
305
306       vehicles[0] = GameBall {
307          location = {-300+32,-200+32};
308          velocity = {0.0, 0.0};
309          radius = 24.0;
310          elasticity = 0.3; //if this is 0, the ball rolls the wrong way for some reason
311          angle = 0.0;
312          angle_velocity = 0.0;
313       };
314    }
315    void Frame(void)
316    {
317       for (i:vehicles)
318       {
319          GameVehicle v = i;
320          v.Update(this);
321       }
322    }
323    void FrameMulti(uint count) {
324       while (count--)
325          Frame();
326    }
327    //parameters are the camera's field of vision or bigger
328    void DrawFrame(double x0, double y0, double x1, double y1) {
329       FindLinesInBox(x0,y0,x1,y1, true);
330       for (i:linesInBox)
331          DrawLine(master, i);
332         for (i:vehicles) {
333          if (i.type==ball)
334                    DrawBall(master, (GameBall)i);
335       }
336    }
337
338    void AddLineSeries(double c_array[],uint count) {
339       double *c=c_array;
340       if (count<2)
341         return;
342       count--;
343       for (;count--;c+=2)
344          lines.Add({c[0],c[1],c[2],c[3],regular,floor});
345    }
346
347    //returns true if any lines were erased
348    bool EraseAtBox(double x0, double y0, double x1, double y1) {
349       uint erasures = 0;
350       FindLinesInBox(x0,y0,x1,y1, false);
351       //the linesInBoxIndices array is assumed to be sorted
352       for (i:linesInBoxIndices) {
353          uint r = i-erasures;
354          //FindLinesInBox only gives us an estimate of lines in the box, so we will do a finer check here
355          if (!LineReallyInBox(lines[i].x0, lines[i].y0, lines[i].x1, lines[i].y1, x0,y0,x1,y1))
356             continue;
357          //lines.Remove((IteratorPointer)(lines.array+i-erasures));
358          memmove(lines.array+r, lines.array+r+1, (lines.size-r-1)*sizeof(*lines.array));
359          erasures++;
360       }
361       lines.size -= erasures;
362       return !!erasures;
363    }
364
365    //used for efficiency, not for precision
366    //This only checks for lines in box by looking at the bounding boxes of lines in question
367    void FindLinesInBox(double x0, double y0, double x1, double y1, bool tolerance) {
368       #define Swap(a,b) {tmp=a;a=b;b=tmp;}
369       double tmp;
370       if (x0>x1)
371          Swap(x0,x1);
372       if (y0>y1)
373          Swap(y0,y1);
374       if (tolerance) {
375          x0 -= BOX_TOLERANCE;
376          y0 -= BOX_TOLERANCE;
377          x1 += BOX_TOLERANCE;
378          y1 += BOX_TOLERANCE;
379       }
380       linesInBox.minAllocSize = lines.count;
381       linesInBoxIndices.minAllocSize = lines.count;
382       linesInBox.RemoveAll();
383       linesInBoxIndices.RemoveAll();
384       for (i:lines) {
385          double lx0=i.x0,ly0=i.y0,lx1=i.x1,ly1=i.y1;
386          if (lx0>lx1)
387             Swap(lx0,lx1);
388          if (ly0>ly1)
389             Swap(ly0,ly1);
390          if (lx0>x1 || ly0>y1 || x0>lx1 || y0>ly1)
391             continue;
392          linesInBox.Add(i);
393          linesInBoxIndices.Add(&i - lines.array);
394       }
395       #undef Swap
396    }
397
398    virtual void Instance::DrawLine(GameLine line);
399    virtual void Instance::DrawBall(GameBall ball);
400 }
401
402 /*void g_init(void);
403 void g_uninit(void);
404 void g_frame(void);
405 void g_frame_multi(uint count);
406 void g_draw_frame(void);
407
408 void g_add_line(double x0,double y0,double x1,double y1,char type,char floor_or_ceiling);
409 */