Browse Source

Version bump.
Initial support for IF:M elements and string formatting.
Various minor changes related to ERM interpreter.

The following script should be functional now:
ZVSE
!?PI;
!!DO1/0/6/1&v2777<>1:P0;

!?FU1;
!!IF:M^Hello world number %X16!^;

Michał W. Urbańczyk 14 năm trước cách đây
mục cha
commit
9775f88045
10 tập tin đã thay đổi với 184 bổ sung16 xóa
  1. 24 0
      client/CMT.cpp
  2. 15 6
      client/Client.cpp
  3. 5 0
      client/Client.h
  4. 1 1
      global.h
  5. 113 7
      lib/ERMInterpreter.cpp
  6. 2 0
      lib/ERMInterpreter.h
  7. 1 1
      lib/ERMScriptModule.h
  8. 12 0
      lib/IGameCallback.cpp
  9. 4 0
      lib/IGameCallback.h
  10. 7 1
      lib/NetPacks.h

+ 24 - 0
client/CMT.cpp

@@ -45,6 +45,7 @@
 #include "../lib/CObjectHandler.h"
 #include <boost/program_options.hpp>
 #include "../lib/CArtHandler.h"
+#include "../lib/ERMScriptModule.h"
 
 #ifdef _WIN32
 #include "SDL_syswm.h"
@@ -85,6 +86,7 @@ boost::mutex eventsM;
 static bool gOnlyAI = false;
 static bool setResolution = false; //set by event handling thread after resolution is adjusted
 
+static bool ermInteractiveMode = false; //structurize when time is right
 void processCommand(const std::string &message);
 static void setScreenRes(int w, int h, int bpp, bool fullscreen);
 void dispose();
@@ -312,8 +314,30 @@ void processCommand(const std::string &message)
 	if(LOCPLINT && LOCPLINT->cingconsole)
 		LOCPLINT->cingconsole->print(message);
 
+	if(ermInteractiveMode)
+	{
+		if(cn == "exit")
+		{
+			ermInteractiveMode = false;
+			return;
+		}
+		else
+		{
+			if(client && client->erm)
+				client->erm->executeUserCommand(message);
+			tlog0 << "erm>";
+		}
+	}
+
 	if(message==std::string("die, fool"))
+	{
 		exit(EXIT_SUCCESS);
+	}
+	else if(cn == "erm")
+	{
+		ermInteractiveMode = true;
+		tlog0 << "erm>";
+	}
 	else if(cn==std::string("activate"))
 	{
 		int what;

+ 15 - 6
client/Client.cpp

@@ -94,6 +94,7 @@ void CClient::init()
 	serv = NULL;
 	gs = NULL;
 	cb = NULL;
+	erm = NULL;
 	terminate = false;
 }
 
@@ -428,12 +429,12 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 	hotSeat = (humanPlayers > 1);
 
 
-// 	CScriptingModule *erm = getERMModule();
-// 	privilagedGameEventReceivers.push_back(erm);
-// 	privilagedBattleEventReceivers.push_back(erm);
-// 	icb = this;
-// 	acb = this;
-// 	erm->init();
+	erm = getERMModule();
+ 	privilagedGameEventReceivers.push_back(erm);
+ 	privilagedBattleEventReceivers.push_back(erm);
+ 	icb = this;
+ 	acb = this;
+ 	erm->init();
 }
 
 template <typename Handler>
@@ -594,10 +595,18 @@ void CClient::loadNeutralBattleAI()
 void CClient::commitPackage( CPackForClient *pack )
 {
 	CommitPackage cp;
+	cp.freePack = false;
 	cp.packToCommit = pack;
 	*serv << &cp;
 }
 
+int CClient::getLocalPlayer() const
+{
+	if(LOCPLINT)
+		return LOCPLINT->playerID;
+	return getCurrentPlayer();
+}
+
 template void CClient::serialize( CISer<CLoadFile> &h, const int version );
 template void CClient::serialize( COSer<CSaveFile> &h, const int version );
 

+ 5 - 0
client/Client.h

@@ -29,6 +29,7 @@ class CCallback;
 struct BattleAction;
 struct SharedMem;
 class CClient;
+class CScriptingModule;
 struct CPathsInfo;
 namespace boost { class thread; }
 
@@ -72,6 +73,7 @@ public:
 	CConnection *serv;
 	BattleAction *curbaction;
 	CPathsInfo *pathInfo;
+	CScriptingModule *erm;
 
 	CondSh<bool> waitingRequest;
 
@@ -99,6 +101,9 @@ public:
 	bool terminate;	// tell to terminate
 	boost::thread *connectionHandler; //thread running run() method
 
+	//////////////////////////////////////////////////////////////////////////
+	virtual int getLocalPlayer() const OVERRIDE;
+
 	//////////////////////////////////////////////////////////////////////////
 	//not working yet, will be implement somewhen later with support for local-sim-based gameplay
 	void changeSpells(int hid, bool give, const std::set<ui32> &spells) OVERRIDE {};

