2 * This file is part of NumptyPhysics
3 * Copyright (C) 2008 Tim Edmonds
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.
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.
30 #include "Multitouch.h"
33 #include <SDL/SDL_image.h>
45 unsigned char levelbuf
[64*1024];
50 DemoEntry( int _t
, int _o
, SDL_Event
& _e
) : t(_t
), o(_o
), e(_e
) {}
55 class DemoLog
: public Array
<DemoEntry
>
58 std::string
asString( int i
)
63 s
<< "E:" << e
.t
<< " ";
66 s
<< "K" << e
.e
.key
.keysym
.sym
;
69 s
<< "k" << e
.e
.key
.keysym
.sym
;
71 case SDL_MOUSEBUTTONDOWN
:
72 s
<< "B" << '0'+e
.e
.button
.button
;
73 s
<< "," << e
.e
.button
.x
<< "," << e
.e
.button
.y
;
75 case SDL_MOUSEBUTTONUP
:
76 s
<< "b" << '0'+e
.e
.button
.button
;
77 s
<< "," << e
.e
.button
.x
<< "," << e
.e
.button
.y
;
80 s
<< "M" << e
.e
.button
.x
<< "," << e
.e
.button
.y
;
90 void append( int tick
, int offset
, SDL_Event
& ev
)
92 Array
<DemoEntry
>::append( DemoEntry( tick
, offset
, ev
) );
95 void appendFromString( const std::string
& str
)
97 const char *s
= str
.c_str();
102 if ( sscanf(s
,"E:%d %c%d",&t
,&c
,&v1
)==3 ) { //1 arg
104 case 'K': ev
.type
= SDL_KEYDOWN
; break;
105 case 'k': ev
.type
= SDL_KEYUP
; break;
107 ev
.key
.keysym
.sym
= (SDLKey
)v1
;
108 } else if ( sscanf(s
,"E:%d %c%d,%d",&t
,&c
,&v1
,&v2
)==4 ) { //2 args
110 case 'M': ev
.type
= SDL_MOUSEMOTION
; break;
114 } else if ( sscanf(s
,"E:%d %c%d,%d,%d",&t
,&c
,&v1
,&v2
,&v3
)==5 ) { //3 args
116 case 'B': ev
.type
= SDL_MOUSEBUTTONDOWN
; break;
117 case 'b': ev
.type
= SDL_MOUSEBUTTONUP
; break;
119 ev
.button
.button
= v1
;
123 if ( ev
.type
!= 0xff ) {
139 m_lastTickTime
= SDL_GetTicks();
145 printf("stop recording: %d events:\n", m_log
.size());
146 for ( int i
=0; i
<m_log
.size(); i
++ ) {
147 std::string e
= m_log
.asString(i
);
148 if ( e
.length() > 0 ) {
149 printf(" %s\n",e
.c_str());
159 m_lastTickTime
= SDL_GetTicks();
163 void record( SDL_Event
& ev
)
167 case SDL_MOUSEBUTTONDOWN
:
168 m_pressed
|= 1<<ev
.button
.button
;
170 case SDL_MOUSEBUTTONUP
:
171 m_pressed
&= ~(1<<ev
.button
.button
);
173 case SDL_MOUSEMOTION
:
174 if ( m_pressed
== 0 ) {
178 m_log
.append( m_lastTick
, SDL_GetTicks()-m_lastTickTime
, ev
);
182 DemoLog
& getLog() { return m_log
; }
197 void start( const DemoLog
* log
)
203 printf("start playback: %d events\n",m_log
->size());
206 bool isRunning() { return m_playing
; }
221 bool fetchEvent( SDL_Event
& ev
)
224 if ( m_index
< m_log
->size()
225 && m_log
->at(m_index
).t
<= m_lastTick
) {
226 printf("demo event at t=%d\n",m_lastTick
);
227 ev
= m_log
->at(m_index
).e
;
237 const DemoLog
* m_log
;
243 class CollectionSelector
: public ListProvider
247 CollectionSelector( GameControl
& game
)
249 m_list
= createListOverlay( game
, this );
251 Overlay
* overlay() { return m_list
; }
253 virtual int countItems() {
256 virtual Canvas
* provideItem( int i
, Canvas
* old
) {
259 sprintf(buf
,"%d. Item",i
);
260 Canvas
* c
= new Canvas( 100, 32 );
261 c
->setBackground(0xffffff);
263 Font::headingFont()->drawLeft( c
, Vec2(3,3), buf
, i
<<3 );
266 virtual void releaseItem( Canvas
* old
) {
269 virtual void onSelection( int i
, int ix
, int iy
) {
270 printf("Selected: %d (%d,%d)\n",i
,ix
,iy
);
283 startTime
= SDL_GetTicks();
290 class Game
: public GameControl
, public Widget
293 Stroke
*m_createStrokes
[MT_MAX_CURSORS
];
294 Stroke
*m_moveStrokes
[MT_MAX_CURSORS
];
295 Array
<Overlay
*> m_overlays
;
297 Overlay
*m_pauseOverlay
;
298 Overlay
*m_editOverlay
;
299 Overlay
*m_completedOverlay
;
300 // DemoOverlay m_demoOverlay;
301 DemoRecorder m_recorder
;
303 CollectionSelector m_cselector
;
309 Game( Levels
* levels
, int width
, int height
)
310 : m_window(width
,height
,"Numpty Physics","NPhysics"),
311 m_pauseOverlay( NULL
),
312 m_editOverlay( NULL
),
313 m_completedOverlay( NULL
),
314 m_isCompleted(false),
315 m_cselector( *this ),
318 //,m_demoOverlay( *this )
320 for(int n
=0; n
<MT_MAX_CURSORS
; n
++) {
321 m_createStrokes
[n
] = NULL
;
322 m_moveStrokes
[n
] = NULL
;
324 configureScreenTransform( m_window
.width(), m_window
.height() );
330 virtual bool renderScene( Canvas
& c
, int level
)
333 int size
= m_levels
->load( level
, levelbuf
, sizeof(levelbuf
) );
334 if ( size
&& scene
.load( levelbuf
, size
) ) {
335 scene
.draw( c
, FULLSCREEN_RECT
);
341 void gotoLevel( int level
, bool replay
=false )
343 if ( level
>= 0 && level
< m_levels
->numLevels() ) {
344 int size
= m_levels
->load( level
, levelbuf
, sizeof(levelbuf
) );
345 if ( size
&& m_scene
.load( levelbuf
, size
) ) {
346 m_scene
.activateAll();
347 //m_window.setSubName( file );
355 m_player
.start( &m_recorder
.getLog() );
366 bool save( const char *file
=NULL
)
372 p
= Config::userDataDir() + Os::pathSep
+ "L99_saved.nph";
374 if ( m_scene
.save( p
) ) {
375 m_levels
->addPath( p
.c_str() );
376 int l
= m_levels
->findLevel( p
.c_str() );
379 m_window
.setSubName( p
.c_str() );
388 if ( save( SEND_TEMP_FILE
) ) {
390 if ( h
.post( Config::planetRoot().c_str(),
391 "upload", SEND_TEMP_FILE
, "type=level" ) ) {
392 std::string id
= h
.getHeader("NP-Upload-Id");
393 if ( id
.length() > 0 ) {
394 printf("uploaded as id %s\n",id
.c_str());
395 if ( !m_os
->openBrowser((Config::planetRoot()+"?level="+id
).c_str()) ) {
396 showMessage("Unable to launch browser");
399 showMessage("UploadFailed: unknown error");
402 showMessage(std::string("UploadFailed: ")+h
.errorMessage());
408 void setTool( int t
)
413 void editMode( bool set
)
418 void showMessage( const std::string
& msg
)
421 printf("showMessage \"%s\"\n",msg
.c_str());
424 void showOverlay( Overlay
* o
)
430 void hideOverlay( Overlay
* o
)
432 parent()->remove( o
);
440 if ( !m_pauseOverlay
) {
441 m_pauseOverlay
= createIconOverlay( *this, "pause.png", 50, 50 );
443 showOverlay( m_pauseOverlay
);
446 hideOverlay( m_pauseOverlay
);
456 void edit( bool doEdit
)
458 if ( m_edit
!= doEdit
) {
461 if ( !m_editOverlay
) {
462 m_editOverlay
= createEditOverlay(*this);
464 showOverlay( m_editOverlay
);
467 hideOverlay( m_editOverlay
);
468 m_strokeFixed
= false;
469 m_strokeSleep
= false;
470 m_strokeDecor
= false;
471 if ( m_colour
< 2 ) m_colour
= 2;
477 Vec2
cursorPoint(Uint16 x
, Uint16 y
)
480 worldToScreen
.inverseTransform( pt
);
484 Vec2
mousePoint( SDL_Event ev
)
486 Vec2
pt( ev
.button
.x
, ev
.button
.y
);
487 worldToScreen
.inverseTransform( pt
);
491 bool handleGameEvent( SDL_Event
&ev
)
495 switch ( ev
.key
.keysym
.sym
) {
505 showOverlay( createMenuOverlay( *this ) );
508 showOverlay( m_cselector
.overlay() );
515 //toggleOverlay( m_demoOverlay );
519 gotoLevel( m_level
);
523 gotoLevel( m_level
+1 );
527 gotoLevel( m_level
-1 );
530 gotoLevel( m_level
, true );
542 bool handleModEvent( SDL_Event
&ev
)
545 //printf("mod=%d\n",ev.key.keysym.sym,mod);
548 //printf("mod key=%x mod=%d\n",ev.key.keysym.sym,mod);
549 if ( ev
.key
.keysym
.sym
== SDLK_F8
) {
550 mod
= 1; //zoom- == middle (delete)
552 } else if ( ev
.key
.keysym
.sym
== SDLK_F7
) {
553 mod
= 2; //zoom+ == right (move)
558 if ( ev
.key
.keysym
.sym
== SDLK_F7
559 || ev
.key
.keysym
.sym
== SDLK_F8
) {
564 case SDL_MOUSEBUTTONDOWN
:
565 case SDL_MOUSEBUTTONUP
:
566 if ( ev
.button
.button
== SDL_BUTTON_LEFT
&& mod
!= 0 ) {
567 ev
.button
.button
= ((mod
==1) ? SDL_BUTTON_MIDDLE
: SDL_BUTTON_RIGHT
);
574 bool handlePlayEvent( SDL_Event
&ev
)
577 case SDL_MOUSEBUTTONDOWN
:
578 if ( ev
.button
.button
== SDL_BUTTON_LEFT
) {
579 if (startStroke(0, ev
.button
.x
, ev
.button
.y
))
585 case SDL_MOUSEBUTTONUP
:
586 if ( ev
.button
.button
== SDL_BUTTON_LEFT
) {
587 if (finishStroke(0)) {
592 case SDL_MOUSEMOTION
:
593 if (extendStroke(0, ev
.button
.x
, ev
.button
.y
)) {
598 if ( ev
.key
.keysym
.sym
== SDLK_ESCAPE
) {
599 if ( m_createStroke
) {
600 m_scene
.deleteStroke( m_createStroke
);
601 m_createStroke
= NULL
;
602 } else if ( m_scene
.deleteStroke( m_scene
.strokes().at(m_scene
.strokes().size()-1) ) ) {
615 bool startStroke(int index
, Uint16 x
, Uint16 y
)
617 if (m_createStrokes
[index
])
623 if ( m_strokeFixed
) {
624 attrib
|= ATTRIB_GROUND
;
626 if ( m_strokeSleep
) {
627 attrib
|= ATTRIB_SLEEPING
;
629 if ( m_strokeDecor
) {
630 attrib
|= ATTRIB_DECOR
;
632 m_createStrokes
[index
] = m_scene
.newStroke( Path()&cursorPoint(x
, y
), m_colour
, attrib
);
637 bool extendStroke(int index
, Uint16 x
, Uint16 y
)
639 if ( m_createStrokes
[index
] ) {
640 m_scene
.extendStroke( m_createStrokes
[index
], cursorPoint(x
, y
) );
647 bool finishStroke(int index
)
649 if (!m_createStrokes
[index
]) {
653 if ( m_scene
.activate( m_createStrokes
[index
] ) ) {
654 m_stats
.strokeCount
++;
656 m_stats
.pausedStrokes
++;
659 m_scene
.deleteStroke( m_createStrokes
[index
] );
661 m_createStrokes
[index
] = NULL
;
666 bool handleEditEvent( SDL_Event
&ev
)
668 //allow move/delete in normal play!!
669 //if ( !m_edit ) return false;
672 case SDL_MOUSEBUTTONDOWN
:
673 if ( ev
.button
.button
== SDL_BUTTON_RIGHT
) {
675 if (startMoveStroke(0, ev
.button
.x
, ev
.button
.y
)) {
679 if ( ev
.button
.button
== SDL_BUTTON_MIDDLE
) {
680 if (deleteStroke(ev
.button
.x
, ev
.button
.y
)) {
685 case SDL_MOUSEBUTTONUP
:
686 if ( ev
.button
.button
== SDL_BUTTON_RIGHT
) {
687 if (endMoveStroke(0)) {
692 case SDL_MOUSEMOTION
:
693 if (moveStroke(0, ev
.button
.x
, ev
.button
.y
)) {
703 bool startMoveStroke(int index
, Uint16 x
, Uint16 y
)
705 if (m_moveStrokes
[index
]) {
709 m_moveStrokes
[index
] = m_scene
.strokeAtPoint( cursorPoint(x
, y
),
712 if (m_moveStrokes
[index
]) {
713 m_scene
.setNoMass(m_moveStrokes
[index
]);
719 bool endMoveStroke(int index
)
721 if (!m_moveStrokes
[index
]) {
724 m_scene
.setDefaultMass(m_moveStrokes
[index
]);
728 m_moveStrokes
[index
] = NULL
;
732 bool moveStroke(int index
, Uint16 x
, Uint16 y
)
734 if (!m_moveStrokes
[index
]) {
738 m_scene
.moveStroke( m_moveStrokes
[index
], cursorPoint(x
, y
) );
743 bool deleteStroke(Uint16 x
, Uint16 y
)
745 m_scene
.deleteStroke( m_scene
.strokeAtPoint( cursorPoint(x
, y
),
746 SELECT_TOLERANCE
) );
751 bool handleMultitouchEvent ( SDL_Event
&ev
)
754 case SDL_NP_START_STROKE
: {
755 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
756 startStroke(de
->cursor_id
, de
->x
, de
->y
);
761 case SDL_NP_APPEND_STROKE
: {
762 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
763 extendStroke(de
->cursor_id
, de
->x
, de
->y
);
768 case SDL_NP_FINISH_STROKE
: {
769 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
770 finishStroke(de
->cursor_id
);
775 case SDL_NP_START_ROPE
:
778 case SDL_NP_APPEND_ROPE
:
781 case SDL_NP_FINISH_ROPE
:
784 case SDL_NP_START_DRAG
: {
785 DragEvent
* de
= (DragEvent
*)ev
.user
.data1
;
786 startMoveStroke(de
->cursor_id
, de
->x
, de
->y
);
792 DragEvent
* de
= (DragEvent
*)ev
.user
.data1
;
793 moveStroke(de
->cursor_id
, de
->x
, de
->y
);
798 case SDL_NP_END_DRAG
: {
799 DragEvent
* de
= (DragEvent
*)ev
.user
.data1
;
800 endMoveStroke(de
->cursor_id
);
811 case SDL_NP_DELETE
: {
812 DeleteEvent
* de
= (DeleteEvent
*)ev
.user
.data1
;
813 deleteStroke(de
->x
, de
->y
);
824 ////////////////////////////////////////////////////////////////
826 ////////////////////////////////////////////////////////////////
828 virtual bool isDirty()
830 //TODO this can be a bit heavyweight
831 return !dirtyArea().isEmpty();
834 virtual Rect
dirtyArea()
838 return FULLSCREEN_RECT
;
840 return m_scene
.dirtyArea();
844 virtual void onTick( int tick
)
853 while ( m_player
.fetchEvent(ev
) ) {
854 // todo only dispatch play events?
856 || handleGameEvent(ev
)
857 || handleEditEvent(ev
)
858 || handlePlayEvent(ev
)
859 || handleMultitouchEvent(ev
);
862 if ( m_isCompleted
&& m_edit
) {
863 hideOverlay( m_completedOverlay
);
864 m_isCompleted
= false;
866 if ( m_scene
.isCompleted() != m_isCompleted
&& !m_edit
) {
867 m_isCompleted
= m_scene
.isCompleted();
868 if ( m_isCompleted
) {
869 m_stats
.endTime
= SDL_GetTicks();
872 m_completedOverlay
= createNextLevelOverlay(*this, m_scene
.getWinner());
873 showOverlay( m_completedOverlay
);
875 hideOverlay( m_completedOverlay
);
881 for ( char *f
= m_os
->getLaunchFile(); f
; f
=m_os
->getLaunchFile() ) {
882 if ( strstr(f
,".npz") ) {
885 m_levels
->addPath( f
);
886 int l
= m_levels
->findLevel( f
);
895 virtual void draw( Canvas
& screen
, const Rect
& area
)
897 m_scene
.draw( screen
, area
);
903 virtual bool handleEvent( SDL_Event
& ev
)
905 if ( m_player
.isRunning()
906 && ( ev
.type
==SDL_MOUSEMOTION
||
907 ev
.type
==SDL_MOUSEBUTTONDOWN
||
908 ev
.type
==SDL_MOUSEBUTTONUP
) ) {
910 } else if (handleModEvent(ev
)
911 || handleGameEvent(ev
)
912 || handleEditEvent(ev
)
913 || handlePlayEvent(ev
)
914 || handleMultitouchEvent(ev
)) {
915 //\TODO only record edit,play events etc
916 m_recorder
.record( ev
);
925 Widget
* createGameLayer( Levels
* levels
, int width
, int height
)
927 return new Game(levels
,width
,height
);