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 Overlay
*m_levelnameOverlay
;
301 int m_levelnameHideTime
;
302 // DemoOverlay m_demoOverlay;
303 DemoRecorder m_recorder
;
305 CollectionSelector m_cselector
;
311 Game( Levels
* levels
, int width
, int height
, bool fullscreen
)
312 : m_window(width
,height
,"Numpty Physics","NPhysics", fullscreen
),
313 m_pauseOverlay( NULL
),
314 m_editOverlay( NULL
),
315 m_completedOverlay( NULL
),
316 m_levelnameOverlay( NULL
),
317 m_levelnameHideTime( 0 ),
318 m_isCompleted(false),
319 m_cselector( *this ),
322 //,m_demoOverlay( *this )
324 for(int n
=0; n
<MT_MAX_CURSORS
; n
++) {
325 m_createStrokes
[n
] = NULL
;
326 m_moveStrokes
[n
] = NULL
;
328 configureScreenTransform( m_window
.width(), m_window
.height() );
334 virtual bool renderScene( Canvas
& c
, int level
)
337 int size
= m_levels
->load( level
, levelbuf
, sizeof(levelbuf
) );
338 if ( size
&& scene
.load( levelbuf
, size
) ) {
339 scene
.draw( c
, FULLSCREEN_RECT
);
345 void gotoLevel( int level
, bool replay
=false )
347 if ( level
>= 0 && level
< m_levels
->numLevels() ) {
348 int size
= m_levels
->load( level
, levelbuf
, sizeof(levelbuf
) );
349 if ( size
&& m_scene
.load( levelbuf
, size
) ) {
350 m_scene
.activateAll();
351 //m_window.setSubName( file );
359 m_player
.start( &m_recorder
.getLog() );
365 if (m_levelnameOverlay
&& m_levelnameHideTime
) {
366 hideOverlay(m_levelnameOverlay
);
367 delete m_levelnameOverlay
;
368 m_levelnameHideTime
= 0;
371 std::string title
= m_scene
.getTitle(), author
= m_scene
.getAuthor();
373 /* Only show title if we have at least one of (title, author) specified */
374 if (!title
.empty() || !author
.empty()) {
375 m_levelnameOverlay
= createTextOverlay( *this,
376 ((title
.empty())?(std::string("Untitled")):(title
)) +
377 std::string(" by ") +
378 ((author
.empty())?(std::string("Anonymous")):(author
)));
379 m_levelnameHideTime
= -1; /* in onTick, -1 means "show the overlay" */
386 bool save( const char *file
=NULL
)
392 p
= Config::userDataDir() + Os::pathSep
+ "L99_saved.nph";
394 if ( m_scene
.save( p
) ) {
395 m_levels
->addPath( p
.c_str() );
396 int l
= m_levels
->findLevel( p
.c_str() );
399 m_window
.setSubName( p
.c_str() );
408 if ( save( SEND_TEMP_FILE
) ) {
410 if ( h
.post( Config::planetRoot().c_str(),
411 "upload", SEND_TEMP_FILE
, "type=level" ) ) {
412 std::string id
= h
.getHeader("NP-Upload-Id");
413 if ( id
.length() > 0 ) {
414 printf("uploaded as id %s\n",id
.c_str());
415 if ( !m_os
->openBrowser((Config::planetRoot()+"?level="+id
).c_str()) ) {
416 showMessage("Unable to launch browser");
419 showMessage("UploadFailed: unknown error");
422 showMessage(std::string("UploadFailed: ")+h
.errorMessage());
428 void setTool( int t
)
433 void editMode( bool set
)
438 void showMessage( const std::string
& msg
)
441 printf("showMessage \"%s\"\n",msg
.c_str());
444 void showOverlay( Overlay
* o
)
450 void hideOverlay( Overlay
* o
)
452 parent()->remove( o
);
460 if ( !m_pauseOverlay
) {
461 m_pauseOverlay
= createIconOverlay( *this, "pause.png", 50, 50 );
463 showOverlay( m_pauseOverlay
);
466 hideOverlay( m_pauseOverlay
);
476 void edit( bool doEdit
)
478 if ( m_edit
!= doEdit
) {
481 if ( !m_editOverlay
) {
482 m_editOverlay
= createEditOverlay(*this);
484 showOverlay( m_editOverlay
);
487 hideOverlay( m_editOverlay
);
488 m_strokeFixed
= false;
489 m_strokeSleep
= false;
490 m_strokeDecor
= false;
491 if ( m_colour
< 2 ) m_colour
= 2;
497 Vec2
cursorPoint(Uint16 x
, Uint16 y
)
500 worldToScreen
.inverseTransform( pt
);
504 Vec2
mousePoint( SDL_Event ev
)
506 Vec2
pt( ev
.button
.x
, ev
.button
.y
);
507 worldToScreen
.inverseTransform( pt
);
511 bool handleGameEvent( SDL_Event
&ev
)
515 switch ( ev
.key
.keysym
.sym
) {
525 showOverlay( createMenuOverlay( *this ) );
528 showOverlay( m_cselector
.overlay() );
535 //toggleOverlay( m_demoOverlay );
539 gotoLevel( m_level
);
543 gotoLevel( m_level
+1 );
547 gotoLevel( m_level
-1 );
550 gotoLevel( m_level
, true );
562 bool handleModEvent( SDL_Event
&ev
)
565 //printf("mod=%d\n",ev.key.keysym.sym,mod);
568 //printf("mod key=%x mod=%d\n",ev.key.keysym.sym,mod);
569 if ( ev
.key
.keysym
.sym
== SDLK_F8
) {
570 mod
= 1; //zoom- == middle (delete)
572 } else if ( ev
.key
.keysym
.sym
== SDLK_F7
) {
573 mod
= 2; //zoom+ == right (move)
578 if ( ev
.key
.keysym
.sym
== SDLK_F7
579 || ev
.key
.keysym
.sym
== SDLK_F8
) {
584 case SDL_MOUSEBUTTONDOWN
:
585 case SDL_MOUSEBUTTONUP
:
586 if ( ev
.button
.button
== SDL_BUTTON_LEFT
&& mod
!= 0 ) {
587 ev
.button
.button
= ((mod
==1) ? SDL_BUTTON_MIDDLE
: SDL_BUTTON_RIGHT
);
594 bool handlePlayEvent( SDL_Event
&ev
)
597 case SDL_MOUSEBUTTONDOWN
:
598 if ( ev
.button
.button
== SDL_BUTTON_LEFT
) {
599 if (startStroke(0, ev
.button
.x
, ev
.button
.y
))
605 case SDL_MOUSEBUTTONUP
:
606 if ( ev
.button
.button
== SDL_BUTTON_LEFT
) {
607 if (finishStroke(0)) {
612 case SDL_MOUSEMOTION
:
613 if (extendStroke(0, ev
.button
.x
, ev
.button
.y
)) {
618 if ( ev
.key
.keysym
.sym
== SDLK_ESCAPE
) {
619 if ( m_createStroke
) {
620 m_scene
.deleteStroke( m_createStroke
);
621 m_createStroke
= NULL
;
622 } else if ( m_scene
.deleteStroke( m_scene
.strokes().at(m_scene
.strokes().size()-1) ) ) {
635 bool startStroke(int index
, Uint16 x
, Uint16 y
)
637 if (m_createStrokes
[index
])
643 if ( m_strokeFixed
) {
644 attrib
|= ATTRIB_GROUND
;
646 if ( m_strokeSleep
) {
647 attrib
|= ATTRIB_SLEEPING
;
649 if ( m_strokeDecor
) {
650 attrib
|= ATTRIB_DECOR
;
652 m_createStrokes
[index
] = m_scene
.newStroke( Path()&cursorPoint(x
, y
), m_colour
, attrib
);
657 bool extendStroke(int index
, Uint16 x
, Uint16 y
)
659 if ( m_createStrokes
[index
] ) {
660 m_scene
.extendStroke( m_createStrokes
[index
], cursorPoint(x
, y
) );
667 bool finishStroke(int index
)
669 if (!m_createStrokes
[index
]) {
673 if ( m_scene
.activate( m_createStrokes
[index
] ) ) {
674 m_stats
.strokeCount
++;
676 m_stats
.pausedStrokes
++;
679 m_scene
.deleteStroke( m_createStrokes
[index
] );
681 m_createStrokes
[index
] = NULL
;
686 bool handleEditEvent( SDL_Event
&ev
)
688 //allow move/delete in normal play!!
689 //if ( !m_edit ) return false;
692 case SDL_MOUSEBUTTONDOWN
:
693 if ( ev
.button
.button
== SDL_BUTTON_RIGHT
) {
695 if (startMoveStroke(0, ev
.button
.x
, ev
.button
.y
)) {
699 if ( ev
.button
.button
== SDL_BUTTON_MIDDLE
) {
700 if (deleteStroke(ev
.button
.x
, ev
.button
.y
)) {
705 case SDL_MOUSEBUTTONUP
:
706 if ( ev
.button
.button
== SDL_BUTTON_RIGHT
) {
707 if (endMoveStroke(0)) {
712 case SDL_MOUSEMOTION
:
713 if (moveStroke(0, ev
.button
.x
, ev
.button
.y
)) {
723 bool startMoveStroke(int index
, Uint16 x
, Uint16 y
)
725 if (m_moveStrokes
[index
]) {
729 m_moveStrokes
[index
] = m_scene
.strokeAtPoint( cursorPoint(x
, y
),
732 if (m_moveStrokes
[index
]) {
733 m_scene
.setNoMass(m_moveStrokes
[index
]);
739 bool endMoveStroke(int index
)
741 if (!m_moveStrokes
[index
]) {
744 m_scene
.setDefaultMass(m_moveStrokes
[index
]);
748 m_moveStrokes
[index
] = NULL
;
752 bool moveStroke(int index
, Uint16 x
, Uint16 y
)
754 if (!m_moveStrokes
[index
]) {
758 m_scene
.moveStroke( m_moveStrokes
[index
], cursorPoint(x
, y
) );
763 bool deleteStroke(Uint16 x
, Uint16 y
)
765 m_scene
.deleteStroke( m_scene
.strokeAtPoint( cursorPoint(x
, y
),
766 SELECT_TOLERANCE
) );
771 bool handleMultitouchEvent ( SDL_Event
&ev
)
774 case SDL_NP_START_STROKE
: {
775 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
776 startStroke(de
->cursor_id
, de
->x
, de
->y
);
781 case SDL_NP_APPEND_STROKE
: {
782 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
783 extendStroke(de
->cursor_id
, de
->x
, de
->y
);
788 case SDL_NP_FINISH_STROKE
: {
789 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
790 finishStroke(de
->cursor_id
);
795 case SDL_NP_START_ROPE
:
798 case SDL_NP_APPEND_ROPE
:
801 case SDL_NP_FINISH_ROPE
:
804 case SDL_NP_START_DRAG
: {
805 DragEvent
* de
= (DragEvent
*)ev
.user
.data1
;
806 startMoveStroke(de
->cursor_id
, de
->x
, de
->y
);
812 DragEvent
* de
= (DragEvent
*)ev
.user
.data1
;
813 moveStroke(de
->cursor_id
, de
->x
, de
->y
);
818 case SDL_NP_END_DRAG
: {
819 DragEvent
* de
= (DragEvent
*)ev
.user
.data1
;
820 endMoveStroke(de
->cursor_id
);
831 case SDL_NP_DELETE
: {
832 DeleteEvent
* de
= (DeleteEvent
*)ev
.user
.data1
;
833 deleteStroke(de
->x
, de
->y
);
844 ////////////////////////////////////////////////////////////////
846 ////////////////////////////////////////////////////////////////
848 virtual bool isDirty()
850 //TODO this can be a bit heavyweight
851 return !dirtyArea().isEmpty();
854 virtual Rect
dirtyArea()
858 return FULLSCREEN_RECT
;
860 return m_scene
.dirtyArea();
864 virtual void onTick( int tick
)
872 if (m_levelnameHideTime
== -1 && m_levelnameOverlay
) {
873 showOverlay(m_levelnameOverlay
);
874 m_levelnameHideTime
= tick
+ 4000;
875 } else if (m_levelnameHideTime
!= 0 &&
876 m_levelnameHideTime
<tick
&& m_levelnameOverlay
) {
877 hideOverlay(m_levelnameOverlay
);
878 m_levelnameHideTime
= 0;
882 while ( m_player
.fetchEvent(ev
) ) {
883 // todo only dispatch play events?
885 || handleGameEvent(ev
)
886 || handleEditEvent(ev
)
887 || handlePlayEvent(ev
)
888 || handleMultitouchEvent(ev
);
891 if ( m_isCompleted
&& m_edit
) {
892 hideOverlay( m_completedOverlay
);
893 m_isCompleted
= false;
895 if ( m_scene
.isCompleted() != m_isCompleted
&& !m_edit
) {
896 m_isCompleted
= m_scene
.isCompleted();
897 if ( m_isCompleted
) {
898 m_stats
.endTime
= SDL_GetTicks();
901 m_completedOverlay
= createNextLevelOverlay(*this, m_scene
.getWinner());
902 showOverlay( m_completedOverlay
);
904 hideOverlay( m_completedOverlay
);
910 for ( char *f
= m_os
->getLaunchFile(); f
; f
=m_os
->getLaunchFile() ) {
911 if ( strstr(f
,".npz") ) {
914 m_levels
->addPath( f
);
915 int l
= m_levels
->findLevel( f
);
924 virtual void draw( Canvas
& screen
, const Rect
& area
)
926 m_scene
.draw( screen
, area
);
932 virtual bool handleEvent( SDL_Event
& ev
)
934 if ( m_player
.isRunning()
935 && ( ev
.type
==SDL_MOUSEMOTION
||
936 ev
.type
==SDL_MOUSEBUTTONDOWN
||
937 ev
.type
==SDL_MOUSEBUTTONUP
) ) {
939 } else if (handleModEvent(ev
)
940 || handleGameEvent(ev
)
941 || handleEditEvent(ev
)
942 || handlePlayEvent(ev
)
943 || handleMultitouchEvent(ev
)) {
944 //\TODO only record edit,play events etc
945 m_recorder
.record( ev
);
951 virtual Window
* getWindow() {
958 Widget
* createGameLayer( Levels
* levels
, int width
, int height
, bool fullscreen
)
960 return new Game(levels
,width
,height
,fullscreen
);