1 /*
2 * This file is part of NumptyPhysics
3 * Copyright (C) 2008 Tim Edmonds
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 */
16
17 #include "Common.h"
18 #include "Array.h"
19 #include "Config.h"
20 #include "Game.h"
21 #include "Overlay.h"
22 #include "Path.h"
23 #include "Canvas.h"
24 #include "Levels.h"
25 #include "Http.h"
26 #include "Os.h"
27
28 #include <SDL/SDL.h>
29 #include <SDL/SDL_image.h>
30
31 #include <cstdio>
32 #include <iostream>
33 #include <sstream>
34 #include <fstream>
35 #include <memory.h>
36 #include <errno.h>
37 #include <sys/stat.h>
38
39 using namespace std;
40
41 unsigned char levelbuf[64*1024];
42
43 class Transform
44 {
45 public:
Transform(float32 scale,float32 rotation,const Vec2 & translation)46 Transform( float32 scale, float32 rotation, const Vec2& translation )
47 {
48 set( scale, rotation, translation );
49 }
set(float32 scale,float32 rotation,const Vec2 & translation)50 void set( float32 scale, float32 rotation, const Vec2& translation )
51 {
52 if ( scale==0.0f && rotation==0.0f && translation==Vec2(0,0) ) {
53 m_bypass = true;
54 } else {
55 m_rot.Set( rotation );
56 m_pos = translation;
57 m_rot.col1.x *= scale;
58 m_rot.col1.y *= scale;
59 m_rot.col2.x *= scale;
60 m_rot.col2.y *= scale;
61 m_invrot = m_rot.Invert();
62 m_bypass = false;
63 }
64 }
transform(const Path & pin,Path & pout)65 inline void transform( const Path& pin, Path& pout ) {
66 pout = pin;
67 if ( !m_bypass ) {
68 pout.rotate( m_rot );
69 pout.translate( m_pos );
70 }
71 }
transform(Vec2 & vec)72 inline void transform( Vec2& vec ) {
73 if ( !m_bypass ) {
74 vec = Vec2( b2Mul( m_rot, vec ) ) + m_pos;
75 }
76 }
inverseTransform(Vec2 & vec)77 inline void inverseTransform( Vec2& vec ) {
78 if ( !m_bypass ) {
79 vec = Vec2( b2Mul( m_invrot, vec-m_pos ) );
80 }
81 }
82 private:
Transform()83 Transform() {}
84 bool m_bypass;
85 b2Mat22 m_rot;
86 b2Mat22 m_invrot;
87 Vec2 m_pos;
88 };
89
90 Transform worldToScreen( 0.5f, M_PI/2, Vec2(240,0) );
91
configureScreenTransform(int w,int h)92 void configureScreenTransform( int w, int h )
93 {
94 SCREEN_WIDTH = w;
95 SCREEN_HEIGHT = h;
96 FULLSCREEN_RECT = Rect(0,0,w-1,h-1);
97 if ( w==WORLD_WIDTH && h==WORLD_HEIGHT ) { //unity
98 worldToScreen.set( 0.0f, 0.0f, Vec2(0,0) );
99 } else {
100 float rot = 0.0f;
101 Vec2 tr(0,0);
102 if ( h > w ) { //portrait
103 rot = M_PI/2;
104 tr = Vec2( w, 0 );
105 b2Swap( h, w );
106 }
107 float scalew = (float)w/(float)WORLD_WIDTH;
108 float scaleh = (float)h/(float)WORLD_HEIGHT;
109 if ( scalew < scaleh ) {
110 worldToScreen.set( scalew, rot, tr );
111 } else {
112 worldToScreen.set( scaleh, rot, tr );
113 }
114 }
115 }
116
117 class Stroke
118 {
119 public:
120 typedef enum {
121 ATTRIB_DUMMY = 0,
122 ATTRIB_GROUND = 1,
123 ATTRIB_TOKEN = 2,
124 ATTRIB_GOAL = 4,
125 ATTRIB_DECOR = 8,
126 ATTRIB_SLEEPING = 16,
127 ATTRIB_HIDDEN = 32,
128 ATTRIB_DELETED = 64,
129 ATTRIB_CLASSBITS = ATTRIB_TOKEN | ATTRIB_GOAL
130 } Attribute;
131
132 private:
133 struct JointDef : public b2RevoluteJointDef
134 {
JointDefStroke::JointDef135 JointDef( b2Body* b1, b2Body* b2, const b2Vec2& pt )
136 {
137 Initialize( b1, b2, pt );
138 maxMotorTorque = 10.0f;
139 motorSpeed = 0.0f;
140 enableMotor = true;
141 }
142 };
143
144 struct BoxDef : public b2PolygonDef
145 {
initStroke::BoxDef146 void init( const Vec2& p1, const Vec2& p2, int attr )
147 {
148 b2Vec2 barOrigin = p1;
149 b2Vec2 bar = p2 - p1;
150 bar *= 1.0f/PIXELS_PER_METREf;
151 barOrigin *= 1.0f/PIXELS_PER_METREf;;
152 SetAsBox( bar.Length()/2.0f, 0.1f,
153 0.5f*bar + barOrigin, vec2Angle( bar ));
154 // SetAsBox( bar.Length()/2.0f+b2_toiSlop, b2_toiSlop*2.0f,
155 // 0.5f*bar + barOrigin, vec2Angle( bar ));
156 friction = 0.3f;
157 if ( attr & ATTRIB_GROUND ) {
158 density = 0.0f;
159 } else if ( attr & ATTRIB_GOAL ) {
160 density = 100.0f;
161 } else if ( attr & ATTRIB_TOKEN ) {
162 density = 3.0f;
163 friction = 0.1f;
164 } else {
165 density = 5.0f;
166 }
167 restitution = 0.2f;
168 }
169 };
170
171 public:
Stroke(const Path & path)172 Stroke( const Path& path )
173 : m_rawPath(path)
174 {
175 m_colour = brushColours[DEFAULT_BRUSH];
176 m_attributes = 0;
177 m_origin = m_rawPath.point(0);
178 m_rawPath.translate( -m_origin );
179 reset();
180 }
181
Stroke(const string & str)182 Stroke( const string& str )
183 {
184 int col = 0;
185 m_colour = brushColours[DEFAULT_BRUSH];
186 m_attributes = 0;
187 m_origin = Vec2(400,240);
188 reset();
189 const char *s = str.c_str();
190 while ( *s && *s!=':' && *s!='\n' ) {
191 switch ( *s ) {
192 case 't': setAttribute( ATTRIB_TOKEN ); break;
193 case 'g': setAttribute( ATTRIB_GOAL ); break;
194 case 'f': setAttribute( ATTRIB_GROUND ); break;
195 case 's': setAttribute( ATTRIB_SLEEPING ); break;
196 case 'd': setAttribute( ATTRIB_DECOR ); break;
197 default:
198 if ( *s >= '0' && *s <= '9' ) {
199 col = col*10 + *s -'0';
200 }
201 break;
202 }
203 s++;
204 }
205 if ( col >= 0 && col < NUM_BRUSHES ) {
206 m_colour = brushColours[col];
207 }
208 if ( *s++ == ':' ) {
209 float32 x,y;
210 while ( sscanf( s, "%f,%f", &x, &y )==2) {
211 m_rawPath.append( Vec2((int)x,(int)y) );
212 while ( *s && *s!=' ' && *s!='\t' ) s++;
213 while ( *s==' ' || *s=='\t' ) s++;
214 }
215 }
216 if ( m_rawPath.size() < 2 ) {
217 throw "invalid stroke def";
218 }
219 m_origin = m_rawPath.point(0);
220 m_rawPath.translate( -m_origin );
221 setAttribute( ATTRIB_DUMMY );
222 }
223
reset(b2World * world=NULL)224 void reset( b2World* world=NULL )
225 {
226 if (m_body && world) {
227 world->DestroyBody( m_body );
228 }
229 m_body = NULL;
230 m_xformAngle = 7.0f;
231 m_drawnBbox.tl = m_origin;
232 m_drawnBbox.br = m_origin;
233 m_jointed[0] = m_jointed[1] = false;
234 m_shapePath = m_rawPath;
235 m_hide = 0;
236 m_drawn = false;
237 }
238
asString()239 string asString()
240 {
241 stringstream s;
242 s << 'S';
243 if ( hasAttribute(ATTRIB_TOKEN) ) s<<'t';
244 if ( hasAttribute(ATTRIB_GOAL) ) s<<'g';
245 if ( hasAttribute(ATTRIB_GROUND) ) s<<'f';
246 if ( hasAttribute(ATTRIB_SLEEPING) ) s<<'s';
247 if ( hasAttribute(ATTRIB_DECOR) ) s<<'d';
248 for ( int i=0; i<NUM_BRUSHES; i++ ) {
249 if ( m_colour==brushColours[i] ) s<<i;
250 }
251 s << ":";
252 transform();
253 for ( int i=0; i<m_xformedPath.size(); i++ ) {
254 const Vec2& p = m_xformedPath.point(i);
255 s <<' '<< p.x << ',' << p.y;
256 }
257 s << endl;
258 return s.str();
259 }
260
setAttribute(Stroke::Attribute a)261 void setAttribute( Stroke::Attribute a )
262 {
263 m_attributes |= a;
264 if ( m_attributes & ATTRIB_TOKEN ) m_colour = brushColours[RED_BRUSH];
265 else if ( m_attributes & ATTRIB_GOAL ) m_colour = brushColours[YELLOW_BRUSH];
266 }
267
clearAttribute(Stroke::Attribute a)268 void clearAttribute( Stroke::Attribute a )
269 {
270 m_attributes &= ~a;
271 }
272
hasAttribute(Stroke::Attribute a)273 bool hasAttribute( Stroke::Attribute a )
274 {
275 return (m_attributes&a) != 0;
276 }
setColour(int c)277 void setColour( int c )
278 {
279 m_colour = c;
280 }
281
createBodies(b2World & world)282 void createBodies( b2World& world )
283 {
284 process();
285 if ( hasAttribute( ATTRIB_DECOR ) ){
286 return; //decorators have no physical embodiment
287 }
288 int n = m_shapePath.numPoints();
289 if ( n > 1 ) {
290 b2BodyDef bodyDef;
291 bodyDef.position = m_origin;
292 bodyDef.position *= 1.0f/PIXELS_PER_METREf;
293 bodyDef.userData = this;
294 if ( m_attributes & ATTRIB_SLEEPING ) {
295 bodyDef.isSleeping = true;
296 }
297 m_body = world.CreateBody( &bodyDef );
298 for ( int i=1; i<n; i++ ) {
299 BoxDef boxDef;
300 boxDef.init( m_shapePath.point(i-1),
301 m_shapePath.point(i),
302 m_attributes );
303 m_body->CreateShape( &boxDef );
304 }
305 m_body->SetMassFromShapes();
306
307 }
308 transform();
309 }
310
maybeCreateJoint(b2World & world,Stroke * other)311 bool maybeCreateJoint( b2World& world, Stroke* other )
312 {
313 if ( (m_attributes&ATTRIB_CLASSBITS)
314 != (other->m_attributes&ATTRIB_CLASSBITS) ) {
315 return false; // can only joint matching classes
316 } else if ( hasAttribute(ATTRIB_GROUND) ) {
317 return true; // no point jointing grounds
318 } else if ( m_body && other->body() ) {
319 transform();
320 int n = m_xformedPath.numPoints();
321 for ( int end=0; end<2; end++ ) {
322 if ( !m_jointed[end] ) {
323 const Vec2& p = m_xformedPath.point( end ? n-1 : 0 );
324 if ( other->distanceTo( p ) <= JOINT_TOLERANCE ) {
325 //printf("jointed end %d d=%f\n",end,other->distanceTo( p ));
326 b2Vec2 pw = p;
327 pw *= 1.0f/PIXELS_PER_METREf;
328 JointDef j( m_body, other->m_body, pw );
329 world.CreateJoint( &j );
330 m_jointed[end] = true;
331 }
332 }
333 }
334 }
335 if ( m_body ) {
336 return m_jointed[0] && m_jointed[1];
337 }
338 return true; ///nothing to do
339 }
340
draw(Canvas & canvas)341 void draw( Canvas& canvas )
342 {
343 if ( m_hide < HIDE_STEPS ) {
344 bool thick = (canvas.width() > 400);
345 transform();
346 canvas.drawPath( m_screenPath, canvas.makeColour(m_colour), thick );
347 m_drawn = true;
348 }
349 m_drawnBbox = m_screenBbox;
350 }
351
addPoint(const Vec2 & pp)352 void addPoint( const Vec2& pp )
353 {
354 Vec2 p = pp; p -= m_origin;
355 if ( p == m_rawPath.point( m_rawPath.numPoints()-1 ) ) {
356 } else {
357 m_rawPath.append( p );
358 m_drawn = false;
359 }
360 }
361
origin(const Vec2 & p)362 void origin( const Vec2& p )
363 {
364 // todo
365 if ( m_body ) {
366 b2Vec2 pw = p;
367 pw *= 1.0f/PIXELS_PER_METREf;
368 m_body->SetXForm( pw, m_body->GetAngle() );
369 }
370 m_origin = p;
371 }
372
body()373 b2Body* body() { return m_body; }
374
distanceTo(const Vec2 & pt)375 float32 distanceTo( const Vec2& pt )
376 {
377 float32 best = 100000.0;
378 transform();
379 for ( int i=1; i<m_xformedPath.numPoints(); i++ ) {
380 Segment s( m_xformedPath.point(i-1), m_xformedPath.point(i) );
381 float32 d = s.distanceTo( pt );
382 //printf(" d[%d]=%f %d,%d\n",i,d,m_rawPath.point(i-1).x,m_rawPath.point(i-1).y);
383 if ( d < best ) {
384 best = d;
385 }
386 }
387 return best;
388 }
389
screenBbox()390 Rect screenBbox()
391 {
392 transform();
393 return m_screenBbox;
394 }
395
lastDrawnBbox()396 Rect lastDrawnBbox()
397 {
398 return m_drawnBbox;
399 }
400
worldBbox()401 Rect worldBbox()
402 {
403 return m_xformedPath.bbox();
404 }
405
isDirty()406 bool isDirty()
407 {
408 return !m_drawn || transform();
409 }
410
hide()411 void hide()
412 {
413 if ( m_hide==0 ) {
414 m_hide = 1;
415
416 if (m_body) {
417 // stash the body where no-one will find it
418 m_body->SetXForm( b2Vec2(0.0f,SCREEN_HEIGHT*2.0f), 0.0f );
419 m_body->SetLinearVelocity( b2Vec2(0.0f,0.0f) );
420 m_body->SetAngularVelocity( 0.0f );
421 }
422 }
423 }
424
hidden()425 bool hidden()
426 {
427 return m_hide >= HIDE_STEPS;
428 }
429
numPoints()430 int numPoints()
431 {
432 return m_rawPath.numPoints();
433 }
434
435 private:
vec2Angle(b2Vec2 v)436 static float32 vec2Angle( b2Vec2 v )
437 {
438 return b2Atan2(v.y, v.x);
439 }
440
process()441 void process()
442 {
443 float32 thresh = 0.1*SIMPLIFY_THRESHOLDf;
444 m_rawPath.simplify( thresh );
445 m_shapePath = m_rawPath;
446
447 while ( m_shapePath.numPoints() > MULTI_VERTEX_LIMIT ) {
448 thresh += SIMPLIFY_THRESHOLDf;
449 m_shapePath.simplify( thresh );
450 }
451 }
452
transform()453 bool transform()
454 {
455 // distinguish between xformed raw and shape path as needed
456 if ( m_hide ) {
457 if ( m_hide < HIDE_STEPS ) {
458 //printf("hide %d\n",m_hide);
459 Vec2 o = m_screenBbox.centroid();
460 m_screenPath -= o;
461 m_screenPath.scale( 0.99 );
462 m_screenPath += o;
463 m_screenBbox = m_screenPath.bbox();
464 m_hide++;
465 return true;
466 }
467 } else if ( m_body ) {
468 if ( hasAttribute( ATTRIB_DECOR ) ) {
469 return false; // decor never moves
470 } else if ( hasAttribute( ATTRIB_GROUND )
471 && m_xformAngle == m_body->GetAngle() ) {
472 return false; // ground strokes never move.
473 } else if ( m_xformAngle != m_body->GetAngle()
474 || ! (m_xformPos == m_body->GetPosition()) ) {
475 //printf("transform stroke - rot or pos\n");
476 b2Mat22 rot( m_body->GetAngle() );
477 b2Vec2 orig = PIXELS_PER_METREf * m_body->GetPosition();
478 m_xformedPath = m_rawPath;
479 m_xformedPath.rotate( rot );
480 m_xformedPath.translate( Vec2(orig) );
481 m_xformAngle = m_body->GetAngle();
482 m_xformPos = m_body->GetPosition();
483 worldToScreen.transform( m_xformedPath, m_screenPath );
484 m_screenBbox = m_screenPath.bbox();
485 } else {
486 //printf("transform none\n");
487 return false;
488 }
489 } else {
490 //printf("transform no body\n");
491 m_xformedPath = m_rawPath;
492 m_xformedPath.translate( m_origin );
493 worldToScreen.transform( m_xformedPath, m_screenPath );
494 m_screenBbox = m_screenPath.bbox();
495 return false;
496 }
497 return true;
498 }
499
500 Path m_rawPath;
501 int m_colour;
502 int m_attributes;
503 Vec2 m_origin;
504 Path m_shapePath;
505 Path m_xformedPath;
506 Path m_screenPath;
507 float32 m_xformAngle;
508 b2Vec2 m_xformPos;
509 Rect m_screenBbox;
510 Rect m_drawnBbox;
511 bool m_drawn;
512 b2Body* m_body;
513 bool m_jointed[2];
514 int m_hide;
515 };
516
517
518 class Scene : private b2ContactListener
519 {
520 public:
521
Scene(bool noWorld=false)522 Scene( bool noWorld=false )
523 : m_world( NULL ),
524 m_bgImage( NULL ),
525 m_protect( 0 )
526 {
527 if ( !noWorld ) {
528 b2AABB worldAABB;
529 worldAABB.lowerBound.Set(-100.0f, -100.0f);
530 worldAABB.upperBound.Set(100.0f, 100.0f);
531
532 b2Vec2 gravity(0.0f, 10.0f);
533 bool doSleep = true;
534 m_world = new b2World(worldAABB, gravity, doSleep);
535 m_world->SetContactListener( this );
536 }
537 }
538
~Scene()539 ~Scene()
540 {
541 clear();
542 if ( m_world ) {
543 step();
544 delete m_world;
545 }
546 }
547
numStrokes()548 int numStrokes()
549 {
550 return m_strokes.size();
551 }
552
newStroke(const Path & p)553 Stroke* newStroke( const Path& p ) {
554 Stroke *s = new Stroke(p);
555 m_strokes.append( s );
556 return s;
557 }
558
deleteStroke(Stroke * s)559 void deleteStroke( Stroke *s ) {
560 //printf("delete stroke %p\n",s);
561 if ( s ) {
562 int i = m_strokes.indexOf(s);
563 if ( i >= m_protect ) {
564 reset(s);
565 m_strokes.erase( m_strokes.indexOf(s) );
566 }
567 }
568 }
569
570
activate(Stroke * s)571 void activate( Stroke *s )
572 {
573 //printf("activate stroke\n");
574 s->createBodies( *m_world );
575 createJoints( s );
576 }
577
activateAll()578 void activateAll()
579 {
580 for ( int i=0; i < m_strokes.size(); i++ ) {
581 m_strokes[i]->createBodies( *m_world );
582 }
583 for ( int i=0; i < m_strokes.size(); i++ ) {
584 createJoints( m_strokes[i] );
585 }
586 }
587
createJoints(Stroke * s)588 void createJoints( Stroke *s )
589 {
590 for ( int j=m_strokes.size()-1; j>=0; j-- ) {
591 if ( s != m_strokes[j] ) {
592 //printf("try join to %d\n",j);
593 s->maybeCreateJoint( *m_world, m_strokes[j] );
594 m_strokes[j]->maybeCreateJoint( *m_world, s );
595 }
596 }
597 }
598
step()599 void step()
600 {
601 m_world->Step( ITERATION_TIMESTEPf, SOLVER_ITERATIONS );
602 // clean up delete strokes
603 for ( int i=0; i< m_strokes.size(); i++ ) {
604 if ( m_strokes[i]->hasAttribute(Stroke::ATTRIB_DELETED) ) {
605 m_strokes[i]->clearAttribute(Stroke::ATTRIB_DELETED);
606 m_strokes[i]->hide();
607 }
608 }
609 // check for token respawn
610 for ( int i=0; i < m_strokes.size(); i++ ) {
611 if ( m_strokes[i]->hasAttribute( Stroke::ATTRIB_TOKEN )
612 && !BOUNDS_RECT.intersects( m_strokes[i]->worldBbox() ) ) {
613 printf("RESPAWN token %d\n",i);
614 reset( m_strokes[i] );
615 activate( m_strokes[i] );
616 }
617 }
618 }
619
620 // b2ContactListener callback when a new contact is detected
Add(const b2ContactPoint * point)621 virtual void Add(const b2ContactPoint* point)
622 {
623 // check for completion
624 //if (c->GetManifoldCount() > 0) {
625 Stroke* s1 = (Stroke*)point->shape1->GetBody()->GetUserData();
626 Stroke* s2 = (Stroke*)point->shape2->GetBody()->GetUserData();
627 if ( s1 && s2 ) {
628 if ( s2->hasAttribute(Stroke::ATTRIB_TOKEN) ) {
629 b2Swap( s1, s2 );
630 }
631 if ( s1->hasAttribute(Stroke::ATTRIB_TOKEN)
632 && s2->hasAttribute(Stroke::ATTRIB_GOAL) ) {
633 s2->setAttribute(Stroke::ATTRIB_DELETED);
634 printf("SUCCESS!! level complete\n");
635 }
636 }
637 }
638
isCompleted()639 bool isCompleted()
640 {
641 for ( int i=0; i < m_strokes.size(); i++ ) {
642 if ( m_strokes[i]->hasAttribute( Stroke::ATTRIB_GOAL )
643 && !m_strokes[i]->hidden() ) {
644 return false;
645 }
646 }
647 //printf("completed!\n");
648 return true;
649 }
650
dirtyArea()651 Rect dirtyArea()
652 {
653 Rect r(0,0,0,0),temp;
654 int numDirty = 0;
655 for ( int i=0; i<m_strokes.size(); i++ ) {
656 if ( m_strokes[i]->isDirty() ) {
657 // acumulate new areas to draw
658 temp = m_strokes[i]->screenBbox();
659 if ( !temp.isEmpty() ) {
660 if ( numDirty==0 ) {
661 r = temp;
662 } else {
663 r.expand( m_strokes[i]->screenBbox() );
664 }
665 // plus prev areas to erase
666 r.expand( m_strokes[i]->lastDrawnBbox() );
667 numDirty++;
668 }
669 }
670 }
671 if ( !r.isEmpty() ) {
672 // expand to allow for thick lines
673 r.tl.x--; r.tl.y--;
674 r.br.x++; r.br.y++;
675 }
676 return r;
677 }
678
draw(Canvas & canvas,const Rect & area)679 void draw( Canvas& canvas, const Rect& area )
680 {
681 if ( m_bgImage ) {
682 canvas.setBackground( m_bgImage );
683 } else {
684 canvas.setBackground( 0 );
685 }
686 canvas.clear( area );
687 Rect clipArea = area;
688 clipArea.tl.x--;
689 clipArea.tl.y--;
690 clipArea.br.x++;
691 clipArea.br.y++;
692 for ( int i=0; i<m_strokes.size(); i++ ) {
693 if ( area.intersects( m_strokes[i]->screenBbox() ) ) {
694 m_strokes[i]->draw( canvas );
695 }
696 }
697 //canvas.drawRect( area, 0xffff0000, false );
698 }
699
reset(Stroke * s=NULL)700 void reset( Stroke* s=NULL )
701 {
702 for ( int i=0; i<m_strokes.size(); i++ ) {
703 if (s==NULL || s==m_strokes[i]) {
704 m_strokes[i]->reset(m_world);
705 }
706 }
707 }
708
strokeAtPoint(const Vec2 pt,float32 max)709 Stroke* strokeAtPoint( const Vec2 pt, float32 max )
710 {
711 Stroke* best = NULL;
712 for ( int i=0; i<m_strokes.size(); i++ ) {
713 float32 d = m_strokes[i]->distanceTo( pt );
714 //printf("stroke %d dist %f\n",i,d);
715 if ( d < max ) {
716 max = d;
717 best = m_strokes[i];
718 }
719 }
720 return best;
721 }
722
clear()723 void clear()
724 {
725 reset();
726 while ( m_strokes.size() ) {
727 delete m_strokes[0];
728 m_strokes.erase(0);
729 }
730 if ( m_world ) {
731 //step is required to actually destroy bodies and joints
732 m_world->Step( ITERATION_TIMESTEPf, SOLVER_ITERATIONS );
733 }
734 }
735
736
737
load(unsigned char * buf,int bufsize)738 bool load( unsigned char *buf, int bufsize )
739 {
740 string s( (const char*)buf, bufsize );
741 stringstream in( s, ios::in );
742 return load( in );
743 }
744
load(const string & file)745 bool load( const string& file )
746 {
747 ifstream in( file.c_str(), ios::in );
748 return load( in );
749 }
750
load(istream & in)751 bool load( istream& in )
752 {
753 clear();
754 if ( g_bgImage==NULL ) {
755 g_bgImage = new Image("paper.jpg");
756 g_bgImage->scale( SCREEN_WIDTH, SCREEN_HEIGHT );
757 }
758 m_bgImage = g_bgImage;
759 string line;
760 while ( !in.eof() ) {
761 getline( in, line );
762 parseLine( line );
763 }
764 protect();
765 return true;
766 }
767
parseLine(const string & line)768 bool parseLine( const string& line )
769 {
770 switch( line[0] ) {
771 case 'T': m_title = line.substr(line.find(':')+1); return true;
772 case 'B': m_bg = line.substr(line.find(':')+1); return true;
773 case 'A': m_author = line.substr(line.find(':')+1); return true;
774 case 'S': m_strokes.append( new Stroke(line) ); return true;
775 }
776 return false;
777 }
778
protect(int n=-1)779 void protect( int n=-1 )
780 {
781 m_protect = (n==-1 ? m_strokes.size() : n );
782 }
783
save(const std::string & file)784 bool save( const std::string& file )
785 {
786 printf("saving to %s\n",file.c_str());
787 ofstream o( file.c_str(), ios::out );
788 if ( o.is_open() ) {
789 o << "Title: "<<m_title<<endl;
790 o << "Author: "<<m_author<<endl;
791 o << "Background: "<<m_bg<<endl;
792 for ( int i=0; i<m_strokes.size(); i++ ) {
793 o << m_strokes[i]->asString();
794 }
795 o.close();
796 return true;
797 } else {
798 return false;
799 }
800 }
801
strokes()802 Array<Stroke*>& strokes()
803 {
804 return m_strokes;
805 }
806
807 private:
808 b2World *m_world;
809 Array<Stroke*> m_strokes;
810 string m_title, m_author, m_bg;
811 Image *m_bgImage;
812 static Image *g_bgImage;
813 int m_protect;
814 };
815
816 Image *Scene::g_bgImage = NULL;
817
818
819
820
821
822 struct DemoEntry {
DemoEntryDemoEntry823 DemoEntry( int _t, int _o, SDL_Event& _e ) : t(_t), o(_o), e(_e) {}
824 int t,o;
825 SDL_Event e;
826 };
827
828 class DemoLog : public Array<DemoEntry>
829 {
830 public:
asString(int i)831 std::string asString( int i )
832 {
833 if ( i < size() ) {
834 DemoEntry& e = at(i);
835 stringstream s;
836 s << "E: " << e.t << " " << e.o << " ";
837 switch( e.e.type ) {
838 case SDL_KEYDOWN:
839 s << "K" << e.e.key.keysym.sym;
840 break;
841 case SDL_KEYUP:
842 s << "k" << e.e.key.keysym.sym;
843 break;
844 case SDL_MOUSEBUTTONDOWN:
845 s << "B" << e.e.button.button;
846 s << "," << e.e.button.x << "," << e.e.button.y;
847 break;
848 case SDL_MOUSEBUTTONUP:
849 s << "b" << e.e.button.button;
850 s << "," << e.e.button.x << "," << e.e.button.y;
851 break;
852 case SDL_MOUSEMOTION:
853 s << "M" << e.e.button.x << "," << e.e.button.y;
854 break;
855 }
856 return s.str();
857 }
858 return std::string();
859 }
860
append(int tick,int offset,SDL_Event & ev)861 void append( int tick, int offset, SDL_Event& ev )
862 {
863 Array<DemoEntry>::append( DemoEntry( tick, offset, ev ) );
864 }
865
appendFromString(const std::string & str)866 void appendFromString( const std::string& str )
867 {
868 const char *s = str.c_str();
869 int t, o, v1, v2, v3;
870 char c;
871 SDL_Event ev = {0};
872 ev.type = 0xff;
873 if ( sscanf(s,"E: %d %d %c%d",&t,&o,&c,&v1)==4 ) { //1 arg
874 switch ( c ) {
875 case 'K': ev.type = SDL_KEYDOWN; break;
876 case 'k': ev.type = SDL_KEYUP; break;
877 }
878 ev.key.keysym.sym = (SDLKey)v1;
879 } else if ( sscanf(s,"E: %d %d %c%d,%d",&t,&o,&c,&v1,&v2)==5 ) { //2 args
880 switch ( c ) {
881 case 'M': ev.type = SDL_MOUSEMOTION; break;
882 }
883 ev.button.x = v1;
884 ev.button.y = v2;
885 } else if ( sscanf(s,"E: %d %d %c%d,%d,%d",&t,&o,&c,&v1,&v2,&v3)==6 ) { //3 args
886 switch ( c ) {
887 case 'B': ev.type = SDL_MOUSEBUTTONDOWN; break;
888 case 'b': ev.type = SDL_MOUSEBUTTONUP; break;
889 }
890 ev.button.button = v1;
891 ev.button.x = v2;
892 ev.button.y = v3;
893 }
894 if ( ev.type != 0xff ) {
895 append( t, o, ev );
896 }
897 }
898 };
899
900 class DemoRecorder
901 {
902 public:
903
start()904 void start()
905 {
906 m_running = true;
907 m_log.empty();
908 m_log.capacity(512);
909 m_lastTick = 0;
910 m_lastTickTime = SDL_GetTicks();
911 }
912
stop()913 void stop()
914 {
915 printf("stop recording: %d events\n",m_log.size());
916 m_running = false;
917 }
918
tick()919 void tick()
920 {
921 if ( m_running ) {
922 m_lastTick++;
923 m_lastTickTime = SDL_GetTicks();
924 }
925 }
926
record(SDL_Event & ev)927 void record( SDL_Event& ev )
928 {
929 if ( m_running ) {
930 m_log.append( m_lastTick, SDL_GetTicks()-m_lastTickTime, ev );
931 }
932 }
933
getLog()934 DemoLog& getLog() { return m_log; }
935
936 private:
937 bool m_running;
938 DemoLog m_log;
939 int m_lastTick;
940 int m_lastTickTime;
941 };
942
943
944 class DemoPlayer
945 {
946 public:
947
start(const DemoLog * log)948 void start( const DemoLog* log )
949 {
950 m_playing = true;
951 m_log = log;
952 m_index = 0;
953 m_lastTick = 0;
954 m_lastTickTime = SDL_GetTicks();
955 printf("start playback: %d events\n",m_log->size());
956 }
957
isRunning()958 bool isRunning() { return m_playing; }
959
stop()960 void stop()
961 {
962 m_playing = false;
963 m_log = NULL;
964 }
965
tick()966 void tick()
967 {
968 if ( m_playing ) {
969 m_lastTick++;
970 m_lastTickTime = SDL_GetTicks();
971 }
972 }
973
fetchEvent(SDL_Event & ev)974 bool fetchEvent( SDL_Event& ev )
975 {
976 if ( m_playing
977 && m_index < m_log->size()
978 && m_log->at(m_index).t <= m_lastTick
979 /*&& m_log->at(m_index).o <= SDL_GetTicks()-m_lastTickTime*/ ) {
980 ev = m_log->at(m_index).e;
981 m_index++;
982 return true;
983 }
984 return false;
985 }
986
987 private:
988 bool m_playing;
989 const DemoLog* m_log;
990 int m_index;
991 int m_lastTick;
992 int m_lastTickTime;
993 };
994
995
996 class Game : public GameControl
997 {
998 Scene m_scene;
999 Stroke *m_createStroke;
1000 Stroke *m_moveStroke;
1001 Array<Overlay*> m_overlays;
1002 Window m_window;
1003 Overlay *m_pauseOverlay;
1004 Overlay *m_editOverlay;
1005 // DemoOverlay m_demoOverlay;
1006 DemoRecorder m_recorder;
1007 DemoPlayer m_player;
1008 Os *m_os;
1009 public:
Game(int width,int height)1010 Game( int width, int height )
1011 : m_createStroke(NULL),
1012 m_moveStroke(NULL),
1013 m_window(width,height,"Numpty Physics","NPhysics"),
1014 m_pauseOverlay( NULL ),
1015 m_editOverlay( NULL ),
1016 m_os( Os::get() )
1017 //,m_demoOverlay( *this )
1018 {
1019 configureScreenTransform( m_window.width(), m_window.height() );
1020 }
1021
1022
renderScene(Canvas & c,int level)1023 virtual bool renderScene( Canvas& c, int level )
1024 {
1025 Scene scene( true );
1026 int size = m_levels.load( level, levelbuf, sizeof(levelbuf) );
1027 if ( size && scene.load( levelbuf, size ) ) {
1028 scene.draw( c, FULLSCREEN_RECT );
1029 return true;
1030 }
1031 return false;
1032 }
1033
gotoLevel(int level,bool replay=false)1034 void gotoLevel( int level, bool replay=false )
1035 {
1036 printf("gotoLevel %d\n",level);
1037 if ( level >= 0 && level < m_levels.numLevels() ) {
1038 int size = m_levels.load( level, levelbuf, sizeof(levelbuf) );
1039 if ( size && m_scene.load( levelbuf, size ) ) {
1040 m_scene.activateAll();
1041 //m_window.setSubName( file );
1042 m_refresh = true;
1043 if ( m_edit ) {
1044 m_scene.protect(0);
1045 }
1046 m_recorder.stop();
1047 m_player.stop();
1048 if ( replay ) {
1049 m_player.start( &m_recorder.getLog() );
1050 } else {
1051 m_recorder.start();
1052 }
1053 m_level = level;
1054 }
1055 }
1056 }
1057
1058
save(const char * file=NULL)1059 bool save( const char *file=NULL )
1060 {
1061 string p;
1062 if ( file ) {
1063 p = file;
1064 } else {
1065 p = Config::userDataDir() + Os::pathSep + "L99_saved.nph";
1066 }
1067 if ( m_scene.save( p ) ) {
1068 m_levels.addPath( p.c_str() );
1069 int l = m_levels.findLevel( p.c_str() );
1070 if ( l >= 0 ) {
1071 m_level = l;
1072 m_window.setSubName( p.c_str() );
1073 }
1074 return true;
1075 }
1076 return false;
1077 }
1078
send()1079 bool send()
1080 {
1081 if ( save( SEND_TEMP_FILE ) ) {
1082 Http h;
1083 if ( h.post( Config::planetRoot().c_str(), "upload", SEND_TEMP_FILE ) ) {
1084 std::string id = h.getHeader("NP-Upload-Id");
1085 if ( id.length() > 0 ) {
1086 if ( !m_os->openBrowser((Config::planetRoot()+"?level="+id).c_str()) ) {
1087 showMessage("Unable to launch browser");
1088 }
1089 } else {
1090 showMessage("UploadFailed: unknown error");
1091 }
1092 } else {
1093 showMessage(std::string("UploadFailed: ")+h.errorMessage());
1094 }
1095 }
1096 return false;
1097 }
1098
setTool(int t)1099 void setTool( int t )
1100 {
1101 m_colour = t;
1102 }
1103
editMode(bool set)1104 void editMode( bool set )
1105 {
1106 m_edit = set;
1107 }
1108
showMessage(const std::string & msg)1109 void showMessage( const std::string& msg )
1110 {
1111 //todo
1112 printf("showMessage \"%s\"\n",msg.c_str());
1113 }
1114
showOverlay(Overlay * o)1115 void showOverlay( Overlay* o )
1116 {
1117 printf("show overlay\n");
1118 m_overlays.append( o );
1119 o->onShow();
1120 }
1121
hideOverlay(Overlay * o)1122 void hideOverlay( Overlay* o )
1123 {
1124 printf("hide overlay\n");
1125 o->onHide();
1126 m_overlays.erase( m_overlays.indexOf(o) );
1127 m_refresh = true;
1128 }
1129
toggleOverlay(Overlay * o)1130 void toggleOverlay( Overlay* o )
1131 {
1132 if ( m_overlays.indexOf( o ) >= 0 ) {
1133 hideOverlay( o );
1134 } else {
1135 showOverlay( o );
1136 }
1137 }
1138
isPaused()1139 bool isPaused()
1140 {
1141 return m_pauseOverlay != NULL
1142 && m_overlays.indexOf( m_pauseOverlay ) >= 0;
1143 }
1144
edit(bool doEdit)1145 void edit( bool doEdit )
1146 {
1147 if ( m_edit != doEdit ) {
1148 m_edit = doEdit;
1149 if ( m_edit ) {
1150 if ( !m_editOverlay ) {
1151 m_editOverlay = createEditOverlay(*this);
1152 }
1153 showOverlay( m_editOverlay );
1154 m_scene.protect(0);
1155 } else {
1156 hideOverlay( m_editOverlay );
1157 m_strokeFixed = false;
1158 m_strokeSleep = false;
1159 m_strokeDecor = false;
1160 if ( m_colour < 2 ) m_colour = 2;
1161 m_scene.protect();
1162 }
1163 }
1164 }
1165
mousePoint(SDL_Event ev)1166 Vec2 mousePoint( SDL_Event ev )
1167 {
1168 Vec2 pt( ev.button.x, ev.button.y );
1169 worldToScreen.inverseTransform( pt );
1170 return pt;
1171 }
1172
handleGameEvent(SDL_Event & ev)1173 bool handleGameEvent( SDL_Event &ev )
1174 {
1175 switch( ev.type ) {
1176 case SDL_QUIT:
1177 m_quit = true;
1178 break;
1179 case SDL_KEYDOWN:
1180 switch ( ev.key.keysym.sym ) {
1181 case SDLK_q:
1182 m_quit = true;
1183 break;
1184 case SDLK_SPACE:
1185 case SDLK_KP_ENTER:
1186 case SDLK_RETURN:
1187 if ( !m_pauseOverlay ) {
1188 m_pauseOverlay = createIconOverlay( *this, "pause.png", 50, 50 );
1189 }
1190 toggleOverlay( m_pauseOverlay );
1191 break;
1192 case SDLK_s:
1193 case SDLK_F4:
1194 save();
1195 break;
1196 case SDLK_e:
1197 case SDLK_F6:
1198 edit( !m_edit );
1199 break;
1200 case SDLK_d:
1201 //toggleOverlay( m_demoOverlay );
1202 break;
1203 case SDLK_r:
1204 case SDLK_UP:
1205 gotoLevel( m_level );
1206 break;
1207 case SDLK_n:
1208 case SDLK_RIGHT:
1209 gotoLevel( m_level+1 );
1210 break;
1211 case SDLK_p:
1212 case SDLK_LEFT:
1213 gotoLevel( m_level-1 );
1214 break;
1215 case SDLK_v:
1216 gotoLevel( m_level, true );
1217 break;
1218 default:
1219 break;
1220 }
1221 break;
1222 default:
1223 break;
1224 }
1225 return false;
1226 }
1227
1228
handleModEvent(SDL_Event & ev)1229 bool handleModEvent( SDL_Event &ev )
1230 {
1231 static int mod=0;
1232 //printf("mod=%d\n",ev.key.keysym.sym,mod);
1233 switch( ev.type ) {
1234 case SDL_KEYDOWN:
1235 //printf("mod key=%x mod=%d\n",ev.key.keysym.sym,mod);
1236 if ( ev.key.keysym.sym == SDLK_F8 ) {
1237 mod = 1; //zoom- == middle (delete)
1238 return true;
1239 } else if ( ev.key.keysym.sym == SDLK_F7 ) {
1240 mod = 2; //zoom+ == right (move)
1241 return true;
1242 }
1243 break;
1244 case SDL_KEYUP:
1245 if ( ev.key.keysym.sym == SDLK_F7
1246 || ev.key.keysym.sym == SDLK_F8 ) {
1247 mod = 0;
1248 return true;
1249 }
1250 break;
1251 case SDL_MOUSEBUTTONDOWN:
1252 case SDL_MOUSEBUTTONUP:
1253 if ( ev.button.button == SDL_BUTTON_LEFT && mod != 0 ) {
1254 ev.button.button = ((mod==1) ? SDL_BUTTON_MIDDLE : SDL_BUTTON_RIGHT);
1255 }
1256 break;
1257 }
1258 return false;
1259 }
1260
handlePlayEvent(SDL_Event & ev)1261 bool handlePlayEvent( SDL_Event &ev )
1262 {
1263 switch( ev.type ) {
1264 case SDL_MOUSEBUTTONDOWN:
1265 if ( ev.button.button == SDL_BUTTON_LEFT
1266 && !m_createStroke ) {
1267 m_createStroke = m_scene.newStroke( Path()&mousePoint(ev) );
1268 if ( m_createStroke ) {
1269 switch ( m_colour ) {
1270 case 0: m_createStroke->setAttribute( Stroke::ATTRIB_TOKEN ); break;
1271 case 1: m_createStroke->setAttribute( Stroke::ATTRIB_GOAL ); break;
1272 default: m_createStroke->setColour( brushColours[m_colour] ); break;
1273 }
1274 if ( m_strokeFixed ) {
1275 m_createStroke->setAttribute( Stroke::ATTRIB_GROUND );
1276 }
1277 if ( m_strokeSleep ) {
1278 m_createStroke->setAttribute( Stroke::ATTRIB_SLEEPING );
1279 }
1280 if ( m_strokeDecor ) {
1281 m_createStroke->setAttribute( Stroke::ATTRIB_DECOR );
1282 }
1283 }
1284 }
1285 break;
1286 case SDL_MOUSEBUTTONUP:
1287 if ( ev.button.button == SDL_BUTTON_LEFT
1288 && m_createStroke ) {
1289 if ( m_createStroke->numPoints() > 1 ) {
1290 m_scene.activate( m_createStroke );
1291 } else {
1292 m_scene.deleteStroke( m_createStroke );
1293 }
1294 m_createStroke = NULL;
1295 }
1296 break;
1297 case SDL_MOUSEMOTION:
1298 if ( m_createStroke ) {
1299 m_createStroke->addPoint( mousePoint(ev) );
1300 }
1301 break;
1302 case SDL_KEYDOWN:
1303 if ( ev.key.keysym.sym == SDLK_ESCAPE ) {
1304 if ( m_createStroke ) {
1305 m_scene.deleteStroke( m_createStroke );
1306 m_createStroke = NULL;
1307 } else {
1308 m_scene.deleteStroke( m_scene.strokes().at(m_scene.strokes().size()-1) );
1309 }
1310 m_refresh = true;
1311 }
1312 break;
1313 default:
1314 break;
1315 }
1316 return false;
1317 }
1318
handleEditEvent(SDL_Event & ev)1319 bool handleEditEvent( SDL_Event &ev )
1320 {
1321 //allow move/delete in normal play!!
1322 //if ( !m_edit ) return false;
1323
1324 switch( ev.type ) {
1325 case SDL_MOUSEBUTTONDOWN:
1326 if ( ev.button.button == SDL_BUTTON_RIGHT
1327 && !m_moveStroke ) {
1328 m_moveStroke = m_scene.strokeAtPoint( mousePoint(ev),
1329 SELECT_TOLERANCE );
1330 } else if ( ev.button.button == SDL_BUTTON_MIDDLE ) {
1331 m_scene.deleteStroke( m_scene.strokeAtPoint( mousePoint(ev),
1332 SELECT_TOLERANCE ) );
1333 m_refresh = true;
1334 }
1335 break;
1336 case SDL_MOUSEBUTTONUP:
1337 if ( ev.button.button == SDL_BUTTON_RIGHT
1338 && m_moveStroke ) {
1339 m_moveStroke = NULL;
1340 }
1341 break;
1342 case SDL_MOUSEMOTION:
1343 if ( m_moveStroke ) {
1344 m_moveStroke->origin( mousePoint(ev) );
1345 }
1346 break;
1347 default:
1348 break;
1349 }
1350 return false;
1351 }
1352
run()1353 void run()
1354 {
1355 Overlay *completedOverlay = createNextLevelOverlay(*this);
1356
1357 m_scene.draw( m_window, FULLSCREEN_RECT );
1358 m_window.update( FULLSCREEN_RECT );
1359
1360 int iterateCounter = 0;
1361 int lastTick = SDL_GetTicks();
1362 bool isComplete = false;
1363
1364 while ( !m_quit ) {
1365 for ( int i=0; i<m_overlays.size(); i++ ) {
1366 m_overlays[i]->onTick( lastTick );
1367 }
1368
1369 //assumes RENDER_RATE <= ITERATION_RATE
1370 while ( iterateCounter < ITERATION_RATE ) {
1371 if ( !isPaused() ) {
1372 m_scene.step();
1373 m_recorder.tick();
1374 m_player.tick();
1375 }
1376
1377 SDL_Event ev;
1378 bool more = true;
1379 while ( more ) {
1380 more = SDL_PollEvent(&ev);
1381 if ( m_player.isRunning()
1382 && ( ev.type==SDL_MOUSEMOTION ||
1383 ev.type==SDL_MOUSEBUTTONDOWN ||
1384 ev.type==SDL_MOUSEBUTTONUP ) ) {
1385 more = false; //discard
1386 }
1387 if (!more) {
1388 more = m_player.fetchEvent(ev);
1389 }
1390 if ( more ) {
1391 bool handled = false;
1392 m_recorder.record( ev );
1393 for ( int i=m_overlays.size()-1; i>=0 && !handled; i-- ) {
1394 handled = m_overlays[i]->handleEvent(ev);
1395 }
1396 if ( !handled ) {
1397 handled = false
1398 || handleModEvent(ev)
1399 || handleGameEvent(ev)
1400 || handleEditEvent(ev)
1401 || handlePlayEvent(ev);
1402 }
1403 }
1404 }
1405 iterateCounter += RENDER_RATE;
1406 }
1407 iterateCounter -= ITERATION_RATE;
1408
1409
1410 if ( isComplete && m_edit ) {
1411 hideOverlay( completedOverlay );
1412 isComplete = false;
1413 }
1414 if ( m_scene.isCompleted() != isComplete && !m_edit ) {
1415 isComplete = m_scene.isCompleted();
1416 if ( isComplete ) {
1417 m_player.stop();
1418 m_recorder.stop();
1419 showOverlay( completedOverlay );
1420 } else {
1421 hideOverlay( completedOverlay );
1422 }
1423 }
1424
1425 Rect r = m_scene.dirtyArea();
1426 if ( m_refresh ) {
1427 r = FULLSCREEN_RECT;
1428 } else {
1429 for ( int i=0; i<m_overlays.size(); i++ ) {
1430 if ( m_overlays[i]->isDirty() ) {
1431 r.expand( m_overlays[i]->dirtyArea() );
1432 }
1433 }
1434 }
1435
1436 if ( !r.isEmpty() ) {
1437 m_scene.draw( m_window, r );
1438 if ( m_fade ) {
1439 m_window.fade( r );
1440 }
1441 }
1442 for ( int i=0; i<m_overlays.size(); i++ ) {
1443 if ( m_overlays[i]->dirtyArea().intersects(r) ) {
1444 m_overlays[i]->draw( m_window );
1445 }
1446 }
1447
1448 if ( m_refresh ) {
1449 m_window.update( FULLSCREEN_RECT );
1450 m_refresh = false;
1451 } else {
1452 r.br.x++; r.br.y++;
1453 //uncomment this to show dirty areas
1454 //m_window.drawRect( r, 0x00ff00, false );
1455 m_window.update( r );
1456 }
1457
1458
1459 if ( m_os ) {
1460 m_os->poll();
1461 for ( char *f = m_os->getLaunchFile(); f; f=m_os->getLaunchFile() ) {
1462 if ( f ) {
1463 m_levels.addPath( f );
1464 int l = m_levels.findLevel( f );
1465 if ( l >= 0 ) {
1466 gotoLevel( l );
1467 m_window.raise();
1468 }
1469 }
1470 }
1471 }
1472
1473 int sleepMs = lastTick + RENDER_INTERVAL - SDL_GetTicks();
1474 if ( sleepMs > 0 ) {
1475 SDL_Delay( sleepMs );
1476 //printf("sleep %dms\n",sleepMs);
1477 } else {
1478 printf("overrun %dms\n",-sleepMs);
1479 }
1480 lastTick = SDL_GetTicks();
1481 }
1482 }
1483 };
1484
1485
init(bool useDummyVideo=false)1486 void init( bool useDummyVideo=false )
1487 {
1488 if ( useDummyVideo ) {
1489 putenv((char*)"SDL_VIDEODRIVER=dummy");
1490 } else {
1491 putenv((char*)"SDL_VIDEO_X11_WMCLASS=NPhysics");
1492 }
1493
1494 if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER) < 0) {
1495 throw "Couldn't initialize SDL";
1496 }
1497
1498 if ( mkdir( (string(getenv("HOME"))+"/" USER_BASE_PATH).c_str(),
1499 0755)==0 ) {
1500 printf("created user dir\n");
1501 } else if ( errno==EEXIST ){
1502 //printf("user dir ok\n");
1503 } else {
1504 printf("failed to create user dir\n");
1505 }
1506
1507 }
1508
npmain(int argc,char ** argv)1509 int npmain(int argc, char** argv)
1510 {
1511 try {
1512 bool rotate = false;
1513 bool thumbnailMode = false;
1514 int width=SCREEN_WIDTH, height=SCREEN_HEIGHT;
1515 Array<const char*> files;
1516
1517 for ( int i=1; i<argc; i++ ) {
1518 if ( strcmp(argv[i],"-bmp")==0 ) {
1519 thumbnailMode = true;
1520 } else if ( strcmp(argv[i],"-rotate")==0 ) {
1521 rotate = true;
1522 } else if ( strcmp(argv[i],"-geometry")==0 && i<argc-1) {
1523 int ww, hh;
1524 if ( sscanf(argv[i+1],"%dx%d",&ww,&hh) ==2 ) {
1525 width = ww;
1526 height = hh;
1527 }
1528 i++;
1529 } else {
1530 files.append( argv[i] );
1531 }
1532 }
1533
1534 if ( thumbnailMode ) {
1535 init(true);
1536 configureScreenTransform( width, height );
1537 for ( int i=0; i<files.size(); i++ ) {
1538 Scene scene( true );
1539 if ( scene.load( files[i] ) ) {
1540 printf("generating bmp %s\n", files[i]);
1541 Canvas temp( width, height );
1542 scene.draw( temp, FULLSCREEN_RECT );
1543 string bmp( files[i] );
1544 bmp += ".bmp";
1545 temp.writeBMP( bmp.c_str() );
1546 }
1547 }
1548 } else {
1549 init();
1550 Game game( width, height );
1551
1552 if ( files.size() > 0 ) {
1553 for ( int i=0; i<files.size(); i++ ) {
1554 game.levels().addPath( files[i] );
1555 }
1556 } else {
1557 struct stat st;
1558 if ( stat("Game.cpp",&st)==0 ) {
1559 game.levels().addPath( "data" );
1560 } else {
1561 game.levels().addPath( DEFAULT_LEVEL_PATH );
1562 }
1563 game.levels().addPath( Config::userDataDir().c_str() );
1564 }
1565 game.gotoLevel(0);
1566 game.run();
1567 }
1568 } catch ( const char* e ) {
1569 cout <<"*** CAUGHT: "<<e<<endl;
1570 }
1571 return 0;
1572 }
1573
1574