+ 1 - 1
global.h

@@ -34,7 +34,7 @@ typedef si32 TBonusSubtype;
 #define THC
 #endif
 
-#define NAME_VER ("VCMI 0.85")
+#define NAME_VER ("VCMI 0.85b")
 extern std::string NAME; //full name
 extern std::string NAME_AFFIX; //client / server
 #define CONSOLE_LOGGING_LEVEL 5

+ 113 - 7
lib/ERMInterpreter.cpp

@@ -412,7 +412,8 @@ void ERMInterpreter::printScripts( EPrintMode mode /*= EPrintMode::ALL*/ )
 		{
 			tlog2 << "----------------- script " << it->first.file->filename << " ------------------\n";
 		}
-		
+
+		tlog0 << it->first.realLineNum << '\t';
 		ERMPrinter::printAST(it->second);
 		prevIt = it;
 	}
@@ -774,6 +775,107 @@ struct HEPerformer : StandardReceiverVisitor<const CGHeroInstance *>
 	}
 
 };
+struct IFPerformer;
+
+struct IF_MPerformer : StandardBodyOptionItemVisitor<IFPerformer>
+{
+	explicit IF_MPerformer(IFPerformer & _owner) : StandardBodyOptionItemVisitor<IFPerformer>(_owner){}
+	using StandardBodyOptionItemVisitor<IFPerformer>::operator();
+
+	void operator()(TStringConstant const& cmp) const OVERRIDE;
+};
+
+struct IFPerformer : StandardReceiverVisitor<TUnusedType>
+{
+	IFPerformer(ERMInterpreter * _interpr) : StandardReceiverVisitor<TUnusedType>(_interpr, 0)
+	{}
+	using StandardReceiverVisitor<TUnusedType>::operator();
+
+
+	void operator()(TNormalBodyOption const& trig) const OVERRIDE
+	{
+		std::string message; //to be shown
+		switch(trig.optionCode)
+		{
+		case 'M': //Show the message (Text) or contents of z$ variable on the screen immediately.
+			performOptionTakingOneParamter<IF_MPerformer>(trig.params);
+			break;
+		default:
+			break;
+		}
+	}
+
+	// startpos is the first digit
+	// digits will be converted to number and returned
+	static int getNum(std::string &msg, int numStart, int &digitsUsed) 
+	{
+		int numEnd = msg.find_first_not_of("1234567890", numStart);
+
+		if(numEnd == std::string::npos)
+			digitsUsed = msg.size() - numStart;
+		else
+			digitsUsed = numEnd - numStart;
+
+		return boost::lexical_cast<int>(msg.substr(numStart, digitsUsed));
+	}
+
+	static void formatMessage(std::string &msg)
+	{
+		int pos = 0; //index of the first not yet processed character in string
+
+		//according to the ERM help:
+		//"%%" -> "%"
+		//"%V#" -> current value of # flag.
+		//"%Vf"..."%Vt" -> current value of corresponding variable.
+		//"%W1"..."%W100" -> current value of corresponding hero variable.
+		//"%X1"..."%X16" -> current value of corresponding function parameter.
+		//"%Y1"..."%Y100" -> current value of corresponding local variable.
+		//"%Z1"..."%Z500" -> current value of corresponding string variable.
+		//"%$macro$" -> macro name of corresponding variable
+		//"%Dd" -> current day of week
+		//"%Dw" -> current week
+		//"%Dm" -> current month
+		//"%Da" -> current day from beginning of the game
+		//"%Gc" -> the color of current gamer in text
+		
+		while(pos < msg.size())
+		{
+			int percentPos = msg.find_first_of('%', pos);
+			if(percentPos == std::string::npos) //processing done?
+				break;
+
+			if(percentPos + 1 >= msg.size()) //at least one character after % is required
+				throw EScriptExecError("Formatting error: % at the end of string!"); 
+;
+			switch(msg[percentPos+1])
+			{
+			case '%':
+				msg.erase(percentPos+1, 1); //just delete superfluous %
+				break;
+			case 'X':
+				{
+					int digits;
+					int varNum = getNum(msg, percentPos+2, digits);
+					msg.replace(percentPos, digits+2, boost::lexical_cast<std::string>(erm->getVar("x", varNum).getInt()));
+				}
+				break;
+			}
+			pos++;
+		}
+	}
+
+	void showMessage(const std::string &msg)
+	{
+		std::string msgToFormat = msg;
+		IFPerformer::formatMessage(msgToFormat);
+		acb->showInfoDialog(msgToFormat, icb->getLocalPlayer());
+	}
+};
+
+void IF_MPerformer::operator()(TStringConstant const& cmp) const
+{
+	owner.showMessage(cmp.str);
+}
 
 template<int opcode>
 void HE_BPerformer<opcode>::operator()( TVarpExp const& cmp ) const
