Have added another little check in the commands system, and added a new command for hosts to alter starting money.
As I said earlier, Iunit requires Iunit.dll, which I'm noticing is missing from my fresh install of OP2. So that's an issue right there. Things can be complicated even further if there are different versions of Iunit.dll. On top of that, you can do everything with HFL that you could do with Iunit, and more.It's op2extra.dll, not Iunit.dll. It's still dumb that Eddy-B made it a dynamic library rather than a static one like HFL.
I was referring to #include <IUnit.h> in Outpost Monopoly's Main.cpp. I assumed it was a reference to IUnit.h in the repository under API/HFL-IUnit, but perhaps that is a bad assumption?Not really a bad assumption, but there were about 3 projects floating about using the name IUnit. I suspect you can get things working with the version from HFL. They all had sort of the same goals, with many shared or similar functions. It's possible you'll have to rename some calls or make minor adaptations.
snprintf(dest, destSize, "Hello World"); // Works, but beware of this form. The string is scanned for special characters.
snprintf(dest, destSize, source); // Bad idea if source comes from an untrusted source
snprintf(dest, destSize, "Hello World %x %x %x %x %x ..."); // Not a simple string copy. This will do a hex dump of your call stack
snprintf(dest, destSize, "%s", source); // This is fine. The format string says insert a string here from the following parameters. The following parameter is a data string which is not scanned for special characters.
void Hook (char* chatText, int sourcePlayerNum)
{
char string[10] = "", argument1[10] = "", argument2[10] = "";;
int wordLength;
bool errored = false, targetPlayerFound = false, cardFound = false;
if (strncmp (chatText, "/trade", 6) == false) //If the message starts with the trade command
{
//Skip the /trade tag from the message string
wordLength = strcspn(chatText, " .,?!");
strncpy(string, chatText, wordLength);
strcpy (chatText, chatText + wordLength + 1);
//Store the first argument, be it the words "lot" or "goojf" or the amount of money to be traded.
wordLength = strcspn(chatText, " .,?!");
strncpy(string, chatText, wordLength);
strcpy (chatText, chatText + wordLength + 1);
if (strncmp (string, "lot", 3) == false) //If lot trading has been called
{
//Store the number of the lot to be traded
wordLength = strcspn(chatText, " .,?!");
strncpy(argument1, chatText, wordLength);
strcpy (chatText, chatText + wordLength + 1);
//Skip the intermediate word, formally "to"
wordLength = strcspn(chatText, " .,?!");
strncpy(argument2, chatText, wordLength);
strcpy (chatText, chatText + wordLength + 1);
//Store the target player, who will receive the lot
wordLength = strcspn(chatText, " .,?!");
strncpy(argument2, chatText, wordLength);
if (isdigit(argument2[0]) != false)
if (atoi(argument2) > 0 && atoi(argument2) <= TethysGame::NoPlayers()) //If the target player exists
{
for (i = 0; i < noActivePlayers; i++)
if (PlayerOrder[i].IsPlayerNo == atoi(argument2) - 1)
{
if (atoi(argument2) != Lands[atoi(argument1)].Owner + 1) //If the target is not the sender
if (Lands[atoi(argument1)].Owner == sourcePlayerNum) //If the sender owns the lot to be traded
if (isdigit(argument1[0]) != false)
if (atoi(argument1) > 0 && atoi(argument1) < 40) //If in the list
if (atoi(argument1) == 5 || atoi(argument1) == 12 || atoi(argument1) == 15 || atoi(argument1) == 25 || atoi(argument1) == 28 || atoi(argument1) == 35) //If Utility/Railroad, simply trade
TradeLot(atoi(argument1), atoi(argument2) - 1, sourcePlayerNum, chatText);
else if (Lands[atoi(argument1)].Housing == 0) TradeLot(atoi(argument1), atoi(argument2) - 1, sourcePlayerNum, chatText); //If upgradable lot, check for 0 houses and trade
else {strcpy(chatText, "Can't trade upgraded lots."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Not a lot."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Must input a lot &n&u&m&b&e&r."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "You don't own that lot."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Can't trade to yourself."); TethysGame::AddGameSound (sndBld_not, -1);}
targetPlayerFound = true;
break;
} else;
if (targetPlayerFound == false) {strcpy (chatText, "Player is no longer active."); TethysGame::AddGameSound (sndBld_not, -1);}
}
else {strcpy (chatText, "No such player."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Must input a player &n&u&m&b&e&r."); TethysGame::AddGameSound (sndBld_not, -1);}
}
else if (strncmp (string, "goojf", 5) == false) // If GOoJF card trading is called
{
//Skip the intermediate word, formally "to"
wordLength = strcspn(chatText, " .,?!");
strncpy(argument1, chatText, wordLength);
strcpy (chatText, chatText + wordLength + 1);
//Store the target player, who will receive the card
wordLength = strcspn(chatText, " .,?!");
strncpy(argument1, chatText, wordLength);
for (j = 2; j >= 0; j--) //Find an owned GOoJF card
if (GOOJF[j] == sourcePlayerNum) //Make sure it's owned by the sender
{
if (isdigit(argument1[0]) != false)
if (atoi(argument1) > 0 && atoi(argument1) <= TethysGame::NoPlayers()) // If the target player exists
{
for (i = 0; i < noActivePlayers; i++)
if (PlayerOrder[i].IsPlayerNo == atoi(argument1) - 1)
{
if (atoi(argument1) != sourcePlayerNum + 1) TradeGOoJF(j, atoi(argument1) - 1, sourcePlayerNum, chatText); //If the target is not the sender, send card
else {strcpy(chatText, "Can't trade to yourself."); TethysGame::AddGameSound (sndBld_not, -1);}
targetPlayerFound = true;
break;
}
if (targetPlayerFound == false) {strcpy (chatText, "Player is no longer active."); TethysGame::AddGameSound (sndBld_not, -1);}
}
else {strcpy(chatText, "No such player."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Must input a player &n&u&m&b&e&r."); TethysGame::AddGameSound (sndBld_not, -1);}
cardFound = true;
break;
}
if (cardFound == false) {strcpy (chatText, "You don't own a GOoJF card."); TethysGame::AddGameSound (sndBld_not, -1);}
}
else if (atoi(string) > 0) //If ore trading is called
{
//Skip the intermediate word, formally "to"
wordLength = strcspn(chatText, " .,?!");
strncpy(argument1, chatText, wordLength);
strcpy (chatText, chatText + wordLength + 1);
//Store the target player, who will receive the ore
wordLength = strcspn(chatText, " .,?!");
strncpy(argument1, chatText, wordLength);
if (isdigit(argument1[0]) != false)
if (atoi(argument1) > 0 && atoi(argument1) <= TethysGame::NoPlayers()) // If the target player exists
{
for (i = 0; i < noActivePlayers; i++)
if (PlayerOrder[i].IsPlayerNo == atoi(argument1) - 1)
{
if (atoi(argument1) != sourcePlayerNum + 1) //If the target is not the sender
if (Player[sourcePlayerNum].Ore() >= atoi(string)) TradeOre(atoi(string), atoi(argument1) - 1, sourcePlayerNum, chatText); //If the sender has enough ore, trade
else {strcpy (chatText, "You do not have enough ore!"); TethysGame::AddGameSound (sndBld_not, -1);} //Errors
else {strcpy(chatText, "Can't trade to yourself."); TethysGame::AddGameSound (sndBld_not, -1);}
targetPlayerFound = true;
break;
}
if (targetPlayerFound == false) {strcpy (chatText, "Player is no longer active."); TethysGame::AddGameSound (sndBld_not, -1);}
}
else {strcpy(chatText, "No such player."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Must input a player &n&u&m&b&e&r."); TethysGame::AddGameSound (sndBld_not, -1);}
}
else errored = true; //Worst error possible
}
else if (strncmp (chatText, "/pay jail", 9) == false)
if (PlayerOrder[playerTurn].IsPlayerNo == sourcePlayerNum)
if (Jail[sourcePlayerNum].Jailed == true)
{
for (i = 0; i < 3; i++)
if (GOOJF[i] == sourcePlayerNum)
{
GOOJF[i] = -1;
Jail[sourcePlayerNum].Jailed = false;
strcpy (chatText, "Released from jail with a GOoJF.");
TethysGame::AddGameSound (sndSavnt277, -1);
break;
}
else {strcpy (chatText, "You don't own a GOoJF."); TethysGame::AddGameSound (sndBld_not, -1);}
if (Player[sourcePlayerNum].Ore() >= 50 && Jail[sourcePlayerNum].Jailed == true)
{
Player[sourcePlayerNum].SetOre(Player[sourcePlayerNum].Ore()-50);
Jail[sourcePlayerNum].Jailed = false;
strcpy (chatText, "Released from jail on bail of 50.");
TethysGame::AddGameSound (sndSavnt277, -1);
}
}
else {strcpy(chatText, "Not Jailed."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Not your turn."); TethysGame::AddGameSound (sndBld_not, -1);}
else if (strncmp (chatText, "/kick", 5) == false)
if (sourcePlayerNum == 0)
{
//Skip the /kick tag
wordLength = strcspn(chatText, " .,?!");
strncpy(argument1, chatText, wordLength);
strcpy (chatText, chatText + wordLength + 1);
//Store the target player, who will be kicked
wordLength = strcspn(chatText, " .,?!");
strncpy(argument1, chatText, wordLength);
if (atoi(argument1) > 0 && atoi(argument1) <= TethysGame::NoPlayers())
if (atoi(argument1) != sourcePlayerNum + 1)
{
RemovePlayer (atoi(argument1) - 1);
if (leavingPlayerFound == true)
{
strcpy (chatText, "Player ");
char dumpstring[8] = "";
strcat (chatText, argument1); strcat (chatText, " has been kicked from the game.");
}
else {strcpy(chatText, "Already left the game."); TethysGame::AddGameSound (sndBld_not, -1);}
}
else {strcpy(chatText, "Can't kick yourself."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "No such player."); TethysGame::AddGameSound (sndBld_not, -1);}
}
else {strcpy(chatText, "Only the host can kick."); TethysGame::AddGameSound (sndBld_not, -1);}
else if (strncmp (chatText, "/quit", 5) == false)
{
RemovePlayer (sourcePlayerNum);
if (leavingPlayerFound == true)
{
strcpy (chatText, "Player ");
char dumpstring[8] = "";
strcat (chatText, itoa(sourcePlayerNum + 1, dumpstring, 10)); strcat (chatText, " has quit the game.");
}
else {strcpy(chatText, "Already left the game."); TethysGame::AddGameSound (sndBld_not, -1);}
}
else if (strncmp (chatText, "/start", 6) == false)
{
//Skip the /start tag
wordLength = strcspn(chatText, " .,?!");
strncpy(argument1, chatText, wordLength);
strcpy (chatText, chatText + wordLength + 1);
//Store the multiplier
wordLength = strcspn(chatText, " .,?!");
strncpy(argument1, chatText, wordLength);
if (sourcePlayerNum == 0)
if (isdigit(argument1[0]) != false)
if (boolMustInsert == true)
{
for (i = 0; i < noActivePlayers; i++)
Player[i].SetOre(Player[i].Ore() * atoi(argument1) / 10);
strcpy(chatText, "Starting ore altered by host.");
}
else {strcpy(chatText, "Can no longer alter starting money."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Must input a &n&u&m&b&e&r."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Only host can alter starting money."); TethysGame::AddGameSound (sndBld_not, -1);}
}
if (errored == true) {strcpy(chatText, "Unrecognized trade command."); TethysGame::AddGameSound (sndBld_not, -1);}//Send the worst error psossible
}
if (remainingString = hasPrefix(originalString, "HardCodedPrefixToCheckFor")) {
// Use remainingString ...
}
typedef struct PlayerDetailsType
{
short PlayerNumber;
short Spot;
} PlayerDetails;
typedef struct HouseTriggerType
{
Trigger HouseBuildTrigger;
short LotNumber;
} HouseDetails;
PlayerDetails PlayerOrder [4];
HouseDetails HouseBuyTriggers [22];
typedef struct NodeStructName {
NodeStructName* next; // Here NodeStructName is visible, while NodeTypedefName is not
Data* data;
} NodeTypedefName;
typedef struct Name { ... } Name; // Both names are the same
typedef struct { ... } TypedefName; // No struct name
//https://www.safaribooksonline.com/library/view/c-cookbook/0596007612/ch04s07.html
void splitStringToVector(const string& stringToSplit, char delimiter, vector<string>& splitString)
{
string::size_type i = 0;
string::size_type j = stringToSplit.find(delimiter);
while (j != string::npos)
{
splitString.push_back(stringToSplit.substr(i, j - i));
i = ++j;
j = stringToSplit.find(delimiter, j);
if (j == string::npos)
{
splitString.push_back(stringToSplit.substr(i, stringToSplit.length()));
}
}
}
void Hook(char* chatText, int sourcePlayerNum)
{
vector<string> splitString;
string chatString(chatText);
std::transform(chatString.begin(), chatString.end(), chatString.begin(), toupper);
splitStringToVector(chatString, ' ', splitString);
string command = splitString[0];
if (command.compare("/TRADE") == 0)
{
ParseTrade(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/PAY") == 0 && splitString[1].compare("JAIL") == 0)
{
ParsePayJail(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/KICK") == 0)
{
ParseKick(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/QUIT") == 0)
{
ParseQuit(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/START") == 0)
{
ParseStart(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/REVIEW") == 0)
{
ParseReview(chatText, sourcePlayerNum, splitString);
}
}
void ParseTrade(char* chatText, const int sourcePlayerNum, const vector<string>& splitString)
{
string tradeType = splitString[1];
if (tradeType.compare("LOT") == 0 || tradeType.compare("PROPERTY") || tradeType.compare("LAND"))
{
ParseTradeProperty(chatText, sourcePlayerNum, splitString);
}
else if (tradeType.compare("GOOJF") == 0 || tradeType.compare("JAIL"))
{
ParseTradeGoOJF(chatText, sourcePlayerNum, splitString);
}
else if (tradeType.compare("ORE") == 0 || tradeType.compare("MONEY") || IsNumeric(tradeType))
{
ParseTradeOre(chatText, sourcePlayerNum, splitString);
}
else
{
strcpy(chatText, "Unrecognized trade command.");
TethysGame::AddGameSound(sndBld_not, -1);
}
}
bool IsNumeric(const std::string& input) {
return std::all_of(input.begin(), input.end(), ::isdigit);
}
bool ParsePlayerIndex(char* chatText, const string& inputString, int& outputInt)
{
if (!IsNumeric(inputString))
{
FormatInputNotNumberResponse(chatText);
return false;
}
outputInt = stoi(inputString);
if (!IsValidPlayerIndex(outputInt))
{
FormatPlayerDoesNotExistResponse(chatText);
return false;
}
return true;
}
bool PlayerActive(const int playerIndex)
{
for (int i = 0; i < numberActivePlayers; i++)
{
if (MonopolyPlayers[i].TurnIndex == playerIndex - 1)
{
return true;
}
}
return false;
}
bool FindPlayersGoOJFCard(const int playerIndex, int& cardIndex)
{
for (int i = 2; i >= 0; i--) //Find an owned GOoJF card
{
if (GOOJF[i] == playerIndex) //Make sure it's owned by the sender
{
cardIndex = i;
return true;
}
}
return false;
}
void ParseTradeGoOJF(char* chatText, const int sourcePlayerIndex, const vector<string> splitString)
{
int receivingPlayerIndex;
if (!ParsePlayerIndex(chatText, splitString[2], receivingPlayerIndex))
{
return;
}
if (sourcePlayerIndex == receivingPlayerIndex)
{
FormatTradeWithSelfResponse(chatText);
return;
}
if (!PlayerActive(receivingPlayerIndex))
{
FormatPlayerNoLongerActive(chatText);
return;
}
int goojfIndex;
if (!FindPlayersGoOJFCard(sourcePlayerIndex, goojfIndex))
{
FormatDoNotOwnGetOutOfJailFreeCard(chatText);
return;
}
TradeGOoJF(goojfIndex, receivingPlayerIndex, sourcePlayerIndex, chatText);
}
I'm sending both the pointer to the chat text and the split string for parsing because the chat text is actually replaced by a new message and I need to pass a reference/pointer to do this.
GoOJF stands for Get Out Of Jail Free.
I'm not a fan of the format of functions like bool FindPlayersGoOJFCard(const int playerIndex, int& cardIndex). The other ways I know to solve this would be to use try/catch or set the index to -1 if the parse doesn't work out. I'm not really a fan of any of these solutions though, so I just went with setting the value and returning a bool if it is valid value or not.
namespace OutpostDice
{
//Dice Pip Index locations
// 0 4
// 1 3 5
// 2 6
LOCATION relativePipLocs[] =
{
LOCATION(0,0),
LOCATION(0,1),
LOCATION(0,2),
LOCATION(1,1),
LOCATION(2,0),
LOCATION(2,1),
LOCATION(2,2)
};
int pipsOn1[] = { 3 };
int pipsOn2[] = { 0, 6 };
int pipsOn3[] = { 0, 3, 6 };
int pipsOn4[] = { 0, 2, 4, 6 };
int pipsOn5[] = { 0, 2, 3, 4, 6 };
int pipsOn6[] = { 0, 1, 2, 4, 5, 6 };
void CreateRoboMinerDie(const LOCATION& dieTopLeftLoc, const int pipLocations[], const int pipCount, const int pipSpacing, const int playerIndex)
{
Unit roboDie;
for (int i = 0; i < pipCount; ++i)
{
int pipIndex = pipsOn1[i];
LOCATION roboDieLoc = dieTopLeftLoc + LOCATION(relativePipLocs[pipIndex].x * pipSpacing, relativePipLocs[pipIndex].y * pipSpacing);
TethysGame::CreateUnit(roboDie, mapRoboMiner, roboDieLoc, playerIndex, mapNone, TethysGame::GetRand(7));
}
}
void CreateRoboMinerDiceRoll(const LOCATION& dieTopLeftLoc, const int pipSpacing, const int rollValue, const int playerIndex)
{
LOCATION roboDieLoc = dieTopLeftLoc;
Unit roboDie;
switch (rollValue)
{
case 1:
CreateRoboMinerDie(dieTopLeftLoc, pipsOn1, rollValue, pipSpacing, playerIndex);
return;
case 2:
CreateRoboMinerDie(dieTopLeftLoc, pipsOn2, rollValue, pipSpacing, playerIndex);
return;
case 3:
CreateRoboMinerDie(dieTopLeftLoc, pipsOn3, rollValue, pipSpacing, playerIndex);
return;
case 4:
CreateRoboMinerDie(dieTopLeftLoc, pipsOn4, rollValue, pipSpacing, playerIndex);
return;
case 5:
CreateRoboMinerDie(dieTopLeftLoc, pipsOn5, rollValue, pipSpacing, playerIndex);
return;
case 6:
CreateRoboMinerDie(dieTopLeftLoc, pipsOn6, rollValue, pipSpacing, playerIndex);
return;
}
}
}
SCRIPT_API void PlaceDice()
{
LOCATION dieLocFirst = LOCATION(233 - 1, 112 - 1);
LOCATION dieLocSecond = LOCATION(247 - 1, 112 - 1);
int pipSpacing = 4;
dieValue = 1 + TethysGame::GetRand(6);
diceTotal = dieValue;
int turnIndex = MonopolyPlayers[playerTurn].TurnIndex;
OutpostDice::CreateRoboMinerDiceRoll(dieLocFirst, pipSpacing, dieValue, turnIndex);
dieValue = 1 + TethysGame::GetRand(6);
diceTotal += dieValue;
OutpostDice::CreateRoboMinerDiceRoll(dieLocSecond, pipSpacing, dieValue, turnIndex);
CountRolls(); //Check which roll we're on and continue processing
}
After playing for a while, we thing the dice is rolling multiple times per turn and just using the final roll. Hopefully it won't be too far to fix this.
Hooman,
Thank you for the feedback. Yeah, it was a lot of code to post. I was trying to catch the background of what I was doing. Thank you for the insight on the standard library. I think part of what makes C++ difficult to learn is all the baggage it carries around due to it's fantastic backwards compatibility. I suppose a newer language like C# or Java doesn't have to worry about this so much.
I jumped into making lots of major changes and didn't approach the refactoring in a way that was revision control friendly. My current copy is now in the repository. I'll try to be better about pushing changes regularly. I'm afraid the scenario is currently more broken then it started (It still compiles, but I have not been thoroughly testing each refactor).
Outpost Monopoly was programmed from a procedural standpoint (no classes). I thought about switching the project over to more of an object oriented solution. After some thought, I realized procedural was working quite well and it would probably be better to stick with this. For any larger project I work on, I always think in Object-Oriented terms, so I think it will be a good exercise to keep with procedural.
On that note, here is the refactored Outpost Dice code. It is meant to show robo miners on screen in a pattern that represents the pips on a standard 6 sided dice. Perhaps someone else will have need for such Outpost code in the future. :-\
/* snip */
There is still a bug in the code causing multiple dice rolls to be made each turn. I thought perhaps the dice code was causing it, but after refactoring, it looks like the problem is related to the triggers involved in calling for dice rolls. I'm also seeing TethysGame::GetRand(6); produces the vast majority of the numbers 2 and 3. Once I get the dice just rolling one time per turn, I'll look into this some more and see what is causing the lack of randomness.
#include <random>
#include <iostream>
int main()
{
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<> dis(1, 6);
// A few random numbers:
for (int n = 0; n < 10; ++n)
std::cout << dis(gen) << ' ';
std::cout << std::endl;
return 0;
}
I'm also seeing TethysGame::GetRand(6); produces the vast majority of the numbers 2 and 3. Once I get the dice just rolling one time per turn, I'll look into this some more and see what is causing the lack of randomness.
Not sure I'd call it fantastic but meh. The current STL and c++11 standards suffice. The legacy stuff, IMHO, should just be dumped but that would literally break decades worth of code.
I looked over it myself and was floored by what I saw. Saying it was programmed from a procedural standpoint roughly translates to it's just bad code. This isn't to say that procedural code is bad, just that this code is. No offense to the original author (Hidiot I believe) but when I saw the giant nested if's for basic string processing I was blown away. Truly deserving of a submission to The Daily WTF.
...
I believe it's a worthwhile project and this code could really use the touch of some more experienced programmers. While the implementation is... questionable... the idea is solid.
I'm not sure how OP2's internal random number generator is -- since we're using VS2015 which implements most of the C++11 standards we can just use the better built in PRNG code.
copy /Y “$(TargetPath)” “..\..\..\..\GameDownload\Outpost2\trunk\$(TargetFilename)”
copy /Y “$(ProjectDir)atwmon.map” “..\..\..\..\GameDownload\Outpost2\trunk\atwmon.map”
copy /Y “$(ProjectDir)monoptech.txt” “..\..\..\..\GameDownload\Outpost2\trunk\monoptech.txt”
QuoteNot sure I'd call it fantastic but meh. The current STL and c++11 standards suffice. The legacy stuff, IMHO, should just be dumped but that would literally break decades worth of code.
I believe that's called D (https://dlang.org/).
QuoteI looked over it myself and was floored by what I saw. Saying it was programmed from a procedural standpoint roughly translates to it's just bad code. This isn't to say that procedural code is bad, just that this code is. No offense to the original author (Hidiot I believe) but when I saw the giant nested if's for basic string processing I was blown away. Truly deserving of a submission to The Daily WTF.
...
I believe it's a worthwhile project and this code could really use the touch of some more experienced programmers. While the implementation is... questionable... the idea is solid.
:o Ouch! Play nice!
I don't think Hidiot was particularly inexperienced. He seemed to do alright. And consider that level works by hooking the in game chat. That's not exactly a beginner thing to do.
Of course I should mention Arklon and BlackBox sometimes tear apart my code.
if (atoi(argument2) > 0 && atoi(argument2) <= TethysGame::NoPlayers()) //If the target player exists
{
for (i = 0; i < noActivePlayers; i++)
if (PlayerOrder[i].IsPlayerNo == atoi(argument2) - 1)
{
if (atoi(argument2) != Lands[atoi(argument1)].Owner + 1) //If the target is not the sender
if (Lands[atoi(argument1)].Owner == sourcePlayerNum) //If the sender owns the lot to be traded
if (atoi(argument1) > 0 && atoi(argument1) < 40) //If in the list
if (atoi(argument1) == 5 || atoi(argument1) == 12 || atoi(argument1) == 15 || atoi(argument1) == 25 || atoi(argument1) == 28 || atoi(argument1) == 35) //If Utility/Railroad, simply trade
TradeLot(atoi(argument1), atoi(argument2) - 1, sourcePlayerNum, chatText);
else if (Lands[atoi(argument1)].Housing == 0) TradeLot(atoi(argument1), atoi(argument2) - 1, sourcePlayerNum, chatText); //If upgradable lot, check for 0 houses and trade
else {strcpy(chatText, "Can't trade upgraded lots."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Not a lot."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "You don't own that lot."); TethysGame::AddGameSound (sndBld_not, -1);}
else {strcpy(chatText, "Can't trade to yourself."); TethysGame::AddGameSound (sndBld_not, -1);}
targetPlayerFound = true;
break;
} else;
QuoteI'm not sure how OP2's internal random number generator is -- since we're using VS2015 which implements most of the C++11 standards we can just use the better built in PRNG code.
Keep in mind game sync issues. If you use the built-in random number generator, it already has a shared seed between all client in multiplayer. If you use a custom random number generator, you'll need to consider sync issues, which means setting a shared initial seed, and always calling the random number generator the same number of times, and in the same order on all clients.
I think HiIdiot had some great ideas, the source code just wasn't designed with maintainability or for sharing with others. But that is fine as he was working by himself. Cloning Monopoly isn't a trivial project. As Hooman mentioned, it is beyond my skill to hook into existing code like he did with the chat system.
I just uploaded a newer version of Outpost Monopoly to the repository. I fixed a couple of minor errors in some of my refactored methods. I also compiled and ran the scenario. Currently, the first player just continuously roles and moves across the board, so I created a new bug somewhere...
Leeor, the help would be appreciated. I have never collaborated with someone else in C++, so I probably have some funny habits. I'll start to post what part of the code I'm working on so if you do start making changes we can try to avoid working the same pieces and having major subversion diff fights. It would also be helpful to start posting a list of things that need done. Maybe starting a new forum thread or a redmine section, although redmine might be a bit overkill?
Error MSB3073What's curious about this error message, is it's one error message for three commands. Perhaps it's trying to execute the "copy copy copy" command?
The command
"copy /Y “C:\Users\Brett\Documents\Outpost2SVN\LevelsAndMods\trunk\Levels\OutpostMonopoly\Debug\Ml4Mono.dll” “..\..\..\..\GameDownload\Outpost2\trunk\Ml4Mono.dll”
copy /Y “C:\Users\Brett\Documents\Outpost2SVN\LevelsAndMods\trunk\Levels\OutpostMonopoly\atwmon.map” “..\..\..\..\GameDownload\Outpost2\trunk\atwmon.map”
copy /Y “C:\Users\Brett\Documents\Outpost2SVN\LevelsAndMods\trunk\Levels\OutpostMonopoly\monoptech.txt” “..\..\..\..\GameDownload\Outpost2\trunk\monoptech.txt”
:VCEnd" exited with code 1
<PostBuildEvent>
<Command>copy /Y “$(TargetPath)” “..\..\..\..\GameDownload\Outpost2\trunk\
$(TargetFilename)”
copy /Y “$(ProjectDir)atwmon.map” “..\..\..\..\GameDownload\Outpost2\trunk\atwmo
n.map”
copy /Y “$(ProjectDir)monoptech.txt” “..\..\..\..\GameDownload\Outpost2\trunk\mo
noptech.txt”</Command>
<Message>Copy Level DLL, Map File and Tech Tree file to Outpost 2 game dir
ectory in SVN. NOTE: Remaked out by default.</Message>
</PostBuildEvent>
copy file1 file2 file3 destFolder
I have never collaborated with someone else in C++, so I probably have some funny habits. I'll start to post what part of the code I'm working on so if you do start making changes we can try to avoid working the same pieces and having major subversion diff fights.
I'm not saying I'm superior ...
void TradeLot(const int lotIndex, const int targetPlayerIndex, const int sourcePlayerIndex, char* chatMessage)
{
ChangeLotOwner(lotIndex, targetPlayerIndex);
string successMessage = "Player " + std::to_string(sourcePlayerIndex) +
" traded lot " + std::to_string(lotIndex) +
" to player " + std::to_string(targetPlayerIndex);
std::vector<char> charVector(successMessage.begin(), successMessage.end());
charVector.push_back('\0');
chatMessage = &charVector[0];
}
xcopy "$(ProjectDir)atwmon.map" "..\..\..\..\GameDownload\Outpost2\trunk\" /Y
xcopy "$(ProjectDir)monoptech.txt" "..\..\..\..\GameDownload\Outpost2\trunk\" /Y
rem xcopy “$(TargetPath)” “..\..\..\..\GameDownload\Outpost2\trunk\” /Y
void TradeLot(const int lotIndex, const int targetPlayerIndex, const int sourcePlayerIndex, char* chatMessage)
{
ChangeLotOwner(lotIndex, targetPlayerIndex);
string successMessage = "Player " + std::to_string(sourcePlayerIndex) +
" traded lot " + std::to_string(lotIndex) +
" to player " + std::to_string(targetPlayerIndex);
chatMessage = const_cast<char*>(successMessage.c_str());
}
const std::string& TradeLot(int lotIndex, int targetPlayerIndex, int sourcePlayerIndex)
{
ChangeLotOwner(lotIndex, targetPlayerIndex);
string successMessage = "Player " + std::to_string(sourcePlayerIndex) +
" traded lot " + std::to_string(lotIndex) +
" to player " + std::to_string(targetPlayerIndex);
return successMessage;
}
// Const member function
void SomeClass::SomeMethod(const ParamType1* param1, ParamType2 param2) const;
// Roughly equivalent to
void SomeClass_SomeMethod(const SomeClass* this, const ParamType1* param1, ParamType2 param2);
This should be converted to using std::string's instead which are automatically freed when they go out of scope.
The string formatting in your TradeLot function can be reduced to a single line using snprintf. This can write to the destination buffer directly, so no need to mess with return pointers, or converting between std::string and character arrays. Plus you can set a cap on the destination buffer size, which should be done due to game limitations on the length of such strings.
Quote from: leeor_netThis should be converted to using std::string's instead which are automatically freed when they go out of scope.
You realize you say this just after showing how the automatic cleanup leads to a dangling pointer problem, and undefined behaviour.
Avoid const_cast if you can, or any casting really, unless you know what you're doing. There are very few specific circumstances where they should be used.
const int returnAddress = 0x40FD8A;
int* const callRelativeAddrPtr = (int*)(returnAddress - 4); /* Look back 4 bytes for the offset part of the call instruction */
void* originalFunctionPtr = 0;
BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hinstDLL);
InstallMessageHook();
}
if (fdwReason == DLL_PROCESS_DETACH)
{
UninstallMessageHook();
}
return true;
}
void InstallMessageHook()
{
// Unprotect section of code at <returnAddress>
DWORD oldAttributes;
if (VirtualProtect(callRelativeAddrPtr, 4, PAGE_EXECUTE_READWRITE, &oldAttributes) == 0)
return; // Could not unprotect pages. Abort
// Store the old destination of the call instruction
// Note: The call operand is an offset relative to the start of the next instruction
originalFunctionPtr = (void*)(*callRelativeAddrPtr + returnAddress);
// Replace the address of the call instruction with a pointer to our function
// Note: The call operand is an offset relative to the start of the next instruction
*callRelativeAddrPtr = (int)((int)&Hook - returnAddress);
}
void UninstallMessageHook()
{
*callRelativeAddrPtr = (int)originalFunctionPtr - returnAddress;
}
void Hook(char* chatText, int sourcePlayerIndex)
{
std::string response;
try
{
vector<string> splitString = CastOutpostChatToVectorString(chatText);
MonopolyCommand command = MonopolyChat::DetermineChatCommand(splitString);
if (command == MonopolyCommand::None)
{
return;
}
response = MonopolyChat::ParseMonopolyCommand(command, sourcePlayerIndex, splitString);
}
catch (...)
{
response = "Unkown Chat Parse Error";
}
if (!response.empty())
{
CastOutpostChatToCharStar(response, chatText);
}
}
vector<string> CastOutpostChatToVectorString(char* chatText)
{
string chatString(chatText);
MakeStringUppercase(chatString);
vector<string> splitString;
SplitStringToVector(chatString, ' ', splitString);
return splitString;
}
void CastOutpostChatToCharStar(string& textString, char* textCharStar)
{
size_t maxMessageStringSize = 29;
if (textString.length() > maxMessageStringSize)
{
textString = "Too many characters in chat";
}
TethysGame::AddGameSound(sndBld_not, -1);
textCharStar = const_cast<char*>(textString.c_str());
}
This implementation eliminates the dangling pointer problem, avoids having to pass around pointers to memory buffers that need to be managed, etc. And, when calling it you could do something like char* = TradeLot(0, 0, 0).c_str(); or however it's used.
std::string TradeLot(int lotIndex, int targetPlayerIndex, int sourcePlayerIndex)
void TradeLot(int lotIndex, int targetPlayerIndex, int sourcePlayerIndex, std::string& msg)
vector<string> CastOutpostChatToVectorString(char* chatText)
{
vector<string> splitString;
SplitStringToVector(MakeStringUppercase(chatText), ' ', splitString);
return splitString;
}
xcopy "$(ProjectDir)Debug\Ml4Mono.dll" "..\..\..\..\GameDownload\Outpost2\trunk\" /Y
/* Can use any STL container that implements insert(iterator, value), e.g. vector, list, (unordered_)set */
template <class T>
void TokenizeString(const std::string &in, const std::string &delimiters, T &out) {
std::string::size_type curPosition, lastPosition = 0;
while (lastPosition < in.length()) {
curPosition = in.find_first_of(delimiters, lastPosition);
if (curPosition == std::string::npos) {
curPosition = in.length();
}
if (curPosition != lastPosition) {
out.insert(out.end(),
T::value_type(in.data() + lastPosition,
static_cast<T::size_type>(curPosition - lastPosition)));
}
lastPosition = curPosition + 1;
}
}
/* std::string can be implicitly constructed from char*
* Make vector<string> and pass it as the out argument, more efficient than regular returning one
* Does this really need to be its own function at this point? */
void ProcessChatString(std::string &in, std::vector<std::string> &out) {
std::transform(in.begin(), in.end(), in.begin(), std::toupper);
TokenizeString(in, " ", out);
}
/* Again, does this need to be its own function?
* Just copying string contents because const_cast is sick */
void CopyProcessedStringToChat(const std::string &in, char *out) {
TethysGame::AddGameSound(sndBld_not, -1);
strncpy_s(out, 30, in.c_str(), _TRUNCATE);
}