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
) {
624 else if ( m_scene
.deleteStroke( m_scene
.strokes().at(m_scene
.strokes().size()-1) ) ) {
637 bool startStroke(int index
, Uint16 x
, Uint16 y
)
639 if (m_createStrokes
[index
])
645 if ( m_strokeFixed
) {
646 attrib
|= ATTRIB_GROUND
;
648 if ( m_strokeSleep
) {
649 attrib
|= ATTRIB_SLEEPING
;
651 if ( m_strokeDecor
) {
652 attrib
|= ATTRIB_DECOR
;
654 m_createStrokes
[index
] = m_scene
.newStroke( Path()&cursorPoint(x
, y
), m_colour
, attrib
);
659 bool extendStroke(int index
, Uint16 x
, Uint16 y
)
661 if ( m_createStrokes
[index
] ) {
662 m_scene
.extendStroke( m_createStrokes
[index
], cursorPoint(x
, y
) );
669 bool finishStroke(int index
)
671 if (!m_createStrokes
[index
]) {
675 if ( m_scene
.activate( m_createStrokes
[index
] ) ) {
676 m_stats
.strokeCount
++;
678 m_stats
.pausedStrokes
++;
681 m_scene
.deleteStroke( m_createStrokes
[index
] );
683 m_createStrokes
[index
] = NULL
;
688 bool cancelDraw(int index
)
690 if ( m_createStrokes
[index
] )
692 m_scene
.deleteStroke( m_createStrokes
[index
] );
693 m_createStrokes
[index
] = NULL
;
699 bool handleEditEvent( SDL_Event
&ev
)
701 //allow move/delete in normal play!!
702 //if ( !m_edit ) return false;
705 case SDL_MOUSEBUTTONDOWN
:
706 if ( ev
.button
.button
== SDL_BUTTON_RIGHT
) {
708 if (startMoveStroke(0, ev
.button
.x
, ev
.button
.y
)) {
712 if ( ev
.button
.button
== SDL_BUTTON_MIDDLE
) {
713 if (deleteStroke(ev
.button
.x
, ev
.button
.y
)) {
718 case SDL_MOUSEBUTTONUP
:
719 if ( ev
.button
.button
== SDL_BUTTON_RIGHT
) {
720 if (endMoveStroke(0)) {
725 case SDL_MOUSEMOTION
:
726 if (moveStroke(0, ev
.button
.x
, ev
.button
.y
)) {
736 bool startMoveStroke(int index
, Uint16 x
, Uint16 y
)
738 if (m_moveStrokes
[index
]) {
742 m_moveStrokes
[index
] = m_scene
.strokeAtPoint( cursorPoint(x
, y
),
745 if (m_moveStrokes
[index
]) {
746 m_scene
.setNoMass(m_moveStrokes
[index
]);
752 bool endMoveStroke(int index
)
754 if (!m_moveStrokes
[index
]) {
757 m_scene
.setDefaultMass(m_moveStrokes
[index
]);
761 m_moveStrokes
[index
] = NULL
;
765 bool moveStroke(int index
, Uint16 x
, Uint16 y
)
767 if (!m_moveStrokes
[index
]) {
771 m_scene
.moveStroke( m_moveStrokes
[index
], cursorPoint(x
, y
) );
776 bool deleteStroke(Uint16 x
, Uint16 y
)
778 m_scene
.deleteStroke( m_scene
.strokeAtPoint( cursorPoint(x
, y
),
779 SELECT_TOLERANCE
) );
784 bool handleMultitouchEvent ( SDL_Event
&ev
)
787 case SDL_NP_START_STROKE
: {
788 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
789 startStroke(de
->cursor_id
, de
->x
, de
->y
);
794 case SDL_NP_APPEND_STROKE
: {
795 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
796 extendStroke(de
->cursor_id
, de
->x
, de
->y
);
801 case SDL_NP_FINISH_STROKE
: {
802 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
803 finishStroke(de
->cursor_id
);
808 case SDL_NP_START_ROPE
:
811 case SDL_NP_APPEND_ROPE
:
814 case SDL_NP_FINISH_ROPE
:
817 case SDL_NP_CANCEL_DRAW
: {
818 CursorEvent
* ce
= (CursorEvent
*)ev
.user
.data1
;
819 cancelDraw(ce
->cursor_id
);
823 case SDL_NP_START_DRAG
: {
824 DragEvent
* de
= (DragEvent
*)ev
.user
.data1
;
825 startMoveStroke(de
->cursor_id
, de
->x
, de
->y
);
831 DragEvent
* de
= (DragEvent
*)ev
.user
.data1
;
832 moveStroke(de
->cursor_id
, de
->x
, de
->y
);
837 case SDL_NP_END_DRAG
: {
838 DragEvent
* de
= (DragEvent
*)ev
.user
.data1
;
839 endMoveStroke(de
->cursor_id
);
850 case SDL_NP_DELETE
: {
851 DeleteEvent
* de
= (DeleteEvent
*)ev
.user
.data1
;
852 deleteStroke(de
->x
, de
->y
);
857 case SDL_NP_PREVIEW_CURSOR
: {
858 DrawEvent
* de
= (DrawEvent
*)ev
.user
.data1
;
859 previewCursor(de
->cursor_id
, de
->x
, de
->y
);
867 bool previewCursor(int cursor_id
, int x
, int y
)
869 m_window
.previewCursor(cursor_id
, x
, y
);
873 ////////////////////////////////////////////////////////////////
875 ////////////////////////////////////////////////////////////////
877 virtual bool isDirty()
879 //TODO this can be a bit heavyweight
880 return !dirtyArea().isEmpty();
883 virtual Rect
dirtyArea()
887 return FULLSCREEN_RECT
;
889 return m_scene
.dirtyArea();
893 virtual void onTick( int tick
)
901 if (m_levelnameHideTime
== -1 && m_levelnameOverlay
) {
902 showOverlay(m_levelnameOverlay
);
903 m_levelnameHideTime
= tick
+ 4000;
904 } else if (m_levelnameHideTime
!= 0 &&
905 m_levelnameHideTime
<tick
&& m_levelnameOverlay
) {
906 hideOverlay(m_levelnameOverlay
);
907 m_levelnameHideTime
= 0;
911 while ( m_player
.fetchEvent(ev
) ) {
912 // todo only dispatch play events?
914 || handleGameEvent(ev
)
915 || handleEditEvent(ev
)
916 || handlePlayEvent(ev
)
917 || handleMultitouchEvent(ev
);
920 if ( m_isCompleted
&& m_edit
) {
921 hideOverlay( m_completedOverlay
);
922 m_isCompleted
= false;
924 if ( m_scene
.isCompleted() != m_isCompleted
&& !m_edit
) {
925 m_isCompleted
= m_scene
.isCompleted();
926 if ( m_isCompleted
) {
927 m_stats
.endTime
= SDL_GetTicks();
930 m_completedOverlay
= createNextLevelOverlay(*this, m_scene
.getWinner());
931 showOverlay( m_completedOverlay
);
933 hideOverlay( m_completedOverlay
);
939 for ( char *f
= m_os
->getLaunchFile(); f
; f
=m_os
->getLaunchFile() ) {
940 if ( strstr(f
,".npz") ) {
943 m_levels
->addPath( f
);
944 int l
= m_levels
->findLevel( f
);
953 virtual void draw( Canvas
& screen
, const Rect
& area
)
955 m_scene
.draw( screen
, area
);
961 virtual bool handleEvent( SDL_Event
& ev
)
963 if ( m_player
.isRunning()
964 && ( ev
.type
==SDL_MOUSEMOTION
||
965 ev
.type
==SDL_MOUSEBUTTONDOWN
||
966 ev
.type
==SDL_MOUSEBUTTONUP
) ) {
968 } else if (handleModEvent(ev
)
969 || handleGameEvent(ev
)
970 || handleEditEvent(ev
)
971 || handlePlayEvent(ev
)
972 || handleMultitouchEvent(ev
)) {
973 //\TODO only record edit,play events etc
974 m_recorder
.record( ev
);
980 virtual Window
* getWindow() {
987 Widget
* createGameLayer( Levels
* levels
, int width
, int height
, bool fullscreen
)
989 return new Game(levels
,width
,height
,fullscreen
);