@@ -1281,8 +1383,13 @@ struct ERMExpDispatch : boost::static_visitor<>
 			else
 				throw EScriptExecError("HE receiver must have an identifier!");
 		}
+		else if(trig.name == "IF")
+		{
+			helper.performBody(trig.body, IFPerformer(owner));
+		}
 		else
 		{
+			tlog3 << trig.name << " receiver is not supported yet, doing nothing...\n";
 			//not supported or invalid trigger
 		}
 	}
@@ -2264,12 +2371,6 @@ void ERMInterpreter::init()
 
 	scanForScripts();
 	scanScripts();
-	for(std::map<VERMInterpreter::LinePointer, ERM::TLine>::iterator it = scripts.begin();
-		it != scripts.end(); ++it)
-	{
-		tlog0 << it->first.realLineNum << '\t';
-		ERMPrinter::printAST(it->second);
-	}
 
 	executeInstructions();
 	executeTriggerType("PI");
@@ -2453,6 +2554,11 @@ VOptionList ERMInterpreter::evalEach( VermTreeIterator list, Environment * env /
 	return ret;
 }
 
+void ERMInterpreter::executeUserCommand(const std::string &cmd)
+{
+	tlog0 << "ERM here: received command: " << cmd << std::endl;
+}
+
 namespace VERMInterpreter
 {
 	VOption convertToVOption(const ERM::TVOption & tvo)

+ 2 - 0
lib/ERMInterpreter.h

@@ -690,6 +690,8 @@ public:
 	//overload CScriptingModule
 	virtual void heroVisit(const CGHeroInstance *visitor, const CGObjectInstance *visitedObj, bool start) OVERRIDE;
 	virtual void init() OVERRIDE;//sets up environment etc.
+	virtual void executeUserCommand(const std::string &cmd) OVERRIDE;
+
 	virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) OVERRIDE;
 
 	const CGObjectInstance *getObjFrom(int3 pos);

+ 1 - 1
lib/ERMScriptModule.h

@@ -11,7 +11,7 @@ class ERMInterpreter;
 class DLL_EXPORT CScriptingModule : public IGameEventsReceiver, public IBattleEventsReceiver
 {
 public:
-
+	virtual void executeUserCommand(const std::string &cmd){};
 	virtual void init(){}; //called upon the start of game (after map randomization, before first turn)
 	virtual ~CScriptingModule();
 };

+ 12 - 0
lib/IGameCallback.cpp

@@ -1180,12 +1180,24 @@ const CGHeroInstance* CGameInfoCallback::getHeroWithSubid( int subid ) const
 	return NULL;
 }
 
+int CGameInfoCallback::getLocalPlayer() const
+{
+	return getCurrentPlayer();
+}
 
 void IGameEventRealizer::showInfoDialog( InfoWindow *iw )
 {
 	commitPackage(iw);
 }
 
+void IGameEventRealizer::showInfoDialog(const std::string &msg, int player)
+{
+	InfoWindow iw;
+	iw.player = player;
+	iw.text << msg;
+	showInfoDialog(&iw);
+}
+
 void IGameEventRealizer::setObjProperty(int objid, int prop, si64 val)
 {
 	SetObjectProperty sob;

+ 4 - 0
lib/IGameCallback.h

@@ -144,6 +144,7 @@ public:
 	void getThievesGuildInfo(SThievesGuildInfo & thi, const CGObjectInstance * obj); //get thieves' guild info obtainable while visiting given object
 	int getPlayerStatus(int player) const; //-1 if no such player
 	int getCurrentPlayer() const; //player that currently makes move // TODO synchronous turns
+	virtual int getLocalPlayer() const; //player that is currently owning given client (if not a client, then returns current player)
 	const PlayerSettings * getPlayerSettings(int color) const;
 
 
@@ -249,6 +250,9 @@ public:
 
 	virtual void showInfoDialog(InfoWindow *iw);
 	virtual void setObjProperty(int objid, int prop, si64 val);
+
+
+	virtual void showInfoDialog(const std::string &msg, int player);
 };
 
 class DLL_EXPORT IGameEventCallback : public IGameEventRealizer

+ 7 - 1
lib/NetPacks.h

@@ -1499,12 +1499,18 @@ struct AdvmapSpellCast : public CPackForClient //108
 
 struct CommitPackage : public CPackForServer
 {
+	bool freePack; //for local usage, DO NOT serialize
 	bool applyGh(CGameHandler *gh);
 	CPackForClient *packToCommit;
 
+	CommitPackage()
+	{
+		freePack = true;
+	}
 	~CommitPackage()
 	{
-		delete packToCommit;
+		if(freePack)
+			delete packToCommit;
 	}
 
 	template <typename Handler> void serialize(Handler &h, const int version)