Commits

stqn committed f6dad44

Improvements to the editor (auto-tmp-save before testing level, view position, help text, toolbox auto-opens). FontManager for different fonts.

  • Participants
  • Parent commits 6cd1637

Comments (0)

Files changed (9)

 Choses à faire et idées.
 
-Indispensable:
-- Map du niveau 1 ? (ou on laisse les joueur s'en occuper)
+Bugs bloquants :
+ aucun
 
-WIP:
-- Finalisation/intégration des GameParts afin d'implémenter proprement les
-  menus, jeu et éditeur, écran de gain.
+Bugs gênants :
+- La liste des maps devrait être scrollable quand ça ne tient pas sur l'écran.
 
-- Implémentation de Controlers pour le gameplay, l'éditeur et le héros afin
-  de pouvoir facilement changer les contrôles.
-
-- Éditeur ingame : permet de pauser le jeu et de modifier la map.
-  Le même GamePart est utilisé pour le gameplay et l'éditeur.
-  Afin de modifier les positions de départ des ennemis, powerups, portes, il
-  est souhaitable de pouvoir afficher l'état original de la map.
-
-  Changement de plan : l'éditeur n'est pas ingame, mais il devrait y avoir
-  des facilités pour aller tester directement l'endroit qui nous intéresse
-  avec les powerups qu'on veut.
-
-Menus:
-	Menu principal
-	- Play
-	- Options
-	- Quitter
-
-	Menu "Play"
-	- Choix du niveau
-
-	Menu Options
-	- Volume musique
-	- Volume bruitages
-	- Fullscreen
-	- Langue
-
-	Menu Langue
-	- Choix de la langue
-
-	Menu Pause
-
+Bugs mineurs :
+- Éditeur : la case highlightée n'est pas sous le pointeur quand la view n'est
+  pas alignée sur la grille.
 
 À faire un jour peut-être :
+- Vraie map fun à jouer
 - Écran de gain
-- Pause du jeu quand on désélectionne la fenêtre
+- Pause du jeu quand on désélectionne la fenêtre (pour prendre 0% du cpu)
 - Explosions/Particules (projectiles et ennemis)
-- High scores (onlines ? pour empêcher la triche il faut enregistrer la partie et la vérifier...)
+- High scores (onlines ? pour empêcher la triche il faut enregistrer la partie
+  et la vérifier...)
 - Credits
 - Inclusion des libs Linux ? ... non
 - Réduction du temps du chrono quand on tue des ennemis ?
 - GameObject qui change la musique ? Si on a plusieurs musiques...
 - Voir si on peut enlever une des deux zlib.dll (Windows sucks and must die ?)
 - Tremblement de la View quand on se fait toucher
-- Mode plein écran
+- Mode plein écran avec ratio correct et qui ne déplace pas les fenêtres
 - Vraie synchro vbl (note: essayer avec vblank_mode=1 ?)
-
-Notes pour un éditeur de niveaux:
-- vues : normale, niveau complet (passage de l'une à l'autre par click molette)
-- actions : ajoût d'une tuile, undo, insertion/suppression d'une
-  colonne/ligne/rectangle, sélection d'une tuile, sélection d'une action,
-  chargement, sauvegarde
-- test du jeu en positionnant le héros sous le pointeur avec tous les powerups.
-- ne grabber la souris que quand RMB est pressé
+- Éditeur : Demander si on veut sauver quand on quitte et qu'il y a eu des
+  modifs.
+- Éditeur : rotation molette pour dézoomer/zoomer sur le niveau (pour avoir une
+  vue d'ensemble et aller rapidement à un endroit éloigné).
+- Éditeur : undo/redo
+- Éditeur : copier/couper/coller (?)
+- Éditeur : insertion/suppression d'une zone rectangulaire (?)
+- Éditeur : test du jeu en positionnant le héros sous le pointeur avec tous les
+  powerups (?).
 
 Notes pour le partage de maps sur Internet :
 - Champ "auteur" rempli par le joueur.
 debug import std.stdio;
 
 class Editor: public GamePart {
+
+	const string TMP_TEST_MAP_NAME = "editor_test_tmp";
 
 	GAMEPART_STATE mState;
 	SDL_Surface* mBg;
 	ActionType mAction;
 	string mPointerIcon;
 	char mTileCode;
-	bool mFromGame;
+	//bool mFromGame;
 	SDL_Rect mHighlightRect;
 	SDL_Surface* mHighlight;
 	MapNameDialog mMapNameDialog;
 	void delegate() fMode;
 	void delegate() fPreviousMode;
 
-	this( bool fromGame=false ) {
+	this( bool fromGame = false ) {
 		// if coming from game ( test mode ), reload the map
 		// else load an empty map.
-		mFromGame = fromGame;
-		string map_name;
+		//mFromGame = fromGame;
+		string map_name;
 		if( gMap !is null ) {
-			 map_name = gMap.name;
+			map_name = gMap.name();
 			delete gMap;
 		}
-		if( fromGame && map_name.length>0 ) new Map( map_name );
-		else new Map("");
-
-		gView.setTarget( null );
+		if( fromGame /*&& map_name.length>0*/ ) {
+			new Map( TMP_TEST_MAP_NAME );
+			std.file.remove( "maps/" ~ gMap.name() );
+			gMap.name( map_name );
+		}
+		else {
+			new Map("");
+		}
+
+		// Don't follow the hero
+		gView.setFollowTarget( false );
+		//gView.setPosition( 0.f, 0.f );
+		gView.clampPosition();
 
 		mBg = gTextureManager.get( "background.png" );
 
 		mAction = ActionType.NOTHING;
-
-		fMode = &editMode;
+
+		if( !fromGame ) {
+			openTools();
+		}
+		else {
+			fMode = &editMode;
+		}
 	}
 
 	~this() {
 		gView.draw();
 		fMode();
 	}
-
+
+	/**
+	 * Open the toolbox (enter toolsMode).
+	 */
+	void openTools() {
+		mTools = new EditorTools;
+		GUI.instance.changePointer( "pointer.png" );
+		fMode = &toolsMode;
+	}
+
 	void editMode() {
 		GUI.instance.update();
 
 		checkEscapeToQuit();
 
 		if( gInputManager.isMouseButtonUp(SDL_BUTTON_MIDDLE) ) {
-			mTools = new EditorTools;
-			GUI.instance.changePointer( "pointer.png" );
-			fMode = &toolsMode;
+			openTools();
 			return;
 		}
 
 
 				case ActionType.ADD_TILE:
 					gMap.replaceTile( xMap+xOffset, yMap+yOffset, mTileCode );
-					gView.setTarget( null ); // to reset the target set in Hero.
 				break;
 
 				case ActionType.DELETE_TILE:
 			fMode = fPreviousMode;
 		}
 		else {
-
 			// movement relative to the center of the screen
 			// manually computed because of the SDL relative events generated by
 			// WarpMouse...
 				break;
 
 				case ActionType.TEST_MAP:
-					//mAlert = new Alert("Not implemented yet !");
-					//fMode=&mapTestMode;
-					mState = GAMEPART_STATE.CONTINUE;
-					//fMode = &alertMode;
-					//fPreviousMode = &editMode;
+					// Save map before in temporary file before testing
+					string originalMapName = gMap.name();
+					gMap.name( TMP_TEST_MAP_NAME );
+					bool mapSaved = saveMap( true );
+					gMap.name( originalMapName );
+
+					if( mapSaved ) {
+						mState = GAMEPART_STATE.CONTINUE;
+					}
 				break;
 
 				case ActionType.QUIT_EDITOR:
 
 	}
 
-	void saveMap() {
-		if( gMap.save() ) {
-			mAlert = new Alert( "map '"~gMap.name~"' saved !" );
+	bool saveMap( bool quiet = false ) {
+		bool mapSaved = gMap.save();
+		if( mapSaved ) {
+			if( !quiet ) {
+				mAlert = new Alert( "map '"~gMap.name~"' saved !" );
+			}
 		}
 		else {
 			mAlert = new Alert( "Error while saving map !", "" );
 		}
-		fMode = &alertMode;
-		fPreviousMode = &editMode;
+		if( !quiet || !mapSaved ) {
+			fMode = &alertMode;
+			fPreviousMode = &editMode;
+		}
+		return mapSaved;
 	}
 
 	void mapLoadMode() {
-
 		GUI.instance.update();
 		if( mMapListDialog.done() ) {
 			if( mMapListDialog.ok() && mMapListDialog.getMapName().length > 0 ) {
 				delete(gMap);
 				new Map(mMapListDialog.getMapName() );
-				gView.setTarget( null );
+				gView.clampPosition();
 			}
 			fMode = &editMode;
 			delete( mMapListDialog );

src/editortools.d

 
 module editortools;
-
-import gui;
-import gameobject;
+
 import std.stdio;
-import gameobjectfactory;
 
 import editoractions;
+import gameobject;
+import gameobjectfactory;
+import gui;
+import textsurface;
+
 
 class EditorTools: public GUI.ButtonListener {
 
 	GUI.Panel mTilesPanel;
 	GUI.Panel mActionsPanel;
+	GUI.Panel mHelpPanel;
 
 	struct Action {
 		string caption;
 	this() {
 		initActions();
 		initTiles();
+		initHelp();
 	}
 
 	~this() {
 		GUI.instance.removePanel( mActionsPanel );
 		GUI.instance.removePanel( mTilesPanel );
+		GUI.instance.removePanel( mHelpPanel );
 	}
 
 	void initActions() {
 		mActions ~= Action( "Delete Line", "row_delete.png", ActionType.DELETE_LINE );
 
 		mActionsPanel = GUI.instance.addPanel( 0,0 );
-		mActionsPanel.setAlign( GUI.Panel.ALIGN.MIN, GUI.Panel.ALIGN.CENTERED );
+		mActionsPanel.setAlign( GUI.Panel.ALIGN.MIN, GUI.Panel.ALIGN.MAX );
 		foreach( inout action; mActions ) {
 			if( action.type == ActionType.NOTHING ) {
 				mActionsPanel.addLabel( "--------" );
 				mActionsPanel.nl();
 			}
 		}
-
 	}
 
 	void initTiles() {
 		mTiles ~= Tile( "delete.png", '\1' );
 
 		mTilesPanel = GUI.instance.addPanel( 300, 0 );
-		mTilesPanel.setAlign( GUI.Panel.ALIGN.MAX, GUI.Panel.ALIGN.CENTERED );
+		mTilesPanel.setAlign( GUI.Panel.ALIGN.MAX, GUI.Panel.ALIGN.MAX );
 		foreach( inout tile; mTiles ) {
 
 			if( tile.mapCode == 0 ) {
 		}
 
 		mSelectedAction = ActionType.NOTHING;
-
 	}
+
+	void initHelp() {
+		mHelpPanel = GUI.instance.addPanel( 0, 0 );
+		mHelpPanel.setAlign( GUI.Panel.ALIGN.CENTERED, GUI.Panel.ALIGN.MIN );
+		mHelpPanel.addLabel( "Middle mouse button to show/hide the menu.", FontManager.smallFontId() );
+		mHelpPanel.nl();
+		mHelpPanel.addLabel( "Right mouse button + movement to scroll.", FontManager.smallFontId() );
+		mHelpPanel.nl();
+		mHelpPanel.addLabel( "Left mouse button to draw.", FontManager.smallFontId() );
+	}
 
 	void onButtonPressed( GUI.Button b ) {
 
         new Chrono;
 		gSoundManager.play( gSoundManager.get("start.ogg"), MIX_MAX_VOLUME/2 );
 		mTestMode = testMode;
+		// Follow the hero
+		gView.setFollowTarget( true );
     }
 
     ~this() {

src/gamemanager.d

 			case GAMEPART_STATE.EDITOR_INGAME: changePart( new Editor(true) ); break;
 			case GAMEPART_STATE.EDITOR: changePart( new Editor(false) ); break;
 			case GAMEPART_STATE.MENU_MAP_EDITOR: changePart( new MapMenuEditor() );	break;
-			case GAMEPART_STATE.CONTINUE: changePart( new Game(true)); break;
+			case GAMEPART_STATE.CONTINUE: changePart( new Game(true) ); break;
 			case GAMEPART_STATE.MENU_OPTIONS: changePart( new MenuOptions() ); break;
 			//case GAMEPART_STATE.INPUT_CONFIG: changePart( new InputConfigurationDialog() ); break;
 			case GAMEPART_STATE.EXIT:
 
 	class Label: public Widget, Renderable {
 		TextSurface mText;
-		this( Widget parent, int x, int y, string label, string fontid ) {
+		this( Widget parent, int x, int y, string label, string fontId = "" ) {
 			super(parent, x, y );
-			mText = new TextSurface(label); // , fontid, 0x00FFFF );
+			mText = new TextSurface( label, fontId );//, 0x00FFFF );
 			mW = mText.w;
 			mH = mText.h;
 		}
 			return( b );
 		}
 
-		Label addLabel( string label, string fontid="" ) {
-			Label l = new Label( this, mXInsert, mYInsert, label, fontid );
+		Label addLabel( string label, string fontId = "" ) {
+			Label l = new Label( this, mXInsert, mYInsert, label, fontId );
 			mLabels ~= l;
 			resize( l.width, l.height );
 			return(l);
 		}
 	}
 
+
 	bool load( string mapFileName ) {
+		//char[] file = cast(char[]) read("maps/" ~ mapFileName);
+		//mMapFileName = mapFileName;
+		name = mapFileName;	// needed because of the ".map" thing...
 
-		//char[] file = cast(char[]) read("maps/" ~ mapFileName);
-		mMapFileName = mapFileName;
+		writefln( "parsing ", mMapFileName );
 
-		writefln( "parsing ", mapFileName );
-
-		if( parse( "maps/" ~ mapFileName ) ) {
+		if( parse( "maps/" ~ mMapFileName ) ) {
 			//mCharMap = file.splitlines();
 			mHeight = mCharMap.length;
 			mWidth = mCharMap[0].length;

src/textsurface.d

-/**
- * Class for creating and drawing text surfaces.
- */
-
 module textsurface;
 
 import derelict.sdl.sdl;
 import derelict.sdl.ttf;
+import std.conv;
 import std.stdio;
 import std.string;
 
 import screen;
 
 
+/**
+ * Class for loading fonts.
+ */
+class FontManager {
+	static bool ttfInit = false;
+	static TTF_Font*[string] mFonts;
+
+	/**
+	 * @param fontId string of the type "Font Name.ttf/size".
+	 */
+	static TTF_Font* getFont( string fontId ) {
+		if( !ttfInit ) {
+			DerelictSDLttf.load();
+			if( TTF_Init() ) {
+				writefln( "Can't init SDL_ttf." );
+				assert(0);
+			}
+		}
+
+		//string fontId = name ~ "/" ~ std.string.toString(size);
+		//writefln( "getFont( ", fontId, " )" );
+		if( fontId == "" ) {
+			fontId = normalFontId();
+		}
+		if( !(fontId in mFonts) ) {
+			auto str = std.string.split( fontId, "/" );
+			//writefln( str[0], ", ", str[1] );
+			auto name = str[0];
+			auto size = std.conv.toInt( str[1] );
+
+			mFonts[fontId] = TTF_OpenFont( toStringz("data/" ~ name), size );
+			if( !mFonts[fontId] ) {
+				writefln( "openfont error: %s", std.string.toString(TTF_GetError()) );
+				assert(0);
+			}
+		}
+
+		return mFonts[fontId];
+	}
+
+	static string smallFontId() {
+		return "Comfortaa Bold.ttf/16";
+	}
+
+	static string normalFontId() {
+		return "Comfortaa Bold.ttf/24";
+	}
+}
+
+
+/**
+ * Class for creating and drawing text surfaces.
+ */
 class TextSurface {
 
 	SDL_Surface* mSurface;
 	char[] mText;
 
-	static TTF_Font* mFont;
+	TTF_Font* mFont;
 
 
-	this( char[] text = "" ) {
-		if( !mFont ) {
-			DerelictSDLttf.load();
-			if( TTF_Init() ) {
-				writefln( "Can't init SDL_ttf." );
-				assert(0);
-			}
-			mFont = TTF_OpenFont( toStringz("data/Comfortaa Bold.ttf"), 24 );
-			if( !mFont ) {
-				writefln( "openfont error: %s", std.string.toString(TTF_GetError()) );
-				assert(0);
-			}
-		}
-
+	this( char[] text = "", string fontId = "" ) {
+		//writefln( "new TextSurface( ", text, " )" );
+		mFont = FontManager.getFont( fontId );
 		setText( text );
 	}
 
 		return mSurface;
 	}
 
-	uint w() { if( mSurface) return mSurface.w;  else return(0); }
+	uint w() { if( mSurface) return mSurface.w; else return(0); }
 	uint h() { if( mSurface) return mSurface.h; else return(0); }
 
 	void setText( char[] text ) {
 	int mHeight;		/** Height of the view in tiles */
 
 	GameObject mTargetObj;
+	bool mFollowTarget;
 
 
 	this() {
-
 		gView = this;
 		mWidth = Screen.WIDTH / Map.TILE_WIDTH;
 		mHeight = Screen.HEIGHT / Map.TILE_HEIGHT;
-
 	}
 
 
 		mY = y;
 		clampPosition();
 	}
+
 
 	void moveRelative( float relx, float rely ) {
 		mX += relx;
 	}
 
 
+	void setFollowTarget( bool follow ) {
+		mFollowTarget = follow;
+	}
+
+
 	void update() {
-		if( mTargetObj ) {
+		if( mFollowTarget && mTargetObj ) {
 			followTarget();
 		}
 	}
 
 
+	void followTarget() {
+		float[2] d;
+		d[0] = mTargetObj.mX - Screen.WIDTH / 2 - mX;
+		d[1] = mTargetObj.mY - Screen.HEIGHT / 2 - mY;
+
+		float time = gTime.getDelta();
+		const float slice = 1.f / (60 * 5);
+		const float factor = 0.99f;
+
+		do {
+			for( int i = 0 ; i < 2 ; i++ ) {
+				d[i] *= factor;
+			}
+			time -= slice;
+		} while( time > slice / 2 )
+
+		mX = mTargetObj.mX - Screen.WIDTH / 2 - d[0];
+		mY = mTargetObj.mY - Screen.HEIGHT / 2 - d[1];
+
+		clampPosition();
+	}
+
+
 	void draw() {
 		int xMapMin = cast(int) ((mX - gGameObjectManager.mMaxObjectWidth + 1) / Map.TILE_WIDTH);
 		int yMapMin = cast(int) ((mY - gGameObjectManager.mMaxObjectHeight + 1) / Map.TILE_HEIGHT);
 				SDL_BlitSurface( o.mSurface, null, gScreen.mSurface, &dest );
 			}
 		}
-
-	}
-
-
-	void followTarget() {
-		float[2] d;
-		d[0] = mTargetObj.mX - Screen.WIDTH / 2 - mX;
-		d[1] = mTargetObj.mY - Screen.HEIGHT / 2 - mY;
-
-		float time = gTime.getDelta();
-		const float slice = 1.f / (60 * 5);
-		const float factor = 0.99f;
-
-		do {
-			for( int i = 0 ; i < 2 ; i++ ) {
-				d[i] *= factor;
-			}
-			time -= slice;
-		} while( time > slice / 2 )
-
-		mX = mTargetObj.mX - Screen.WIDTH / 2 - d[0];
-		mY = mTargetObj.mY - Screen.HEIGHT / 2 - d[1];
-
-		clampPosition();
 	}
 
 }