# HG changeset patch
# User nemo
# Date 1547487332 18000
# Node ID e54e41554529abb2e93c8cc4814daba35c39c23b
# Parent ab79cd4a73824e68dcb700be67ae743711344082# Parent 8736f3a0ff7f76e65217794a6c4fbd51b9bfc18f
branch merge
diff -r 8736f3a0ff7f -r e54e41554529 .hgignore
--- a/.hgignore Mon Jan 14 12:34:47 2019 -0500
+++ b/.hgignore Mon Jan 14 12:35:32 2019 -0500
@@ -20,6 +20,7 @@
cmake_install.cmake
QTfrontend/hwconsts.cpp
QTfrontend/servermessages.h
+QTfrontend/creditsmessages.h
CPackConfig.cmake
CPackSourceConfig.cmake
tools/cmake_uninstall.cmake
@@ -89,3 +90,5 @@
*.user
*.iml
build-qmlfrontend*
+.cabal-sandbox
+cabal.sandbox.config
diff -r 8736f3a0ff7f -r e54e41554529 CMakeLists.txt
--- a/CMakeLists.txt Mon Jan 14 12:34:47 2019 -0500
+++ b/CMakeLists.txt Mon Jan 14 12:35:32 2019 -0500
@@ -72,9 +72,6 @@
if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4" AND UNIX AND NOT APPLE)
set(BUILD_ENGINE_C ON CACHE STRING "PAS2C force-enabled due to a freepascal 32 bit alignment bug" FORCE)
endif()
-if(BUILD_ENGINE_C)
- set(NOVIDEOREC ON CACHE STRING "PAS2C does not support video recording at present" FORCE)
-endif()
#system paths for finding required fonts (see share/hedgewars/Data/fonts)
#subdirectories will NOT be searched.
@@ -82,11 +79,15 @@
set(FONTS_DIRS "" CACHE STRING "Additional paths to folders where required fonts can be found ( ; is separator)")
#versioning
-set(CPACK_PACKAGE_VERSION_MAJOR 0)
-set(CPACK_PACKAGE_VERSION_MINOR 9)
-set(CPACK_PACKAGE_VERSION_PATCH 25)
-set(HEDGEWARS_PROTO_VER 57)
-set(HEDGEWARS_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
+set(CPACK_PACKAGE_VERSION_MAJOR 1)
+set(CPACK_PACKAGE_VERSION_MINOR 0)
+set(CPACK_PACKAGE_VERSION_PATCH 0)
+set(HEDGEWARS_PROTO_VER 58)
+if((CMAKE_BUILD_TYPE MATCHES "RELEASE") OR (CMAKE_BUILD_TYPE MATCHES "RELWITHDEBUGINFO"))
+ set(HEDGEWARS_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
+else()
+ set(HEDGEWARS_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}-dev")
+endif()
include(${CMAKE_MODULE_PATH}/revinfo.cmake)
message(STATUS "Building ${HEDGEWARS_VERSION}-r${HEDGEWARS_REVISION} (${HEDGEWARS_HASH})")
diff -r 8736f3a0ff7f -r e54e41554529 CREDITS
--- a/CREDITS Mon Jan 14 12:34:47 2019 -0500
+++ b/CREDITS Mon Jan 14 12:35:32 2019 -0500
@@ -1,6 +1,7 @@
=============================
=== EXTENDED CREDITS LIST ===
=============================
+For the main credits, click on the Hedgewars logo in the main menu.
IF NOT OTHERWISE SPECIFIED, ALL OTHER CONTENT IS PROPERTY OF Andrey Korotaev .
IF NO LICENSE IS SPECIFIED, THE LICENSE IS THE SAME AS MENTIONED IN README.md.
@@ -12,26 +13,6 @@
==========
- see Fonts_LICENSE.txt
-=================
-= FRONTEND IMAGES
-=================
-(File name suffixes are omitted)
-
-- Tango Project and Wuzzy -> audio, home (CC0)
-- abustany and Wuzzy -> Videos (CC0)
-- Juliane Krug and Wuzzy -> Palette (CC0)
-- raseone and Wuzzy -> folder (CC0)
-
-==========
-= FORTS
-==========
-- Carlos Vives -> Tank (2010)
-- Dragonfly -> EvilChicken (2010)
-- Randy Broda -> SteelTower (2013)
-- Jon Dum and Wuzzy -> Snail (2017)
-- Maciej Mroziński (a.k.a. alzen) and Wuzzy -> Lonely_Island (2017)
-- Guillaume Englert and Wuzzy -> Olympic (2017)
-
==========
= HATS
==========
@@ -51,57 +32,32 @@
- Terrington_Snyde -> pirate_eyepatch (2013), jester (2013)
- Wohlstand -> policegirl [based on policecap and sm_daisy] (2014)
- TheMadCharles -> barrelhider (CC BY 3.0) (2015)
+- Trey Perry -> Other hats
==========
-= GRAVESTONES
+= GRAVES
==========
- Randy Broda -> dragonball (2012)
- CheezeMonkey -> pi (2011)
- rosenholz -> Whisky (2013)
-==========
-= MAPS
-==========
-- John Dum -> Bath (2008), Hedgelove (2008), Hedgewars (2008), Hydrant (2008), mushrooms (2008), Plane (2008)
-- Joshua Frese -> Bamboo (2008), EarthRise (2008), Freeway (2008), BambooPlinko (2008)
-- Stanko Tadić -> Castle (2008), PirateFlag (2008)
-- dctPL -> Sticks (2010)
-- wolfmarc & Dragonfly -> TrophyRace (2010), ShoppaKing (2010)
+=================
+= FRONTEND IMAGES
+=================
+(File name suffixes are omitted)
-==========
-= MUSIC
-==========
-- HSR ( http://elhombresinremedio.com ) -> (new) City theme, Rock theme and many other tracks
-- John Dum -> Nature theme
-- Jonatan Nilsson -> Pirate theme, EarthRise (former City) theme, Oriental theme, Snow theme)
-- yd - http://opengameart.org/users/yd -> "oriented", used as Olympics SD theme
-- Kevin MacLeod - http://incompetech.com/ -> "hitman", used as basis for preliminary default SD theme
-- Valentin Kraevskiy (alias alfadur) -> Jungle theme, Fruit theme
+- Tango Project and Wuzzy -> audio, home (CC0)
+- abustany and Wuzzy -> Videos (CC0)
+- Juliane Krug and Wuzzy -> Palette (CC0)
+- raseone and Wuzzy -> folder (CC0)
+
+==================
+= OTHER GRAPHICS =
+==================
+- Custom ammo images for Continental supplies: KarBoy2314PL
==========
-= THEMES
-==========
-- John Dum -> Nature (2008), Snow (2008), City
-- Joshua Frese -> Bamboo (2008), EarthRise (2008), Freeway (2008), BambooPlinko (2008)
-- Stanko Tadić -> Hell (2008)
-- Julien Koesten -> Sheep (2008)
-- KoRn666 - Jungle (2010)
-- Randy Broda -> Fruit (2013), Cake (2014)
-
-==========
-= VOICES
-==========
-- Stephen Alexander
-- mtg90pl : Default_pl, Russian_pl
-
-==========
-= MISSIONS
-==========
-- Arkhnen -> Teamwork 2 (2012)
-- Wuzzy -> Big Armory (2016)
-
-==========
-= SOUNDS
+= SOUND EFFECTS
==========
- Mine impact sound from http://www.freesound.org/people/adcbicycle/sounds/13947/
- Hammer sound from http://www.freesound.org/people/Syna-Max/sounds/43586/
@@ -139,11 +95,6 @@
- Shoryuken hit: Based off sound by CGEffex (CC-BY 3.0), tweaked by alfadur
http://freesound.org/people/CGEffex/sounds/98341/
-==================
-= OTHER GRAPHICS =
-==================
-- Custom ammo images for Continental supplies: KarBoy2314PL
-
======================
= LICENSE REFERENCES =
======================
diff -r 8736f3a0ff7f -r e54e41554529 ChangeLog.txt
--- a/ChangeLog.txt Mon Jan 14 12:34:47 2019 -0500
+++ b/ChangeLog.txt Mon Jan 14 12:35:32 2019 -0500
@@ -1,5 +1,84 @@
+ features
* bugfixes
+============== 1.0.0-dev (unreleased) ==============
+Gameplay:
+ + Hand-drawn maps can now be scaled with slider
+ + Slightly longer delays between turns to make it easier to follow the game
+ + Track high scores in singleplayer challenges
+ + Show check mark for completed scenarios, challenges and trainings
+ + Training/challenge/scenario menu now supports team selection
+ * Fix hedgehogs being pushed around (and other collision bugs) when they overlap
+ * Fix homing bee flying weird if passing wrap world edge or target was placed beyond it
+ * Fix poison damage not working in first round
+ * Use player-chosen team identity in campaigns and singleplayer missions
+ * Fix player-chosen teams ignoring custom team controls in campaigns
+ * Fix broken behaviour of airborne attacks when placed near bounce world edge
+ * Deny placement of airborne attack in “impossible” places in maps with bounce world edge
+ * Deny placement of piano beyond bounce world edge
+
+Styles and schemes:
+ + The Specialists: Unlock game scheme
+ + The Specialists: Add script parameter support to set custom specialists order
+ * Balanced Random Weapon: Fix Lua errors after using Time Box
+ * Racer: Fix racer ghost not getting reset after a skip
+ * King Mode: Fix team sometimes not being killed properly if king drowned
+ * King Mode: Kill resurrected minions if king is not alive
+
+A Classic Fairytale:
+ * First blood: Fix Lua error when hitting Attack after failing the rope challenge
+
+A Space Adventure:
+ + Show your current records at mission start when re-playing one of the challenges
+ + Spacetrip: Move flowers of desert planet above cactus
+ * Searching in the dust: Fix mission ending when all smugglers are dead
+ * Chasing the blue hog: Fix player not losing the race when timing out while still having the rope
+ * Chasing the blue hog: Fix player winning if Crazy Runner died
+ * Various minor tweaks and bugfixes
+
+Controls:
+ + Add control to unselect current weapon (no key chosen by default)
+ + Allow to leave a control unused
+ + New chat command: “/help room” (shows room chat commands within the game)
+
+Graphics:
+ + Show contour of flying saucer when in highly opaque water
+ * Fix speech bubbles overlapping in the wrong order
+ * Fix wrong ice beam angle if it goes diagonally up out of map through world wrap
+ * Fix double water splash when flying saucer drowns
+
+Game HUD:
+ + Colorize switching arrows, pointing arrow and target cross in clan color
+ + Skip ammo menu animation when playing with turn time of 10s or less
+ + Don't show crate spawn message for initial crates in missions
+ + Display player name of own teams in online games
+ * Fix last 2 characters in demo chat being missing
+ * Hide most HUD elements in cinematic mode
+ * Don't show "F1", "F2", etc. in ammo menu if these aren't the actual slot keys
+ * Fix wind bar animation not looping properly
+ * Fix airplane line being drawn above many HUD elements
+ * Suppress “ is gone.” message at end of game
+
+Frontend:
+ + Restructure credits
+ + Credits screen in main menu is now translatable
+ * Fix force-locked schemes getting unlocked when changing map types
+
+Lua API:
+ + New call: SaveMissionVar(varname, value): Save value to mission variable (variable for non-campaign mission)
+ + New call: GetMissionVar(varname): Get value of mission variable
+ + New call: SetTurnTimePaused(isPaused): Call with true to pause turn time, false to unpause
+ + New call: GetTurnTimePaused(): Returns true if turn time is paused due to Lua
+ + New call: AddMissionTeam(color): Add mission team, i.e. the team selected by player in campaign/mission page
+ + New call: AddMissionHog(health): Add a hedgehog for the mission team
+ + New return value: AddTeam/AddMissionTeam return ,
+ + Utils library: New calls: getReadableChallengeRecord, updateChallengeRecord
+ + New callback: onGameResult(winningClan): Called when the game ends normally. winningClan = index of winning clan or -1 on draw
+ + SendStat extension: Option to use predefined modes with siPointType, like "!POINTS" or "!TIME"
+ + SimpleMission: Add isMissionTeam attribute for teams
+ + SpeedShoppa/TargetPractice libraries: Remove custom hog and team info settings
+ + Params explode, poison in the SpawnFake*Crate functions now optional and default to false
+ + New global: InitHealth: Initial hog health value from game scheme (read-only)
+
====================== 0.9.25 ======================
HIGHLIGHTS:
+ Complete overhaul of Continental supplies
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/CMakeLists.txt
--- a/QTfrontend/CMakeLists.txt Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/CMakeLists.txt Mon Jan 14 12:35:32 2019 -0500
@@ -45,6 +45,37 @@
list(APPEND locsout ${firstline} "\n}\\;\n")
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/servermessages.h ${locsout})
+
+# Credits localization
+file(GLOB CreditsCSV ${CMAKE_SOURCE_DIR}/QTfrontend/res/credits.csv)
+foreach(csvfile ${CreditsCSV})
+ # Load credits.csv
+ file(READ ${csvfile} csv)
+
+ # Match first line of CSV file
+ string(REGEX MATCH "(E|S|U),\"[^\n\"]+\"" loc_top ${csv})
+ string(REGEX REPLACE "(E|S|U),\"([^\n\"]+)\"" "\nQT_TRANSLATE_NOOP(\"credits\", \"\\2\")" s ${loc_top})
+ list(APPEND csvlocs ${s})
+
+ # Match remaining lines of CSV file
+ string(REGEX MATCHALL "\n(E|S|U),\"[^\n\"]+\"" locs ${csv})
+ foreach(str ${locs})
+ string(REGEX REPLACE "(E|S|U),\"([^\n\"]+)\"" "QT_TRANSLATE_NOOP(\"credits\", \"\\2\")" s ${str})
+ list(APPEND csvlocs ${s})
+ endforeach(str)
+endforeach(csvfile)
+
+list(REMOVE_DUPLICATES csvlocs)
+list(GET csvlocs 0 firstline)
+list(REMOVE_AT csvlocs 0)
+set(locsout "const char * creditsMessages[] = {")
+foreach(l ${csvlocs})
+ list(APPEND locsout ${l} ",")
+endforeach(l)
+list(APPEND locsout ${firstline} "\n}\\;\n")
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/creditsmessages.h ${locsout})
+
+
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/model)
@@ -92,6 +123,7 @@
main.cpp
team.cpp
campaign.cpp
+ mission.cpp
ui_hwform.cpp
${CMAKE_CURRENT_BINARY_DIR}/hwconsts.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sdlkeys.cpp
@@ -137,7 +169,9 @@
hwconsts.h
sdlkeys.h
campaign.h
+ mission.h
${CMAKE_CURRENT_BINARY_DIR}/servermessages.h
+ ${CMAKE_CURRENT_BINARY_DIR}/creditsmessages.h
)
set(hwfr_rez hedgewars.qrc)
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/binds.cpp
--- a/QTfrontend/binds.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/binds.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -39,6 +39,7 @@
{"slot 8", "f8", QT_TRANSLATE_NOOP("binds", "slot 8"), NULL, NULL},
{"slot 9", "f9", QT_TRANSLATE_NOOP("binds", "slot 9"), NULL, NULL},
{"slot :", "f10", QT_TRANSLATE_NOOP("binds", "slot 10"), NULL, NULL},
+ {"setweap ~", "none", QT_TRANSLATE_NOOP("binds", "unselect weapon"), NULL, NULL},
{"timer 1", "1", QT_TRANSLATE_NOOP("binds", "timer 1 sec"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Set the timer on bombs and timed weapons:")},
{"timer 2", "2", QT_TRANSLATE_NOOP("binds", "timer 2 sec"), NULL, NULL},
{"timer 3", "3", QT_TRANSLATE_NOOP("binds", "timer 3 sec"), NULL, NULL},
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/binds.h
--- a/QTfrontend/binds.h Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/binds.h Mon Jan 14 12:35:32 2019 -0500
@@ -22,9 +22,9 @@
#include
#ifdef VIDEOREC
-#define BINDS_NUMBER 51
+#define BINDS_NUMBER 52
#else
-#define BINDS_NUMBER 50
+#define BINDS_NUMBER 51
#endif
struct BindAction
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/campaign.cpp
--- a/QTfrontend/campaign.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/campaign.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -52,7 +52,7 @@
missionInList: QComboBox index of the mission as selected in the mission widget
teamName: Name of the playing team
*/
-bool isMissionWon(QString & campaignName, int missionInList, QString & teamName)
+bool isCampMissionWon(QString & campaignName, int missionInList, QString & teamName)
{
QSettings* teamfile = getCampTeamFile(campaignName, teamName);
int progress = teamfile->value("Campaign " + campaignName + "/Progress", 0).toInt();
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/campaign.h
--- a/QTfrontend/campaign.h Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/campaign.h Mon Jan 14 12:35:32 2019 -0500
@@ -36,7 +36,7 @@
QSettings* getCampTeamFile(QString & campaignName, QString & teamName);
QSettings* getCampMetaInfo();
bool isCampWon(QString & campaignName, QString & teamName);
-bool isMissionWon(QString & campaignName, int missionInList, QString & teamName);
+bool isCampMissionWon(QString & campaignName, int missionInList, QString & teamName);
QString getRealCampName(const QString & campaignName);
QList getCampMissionList(QString & campaignName, QString & teamName);
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/game.cpp
--- a/QTfrontend/game.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/game.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -29,6 +29,8 @@
#include "hwform.h"
#include "ui/page/pageoptions.h"
+#include "ui/page/pagetraining.h"
+#include "ui/page/pagecampaign.h"
#include "game.h"
#include "hwconsts.h"
#include "gameuiconfig.h"
@@ -44,12 +46,11 @@
// last game info
QList lastGameStartArgs = QList();
GameType lastGameType = gtNone;
-QString lastTrainingSubFolder = NULL;
GameCFGWidget * lastGameCfg = NULL;
QString lastGameAmmo = NULL;
TeamSelWidget * lastGameTeamSel = NULL;
-QString training, campaign, campaignScript, campaignTeam; // TODO: Cleaner solution?
+QString trainingName, trainingScript, trainingTeam, campaign, campaignScript, campaignTeam; // TODO: Cleaner solution?
HWGame::HWGame(GameUIConfig * config, GameCFGWidget * gamecfg, QString ammo, TeamSelWidget* pTeamSelWidget) :
TCPBase(true, 0),
@@ -184,8 +185,16 @@
{
QByteArray traincfg;
HWProto::addStringToBuffer(traincfg, "TL");
+
+ HWTeam missionTeam = HWTeam();
+ missionTeam.setName(config->Form->ui.pageTraining->CBTeam->currentText());
+ missionTeam.loadFromFile();
+ missionTeam.setNumHedgehogs(HEDGEHOGS_PER_TEAM);
+ missionTeam.setMissionTeam(true);
+ HWProto::addStringListToBuffer(traincfg, missionTeam.teamGameConfig(100));
+
HWProto::addStringToBuffer(traincfg, "eseed " + QUuid::createUuid().toString());
- HWProto::addStringToBuffer(traincfg, "escript " + training);
+ HWProto::addStringToBuffer(traincfg, "escript " + trainingScript);
RawSendIPC(traincfg);
}
@@ -194,8 +203,15 @@
{
QByteArray campaigncfg;
HWProto::addStringToBuffer(campaigncfg, "TL");
+
+ HWTeam missionTeam = HWTeam();
+ missionTeam.setName(config->Form->ui.pageCampaign->CBTeam->currentText());
+ missionTeam.loadFromFile();
+ missionTeam.setNumHedgehogs(HEDGEHOGS_PER_TEAM);
+ missionTeam.setMissionTeam(true);
+ HWProto::addStringListToBuffer(campaigncfg, missionTeam.teamGameConfig(100));
+
HWProto::addStringToBuffer(campaigncfg, "eseed " + QUuid::createUuid().toString());
-
HWProto::addStringToBuffer(campaigncfg, "escript " + campaignScript);
RawSendIPC(campaigncfg);
@@ -288,7 +304,7 @@
QString msgbody = QString::fromUtf8(msg.mid(2).left(size - 4));
emit SendChat(msgbody);
QByteArray buf;
- HWProto::addStringToBuffer(buf, "s" + HWProto::formatChatMsg(config->netNick(), msgbody));
+ HWProto::addStringToBuffer(buf, "s" + HWProto::formatChatMsg(config->netNick(), msgbody) + "\x20\x20");
demo.append(buf);
break;
}
@@ -307,6 +323,14 @@
writeCampaignVar(msg.right(msg.size() - 3));
break;
}
+ case 'v':
+ {
+ if (msg.at(2) == '?')
+ sendMissionVar(msg.right(msg.size() - 3));
+ else if (msg.at(2) == '!')
+ writeMissionVar(msg.right(msg.size() - 3));
+ break;
+ }
case 'W':
{
// fetch new window resolution via IPC and save it in the settings
@@ -342,21 +366,21 @@
void HWGame::FromNetChat(const QString & msg)
{
QByteArray buf;
- HWProto::addStringToBuffer(buf, 's' + msg);
+ HWProto::addStringToBuffer(buf, 's' + msg + "\x20\x20");
RawSendIPC(buf);
}
void HWGame::FromNetWarning(const QString & msg)
{
QByteArray buf;
- HWProto::addStringToBuffer(buf, "s\x00" + msg);
+ HWProto::addStringToBuffer(buf, "s\x00" + msg + "\x20\x20");
RawSendIPC(buf);
}
void HWGame::FromNetError(const QString & msg)
{
QByteArray buf;
- HWProto::addStringToBuffer(buf, "s\x05" + msg);
+ HWProto::addStringToBuffer(buf, "s\x05" + msg + "\x20\x20");
RawSendIPC(buf);
}
@@ -500,16 +524,19 @@
SetGameState(gsStarted);
}
-void HWGame::StartTraining(const QString & file, const QString & subFolder)
+void HWGame::StartTraining(const QString & file, const QString & subFolder, const QString & trainTeam)
{
lastGameStartArgs.clear();
lastGameStartArgs.append(file);
+ lastGameStartArgs.append(subFolder);
+ lastGameStartArgs.append(trainTeam);
lastGameType = gtTraining;
- lastTrainingSubFolder = subFolder;
gameType = gtTraining;
- training = "Missions/" + subFolder + "/" + file + ".lua";
+ trainingScript = "Missions/" + subFolder + "/" + file + ".lua";
+ trainingName = file;
+ trainingTeam = trainTeam;
demo.clear();
Start(false);
SetGameState(gsStarted);
@@ -538,7 +565,11 @@
emit GameStateChanged(state);
if (gameType == gtCampaign)
{
- emit CampStateChanged(1);
+ emit CampStateChanged(1);
+ }
+ else if (gameType == gtTraining)
+ {
+ emit TrainingStateChanged(1);
}
}
@@ -574,3 +605,28 @@
teamfile.setValue("Campaign " + campaign + "/" + varToWrite, varValue);
}
+void HWGame::sendMissionVar(const QByteArray &varToSend)
+{
+ QString varToFind = QString::fromUtf8(varToSend);
+ QSettings teamfile(QString(cfgdir->absolutePath() + "/Teams/%1.hwt").arg(trainingTeam), QSettings::IniFormat, 0);
+ teamfile.setIniCodec("UTF-8");
+ QString varValue = teamfile.value("Mission " + trainingName + "/" + varToFind, "").toString();
+ QByteArray command;
+ HWProto::addStringToBuffer(command, "v." + varValue);
+ RawSendIPC(command);
+}
+
+void HWGame::writeMissionVar(const QByteArray & varVal)
+{
+ int i = varVal.indexOf(" ");
+ if(i < 0)
+ return;
+
+ QString varToWrite = QString::fromUtf8(varVal.left(i));
+ QString varValue = QString::fromUtf8(varVal.mid(i + 1));
+
+ QSettings teamfile(QString(cfgdir->absolutePath() + "/Teams/%1.hwt").arg(trainingTeam), QSettings::IniFormat, 0);
+ teamfile.setIniCodec("UTF-8");
+ teamfile.setValue("Mission " + trainingName + "/" + varToWrite, varValue);
+}
+
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/game.h
--- a/QTfrontend/game.h Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/game.h Mon Jan 14 12:35:32 2019 -0500
@@ -64,7 +64,6 @@
// last game info
extern QList lastGameStartArgs;
extern GameType lastGameType;
-extern QString lastTrainingSubFolder;
extern GameCFGWidget * lastGameCfg;
extern QString lastGameAmmo;
extern TeamSelWidget * lastGameTeamSel;
@@ -80,7 +79,7 @@
void StartLocal();
void StartQuick();
void StartNet();
- void StartTraining(const QString & file, const QString & subFolder);
+ void StartTraining(const QString & file, const QString & subFolder, const QString & trainTeam);
void StartCampaign(const QString & camp, const QString & campScript, const QString & campTeam);
void abort();
GameState gameState;
@@ -100,6 +99,7 @@
void HaveRecord(RecordType type, const QByteArray & record);
void ErrorMessage(const QString &);
void CampStateChanged(int);
+ void TrainingStateChanged(int);
void SendConsoleCommand(const QString & command);
public slots:
@@ -127,6 +127,8 @@
void SetGameState(GameState state);
void sendCampaignVar(const QByteArray & varToSend);
void writeCampaignVar(const QByteArray &varVal);
+ void sendMissionVar(const QByteArray & varToSend);
+ void writeMissionVar(const QByteArray &varVal);
void flushNetBuffer();
};
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/hedgewars.qrc
--- a/QTfrontend/hedgewars.qrc Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/hedgewars.qrc Mon Jan 14 12:35:32 2019 -0500
@@ -44,6 +44,8 @@
res/CampaignDefault.png
res/Multiplayer.png
res/Trainings.png
+ res/Challenges.png
+ res/Scenarios.png
res/Background.png
res/BackgroundChristmas.png
res/BackgroundEaster.png
@@ -206,10 +208,13 @@
res/chat/lamp_off.png
res/chat/ingame.png
res/splash.png
- res/html/about.html
+ res/credits.csv
res/chat/hedgehogcontributor.png
res/chat/hedgehogcontributor_gray.png
res/chat/roomadmincontributor.png
res/chat/roomadmincontributor_gray.png
+
+ res/Trainings_en.png
+
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/hwform.cpp
--- a/QTfrontend/hwform.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/hwform.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -57,6 +57,7 @@
#include "hwform.h"
#include "game.h"
#include "team.h"
+#include "mission.h"
#include "campaign.h"
#include "teamselect.h"
#include "selectWeapon.h"
@@ -218,6 +219,7 @@
previousTeamName = "";
UpdateTeamsLists();
InitCampaignPage();
+ RestoreSingleplayerTeamSelection();
UpdateCampaignPage(0);
UpdateCampaignPageTeam(0);
UpdateCampaignPageMission(0);
@@ -330,6 +332,9 @@
connect(ui.pageCampaign->CBTeam, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPageTeam(int)));
connect(ui.pageCampaign->CBCampaign, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPage(int)));
connect(ui.pageCampaign->CBMission, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPageMission(int)));
+ connect(ui.pageTraining->CBTeam, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateTrainingPageTeam(int)));
+ connect(ui.pageCampaign->CBTeam, SIGNAL(currentIndexChanged(int)), ui.pageTraining->CBTeam, SLOT(setCurrentIndex(int)));
+ connect(ui.pageTraining->CBTeam, SIGNAL(currentIndexChanged(int)), ui.pageCampaign->CBTeam, SLOT(setCurrentIndex(int)));
connect(ui.pageSelectWeapon->pWeapons, SIGNAL(weaponsDeleted(QString)),
this, SLOT(DeleteWeapons(QString)));
@@ -572,7 +577,8 @@
ui.pageOptions->CBTeamName->clear();
ui.pageOptions->CBTeamName->addItems(teamslist);
ui.pageCampaign->CBTeam->clear();
- /* Only show human teams in campaign page */
+ ui.pageTraining->CBTeam->clear();
+ /* Only show human teams in campaign/training page */
for(int i=0; iCBTeam->addItem(teamslist[i]);
+ ui.pageTraining->CBTeam->addItem(teamslist[i]);
}
}
+ UpdateTrainingPageTeam(0);
}
void HWForm::GoToNewWeapons()
@@ -921,6 +929,11 @@
GoBack();
}
+ if (curid == ID_PAGE_CAMPAIGN)
+ config->setValue("frontend/lastSingleplayerTeam", ui.pageCampaign->CBTeam->currentText());
+ if (curid == ID_PAGE_TRAINING)
+ config->setValue("frontend/lastSingleplayerTeam", ui.pageTraining->CBTeam->currentText());
+
if (curid == ID_PAGE_ROOMSLIST || curid == ID_PAGE_CONNECTING) NetDisconnect();
if (curid == ID_PAGE_NETGAME && hwnet && hwnet->isInRoom()) hwnet->partRoom();
// need to work on this, can cause invalid state for admin quit trying to prevent bad state message on kick
@@ -1790,6 +1803,7 @@
{
game = new HWGame(config, gamecfg, ammo, pTeamSelWidget);
connect(game, SIGNAL(CampStateChanged(int)), this, SLOT(UpdateCampaignPageProgress(int)));
+ connect(game, SIGNAL(TrainingStateChanged(int)), this, SLOT(UpdateTrainingPageTeam(int)));
connect(game, SIGNAL(GameStateChanged(GameState)), this, SLOT(GameStateChanged(GameState)));
connect(game, SIGNAL(GameStats(char, const QString &)), ui.pageGameStats, SLOT(GameStats(char, const QString &)));
connect(game, SIGNAL(ErrorMessage(const QString &)), this, SLOT(ShowFatalErrorMessage(const QString &)), Qt::QueuedConnection);
@@ -1842,7 +1856,8 @@
{
CreateGame(0, 0, 0);
- game->StartTraining(scriptName, subFolder);
+ QString trainTeam = ui.pageTraining->CBTeam->currentText();
+ game->StartTraining(scriptName, subFolder, trainTeam);
}
void HWForm::StartCampaign()
@@ -1997,6 +2012,36 @@
}
}
+void HWForm::UpdateTrainingPageTeam(int index)
+{
+ Q_UNUSED(index);
+ HWTeam team(ui.pageTraining->CBTeam->currentText());
+ QString tName = team.name();
+
+ QListWidget* listWidget;
+ for(int w = 0; w < 3; w++)
+ {
+ switch(w) {
+ case 0: listWidget = ui.pageTraining->lstTrainings; break;
+ case 1: listWidget = ui.pageTraining->lstChallenges; break;
+ case 2: listWidget = ui.pageTraining->lstScenarios; break;
+ default: listWidget = ui.pageTraining->lstTrainings; break;
+ }
+ unsigned int n = listWidget->count();
+
+ for(unsigned int i = 0; i < n; i++)
+ {
+ QListWidgetItem* item = listWidget->item(i);
+ QString missionName = QString(item->data(Qt::UserRole).toString()).replace(QString(" "),QString("_"));
+ if(isMissionWon(missionName, tName))
+ item->setIcon(finishedIcon);
+ else
+ item->setIcon(notFinishedIcon);
+ }
+ }
+ ui.pageTraining->updateInfo();
+}
+
void HWForm::InitCampaignPage()
{
ui.pageCampaign->CBCampaign->clear();
@@ -2016,6 +2061,27 @@
QString tName = team.name();
ui.pageCampaign->CBCampaign->addItem(getRealCampName(campaignName), campaignName);
}
+
+}
+
+void HWForm::RestoreSingleplayerTeamSelection()
+{
+ QString lastTeam = config->value("frontend/lastSingleplayerTeam", QString()).toString();
+ if (!lastTeam.isNull() && !lastTeam.isEmpty())
+ {
+ int index = ui.pageCampaign->CBTeam->findData(lastTeam, Qt::DisplayRole);
+ if(index != -1)
+ {
+ ui.pageCampaign->CBTeam->setCurrentIndex(index);
+ UpdateCampaignPageTeam(index);
+ }
+ index = ui.pageTraining->CBTeam->findData(lastTeam, Qt::DisplayRole);
+ if(index != -1)
+ {
+ ui.pageTraining->CBTeam->setCurrentIndex(index);
+ UpdateTrainingPageTeam(index);
+ }
+ }
}
void HWForm::UpdateCampaignPage(int index)
@@ -2031,7 +2097,7 @@
for(int i=0;iCBMission->addItem(QString(campaignMissionInfo[i].realName), QString(campaignMissionInfo[i].name));
- if(isMissionWon(campaignName, i, tName))
+ if(isCampMissionWon(campaignName, i, tName))
ui.pageCampaign->CBMission->setItemIcon(i, finishedIcon);
else
ui.pageCampaign->CBMission->setItemIcon(i, notFinishedIcon);
@@ -2235,7 +2301,7 @@
switch(lastGameType) {
case gtTraining:
- game->StartTraining(lastGameStartArgs.at(0).toString(), lastTrainingSubFolder);
+ game->StartTraining(lastGameStartArgs.at(0).toString(), lastGameStartArgs.at(1).toString(), lastGameStartArgs.at(2).toString());
break;
case gtQLocal:
game->StartQuick();
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/hwform.h
--- a/QTfrontend/hwform.h Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/hwform.h Mon Jan 14 12:35:32 2019 -0500
@@ -135,7 +135,9 @@
void UpdateCampaignPageTeam(int index);
void UpdateCampaignPageProgress(int index);
void UpdateCampaignPageMission(int index);
+ void UpdateTrainingPageTeam(int index);
void InitCampaignPage();
+ void RestoreSingleplayerTeamSelection();
void showFeedbackDialog();
void showFeedbackDialogNetChecked();
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/mission.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/mission.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,79 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2018 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "mission.h"
+#include "hwconsts.h"
+#include "DataManager.h"
+#include
+#include
+#include
+
+QSettings* getMissionTeamFile(QString & missionName, QString & teamName)
+{
+ QSettings* teamfile = new QSettings(cfgdir->absolutePath() + "/Teams/" + teamName + ".hwt", QSettings::IniFormat, 0);
+ teamfile->setIniCodec("UTF-8");
+ if (!teamfile->childGroups().contains("Mission " + missionName) &&
+ teamfile->childGroups().contains("Mission " + missionName)){
+ teamfile->beginGroup("Mission " + missionName);
+ QStringList keys = teamfile->childKeys();
+ teamfile->endGroup();
+ for (int i=0;ivalue("Mission " + missionName + "/" + keys[i]);
+ teamfile->setValue("Mission " + missionName + "/" + keys[i], value);
+ }
+ teamfile->remove("Mission " + missionName);
+ }
+
+ return teamfile;
+}
+
+/**
+ Returns true if the specified mission has been completed
+ missionName: Name of the mission in question
+ teamName: Name of the playing team
+*/
+bool isMissionWon(QString & missionName, QString & teamName)
+{
+ QSettings* teamfile = getMissionTeamFile(missionName, teamName);
+ bool won = teamfile->value("Mission " + missionName + "/Won", false).toBool();
+ return won;
+}
+
+/**
+ Returns true if the mission value adressed with the provided
+ missionName: Name of the mission in question
+ teamName: Name of the playing team
+ key: name of key to check
+*/
+bool missionValueExists(QString & missionName, QString & teamName, QString key)
+{
+ QSettings* teamfile = getMissionTeamFile(missionName, teamName);
+ return teamfile->contains("Mission " + missionName + "/" + key);
+}
+/**
+ Returns a mission value.
+ NOTE: Check whether the mission value exists first, using missionValueExists.
+ missionName: Name of the mission in question
+ teamName: Name of the playing team
+ key: name of key to read its value from
+*/
+QVariant getMissionValue(QString & missionName, QString & teamName, QString key)
+{
+ QSettings* teamfile = getMissionTeamFile(missionName, teamName);
+ return teamfile->value("Mission " + missionName + "/" + key);
+}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/mission.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/mission.h Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,30 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2018 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef MISSION_H
+#define MISSION_H
+
+#include
+#include
+
+QSettings* getMissionTeamFile(QString & missionName, QString & teamName);
+bool isMissionWon(QString & missionName, QString & teamName);
+bool missionValueExists(QString & missionName, QString & teamName, QString key);
+QVariant getMissionValue(QString & missionName, QString & teamName, QString key);
+
+#endif
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/model/gameSchemeModel.cpp
--- a/QTfrontend/model/gameSchemeModel.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/model/gameSchemeModel.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -89,6 +89,7 @@
<< "King Mode"
<< "Mutant"
<< "Construction Mode"
+ << "The Specialists"
<< "Space Invasion"
<< "HedgeEditor"
;
@@ -671,9 +672,58 @@
<< QVariant("initialenergy=550, energyperround=50, maxenergy=1000, cratesperround=5") // scriptparam 43
;
+ QList specialists;
+ specialists
+ << predefSchemesNames[12] // name 0
+ << QVariant(true) // switchhog 1
+ << QVariant(false) // team divide 2
+ << QVariant(false) // solid land 3
+ << QVariant(false) // border 4
+ << QVariant(false) // low gravity 5
+ << QVariant(false) // laser sight 6
+ << QVariant(false) // invulnerable 7
+ << QVariant(false) // reset health 8
+ << QVariant(false) // vampiric 9
+ << QVariant(false) // karma 10
+ << QVariant(false) // artillery 11
+ << QVariant(false) // random order 12
+ << QVariant(false) // king 13
+ << QVariant(true) // place hog 14
+ << QVariant(false) // shared ammo 15
+ << QVariant(false) // disable girders 16
+ << QVariant(false) // disable land objects 17
+ << QVariant(false) // AI survival 18
+ << QVariant(true) // inf. attack 19
+ << QVariant(true) // reset weps 20
+ << QVariant(true) // per hog ammo 21
+ << QVariant(false) // no wind 22
+ << QVariant(false) // more wind 23
+ << QVariant(false) // tag team 24
+ << QVariant(false) // bottom border 25
+ << QVariant(100) // damage modfier 26
+ << QVariant(45) // turn time 27
+ << QVariant(100) // init health 28
+ << QVariant(15) // sudden death 29
+ << QVariant(5) // case prob 30
+ << QVariant(3) // mines time 31
+ << QVariant(0) // mines number 32
+ << QVariant(0) // mine dud pct 33
+ << QVariant(0) // explosives 34
+ << QVariant(0) // air mines 35
+ << QVariant(100) // health case pct 36
+ << QVariant(25) // health case amt 37
+ << QVariant(47) // water rise amt 38
+ << QVariant(5) // health dec amt 39
+ << QVariant(100) // rope modfier 40
+ << QVariant(100) // get away time 41
+ << QVariant(0) // world edge 42
+ // NOTE: If you change this, also change the defaults in the The Specialists script
+ << QVariant("t=SENDXHPL") // scriptparam 43
+ ;
+
QList spaceinvasion;
spaceinvasion
- << predefSchemesNames[12] // name 0
+ << predefSchemesNames[13] // name 0
<< QVariant(false) // switchhog 1
<< QVariant(false) // team divide 2
<< QVariant(false) // solid land 3
@@ -722,7 +772,7 @@
QList hedgeeditor;
hedgeeditor
- << predefSchemesNames[13] // name 0
+ << predefSchemesNames[14] // name 0
<< QVariant(false) // switchhog 1
<< QVariant(false) // team divide 2
<< QVariant(false) // solid land 3
@@ -782,6 +832,7 @@
schemes.append(kingmode);
schemes.append(mutant);
schemes.append(construction);
+ schemes.append(specialists);
schemes.append(spaceinvasion);
schemes.append(hedgeeditor);
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/Challenges.png
Binary file QTfrontend/res/Challenges.png has changed
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/Challenges.xcf
Binary file QTfrontend/res/Challenges.xcf has changed
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/Scenarios.png
Binary file QTfrontend/res/Scenarios.png has changed
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/Trainings.png
Binary file QTfrontend/res/Trainings.png has changed
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/Trainings_en.png
Binary file QTfrontend/res/Trainings_en.png has changed
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/credits.csv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/res/credits.csv Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,179 @@
+S,"Project founder",,,
+E,,"Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+S,"Programming",,,
+U,"Game engine",,,
+E,"Creator","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Many engine improvements","Derek Pomery","nemo@m8y.org","nemo"
+E,"Many engine improvements","Carlos Vives","mail@carlosvives.es",
+E,"Many engine improvements","Richard Karolyi","sheepluva@ercatec.net","sheepluva"
+E,,,"Wuzzy2@mail.ru","Wuzzy"
+E,,"Henrik Rostedt","henrik.rostedt@gmail.com",
+E,"Gamepad and Lua integration","Mario Liebisch","mario.liebisch@gmail.com",
+E,"Campaign support","Szabolcs Orbàn","szabibibi@gmail.com",
+E,"Theme customization improvements",,,"KoBeWi"
+E,"Some Pas2C and GLES2 work","Meng Xiangyun","xymengxy@gmail.com",
+E,"Video recording","Stepan Podoskin","stepik-777@mail.ru",
+E,"Other improvements","Valentin Kraevskiy",,"alfadur"
+U,"Map generation",,,
+E,"Core map generators","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Perlin maps and other improvements","Derek Pomery","nemo@m8y.org","nemo"
+E,"Maze maps","Henning Kühn","prg@cooco.de",
+U,"Weapons",,,
+E,"Most core weapons","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Air mine, rubber, others","Derek Pomery","nemo@m8y.org","nemo"
+E,"Drill rocket, ballgun, RC plane","Martin Boze","afffect@gmail.com",
+E,"Freezer","Julia Struchenko","urbertar@gmail.com",
+E,"Mine number and time game settings","David A. Cuadrado","krawek@gmail.com",
+M,,,,
+U,"Frontend / main menu",,,
+E,"Creator","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Many frontend improvements","Derek Pomery","nemo@m8y.org","nemo"
+E,"Many frontend improvements","Richard Karolyi","sheepluva@ercatec.net","sheepluva"
+E,"Many frontend improvements","Igor Ulyanov","disinbox@gmail.com",
+E,"Keybinds, feedback, maps and hats interfaces","Drew Gottlieb","gottlieb.drew@gmail.com",
+E,"Login dialogs, other improvements","Ondrej Skopek","skopekondrej@gmail.com",
+E,,,"Wuzzy2@mail.ru","Wuzzy"
+E,,"Martin Minarik","ttsmj@pokec.sk",
+E,,"Kristian Lehmann","email@thexception.net",
+E,,"Henrik Rostedt","henrik.rostedt@gmail.com",
+E,,"Mayur Pawashe","zorgiepoo@gmail.com",
+E,,"Valentin Kraevskiy",,"alfadur"
+U,"Missions and styles",,,
+E,"A Classic Fairytale","Szabolcs Orbàn","szabibibi@gmail.com",
+E,"A Space Adventure",,,"Master_ex"
+E,"Created Capture the Flag, Construction Mode, Control, HedgeEditor, Highlander, Racer, TechRacer, The Specialists, WxW",,,"mikade"
+E,"Training, time-trial and target practice challenges, Bazooka Battlefield, Tentacle Terror, Big Armory, bugfixes and maintenance",,"Wuzzy2@mail.ru","Wuzzy"
+E,"Some styles and missions","John Lambert","redgrinner@gmail.com","redgrinner"
+E,"Battalion",,"Anachron14@gmx.de","Anachron"
+E,"Continental supplies",,,"Vatten"
+E,"Teamwork 2",,,"Arkhnen"
+E,"Climb Home","Derek Pomery","nemo@m8y.org","nemo"
+E,"Portal Mind Challenge",,,"sphrix"
+M,,,,
+U,"Game server",,,
+E,"Creator","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+M,,,,
+U,"Ports",,,
+E,"macOS/iPhone port, OpenGL-ES conversion","Vittorio Giovara","vittorio.giovara@gmail.com",
+E,"Android port","Richard Deurwaarder","xeli@xelification.com","xeli"
+E,"Android netplay, portability abstraction","Simeon Maxein","smaxein@googlemail.com",
+E,"WebGL port","Meng Xiangyun","xymengxy@gmail.com",
+E,"iPhone/iPad ports","Anton Malmygina","antonc27@mail.ru",
+S,"Graphics",,,
+U,"General",,,
+E,,"John Dum","fizzy@gmail.com",
+E,,"Joshua Frese","joshfrese@gmail.com",
+E,,"Stanko Tadić","stanko@mfhinc.net",
+E,,"Julien Koesten","julienkoesten@aol.com",
+E,,"Joshua O'Sullivan","coheedftw@hotmail.co.uk",
+E,,"Nils Lück","nils.luck.design@gmail.com",
+E,,"Guillaume Englert","genglert@hybird.org",
+E,,,"ppicondo.cvac@gmail.com","CopherNeue"
+E,,"Valentin Kraevskiy",,"alfadur"
+E,,"Carlos Vives","mail@carlosvives.es",
+U,"Themes",,,
+E,"Nature, Snow, City, Castle, Halloween, Island","John Dum","fizzy@gmail.com",
+E,"Bamboo, EarthRise, BambooPlinko","Joshua Frese","joshfrese@gmail.com",
+E,"Golf, Hoggywood, Stage",,,"RoFra"
+E,"Hoggywood",,"Wuzzy2@mail.ru","Wuzzy"
+E,"Cave, Olympics","Guillaume Englert","genglert@hybird.org",
+E,"Fruit, Cake","Randy Broda",,"Randy"
+E,"Art",,,"Zippy"
+E,"Beach",,"ppicondo.cvac@gmail.com","CopherNeue"
+E,"Beach",,,"Miguelac"
+E,"Brick",,,"AlexYeCu"
+E,"Hell","Stanko Tadić","stanko@mfhinc.net",
+E,"Jungle",,,"KoRn666"
+E,"Sheep","Julien Koesten","julienkoesten@aol.com",
+M,,,,
+U,"Maps",,,
+E,"Basketball, BasketballField, Bath, Bubbleflow, Hammock, Hedgelove, Hedgewars, Hydrant, Mushrooms, Plane, Ropes, Tree","John Dum","fizzy@gmail.com",
+E,"SB_Bones, SB_Crystal, SB_Grassy, SB_Grove, SB_Haunty, SB_Oaks, SB_Shrooms, SB_Tentacle","Chucklefish, Ltd",,
+E,"Bamboo, Blox, Cake, Cogs, EarthRise, Freeway","Joshua Frese","joshfrese@gmail.com",
+E,"Castle, PirateFlag","Stanko Tadić","stanko@mfhinc.net",
+E,"ShoppaKing, TrophyRace",,,"wolfmarc & Dragonfly"
+E,"Battlefield",,,"nickstu"
+E,"CTF_Blizzard",,,"Palewolf"
+E,"Cheese",,"ppicondo.cvac@gmail.com","CopherNeue"
+E,"ClimbHome","Derek Pomery","nemo@m8y.org","nemo"
+E,"Lonely_Island","Maciej Mrozinski","mynick2@o2.pl","alzen"
+E,"Octorama",,,"jessor"
+E,"portal",,,"sphrix"
+E,"Ruler","Guillaume Englert","genglert@hybrid.org",
+E,"Sticks",,,"dctPL"
+M,,,,
+U,"Forts",,,
+E,"EvilChicken",,,"Dragonfly"
+E,"Lonely_Island","Maciej Mrozinski","mynick2@o2.pl","alzen"
+E,"Olympic","Guillaume Englert","genglert@hybird.org",
+E,"Olympic",,"Wuzzy2@mail.ru","Wuzzy"
+E,"Tank","Carlos Vives","mail@carlosvives.es",
+E,"Snail","John Dum","fizzy@gmail.com",
+E,"Snail",,"Wuzzy2@mail.ru","Wuzzy"
+E,"SteelTower","Randy Broda",,"Randy"
+M,,,,
+U,"Hats, graves, other",,,
+E,"See CREDITS text file",,,
+S,"Sounds",,,
+E,"Hedgehogs voice","Stephen Alexander","ArmagonNo1@gmail.com","Armagon"
+E,"Default_pl, Russian_pl voices",,"mtg90pl@gmail.com","mtg90pl"
+E,,"John Dum","fizzy@gmail.com",
+E,,"Jonatan Nilsson","jonatanfan@gmail.com",
+E,,"Daniel Martin","elhombresinremedio@gmail.com","HSR"
+E,"Various authors from www.freesound.org (see CREDITS text file)",,
+S,"Music",,,
+E,"City, Rock, others","Daniel Martin","elhombresinremedio@gmail.com","HSR"
+E,"Compost",,,"HG"
+E,"EarthRise, oriental, Pirate, snow","Jonatan Nilsson","jonatanfan@gmail.com",
+E,"Fruit, Jungle","Valentin Kraevskiy",,"alfadur"
+E,"Nature","John Dum","fizzy@gmail.com",
+E,"olympics_sd",,,"yd <http://opengameart.org/users/yd>"
+E,"sdmusic (Hitman [sheepluva edit])","Kevin MacLeod",,
+M,,,,
+S,"Translations",,,
+E,"Brazilian Portuguese","Romulo Fernandes Machado","abra185@gmail.com",
+E,"Bulgarian","Svetoslav Stefanov",,
+E,"Czech","Petr Řezáček","rezacek@gmail.com",
+E,"Chinese","Jie Luo","lililjlj@gmail.com",
+E,"Finnish","Nina Kuisma","ninnnu@gmail.com",
+E,"Finnish","Janne Uusitupa",,
+E,"French","Antoine Turmel","geekshadow@gmail.com",
+E,"French","Clement Woitrain","sphrixclement@gmail.com",
+E,"French",,,"Matisumi"
+E,"French",,,"Case_Of"
+E,"German","Peter Hüwe","PeterHuewe@gmx.de",
+E,"German","Mario Liebisch","mario.liebisch@gmail.com",
+E,"German","Richard Karolyi","sheepluva@ercatec.net","sheepluva"
+E,"German",,"Wuzzy2@mail.ru","Wuzzy"
+E,"Greek",,"talos_kriti@yahoo.gr",
+E,"Italian","Luca Bonora","bonora.luca@gmail.com",
+E,"Italian","Marco Bresciani","m.bresciani@email.it",
+E,"Italian","Gianfranco Costamagna","costamagnagianfranco@yahoo.it",
+E,"Italian",,"enricobe@hotmail.com","Enrico"
+E,"Japanese","ADAM Etienne","etienne.adam@gmail.com",
+E,"Japanese","Marco Bresciani","m.bresciani@email.it",
+E,"Korean","Anthony Bellew","anthonyreflected@gmail.com",
+E,"Lithuanian","Lukas Urbonas","lukasu08@gmail.com",
+E,"Polish","Maciej Mroziński","mynick2@o2.pl","alzen"
+E,"Polish","Wojciech Latkowski","magik17l@gmail.com",
+E,"Polish","Piotr Mitana",,
+E,"Polish","Maciej Górny",,
+E,"Polish",,,"KoBeWi"
+E,"Portuguese","Fábio Canário","inufabie@gmail.com",
+E,"Russian","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Russian","Vitaly Novichkov","admin@wohlnet.ru",
+E,"Russian","Anton Malmygina","antonc27@mail.ru",
+E,"Russian","Grigory Ustinov","grenka@altlinux.org","grenka"
+E,"Scottish Gaelic",,,"GunChleoc"
+E,"Slovak","Jose Riha",,
+E,"Spanish","Carlos Vives","mail@carlosvives.es",
+E,"Swedish","Niklas Grahn","raewolusjoon@yaoo.com",
+E,"Swedish","Henrik Rostedt","henrik.rostedt@gmail.com",
+E,"Ukrainian","Eugene V. Lyubimkin","jackyf.devel@gmail.com",
+E,"Ukrainian","Igor Paliychuk","mansonigor@gmail.com",
+E,"Ukrainian","Eugene Sakara","eresid@gmail.com",
+S,"Special thanks",,,
+E,,"Aleksey Andreev","blaknayabr@gmail.com",,
+E,,"Aleksander Rudalev","alexv@pomorsu.ru",,
+E,,"Natasha Korotaeva","layout@pisem.net",,
+E,,"Adam Higerd",,"ahigerd"
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/css/april1.css
--- a/QTfrontend/res/css/april1.css Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/res/css/april1.css Mon Jan 14 12:35:32 2019 -0500
@@ -442,7 +442,7 @@
background-color: #150A61;
}
-QDialogButtonBox QPushButton {
+.QPushButton, .QPushButtonWithSound {
padding: 3px 5px;
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/css/birthday.css
--- a/QTfrontend/res/css/birthday.css Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/res/css/birthday.css Mon Jan 14 12:35:32 2019 -0500
@@ -446,7 +446,7 @@
background-color: #150A61;
}
-QDialogButtonBox QPushButton {
+.QPushButton, .QPushButtonWithSound {
padding: 3px 5px;
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/css/christmas.css
--- a/QTfrontend/res/css/christmas.css Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/res/css/christmas.css Mon Jan 14 12:35:32 2019 -0500
@@ -441,7 +441,7 @@
background-color: #150A61;
}
-QDialogButtonBox QPushButton {
+.QPushButton, .QPushButtonWithSound {
padding: 3px 5px;
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/css/easter.css
--- a/QTfrontend/res/css/easter.css Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/res/css/easter.css Mon Jan 14 12:35:32 2019 -0500
@@ -438,7 +438,7 @@
background-color: #150A61;
}
-QDialogButtonBox QPushButton {
+.QPushButton, .QPushButtonWithSound {
padding: 3px 5px;
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/css/qt.css
--- a/QTfrontend/res/css/qt.css Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/res/css/qt.css Mon Jan 14 12:35:32 2019 -0500
@@ -433,7 +433,7 @@
background-color: #150A61;
}
-QDialogButtonBox QPushButton {
+.QPushButton, .QPushButtonWithSound {
padding: 3px 5px;
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/res/html/about.html
--- a/QTfrontend/res/html/about.html Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-
-
-
-Hedgewars - Authors
-
-
-
-
- Developers:
-
- Engine, frontend, net server: Andrey Korotaev <unC0Rr@gmail.com >
- Many frontend improvements: Igor Ulyanov <disinbox@gmail.com >
- Many engine and frontend improvements: Derek Pomery <nemo@m8y.org >
- Drill rocket, Ballgun, RC Plane weapons: Martin Boze <afffect@gmail.com >
- Mine number and time game settings: David A. Cuadrado <krawek@gmail.com >
- Frontend improvements: Martin Minarik <ttsmj@pokec.sk >
- Frontend improvements: Kristian Lehmann <email@thexception.net >
- Mac OS X/iPhone port, OpenGL-ES conversion: Vittorio Giovara <vittorio.giovara@gmail.com >
- Many engine and frontend improvements (and bugs): Richard Karolyi <sheepluva@ercatec.net >
- Gamepad and Lua integration: Mario Liebisch <mario.liebisch@gmail.com >
- Many engine improvements and graphics: Carlos Vives <mail@carlosvives.es >
- Maze maps: Henning Kühn <prg@cooco.de >
- Engine and frontend improvements: Henrik Rostedt <henrik.rostedt@gmail.com >
- Lua game modes and missions: John Lambert <redgrinner@gmail.com >
- Frontend improvements: Mayur Pawashe <zorgiepoo@gmail.com >
- Android port: Richard Deurwaarder <xeli@xelification.com >
- Android netplay, portability abstraction: Simeon Maxein <smaxein@googlemail.com >
- WebGL port, some pas2c and GLES2 work: Meng Xiangyun <xymengxy@gmail.com >
- Video recording: Stepan Podoskin <stepik-777@mail.ru >
- Campaign support, first campaign: Szabolcs Orbàn <szabibibi@gmail.com >
- Keybinds, feedback, maps and hats interfaces: Drew Gottlieb <gottlieb.drew@gmail.com >
- Login dialogs, frontend improvements: Ondrej Skopek <skopekondrej@gmail.com >
- Icegun weapon: Julia Struchenko <urbertar@gmail.com >
- iPhone/iPad ports: Anton Malmygin <antonc27@mail.ru >
- Battalion style: Anachron <Anachron14@gmx.de >
- Scripting, engine, frontend improvements, some missions: Wuzzy <Wuzzy2@mail.ru >
- Theme customization improvements: KoBeWi
- Theme music, engine and frontend improvements, graphics: Valentin Kraevskiy
-
-
- Art:
- John Dum <fizzy@gmail.com >
-
- Joshua Frese <joshfrese@gmail.com >
-
- Stanko Tadić <stanko@mfhinc.net >
-
- Julien Koesten <julienkoesten@aol.com >
-
- Joshua O'Sullivan <coheedftw@hotmail.co.uk >
-
- Nils Lück <nils.luck.design@gmail.com >
-
- Guillaume Englert <genglert@hybird.org >
-
-
CopherNeue <ppicondo.cvac@gmail.com >
-
- Hats: Trey Perry <tx.perry.j@gmail.com >
-
-
- Sounds:
-
- Hedgehogs voice: Stephen Alexander <ArmagonNo1@gmail.com >
-
- John Dum <fizzy@gmail.com >
-
- Jonatan Nilsson <jonatanfan@gmail.com >
-
- Daniel Martin <elhombresinremedio@gmail.com >
-
-
- Translations:
- Brazilian Portuguese: Romulo Fernandes Machado <abra185@gmail.com >
- Bulgarian: Svetoslav Stefanov
- Czech: Petr Řezáček <rezacek@gmail.com >
- Chinese: Jie Luo <lililjlj@gmail.com >
- English: Andrey Korotaev <unC0Rr@gmail.com >
- Finnish: Nina Kuisma <ninnnu@gmail.com >, Janne Uusitupa
- French: Antoine Turmel <geekshadow@gmail.com >, Clement Woitrain <sphrixclement@gmail.com >, Matisumi, Case_Of
- German: Peter Hüwe <PeterHuewe@gmx.de >, Mario Liebisch <mario.liebisch@gmail.com >, Richard Karolyi <sheepluva@ercatec.net >, Wuzzy <Wuzzy2@mail.ru >
- Greek: <talos_kriti@yahoo.gr >
- Italian: Luca Bonora <bonora.luca@gmail.com >, Marco Bresciani <m.bresciani@email.it >, Gianfranco Costamagna <costamagnagianfranco@yahoo.it >, Enrico <enricobe@hotmail.com >
- Japanese: ADAM Etienne <etienne.adam@gmail.com >, Marco Bresciani <m.bresciani@email.it >
- Korean: Anthony Bellew <anthonyreflected@gmail.com >
- Lithuanian: Lukas Urbonas <lukasu08@gmail.com >
- Polish: Maciej Mroziński <mynick2@o2.pl >, Wojciech Latkowski <magik17l@gmail.com >, Piotr Mitana, Maciej Górny, KoBeWi
- Portuguese: Fábio Canário <inufabie@gmail.com >
- Russian: Andrey Korotaev <unC0Rr@gmail.com >, Vitaly Novichkov <admin@wohlnet.ru >, Anton Malmygin <antonc27@mail.ru >, greno4ka, Grigory Ustinov <grenka@altlinux.org >
- Scottish Gaelic: GunChleoc
- Slovak: Jose Riha
- Spanish: Carlos Vives <mail@carlosvives.es >
- Swedish: Niklas Grahn <raewolusjoon@yaoo.com >, Henrik Rostedt <henrik.rostedt@gmail.com >
- Ukrainian: Eugene V. Lyubimkin <jackyf.devel@gmail.com >, Igor Paliychuk <mansonigor@gmail.com >, Eugene Sakara <eresid@gmail.com >
-
-
- Special thanks:
- Aleksey Andreev <blaknayabr@gmail.com >
- Aleksander Rudalev <alexv@pomorsu.ru >
- Natasha Korotaeva <layout@pisem.net >
- Adam Higerd (aka ahigerd at FreeNode)
-
-
-
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/sdlkeys.cpp
--- a/QTfrontend/sdlkeys.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/sdlkeys.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -197,3 +197,6 @@
char controllerleft[128] = QT_TRANSLATE_NOOP("binds (keys)", "Left");
char controllerright[128] = QT_TRANSLATE_NOOP("binds (keys)", "Right");
+//: When a control has no key binding
+char unboundcontrol[128] = QT_TRANSLATE_NOOP("binds (keys)", "(Unused)");
+
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/sdlkeys.h
--- a/QTfrontend/sdlkeys.h Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/sdlkeys.h Mon Jan 14 12:35:32 2019 -0500
@@ -30,5 +30,6 @@
extern char controllerdown[128];
extern char controllerleft[128];
extern char controllerright[128];
+extern char unboundcontrol[128];
#endif
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/team.cpp
--- a/QTfrontend/team.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/team.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -35,6 +35,7 @@
, m_difficulty(0)
, m_numHedgehogs(4)
, m_isNetTeam(false)
+ , m_isMissionTeam(false)
{
m_name = teamname;
OldTeamName = m_name;
@@ -61,6 +62,7 @@
QObject(0)
, m_numHedgehogs(4)
, m_isNetTeam(true)
+ , m_isMissionTeam(false)
{
// net teams are configured from QStringList
if(strLst.size() != 23) throw HWTeamConstructException();
@@ -88,6 +90,7 @@
, m_difficulty(0)
, m_numHedgehogs(4)
, m_isNetTeam(false)
+ , m_isMissionTeam(false)
{
m_name = QString("Team");
for (int i = 0; i < HEDGEHOGS_PER_TEAM; i++)
@@ -125,6 +128,7 @@
, m_numHedgehogs(other.m_numHedgehogs)
, m_color(other.m_color)
, m_isNetTeam(other.m_isNetTeam)
+ , m_isMissionTeam(other.m_isMissionTeam)
, m_owner(other.m_owner)
// , AchievementProgress(other.AchievementProgress)
{
@@ -149,6 +153,7 @@
m_isNetTeam = other.m_isNetTeam;
m_owner = other.m_owner;
m_color = other.m_color;
+ m_isMissionTeam = other.m_isMissionTeam;
}
return *this;
@@ -243,12 +248,25 @@
QStringList HWTeam::teamGameConfig(quint32 InitHealth) const
{
QStringList sl;
+ QString cmdAddHog = "eaddhh";
+
if (m_isNetTeam)
{
sl.push_back(QString("eaddteam %3 %1 %2").arg(qcolor().rgb() & 0xffffff).arg(m_name).arg(QString(QCryptographicHash::hash(m_owner.toUtf8(), QCryptographicHash::Md5).toHex())));
sl.push_back("erdriven");
}
- else sl.push_back(QString("eaddteam %3 %1 %2").arg(qcolor().rgb() & 0xffffff).arg(m_name).arg(playerHash));
+ else
+ {
+ if (m_isMissionTeam)
+ {
+ sl.push_back(QString("esetmissteam %3 %1 %2").arg(qcolor().rgb() & 0xffffff).arg(m_name).arg(playerHash));
+ cmdAddHog = "eaddmisshh";
+ }
+ else
+ {
+ sl.push_back(QString("eaddteam %3 %1 %2").arg(qcolor().rgb() & 0xffffff).arg(m_name).arg(playerHash));
+ }
+ }
sl.push_back(QString("egrave " + m_grave));
sl.push_back(QString("efort " + m_fort));
@@ -260,7 +278,7 @@
for (int t = 0; t < m_numHedgehogs; t++)
{
- sl.push_back(QString("eaddhh %1 %2 %3")
+ sl.push_back(QString(cmdAddHog + " %1 %2 %3")
.arg(QString::number(m_difficulty),
QString::number(InitHealth),
m_hedgehogs[t].Name));
@@ -281,6 +299,15 @@
return m_isNetTeam;
}
+void HWTeam::setMissionTeam(bool isMissionTeam)
+{
+ m_isMissionTeam = isMissionTeam;
+}
+
+bool HWTeam::isMissionTeam() const
+{
+ return m_isMissionTeam;
+}
bool HWTeam::operator==(const HWTeam& t1) const
{
@@ -322,6 +349,10 @@
return m_owner;
}
+void HWTeam::setOwner(const QString & owner)
+{
+ m_owner = owner;
+}
// difficulty
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/team.h
--- a/QTfrontend/team.h Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/team.h Mon Jan 14 12:35:32 2019 -0500
@@ -72,10 +72,12 @@
QString grave() const;
const HWHog & hedgehog(unsigned int idx) const;
bool isNetTeam() const;
+ bool isMissionTeam() const;
QString keyBind(unsigned int idx) const;
QString name() const;
unsigned char numHedgehogs() const;
QString owner() const;
+ void setOwner(const QString & owner);
QString voicepack() const;
// attribute setters
@@ -89,6 +91,7 @@
void setNumHedgehogs(unsigned char num);
void setVoicepack(const QString & voicepack);
void setNetTeam(bool isNetTeam);
+ void setMissionTeam(bool isMissionTeam);
// convert team info into strings for further computation
QStringList teamGameConfig(quint32 InitHealth) const;
@@ -119,6 +122,7 @@
quint8 m_numHedgehogs;
int m_color;
bool m_isNetTeam;
+ bool m_isMissionTeam;
QString m_owner;
// class members that contain statistics, etc.
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/page/pagegamestats.cpp
--- a/QTfrontend/ui/page/pagegamestats.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/page/pagegamestats.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -320,7 +320,8 @@
i = playerinfo.indexOf(' ');
- int kills = playerinfo.left(i).toInt();
+ QString killsString = playerinfo.left(i);
+ int kills = killsString.toInt();
QString playername = playerinfo.mid(i + 1);
QString image;
@@ -351,14 +352,30 @@
QString message;
QString killstring;
- if(kindOfPoints.compare("") == 0) {
+ if(kindOfPoints.isEmpty()) {
//: Number of kills in stats screen, written after the team name
killstring = PageGameStats::tr("(%1 kill)", "", kills).arg(kills);
+ } else if (kindOfPoints == "!POINTS") {
+ //: Number of points in stats screen, written after the team name
+ killstring = PageGameStats::tr("(%1 point(s))", "", kills).arg(kills);
+ } else if (kindOfPoints == "!TIME") {
+ //: Time in seconds
+ killstring = PageGameStats::tr("(%L1 second(s))", "", kills).arg((double) kills/1000, 0, 'g', 3);
+ } else if (kindOfPoints.startsWith("!TIME") && kindOfPoints.length() == 6) {
+ int len = kindOfPoints.at(6).digitValue();
+ if(len != -1)
+ killstring = PageGameStats::tr("(%L1 second(s))", "", kills).arg((double) kills/1000, 0, 'g', len);
+ else
+ qWarning("SendStat: siPointType received with !TIME and invalid number length!");
+ } else if (kindOfPoints == "!CRATES") {
+ killstring = PageGameStats::tr("(%1 crate(s))", "", kills).arg(kills);
+ } else if (kindOfPoints == "!EMPTY") {
+ killstring = QString("");
} else {
//: For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points”
killstring = PageGameStats::tr("(%1 %2)", "", kills).arg(kills).arg(kindOfPoints);
- kindOfPoints = QString("");
}
+ kindOfPoints = QString("");
message = QString("
%1 %2. %3 ").arg(image, QString::number(realPlayerPosition), playername, clanColor.name()) + killstring + "
";
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/page/pagenet.cpp
--- a/QTfrontend/ui/page/pagenet.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/page/pagenet.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -47,6 +47,7 @@
BtnNetConnect = new QPushButton(ConnGroupBox);
BtnNetConnect->setFont(*font14);
BtnNetConnect->setText(QPushButton::tr("Connect"));
+ BtnNetConnect->setWhatsThis(tr("Connect to the selected server"));
GBClayout->addWidget(BtnNetConnect, 2, 2);
tvServersList = new QTableView(ConnGroupBox);
@@ -56,11 +57,13 @@
BtnUpdateSList = new QPushButton(ConnGroupBox);
BtnUpdateSList->setFont(*font14);
BtnUpdateSList->setText(QPushButton::tr("Update"));
+ BtnUpdateSList->setWhatsThis(tr("Update the list of servers"));
GBClayout->addWidget(BtnUpdateSList, 2, 0);
BtnSpecifyServer = new QPushButton(ConnGroupBox);
BtnSpecifyServer->setFont(*font14);
- BtnSpecifyServer->setText(QPushButton::tr("Specify"));
+ BtnSpecifyServer->setText(QPushButton::tr("Specify address"));
+ BtnSpecifyServer->setWhatsThis(tr("Specify the address and port number of a known server and connect to it directly"));
GBClayout->addWidget(BtnSpecifyServer, 2, 1);
return pageLayout;
@@ -71,6 +74,7 @@
QHBoxLayout * footerLayout = new QHBoxLayout();
BtnNetSvrStart = formattedButton(QPushButton::tr("Start server"));
+ BtnNetSvrStart->setWhatsThis(tr("Start private server"));
BtnNetSvrStart->setMinimumSize(180, 50);
QString serverPath = bindir->absolutePath() + "/hedgewars-server";
#ifdef Q_OS_WIN
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/page/pageoptions.cpp
--- a/QTfrontend/ui/page/pageoptions.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/page/pageoptions.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -142,6 +142,7 @@
BtnNewTeam->setIconSize(pmNew.size());
BtnNewTeam->setIcon(pmNew);
BtnNewTeam->setMaximumWidth(pmNew.width() + 6);
+ BtnNewTeam->setStyleSheet("padding: 0px;");
connect(BtnNewTeam, SIGNAL(clicked()), this, SIGNAL(newTeamRequested()));
groupTeams->layout()->addWidget(BtnNewTeam, 0, 1);
@@ -150,6 +151,7 @@
BtnEditTeam->setIconSize(pmEdit.size());
BtnEditTeam->setIcon(pmEdit);
BtnEditTeam->setMaximumWidth(pmEdit.width() + 6);
+ BtnEditTeam->setStyleSheet("padding: 0px;");
connect(BtnEditTeam, SIGNAL(clicked()), this, SLOT(requestEditSelectedTeam()));
groupTeams->layout()->addWidget(BtnEditTeam, 0, 2);
@@ -158,6 +160,7 @@
BtnDeleteTeam->setIconSize(pmDelete.size());
BtnDeleteTeam->setIcon(pmDelete);
BtnDeleteTeam->setMaximumWidth(pmDelete.width() + 6);
+ BtnDeleteTeam->setStyleSheet("padding: 0px;");
connect(BtnDeleteTeam, SIGNAL(clicked()), this, SLOT(requestDeleteSelectedTeam()));
groupTeams->layout()->addWidget(BtnDeleteTeam, 0, 3);
@@ -184,6 +187,7 @@
SchemeNew->setIconSize(pmNew.size());
SchemeNew->setIcon(pmNew);
SchemeNew->setMaximumWidth(pmNew.width() + 6);
+ SchemeNew->setStyleSheet("padding: 0px;");
groupSchemes->layout()->addWidget(SchemeNew, 0, 1);
SchemeEdit = new QPushButton(groupSchemes);
@@ -191,6 +195,7 @@
SchemeEdit->setIconSize(pmEdit.size());
SchemeEdit->setIcon(pmEdit);
SchemeEdit->setMaximumWidth(pmEdit.width() + 6);
+ SchemeEdit->setStyleSheet("padding: 0px;");
groupSchemes->layout()->addWidget(SchemeEdit, 0, 2);
SchemeDelete = new QPushButton(groupSchemes);
@@ -198,6 +203,7 @@
SchemeDelete->setIconSize(pmDelete.size());
SchemeDelete->setIcon(pmDelete);
SchemeDelete->setMaximumWidth(pmDelete.width() + 6);
+ SchemeDelete->setStyleSheet("padding: 0px;");
groupSchemes->layout()->addWidget(SchemeDelete, 0, 3);
}
@@ -217,6 +223,7 @@
WeaponNew->setIconSize(pmNew.size());
WeaponNew->setIcon(pmNew);
WeaponNew->setMaximumWidth(pmNew.width() + 6);
+ WeaponNew->setStyleSheet("padding: 0px;");
groupWeapons->layout()->addWidget(WeaponNew, 0, 1);
WeaponEdit = new QPushButton(groupWeapons);
@@ -224,6 +231,7 @@
WeaponEdit->setIconSize(pmEdit.size());
WeaponEdit->setIcon(pmEdit);
WeaponEdit->setMaximumWidth(pmEdit.width() + 6);
+ WeaponEdit->setStyleSheet("padding: 0px;");
groupWeapons->layout()->addWidget(WeaponEdit, 0, 2);
WeaponDelete = new QPushButton(groupWeapons);
@@ -231,6 +239,7 @@
WeaponDelete->setIconSize(pmDelete.size());
WeaponDelete->setIcon(pmDelete);
WeaponDelete->setMaximumWidth(pmDelete.width() + 6);
+ WeaponDelete->setStyleSheet("padding: 0px;");
groupWeapons->layout()->addWidget(WeaponDelete, 0, 3);
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/page/pagescheme.cpp
--- a/QTfrontend/ui/page/pagescheme.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/page/pagescheme.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -533,8 +533,11 @@
bottomLayout->addWidget(selectScheme, 0);
BtnCopy = addButton(tr("Copy"), bottomLayout, 1);
+ BtnCopy->setStyleSheet("padding: 5px;");
BtnNew = addButton(tr("New"), bottomLayout, 2);
+ BtnNew->setStyleSheet("padding: 5px;");
BtnDelete = addButton(tr("Delete"), bottomLayout, 3);
+ BtnDelete->setStyleSheet("padding: 5px;");
bottomLayout->setStretch(1,1);
bottomLayout->setStretch(2,1);
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/page/pageselectweapon.cpp
--- a/QTfrontend/ui/page/pageselectweapon.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/page/pageselectweapon.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -46,11 +46,15 @@
// first row
BtnNew = addButton(tr("New"), bottomLayout, 0, 1);
+ BtnNew->setStyleSheet("padding: 3px;");
BtnDefault = addButton(tr("Default"), bottomLayout, 0, 2);
+ BtnDefault->setStyleSheet("padding: 3px;");
// second row
BtnCopy = addButton(tr("Copy"), bottomLayout, 1, 1);
+ BtnCopy->setStyleSheet("padding: 3px;");
BtnDelete = addButton(tr("Delete"), bottomLayout, 1, 2);
+ BtnDelete->setStyleSheet("padding: 3px;");
bottomLayout->setColumnStretch(1,1);
bottomLayout->setColumnStretch(2,1);
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/page/pagesingleplayer.cpp
--- a/QTfrontend/ui/page/pagesingleplayer.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/page/pagesingleplayer.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -48,7 +48,7 @@
BtnCampaignPage->setVisible(true);
BtnTrainPage = addButton(":/res/Trainings.png", middleLine, 1, true);
- BtnTrainPage->setWhatsThis(tr("Practice your skills in a range of training missions"));
+ BtnTrainPage->setWhatsThis(tr("Singleplayer missions: Learn how to play in the training, practice your skills in challenges or try to complete goals in scenarios."));
return vLayout;
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/page/pagetraining.cpp
--- a/QTfrontend/ui/page/pagetraining.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/page/pagetraining.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -28,6 +28,7 @@
#include
#include
+#include "mission.h"
#include "hwconsts.h"
#include "DataManager.h"
@@ -37,29 +38,32 @@
{
QGridLayout * pageLayout = new QGridLayout();
-// left column
-
// declare start button, caption and description
btnPreview = formattedButton(":/res/Trainings.png", true);
- // make both rows equal height
+ // tweak widget spacing
pageLayout->setRowStretch(0, 1);
pageLayout->setRowStretch(1, 1);
+ pageLayout->setRowStretch(2, 1);
+ pageLayout->setColumnStretch(0, 5);
+ pageLayout->setColumnStretch(1, 1);
+ pageLayout->setColumnStretch(2, 9);
+ pageLayout->setColumnStretch(3, 5);
- // add start button, caption and description to 3 different rows
- pageLayout->addWidget(btnPreview, 0, 0);
+ QWidget * infoWidget = new QWidget();
+ QHBoxLayout * infoLayout = new QHBoxLayout();
+ // add preview, caption and description
+ infoWidget->setLayout(infoLayout);
+ infoLayout->addWidget(btnPreview);
// center preview
- pageLayout->setAlignment(btnPreview, Qt::AlignRight | Qt::AlignVCenter);
-
-
-// right column
+ infoLayout->setAlignment(btnPreview, Qt::AlignRight | Qt::AlignVCenter);
// info area (caption on top, description below)
- QWidget * infoWidget = new QWidget();
- QVBoxLayout * infoLayout = new QVBoxLayout();
- infoWidget->setObjectName("trainingInfo");
- infoWidget->setLayout(infoLayout);
+ QWidget * infoTextWidget = new QWidget();
+ QVBoxLayout * infoTextLayout = new QVBoxLayout();
+ infoTextWidget->setObjectName("trainingInfo");
+ infoTextWidget->setLayout(infoTextLayout);
lblCaption = new QLabel();
lblCaption->setMinimumWidth(360);
@@ -69,21 +73,27 @@
lblDescription->setMinimumWidth(360);
lblDescription->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
lblDescription->setWordWrap(true);
+ lblHighscores = new QLabel();
+ lblHighscores->setMinimumWidth(360);
+ lblHighscores->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
- infoLayout->addWidget(lblCaption);
- infoLayout->addWidget(lblDescription);
+ infoTextLayout->addWidget(lblCaption);
+ infoTextLayout->addWidget(lblDescription);
+ infoTextLayout->addWidget(lblHighscores);
- pageLayout->addWidget(infoWidget, 0, 1);
- pageLayout->setAlignment(infoWidget, Qt::AlignLeft);
+ infoLayout->addWidget(infoTextWidget);
+
+ pageLayout->addWidget(infoWidget, 0, 1, 1, 2); // span 2 columns
+ pageLayout->setAlignment(infoTextWidget, Qt::AlignLeft);
// tab widget containing all lists
tbw = new QTabWidget(this);
- pageLayout->addWidget(tbw, 1, 0, 1, 2); // span 2 columns
+ pageLayout->addWidget(tbw, 1, 0, 1, 4); // span 4 columns
// let's not make the tab widget use more space than needed
tbw->setFixedWidth(400);
pageLayout->setAlignment(tbw, Qt::AlignHCenter);
-
+
tbw->setStyleSheet("QListWidget { border-style: none; padding-top: 6px; }");
// training/challenge/scenario lists
@@ -101,6 +111,12 @@
tbw->addTab(lstScenarios, tr("Scenarios"));
tbw->setCurrentWidget(lstTrainings);
+ QLabel* lblteam = new QLabel(tr("Team"));
+ CBTeam = new QComboBox(this);
+ CBTeam->setMaxVisibleItems(30);
+ pageLayout->addWidget(lblteam, 2, 1);
+ pageLayout->addWidget(CBTeam, 2, 2);
+
return pageLayout;
}
@@ -200,6 +216,7 @@
// first, load scripts in order specified in order.cfg (if present)
QFile orderFile(QString("physfs://Missions/%1/order.cfg").arg(subFolder));
QStringList orderedMissions;
+
if (orderFile.open(QFile::ReadOnly))
{
QString m_id;
@@ -302,36 +319,65 @@
list = (QListWidget*) tbw->currentWidget();
if (list->currentItem())
{
+ QString missionName = list->currentItem()->data(Qt::UserRole).toString();
QString thumbFile = "physfs://Graphics/Missions/" +
subFolder + "/" +
- list->currentItem()->data(Qt::UserRole).toString() +
+ missionName +
"@2x.png";
if (QFile::exists(thumbFile))
btnPreview->setIcon(QIcon(thumbFile));
+ else if (tbw->currentWidget() == lstChallenges)
+ btnPreview->setIcon(QIcon(":/res/Challenges.png"));
+ else if (tbw->currentWidget() == lstScenarios)
+ // TODO: Prettier scenario fallback image
+ btnPreview->setIcon(QIcon(":/res/Scenarios.png"));
else
btnPreview->setIcon(QIcon(":/res/Trainings.png"));
btnPreview->setWhatsThis(tr("Start fighting"));
- QString realName = list->currentItem()->data(
- Qt::UserRole).toString();
-
- QString caption = m_info->value(realName + ".name",
+ QString caption = m_info->value(missionName + ".name",
list->currentItem()->text()).toString();
- QString description = m_info->value(realName + ".desc",
+ QString description = m_info->value(missionName + ".desc",
tr("No description available")).toString();
lblCaption->setText("" + caption +" ");
lblDescription->setText(description);
+
+ // Challenge highscores
+ QString highscoreText = QString("");
+ QString teamName = CBTeam->currentText();
+ if (missionValueExists(missionName, teamName, "Highscore"))
+ highscoreText = highscoreText +
+ //: Highest score of a team
+ tr("Team highscore: %1")
+ .arg(getMissionValue(missionName, teamName, "Highscore").toString()) + "\n";
+ if (missionValueExists(missionName, teamName, "Lowscore"))
+ highscoreText = highscoreText +
+ //: Lowest score of a team
+ tr("Team lowscore: %1")
+ .arg(getMissionValue(missionName, teamName, "Lowscore").toString()) + "\n";
+ if (missionValueExists(missionName, teamName, "TimeRecord"))
+ {
+ double time = ((double) getMissionValue(missionName, teamName, "TimeRecord").toInt()) / 1000.0;
+ highscoreText = highscoreText + tr("Team's best time: %L1 s").arg(time, 0, 'f', 3) + "\n";
+ }
+ if (missionValueExists(missionName, teamName, "TimeRecordHigh"))
+ {
+ double time = ((double) getMissionValue(missionName, teamName, "TimeRecordHigh").toInt()) / 1000.0;
+ highscoreText = highscoreText + tr("Team's longest time: %L1 s").arg(time, 0, 'f', 3) + "\n";
+ }
+
+ lblHighscores->setText(highscoreText);
}
else
{
btnPreview->setIcon(QIcon(":/res/Trainings.png"));
lblCaption->setText(tr("Select a mission!"));
- // TODO better text and tr()
lblDescription->setText("");
+ lblHighscores->setText("");
}
}
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/page/pagetraining.h
--- a/QTfrontend/ui/page/pagetraining.h Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/page/pagetraining.h Mon Jan 14 12:35:32 2019 -0500
@@ -27,7 +27,13 @@
public:
PageTraining(QWidget* parent = 0);
+ QListWidget * lstTrainings;
+ QListWidget * lstChallenges;
+ QListWidget * lstScenarios;
+ QComboBox * CBTeam;
+ public slots:
+ void updateInfo();
signals:
void startMission(const QString & scriptName, const QString & subFolder);
@@ -44,17 +50,14 @@
QPushButton * btnStart;
QLabel * lblCaption;
QLabel * lblDescription;
+ QLabel * lblHighscores;
QTabWidget * tbw;
- QListWidget * lstTrainings;
- QListWidget * lstChallenges;
- QListWidget * lstScenarios;
QSettings * m_info;
QString getSubFolderOfSelected();
private slots:
void startSelected();
- void updateInfo();
};
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/widget/about.cpp
--- a/QTfrontend/ui/widget/about.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/widget/about.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -32,6 +32,8 @@
#include "SDL.h"
#include "SDL_version.h"
#include "physfs.h"
+#include "creditsmessages.h"
+#include "HWApplication.h"
#ifdef VIDEOREC
extern "C"
@@ -53,6 +55,193 @@
#include "about.h"
+QString About::getCreditsHtml()
+{
+ // Open the credits file
+
+ /* *** FILE FORMAT OF CREDITS FILE ***
+ The credits file is an RFC-4180-compliant CSV file with 5 columns.
+ The first column (column 1) is always 1 letter long and is the row type.
+ The row type determines the meaning of the other columns.
+
+ The following row types are supported:
+
+ * E: Credits entry
+ * Column 2: Task/contribution
+ * Column 3: Contributor name
+ * Column 4: Contributor e-mail
+ * Column 5: Contributor nickname
+ * M: Alternative credits entry that is a placeholder for other or unknown authors
+ * Columns 2-5: Unused
+ * S: Section
+ * Column 2: Section name
+ * Columns 3-5: Unused
+ * U: Subsection
+ * Column 2: Subsection name
+ * Columns 3-5: Unused
+
+ Columns 2, 3 and 5 MUST be in US-ASCII.
+ */
+ QFile creditsFile(":/res/credits.csv");
+ if (!creditsFile.open(QIODevice::ReadOnly))
+ {
+ qWarning("ERROR: Credits file could not be opened!");
+ return "ERROR: Credits file could not be opened!
";
+ }
+ QString creditsString = creditsFile.readAll();
+ QString out = QString("" + tr("Credits") + " \n");
+ QStringList cells = QStringList() << QString("") << QString("") << QString("") << QString("") << QString("");
+ bool firstSection = true;
+ unsigned long int column = 0;
+ unsigned long int charInCell = 0;
+ bool isInQuote = false;
+ bool ignoreChar = false;
+ bool lineComplete = false;
+ QChar currChar;
+ QChar prevChar;
+ for(long long int i = 0; i 0 && prevChar == '"' && (currChar == '\r' || currChar == ','))
+ {
+ isInQuote = false;
+ ignoreChar = true;
+ }
+
+ charInCell++;
+ if(!isInQuote && currChar == ',')
+ {
+ column++;
+ charInCell = 0;
+ }
+ else if(!isInQuote && currChar == '\n' && prevChar == '\r')
+ {
+ lineComplete = true;
+ }
+ if(!isInQuote && (currChar == '\r' || currChar == '\n' || currChar == ','))
+ {
+ ignoreChar = true;
+ }
+
+
+ if(!ignoreChar)
+ {
+ cells[column].append(currChar);
+ }
+ ignoreChar = false;
+
+ if(lineComplete)
+ {
+ type = cells[0];
+ task = cells[1];
+ name = cells[2];
+ mail = cells[3];
+ nick = cells[4];
+
+ if(type == "S")
+ {
+ // section
+ if (!firstSection)
+ out = out + "\n";
+ out = out + "" + HWApplication::translate("credits", task.toLatin1().constData()) + " \n\n";
+ firstSection = false;
+ }
+ else if(type == "U")
+ {
+ // subsection
+ out = out + " \n";
+ out = out + "" + HWApplication::translate("credits", task.toLatin1().constData()) + " \n\n";
+ }
+ else if(type == "M")
+ {
+ // other people
+ out = out + "" + tr("Other people") + " " + "\n";
+ }
+ else if(type == "E")
+ {
+ QString showName = QString("");
+ if(!name.isEmpty())
+ name = ""+name+" ";
+ if(!nick.isEmpty())
+ nick= ""+nick+" ";
+ if(!name.isEmpty() && !nick.isEmpty())
+ showName = tr("%1 (alias %2)").arg(name).arg(nick);
+ else if(name.isEmpty() && !nick.isEmpty())
+ showName = nick;
+ else if(!name.isEmpty() && nick.isEmpty())
+ showName = name;
+ // credits list entry
+ QString mailLink = QString("%1 ").arg(mail);
+ if(task.isEmpty() && mail.isEmpty() && !showName.isEmpty())
+ {
+ // Name only
+ out = out + "" + showName + " \n";
+ }
+ else if(showName.isEmpty() && mail.isEmpty() && !task.isEmpty())
+ {
+ // Task only
+ out = out + "" + HWApplication::translate("credits", task.toLatin1().constData()) + " \n";
+ }
+ else if(task.isEmpty())
+ {
+ // Name and e-mail
+ //: Part of credits. %1: Contributor name. %2: E-mail address
+ out = out + "" + tr("%1 <%2>").arg(showName).arg(mailLink) + " \n";
+ }
+ else if(mail.isEmpty())
+ {
+ // Contribution and name
+ //: Part of credits. %1: Description of contribution. %2: Contributor name
+ out = out + "" + tr("%1: %2")
+ .arg(HWApplication::translate("credits", task.toLatin1().constData()))
+ .arg(showName)
+ + " \n";
+ }
+ else
+ {
+ // Contribution, name and e-mail
+ //: Part of credits. %1: Description of contribution. %2: Contributor name. %3: E-mail address
+ out = out + "" + tr("%1: %2 <%3>")
+ .arg(HWApplication::translate("credits", task.toLatin1().constData()))
+ .arg(showName)
+ .arg(mailLink)
+ + " \n";
+ }
+ }
+ else
+ {
+ qWarning("Invalid row type in credits.csv: %s", qPrintable(type));
+ }
+ lineComplete = false;
+ column = 0;
+ cells[0] = "";
+ cells[1] = "";
+ cells[2] = "";
+ cells[3] = "";
+ cells[4] = "";
+ charInCell = 0;
+ }
+
+ prevChar = currChar;
+ }
+ creditsFile.close();
+ out = out + " ";
+ return out;
+}
+
About::About(QWidget * parent) :
QWidget(parent)
{
@@ -89,11 +278,35 @@
lbl1->setWordWrap(true);
mainLayout->addWidget(lbl1, 0, 1);
- lbl2 = new QTextBrowser(this);
- lbl2->setOpenExternalLinks(true);
- QUrl localpage = QUrl::fromLocalFile(":/res/html/about.html");
- lbl2->setSource(localpage); //sets the source of the label from the file above
- mainLayout->addWidget(lbl2, 1, 1);
+ /* Credits */
+ creditsBrowser = new QTextBrowser(this);
+ creditsBrowser->setOpenExternalLinks(true);
+ QString credits = getCreditsHtml();
+
+ QString header =
+ ""
+ ""
+ ""
+ "Hedgewars Credits "
+ " "
+ ""
+ ""
+ ""
+ "";
+ QString footer =
+ ""
+ "" + tr("Extended Credits") + " "
+ "" + tr("An extended credits list can be found in the CREDITS text file.") + "
"
+ "";
+
+ creditsBrowser->setHtml(header + credits + footer);
+ mainLayout->addWidget(creditsBrowser, 1, 1);
/* Library information */
@@ -102,6 +315,10 @@
#if defined(__GNUC__)
libinfo.append(QString(tr("GCC : %1")).arg(__VERSION__));
+#elif defined(WIN32_VCPKG)
+ libinfo.append(QString(tr("VC++ : %1")).arg(_MSC_FULL_VER));
+#elif defined(__VERSION__)
+ libinfo.append(QString(tr("Unknown Compiler: %1")).arg(__VERSION__));
#else
libinfo.append(QString(tr("Unknown Compiler")));
#endif
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/widget/about.h
--- a/QTfrontend/ui/widget/about.h Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/widget/about.h Mon Jan 14 12:35:32 2019 -0500
@@ -35,7 +35,8 @@
virtual void dropEvent(QDropEvent * event);
private:
- QTextBrowser * lbl2;
+ QString getCreditsHtml();
+ QTextBrowser * creditsBrowser;
};
#endif // _ABOUT_H
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/widget/gamecfgwidget.cpp
--- a/QTfrontend/ui/widget/gamecfgwidget.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/widget/gamecfgwidget.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -150,6 +150,7 @@
goToSchemePage->setIconSize(pmEdit.size());
goToSchemePage->setIcon(iconEdit);
goToSchemePage->setMaximumWidth(pmEdit.width() + 6);
+ goToSchemePage->setStyleSheet("padding: 0px;");
SchemeWidgetLayout->addWidget(goToSchemePage, 0, 3);
connect(goToSchemePage, SIGNAL(clicked()), this, SLOT(jumpToSchemes()));
@@ -172,6 +173,7 @@
goToWeaponPage->setIconSize(pmEdit.size());
goToWeaponPage->setIcon(pmEdit);
goToWeaponPage->setMaximumWidth(pmEdit.width() + 6);
+ goToWeaponPage->setStyleSheet("padding: 0px;");
SchemeWidgetLayout->addWidget(goToWeaponPage, 1, 3);
connect(goToWeaponPage, SIGNAL(clicked()), this, SLOT(jumpToWeapons()));
@@ -336,6 +338,7 @@
bcfg << QString("e$gmflags %1").arg(getGameFlags()).toUtf8();
bcfg << QString("e$damagepct %1").arg(schemeData(26).toInt()).toUtf8();
bcfg << QString("e$turntime %1").arg(schemeData(27).toInt() * 1000).toUtf8();
+ bcfg << QString("e$inithealth %1").arg(schemeData(28).toInt()).toUtf8();
bcfg << QString("e$sd_turns %1").arg(schemeData(29).toInt()).toUtf8();
bcfg << QString("e$casefreq %1").arg(schemeData(30).toInt()).toUtf8();
bcfg << QString("e$minestime %1").arg(schemeData(31).toInt() * 1000).toUtf8();
@@ -545,6 +548,60 @@
}
}
+void GameCFGWidget::updateSchemeEnabledStates(int scriptIndex)
+{
+ QString scheme;
+ QString weapons;
+ if(scriptIndex > 0)
+ {
+ scheme = Scripts->itemData(scriptIndex, GameStyleModel::SchemeRole).toString();
+ weapons = Scripts->itemData(scriptIndex, GameStyleModel::WeaponsRole).toString();
+ }
+ else
+ {
+ scheme = pMapContainer->getCurrentScheme();
+ weapons = pMapContainer->getCurrentWeapons();
+ }
+ if (scheme == "locked")
+ {
+ GameSchemes->setEnabled(false);
+ goToSchemePage->setEnabled(false);
+ lblScheme->setEnabled(false);
+ GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
+ }
+ else if(m_master)
+ {
+ GameSchemes->setEnabled(true);
+ goToSchemePage->setEnabled(true);
+ lblScheme->setEnabled(true);
+ int num = GameSchemes->findText(scheme);
+ if (num != -1)
+ GameSchemes->setCurrentIndex(num);
+ }
+
+ if (weapons == "locked")
+ {
+ WeaponsName->setEnabled(false);
+ goToWeaponPage->setEnabled(false);
+ lblWeapons->setEnabled(false);
+ WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
+ }
+ else if(m_master)
+ {
+ WeaponsName->setEnabled(true);
+ goToWeaponPage->setEnabled(true);
+ lblWeapons->setEnabled(true);
+ int num = WeaponsName->findText(weapons);
+ if (num != -1)
+ WeaponsName->setCurrentIndex(num);
+ }
+
+ if (scheme != "locked" && weapons != "locked")
+ bindEntries->setEnabled(true);
+ else
+ bindEntries->setEnabled(false);
+}
+
void GameCFGWidget::mapChanged(const QString & value)
{
if(isEnabled() && pMapContainer->getCurrentIsMission())
@@ -552,49 +609,7 @@
Scripts->setEnabled(false);
lblScript->setEnabled(false);
Scripts->setCurrentIndex(0);
-
- if (pMapContainer->getCurrentScheme() == "locked")
- {
- GameSchemes->setEnabled(false);
- goToSchemePage->setEnabled(false);
- lblScheme->setEnabled(false);
- GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
- }
- else
- {
- GameSchemes->setEnabled(true);
- goToSchemePage->setEnabled(true);
- lblScheme->setEnabled(true);
- int num = GameSchemes->findText(pMapContainer->getCurrentScheme());
- if (num != -1)
- GameSchemes->setCurrentIndex(num);
- //else
- // GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
- }
-
- if (pMapContainer->getCurrentWeapons() == "locked")
- {
- WeaponsName->setEnabled(false);
- goToWeaponPage->setEnabled(false);
- lblWeapons->setEnabled(false);
- WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
- }
- else
- {
- WeaponsName->setEnabled(true);
- goToWeaponPage->setEnabled(true);
- lblWeapons->setEnabled(true);
- int num = WeaponsName->findText(pMapContainer->getCurrentWeapons());
- if (num != -1)
- WeaponsName->setCurrentIndex(num);
- //else
- // WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
- }
-
- if (pMapContainer->getCurrentScheme() != "locked" && pMapContainer->getCurrentWeapons() != "locked")
- bindEntries->setEnabled(true);
- else
- bindEntries->setEnabled(false);
+ updateSchemeEnabledStates(0);
}
else
{
@@ -671,51 +686,7 @@
if(isEnabled() && index > 0)
{
- QString scheme = Scripts->itemData(index, GameStyleModel::SchemeRole).toString();
- QString weapons = Scripts->itemData(index, GameStyleModel::WeaponsRole).toString();
-
- if (scheme == "locked")
- {
- GameSchemes->setEnabled(false);
- goToSchemePage->setEnabled(false);
- lblScheme->setEnabled(false);
- GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
- }
- else if (m_master)
- {
- GameSchemes->setEnabled(true);
- goToSchemePage->setEnabled(true);
- lblScheme->setEnabled(true);
- int num = GameSchemes->findText(scheme);
- if (num != -1)
- GameSchemes->setCurrentIndex(num);
- //else
- // GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
- }
-
- if (weapons == "locked")
- {
- WeaponsName->setEnabled(false);
- goToWeaponPage->setEnabled(false);
- lblWeapons->setEnabled(false);
- WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
- }
- else if (m_master)
- {
- WeaponsName->setEnabled(true);
- goToWeaponPage->setEnabled(true);
- lblWeapons->setEnabled(true);
- int num = WeaponsName->findText(weapons);
- if (num != -1)
- WeaponsName->setCurrentIndex(num);
- //else
- // WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
- }
-
- if (scheme != "locked" && weapons != "locked")
- bindEntries->setEnabled(true);
- else
- bindEntries->setEnabled(false);
+ updateSchemeEnabledStates(index);
}
else
{
@@ -744,6 +715,8 @@
void GameCFGWidget::mapgenChanged(MapGenerator m)
{
+ int scriptIndex = Scripts->currentIndex();
+ updateSchemeEnabledStates(scriptIndex);
emit paramChanged("MAPGEN", QStringList(QString::number(m)));
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/widget/gamecfgwidget.h
--- a/QTfrontend/ui/widget/gamecfgwidget.h Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/widget/gamecfgwidget.h Mon Jan 14 12:35:32 2019 -0500
@@ -81,6 +81,7 @@
void jumpToSchemes();
void jumpToWeapons();
void mapgenChanged(MapGenerator m);
+ void updateSchemeEnabledStates(int scriptIndex);
void maze_sizeChanged(int s);
void slMapFeatureSizeChanged(int s);
void onDrawnMapChanged(const QByteArray & data);
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/widget/mapContainer.cpp
--- a/QTfrontend/ui/widget/mapContainer.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/widget/mapContainer.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -426,7 +426,7 @@
getDrawnMapData(),
m_script,
m_scriptparam,
- m_mapFeatureSize
+ m_mapFeatureSize
);
setHHLimit(0);
@@ -921,6 +921,7 @@
QString randomNoMapPrev = tr("Click to randomize the theme and seed");
QString mfsComplex = QString(tr("Adjust the complexity of the generated map"));
QString mfsFortsDistance = QString(tr("Adjust the distance between forts"));
+ QString mfsDrawnMap = QString(tr("Scale size of the drawn map"));
switch (type)
{
case MapModel::GeneratedMap:
@@ -938,6 +939,7 @@
case MapModel::HandDrawnMap:
mapPreview->setWhatsThis(tr("Click to edit"));
btnRandomize->setWhatsThis(randomSeed);
+ mapFeatureSize->setWhatsThis(mfsDrawnMap);
break;
case MapModel::FortsMap:
mapPreview->setWhatsThis(randomNoMapPrev);
@@ -990,7 +992,7 @@
mapgen = MAPGEN_DRAWN;
setMapInfo(MapModel::MapInfoDrawn);
btnLoadMap->show();
- mapFeatureSize->hide();
+ //mapFeatureSize->hide();
btnEditMap->show();
break;
case MapModel::MissionMap:
@@ -1075,14 +1077,15 @@
//if (qAbs(m_prevMapFeatureSize-m_mapFeatureSize) > 4)
{
m_prevMapFeatureSize = m_mapFeatureSize;
- updatePreview();
+ if(m_mapInfo.type!= MapModel::HandDrawnMap || m_script.length() > 0)
+ updatePreview();
}
}
// unused because I needed the space for the slider
void HWMapContainer::updateThemeButtonSize()
{
- if (m_mapInfo.type != MapModel::StaticMap && m_mapInfo.type != MapModel::HandDrawnMap)
+ if (m_mapInfo.type != MapModel::StaticMap)
{
btnTheme->setIconSize(QSize(30, 30));
btnTheme->setFixedHeight(30);
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/ui/widget/teamselect.cpp
--- a/QTfrontend/ui/widget/teamselect.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/ui/widget/teamselect.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -215,8 +215,9 @@
// dont playing team => playing
itDontPlay->setColor(framePlaying->getNextColor());
team=*itDontPlay; // for net team info saving in framePlaying (we have only name with netID from network)
- curPlayingTeams.push_back(*itDontPlay);
- if(!m_acceptOuter) emit teamWillPlay(*itDontPlay);
+ team.setOwner(m_curUser);
+ curPlayingTeams.push_back(team);
+ if(!m_acceptOuter) emit teamWillPlay(team);
m_curNotPlayingTeams.erase(itDontPlay);
// Hide team notice if at least two teams.
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/util/DataManager.cpp
--- a/QTfrontend/util/DataManager.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/util/DataManager.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -159,7 +159,9 @@
for(int j = 0; sdlkeys[j][1][0] != '\0'; j++)
{
QStandardItem * item = new QStandardItem();
- item->setData(HWApplication::translate("binds (keys)", sdlkeys[j][1]).contains(": ") ? HWApplication::translate("binds (keys)", sdlkeys[j][1]) : HWApplication::translate("binds (keys)", "Keyboard") + QString(": ") + HWApplication::translate("binds (keys)", sdlkeys[j][1]), Qt::DisplayRole);
+ QString keyId = QString(sdlkeys[j][0]);
+ QString keyTr = HWApplication::translate("binds (keys)", sdlkeys[j][1]);
+ item->setData((keyId == "none" || keyTr.contains(": ")) ? keyTr : HWApplication::translate("binds (keys)", "Keyboard") + QString(": ") + keyTr, Qt::DisplayRole);
item->setData(sdlkeys[j][0], Qt::UserRole + 1);
m_bindsModel->appendRow(item);
}
diff -r 8736f3a0ff7f -r e54e41554529 QTfrontend/util/SDLInteraction.cpp
--- a/QTfrontend/util/SDLInteraction.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/QTfrontend/util/SDLInteraction.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -52,6 +52,13 @@
lastchannel = 0;
if(SDL_NumJoysticks())
addGameControllerKeys();
+
+ int i = 0;
+ while(i < 1024 && sdlkeys[i][1][0] != '\0')
+ i++;
+ sprintf(sdlkeys[i][0], "none");
+ sprintf(sdlkeys[i++][1], "%s", HWApplication::translate("binds (keys)", unboundcontrol).toUtf8().constData());
+
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
m_soundMap = new QMap();
diff -r 8736f3a0ff7f -r e54e41554529 README.md
--- a/README.md Mon Jan 14 12:34:47 2019 -0500
+++ b/README.md Mon Jan 14 12:35:32 2019 -0500
@@ -155,8 +155,8 @@
Licence version 1.2. See the `COPYING` file for the full text of the licenses.
Copyright 2004-2018 Andrey Korotaev and others.
-See `QTfrontend/res/html/about.html` and `CREDITS` for a more complete list of
-authors.
+Click on the Hedgewars logo in the main menu and read the `CREDITS` text file
+for a more complete list of authors.
Contact
-------
diff -r 8736f3a0ff7f -r e54e41554529 gameServer/Actions.hs
--- a/gameServer/Actions.hs Mon Jan 14 12:34:47 2019 -0500
+++ b/gameServer/Actions.hs Mon Jan 14 12:35:32 2019 -0500
@@ -885,3 +885,13 @@
processAction CheckVotes =
checkVotes >>= mapM_ processAction
+
+processAction (ShowRegisteredOnlyState chans) = do
+ si <- gets serverInfo
+ processAction $ AnswerClients chans
+ ["CHAT", nickServer,
+ if isRegisteredUsersOnly si then
+ loc "This server no longer allows unregistered players to join."
+ else
+ loc "This server now allows unregistered players to join."
+ ]
diff -r 8736f3a0ff7f -r e54e41554529 gameServer/CoreTypes.hs
--- a/gameServer/CoreTypes.hs Mon Jan 14 12:34:47 2019 -0500
+++ b/gameServer/CoreTypes.hs Mon Jan 14 12:35:32 2019 -0500
@@ -103,6 +103,7 @@
| ReactCmd [B.ByteString]
| CheckVotes
| SetRandomSeed
+ | ShowRegisteredOnlyState [ClientChan]
data Event = LobbyChatMessage
diff -r 8736f3a0ff7f -r e54e41554529 gameServer/HWProtoCore.hs
--- a/gameServer/HWProtoCore.hs Mon Jan 14 12:34:47 2019 -0500
+++ b/gameServer/HWProtoCore.hs Mon Jan 14 12:35:32 2019 -0500
@@ -107,7 +107,7 @@
-- lobby-only commands
h "STATS" _ = handleCmd_lobbyOnly ["STATS"]
- h "RESTART_SERVER" "YES" = handleCmd_lobbyOnly ["RESTART_SERVER"]
+ h "RESTART_SERVER" p = handleCmd_lobbyOnly ["RESTART_SERVER", upperCase p]
-- room and lobby commands
h "QUIT" _ = handleCmd ["QUIT"]
@@ -120,11 +120,11 @@
h "INFO" n | not $ B.null n = handleCmd ["INFO", n]
h "HELP" _ = handleCmd ["HELP"]
h "REGISTERED_ONLY" _ = serverAdminOnly $ do
- cl <- thisClient
+ rnc <- liftM snd ask
+ let chans = map (sendChan . client rnc) $ allClients rnc
return
[ModifyServerInfo(\s -> s{isRegisteredUsersOnly = not $ isRegisteredUsersOnly s})
- -- TODO: Say whether 'registered only' state is on or off
- , AnswerClients [sendChan cl] ["CHAT", nickServer, loc "'Registered only' state toggled."]
+ , ShowRegisteredOnlyState chans
]
h "SUPER_POWER" _ = serverAdminOnly $ do
cl <- thisClient
diff -r 8736f3a0ff7f -r e54e41554529 gameServer/HWProtoInRoomState.hs
--- a/gameServer/HWProtoInRoomState.hs Mon Jan 14 12:34:47 2019 -0500
+++ b/gameServer/HWProtoInRoomState.hs Mon Jan 14 12:35:32 2019 -0500
@@ -382,7 +382,7 @@
where
-- This is formatted in a way so it can parsed by engine to make it translatable
-- Format: b]
- engineMsg cl = toEngineMsg $ B.concat ["b", nick cl, "]", msg]
+ engineMsg cl = toEngineMsg $ B.concat ["b", nick cl, "]", msg, "\x20\x20"]
handleCmd_inRoom ["BAN", banNick] = do
@@ -452,7 +452,7 @@
handleCmd_inRoom ["CALLVOTE"] = do
cl <- thisClient
return [AnswerClients [sendChan cl]
- ["CHAT", nickServer, loc "Available callvote commands: kick , map , pause, newseed, hedgehogs"]
+ ["CHAT", nickServer, loc "Available callvote commands: hedgehogs , pause, newseed, map , kick "]
]
handleCmd_inRoom ["CALLVOTE", "KICK"] = do
diff -r 8736f3a0ff7f -r e54e41554529 gameServer/HWProtoLobbyState.hs
--- a/gameServer/HWProtoLobbyState.hs Mon Jan 14 12:34:47 2019 -0500
+++ b/gameServer/HWProtoLobbyState.hs Mon Jan 14 12:35:32 2019 -0500
@@ -220,12 +220,18 @@
handleCmd_lobby ["CLEAR_ACCOUNTS_CACHE"] = serverAdminOnly $
return [ClearAccountsCache]
+handleCmd_lobby ["RESTART_SERVER", "YES"] = serverAdminOnly $
+ return [RestartServer]
+
handleCmd_lobby ["RESTART_SERVER"] = serverAdminOnly $
- return [RestartServer]
+ return [Warning $ loc "Please confirm server restart with '/restart_server yes'."]
+
+handleCmd_lobby ["RESTART_SERVER", _] = handleCmd_lobby ["RESTART_SERVER"]
+
handleCmd_lobby ["STATS"] = serverAdminOnly $
return [Stats]
handleCmd_lobby (s:_) = return [ProtocolError $ "Incorrect command '" `B.append` s `B.append` "' (state: in lobby)"]
-handleCmd_lobby [] = return [ProtocolError "Empty command (state: in lobby)"]
\ No newline at end of file
+handleCmd_lobby [] = return [ProtocolError "Empty command (state: in lobby)"]
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/Cargo.toml
--- a/gameServer2/Cargo.toml Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-[package]
-edition = "2018"
-name = "hedgewars-server"
-version = "0.0.1"
-authors = [ "Andrey Korotaev " ]
-
-[features]
-official-server = ["openssl"]
-tls-connections = ["openssl"]
-default = []
-
-[dependencies]
-rand = "0.5"
-mio = "0.6"
-slab = "0.4"
-netbuf = "0.4"
-nom = "4.1"
-env_logger = "0.6"
-log = "0.4"
-base64 = "0.10"
-bitflags = "1.0"
-serde = "1.0"
-serde_yaml = "0.8"
-serde_derive = "1.0"
-openssl = { version = "0.10", optional = true }
-
-[dev-dependencies]
-proptest = "0.8"
\ No newline at end of file
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/main.rs
--- a/gameServer2/src/main.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-#![allow(unused_imports)]
-#![deny(bare_trait_objects)]
-
-//use std::io::*;
-//use rand::Rng;
-//use std::cmp::Ordering;
-use mio::net::*;
-use mio::*;
-use log::*;
-
-mod utils;
-mod server;
-mod protocol;
-
-use crate::server::network::NetworkLayer;
-use std::time::Duration;
-
-fn main() {
- env_logger::init();
-
- info!("Hedgewars game server, protocol {}", utils::PROTOCOL_VERSION);
-
- let address = "0.0.0.0:46631".parse().unwrap();
- let listener = TcpListener::bind(&address).unwrap();
-
- let poll = Poll::new().unwrap();
- let mut hw_network = NetworkLayer::new(listener, 1024, 512);
- hw_network.register_server(&poll).unwrap();
-
- let mut events = Events::with_capacity(1024);
-
- loop {
- let timeout = if hw_network.has_pending_operations() {
- Some(Duration::from_millis(1))
- } else {
- None
- };
- poll.poll(&mut events, timeout).unwrap();
-
- for event in events.iter() {
- if event.readiness() & Ready::readable() == Ready::readable() {
- match event.token() {
- utils::SERVER => hw_network.accept_client(&poll).unwrap(),
- Token(tok) => hw_network.client_readable(&poll, tok).unwrap(),
- }
- }
- if event.readiness() & Ready::writable() == Ready::writable() {
- match event.token() {
- utils::SERVER => unreachable!(),
- Token(tok) => hw_network.client_writable(&poll, tok).unwrap(),
- }
- }
-// if event.kind().is_hup() || event.kind().is_error() {
-// match event.token() {
-// utils::SERVER => unreachable!(),
-// Token(tok) => server.client_error(&poll, tok).unwrap(),
-// }
-// }
- }
- hw_network.on_idle(&poll).unwrap();
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/protocol/messages.rs
--- a/gameServer2/src/protocol/messages.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,316 +0,0 @@
-use crate::server::coretypes::{
- ServerVar, GameCfg, TeamInfo,
- HedgehogInfo, VoteType
-};
-use std::{ops, convert::From, iter::once};
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum HWProtocolMessage {
- // core
- Ping,
- Pong,
- Quit(Option),
- //Cmd(String, Vec),
- Global(String),
- Watch(String),
- ToggleServerRegisteredOnly,
- SuperPower,
- Info(String),
- // not entered state
- Nick(String),
- Proto(u16),
- Password(String, String),
- Checker(u16, String, String),
- // lobby
- List,
- Chat(String),
- CreateRoom(String, Option),
- JoinRoom(String, Option),
- Follow(String),
- Rnd(Vec),
- Kick(String),
- Ban(String, String, u32),
- BanIP(String, String, u32),
- BanNick(String, String, u32),
- BanList,
- Unban(String),
- SetServerVar(ServerVar),
- GetServerVar,
- RestartServer,
- Stats,
- // in room
- Part(Option),
- Cfg(GameCfg),
- AddTeam(Box),
- RemoveTeam(String),
- SetHedgehogsNumber(String, u8),
- SetTeamColor(String, u8),
- ToggleReady,
- StartGame,
- EngineMessage(String),
- RoundFinished,
- ToggleRestrictJoin,
- ToggleRestrictTeams,
- ToggleRegisteredOnly,
- RoomName(String),
- Delegate(String),
- TeamChat(String),
- MaxTeams(u8),
- Fix,
- Unfix,
- Greeting(String),
- CallVote(Option),
- Vote(bool),
- ForceVote(bool),
- Save(String, String),
- Delete(String),
- SaveRoom(String),
- LoadRoom(String),
- Malformed,
- Empty,
-}
-
-#[derive(Debug)]
-pub enum HWServerMessage {
- Ping,
- Pong,
- Bye(String),
- Nick(String),
- Proto(u16),
- ServerAuth(String),
- LobbyLeft(String, String),
- LobbyJoined(Vec),
- ChatMsg {nick: String, msg: String},
- ClientFlags(String, Vec),
- Rooms(Vec),
- RoomAdd(Vec),
- RoomJoined(Vec),
- RoomLeft(String, String),
- RoomRemove(String),
- RoomUpdated(String, Vec),
- TeamAdd(Vec),
- TeamRemove(String),
- TeamAccepted(String),
- TeamColor(String, u8),
- HedgehogsNumber(String, u8),
- ConfigEntry(String, Vec),
- Kicked,
- RunGame,
- ForwardEngineMessage(Vec),
- RoundFinished,
-
- ServerMessage(String),
- Notice(String),
- Warning(String),
- Error(String),
- Connected(u32),
- Unreachable,
-
- //Deprecated messages
- LegacyReady(bool, Vec)
-}
-
-pub fn server_chat(msg: String) -> HWServerMessage {
- HWServerMessage::ChatMsg{ nick: "[server]".to_string(), msg }
-}
-
-impl GameCfg {
- pub fn to_protocol(&self) -> (String, Vec) {
- use crate::server::coretypes::GameCfg::*;
- match self {
- FeatureSize(s) => ("FEATURE_SIZE".to_string(), vec![s.to_string()]),
- MapType(t) => ("MAP".to_string(), vec![t.to_string()]),
- MapGenerator(g) => ("MAPGEN".to_string(), vec![g.to_string()]),
- MazeSize(s) => ("MAZE_SIZE".to_string(), vec![s.to_string()]),
- Seed(s) => ("SEED".to_string(), vec![s.to_string()]),
- Template(t) => ("TEMPLATE".to_string(), vec![t.to_string()]),
-
- Ammo(n, None) => ("AMMO".to_string(), vec![n.to_string()]),
- Ammo(n, Some(s)) => ("AMMO".to_string(), vec![n.to_string(), s.to_string()]),
- Scheme(n, s) if s.is_empty() => ("SCHEME".to_string(), vec![n.to_string()]),
- Scheme(n, s) => ("SCHEME".to_string(), {
- let mut v = vec![n.to_string()];
- v.extend(s.clone().into_iter());
- v
- }),
- Script(s) => ("SCRIPT".to_string(), vec![s.to_string()]),
- Theme(t) => ("THEME".to_string(), vec![t.to_string()]),
- DrawnMap(m) => ("DRAWNMAP".to_string(), vec![m.to_string()])
- }
- }
-
- pub fn to_server_msg(&self) -> HWServerMessage {
- use self::HWServerMessage::ConfigEntry;
- let (name, args) = self.to_protocol();
- HWServerMessage::ConfigEntry(name, args)
- }
-}
-
-macro_rules! const_braces {
- ($e: expr) => { "{}\n" }
-}
-
-macro_rules! msg {
- [$($part: expr),*] => {
- format!(concat!($(const_braces!($part)),*, "\n"), $($part),*);
- };
-}
-
-#[cfg(test)]
-macro_rules! several {
- [$part: expr] => { once($part) };
- [$part: expr, $($other: expr),*] => { once($part).chain(several![$($other),*]) };
-}
-
-impl HWProtocolMessage {
- /** Converts the message to a raw `String`, which can be sent over the network.
- *
- * This is the inverse of the `message` parser.
- */
- #[cfg(test)]
- pub(crate) fn to_raw_protocol(&self) -> String {
- use self::HWProtocolMessage::*;
- match self {
- Ping => msg!["PING"],
- Pong => msg!["PONG"],
- Quit(None) => msg!["QUIT"],
- Quit(Some(msg)) => msg!["QUIT", msg],
- Global(msg) => msg!["CMD", format!("GLOBAL {}", msg)],
- Watch(name) => msg!["CMD", format!("WATCH {}", name)],
- ToggleServerRegisteredOnly => msg!["CMD", "REGISTERED_ONLY"],
- SuperPower => msg!["CMD", "SUPER_POWER"],
- Info(info) => msg!["CMD", format!("INFO {}", info)],
- Nick(nick) => msg!("NICK", nick),
- Proto(version) => msg!["PROTO", version],
- Password(p, s) => msg!["PASSWORD", p, s],
- Checker(i, n, p) => msg!["CHECKER", i, n, p],
- List => msg!["LIST"],
- Chat(msg) => msg!["CHAT", msg],
- CreateRoom(name, None) => msg!["CREATE_ROOM", name],
- CreateRoom(name, Some(password)) =>
- msg!["CREATE_ROOM", name, password],
- JoinRoom(name, None) => msg!["JOIN_ROOM", name],
- JoinRoom(name, Some(password)) =>
- msg!["JOIN_ROOM", name, password],
- Follow(name) => msg!["FOLLOW", name],
- Rnd(args) => if args.is_empty() {
- msg!["CMD", "RND"]
- } else {
- msg!["CMD", format!("RND {}", args.join(" "))]
- },
- Kick(name) => msg!["KICK", name],
- Ban(name, reason, time) => msg!["BAN", name, reason, time],
- BanIP(ip, reason, time) => msg!["BAN_IP", ip, reason, time],
- BanNick(nick, reason, time) =>
- msg!("BAN_NICK", nick, reason, time),
- BanList => msg!["BANLIST"],
- Unban(name) => msg!["UNBAN", name],
- //SetServerVar(ServerVar), ???
- GetServerVar => msg!["GET_SERVER_VAR"],
- RestartServer => msg!["CMD", "RESTART_SERVER YES"],
- Stats => msg!["CMD", "STATS"],
- Part(None) => msg!["PART"],
- Part(Some(msg)) => msg!["PART", msg],
- Cfg(config) => {
- let (name, args) = config.to_protocol();
- msg!["CFG", name, args.join("\n")]
- },
- AddTeam(info) =>
- msg!["ADD_TEAM", info.name, info.color, info.grave, info.fort,
- info.voice_pack, info.flag, info.difficulty,
- info.hedgehogs.iter()
- .flat_map(|h| several![&h.name[..], &h.hat[..]])
- .collect::>().join("\n")],
- RemoveTeam(name) => msg!["REMOVE_TEAM", name],
- SetHedgehogsNumber(team, number) => msg!["HH_NUM", team, number],
- SetTeamColor(team, color) => msg!["TEAM_COLOR", team, color],
- ToggleReady => msg!["TOGGLE_READY"],
- StartGame => msg!["START_GAME"],
- EngineMessage(msg) => msg!["EM", msg],
- RoundFinished => msg!["ROUNDFINISHED"],
- ToggleRestrictJoin => msg!["TOGGLE_RESTRICT_JOINS"],
- ToggleRestrictTeams => msg!["TOGGLE_RESTRICT_TEAMS"],
- ToggleRegisteredOnly => msg!["TOGGLE_REGISTERED_ONLY"],
- RoomName(name) => msg!["ROOM_NAME", name],
- Delegate(name) => msg!["CMD", format!("DELEGATE {}", name)],
- TeamChat(msg) => msg!["TEAMCHAT", msg],
- MaxTeams(count) => msg!["CMD", format!("MAXTEAMS {}", count)] ,
- Fix => msg!["CMD", "FIX"],
- Unfix => msg!["CMD", "UNFIX"],
- Greeting(msg) => msg!["CMD", format!("GREETING {}", msg)],
- //CallVote(Option<(String, Option)>) =>, ??
- Vote(msg) => msg!["CMD", format!("VOTE {}", if *msg {"YES"} else {"NO"})],
- ForceVote(msg) => msg!["CMD", format!("FORCE {}", if *msg {"YES"} else {"NO"})],
- Save(name, location) => msg!["CMD", format!("SAVE {} {}", name, location)],
- Delete(name) => msg!["CMD", format!("DELETE {}", name)],
- SaveRoom(name) => msg!["CMD", format!("SAVEROOM {}", name)],
- LoadRoom(name) => msg!["CMD", format!("LOADROOM {}", name)],
- Malformed => msg!["A", "QUICK", "BROWN", "HOG", "JUMPS", "OVER", "THE", "LAZY", "DOG"],
- Empty => msg![""],
- _ => panic!("Protocol message not yet implemented")
- }
- }
-}
-
-fn construct_message(header: &[&str], msg: &[String]) -> String {
- let mut v: Vec<_> = header.iter().cloned().collect();
- v.extend(msg.iter().map(|s| &s[..]));
- v.push("\n");
- v.join("\n")
-}
-
-impl HWServerMessage {
- pub fn to_raw_protocol(&self) -> String {
- use self::HWServerMessage::*;
- match self {
- Ping => msg!["PING"],
- Pong => msg!["PONG"],
- Connected(protocol_version) => msg![
- "CONNECTED",
- "Hedgewars server https://www.hedgewars.org/",
- protocol_version],
- Bye(msg) => msg!["BYE", msg],
- Nick(nick) => msg!["NICK", nick],
- Proto(proto) => msg!["PROTO", proto],
- ServerAuth(hash) => msg!["SERVER_AUTH", hash],
- LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg],
- LobbyJoined(nicks) =>
- construct_message(&["LOBBY:JOINED"], &nicks),
- ClientFlags(flags, nicks) =>
- construct_message(&["CLIENT_FLAGS", flags], &nicks),
- Rooms(info) =>
- construct_message(&["ROOMS"], &info),
- RoomAdd(info) =>
- construct_message(&["ROOM", "ADD"], &info),
- RoomJoined(nicks) =>
- construct_message(&["JOINED"], &nicks),
- RoomLeft(nick, msg) => msg!["LEFT", nick, msg],
- RoomRemove(name) => msg!["ROOM", "DEL", name],
- RoomUpdated(name, info) =>
- construct_message(&["ROOM", "UPD", name], &info),
- TeamAdd(info) =>
- construct_message(&["ADD_TEAM"], &info),
- TeamRemove(name) => msg!["REMOVE_TEAM", name],
- TeamAccepted(name) => msg!["TEAM_ACCEPTED", name],
- TeamColor(name, color) => msg!["TEAM_COLOR", name, color],
- HedgehogsNumber(name, number) => msg!["HH_NUM", name, number],
- ConfigEntry(name, values) =>
- construct_message(&["CFG", name], &values),
- Kicked => msg!["KICKED"],
- RunGame => msg!["RUN_GAME"],
- ForwardEngineMessage(em) =>
- construct_message(&["EM"], &em),
- RoundFinished => msg!["ROUND_FINISHED"],
- ChatMsg {nick, msg} => msg!["CHAT", nick, msg],
- ServerMessage(msg) => msg!["SERVER_MESSAGE", msg],
- Notice(msg) => msg!["NOTICE", msg],
- Warning(msg) => msg!["WARNING", msg],
- Error(msg) => msg!["ERROR", msg],
-
- LegacyReady(is_ready, nicks) =>
- construct_message(&[if *is_ready {"READY"} else {"NOT_READY"}], &nicks),
-
- _ => msg!["ERROR", "UNIMPLEMENTED"],
- }
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/protocol/mod.rs
--- a/gameServer2/src/protocol/mod.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-use netbuf;
-use std::{
- io::{Read, Result}
-};
-use nom::{
- IResult, Err
-};
-
-pub mod messages;
-#[cfg(test)]
-pub mod test;
-mod parser;
-
-pub struct ProtocolDecoder {
- buf: netbuf::Buf,
- consumed: usize,
-}
-
-impl ProtocolDecoder {
- pub fn new() -> ProtocolDecoder {
- ProtocolDecoder {
- buf: netbuf::Buf::new(),
- consumed: 0,
- }
- }
-
- pub fn read_from(&mut self, stream: &mut R) -> Result {
- self.buf.read_from(stream)
- }
-
- pub fn extract_messages(&mut self) -> Vec {
- let parse_result = parser::extract_messages(&self.buf[..]);
- match parse_result {
- Ok((tail, msgs)) => {
- self.consumed = self.buf.len() - self.consumed - tail.len();
- msgs
- },
- Err(Err::Incomplete(_)) => unreachable!(),
- Err(Err::Error(_)) | Err(Err::Failure(_)) => unreachable!(),
- }
- }
-
- pub fn sweep(&mut self) {
- self.buf.consume(self.consumed);
- self.consumed = 0;
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/protocol/parser.rs
--- a/gameServer2/src/protocol/parser.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,284 +0,0 @@
-/** The parsers for the chat and multiplayer protocol. The main parser is `message`.
- * # Protocol
- * All messages consist of `\n`-separated strings. The end of a message is
- * indicated by a double newline - `\n\n`.
- *
- * For example, a nullary command like PING will be actually sent as `PING\n\n`.
- * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`.
- */
-
-use nom::*;
-
-use std::{
- str, str::FromStr,
- ops::Range
-};
-use super::{
- messages::{HWProtocolMessage, HWProtocolMessage::*}
-};
-#[cfg(test)]
-use {
- super::test::gen_proto_msg,
- proptest::{proptest, proptest_helper}
-};
-use crate::server::coretypes::{
- HedgehogInfo, TeamInfo, GameCfg, VoteType, MAX_HEDGEHOGS_PER_TEAM
-};
-
-named!(end_of_message, tag!("\n\n"));
-named!(str_line<&[u8], &str>, map_res!(not_line_ending, str::from_utf8));
-named!( a_line<&[u8], String>, map!(str_line, String::from));
-named!(cmd_arg<&[u8], String>,
- map!(map_res!(take_until_either!(" \n"), str::from_utf8), String::from));
-named!( u8_line<&[u8], u8>, map_res!(str_line, FromStr::from_str));
-named!(u16_line<&[u8], u16>, map_res!(str_line, FromStr::from_str));
-named!(u32_line<&[u8], u32>, map_res!(str_line, FromStr::from_str));
-named!(yes_no_line<&[u8], bool>, alt!(
- do_parse!(tag_no_case!("YES") >> (true))
- | do_parse!(tag_no_case!("NO") >> (false))));
-named!(opt_param<&[u8], Option >, alt!(
- do_parse!(peek!(tag!("\n\n")) >> (None))
- | do_parse!(tag!("\n") >> s: str_line >> (Some(s.to_string())))));
-named!(spaces<&[u8], &[u8]>, preceded!(tag!(" "), eat_separator!(" ")));
-named!(opt_space_param<&[u8], Option >, alt!(
- do_parse!(peek!(tag!("\n\n")) >> (None))
- | do_parse!(spaces >> s: str_line >> (Some(s.to_string())))));
-named!(hog_line<&[u8], HedgehogInfo>,
- do_parse!(name: str_line >> eol >> hat: str_line >>
- (HedgehogInfo{name: name.to_string(), hat: hat.to_string()})));
-named!(_8_hogs<&[u8], [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize]>,
- do_parse!(h1: hog_line >> eol >> h2: hog_line >> eol >>
- h3: hog_line >> eol >> h4: hog_line >> eol >>
- h5: hog_line >> eol >> h6: hog_line >> eol >>
- h7: hog_line >> eol >> h8: hog_line >>
- ([h1, h2, h3, h4, h5, h6, h7, h8])));
-named!(voting<&[u8], VoteType>, alt!(
- do_parse!(tag_no_case!("KICK") >> spaces >> n: a_line >>
- (VoteType::Kick(n)))
- | do_parse!(tag_no_case!("MAP") >>
- n: opt!(preceded!(spaces, a_line)) >>
- (VoteType::Map(n)))
- | do_parse!(tag_no_case!("PAUSE") >>
- (VoteType::Pause))
- | do_parse!(tag_no_case!("NEWSEED") >>
- (VoteType::NewSeed))
- | do_parse!(tag_no_case!("HEDGEHOGS") >> spaces >> n: u8_line >>
- (VoteType::HedgehogsPerTeam(n)))));
-
-/** Recognizes messages which do not take any parameters */
-named!(basic_message<&[u8], HWProtocolMessage>, alt!(
- do_parse!(tag!("PING") >> (Ping))
- | do_parse!(tag!("PONG") >> (Pong))
- | do_parse!(tag!("LIST") >> (List))
- | do_parse!(tag!("BANLIST") >> (BanList))
- | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar))
- | do_parse!(tag!("TOGGLE_READY") >> (ToggleReady))
- | do_parse!(tag!("START_GAME") >> (StartGame))
- | do_parse!(tag!("ROUNDFINISHED") >> _m: opt_param >> (RoundFinished))
- | do_parse!(tag!("TOGGLE_RESTRICT_JOINS") >> (ToggleRestrictJoin))
- | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS") >> (ToggleRestrictTeams))
- | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly))
-));
-
-/** Recognizes messages which take exactly one parameter */
-named!(one_param_message<&[u8], HWProtocolMessage>, alt!(
- do_parse!(tag!("NICK") >> eol >> n: a_line >> (Nick(n)))
- | do_parse!(tag!("INFO") >> eol >> n: a_line >> (Info(n)))
- | do_parse!(tag!("CHAT") >> eol >> m: a_line >> (Chat(m)))
- | do_parse!(tag!("PART") >> msg: opt_param >> (Part(msg)))
- | do_parse!(tag!("FOLLOW") >> eol >> n: a_line >> (Follow(n)))
- | do_parse!(tag!("KICK") >> eol >> n: a_line >> (Kick(n)))
- | do_parse!(tag!("UNBAN") >> eol >> n: a_line >> (Unban(n)))
- | do_parse!(tag!("EM") >> eol >> m: a_line >> (EngineMessage(m)))
- | do_parse!(tag!("TEAMCHAT") >> eol >> m: a_line >> (TeamChat(m)))
- | do_parse!(tag!("ROOM_NAME") >> eol >> n: a_line >> (RoomName(n)))
- | do_parse!(tag!("REMOVE_TEAM") >> eol >> n: a_line >> (RemoveTeam(n)))
-
- | do_parse!(tag!("PROTO") >> eol >> d: u16_line >> (Proto(d)))
-
- | do_parse!(tag!("QUIT") >> msg: opt_param >> (Quit(msg)))
-));
-
-/** Recognizes messages preceded with CMD */
-named!(cmd_message<&[u8], HWProtocolMessage>, preceded!(tag!("CMD\n"), alt!(
- do_parse!(tag_no_case!("STATS") >> (Stats))
- | do_parse!(tag_no_case!("FIX") >> (Fix))
- | do_parse!(tag_no_case!("UNFIX") >> (Unfix))
- | do_parse!(tag_no_case!("RESTART_SERVER") >> spaces >> tag!("YES") >> (RestartServer))
- | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly))
- | do_parse!(tag_no_case!("SUPER_POWER") >> (SuperPower))
- | do_parse!(tag_no_case!("PART") >> m: opt_space_param >> (Part(m)))
- | do_parse!(tag_no_case!("QUIT") >> m: opt_space_param >> (Quit(m)))
- | do_parse!(tag_no_case!("DELEGATE") >> spaces >> n: a_line >> (Delegate(n)))
- | do_parse!(tag_no_case!("SAVE") >> spaces >> n: cmd_arg >> spaces >> l: cmd_arg >> (Save(n, l)))
- | do_parse!(tag_no_case!("DELETE") >> spaces >> n: a_line >> (Delete(n)))
- | do_parse!(tag_no_case!("SAVEROOM") >> spaces >> r: a_line >> (SaveRoom(r)))
- | do_parse!(tag_no_case!("LOADROOM") >> spaces >> r: a_line >> (LoadRoom(r)))
- | do_parse!(tag_no_case!("GLOBAL") >> spaces >> m: a_line >> (Global(m)))
- | do_parse!(tag_no_case!("WATCH") >> spaces >> i: a_line >> (Watch(i)))
- | do_parse!(tag_no_case!("GREETING") >> spaces >> m: a_line >> (Greeting(m)))
- | do_parse!(tag_no_case!("VOTE") >> spaces >> m: yes_no_line >> (Vote(m)))
- | do_parse!(tag_no_case!("FORCE") >> spaces >> m: yes_no_line >> (ForceVote(m)))
- | do_parse!(tag_no_case!("INFO") >> spaces >> n: a_line >> (Info(n)))
- | do_parse!(tag_no_case!("MAXTEAMS") >> spaces >> n: u8_line >> (MaxTeams(n)))
- | do_parse!(tag_no_case!("CALLVOTE") >>
- v: opt!(preceded!(spaces, voting)) >> (CallVote(v)))
- | do_parse!(
- tag_no_case!("RND") >> alt!(spaces | peek!(end_of_message)) >>
- v: str_line >>
- (Rnd(v.split_whitespace().map(String::from).collect())))
-)));
-
-named!(complex_message<&[u8], HWProtocolMessage>, alt!(
- do_parse!(tag!("PASSWORD") >> eol >>
- p: a_line >> eol >>
- s: a_line >>
- (Password(p, s)))
- | do_parse!(tag!("CHECKER") >> eol >>
- i: u16_line >> eol >>
- n: a_line >> eol >>
- p: a_line >>
- (Checker(i, n, p)))
- | do_parse!(tag!("CREATE_ROOM") >> eol >>
- n: a_line >>
- p: opt_param >>
- (CreateRoom(n, p)))
- | do_parse!(tag!("JOIN_ROOM") >> eol >>
- n: a_line >>
- p: opt_param >>
- (JoinRoom(n, p)))
- | do_parse!(tag!("ADD_TEAM") >> eol >>
- name: a_line >> eol >>
- color: u8_line >> eol >>
- grave: a_line >> eol >>
- fort: a_line >> eol >>
- voice_pack: a_line >> eol >>
- flag: a_line >> eol >>
- difficulty: u8_line >> eol >>
- hedgehogs: _8_hogs >>
- (AddTeam(Box::new(TeamInfo{
- name, color, grave, fort,
- voice_pack, flag, difficulty,
- hedgehogs, hedgehogs_number: 0
- }))))
- | do_parse!(tag!("HH_NUM") >> eol >>
- n: a_line >> eol >>
- c: u8_line >>
- (SetHedgehogsNumber(n, c)))
- | do_parse!(tag!("TEAM_COLOR") >> eol >>
- n: a_line >> eol >>
- c: u8_line >>
- (SetTeamColor(n, c)))
- | do_parse!(tag!("BAN") >> eol >>
- n: a_line >> eol >>
- r: a_line >> eol >>
- t: u32_line >>
- (Ban(n, r, t)))
- | do_parse!(tag!("BAN_IP") >> eol >>
- n: a_line >> eol >>
- r: a_line >> eol >>
- t: u32_line >>
- (BanIP(n, r, t)))
- | do_parse!(tag!("BAN_NICK") >> eol >>
- n: a_line >> eol >>
- r: a_line >> eol >>
- t: u32_line >>
- (BanNick(n, r, t)))
-));
-
-named!(cfg_message<&[u8], HWProtocolMessage>, preceded!(tag!("CFG\n"), map!(alt!(
- do_parse!(tag!("THEME") >> eol >>
- name: a_line >>
- (GameCfg::Theme(name)))
- | do_parse!(tag!("SCRIPT") >> eol >>
- name: a_line >>
- (GameCfg::Script(name)))
- | do_parse!(tag!("AMMO") >> eol >>
- name: a_line >>
- value: opt_param >>
- (GameCfg::Ammo(name, value)))
- | do_parse!(tag!("SCHEME") >> eol >>
- name: a_line >>
- values: opt!(preceded!(eol, separated_list!(eol, a_line))) >>
- (GameCfg::Scheme(name, values.unwrap_or_default())))
- | do_parse!(tag!("FEATURE_SIZE") >> eol >>
- value: u32_line >>
- (GameCfg::FeatureSize(value)))
- | do_parse!(tag!("MAP") >> eol >>
- value: a_line >>
- (GameCfg::MapType(value)))
- | do_parse!(tag!("MAPGEN") >> eol >>
- value: u32_line >>
- (GameCfg::MapGenerator(value)))
- | do_parse!(tag!("MAZE_SIZE") >> eol >>
- value: u32_line >>
- (GameCfg::MazeSize(value)))
- | do_parse!(tag!("SEED") >> eol >>
- value: a_line >>
- (GameCfg::Seed(value)))
- | do_parse!(tag!("TEMPLATE") >> eol >>
- value: u32_line >>
- (GameCfg::Template(value)))
- | do_parse!(tag!("DRAWNMAP") >> eol >>
- value: a_line >>
- (GameCfg::DrawnMap(value)))
-), Cfg)));
-
-named!(malformed_message<&[u8], HWProtocolMessage>,
- do_parse!(separated_list!(eol, a_line) >> (Malformed)));
-
-named!(empty_message<&[u8], HWProtocolMessage>,
- do_parse!(alt!(end_of_message | eol) >> (Empty)));
-
-named!(message<&[u8], HWProtocolMessage>, alt!(terminated!(
- alt!(
- basic_message
- | one_param_message
- | cmd_message
- | complex_message
- | cfg_message
- ), end_of_message
- )
- | terminated!(malformed_message, end_of_message)
- | empty_message
- )
-);
-
-named!(pub extract_messages<&[u8], Vec >, many0!(complete!(message)));
-
-#[cfg(test)]
-proptest! {
- #[test]
- fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
- println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes());
- assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone())))
- }
-}
-
-#[test]
-fn parse_test() {
- assert_eq!(message(b"PING\n\n"), Ok((&b""[..], Ping)));
- assert_eq!(message(b"START_GAME\n\n"), Ok((&b""[..], StartGame)));
- assert_eq!(message(b"NICK\nit's me\n\n"), Ok((&b""[..], Nick("it's me".to_string()))));
- assert_eq!(message(b"PROTO\n51\n\n"), Ok((&b""[..], Proto(51))));
- assert_eq!(message(b"QUIT\nbye-bye\n\n"), Ok((&b""[..], Quit(Some("bye-bye".to_string())))));
- assert_eq!(message(b"QUIT\n\n"), Ok((&b""[..], Quit(None))));
- assert_eq!(message(b"CMD\nwatch demo\n\n"), Ok((&b""[..], Watch("demo".to_string()))));
- assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), Ok((&b""[..], Ban("me".to_string(), "bad".to_string(), 77))));
-
- assert_eq!(message(b"CMD\nPART\n\n"), Ok((&b""[..], Part(None))));
- assert_eq!(message(b"CMD\nPART _msg_\n\n"), Ok((&b""[..], Part(Some("_msg_".to_string())))));
-
- assert_eq!(message(b"CMD\nRND\n\n"), Ok((&b""[..], Rnd(vec![]))));
- assert_eq!(
- message(b"CMD\nRND A B\n\n"),
- Ok((&b""[..], Rnd(vec![String::from("A"), String::from("B")])))
- );
-
- assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"), Ok((&b""[..], vec![Malformed])));
-
- assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), Ok((&b"P"[..], vec![Ping, Ping])));
- assert_eq!(extract_messages(b"SING\n\nPING\n\n"), Ok((&b""[..], vec![Malformed, Ping])));
- assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"), Ok((&b""[..], vec![Empty, Empty, Ping])));
- assert_eq!(extract_messages(b"\n\n\nPING\n\n"), Ok((&b""[..], vec![Empty, Empty, Ping])));
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/protocol/test.rs
--- a/gameServer2/src/protocol/test.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-use proptest::{
- test_runner::{TestRunner, Reason},
- arbitrary::{any, any_with, Arbitrary, StrategyFor},
- strategy::{Strategy, BoxedStrategy, Just, Map}
-};
-
-use crate::server::coretypes::{GameCfg, TeamInfo, HedgehogInfo};
-
-use super::messages::{
- HWProtocolMessage, HWProtocolMessage::*
-};
-
-// Due to inability to define From between Options
-trait Into2: Sized { fn into2(self) -> T; }
-impl Into2 for T { fn into2(self) -> T { self } }
-impl Into2> for Vec {
- fn into2(self) -> Vec {
- self.into_iter().map(|x| x.0).collect()
- }
-}
-impl Into2 for Ascii { fn into2(self) -> String { self.0 } }
-impl Into2> for Option{
- fn into2(self) -> Option { self.map(|x| {x.0}) }
-}
-
-macro_rules! proto_msg_case {
- ($val: ident()) =>
- (Just($val));
- ($val: ident($arg: ty)) =>
- (any::<$arg>().prop_map(|v| {$val(v.into2())}));
- ($val: ident($arg1: ty, $arg2: ty)) =>
- (any::<($arg1, $arg2)>().prop_map(|v| {$val(v.0.into2(), v.1.into2())}));
- ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) =>
- (any::<($arg1, $arg2, $arg3)>().prop_map(|v| {$val(v.0.into2(), v.1.into2(), v.2.into2())}));
-}
-
-macro_rules! proto_msg_match {
- ($var: expr, def = $default: expr, $($num: expr => $constr: ident $res: tt),*) => (
- match $var {
- $($num => (proto_msg_case!($constr $res)).boxed()),*,
- _ => Just($default).boxed()
- }
- )
-}
-
-/// Wrapper type for generating non-empty strings
-#[derive(Debug)]
-struct Ascii(String);
-
-impl Arbitrary for Ascii {
- type Parameters = ::Parameters;
-
- fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
- "[a-zA-Z0-9]+".prop_map(Ascii).boxed()
- }
-
- type Strategy = BoxedStrategy;
-}
-
-impl Arbitrary for GameCfg {
- type Parameters = ();
-
- fn arbitrary_with(_args: ::Parameters) -> ::Strategy {
- use crate::server::coretypes::GameCfg::*;
- (0..10).no_shrink().prop_flat_map(|i| {
- proto_msg_match!(i, def = FeatureSize(0),
- 0 => FeatureSize(u32),
- 1 => MapType(Ascii),
- 2 => MapGenerator(u32),
- 3 => MazeSize(u32),
- 4 => Seed(Ascii),
- 5 => Template(u32),
- 6 => Ammo(Ascii, Option),
- 7 => Scheme(Ascii, Vec),
- 8 => Script(Ascii),
- 9 => Theme(Ascii),
- 10 => DrawnMap(Ascii))
- }).boxed()
- }
-
- type Strategy = BoxedStrategy;
-}
-
-impl Arbitrary for TeamInfo {
- type Parameters = ();
-
- fn arbitrary_with(_args: ::Parameters) -> ::Strategy {
- ("[a-z]+", 0u8..127u8, "[a-z]+", "[a-z]+", "[a-z]+", "[a-z]+", 0u8..127u8)
- .prop_map(|(name, color, grave, fort, voice_pack, flag, difficulty)| {
- fn hog(n: u8) -> HedgehogInfo {
- HedgehogInfo { name: format!("hog{}", n), hat: format!("hat{}", n)}
- }
- let hedgehogs = [hog(1), hog(2), hog(3), hog(4), hog(5), hog(6), hog(7), hog(8)];
- TeamInfo {
- name, color, grave, fort,
- voice_pack, flag,difficulty,
- hedgehogs, hedgehogs_number: 0
- }
- }).boxed()
- }
-
- type Strategy = BoxedStrategy;
-}
-
-pub fn gen_proto_msg() -> BoxedStrategy where {
- let res = (0..58).no_shrink().prop_flat_map(|i| {
- proto_msg_match!(i, def = Malformed,
- 0 => Ping(),
- 1 => Pong(),
- 2 => Quit(Option),
- //3 => Cmd
- 4 => Global(Ascii),
- 5 => Watch(Ascii),
- 6 => ToggleServerRegisteredOnly(),
- 7 => SuperPower(),
- 8 => Info(Ascii),
- 9 => Nick(Ascii),
- 10 => Proto(u16),
- 11 => Password(Ascii, Ascii),
- 12 => Checker(u16, Ascii, Ascii),
- 13 => List(),
- 14 => Chat(Ascii),
- 15 => CreateRoom(Ascii, Option),
- 16 => JoinRoom(Ascii, Option),
- 17 => Follow(Ascii),
- 18 => Rnd(Vec),
- 19 => Kick(Ascii),
- 20 => Ban(Ascii, Ascii, u32),
- 21 => BanIP(Ascii, Ascii, u32),
- 22 => BanNick(Ascii, Ascii, u32),
- 23 => BanList(),
- 24 => Unban(Ascii),
- //25 => SetServerVar(ServerVar),
- 26 => GetServerVar(),
- 27 => RestartServer(),
- 28 => Stats(),
- 29 => Part(Option),
- 30 => Cfg(GameCfg),
- 31 => AddTeam(Box),
- 32 => RemoveTeam(Ascii),
- 33 => SetHedgehogsNumber(Ascii, u8),
- 34 => SetTeamColor(Ascii, u8),
- 35 => ToggleReady(),
- 36 => StartGame(),
- 37 => EngineMessage(Ascii),
- 38 => RoundFinished(),
- 39 => ToggleRestrictJoin(),
- 40 => ToggleRestrictTeams(),
- 41 => ToggleRegisteredOnly(),
- 42 => RoomName(Ascii),
- 43 => Delegate(Ascii),
- 44 => TeamChat(Ascii),
- 45 => MaxTeams(u8),
- 46 => Fix(),
- 47 => Unfix(),
- 48 => Greeting(Ascii),
- //49 => CallVote(Option<(String, Option)>),
- 50 => Vote(bool),
- 51 => ForceVote(bool),
- 52 => Save(Ascii, Ascii),
- 53 => Delete(Ascii),
- 54 => SaveRoom(Ascii),
- 55 => LoadRoom(Ascii),
- 56 => Malformed(),
- 57 => Empty()
- )});
- res.boxed()
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/actions.rs
--- a/gameServer2/src/server/actions.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,631 +0,0 @@
-use std::{
- io, io::Write,
- iter::once,
- mem::replace
-};
-use super::{
- server::HWServer,
- room::{GameInfo, RoomFlags},
- client::HWClient,
- coretypes::{ClientId, RoomId, GameCfg, VoteType},
- room::HWRoom,
- handlers
-};
-use crate::{
- protocol::messages::{
- HWProtocolMessage,
- HWServerMessage,
- HWServerMessage::*,
- server_chat
- },
- utils::to_engine_msg
-};
-use rand::{thread_rng, Rng, distributions::Uniform};
-
-pub enum Destination {
- ToId(ClientId),
- ToSelf,
- ToAll {
- room_id: Option,
- protocol: Option,
- skip_self: bool
- }
-}
-
-pub struct PendingMessage {
- pub destination: Destination,
- pub message: HWServerMessage
-}
-
-impl PendingMessage {
- pub fn send(message: HWServerMessage, client_id: ClientId) -> PendingMessage {
- PendingMessage{ destination: Destination::ToId(client_id), message}
- }
-
- pub fn send_self(message: HWServerMessage) -> PendingMessage {
- PendingMessage{ destination: Destination::ToSelf, message }
- }
-
- pub fn send_all(message: HWServerMessage) -> PendingMessage {
- let destination = Destination::ToAll {
- room_id: None,
- protocol: None,
- skip_self: false,
- };
- PendingMessage{ destination, message }
- }
-
- pub fn in_room(mut self, clients_room_id: RoomId) -> PendingMessage {
- if let Destination::ToAll {ref mut room_id, ..} = self.destination {
- *room_id = Some(clients_room_id)
- }
- self
- }
-
- pub fn with_protocol(mut self, protocol_number: u16) -> PendingMessage {
- if let Destination::ToAll {ref mut protocol, ..} = self.destination {
- *protocol = Some(protocol_number)
- }
- self
- }
-
- pub fn but_self(mut self) -> PendingMessage {
- if let Destination::ToAll {ref mut skip_self, ..} = self.destination {
- *skip_self = true
- }
- self
- }
-
- pub fn action(self) -> Action { Send(self) }
-}
-
-impl Into for PendingMessage {
- fn into(self) -> Action { self.action() }
-}
-
-impl HWServerMessage {
- pub fn send(self, client_id: ClientId) -> PendingMessage { PendingMessage::send(self, client_id) }
- pub fn send_self(self) -> PendingMessage { PendingMessage::send_self(self) }
- pub fn send_all(self) -> PendingMessage { PendingMessage::send_all(self) }
-}
-
-pub enum Action {
- Send(PendingMessage),
- RemoveClient,
- ByeClient(String),
- ReactProtocolMessage(HWProtocolMessage),
- CheckRegistered,
- JoinLobby,
- AddRoom(String, Option),
- RemoveRoom(RoomId),
- MoveToRoom(RoomId),
- MoveToLobby(String),
- ChangeMaster(RoomId, Option),
- RemoveTeam(String),
- RemoveClientTeams,
- SendRoomUpdate(Option),
- StartRoomGame(RoomId),
- SendTeamRemovalMessage(String),
- FinishRoomGame(RoomId),
- SendRoomData{to: ClientId, teams: bool, config: bool, flags: bool},
- AddVote{vote: bool, is_forced: bool},
- ApplyVoting(VoteType, RoomId),
- Warn(String),
- ProtocolError(String)
-}
-
-use self::Action::*;
-
-pub fn run_action(server: &mut HWServer, client_id: usize, action: Action) {
- match action {
- Send(msg) => server.send(client_id, &msg.destination, msg.message),
- ByeClient(msg) => {
- let c = &server.clients[client_id];
- let nick = c.nick.clone();
-
- if let Some(id) = c.room_id{
- if id != server.lobby_id {
- server.react(client_id, vec![
- MoveToLobby(format!("quit: {}", msg.clone()))]);
- }
- }
-
- server.react(client_id, vec![
- LobbyLeft(nick, msg.clone()).send_all().action(),
- Bye(msg).send_self().action(),
- RemoveClient]);
- },
- RemoveClient => {
- server.removed_clients.push(client_id);
- if server.clients.contains(client_id) {
- server.clients.remove(client_id);
- }
- },
- ReactProtocolMessage(msg) =>
- handlers::handle(server, client_id, msg),
- CheckRegistered => {
- let client = &server.clients[client_id];
- if client.protocol_number > 0 && client.nick != "" {
- let has_nick_clash = server.clients.iter().any(
- |(id, c)| id != client_id && c.nick == client.nick);
-
- let actions = if !client.is_checker() && has_nick_clash {
- if client.protocol_number < 38 {
- vec![ByeClient("Nickname is already in use".to_string())]
- } else {
- server.clients[client_id].nick.clear();
- vec![Notice("NickAlreadyInUse".to_string()).send_self().action()]
- }
- } else {
- vec![JoinLobby]
- };
- server.react(client_id, actions);
- }
- },
- JoinLobby => {
- server.clients[client_id].room_id = Some(server.lobby_id);
-
- let mut lobby_nicks = Vec::new();
- for (_, c) in server.clients.iter() {
- if c.room_id.is_some() {
- lobby_nicks.push(c.nick.clone());
- }
- }
- let joined_msg = LobbyJoined(lobby_nicks);
-
- let everyone_msg = LobbyJoined(vec![server.clients[client_id].nick.clone()]);
- let flags_msg = ClientFlags(
- "+i".to_string(),
- server.clients.iter()
- .filter(|(_, c)| c.room_id.is_some())
- .map(|(_, c)| c.nick.clone())
- .collect());
- let server_msg = ServerMessage("\u{1f994} is watching".to_string());
- let rooms_msg = Rooms(server.rooms.iter()
- .filter(|(id, _)| *id != server.lobby_id)
- .flat_map(|(_, r)|
- r.info(r.master_id.map(|id| &server.clients[id])))
- .collect());
- server.react(client_id, vec![
- everyone_msg.send_all().but_self().action(),
- joined_msg.send_self().action(),
- flags_msg.send_self().action(),
- server_msg.send_self().action(),
- rooms_msg.send_self().action(),
- ]);
- },
- AddRoom(name, password) => {
- let room_id = server.add_room();;
-
- let r = &mut server.rooms[room_id];
- let c = &mut server.clients[client_id];
- r.master_id = Some(c.id);
- r.name = name;
- r.password = password;
- r.protocol_number = c.protocol_number;
-
- let actions = vec![
- RoomAdd(r.info(Some(&c))).send_all()
- .with_protocol(r.protocol_number).action(),
- MoveToRoom(room_id)];
-
- server.react(client_id, actions);
- },
- RemoveRoom(room_id) => {
- let r = &mut server.rooms[room_id];
- let actions = vec![RoomRemove(r.name.clone()).send_all()
- .with_protocol(r.protocol_number).action()];
- server.rooms.remove(room_id);
- server.react(client_id, actions);
- }
- MoveToRoom(room_id) => {
- let r = &mut server.rooms[room_id];
- let c = &mut server.clients[client_id];
- r.players_number += 1;
- c.room_id = Some(room_id);
-
- let is_master = r.master_id == Some(c.id);
- c.set_is_master(is_master);
- c.set_is_ready(is_master);
- c.set_is_joined_mid_game(false);
-
- if is_master {
- r.ready_players_number += 1;
- }
-
- let mut v = vec![
- RoomJoined(vec![c.nick.clone()]).send_all().in_room(room_id).action(),
- ClientFlags("+i".to_string(), vec![c.nick.clone()]).send_all().action(),
- SendRoomUpdate(None)];
-
- if !r.greeting.is_empty() {
- v.push(ChatMsg {nick: "[greeting]".to_string(), msg: r.greeting.clone()}
- .send_self().action());
- }
-
- if !c.is_master() {
- let team_names: Vec<_>;
- if let Some(ref mut info) = r.game_info {
- c.set_is_in_game(true);
- c.set_is_joined_mid_game(true);
-
- {
- let teams = info.client_teams(c.id);
- c.teams_in_game = teams.clone().count() as u8;
- c.clan = teams.clone().next().map(|t| t.color);
- team_names = teams.map(|t| t.name.clone()).collect();
- }
-
- if !team_names.is_empty() {
- info.left_teams.retain(|name|
- !team_names.contains(&name));
- info.teams_in_game += team_names.len() as u8;
- r.teams = info.teams_at_start.iter()
- .filter(|(_, t)| !team_names.contains(&t.name))
- .cloned().collect();
- }
- } else {
- team_names = Vec::new();
- }
-
- v.push(SendRoomData{ to: client_id, teams: true, config: true, flags: true});
-
- if let Some(ref info) = r.game_info {
- v.push(RunGame.send_self().action());
- v.push(ClientFlags("+g".to_string(), vec![c.nick.clone()])
- .send_all().in_room(r.id).action());
- v.push(ForwardEngineMessage(
- vec![to_engine_msg("e$spectate 1".bytes())])
- .send_self().action());
- v.push(ForwardEngineMessage(info.msg_log.clone())
- .send_self().action());
-
- for name in &team_names {
- v.push(ForwardEngineMessage(
- vec![to_engine_msg(once(b'G').chain(name.bytes()))])
- .send_all().in_room(r.id).action());
- }
- if info.is_paused {
- v.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))])
- .send_all().in_room(r.id).action())
- }
- }
- }
- server.react(client_id, v);
- }
- SendRoomData {to, teams, config, flags} => {
- let mut actions = Vec::new();
- let room_id = server.clients[client_id].room_id;
- if let Some(r) = room_id.and_then(|id| server.rooms.get(id)) {
- if config {
- actions.push(ConfigEntry("FULLMAPCONFIG".to_string(), r.map_config())
- .send(to).action());
- for cfg in r.game_config() {
- actions.push(cfg.to_server_msg().send(to).action());
- }
- }
- if teams {
- let current_teams = match r.game_info {
- Some(ref info) => &info.teams_at_start,
- None => &r.teams
- };
- for (owner_id, team) in current_teams.iter() {
- actions.push(TeamAdd(HWRoom::team_info(&server.clients[*owner_id], &team))
- .send(to).action());
- actions.push(TeamColor(team.name.clone(), team.color)
- .send(to).action());
- actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
- .send(to).action());
- }
- }
- if flags {
- if let Some(id) = r.master_id {
- actions.push(ClientFlags("+h".to_string(), vec![server.clients[id].nick.clone()])
- .send(to).action());
- }
- let nicks: Vec<_> = server.clients.iter()
- .filter(|(_, c)| c.room_id == Some(r.id) && c.is_ready())
- .map(|(_, c)| c.nick.clone()).collect();
- if !nicks.is_empty() {
- actions.push(ClientFlags("+r".to_string(), nicks)
- .send(to).action());
- }
- }
- }
- server.react(client_id, actions);
- }
- AddVote{vote, is_forced} => {
- let mut actions = Vec::new();
- if let Some(r) = server.room(client_id) {
- let mut result = None;
- if let Some(ref mut voting) = r.voting {
- if is_forced || voting.votes.iter().all(|(id, _)| client_id != *id) {
- actions.push(server_chat("Your vote has been counted.".to_string())
- .send_self().action());
- voting.votes.push((client_id, vote));
- let i = voting.votes.iter();
- let pro = i.clone().filter(|(_, v)| *v).count();
- let contra = i.filter(|(_, v)| !*v).count();
- let success_quota = voting.voters.len() / 2 + 1;
- if is_forced && vote || pro >= success_quota {
- result = Some(true);
- } else if is_forced && !vote || contra > voting.voters.len() - success_quota {
- result = Some(false);
- }
- } else {
- actions.push(server_chat("You already have voted.".to_string())
- .send_self().action());
- }
- } else {
- actions.push(server_chat("There's no voting going on.".to_string())
- .send_self().action());
- }
-
- if let Some(res) = result {
- actions.push(server_chat("Voting closed.".to_string())
- .send_all().in_room(r.id).action());
- let voting = replace(&mut r.voting, None).unwrap();
- if res {
- actions.push(ApplyVoting(voting.kind, r.id));
- }
- }
- }
-
- server.react(client_id, actions);
- }
- ApplyVoting(kind, room_id) => {
- let mut actions = Vec::new();
- let mut id = client_id;
- match kind {
- VoteType::Kick(nick) => {
- if let Some(c) = server.find_client(&nick) {
- if c.room_id == Some(room_id) {
- id = c.id;
- actions.push(Kicked.send_self().action());
- actions.push(MoveToLobby("kicked".to_string()));
- }
- }
- },
- VoteType::Map(None) => (),
- VoteType::Map(Some(name)) => {
- if let Some(location) = server.rooms[room_id].load_config(&name) {
- actions.push(server_chat(location.to_string())
- .send_all().in_room(room_id).action());
- actions.push(SendRoomUpdate(None));
- for (_, c) in server.clients.iter() {
- if c.room_id == Some(room_id) {
- actions.push(SendRoomData{
- to: c.id, teams: false,
- config: true, flags: false})
- }
- }
- }
- },
- VoteType::Pause => {
- if let Some(ref mut info) = server.rooms[room_id].game_info {
- info.is_paused = !info.is_paused;
- actions.push(server_chat("Pause toggled.".to_string())
- .send_all().in_room(room_id).action());
- actions.push(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))])
- .send_all().in_room(room_id).action());
- }
- },
- VoteType::NewSeed => {
- let seed = thread_rng().gen_range(0, 1_000_000_000).to_string();
- let cfg = GameCfg::Seed(seed);
- actions.push(cfg.to_server_msg().send_all().in_room(room_id).action());
- server.rooms[room_id].set_config(cfg);
- },
- VoteType::HedgehogsPerTeam(number) => {
- let r = &mut server.rooms[room_id];
- let nicks = r.set_hedgehogs_number(number);
- actions.extend(nicks.into_iter().map(|n|
- HedgehogsNumber(n, number).send_all().in_room(room_id).action()
- ));
- },
- }
- server.react(id, actions);
- }
- MoveToLobby(msg) => {
- let mut actions = Vec::new();
- let lobby_id = server.lobby_id;
- if let (c, Some(r)) = server.client_and_room(client_id) {
- r.players_number -= 1;
- if c.is_ready() && r.ready_players_number > 0 {
- r.ready_players_number -= 1;
- }
- if c.is_master() && (r.players_number > 0 || r.is_fixed()) {
- actions.push(ChangeMaster(r.id, None));
- }
- actions.push(ClientFlags("-i".to_string(), vec![c.nick.clone()])
- .send_all().action());
- }
- server.react(client_id, actions);
- actions = Vec::new();
-
- if let (c, Some(r)) = server.client_and_room(client_id) {
- c.room_id = Some(lobby_id);
- if r.players_number == 0 && !r.is_fixed() {
- actions.push(RemoveRoom(r.id));
- } else {
- actions.push(RemoveClientTeams);
- actions.push(RoomLeft(c.nick.clone(), msg)
- .send_all().in_room(r.id).but_self().action());
- actions.push(SendRoomUpdate(Some(r.name.clone())));
- }
- }
- server.react(client_id, actions)
- }
- ChangeMaster(room_id, new_id) => {
- let mut actions = Vec::new();
- let room_client_ids = server.room_clients(room_id);
- let new_id = if server.room(client_id).map(|r| r.is_fixed()).unwrap_or(false) {
- new_id
- } else {
- new_id.or_else(||
- room_client_ids.iter().find(|id| **id != client_id).cloned())
- };
- let new_nick = new_id.map(|id| server.clients[id].nick.clone());
-
- if let (c, Some(r)) = server.client_and_room(client_id) {
- match r.master_id {
- Some(id) if id == c.id => {
- c.set_is_master(false);
- r.master_id = None;
- actions.push(ClientFlags("-h".to_string(), vec![c.nick.clone()])
- .send_all().in_room(r.id).action());
- }
- Some(_) => unreachable!(),
- None => {}
- }
- r.master_id = new_id;
- if !r.is_fixed() && c.protocol_number < 42 {
- r.name.replace_range(.., new_nick.as_ref().map_or("[]", String::as_str));
- }
- r.set_join_restriction(false);
- r.set_team_add_restriction(false);
- let is_fixed = r.is_fixed();
- r.set_unregistered_players_restriction(is_fixed);
- if let Some(nick) = new_nick {
- actions.push(ClientFlags("+h".to_string(), vec![nick])
- .send_all().in_room(r.id).action());
- }
- }
- if let Some(id) = new_id {
- server.clients[id].set_is_master(true)
- }
- server.react(client_id, actions);
- }
- RemoveTeam(name) => {
- let mut actions = Vec::new();
- if let (c, Some(r)) = server.client_and_room(client_id) {
- r.remove_team(&name);
- if let Some(ref mut info) = r.game_info {
- info.left_teams.push(name.clone());
- }
- actions.push(TeamRemove(name.clone()).send_all().in_room(r.id).action());
- actions.push(SendRoomUpdate(None));
- if r.game_info.is_some() && c.is_in_game() {
- actions.push(SendTeamRemovalMessage(name));
- }
- }
- server.react(client_id, actions);
- },
- RemoveClientTeams => {
- if let (c, Some(r)) = server.client_and_room(client_id) {
- let actions = r.client_teams(c.id).map(|t| RemoveTeam(t.name.clone())).collect();
- server.react(client_id, actions);
- }
- }
- SendRoomUpdate(old_name) => {
- if let (c, Some(r)) = server.client_and_room(client_id) {
- let name = old_name.unwrap_or_else(|| r.name.clone());
- let actions = vec![RoomUpdated(name, r.info(Some(&c)))
- .send_all().with_protocol(r.protocol_number).action()];
- server.react(client_id, actions);
- }
- },
- StartRoomGame(room_id) => {
- let actions = {
- let (room_clients, room_nicks): (Vec<_>, Vec<_>) = server.clients.iter()
- .map(|(id, c)| (id, c.nick.clone())).unzip();
- let room = &mut server.rooms[room_id];
-
- if !room.has_multiple_clans() {
- vec![Warn("The game can't be started with less than two clans!".to_string())]
- } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number {
- vec![Warn("Not all players are ready".to_string())]
- } else if room.game_info.is_some() {
- vec![Warn("The game is already in progress".to_string())]
- } else {
- room.start_round();
- for id in room_clients {
- let c = &mut server.clients[id];
- c.set_is_in_game(false);
- c.team_indices = room.client_team_indices(c.id);
- }
- vec![RunGame.send_all().in_room(room.id).action(),
- SendRoomUpdate(None),
- ClientFlags("+g".to_string(), room_nicks)
- .send_all().in_room(room.id).action()]
- }
- };
- server.react(client_id, actions);
- }
- SendTeamRemovalMessage(team_name) => {
- let mut actions = Vec::new();
- if let Some(r) = server.room(client_id) {
- if let Some(ref mut info) = r.game_info {
- let msg = once(b'F').chain(team_name.bytes());
- actions.push(ForwardEngineMessage(vec![to_engine_msg(msg)]).
- send_all().in_room(r.id).but_self().action());
- info.teams_in_game -= 1;
- if info.teams_in_game == 0 {
- actions.push(FinishRoomGame(r.id));
- }
- let remove_msg = to_engine_msg(once(b'F').chain(team_name.bytes()));
- if let Some(m) = &info.sync_msg {
- info.msg_log.push(m.clone());
- }
- if info.sync_msg.is_some() {
- info.sync_msg = None
- }
- info.msg_log.push(remove_msg.clone());
- actions.push(ForwardEngineMessage(vec![remove_msg])
- .send_all().in_room(r.id).but_self().action());
- }
- }
- server.react(client_id, actions);
- }
- FinishRoomGame(room_id) => {
- let mut actions = Vec::new();
-
- let r = &mut server.rooms[room_id];
- r.ready_players_number = 1;
- actions.push(SendRoomUpdate(None));
- actions.push(RoundFinished.send_all().in_room(r.id).action());
-
- if let Some(info) = replace(&mut r.game_info, None) {
- for (_, c) in server.clients.iter() {
- if c.room_id == Some(room_id) && c.is_joined_mid_game() {
- actions.push(SendRoomData{
- to: c.id, teams: false,
- config: true, flags: false});
- for name in &info.left_teams {
- actions.push(TeamRemove(name.clone())
- .send(c.id).action());
- }
- }
- }
- }
-
- let nicks: Vec<_> = server.clients.iter_mut()
- .filter(|(_, c)| c.room_id == Some(room_id))
- .map(|(_, c)| {
- c.set_is_ready(c.is_master());
- c.set_is_joined_mid_game(false);
- c
- }).filter_map(|c| if !c.is_master() {
- Some(c.nick.clone())
- } else {
- None
- }).collect();
-
- if !nicks.is_empty() {
- let msg = if r.protocol_number < 38 {
- LegacyReady(false, nicks)
- } else {
- ClientFlags("-r".to_string(), nicks)
- };
- actions.push(msg.send_all().in_room(room_id).action());
- }
- server.react(client_id, actions);
- }
- Warn(msg) => {
- run_action(server, client_id, Warning(msg).send_self().action());
- }
- ProtocolError(msg) => {
- run_action(server, client_id, Error(msg).send_self().action())
- }
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/client.rs
--- a/gameServer2/src/server/client.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-use super::coretypes::ClientId;
-use bitflags::*;
-
-bitflags!{
- pub struct ClientFlags: u8 {
- const IS_ADMIN = 0b0000_0001;
- const IS_MASTER = 0b0000_0010;
- const IS_READY = 0b0000_0100;
- const IS_IN_GAME = 0b0000_1000;
- const IS_JOINED_MID_GAME = 0b0001_0000;
- const IS_CHECKER = 0b0010_0000;
-
- const NONE = 0b0000_0000;
- const DEFAULT = Self::NONE.bits;
- }
-}
-
-pub struct HWClient {
- pub id: ClientId,
- pub room_id: Option,
- pub nick: String,
- pub web_password: String,
- pub server_salt: String,
- pub protocol_number: u16,
- pub flags: ClientFlags,
- pub teams_in_game: u8,
- pub team_indices: Vec,
- pub clan: Option
-}
-
-impl HWClient {
- pub fn new(id: ClientId, salt: String) -> HWClient {
- HWClient {
- id,
- room_id: None,
- nick: String::new(),
- web_password: String::new(),
- server_salt: salt,
- protocol_number: 0,
- flags: ClientFlags::DEFAULT,
- teams_in_game: 0,
- team_indices: Vec::new(),
- clan: None,
- }
- }
-
- fn contains(& self, mask: ClientFlags) -> bool {
- self.flags.contains(mask)
- }
-
- fn set(&mut self, mask: ClientFlags, value: bool) {
- self.flags.set(mask, value);
- }
-
- pub fn is_admin(&self)-> bool { self.contains(ClientFlags::IS_ADMIN) }
- pub fn is_master(&self)-> bool { self.contains(ClientFlags::IS_MASTER) }
- pub fn is_ready(&self)-> bool { self.contains(ClientFlags::IS_READY) }
- pub fn is_in_game(&self)-> bool { self.contains(ClientFlags::IS_IN_GAME) }
- pub fn is_joined_mid_game(&self)-> bool { self.contains(ClientFlags::IS_JOINED_MID_GAME) }
- pub fn is_checker(&self)-> bool { self.contains(ClientFlags::IS_CHECKER) }
-
- pub fn set_is_admin(&mut self, value: bool) { self.set(ClientFlags::IS_ADMIN, value) }
- pub fn set_is_master(&mut self, value: bool) { self.set(ClientFlags::IS_MASTER, value) }
- pub fn set_is_ready(&mut self, value: bool) { self.set(ClientFlags::IS_READY, value) }
- pub fn set_is_in_game(&mut self, value: bool) { self.set(ClientFlags::IS_IN_GAME, value) }
- pub fn set_is_joined_mid_game(&mut self, value: bool) { self.set(ClientFlags::IS_JOINED_MID_GAME, value) }
- pub fn set_is_checker(&mut self, value: bool) { self.set(ClientFlags::IS_CHECKER, value) }
-}
\ No newline at end of file
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/coretypes.rs
--- a/gameServer2/src/server/coretypes.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-pub type ClientId = usize;
-pub type RoomId = usize;
-
-pub const MAX_HEDGEHOGS_PER_TEAM: u8 = 8;
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum ServerVar {
- MOTDNew(String),
- MOTDOld(String),
- LatestProto(u32),
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum GameCfg {
- FeatureSize(u32),
- MapType(String),
- MapGenerator(u32),
- MazeSize(u32),
- Seed(String),
- Template(u32),
-
- Ammo(String, Option),
- Scheme(String, Vec),
- Script(String),
- Theme(String),
- DrawnMap(String)
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct TeamInfo {
- pub name: String,
- pub color: u8,
- pub grave: String,
- pub fort: String,
- pub voice_pack: String,
- pub flag: String,
- pub difficulty: u8,
- pub hedgehogs_number: u8,
- pub hedgehogs: [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize],
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct HedgehogInfo {
- pub name: String,
- pub hat: String,
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum VoteType {
- Kick(String),
- Map(Option),
- Pause,
- NewSeed,
- HedgehogsPerTeam(u8)
-}
-
-#[derive(Clone, Debug)]
-pub struct Voting {
- pub ttl: u32,
- pub voters: Vec,
- pub votes: Vec<(ClientId, bool)>,
- pub kind: VoteType
-}
-
-impl Voting {
- pub fn new(kind: VoteType, voters: Vec) -> Voting {
- Voting {
- kind, voters, ttl: 2,
- votes: Vec::new()
- }
- }
-}
\ No newline at end of file
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/handlers/checker.rs
--- a/gameServer2/src/server/handlers/checker.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-use mio;
-use log::*;
-
-use crate::{
- server::{
- server::HWServer,
- coretypes::ClientId,
- },
- protocol::messages::{
- HWProtocolMessage
- },
-};
-
-pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
- match message {
- _ => warn!("Unknown command"),
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/handlers/common.rs
--- a/gameServer2/src/server/handlers/common.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-use crate::{
- server::{actions::Action, server::HWServer},
- protocol::messages::{
- HWProtocolMessage::{self, Rnd}, HWServerMessage::{self, ChatMsg},
- }
-};
-use rand::{self, Rng, thread_rng};
-
-pub fn rnd_reply(options: &[String]) -> HWServerMessage {
- let mut rng = thread_rng();
- let reply = if options.is_empty() {
- (*rng.choose(&["heads", "tails"]).unwrap()).to_owned()
- } else {
- rng.choose(&options).unwrap().clone()
- };
-
- ChatMsg {
- nick: "[random]".to_owned(),
- msg: reply.clone(),
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::protocol::messages::HWServerMessage::ChatMsg;
- use crate::server::actions::{
- Action::{self, Send}, PendingMessage,
- };
-
- fn reply2string(r: HWServerMessage) -> String {
- match r {
- ChatMsg { msg: p, .. } => String::from(p),
- _ => panic!("expected a ChatMsg"),
- }
- }
-
- fn run_handle_test(opts: Vec) {
- let opts2 = opts.clone();
- for opt in opts {
- while reply2string(rnd_reply(&opts2)) != opt {}
- }
- }
-
- /// This test terminates almost surely.
- #[test]
- fn test_handle_rnd_empty() {
- run_handle_test(vec![])
- }
-
- /// This test terminates almost surely.
- #[test]
- fn test_handle_rnd_nonempty() {
- run_handle_test(vec!["A".to_owned(), "B".to_owned(), "C".to_owned()])
- }
-
- /// This test terminates almost surely (strong law of large numbers)
- #[test]
- fn test_distribution() {
- let eps = 0.000001;
- let lim = 0.5;
- let opts = vec![0.to_string(), 1.to_string()];
- let mut ones = 0;
- let mut tries = 0;
-
- while tries < 1000 || ((ones as f64 / tries as f64) - lim).abs() >= eps {
- tries += 1;
- if reply2string(rnd_reply(&opts)) == 1.to_string() {
- ones += 1;
- }
- }
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/handlers/inroom.rs
--- a/gameServer2/src/server/handlers/inroom.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,499 +0,0 @@
-use mio;
-
-use crate::{
- server::{
- coretypes::{
- ClientId, RoomId, Voting, VoteType, GameCfg,
- MAX_HEDGEHOGS_PER_TEAM
- },
- server::HWServer,
- room::{HWRoom, RoomFlags},
- actions::{Action, Action::*}
- },
- protocol::messages::{
- HWProtocolMessage,
- HWServerMessage::*,
- server_chat
- },
- utils::is_name_illegal
-};
-use std::{
- mem::swap, fs::{File, OpenOptions},
- io::{Read, Write, Result, Error, ErrorKind}
-};
-use base64::{encode, decode};
-use super::common::rnd_reply;
-use log::*;
-
-#[derive(Clone)]
-struct ByMsg<'a> {
- messages: &'a[u8]
-}
-
-impl <'a> Iterator for ByMsg<'a> {
- type Item = &'a[u8];
-
- fn next(&mut self) -> Option<::Item> {
- if let Some(size) = self.messages.get(0) {
- let (msg, next) = self.messages.split_at(*size as usize + 1);
- self.messages = next;
- Some(msg)
- } else {
- None
- }
- }
-}
-
-fn by_msg(source: &[u8]) -> ByMsg {
- ByMsg {messages: source}
-}
-
-const VALID_MESSAGES: &[u8] =
- b"M#+LlRrUuDdZzAaSjJ,NpPwtgfhbc12345\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A";
-const NON_TIMED_MESSAGES: &[u8] = b"M#hb";
-
-#[cfg(canhazslicepatterns)]
-fn is_msg_valid(msg: &[u8], team_indices: &[u8]) -> bool {
- match msg {
- [size, typ, body..] => VALID_MESSAGES.contains(typ)
- && match body {
- [1...MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' =>
- team_indices.contains(team),
- _ => *typ != b'h'
- },
- _ => false
- }
-}
-
-fn is_msg_valid(msg: &[u8], _team_indices: &[u8]) -> bool {
- if let Some(typ) = msg.get(1) {
- VALID_MESSAGES.contains(typ)
- } else {
- false
- }
-}
-
-fn is_msg_empty(msg: &[u8]) -> bool {
- msg.get(1).filter(|t| **t == b'+').is_some()
-}
-
-fn is_msg_timed(msg: &[u8]) -> bool {
- msg.get(1).filter(|t| !NON_TIMED_MESSAGES.contains(t)).is_some()
-}
-
-fn voting_description(kind: &VoteType) -> String {
- format!("New voting started: {}", match kind {
- VoteType::Kick(nick) => format!("kick {}", nick),
- VoteType::Map(name) => format!("map {}", name.as_ref().unwrap()),
- VoteType::Pause => "pause".to_string(),
- VoteType::NewSeed => "new seed".to_string(),
- VoteType::HedgehogsPerTeam(number) => format!("hedgehogs per team: {}", number)
- })
-}
-
-fn room_message_flag(msg: &HWProtocolMessage) -> RoomFlags {
- use crate::protocol::messages::HWProtocolMessage::*;
- match msg {
- ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN,
- ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD,
- ToggleRegisteredOnly => RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS,
- _ => RoomFlags::empty()
- }
-}
-
-fn read_file(filename: &str) -> Result {
- let mut reader = File::open(filename)?;
- let mut result = String::new();
- reader.read_to_string(&mut result)?;
- Ok(result)
-}
-
-fn write_file(filename: &str, content: &str) -> Result<()> {
- let mut writer = OpenOptions::new().create(true).write(true).open(filename)?;
- writer.write_all(content.as_bytes())
-}
-
-pub fn handle(server: &mut HWServer, client_id: ClientId, room_id: RoomId, message: HWProtocolMessage) {
- use crate::protocol::messages::HWProtocolMessage::*;
- match message {
- Part(None) => server.react(client_id, vec![
- MoveToLobby("part".to_string())]),
- Part(Some(msg)) => server.react(client_id, vec![
- MoveToLobby(format!("part: {}", msg))]),
- Chat(msg) => {
- let actions = {
- let c = &mut server.clients[client_id];
- let chat_msg = ChatMsg {nick: c.nick.clone(), msg};
- vec![chat_msg.send_all().in_room(room_id).but_self().action()]
- };
- server.react(client_id, actions);
- },
- Fix => {
- if let (c, Some(r)) = server.client_and_room(client_id) {
- if c.is_admin() { r.set_is_fixed(true) }
- }
- }
- Unfix => {
- if let (c, Some(r)) = server.client_and_room(client_id) {
- if c.is_admin() { r.set_is_fixed(false) }
- }
- }
- Greeting(text) => {
- if let (c, Some(r)) = server.client_and_room(client_id) {
- if c.is_admin() || c.is_master() && !r.is_fixed() {
- r.greeting = text
- }
- }
- }
- RoomName(new_name) => {
- let actions =
- if is_name_illegal(&new_name) {
- vec![Warn("Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())]
- } else if server.rooms[room_id].is_fixed() {
- vec![Warn("Access denied.".to_string())]
- } else if server.has_room(&new_name) {
- vec![Warn("A room with the same name already exists.".to_string())]
- } else {
- let mut old_name = new_name.clone();
- swap(&mut server.rooms[room_id].name, &mut old_name);
- vec![SendRoomUpdate(Some(old_name))]
- };
- server.react(client_id, actions);
- },
- ToggleReady => {
- if let (c, Some(r)) = server.client_and_room(client_id) {
- let flags = if c.is_ready() {
- r.ready_players_number -= 1;
- "-r"
- } else {
- r.ready_players_number += 1;
- "+r"
- };
-
- let msg = if c.protocol_number < 38 {
- LegacyReady(c.is_ready(), vec![c.nick.clone()])
- } else {
- ClientFlags(flags.to_string(), vec![c.nick.clone()])
- };
-
- let mut v = vec![msg.send_all().in_room(r.id).action()];
-
- if r.is_fixed() && r.ready_players_number == r.players_number {
- v.push(StartRoomGame(r.id))
- }
-
- c.set_is_ready(!c.is_ready());
- server.react(client_id, v);
- }
- }
- AddTeam(info) => {
- let mut actions = Vec::new();
- if let (c, Some(r)) = server.client_and_room(client_id) {
- if r.teams.len() >= r.team_limit as usize {
- actions.push(Warn("Too many teams!".to_string()))
- } else if r.addable_hedgehogs() == 0 {
- actions.push(Warn("Too many hedgehogs!".to_string()))
- } else if r.find_team(|t| t.name == info.name) != None {
- actions.push(Warn("There's already a team with same name in the list.".to_string()))
- } else if r.game_info.is_some() {
- actions.push(Warn("Joining not possible: Round is in progress.".to_string()))
- } else if r.is_team_add_restricted() {
- actions.push(Warn("This room currently does not allow adding new teams.".to_string()));
- } else {
- let team = r.add_team(c.id, *info, c.protocol_number < 42);
- c.teams_in_game += 1;
- c.clan = Some(team.color);
- actions.push(TeamAccepted(team.name.clone())
- .send_self().action());
- actions.push(TeamAdd(HWRoom::team_info(&c, team))
- .send_all().in_room(room_id).but_self().action());
- actions.push(TeamColor(team.name.clone(), team.color)
- .send_all().in_room(room_id).action());
- actions.push(HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
- .send_all().in_room(room_id).action());
- actions.push(SendRoomUpdate(None));
- }
- }
- server.react(client_id, actions);
- },
- RemoveTeam(name) => {
- let mut actions = Vec::new();
- if let (c, Some(r)) = server.client_and_room(client_id) {
- match r.find_team_owner(&name) {
- None =>
- actions.push(Warn("Error: The team you tried to remove does not exist.".to_string())),
- Some((id, _)) if id != client_id =>
- actions.push(Warn("You can't remove a team you don't own.".to_string())),
- Some((_, name)) => {
- c.teams_in_game -= 1;
- c.clan = r.find_team_color(c.id);
- actions.push(Action::RemoveTeam(name.to_string()));
- }
- }
- };
- server.react(client_id, actions);
- },
- SetHedgehogsNumber(team_name, number) => {
- if let (c, Some(r)) = server.client_and_room(client_id) {
- let addable_hedgehogs = r.addable_hedgehogs();
- let actions = if let Some((_, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) {
- if !c.is_master() {
- vec![ProtocolError("You're not the room master!".to_string())]
- } else if number < 1 || number > MAX_HEDGEHOGS_PER_TEAM
- || number > addable_hedgehogs + team.hedgehogs_number {
- vec![HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
- .send_self().action()]
- } else {
- team.hedgehogs_number = number;
- vec![HedgehogsNumber(team.name.clone(), number)
- .send_all().in_room(room_id).but_self().action()]
- }
- } else {
- vec![(Warn("No such team.".to_string()))]
- };
- server.react(client_id, actions);
- }
- },
- SetTeamColor(team_name, color) => {
- if let (c, Some(r)) = server.client_and_room(client_id) {
- let mut owner_id = None;
- let actions = if let Some((owner, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) {
- if !c.is_master() {
- vec![ProtocolError("You're not the room master!".to_string())]
- } else if false {
- Vec::new()
- } else {
- owner_id = Some(owner);
- team.color = color;
- vec![TeamColor(team.name.clone(), color)
- .send_all().in_room(room_id).but_self().action()]
- }
- } else {
- vec![(Warn("No such team.".to_string()))]
- };
-
- if let Some(id) = owner_id {
- server.clients[id].clan = Some(color);
- }
-
- server.react(client_id, actions);
- };
- },
- Cfg(cfg) => {
- if let (c, Some(r)) = server.client_and_room(client_id) {
- let actions = if r.is_fixed() {
- vec![Warn("Access denied.".to_string())]
- } else if !c.is_master() {
- vec![ProtocolError("You're not the room master!".to_string())]
- } else {
- let cfg = match cfg {
- GameCfg::Scheme(name, mut values) => {
- if c.protocol_number == 49 && values.len() >= 2 {
- let mut s = "X".repeat(50);
- s.push_str(&values.pop().unwrap());
- values.push(s);
- }
- GameCfg::Scheme(name, values)
- }
- cfg => cfg
- };
-
- let v = vec![cfg.to_server_msg()
- .send_all().in_room(r.id).but_self().action()];
- r.set_config(cfg);
- v
- };
- server.react(client_id, actions);
- }
- }
- Save(name, location) => {
- let actions = vec![server_chat(format!("Room config saved as {}", name))
- .send_all().in_room(room_id).action()];
- server.rooms[room_id].save_config(name, location);
- server.react(client_id, actions);
- }
- SaveRoom(filename) => {
- if server.clients[client_id].is_admin() {
- let actions = match server.rooms[room_id].get_saves() {
- Ok(text) => match write_file(&filename, &text) {
- Ok(_) => vec![server_chat("Room configs saved successfully.".to_string())
- .send_self().action()],
- Err(e) => {
- warn!("Error while writing the config file \"{}\": {}", filename, e);
- vec![Warn("Unable to save the room configs.".to_string())]
- }
- }
- Err(e) => {
- warn!("Error while serializing the room configs: {}", e);
- vec![Warn("Unable to serialize the room configs.".to_string())]
- }
- };
- server.react(client_id, actions);
- }
- }
- LoadRoom(filename) => {
- if server.clients[client_id].is_admin() {
- let actions = match read_file(&filename) {
- Ok(text) => match server.rooms[room_id].set_saves(&text) {
- Ok(_) => vec![server_chat("Room configs loaded successfully.".to_string())
- .send_self().action()],
- Err(e) => {
- warn!("Error while deserializing the room configs: {}", e);
- vec![Warn("Unable to deserialize the room configs.".to_string())]
- }
- }
- Err(e) => {
- warn!("Error while reading the config file \"{}\": {}", filename, e);
- vec![Warn("Unable to load the room configs.".to_string())]
- }
- };
- server.react(client_id, actions);
- }
- }
- Delete(name) => {
- let actions = if !server.rooms[room_id].delete_config(&name) {
- vec![Warn(format!("Save doesn't exist: {}", name))]
- } else {
- vec![server_chat(format!("Room config {} has been deleted", name))
- .send_all().in_room(room_id).action()]
- };
- server.react(client_id, actions);
- }
- CallVote(None) => {
- server.react(client_id, vec![
- server_chat("Available callvote commands: kick , map , pause, newseed, hedgehogs ".to_string())
- .send_self().action()])
- }
- CallVote(Some(kind)) => {
- let is_in_game = server.rooms[room_id].game_info.is_some();
- let error = match &kind {
- VoteType::Kick(nick) => {
- if server.find_client(&nick).filter(|c| c.room_id == Some(room_id)).is_some() {
- None
- } else {
- Some("/callvote kick: No such user!".to_string())
- }
- },
- VoteType::Map(None) => {
- let names: Vec<_> = server.rooms[room_id].saves.keys().cloned().collect();
- if names.is_empty() {
- Some("/callvote map: No maps saved in this room!".to_string())
- } else {
- Some(format!("Available maps: {}", names.join(", ")))
- }
- },
- VoteType::Map(Some(name)) => {
- if server.rooms[room_id].saves.get(&name[..]).is_some() {
- None
- } else {
- Some("/callvote map: No such map!".to_string())
- }
- },
- VoteType::Pause => {
- if is_in_game {
- None
- } else {
- Some("/callvote pause: No game in progress!".to_string())
- }
- },
- VoteType::NewSeed => {
- None
- },
- VoteType::HedgehogsPerTeam(number) => {
- match number {
- 1...MAX_HEDGEHOGS_PER_TEAM => None,
- _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string())
- }
- },
- };
- match error {
- None => {
- let msg = voting_description(&kind);
- let voting = Voting::new(kind, server.room_clients(client_id));
- server.rooms[room_id].voting = Some(voting);
- server.react(client_id, vec![
- server_chat(msg).send_all().in_room(room_id).action(),
- AddVote{ vote: true, is_forced: false}]);
- }
- Some(msg) => {
- server.react(client_id, vec![
- server_chat(msg).send_self().action()])
- }
- }
- }
- Vote(vote) => {
- server.react(client_id, vec![AddVote{ vote, is_forced: false }]);
- }
- ForceVote(vote) => {
- let is_forced = server.clients[client_id].is_admin();
- server.react(client_id, vec![AddVote{ vote, is_forced }]);
- }
- ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly => {
- if server.clients[client_id].is_master() {
- server.rooms[room_id].flags.toggle(room_message_flag(&message));
- }
- server.react(client_id, vec![SendRoomUpdate(None)]);
- }
- StartGame => {
- server.react(client_id, vec![StartRoomGame(room_id)]);
- }
- EngineMessage(em) => {
- let mut actions = Vec::new();
- if let (c, Some(r)) = server.client_and_room(client_id) {
- if c.teams_in_game > 0 {
- let decoding = decode(&em[..]).unwrap();
- let messages = by_msg(&decoding);
- let valid = messages.filter(|m| is_msg_valid(m, &c.team_indices));
- let non_empty = valid.clone().filter(|m| !is_msg_empty(m));
- let sync_msg = valid.clone().filter(|m| is_msg_timed(m))
- .last().map(|m| if is_msg_empty(m) {Some(encode(m))} else {None});
-
- let em_response = encode(&valid.flat_map(|msg| msg).cloned().collect::>());
- if !em_response.is_empty() {
- actions.push(ForwardEngineMessage(vec![em_response])
- .send_all().in_room(r.id).but_self().action());
- }
- let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::>());
- if let Some(ref mut info) = r.game_info {
- if !em_log.is_empty() {
- info.msg_log.push(em_log);
- }
- if let Some(msg) = sync_msg {
- info.sync_msg = msg;
- }
- }
- }
- }
- server.react(client_id, actions)
- }
- RoundFinished => {
- let mut actions = Vec::new();
- if let (c, Some(r)) = server.client_and_room(client_id) {
- if c.is_in_game() {
- c.set_is_in_game(false);
- actions.push(ClientFlags("-g".to_string(), vec![c.nick.clone()]).
- send_all().in_room(r.id).action());
- if r.game_info.is_some() {
- for team in r.client_teams(c.id) {
- actions.push(SendTeamRemovalMessage(team.name.clone()));
- }
- }
- }
- }
- server.react(client_id, actions)
- },
- Rnd(v) => {
- let result = rnd_reply(&v);
- let mut echo = vec!["/rnd".to_string()];
- echo.extend(v.into_iter());
- let chat_msg = ChatMsg {
- nick: server.clients[client_id].nick.clone(),
- msg: echo.join(" ")
- };
- server.react(client_id, vec![
- chat_msg.send_all().in_room(room_id).action(),
- result.send_all().in_room(room_id).action()])
- },
- _ => warn!("Unimplemented!")
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/handlers/lobby.rs
--- a/gameServer2/src/server/handlers/lobby.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-use mio;
-
-use crate::{
- server::{
- server::HWServer,
- coretypes::ClientId,
- actions::{Action, Action::*}
- },
- protocol::messages::{
- HWProtocolMessage,
- HWServerMessage::*
- },
- utils::is_name_illegal
-};
-use super::common::rnd_reply;
-use log::*;
-
-pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
- use crate::protocol::messages::HWProtocolMessage::*;
- match message {
- CreateRoom(name, password) => {
- let actions =
- if is_name_illegal(&name) {
- vec![Warn("Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())]
- } else if server.has_room(&name) {
- vec![Warn("A room with the same name already exists.".to_string())]
- } else {
- let flags_msg = ClientFlags(
- "+hr".to_string(),
- vec![server.clients[client_id].nick.clone()]);
- vec![AddRoom(name, password),
- flags_msg.send_self().action()]
- };
- server.react(client_id, actions);
- },
- Chat(msg) => {
- let actions = vec![ChatMsg {nick: server.clients[client_id].nick.clone(), msg}
- .send_all().in_room(server.lobby_id).but_self().action()];
- server.react(client_id, actions);
- },
- JoinRoom(name, _password) => {
- let room = server.rooms.iter().find(|(_, r)| r.name == name);
- let room_id = room.map(|(_, r)| r.id);
- let nicks = server.clients.iter()
- .filter(|(_, c)| c.room_id == room_id)
- .map(|(_, c)| c.nick.clone())
- .collect();
- let c = &mut server.clients[client_id];
-
- let actions = if let Some((_, r)) = room {
- if c.protocol_number != r.protocol_number {
- vec![Warn("Room version incompatible to your Hedgewars version!".to_string())]
- } else if r.is_join_restricted() {
- vec![Warn("Access denied. This room currently doesn't allow joining.".to_string())]
- } else if r.players_number == u8::max_value() {
- vec![Warn("This room is already full".to_string())]
- } else {
- vec![MoveToRoom(r.id),
- RoomJoined(nicks).send_self().action()]
- }
- } else {
- vec![Warn("No such room.".to_string())]
- };
- server.react(client_id, actions);
- },
- Rnd(v) => {
- server.react(client_id, vec![rnd_reply(&v).send_self().action()]);
- },
- List => warn!("Deprecated LIST message received"),
- _ => warn!("Incorrect command in lobby state"),
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/handlers/loggingin.rs
--- a/gameServer2/src/server/handlers/loggingin.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-use mio;
-
-use crate::{
- server::{
- client::HWClient,
- server::HWServer,
- coretypes::ClientId,
- actions::{Action, Action::*}
- },
- protocol::messages::{
- HWProtocolMessage, HWServerMessage::*
- },
- utils::is_name_illegal
-};
-#[cfg(feature = "official-server")]
-use openssl::sha::sha1;
-use std::fmt::{Formatter, LowerHex};
-use log::*;
-
-#[derive(PartialEq)]
-struct Sha1Digest([u8; 20]);
-
-impl LowerHex for Sha1Digest {
- fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
- for byte in &self.0 {
- write!(f, "{:02x}", byte)?;
- }
- Ok(())
- }
-}
-
-#[cfg(feature = "official-server")]
-fn get_hash(client: &HWClient, salt1: &str, salt2: &str) -> Sha1Digest {
- let s = format!("{}{}{}{}{}", salt1, salt2,
- client.web_password, client.protocol_number, "!hedgewars");
- Sha1Digest(sha1(s.as_bytes()))
-}
-
-pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
- match message {
- HWProtocolMessage::Nick(nick) => {
- let client = &mut server.clients[client_id];
- debug!("{} {}", nick, is_name_illegal(&nick));
- let actions = if client.room_id != None {
- unreachable!()
- }
- else if !client.nick.is_empty() {
- vec![ProtocolError("Nickname already provided.".to_string())]
- }
- else if is_name_illegal(&nick) {
- vec![ByeClient("Illegal nickname! Nicknames must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())]
- }
- else {
- client.nick = nick.clone();
- vec![Nick(nick).send_self().action(),
- CheckRegistered]
- };
-
- server.react(client_id, actions);
- }
- HWProtocolMessage::Proto(proto) => {
- let client = &mut server.clients[client_id];
- let actions = if client.protocol_number != 0 {
- vec![ProtocolError("Protocol already known.".to_string())]
- }
- else if proto == 0 {
- vec![ProtocolError("Bad number.".to_string())]
- }
- else {
- client.protocol_number = proto;
- vec![Proto(proto).send_self().action(),
- CheckRegistered]
- };
- server.react(client_id, actions);
- }
- #[cfg(feature = "official-server")]
- HWProtocolMessage::Password(hash, salt) => {
- let c = &server.clients[client_id];
-
- let client_hash = get_hash(c, &salt, &c.server_salt);
- let server_hash = get_hash(c, &c.server_salt, &salt);
- let actions = if client_hash == server_hash {
- vec![ServerAuth(format!("{:x}", server_hash)).send_self().action(),
- JoinLobby]
- } else {
- vec![ByeClient("Authentication failed".to_string())]
- };
- server.react(client_id, actions);
- }
- #[cfg(feature = "official-server")]
- HWProtocolMessage::Checker(protocol, nick, password) => {
- let c = &mut server.clients[client_id];
- c.nick = nick;
- c.web_password = password;
- c.set_is_checker(true);
- }
- _ => warn!("Incorrect command in logging-in state"),
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/handlers/mod.rs
--- a/gameServer2/src/server/handlers/mod.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-use mio;
-use std::{io, io::Write};
-
-use super::{
- server::HWServer,
- actions::{Action, Action::*},
- coretypes::ClientId
-};
-use crate::{
- protocol::messages::{
- HWProtocolMessage,
- HWServerMessage::*
- }
-};
-use log::*;
-
-mod loggingin;
-mod lobby;
-mod inroom;
-mod common;
-mod checker;
-
-pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) {
- match message {
- HWProtocolMessage::Ping =>
- server.react(client_id, vec![Pong.send_self().action()]),
- HWProtocolMessage::Quit(Some(msg)) =>
- server.react(client_id, vec![ByeClient("User quit: ".to_string() + &msg)]),
- HWProtocolMessage::Quit(None) =>
- server.react(client_id, vec![ByeClient("User quit".to_string())]),
- HWProtocolMessage::Malformed => warn!("Malformed/unknown message"),
- HWProtocolMessage::Empty => warn!("Empty message"),
- _ => {
- match server.clients[client_id].room_id {
- None =>
- loggingin::handle(server, client_id, message),
- Some(id) if id == server.lobby_id =>
- lobby::handle(server, client_id, message),
- Some(id) =>
- inroom::handle(server, client_id, id, message)
- }
- },
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/mod.rs
--- a/gameServer2/src/server/mod.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-pub mod server;
-pub mod client;
-pub mod room;
-pub mod network;
-pub mod coretypes;
-mod actions;
-mod handlers;
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/network.rs
--- a/gameServer2/src/server/network.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,443 +0,0 @@
-extern crate slab;
-
-use std::{
- io, io::{Error, ErrorKind, Read, Write},
- net::{SocketAddr, IpAddr, Ipv4Addr},
- collections::HashSet,
- mem::{swap, replace}
-};
-
-use mio::{
- net::{TcpStream, TcpListener},
- Poll, PollOpt, Ready, Token
-};
-use netbuf;
-use slab::Slab;
-use log::*;
-
-use crate::{
- utils,
- protocol::{ProtocolDecoder, messages::*}
-};
-use super::{
- server::{HWServer},
- coretypes::ClientId
-};
-#[cfg(feature = "tls-connections")]
-use openssl::{
- ssl::{
- SslMethod, SslContext, Ssl, SslContextBuilder,
- SslVerifyMode, SslFiletype, SslOptions,
- SslStreamBuilder, HandshakeError, MidHandshakeSslStream, SslStream
- },
- error::ErrorStack
-};
-
-const MAX_BYTES_PER_READ: usize = 2048;
-
-#[derive(Hash, Eq, PartialEq, Copy, Clone)]
-pub enum NetworkClientState {
- Idle,
- NeedsWrite,
- NeedsRead,
- Closed,
-}
-
-type NetworkResult = io::Result<(T, NetworkClientState)>;
-
-#[cfg(not(feature = "tls-connections"))]
-pub enum ClientSocket {
- Plain(TcpStream)
-}
-
-#[cfg(feature = "tls-connections")]
-pub enum ClientSocket {
- SslHandshake(Option>),
- SslStream(SslStream)
-}
-
-impl ClientSocket {
- fn inner(&self) -> &TcpStream {
- #[cfg(not(feature = "tls-connections"))]
- match self {
- ClientSocket::Plain(stream) => stream,
- }
-
- #[cfg(feature = "tls-connections")]
- match self {
- ClientSocket::SslHandshake(Some(builder)) => builder.get_ref(),
- ClientSocket::SslHandshake(None) => unreachable!(),
- ClientSocket::SslStream(ssl_stream) => ssl_stream.get_ref()
- }
- }
-}
-
-pub struct NetworkClient {
- id: ClientId,
- socket: ClientSocket,
- peer_addr: SocketAddr,
- decoder: ProtocolDecoder,
- buf_out: netbuf::Buf
-}
-
-impl NetworkClient {
- pub fn new(id: ClientId, socket: ClientSocket, peer_addr: SocketAddr) -> NetworkClient {
- NetworkClient {
- id, socket, peer_addr,
- decoder: ProtocolDecoder::new(),
- buf_out: netbuf::Buf::new()
- }
- }
-
- #[cfg(feature = "tls-connections")]
- fn handshake_impl(&mut self, handshake: MidHandshakeSslStream) -> io::Result {
- match handshake.handshake() {
- Ok(stream) => {
- self.socket = ClientSocket::SslStream(stream);
- debug!("TLS handshake with {} ({}) completed", self.id, self.peer_addr);
- Ok(NetworkClientState::Idle)
- }
- Err(HandshakeError::WouldBlock(new_handshake)) => {
- self.socket = ClientSocket::SslHandshake(Some(new_handshake));
- Ok(NetworkClientState::Idle)
- }
- Err(HandshakeError::Failure(new_handshake)) => {
- self.socket = ClientSocket::SslHandshake(Some(new_handshake));
- debug!("TLS handshake with {} ({}) failed", self.id, self.peer_addr);
- Err(Error::new(ErrorKind::Other, "Connection failure"))
- }
- Err(HandshakeError::SetupFailure(_)) => unreachable!()
- }
- }
-
- fn read_impl(decoder: &mut ProtocolDecoder, source: &mut R,
- id: ClientId, addr: &SocketAddr) -> NetworkResult> {
- let mut bytes_read = 0;
- let result = loop {
- match decoder.read_from(source) {
- Ok(bytes) => {
- debug!("Client {}: read {} bytes", id, bytes);
- bytes_read += bytes;
- if bytes == 0 {
- let result = if bytes_read == 0 {
- info!("EOF for client {} ({})", id, addr);
- (Vec::new(), NetworkClientState::Closed)
- } else {
- (decoder.extract_messages(), NetworkClientState::NeedsRead)
- };
- break Ok(result);
- }
- else if bytes_read >= MAX_BYTES_PER_READ {
- break Ok((decoder.extract_messages(), NetworkClientState::NeedsRead))
- }
- }
- Err(ref error) if error.kind() == ErrorKind::WouldBlock => {
- let messages = if bytes_read == 0 {
- Vec::new()
- } else {
- decoder.extract_messages()
- };
- break Ok((messages, NetworkClientState::Idle));
- }
- Err(error) =>
- break Err(error)
- }
- };
- decoder.sweep();
- result
- }
-
- pub fn read(&mut self) -> NetworkResult> {
- #[cfg(not(feature = "tls-connections"))]
- match self.socket {
- ClientSocket::Plain(ref mut stream) =>
- NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr),
- }
-
- #[cfg(feature = "tls-connections")]
- match self.socket {
- ClientSocket::SslHandshake(ref mut handshake_opt) => {
- let handshake = std::mem::replace(handshake_opt, None).unwrap();
- Ok((Vec::new(), self.handshake_impl(handshake)?))
- },
- ClientSocket::SslStream(ref mut stream) =>
- NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr)
- }
- }
-
- fn write_impl(buf_out: &mut netbuf::Buf, destination: &mut W) -> NetworkResult<()> {
- let result = loop {
- match buf_out.write_to(destination) {
- Ok(bytes) if buf_out.is_empty() || bytes == 0 =>
- break Ok(((), NetworkClientState::Idle)),
- Ok(_) => (),
- Err(ref error) if error.kind() == ErrorKind::Interrupted
- || error.kind() == ErrorKind::WouldBlock => {
- break Ok(((), NetworkClientState::NeedsWrite));
- },
- Err(error) =>
- break Err(error)
- }
- };
- result
- }
-
- pub fn write(&mut self) -> NetworkResult<()> {
- let result = {
- #[cfg(not(feature = "tls-connections"))]
- match self.socket {
- ClientSocket::Plain(ref mut stream) =>
- NetworkClient::write_impl(&mut self.buf_out, stream)
- }
-
- #[cfg(feature = "tls-connections")] {
- match self.socket {
- ClientSocket::SslHandshake(ref mut handshake_opt) => {
- let handshake = std::mem::replace(handshake_opt, None).unwrap();
- Ok(((), self.handshake_impl(handshake)?))
- }
- ClientSocket::SslStream(ref mut stream) =>
- NetworkClient::write_impl(&mut self.buf_out, stream)
- }
- }
- };
-
- self.socket.inner().flush()?;
- result
- }
-
- pub fn send_raw_msg(&mut self, msg: &[u8]) {
- self.buf_out.write_all(msg).unwrap();
- }
-
- pub fn send_string(&mut self, msg: &str) {
- self.send_raw_msg(&msg.as_bytes());
- }
-
- pub fn send_msg(&mut self, msg: &HWServerMessage) {
- self.send_string(&msg.to_raw_protocol());
- }
-}
-
-#[cfg(feature = "tls-connections")]
-struct ServerSsl {
- context: SslContext
-}
-
-pub struct NetworkLayer {
- listener: TcpListener,
- server: HWServer,
- clients: Slab,
- pending: HashSet<(ClientId, NetworkClientState)>,
- pending_cache: Vec<(ClientId, NetworkClientState)>,
- #[cfg(feature = "tls-connections")]
- ssl: ServerSsl
-}
-
-impl NetworkLayer {
- pub fn new(listener: TcpListener, clients_limit: usize, rooms_limit: usize) -> NetworkLayer {
- let server = HWServer::new(clients_limit, rooms_limit);
- let clients = Slab::with_capacity(clients_limit);
- let pending = HashSet::with_capacity(2 * clients_limit);
- let pending_cache = Vec::with_capacity(2 * clients_limit);
-
- NetworkLayer {
- listener, server, clients, pending, pending_cache,
- #[cfg(feature = "tls-connections")]
- ssl: NetworkLayer::create_ssl_context()
- }
- }
-
- #[cfg(feature = "tls-connections")]
- fn create_ssl_context() -> ServerSsl {
- let mut builder = SslContextBuilder::new(SslMethod::tls()).unwrap();
- builder.set_verify(SslVerifyMode::NONE);
- builder.set_read_ahead(true);
- builder.set_certificate_file("ssl/cert.pem", SslFiletype::PEM).unwrap();
- builder.set_private_key_file("ssl/key.pem", SslFiletype::PEM).unwrap();
- builder.set_options(SslOptions::NO_COMPRESSION);
- builder.set_cipher_list("DEFAULT:!LOW:!RC4:!EXP").unwrap();
- ServerSsl { context: builder.build() }
- }
-
- pub fn register_server(&self, poll: &Poll) -> io::Result<()> {
- poll.register(&self.listener, utils::SERVER, Ready::readable(),
- PollOpt::edge())
- }
-
- fn deregister_client(&mut self, poll: &Poll, id: ClientId) {
- let mut client_exists = false;
- if let Some(ref client) = self.clients.get(id) {
- poll.deregister(client.socket.inner())
- .expect("could not deregister socket");
- info!("client {} ({}) removed", client.id, client.peer_addr);
- client_exists = true;
- }
- if client_exists {
- self.clients.remove(id);
- }
- }
-
- fn register_client(&mut self, poll: &Poll, id: ClientId, client_socket: ClientSocket, addr: SocketAddr) {
- poll.register(client_socket.inner(), Token(id),
- Ready::readable() | Ready::writable(),
- PollOpt::edge())
- .expect("could not register socket with event loop");
-
- let entry = self.clients.vacant_entry();
- let client = NetworkClient::new(id, client_socket, addr);
- info!("client {} ({}) added", client.id, client.peer_addr);
- entry.insert(client);
- }
-
- fn flush_server_messages(&mut self) {
- debug!("{} pending server messages", self.server.output.len());
- for (clients, message) in self.server.output.drain(..) {
- debug!("Message {:?} to {:?}", message, clients);
- let msg_string = message.to_raw_protocol();
- for client_id in clients {
- if let Some(client) = self.clients.get_mut(client_id) {
- client.send_string(&msg_string);
- self.pending.insert((client_id, NetworkClientState::NeedsWrite));
- }
- }
- }
- }
-
- fn create_client_socket(&self, socket: TcpStream) -> io::Result {
- #[cfg(not(feature = "tls-connections"))] {
- Ok(ClientSocket::Plain(socket))
- }
-
- #[cfg(feature = "tls-connections")] {
- let ssl = Ssl::new(&self.ssl.context).unwrap();
- let mut builder = SslStreamBuilder::new(ssl, socket);
- builder.set_accept_state();
- match builder.handshake() {
- Ok(stream) =>
- Ok(ClientSocket::SslStream(stream)),
- Err(HandshakeError::WouldBlock(stream)) =>
- Ok(ClientSocket::SslHandshake(Some(stream))),
- Err(e) => {
- debug!("OpenSSL handshake failed: {}", e);
- Err(Error::new(ErrorKind::Other, "Connection failure"))
- }
- }
- }
- }
-
- pub fn accept_client(&mut self, poll: &Poll) -> io::Result<()> {
- let (client_socket, addr) = self.listener.accept()?;
- info!("Connected: {}", addr);
-
- let client_id = self.server.add_client();
- self.register_client(poll, client_id, self.create_client_socket(client_socket)?, addr);
- self.flush_server_messages();
-
- Ok(())
- }
-
- fn operation_failed(&mut self, poll: &Poll, client_id: ClientId, error: &Error, msg: &str) -> io::Result<()> {
- let addr = if let Some(ref mut client) = self.clients.get_mut(client_id) {
- client.peer_addr
- } else {
- SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
- };
- debug!("{}({}): {}", msg, addr, error);
- self.client_error(poll, client_id)
- }
-
- pub fn client_readable(&mut self, poll: &Poll,
- client_id: ClientId) -> io::Result<()> {
- let messages =
- if let Some(ref mut client) = self.clients.get_mut(client_id) {
- client.read()
- } else {
- warn!("invalid readable client: {}", client_id);
- Ok((Vec::new(), NetworkClientState::Idle))
- };
-
- match messages {
- Ok((messages, state)) => {
- for message in messages {
- self.server.handle_msg(client_id, message);
- }
- match state {
- NetworkClientState::NeedsRead => {
- self.pending.insert((client_id, state));
- },
- NetworkClientState::Closed =>
- self.client_error(&poll, client_id)?,
- _ => {}
- };
- }
- Err(e) => self.operation_failed(
- poll, client_id, &e,
- "Error while reading from client socket")?
- }
-
- self.flush_server_messages();
-
- if !self.server.removed_clients.is_empty() {
- let ids: Vec<_> = self.server.removed_clients.drain(..).collect();
- for client_id in ids {
- self.deregister_client(poll, client_id);
- }
- }
-
- Ok(())
- }
-
- pub fn client_writable(&mut self, poll: &Poll,
- client_id: ClientId) -> io::Result<()> {
- let result =
- if let Some(ref mut client) = self.clients.get_mut(client_id) {
- client.write()
- } else {
- warn!("invalid writable client: {}", client_id);
- Ok(((), NetworkClientState::Idle))
- };
-
- match result {
- Ok(((), state)) if state == NetworkClientState::NeedsWrite => {
- self.pending.insert((client_id, state));
- },
- Ok(_) => {}
- Err(e) => self.operation_failed(
- poll, client_id, &e,
- "Error while writing to client socket")?
- }
-
- Ok(())
- }
-
- pub fn client_error(&mut self, poll: &Poll,
- client_id: ClientId) -> io::Result<()> {
- self.deregister_client(poll, client_id);
- self.server.client_lost(client_id);
-
- Ok(())
- }
-
- pub fn has_pending_operations(&self) -> bool {
- !self.pending.is_empty()
- }
-
- pub fn on_idle(&mut self, poll: &Poll) -> io::Result<()> {
- if self.has_pending_operations() {
- let mut cache = replace(&mut self.pending_cache, Vec::new());
- cache.extend(self.pending.drain());
- for (id, state) in cache.drain(..) {
- match state {
- NetworkClientState::NeedsRead =>
- self.client_readable(poll, id)?,
- NetworkClientState::NeedsWrite =>
- self.client_writable(poll, id)?,
- _ => {}
- }
- }
- swap(&mut cache, &mut self.pending_cache);
- }
- Ok(())
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/room.rs
--- a/gameServer2/src/server/room.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,391 +0,0 @@
-use std::{
- iter, collections::HashMap
-};
-use crate::server::{
- coretypes::{
- ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting,
- MAX_HEDGEHOGS_PER_TEAM
- },
- client::{HWClient}
-};
-use bitflags::*;
-use serde::{Serialize, Deserialize};
-use serde_derive::{Serialize, Deserialize};
-use serde_yaml;
-
-const MAX_TEAMS_IN_ROOM: u8 = 8;
-const MAX_HEDGEHOGS_IN_ROOM: u8 =
- MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM;
-
-#[derive(Clone, Serialize, Deserialize)]
-struct Ammo {
- name: String,
- settings: Option
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-struct Scheme {
- name: String,
- settings: Vec
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-struct RoomConfig {
- feature_size: u32,
- map_type: String,
- map_generator: u32,
- maze_size: u32,
- seed: String,
- template: u32,
-
- ammo: Ammo,
- scheme: Scheme,
- script: String,
- theme: String,
- drawn_map: Option
-}
-
-impl RoomConfig {
- fn new() -> RoomConfig {
- RoomConfig {
- feature_size: 12,
- map_type: "+rnd+".to_string(),
- map_generator: 0,
- maze_size: 0,
- seed: "seed".to_string(),
- template: 0,
-
- ammo: Ammo {name: "Default".to_string(), settings: None },
- scheme: Scheme {name: "Default".to_string(), settings: Vec::new() },
- script: "Normal".to_string(),
- theme: "\u{1f994}".to_string(),
- drawn_map: None
- }
- }
-}
-
-fn client_teams_impl(teams: &[(ClientId, TeamInfo)], client_id: ClientId)
- -> impl Iterator- + Clone
-{
- teams.iter().filter(move |(id, _)| *id == client_id).map(|(_, t)| t)
-}
-
-fn map_config_from(c: &RoomConfig) -> Vec
{
- vec![c.feature_size.to_string(), c.map_type.to_string(),
- c.map_generator.to_string(), c.maze_size.to_string(),
- c.seed.to_string(), c.template.to_string()]
-}
-
-fn game_config_from(c: &RoomConfig) -> Vec {
- use crate::server::coretypes::GameCfg::*;
- let mut v = vec![
- Ammo(c.ammo.name.to_string(), c.ammo.settings.clone()),
- Scheme(c.scheme.name.to_string(), c.scheme.settings.clone()),
- Script(c.script.to_string()),
- Theme(c.theme.to_string())];
- if let Some(ref m) = c.drawn_map {
- v.push(DrawnMap(m.to_string()))
- }
- v
-}
-
-pub struct GameInfo {
- pub teams_in_game: u8,
- pub teams_at_start: Vec<(ClientId, TeamInfo)>,
- pub left_teams: Vec,
- pub msg_log: Vec,
- pub sync_msg: Option,
- pub is_paused: bool,
- config: RoomConfig
-}
-
-impl GameInfo {
- fn new(teams: Vec<(ClientId, TeamInfo)>, config: RoomConfig) -> GameInfo {
- GameInfo {
- left_teams: Vec::new(),
- msg_log: Vec::new(),
- sync_msg: None,
- is_paused: false,
- teams_in_game: teams.len() as u8,
- teams_at_start: teams,
- config
- }
- }
-
- pub fn client_teams(&self, client_id: ClientId) -> impl Iterator- + Clone {
- client_teams_impl(&self.teams_at_start, client_id)
- }
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct RoomSave {
- pub location: String,
- config: RoomConfig
-}
-
-bitflags!{
- pub struct RoomFlags: u8 {
- const FIXED = 0b0000_0001;
- const RESTRICTED_JOIN = 0b0000_0010;
- const RESTRICTED_TEAM_ADD = 0b0000_0100;
- const RESTRICTED_UNREGISTERED_PLAYERS = 0b0000_1000;
- }
-}
-
-pub struct HWRoom {
- pub id: RoomId,
- pub master_id: Option
,
- pub name: String,
- pub password: Option,
- pub greeting: String,
- pub protocol_number: u16,
- pub flags: RoomFlags,
-
- pub players_number: u8,
- pub default_hedgehog_number: u8,
- pub team_limit: u8,
- pub ready_players_number: u8,
- pub teams: Vec<(ClientId, TeamInfo)>,
- config: RoomConfig,
- pub voting: Option,
- pub saves: HashMap,
- pub game_info: Option
-}
-
-impl HWRoom {
- pub fn new(id: RoomId) -> HWRoom {
- HWRoom {
- id,
- master_id: None,
- name: String::new(),
- password: None,
- greeting: "".to_string(),
- flags: RoomFlags::empty(),
- protocol_number: 0,
- players_number: 0,
- default_hedgehog_number: 4,
- team_limit: MAX_TEAMS_IN_ROOM,
- ready_players_number: 0,
- teams: Vec::new(),
- config: RoomConfig::new(),
- voting: None,
- saves: HashMap::new(),
- game_info: None
- }
- }
-
- pub fn hedgehogs_number(&self) -> u8 {
- self.teams.iter().map(|(_, t)| t.hedgehogs_number).sum()
- }
-
- pub fn addable_hedgehogs(&self) -> u8 {
- MAX_HEDGEHOGS_IN_ROOM - self.hedgehogs_number()
- }
-
- pub fn add_team(&mut self, owner_id: ClientId, mut team: TeamInfo, preserve_color: bool) -> &TeamInfo {
- if !preserve_color {
- team.color = iter::repeat(()).enumerate()
- .map(|(i, _)| i as u8).take(u8::max_value() as usize + 1)
- .find(|i| self.teams.iter().all(|(_, t)| t.color != *i))
- .unwrap_or(0u8)
- };
- team.hedgehogs_number = if self.teams.is_empty() {
- self.default_hedgehog_number
- } else {
- self.teams[0].1.hedgehogs_number.min(self.addable_hedgehogs())
- };
- self.teams.push((owner_id, team));
- &self.teams.last().unwrap().1
- }
-
- pub fn remove_team(&mut self, name: &str) {
- if let Some(index) = self.teams.iter().position(|(_, t)| t.name == name) {
- self.teams.remove(index);
- }
- }
-
- pub fn set_hedgehogs_number(&mut self, n: u8) -> Vec {
- let mut names = Vec::new();
- let teams = match self.game_info {
- Some(ref mut info) => &mut info.teams_at_start,
- None => &mut self.teams
- };
-
- if teams.len() as u8 * n <= MAX_HEDGEHOGS_IN_ROOM {
- for (_, team) in teams.iter_mut() {
- team.hedgehogs_number = n;
- names.push(team.name.clone())
- };
- self.default_hedgehog_number = n;
- }
- names
- }
-
- pub fn find_team_and_owner_mut(&mut self, f: F) -> Option<(ClientId, &mut TeamInfo)>
- where F: Fn(&TeamInfo) -> bool {
- self.teams.iter_mut().find(|(_, t)| f(t)).map(|(id, t)| (*id, t))
- }
-
- pub fn find_team(&self, f: F) -> Option<&TeamInfo>
- where F: Fn(&TeamInfo) -> bool {
- self.teams.iter().find_map(|(_, t)| Some(t).filter(|t| f(&t)))
- }
-
- pub fn client_teams(&self, client_id: ClientId) -> impl Iterator- {
- client_teams_impl(&self.teams, client_id)
- }
-
- pub fn client_team_indices(&self, client_id: ClientId) -> Vec
{
- self.teams.iter().enumerate()
- .filter(move |(_, (id, _))| *id == client_id)
- .map(|(i, _)| i as u8).collect()
- }
-
- pub fn find_team_owner(&self, team_name: &str) -> Option<(ClientId, &str)> {
- self.teams.iter().find(|(_, t)| t.name == team_name)
- .map(|(id, t)| (*id, &t.name[..]))
- }
-
- pub fn find_team_color(&self, owner_id: ClientId) -> Option {
- self.client_teams(owner_id).nth(0).map(|t| t.color)
- }
-
- pub fn has_multiple_clans(&self) -> bool {
- self.teams.iter().min_by_key(|(_, t)| t.color) !=
- self.teams.iter().max_by_key(|(_, t)| t.color)
- }
-
- pub fn set_config(&mut self, cfg: GameCfg) {
- let c = &mut self.config;
- match cfg {
- FeatureSize(s) => c.feature_size = s,
- MapType(t) => c.map_type = t,
- MapGenerator(g) => c.map_generator = g,
- MazeSize(s) => c.maze_size = s,
- Seed(s) => c.seed = s,
- Template(t) => c.template = t,
-
- Ammo(n, s) => c.ammo = Ammo {name: n, settings: s},
- Scheme(n, s) => c.scheme = Scheme {name: n, settings: s},
- Script(s) => c.script = s,
- Theme(t) => c.theme = t,
- DrawnMap(m) => c.drawn_map = Some(m)
- };
- }
-
- pub fn start_round(&mut self) {
- if self.game_info.is_none() {
- self.game_info = Some(GameInfo::new(
- self.teams.clone(), self.config.clone()));
- }
- }
-
- pub fn is_fixed(&self) -> bool {
- self.flags.contains(RoomFlags::FIXED)
- }
- pub fn is_join_restricted(&self) -> bool {
- self.flags.contains(RoomFlags::RESTRICTED_JOIN)
- }
- pub fn is_team_add_restricted(&self) -> bool {
- self.flags.contains(RoomFlags::RESTRICTED_TEAM_ADD)
- }
- pub fn are_unregistered_players_restricted(&self) -> bool {
- self.flags.contains(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS)
- }
-
- pub fn set_is_fixed(&mut self, value: bool) {
- self.flags.set(RoomFlags::FIXED, value)
- }
- pub fn set_join_restriction(&mut self, value: bool) {
- self.flags.set(RoomFlags::RESTRICTED_JOIN, value)
- }
- pub fn set_team_add_restriction(&mut self, value: bool) {
- self.flags.set(RoomFlags::RESTRICTED_TEAM_ADD, value)
- }
- pub fn set_unregistered_players_restriction(&mut self, value: bool) {
- self.flags.set(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, value)
- }
-
- fn flags_string(&self) -> String {
- let mut result = "-".to_string();
- if self.game_info.is_some() { result += "g" }
- if self.password.is_some() { result += "p" }
- if self.is_join_restricted() { result += "j" }
- if self.are_unregistered_players_restricted() {
- result += "r"
- }
- result
- }
-
- pub fn info(&self, master: Option<&HWClient>) -> Vec {
- let c = &self.config;
- vec![
- self.flags_string(),
- self.name.clone(),
- self.players_number.to_string(),
- self.teams.len().to_string(),
- master.map_or("[]", |c| &c.nick).to_string(),
- c.map_type.to_string(),
- c.script.to_string(),
- c.scheme.name.to_string(),
- c.ammo.name.to_string()
- ]
- }
-
- pub fn map_config(&self) -> Vec {
- match self.game_info {
- Some(ref info) => map_config_from(&info.config),
- None => map_config_from(&self.config)
- }
- }
-
- pub fn game_config(&self) -> Vec {
- match self.game_info {
- Some(ref info) => game_config_from(&info.config),
- None => game_config_from(&self.config)
- }
- }
-
- pub fn save_config(&mut self, name: String, location: String) {
- self.saves.insert(name, RoomSave { location, config: self.config.clone() });
- }
-
- pub fn load_config(&mut self, name: &str) -> Option<&str> {
- if let Some(save) = self.saves.get(name) {
- self.config = save.config.clone();
- Some(&save.location[..])
- } else {
- None
- }
- }
-
- pub fn delete_config(&mut self, name: &str) -> bool {
- self.saves.remove(name).is_some()
- }
-
- pub fn get_saves(&self) -> Result {
- serde_yaml::to_string(&(&self.greeting, &self.saves))
- }
-
- pub fn set_saves(&mut self, text: &str) -> Result<(), serde_yaml::Error> {
- serde_yaml::from_str::<(String, HashMap)>(text).map(|(greeting, saves)| {
- self.greeting = greeting;
- self.saves = saves;
- })
- }
-
- pub fn team_info(owner: &HWClient, team: &TeamInfo) -> Vec {
- let mut info = vec![
- team.name.clone(),
- team.grave.clone(),
- team.fort.clone(),
- team.voice_pack.clone(),
- team.flag.clone(),
- owner.nick.clone(),
- team.difficulty.to_string()];
- let hogs = team.hedgehogs.iter().flat_map(|h|
- iter::once(h.name.clone()).chain(iter::once(h.hat.clone())));
- info.extend(hogs);
- info
- }
-}
\ No newline at end of file
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/server/server.rs
--- a/gameServer2/src/server/server.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-use slab;
-use crate::utils;
-use super::{
- client::HWClient, room::HWRoom, actions, handlers,
- coretypes::{ClientId, RoomId},
- actions::{Destination, PendingMessage}
-};
-use crate::protocol::messages::*;
-use rand::{RngCore, thread_rng};
-use base64::{encode};
-use log::*;
-
-type Slab = slab::Slab;
-
-
-pub struct HWServer {
- pub clients: Slab,
- pub rooms: Slab,
- pub lobby_id: RoomId,
- pub output: Vec<(Vec, HWServerMessage)>,
- pub removed_clients: Vec,
-}
-
-impl HWServer {
- pub fn new(clients_limit: usize, rooms_limit: usize) -> HWServer {
- let rooms = Slab::with_capacity(rooms_limit);
- let clients = Slab::with_capacity(clients_limit);
- let mut server = HWServer {
- clients, rooms,
- lobby_id: 0,
- output: vec![],
- removed_clients: vec![]
- };
- server.lobby_id = server.add_room();
- server
- }
-
- pub fn add_client(&mut self) -> ClientId {
- let key: ClientId;
- {
- let entry = self.clients.vacant_entry();
- key = entry.key();
- let mut salt = [0u8; 18];
- thread_rng().fill_bytes(&mut salt);
-
- let client = HWClient::new(entry.key(), encode(&salt));
- entry.insert(client);
- }
- self.send(key, &Destination::ToSelf, HWServerMessage::Connected(utils::PROTOCOL_VERSION));
- key
- }
-
- pub fn client_lost(&mut self, client_id: ClientId) {
- actions::run_action(self, client_id,
- actions::Action::ByeClient("Connection reset".to_string()));
- }
-
- pub fn add_room(&mut self) -> RoomId {
- let entry = self.rooms.vacant_entry();
- let key = entry.key();
- let room = HWRoom::new(entry.key());
- entry.insert(room);
- key
- }
-
- pub fn handle_msg(&mut self, client_id: ClientId, msg: HWProtocolMessage) {
- debug!("Handling message {:?} for client {}", msg, client_id);
- if self.clients.contains(client_id) {
- handlers::handle(self, client_id, msg);
- }
- }
-
- fn get_recipients(&self, client_id: ClientId, destination: &Destination) -> Vec {
- let mut ids = match *destination {
- Destination::ToSelf => vec![client_id],
- Destination::ToId(id) => vec![id],
- Destination::ToAll {room_id: Some(id), ..} =>
- self.room_clients(id),
- Destination::ToAll {protocol: Some(proto), ..} =>
- self.protocol_clients(proto),
- Destination::ToAll {..} =>
- self.clients.iter().map(|(id, _)| id).collect::>()
- };
- if let Destination::ToAll {skip_self: true, ..} = destination {
- if let Some(index) = ids.iter().position(|id| *id == client_id) {
- ids.remove(index);
- }
- }
- ids
- }
-
- pub fn send(&mut self, client_id: ClientId, destination: &Destination, message: HWServerMessage) {
- let ids = self.get_recipients(client_id, &destination);
- self.output.push((ids, message));
- }
-
- pub fn react(&mut self, client_id: ClientId, actions: Vec) {
- for action in actions {
- actions::run_action(self, client_id, action);
- }
- }
-
- pub fn lobby(&self) -> &HWRoom { &self.rooms[self.lobby_id] }
-
- pub fn has_room(&self, name: &str) -> bool {
- self.rooms.iter().any(|(_, r)| r.name == name)
- }
-
- pub fn find_room(&self, name: &str) -> Option<&HWRoom> {
- self.rooms.iter().find_map(|(_, r)| Some(r).filter(|r| r.name == name))
- }
-
- pub fn find_room_mut(&mut self, name: &str) -> Option<&mut HWRoom> {
- self.rooms.iter_mut().find_map(|(_, r)| Some(r).filter(|r| r.name == name))
- }
-
- pub fn find_client(&self, nick: &str) -> Option<&HWClient> {
- self.clients.iter().find_map(|(_, c)| Some(c).filter(|c| c.nick == nick))
- }
-
- pub fn find_client_mut(&mut self, nick: &str) -> Option<&mut HWClient> {
- self.clients.iter_mut().find_map(|(_, c)| Some(c).filter(|c| c.nick == nick))
- }
-
- pub fn select_clients(&self, f: F) -> Vec
- where F: Fn(&(usize, &HWClient)) -> bool {
- self.clients.iter().filter(f)
- .map(|(_, c)| c.id).collect()
- }
-
- pub fn room_clients(&self, room_id: RoomId) -> Vec {
- self.select_clients(|(_, c)| c.room_id == Some(room_id))
- }
-
- pub fn protocol_clients(&self, protocol: u16) -> Vec {
- self.select_clients(|(_, c)| c.protocol_number == protocol)
- }
-
- pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec {
- let room_id = self.clients[self_id].room_id;
- self.select_clients(|(id, c)| *id != self_id && c.room_id == room_id )
- }
-
- pub fn client_and_room(&mut self, client_id: ClientId) -> (&mut HWClient, Option<&mut HWRoom>) {
- let c = &mut self.clients[client_id];
- if let Some(room_id) = c.room_id {
- (c, Some(&mut self.rooms[room_id]))
- } else {
- (c, None)
- }
- }
-
- pub fn room(&mut self, client_id: ClientId) -> Option<&mut HWRoom> {
- self.client_and_room(client_id).1
- }
-}
diff -r 8736f3a0ff7f -r e54e41554529 gameServer2/src/utils.rs
--- a/gameServer2/src/utils.rs Mon Jan 14 12:34:47 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-use std::iter::Iterator;
-use mio;
-use base64::{encode};
-
-pub const PROTOCOL_VERSION : u32 = 3;
-pub const SERVER: mio::Token = mio::Token(1_000_000_000);
-
-pub fn is_name_illegal(name: &str ) -> bool{
- name.len() > 40 ||
- name.trim().is_empty() ||
- name.chars().any(|c|
- "$()*+?[]^{|}\x7F".contains(c) ||
- '\x00' <= c && c <= '\x1F')
-}
-
-pub fn to_engine_msg(msg: T) -> String
- where T: Iterator- + Clone
-{
- let mut tmp = Vec::new();
- tmp.push(msg.clone().count() as u8);
- tmp.extend(msg);
- encode(&tmp)
-}
-
-pub fn protocol_version_string(protocol_number: u16) -> &'static str {
- match protocol_number {
- 17 => "0.9.7-dev",
- 19 => "0.9.7",
- 20 => "0.9.8-dev",
- 21 => "0.9.8",
- 22 => "0.9.9-dev",
- 23 => "0.9.9",
- 24 => "0.9.10-dev",
- 25 => "0.9.10",
- 26 => "0.9.11-dev",
- 27 => "0.9.11",
- 28 => "0.9.12-dev",
- 29 => "0.9.12",
- 30 => "0.9.13-dev",
- 31 => "0.9.13",
- 32 => "0.9.14-dev",
- 33 => "0.9.14",
- 34 => "0.9.15-dev",
- 35 => "0.9.14.1",
- 37 => "0.9.15",
- 38 => "0.9.16-dev",
- 39 => "0.9.16",
- 40 => "0.9.17-dev",
- 41 => "0.9.17",
- 42 => "0.9.18-dev",
- 43 => "0.9.18",
- 44 => "0.9.19-dev",
- 45 => "0.9.19",
- 46 => "0.9.20-dev",
- 47 => "0.9.20",
- 48 => "0.9.21-dev",
- 49 => "0.9.21",
- 50 => "0.9.22-dev",
- 51 => "0.9.22",
- 52 => "0.9.23-dev",
- 53 => "0.9.23",
- 54 => "0.9.24-dev",
- 55 => "0.9.24",
- 56 => "0.9.25-dev",
- _ => "Unknown"
- }
-}
\ No newline at end of file
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/hwengine.pas
--- a/hedgewars/hwengine.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/hwengine.pas Mon Jan 14 12:35:32 2019 -0500
@@ -264,7 +264,6 @@
SDL_MOUSEWHEEL:
begin
wheelEvent:= true;
- //ProcessMouseWheel(event.wheel.x, event.wheel.y);
ProcessMouseWheel(event.wheel.y);
end;
{$ENDIF}
@@ -354,7 +353,6 @@
///////////////////////////////////////////////////////////////////////////////
procedure GameRoutine;
-//var p: TPathType;
var s: shortstring;
i: LongInt;
begin
@@ -380,7 +378,6 @@
end;
if not allOK then exit;
- //SDL_StartTextInput();
SDL_ShowCursor(0);
@@ -450,7 +447,6 @@
isDeveloperMode:= false;
if checkFails(InitStepsFlags = cifAllInited, 'Some parameters not set (flags = ' + inttostr(InitStepsFlags) + ')', true) then exit;
- //ParseCommand('rotmask', true);
if not allOK then exit;
{$IFDEF USE_VIDEO_RECORDING}
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uAI.pas
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uAIAmmoTests.pas
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uAIMisc.pas
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uChat.pas
--- a/hedgewars/uChat.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uChat.pas Mon Jan 14 12:35:32 2019 -0500
@@ -589,6 +589,15 @@
exit
end;
+ if (copy(s, 2, 9) = 'help room') then
+ begin
+ if (gameType = gmtNet) then
+ SendConsoleCommand('/help')
+ else
+ AddChatString(#0 + shortstring(trcmd[sidCmdHelpRoomFail]));
+ exit;
+ end;
+
if (copy(s, 2, 4) = 'help') then
begin
AddChatString(#3 + shortstring(trcmd[sidCmdHeaderBasic]));
@@ -608,6 +617,8 @@
AddChatString(#3 + shortstring(trcmd[sidCmdHistory]));
AddChatString(#3 + shortstring(trcmd[sidCmdHelp]));
AddChatString(#3 + shortstring(trcmd[sidCmdHelpTaunts]));
+ if gameType = gmtNet then
+ AddChatString(#3 + shortstring(trcmd[sidCmdHelpRoom]));
exit
end;
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uCollisions.pas
--- a/hedgewars/uCollisions.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uCollisions.pas Mon Jan 14 12:35:32 2019 -0500
@@ -501,7 +501,7 @@
begin
if Land[y, x] and Gear^.CollisionMask <> 0 then
begin
- if ((Land[y, x] and Gear^.CollisionMask) and lfNotObjMask) <> 0 then
+ if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then
exit(Land[y, x] and Gear^.CollisionMask)
else
pixel:= Land[y, x] and Gear^.CollisionMask;
@@ -567,7 +567,7 @@
if (x and LAND_WIDTH_MASK) = 0 then
if Land[y, x] > 0 then
begin
- if ((Land[y, x] and Gear^.CollisionMask) and lfNotObjMask) <> 0 then
+ if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then
exit(Land[y, x] and Gear^.CollisionMask)
else // if Land[y, x] <> 0 then
pixel:= Land[y, x] and Gear^.CollisionMask;
@@ -642,7 +642,7 @@
i:= y + Gear^.Radius * 2 - 2;
repeat
if (y and LAND_HEIGHT_MASK) = 0 then
- if ((Land[y, x] and Gear^.CollisionMask) and lfNotObjMask) <> 0 then
+ if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then
exit(Land[y, x] and Gear^.CollisionMask);
inc(y)
until (y > i);
@@ -665,7 +665,7 @@
i:= x + Gear^.Radius * 2 - 2;
repeat
if (x and LAND_WIDTH_MASK) = 0 then
- if ((Land[y, x] and Gear^.CollisionMask) and lfNotObjMask) <> 0 then
+ if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then
exit(Land[y, x] and Gear^.CollisionMask);
inc(x)
until (x > i);
@@ -981,7 +981,7 @@
i:= x + Gear^.Radius * 2 - 2;
repeat
if (x and LAND_WIDTH_MASK) = 0 then
- if (Land[y, x] and lfNotObjMask) <> 0 then
+ if (Land[y, x] and lfLandMask) <> 0 then
if (not isColl) or (abs(x-gx) < abs(collX-gx)) then
begin
isColl:= true;
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uCommandHandlers.pas
--- a/hedgewars/uCommandHandlers.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uCommandHandlers.pas Mon Jan 14 12:35:32 2019 -0500
@@ -478,6 +478,11 @@
if CheckNoTeamOrHH then
exit;
+ (* Use "~" (ASCII character 126) as synonym for NUL byte (=amNothing).
+ This is done to allow to add "setweap ~" in QTfrontend/binds.cpp because
+ the NUL byte would terminate the strings in C++ otherwise. *)
+ if (s[1] = '~') then
+ s[1]:= #0;
if checkFails((s[0] = #1) and (s[1] <= char(High(TAmmoType))), 'Malformed /setweap', true) then exit;
if not isExternalSource then
@@ -785,6 +790,11 @@
cHealthDecrease:= StrToInt(s)
end;
+procedure chInitHealth(var s: shortstring);
+begin
+cInitHealth:= StrToInt(s)
+end;
+
procedure chDamagePercent(var s: shortstring);
begin
cDamagePercent:= StrToInt(s)
@@ -853,6 +863,11 @@
CampaignVariable := s;
end;
+procedure chMissVar(var s:shortstring);
+begin
+ MissionVariable := s;
+end;
+
procedure chWorldEdge(var s: shortstring);
begin
WorldEdge:= TWorldEdge(StrToInt(s))
@@ -921,6 +936,7 @@
RegisterVariable('sd_turns', @chSuddenDTurns , false);
RegisterVariable('waterrise', @chWaterRise , false);
RegisterVariable('healthdec', @chHealthDecrease, false);
+ RegisterVariable('inithealth',@chInitHealth, false);
RegisterVariable('damagepct',@chDamagePercent , false);
RegisterVariable('ropepct' , @chRopePercent , false);
RegisterVariable('getawaytime' , @chGetAwayTime , false);
@@ -963,6 +979,7 @@
RegisterVariable('+cur_r' , @chCurR_p , true );
RegisterVariable('-cur_r' , @chCurR_m , true );
RegisterVariable('campvar' , @chCampVar , true );
+ RegisterVariable('missvar' , @chMissVar , true );
RegisterVariable('record' , @chRecord , true );
RegisterVariable('worldedge',@chWorldEdge , false);
RegisterVariable('advmapgen',@chAdvancedMapGenMode, false);
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uConsts.pas
--- a/hedgewars/uConsts.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uConsts.pas Mon Jan 14 12:35:32 2019 -0500
@@ -336,6 +336,7 @@
= $00100000; // doesn't stop timer while Attacking gear msg is set and inf. attack mode is on
ammoprop_ForceTurnEnd = $00200000; // always ends turn after usage, ignoring inf. attack
ammoprop_NoTargetAfter= $00400000; // disable target selection after attack
+ ammoprop_NoWrapTarget = $00800000; // allow to select target beyond wrap world edge limits
ammoprop_NoRoundEnd = $10000000; // ammo doesn't end turn
AMMO_INFINITE = 100; // internal representation of infinite ammo count
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uGears.pas
--- a/hedgewars/uGears.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uGears.pas Mon Jan 14 12:35:32 2019 -0500
@@ -62,11 +62,18 @@
var delay: LongWord;
delay2: LongWord;
- step: (stInit, stDelay, stChDmg, stSweep, stTurnStats, stChWin1,
- stTurnReact, stAfterDelay, stChWin2, stWater, stChWin3,
- stHealth, stSpawn, stNTurn);
+ step: (stInit, stDelay1, stChDmg, stSweep, stTurnStats, stChWin1,
+ stTurnReact, stDelay2, stChWin2, stWater, stChWin3,
+ stChKing, stSuddenDeath, stDelay3, stHealth, stSpawn, stDelay4,
+ stNTurn);
NewTurnTick: LongWord;
- //SDMusic: shortstring;
+
+const delaySDStart = 1600;
+ delaySDWarning = 1000;
+ delayDamageTagFull = 1500;
+ delayDamageTagShort = 500;
+ delayTurnReact = 800;
+ delayFinal = 100;
function CheckNoDamage: boolean; // returns TRUE in case of no damaged hhs
var Gear: PGear;
@@ -86,7 +93,7 @@
CheckNoDamage:= false;
dmg:= Gear^.Damage;
- if Gear^.Health < dmg then
+ if (Gear^.Health < dmg) then
begin
Gear^.Active:= true;
Gear^.Health:= 0
@@ -105,7 +112,15 @@
RenderHealth(Gear^.Hedgehog^);
RecountTeamHealth(Gear^.Hedgehog^.Team);
+ end
+ else if ((GameFlags and gfKing) <> 0) and (not Gear^.Hedgehog^.Team^.hasKing) then
+ begin
+ Gear^.Active:= true;
+ Gear^.Health:= 0;
+ RenderHealth(Gear^.Hedgehog^);
+ RecountTeamHealth(Gear^.Hedgehog^.Team);
end;
+
if (not isInMultiShoot) then
Gear^.Karma:= 0;
Gear^.Damage:= 0
@@ -114,6 +129,34 @@
end;
end;
+function DoDelay: boolean;
+begin
+if delay <= 0 then
+ delay:= 1
+else
+ dec(delay);
+DoDelay:= delay = 0;
+end;
+
+function CheckMinionsDie: boolean;
+var Gear: PGear;
+begin
+ CheckMinionsDie:= false;
+ if (GameFlags and gfKing) = 0 then
+ exit;
+
+ Gear:= GearsList;
+ while Gear <> nil do
+ begin
+ if (Gear^.Kind = gtHedgehog) and (not Gear^.Hedgehog^.King) and (not Gear^.Hedgehog^.Team^.hasKing) then
+ begin
+ CheckMinionsDie:= true;
+ exit;
+ end;
+ Gear:= Gear^.NextGear;
+ end;
+end;
+
procedure HealthMachine;
var Gear: PGear;
team: PTeam;
@@ -237,8 +280,6 @@
curHandledGear^.Tex:= RenderStringTex(ansistring(inttostr(curHandledGear^.Timer div 1000)), cWhiteColor, fntSmall);
end;
curHandledGear^.doStep(curHandledGear);
- // might be useful later
- //ScriptCall('onGearStep', Gear^.uid);
end
end
end;
@@ -269,22 +310,20 @@
ScriptCall('onEndTurn');
inc(step)
end;
- stDelay:
- begin
- if delay = 0 then
- delay:= cInactDelay
- else
- dec(delay);
-
- if delay = 0 then
- inc(step)
- end;
-
+ stDelay1:
+ if DoDelay() then
+ inc(step);
stChDmg:
if CheckNoDamage then
inc(step)
else
- step:= stDelay;
+ begin
+ if (not bBetweenTurns) and (not isInMultiShoot) then
+ delay:= delayDamageTagShort
+ else
+ delay:= delayDamageTagFull;
+ step:= stDelay1;
+ end;
stSweep:
if SweepDirty then
@@ -314,22 +353,16 @@
begin
uStats.TurnReaction;
uStats.TurnStatsReset;
+ delay:= delayTurnReact;
inc(step)
end
else
inc(step, 2);
end;
- stAfterDelay:
- begin
- if delay = 0 then
- delay:= cInactDelay
- else
- dec(delay);
-
- if delay = 0 then
- inc(step)
- end;
+ stDelay2:
+ if DoDelay() then
+ inc(step);
stChWin2:
begin
CheckForWin();
@@ -357,39 +390,79 @@
inc(step)
end;
- stHealth:
+ stChKing:
+ begin
+ if (not isInMultiShoot) and (CheckMinionsDie) then
+ step:= stChDmg
+ else
+ inc(step);
+ end;
+ stSuddenDeath:
begin
- if (cWaterRise <> 0) or (cHealthDecrease <> 0) then
- begin
- if (TotalRoundsPre = cSuddenDTurns) and (not SuddenDeath) and (not isInMultiShoot) then
- StartSuddenDeath()
- else if (TotalRoundsPre < cSuddenDTurns) and (not isInMultiShoot) then
+ if ((cWaterRise <> 0) or (cHealthDecrease <> 0)) and (not (isInMultiShoot or bBetweenTurns)) then
+ begin
+ // Start Sudden Death
+ if (TotalRoundsPre = cSuddenDTurns) and (not SuddenDeath) then
+ begin
+ StartSuddenDeath();
+ delay:= delaySDStart;
+ inc(step);
+ end
+ // Show Sudden Death warning message
+ else if (TotalRoundsPre < cSuddenDTurns) and ((LastSuddenDWarn = -2) or (LastSuddenDWarn <> TotalRoundsPre)) then
begin
i:= cSuddenDTurns - TotalRoundsPre;
s:= ansistring(inttostr(i));
- if i = 1 then
- AddCaption(trmsg[sidRoundSD], capcolDefault, capgrpGameState)
- else if (i = 2) or ((i > 0) and ((i mod 50 = 0) or ((i <= 25) and (i mod 5 = 0)))) then
- AddCaption(FormatA(trmsg[sidRoundsSD], s), capcolDefault, capgrpGameState);
- end;
+ // X rounds before SD. X = 1, 2, 3, 5, 7, 10, 15, 20, 25, 50, 100, ...
+ if (i > 0) and ((i <= 3) or (i = 7) or ((i mod 50 = 0) or ((i <= 25) and (i mod 5 = 0)))) then
+ begin
+ if i = 1 then
+ AddCaption(trmsg[sidRoundSD], capcolDefault, capgrpGameState)
+ else
+ AddCaption(FormatA(trmsg[sidRoundsSD], s), capcolDefault, capgrpGameState);
+ delay:= delaySDWarning;
+ inc(step);
+ LastSuddenDWarn:= TotalRoundsPre;
+ end
+ else
+ inc(step, 2);
+ end
+ else
+ inc(step, 2);
+ end
+ else
+ inc(step, 2);
+ end;
+ stDelay3:
+ if DoDelay() then
+ inc(step);
+ stHealth:
+ begin
+ if bBetweenTurns
+ or isInMultiShoot
+ or (TotalRoundsReal = -1) then
+ inc(step)
+ else
+ begin
+ bBetweenTurns:= true;
+ HealthMachine;
+ step:= stChDmg
end;
- if bBetweenTurns
- or isInMultiShoot
- or (TotalRoundsPre = -1) then
- inc(step)
- else
- begin
- bBetweenTurns:= true;
- HealthMachine;
- step:= stChDmg
- end
- end;
+ end;
stSpawn:
begin
- if not isInMultiShoot then
+ if (not isInMultiShoot) then
+ begin
SpawnBoxOfSmth;
- inc(step)
+ delay:= delayFinal;
+ inc(step);
+ end
+ else
+ inc(step, 2)
end;
+ stDelay4:
+ if DoDelay() then
+ inc(step);
stNTurn:
begin
if isInMultiShoot then
@@ -464,13 +537,12 @@
if TurnTimeLeft > 0 then
if IsClockRunning() then
- //(CurrentHedgehog^.CurAmmoType in [amShotgun, amDEagle, amSniperRifle])
begin
- if (cHedgehogTurnTime >= 10000)
+ if (cHedgehogTurnTime > TurnTimeLeft)
and (CurrentHedgehog^.Gear <> nil)
and ((CurrentHedgehog^.Gear^.State and gstAttacked) = 0)
and (not isGetAwayTime) and (ReadyTimeLeft = 0) then
- if TurnTimeLeft = 5000 then
+ if (TurnTimeLeft = 5000) and (cHedgehogTurnTime >= 10000) then
PlaySoundV(sndHurry, CurrentTeam^.voicepack)
else if TurnTimeLeft = 4000 then
PlaySound(sndCountdown4)
@@ -552,13 +624,6 @@
for i:= 0 to cMaxHHIndex do
with Hedgehogs[i] do
begin
-(*
- if (SpeechGear <> nil) then
- begin
- DeleteVisualGear(SpeechGear); // remove to restore persisting beyond end of turn. Tiy says was too much of a gameplay issue
- SpeechGear:= nil
- end;
-*)
if (Gear <> nil) then
begin
@@ -691,16 +756,14 @@
while (i < cAirMines) and (j < 1000*cAirMines) do
begin
p:= 0;
+ if (hasBorder) or (WorldEdge = weBounce) then
+ rx:= leftX+GetRandom(rightX-leftX-16)+8
+ else
+ rx:= leftX+GetRandom(rightX-leftX+400)-200;
if hasBorder then
- begin
- rx:= leftX+GetRandom(rightX-leftX-16)+8;
ry:= topY+GetRandom(LAND_HEIGHT-topY-16)+8
- end
else
- begin
- rx:= leftX+GetRandom(rightX-leftX+400)-200;
- ry:= topY+GetRandom(LAND_HEIGHT-topY+400)-200
- end;
+ ry:= topY+GetRandom(LAND_HEIGHT-topY+400)-200;
Gear^.X:= int2hwFloat(CalcWorldWrap(rx,Gear^.Radius));
Gear^.Y:= int2hwFloat(ry);
if CheckLandValue(rx, ry, $FFFF) and
@@ -724,9 +787,6 @@
((rdx.Round+rdy.Round < Gear^.Angle) and
(hwRound(hwSqr(rdx) + hwSqr(rdy)) < sqr(Gear^.Angle))) then
begin
- // Debug line. Remove later
- // AddFileLog('Too Close to Hog @ (' + inttostr(rx) + ',' + inttostr(ry) + ')');
-
p:= 1
end
end;
@@ -776,7 +836,7 @@
rdx:= _90-(GetRandomf*_360);
rdy:= _90-(GetRandomf*_360);
Gear:= AddGear(rx, ry, gtGenericFaller, gstInvisible, rdx, rdy, $FFFFFFFF);
- // Tag=1: This allows this generic faller to be displaced randomly by events
+ // This allows this generic faller to be displaced randomly by events
Gear^.Tag:= 1;
end;
@@ -909,7 +969,6 @@
FindPlace(Gear, false, t, t + playWidth div ClansCount, true);// could make Gear == nil;
if Gear <> nil then
begin
- //AddCI(Gear); uncomment if new hogs should be able to spawn on top of old ones.
Gear^.Pos:= GetRandom(49);
// unless the world is wrapping, make outter teams face to map center
if (WorldEdge <> weWrap) and ((p = 0) or (p = ClansCount - 1)) then
@@ -944,7 +1003,6 @@
FindPlace(ar[i]^.Gear, false, leftX, rightX, true);
if ar[i]^.Gear <> nil then
begin
- //AddCI(ar[i]^.Gear); uncomment if new hogs should be able to spawn on top of old ones
ar[i]^.Gear^.dX.isNegative:= hwRound(ar[i]^.Gear^.X) > leftX + playWidth div 2;
ar[i]^.Gear^.Pos:= GetRandom(19)
end;
@@ -1006,79 +1064,91 @@
function SpawnCustomCrateAt(x, y: LongInt; crate: TCrateType; content, cnt: Longword): PGear;
+var gear: PGear;
begin
- FollowGear := AddGear(x, y, gtCase, 0, _0, _0, 0);
+ gear := AddGear(x, y, gtCase, 0, _0, _0, 0);
+ if(FinishedTurnsTotal > -1) then
+ FollowGear:= gear;
cCaseFactor := 0;
if (crate <> HealthCrate) and (content > ord(High(TAmmoType))) then
content := ord(High(TAmmoType));
- FollowGear^.Power:= cnt;
+ gear^.Power:= cnt;
case crate of
HealthCrate:
begin
- FollowGear^.Pos := posCaseHealth;
+ gear^.Pos := posCaseHealth;
// health crate is smaller than the other crates
- FollowGear^.Radius := cCaseHealthRadius;
- FollowGear^.Health := content;
- AddCaption(GetEventString(eidNewHealthPack), capcolDefault, capgrpAmmoInfo);
+ gear^.Radius := cCaseHealthRadius;
+ gear^.Health := content;
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewHealthPack), capcolDefault, capgrpAmmoInfo);
end;
AmmoCrate:
begin
- FollowGear^.Pos := posCaseAmmo;
- FollowGear^.AmmoType := TAmmoType(content);
- AddCaption(GetEventString(eidNewAmmoPack), capcolDefault, capgrpAmmoInfo);
+ gear^.Pos := posCaseAmmo;
+ gear^.AmmoType := TAmmoType(content);
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewAmmoPack), capcolDefault, capgrpAmmoInfo);
end;
UtilityCrate:
begin
- FollowGear^.Pos := posCaseUtility;
- FollowGear^.AmmoType := TAmmoType(content);
- AddCaption(GetEventString(eidNewUtilityPack), capColDefault, capgrpAmmoInfo);
+ gear^.Pos := posCaseUtility;
+ gear^.AmmoType := TAmmoType(content);
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewUtilityPack), capColDefault, capgrpAmmoInfo);
end;
end;
if ( (x = 0) and (y = 0) ) then
- FindPlace(FollowGear, true, 0, LAND_WIDTH);
+ FindPlace(gear, true, 0, LAND_WIDTH);
- SpawnCustomCrateAt := FollowGear;
+ SpawnCustomCrateAt := gear;
end;
function SpawnFakeCrateAt(x, y: LongInt; crate: TCrateType; explode: boolean; poison: boolean): PGear;
+var gear: PGear;
begin
- FollowGear := AddGear(x, y, gtCase, 0, _0, _0, 0);
+ gear := AddGear(x, y, gtCase, 0, _0, _0, 0);
+ if(FinishedTurnsTotal > -1) then
+ FollowGear:= gear;
cCaseFactor := 0;
- FollowGear^.Pos := posCaseDummy;
+ gear^.Pos := posCaseDummy;
if explode then
- FollowGear^.Pos := FollowGear^.Pos + posCaseExplode;
+ gear^.Pos := gear^.Pos + posCaseExplode;
if poison then
- FollowGear^.Pos := FollowGear^.Pos + posCasePoison;
+ gear^.Pos := gear^.Pos + posCasePoison;
case crate of
HealthCrate:
begin
- FollowGear^.Pos := FollowGear^.Pos + posCaseHealth;
+ gear^.Pos := gear^.Pos + posCaseHealth;
// health crate is smaller than the other crates
- FollowGear^.Radius := cCaseHealthRadius;
- AddCaption(GetEventString(eidNewHealthPack), capcolDefault, capgrpAmmoInfo);
+ gear^.Radius := cCaseHealthRadius;
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewHealthPack), capcolDefault, capgrpAmmoInfo);
end;
AmmoCrate:
begin
- FollowGear^.Pos := FollowGear^.Pos + posCaseAmmo;
- AddCaption(GetEventString(eidNewAmmoPack), capcolDefault, capgrpAmmoInfo);
+ gear^.Pos := gear^.Pos + posCaseAmmo;
+ if(FinishedTurnstotal > -1) then
+ AddCaption(GetEventString(eidNewAmmoPack), capcolDefault, capgrpAmmoInfo);
end;
UtilityCrate:
begin
- FollowGear^.Pos := FollowGear^.Pos + posCaseUtility;
- AddCaption(GetEventString(eidNewUtilityPack), capcolDefault, capgrpAmmoInfo);
+ gear^.Pos := gear^.Pos + posCaseUtility;
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewUtilityPack), capcolDefault, capgrpAmmoInfo);
end;
end;
if ( (x = 0) and (y = 0) ) then
- FindPlace(FollowGear, true, 0, LAND_WIDTH);
+ FindPlace(gear, true, 0, LAND_WIDTH);
- SpawnFakeCrateAt := FollowGear;
+ SpawnFakeCrateAt := gear;
end;
procedure StartSuddenDeath();
@@ -1147,7 +1217,8 @@
or (Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerWhileAttacking <> 0)
or ((GameFlags and gfInfAttack) <> 0) and (Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerWhileAttackingInInfAttackMode <> 0)
or (CurrentHedgehog^.CurAmmoType = amSniperRifle))
- and (not(isInMultiShoot and ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerInMultiShoot) <> 0)));
+ and (not(isInMultiShoot and ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerInMultiShoot) <> 0)))
+ and (not LuaClockPaused);
end;
@@ -1191,14 +1262,6 @@
if text = '' then text:= '...';
- (*
- if CheckNoTeamOrHH then
- begin
- ParseCommand('say ' + text, true);
- exit
- end;
- *)
-
if (x < 4) and (TeamsArray[t] <> nil) then
begin
// if team matches current hedgehog team, default to current hedgehog
@@ -1332,10 +1395,9 @@
//typed const
delay:= 0;
delay2:= 0;
- step:= stDelay;
+ step:= stDelay1;
upd:= 0;
- //SDMusic:= 'hell.ogg';
NewTurnTick:= $FFFFFFFF;
end;
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uGearsHandlersMess.pas
--- a/hedgewars/uGearsHandlersMess.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uGearsHandlersMess.pas Mon Jan 14 12:35:32 2019 -0500
@@ -115,6 +115,7 @@
procedure doStepMovingPortal(Gear: PGear);
procedure doStepPortalShot(newPortal: PGear);
procedure doStepPiano(Gear: PGear);
+procedure doStepPianoWork(Gear: PGear);
procedure doStepSineGunShotWork(Gear: PGear);
procedure doStepSineGunShot(Gear: PGear);
procedure doStepFlamethrowerWork(Gear: PGear);
@@ -357,7 +358,6 @@
procedure doStepFallingGear(Gear: PGear);
var
isFalling: boolean;
- //tmp: QWord;
tX, tdX, tdY: hwFloat;
collV, collH, gX, gY: LongInt;
land, xland: word;
@@ -478,10 +478,8 @@
xland:= TestCollisionXwithGear(Gear, -hwSign(Gear^.dX));
if xland <> 0 then collH := -hwSign(Gear^.dX)
end;
- //if Gear^.AdvBounce and (collV <>0) and (collH <> 0) and (hwSqr(tdX) + hwSqr(tdY) > _0_08) then
if (collV <> 0) and (collH <> 0) and
(((Gear^.AdvBounce=1) and ((collV=-1) or ((tdX.QWordValue + tdY.QWordValue) > _0_2.QWordValue)))) then
- //or ((xland or land) and lfBouncy <> 0)) then
begin
if (xland or land) and lfBouncy = 0 then
begin
@@ -534,7 +532,6 @@
Gear^.X := Gear^.X + Gear^.dX;
Gear^.Y := Gear^.Y + Gear^.dY;
CheckGearDrowning(Gear);
- //if (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) < _0_0002) and
if (not isFalling) and ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) < _0_02.QWordValue) then
Gear^.State := Gear^.State and (not gstMoving)
else
@@ -543,7 +540,7 @@
if ((xland or land) and lfBouncy <> 0) and (Gear^.dX.QWordValue < _0_15.QWordValue) and (Gear^.dY.QWordValue < _0_15.QWordValue) then
Gear^.State := Gear^.State or gstCollision;
- if ((xland or land) and lfBouncy <> 0) and (Gear^.Radius >= 3) and
+ if ((xland or land) and lfBouncy <> 0) and
((Gear^.dX.QWordValue > _0_15.QWordValue) or (Gear^.dY.QWordValue > _0_15.QWordValue)) then
begin
AddBounceEffectForGear(Gear);
@@ -712,14 +709,6 @@
gY := hwRound(Gear^.Y);
for i:= 0 to 4 do
begin
- (*glass:= AddVisualGear(gx+random(7)-3, gy+random(5)-2, vgtEgg);
- if glass <> nil then
- begin
- glass^.Frame:= 2;
- glass^.Tint:= $41B83ED0 - i * $10081000;
- glass^.dX:= 1/(10*(random(11)-5));
- glass^.dY:= -1/(random(4)+5);
- end;*)
glass:= AddVisualGear(gx+random(7)-3, gy+random(7)-3, vgtStraightShot);
if glass <> nil then
with glass^ do
@@ -867,17 +856,6 @@
else if 360 < DirAngle then
DirAngle := DirAngle - 360;
end;
-(*
-We aren't using frametick right now, so just a waste of cycles.
- inc(Health, 8);
- if longword(Health) > vobFrameTicks then
- begin
- dec(Health, vobFrameTicks);
- inc(Timer);
- if Timer = vobFramesCount then
- Timer:= 0
- end;
-*)
// move back to cloud layer
if CheckCoordInWater(xx, yy) then
move:= true
@@ -1046,11 +1024,14 @@
var
t: hwFloat;
gX,gY,i: LongInt;
- uw, nuw: boolean;
+ uw, nuw, wrapped: boolean;
flower: PVisualGear;
begin
- WorldWrap(Gear);
+ wrapped:= WorldWrap(Gear);
+ if wrapped then
+ HomingWrap(Gear);
+
AllInactive := false;
gX := hwRound(Gear^.X);
gY := hwRound(Gear^.Y);
@@ -2392,7 +2373,6 @@
ScriptCall('onGearDamage', Gear^.UID, dmg)
end;
CalcRotationDirAngle(Gear);
- //CheckGearDrowning(Gear)
end
else
begin
@@ -2400,22 +2380,6 @@
AddCI(Gear)
end;
-(*
-Attempt to make a barrel knock itself over an edge. Would need more checks to avoid issues like burn damage
- begin
- x:= hwRound(Gear^.X);
- y:= hwRound(Gear^.Y);
- if (((y+1) and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) then
- if (Land[y+1, x] = 0) then
- begin
- if (((y+1) and LAND_HEIGHT_MASK) = 0) and (((x+Gear^.Radius-2) and LAND_WIDTH_MASK) = 0) and (Land[y+1, x+Gear^.Radius-2] = 0) then
- Gear^.dX:= -_0_08
- else if (((y+1 and LAND_HEIGHT_MASK)) = 0) and (((x-(Gear^.Radius-2)) and LAND_WIDTH_MASK) = 0) and (Land[y+1, x-(Gear^.Radius-2)] = 0) then
- Gear^.dX:= _0_08;
- end;
- if Gear^.dX.QWordValue = 0 then AddCI(Gear)
- end; *)
-
if not Gear^.dY.isNegative and (Gear^.dY < _0_001) and (TestCollisionYwithGear(Gear, 1) <> 0) then
Gear^.dY := _0;
if hwAbs(Gear^.dX) < _0_001 then
@@ -2486,7 +2450,6 @@
if k = gtExplosives then
begin
- //if V > _0_03 then Gear^.State:= Gear^.State or gstAnimation;
if (hwAbs(Gear^.dX) > _0_15) or ((hwAbs(Gear^.dY) > _0_15) and (hwAbs(Gear^.dX) > _0_02)) then
begin
Gear^.doStep := @doStepRollingBarrel;
@@ -2566,7 +2529,6 @@
else if Gear^.dY < - _0_03 then
PlaySound(Gear^.ImpactSound);
end;
- //if Gear^.dY > - _0_001 then Gear^.dY:= _0
CheckGearDrowning(Gear);
end;
@@ -2737,12 +2699,10 @@
Gear^.dX := Gear^.dX * _0_995;
Gear^.dY := Gear^.dY + cGravity;
- // if sticky then Gear^.dY := Gear^.dY + cGravity;
if Gear^.dY.QWordValue > _0_2.QWordValue then
Gear^.dY := Gear^.dY * _0_995;
- //if sticky then Gear^.X := Gear^.X + Gear^.dX else
Gear^.X := Gear^.X + Gear^.dX + cWindSpeed * 640;
Gear^.Y := Gear^.Y + Gear^.dY;
end;
@@ -2817,7 +2777,7 @@
Gear^.Radius := 1;
end
else if ((GameTicks and $3) = 3) then
- doMakeExplosion(gX, gY, Gear^.Boom * 4, Gear^.Hedgehog, 0);//, EXPLNoDamage);
+ doMakeExplosion(gX, gY, Gear^.Boom * 4, Gear^.Hedgehog, 0);
if ((GameTicks and $7) = 0) and (Random(2) = 0) then
for i:= Random(2) downto 0 do
@@ -2914,7 +2874,6 @@
AllInactive := false;
HHGear := Gear^.Hedgehog^.Gear;
DeleteCI(HHGear);
- //HHGear^.X := int2hwFloat(hwRound(HHGear^.X)) - _0_5; WTF?
HHGear^.dX := SignAs(cLittle, Gear^.dX);
HHGear^.dY := - _0_3;
@@ -3015,17 +2974,25 @@
if (Gear^.Health > 0) and (not (Gear^.X < Gear^.dX)) and (Gear^.X < Gear^.dX + cAirPlaneSpeed) then
begin
dec(Gear^.Health);
+ if (WorldEdge = weBounce) and (((Gear^.Tag = 1) and (hwRound(Gear^.X) > rightX)) or ((Gear^.Tag = -1) and (hwRound(Gear^.X) < leftX))) then
+ begin
+ // Don't spawn missile if it would end up inside bounce world edge of the opposite side
+ PlaySound(sndVaporize);
+ AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtSmoke);
+ end
+ else
+ begin
+ // Spawn missile
case Gear^.State of
0: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAirBomb, 0, cBombsSpeed * Gear^.Tag, _0, 0);
1: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtMine, 0, cBombsSpeed * Gear^.Tag, _0, 0);
2: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtNapalmBomb, 0, cBombsSpeed * Gear^.Tag, _0, 0);
3: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtDrill, gsttmpFlag, cBombsSpeed * Gear^.Tag, _0, Gear^.Timer + 1);
- //4: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtWaterMelon, 0, cBombsSpeed *
- // Gear^.Tag, _0, 5000);
end;
- Gear^.dX := Gear^.dX + int2hwFloat(Gear^.Damage * Gear^.Tag);
- if CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y)) then
- FollowGear^.State:= FollowGear^.State or gstSubmersible;
+ Gear^.dX := Gear^.dX + int2hwFloat(Gear^.Damage * Gear^.Tag);
+ if CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y)) then
+ FollowGear^.State:= FollowGear^.State or gstSubmersible;
+ end;
StopSoundChan(Gear^.SoundChannel, 4000);
end;
@@ -3041,9 +3008,45 @@
end;
procedure doStepAirAttack(Gear: PGear);
+var valid: boolean;
+ HHGear: PGear;
begin
AllInactive := false;
+ valid:= true;
+ // Bounce world edge restrictions ...
+ if (WorldEdge = weBounce) then
+ // If plane flies right, deny placement inside the right bounce side
+ if (Gear^.X.QWordValue = 0) and (Gear^.Target.X > rightX) then
+ valid:= false
+ // If plane flies left, deny placement inside the left bounce side
+ else if (Gear^.X.QWordValue <> 0) and (Gear^.Target.X < leftX) then
+ valid:= false
+ // Deny placement of high targets. This serves as a buffer to further
+ // reduce potentially weird bouncy gear behaviour
+ else if (Gear^.Target.Y < (topY - 50)) then
+ valid:= false;
+
+ if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then
+ HHGear:= Gear^.Hedgehog^.Gear;
+
+ if (not valid) then
+ begin
+ if (HHGear <> nil) then
+ begin
+ HHGear^.Message := HHGear^.Message and (not gmAttack);
+ HHGear^.State := HHGear^.State and (not gstAttacking);
+ HHGear^.State := HHGear^.State or gstChooseTarget;
+ isCursorVisible := true;
+ end;
+ DeleteGear(Gear);
+ PlaySound(sndDenied);
+ exit;
+ end;
+
+ if (HHGear <> nil) then
+ PlaySoundV(sndIncoming, Gear^.Hedgehog^.Team^.voicepack);
+
if Gear^.X.QWordValue = 0 then
begin
Gear^.Tag := 1;
@@ -3055,7 +3058,7 @@
Gear^.X := int2hwFloat(max(LAND_WIDTH,4096) + 2048);
end;
- Gear^.Y := int2hwFloat(topY-300);
+ Gear^.Y := int2hwFloat(topY - 300);
Gear^.dX := int2hwFloat(Gear^.Target.X) - int2hwFloat(Gear^.Tag * (Gear^.Health-1) * Gear^.Damage) / 2;
// calcs for Napalm Strike, so that it will hit the target (without wind at least :P)
@@ -3276,12 +3279,10 @@
if ((Gear^.Message and (not (gmSwitch or gmPrecise))) <> 0) or (TurnTimeLeft = 0) then
begin
hedgehog := Gear^.Hedgehog;
- //Msg := Gear^.Message and (not gmSwitch);
ApplyAmmoChanges(hedgehog^);
HHGear := CurrentHedgehog^.Gear;
ApplyAmmoChanges(HHGear^.Hedgehog^);
- //HHGear^.Message := Msg;
DeleteGear(Gear);
exit
end;
@@ -3441,9 +3442,6 @@
inc(Gear^.Damage, 2);
- // if TestCollisionXwithGear(HHGear, hwSign(Gear^.dX))
- // or TestCollisionYwithGear(HHGear, hwSign(Gear^.dY)) then inc(Gear^.Damage, 3);
-
dec(i)
until (i = 0)
or (Gear^.Damage > Gear^.Health);
@@ -3587,10 +3585,6 @@
if Gear^.Pos = 0 then
begin
///////////// adapted from doMakeExplosion ///////////////////////////
- //fX:= Gear^.X;
- //fY:= Gear^.Y;
- //fX.QWordValue:= fX.QWordValue and $FFFFFFFF00000000;
- //fY.QWordValue:= fY.QWordValue and $FFFFFFFF00000000;
fX:= int2hwFloat(hwRound(Gear^.X));
fY:= int2hwFloat(hwRound(Gear^.Y));
dmgBase:= Gear^.Boom shl 1 + cHHRadius div 2;
@@ -3720,7 +3714,7 @@
Gear^.Angle := (LongInt(Gear^.Angle) + 2) and 3;
// Bounce effect
- if (Gear^.Karma = 2) and (Gear^.Radius > 2) then
+ if (Gear^.Karma = 2) then
AddBounceEffectForGear(Gear, 0.55);
Gear^.Tag:= 0;
@@ -3862,30 +3856,6 @@
end ;
AfterAttack;
DeleteGear(Gear);
-
-(*
- Gear^.X := Gear^.X + Gear^.dX;
- Gear^.Y := Gear^.Y + Gear^.dY;
- x := hwRound(Gear^.X);
- y := hwRound(Gear^.Y);
-
- if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) then
- if (Land[y, x] <> 0) then
- begin
- Gear^.dX.isNegative := not Gear^.dX.isNegative;
- Gear^.dY.isNegative := not Gear^.dY.isNegative;
- Gear^.dX := Gear^.dX * _1_5;
- Gear^.dY := Gear^.dY * _1_5 - _0_3;
- AmmoShove(Gear, 0, 40);
- AfterAttack;
- DeleteGear(Gear)
- end
- else
- else
- begin
- AfterAttack;
- DeleteGear(Gear)
- end*)
end;
procedure doStepSeductionWear(Gear: PGear);
@@ -3920,14 +3890,6 @@
if heart <> nil then
with heart^ do
begin
- { // old calcs
- dx:= 0.001 * (random(200));
- dy:= 0.001 * (random(200));
- if random(2) = 0 then
- dx := -dx;
- if random(2) = 0 then
- dy := -dy;
- }
// randomize speed in both directions
dx:= 0.001 * (random(201));
@@ -3965,7 +3927,6 @@
procedure doStepSeduction(Gear: PGear);
begin
AllInactive := false;
- //DeleteCI(Gear^.Hedgehog^.Gear);
Gear^.doStep := @doStepSeductionWear
end;
@@ -4365,14 +4326,8 @@
dec(Gear^.Pos);
AllInactive := false;
HHGear := Gear^.Hedgehog^.Gear;
- //dec(Gear^.Timer);
move := _0_2;
fuel := 50;
-(*if (HHGear^.Message and gmPrecise) <> 0 then
- begin
- move:= _0_02;
- fuel:= 5;
- end;*)
if HHGear^.Message and gmPrecise <> 0 then
HedgehogChAngle(HHGear)
else if (Gear^.Health > 0) or (Gear^.Health = JETPACK_FUEL_INFINITE) then
@@ -4470,9 +4425,8 @@
or (HHGear^.dY < _0) then
doStepHedgehogMoving(HHGear);
- if // (Gear^.Health = 0)
+ if
(HHGear^.Damage <> 0)
- //or CheckGearDrowning(HHGear)
// drown if too deep under water
or (cWaterLine + cVisibleWater * 4 < hwRound(HHGear^.Y))
or (TurnTimeLeft = 0)
@@ -4490,11 +4444,6 @@
HHGear^.Hedgehog^.CurAmmoType:= amJetpack;
isCursorVisible := false;
ApplyAmmoChanges(HHGear^.Hedgehog^);
- // if Gear^.Tex <> nil then FreeTexture(Gear^.Tex);
-
-// Gear^.Tex:= RenderStringTex(trmsg[sidFuel] + ': ' + inttostr(round(Gear^.Health / 20)) + '%', cWhiteColor, fntSmall)
-
-//AddCaption(trmsg[sidFuel]+': '+inttostr(round(Gear^.Health/20))+'%', cWhiteColor, capgrpAmmostate);
DeleteGear(Gear);
end
end;
@@ -4739,7 +4688,6 @@
AllInactive := false;
Gear^.dX := Gear^.dX;
doStepFallingGear(Gear);
- // CheckGearDrowning(Gear); // already checked for in doStepFallingGear
CalcRotationDirAngle(Gear);
if (Gear^.State and gstCollision) <> 0 then
@@ -5294,8 +5242,51 @@
newPortal^.doStep := @doStepMovingPortal;
end;
+procedure doStepPiano(Gear: PGear);
+var valid: boolean;
+ HHGear: PGear;
+begin
+ AllInactive := false;
+ valid := true;
+
+ if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then
+ HHGear := Gear^.Hedgehog^.Gear;
+
+ if (WorldEdge = weBounce) then
+ if (hwRound(Gear^.X) - Gear^.Radius < leftX) then
+ valid := false
+ else if (hwRound(Gear^.X) - Gear^.Radius > rightX) then
+ valid := false;
+
+ if (not valid) then
+ begin
+ if (HHGear <> nil) then
+ begin
+ HHGear^.Message := HHGear^.Message and (not gmAttack);
+ HHGear^.State := HHGear^.State and (not gstAttacking);
+ HHGear^.State := HHGear^.State or gstChooseTarget;
+ isCursorVisible := true;
+ end;
+ DeleteGear(Gear);
+ PlaySound(sndDenied);
+ exit;
+ end;
+
+ isCursorVisible := false;
+ if (HHGear <> nil) then
+ begin
+ PlaySoundV(sndIncoming, Gear^.Hedgehog^.Team^.voicepack);
+ // Tuck the hedgehog away until the piano attack is completed
+ Gear^.Hedgehog^.Unplaced:= true;
+ HHGear^.X:= _0;
+ HHGear^.Y:= _0;
+ end;
+
+ PauseMusic;
+ Gear^.doStep:= @doStepPianoWork;
+end;
////////////////////////////////////////////////////////////////////////////////
-procedure doStepPiano(Gear: PGear);
+procedure doStepPianoWork(Gear: PGear);
var
r0, r1: LongInt;
odY: hwFloat;
@@ -5322,7 +5313,7 @@
CurrentHedgehog^.Gear^.Message := CurrentHedgehog^.Gear^.Message and (not gmSlot);
end;
- if (*((Gear^.Pos = 3) and ((GameFlags and gfSolidLand) <> 0)) or*) (Gear^.Pos = 5) then
+ if (Gear^.Pos = 5) then
begin
Gear^.dY := Gear^.dY + cGravity * 2;
Gear^.Y := Gear^.Y + Gear^.dY;
@@ -5650,7 +5641,6 @@
SignAs(AngleSin(HHGear^.Angle) * speed, HHGear^.dX) + rx,
AngleCos(HHGear^.Angle) * ( - speed) + ry, 0);
flame^.CollisionMask:= lfNotCurHogCrate;
- //flame^.FlightTime:= 500; use the default huge value to avoid sticky flame suddenly being damaging as opposed to other flames
if (Gear^.Health mod 30) = 0 then
begin
@@ -5658,7 +5648,6 @@
SignAs(AngleSin(HHGear^.Angle) * speed, HHGear^.dX) + rx,
AngleCos(HHGear^.Angle) * ( - speed) + ry, 0);
flame^.CollisionMask:= lfNotCurHogCrate;
- //flame^.FlightTime:= 500;
end
end;
Gear^.Timer:= Gear^.Tag
@@ -5831,7 +5820,6 @@
if (tmp^.Kind = gtHedgehog) or (tmp^.Kind = gtMine) or (tmp^.Kind = gtExplosives) then
begin
dmg:= 0;
- //tmp^.State:= tmp^.State or gstFlatened;
if (tmp^.Kind <> gtHedgehog) or (tmp^.Hedgehog^.Effects[heInvulnerable] = 0) then
begin
// base damage on remaining health
@@ -5849,7 +5837,6 @@
if (tmp^.Kind <> gtHedgehog) or (dmg > 0) or (tmp^.Health > tmp^.Damage) then
begin
- //DrawTunnel(tmp^.X, tmp^.Y - _1, _0, _0_5, cHHRadius * 6, cHHRadius * 3);
tmp2:= AddGear(hwRound(tmp^.X), hwRound(tmp^.Y), gtHammerHit, 0, _0, _0, 0);
tmp2^.LinkedGear:= tmp;
SetAllToActive
@@ -5893,7 +5880,6 @@
if CheckLandValue(hwRound(Gear^.X + Gear^.dX + SignAs(_6,Gear^.dX)), hwRound(Gear^.Y + _1_9)
, lfIndestructible) then
begin
- //Gear^.X := Gear^.X + Gear^.dX;
Gear^.Y := Gear^.Y + _1_9
end;
end;
@@ -5905,13 +5891,10 @@
end
else
begin
- //Gear^.dY := Gear^.dY + cGravity;
- //Gear^.Y := Gear^.Y + Gear^.dY;
if CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y)) then
Gear^.Timer := 1
end;
- //Gear^.X := Gear^.X + HitGear^.dX;
HitGear^.X := Gear^.X;
HitGear^.Y := Gear^.Y;
SetLittle(HitGear^.dY);
@@ -5956,13 +5939,6 @@
AllInactive := false;
hh := Gear^.Hedgehog;
- // no, you can't do that here
- {DrawCentered(hwRound(hh^.Gear^.X) + WorldDx, hwRound(hh^.Gear^.Y) + WorldDy -
- cHHRadius - 14 - hh^.HealthTagTex^.h, hh^.HealthTagTex);
- }
- (*DrawCircle(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Radius, 1.5, 0, 0, $FF,
- $FF);*)
-
if ((Gear^.Message and gmUp) <> 0) then
begin
if (GameTicks and $F) <> 0 then
@@ -5998,12 +5974,6 @@
RecountTeamHealth(hh^.Team);
inc(graves.ar^[Gear^.Tag]^.Health);
inc(Gear^.Tag)
-{-for i:= 0 to High(graves) do begin
- if hh^.Gear^.Health > 0 then begin
- dec(hh^.Gear^.Health);
- inc(graves[i]^.Health);
- end;
- end; -}
end
else
begin
@@ -6020,6 +5990,8 @@
RenderHealth(resgear^.Hedgehog^);
RecountTeamHealth(resgear^.Hedgehog^.Team);
resgear^.Hedgehog^.Effects[heResurrected]:= 1;
+ if resgear^.Hedgehog^.King then
+ resgear^.Hedgehog^.Team^.hasKing:= true;
{ Reviving a hog implies its clan is now alive, too. }
resgear^.Hedgehog^.Team^.Clan^.DeathLogged:= false;
s:= ansistring(resgear^.Hedgehog^.Name);
@@ -6037,7 +6009,6 @@
Gear^.Timer := 250;
Gear^.doStep := @doStepIdle;
end
- //if hh^.Gear^.Health = 0 then doStepHedgehogFree(hh^.Gear);
end;
procedure doStepResurrector(Gear: PGear);
@@ -6150,8 +6121,7 @@
((Gear^.State and (gstMoving or gstHHDeath or gstHHGone)) = 0) then
HideHog(HH)
end
- //else if (HH^.Gear <> nil) and (HH^.Gear^.State and gstInvisible <> 0) then
- else if (HH^.GearHidden <> nil) then// and (HH^.Gear^.State and gstInvisible <> 0) then
+ else if (HH^.GearHidden <> nil) then
begin
RestoreHog(HH);
s:= ansistring(HH^.Name);
@@ -6257,9 +6227,8 @@
////////////////////////////////////////////////////////////////////////////////
(*
-WIP. The ice gun will have the following effects. It has been proposed by sheepluva that it take the appearance of a large freezer
-spewing ice cubes. The cubes will be visual gears only. The scatter from them and the impact snow dust should help hide imprecisions in things like the GearsNear effect.
-For now we assume a "ray" like a deagle projected out from the gun.
+The ice gun has the following effects:
+A "ray" like a deagle is projected out from the gun.
All these effects assume the ray's angle is not changed and that the target type was unchanged over a number of ticks. This is a simplifying assumption for "gun was applying freezing effect to the same target".
* When fired at water a layer of ice textured land is added above the water.
* When fired at non-ice land (land and lfLandMask and not lfIce) the land is overlaid with a thin layer of ice textured land around that point (say, 1 or 2px into land, 1px above). For attractiveness, a slope would probably be needed.
@@ -6267,10 +6236,8 @@
As long as the gun is on the hog, a frozen hog sprite creeps up from the feet to the head.
If the effect is interrupted before reaching the top, the freezing state is cleared.
A frozen hog will animate differently.
- To be decided, but possibly in a similar fashion to a grave when it comes to explosions.
- The hog might (possibly) not be damaged by explosions.
+ Frozen hogs take less damage and are harder to push.
This might make freezing potentially useful for friendlies in a bad position.
- It might be better to allow damage though.
A frozen hog stays frozen for a certain number of turns.
Each turn the frozen overlay becomes fainter, until it fades and the hog animates normally again.
*)
@@ -6312,8 +6279,6 @@
procedure updateTarget(Gear:PGear; newX, newY:HWFloat);
-// var
-// iter:PGear;
begin
with Gear^ do
begin
@@ -6330,10 +6295,7 @@
procedure doStepIceGun(Gear: PGear);
const iceWaitCollision = 0;
const iceCollideWithGround = 1;
-//const iceWaitNextTarget:Longint = 2;
-//const iceCollideWithHog:Longint = 4;
const iceCollideWithWater = 5;
-//const waterFreezingTime:Longint = 500;
const groundFreezingTime = 1000;
const iceRadius = 32;
const iceHeight = 40;
@@ -6354,9 +6316,10 @@
exit
end;
updateFuel(Gear);
- if WorldWrap(Gear) and (WorldEdge = weWrap) and (Gear^.Target.X = NoPointX) then
- // Use FlightTime to count number of times the gear has world-wrapped
- inc(Gear^.FlightTime);
+ if (WorldEdge <> weBounce) then
+ if WorldWrap(Gear) and (WorldEdge = weWrap) and (Gear^.Target.X = NoPointX) then
+ // Use FlightTime to count number of times the gear has world-wrapped
+ inc(Gear^.FlightTime);
with Gear^ do
begin
@@ -6366,13 +6329,18 @@
if (ndX <> dX) or (ndY <> dY) or (Gear^.Message and (gmUp or gmDown) <> 0) or
(((Target.X <> NoPointX) and (Target.X and LAND_WIDTH_MASK = 0) and
(Target.Y and LAND_HEIGHT_MASK = 0) and ((Land[Target.Y, Target.X] = 0)) and
- (not CheckCoordInWater(Target.X, Target.Y))) and (CheckGearNear(gtAirMine, int2hwFloat(Target.X),int2hwFloat(Target.Y), Gear^.Radius*3, Gear^.Radius*3) = nil)) then
+ (not CheckCoordInWater(Target.X, Target.Y))) and (CheckGearNear(gtAirMine, int2hwFloat(Target.X),int2hwFloat(Target.Y), Gear^.Radius*3, Gear^.Radius*3) = nil) and
+ (not ((WorldEdge = weBounce) and ((Target.X > rightX) or (Target.X < leftX))))) then
begin
updateTarget(Gear, ndX, ndY);
Timer := iceWaitCollision;
FlightTime := 0;
end
- else
+ // Extend ice beam, unless it is far outside he map boundaries
+ else if (not ((hwRound(X + dX) > max(LAND_WIDTH,4096)*2) or
+ (hwRound(X + dX) < -max(LAND_WIDTH,4096)*2) or
+ (hwRound(Y + dY) < -max(LAND_HEIGHT,4096)*2) or
+ (hwRound(Y + dY) > max(LAND_HEIGHT,4096)+512))) then
begin
X:= X + dX;
Y:= Y + dY;
@@ -6503,7 +6471,6 @@
iter:= iter^.NextGear
end;
- // FillRoundInLandWithIce(Target.X, Target.Y, iceRadius);
SetAllHHToActive;
Timer := iceWaitCollision;
Power:= GameTicks
@@ -6523,22 +6490,6 @@
SetAllHHToActive;
Timer := iceWaitCollision;
end;
-(*
- Any ideas for something that would look good here?
- if (Target.X <> NoPointX) and ((Timer = iceCollideWithGround) or (Timer = iceCollideWithWater)) and (GameTicks mod max((groundFreezingTime-((GameTicks - Power)*2)),2) = 0) then //and CheckLandValue(Target.X, Target.Y, lfIce) then
- begin
- vg:= AddVisualGear(Target.X+random(20)-10, Target.Y+random(40)-10, vgtDust, 1);
- if vg <> nil then
- begin
- i:= random(100) + 155;
- vg^.Tint:= IceColor or $FF;
- vg^.Angle:= random(360);
- vg^.dx:= 0.001 * random(80);
- vg^.dy:= 0.001 * random(80)
- end
- end;
-*)
-
// freeze nearby hogs
hogs := GearsNear(int2hwFloat(Target.X), int2hwFloat(Target.Y), gtHedgehog, Gear^.Radius*2);
if hogs.size > 0 then
@@ -6566,6 +6517,13 @@
X:= HHGear^.X;
Y:= HHGear^.Y
end
+ else if (WorldEdge = weBounce) and ((gX > rightX) or (gX < leftX)) then
+ begin
+ Target.X:= gX;
+ Target.Y:= gY;
+ X:= HHGear^.X;
+ Y:= HHGear^.Y
+ end
else
begin
iter:= CheckGearNear(Gear, gtAirMine, Gear^.Radius*2, Gear^.Radius*2);
@@ -6577,16 +6535,6 @@
Y:= HHGear^.Y
end
end;
- if (gX > max(LAND_WIDTH,4096)*2) or
- (gX < -max(LAND_WIDTH,4096)) or
- (gY < -max(LAND_HEIGHT,4096)) or
- (gY > max(LAND_HEIGHT,4096)+512) then
- begin
- //X:= HHGear^.X;
- //Y:= HHGear^.Y
- Target.X:= gX;
- Target.Y:= gY;
- end
end
end;
end;
@@ -6809,8 +6757,6 @@
////////////////////////////////////////////////////////////////////////////////
procedure doStepKnife(Gear: PGear);
-//var ox, oy: LongInt;
-// la: hwFloat;
var a: real;
begin
// Gear is shrunk so it can actually escape the hog without carving into the terrain
@@ -6834,29 +6780,12 @@
end
else if (Gear^.CollisionIndex = -1) and (Gear^.Timer = 0) then
begin
- (*ox:= 0; oy:= 0;
- if TestCollisionYwithGear(Gear, -1) <> 0 then oy:= -1;
- if TestCollisionXwithGear(Gear, 1) <> 0 then ox:= 1;
- if TestCollisionXwithGear(Gear, -1) <> 0 then ox:= -1;
- if TestCollisionYwithGear(Gear, 1) <> 0 then oy:= 1;
-
- la:= _10000;
- if (ox <> 0) or (oy <> 0) then
- la:= CalcSlopeNearGear(Gear, ox, oy);
- if la = _10000 then
- begin
- // debug for when we couldn't get an angle
- //AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtSmokeWhite);
-*)
if Gear^.Health > 0 then
PlaySound(Gear^.ImpactSound);
Gear^.DirAngle:= DxDy2Angle(Gear^.dX, Gear^.dY) + (random(30)-15);
if (Gear^.dX.isNegative and Gear^.dY.isNegative) or
((not Gear^.dX.isNegative) and (not Gear^.dY.isNegative)) then Gear^.DirAngle:= Gear^.DirAngle-90;
- // end
- // else Gear^.DirAngle:= hwFloat2Float(la)*90; // sheepluva's comment claims 45deg = 0.5 - yet orientation doesn't seem consistent?
- // AddFileLog('la: '+floattostr(la)+' DirAngle: '+inttostr(round(Gear^.DirAngle)));
Gear^.dX:= _0;
Gear^.dY:= _0;
Gear^.State:= Gear^.State and (not gstMoving) or gstCollision;
@@ -6958,137 +6887,4 @@
Gear^.doStep := @doStepBulletWork
end;
-(*
- This didn't end up getting used, but, who knows, might be reasonable for javellin or something
-// Make the knife initial angle based on the hog attack angle, or is that too hard?
-procedure doStepKnife(Gear: PGear);
-var t,
- gx, gy, ga, // gear x,y,angle
- lx, ly, la, // land x,y,angle
- ox, oy, // x,y offset
- w, h, // wXh of clip area
- tx, ty // tip position in sprite
- : LongInt;
- surf: PSDL_Surface;
- s: hwFloat;
-
-begin
- Gear^.dY := Gear^.dY + cGravity;
- if (GameFlags and gfMoreWind) <> 0 then
- Gear^.dX := Gear^.dX + cWindSpeed / Gear^.Density;
- Gear^.X := Gear^.X + Gear^.dX;
- Gear^.Y := Gear^.Y + Gear^.dY;
- CheckGearDrowning(Gear);
- gx:= hwRound(Gear^.X);
- gy:= hwRound(Gear^.Y);
- if Gear^.State and gstDrowning <> 0 then exit;
- with Gear^ do
- begin
- if CheckLandValue(gx, gy, lfLandMask) then
- begin
- t:= Angle + hwRound((hwAbs(dX)+hwAbs(dY)) * _10);
-
- if t < 0 then inc(t, 4096)
- else if 4095 < t then dec(t, 4096);
- Angle:= t;
-
- DirAngle:= Angle / 4096 * 360
- end
- else
- begin
-//This is the set of postions for the knife.
-//Using FlipSurface and copyToXY the knife can be written to the LandPixels at 32 positions, and an appropriate line drawn in Land.
- t:= Angle mod 1024;
- case t div 128 of
- 0: begin
- ox:= 2; oy:= 5;
- w := 25; h:= 5;
- tx:= 0; ty:= 2
- end;
- 1: begin
- ox:= 2; oy:= 15;
- w:= 24; h:= 8;
- tx:= 0; ty:= 7
- end;
- 2: begin
- ox:= 2; oy:= 27;
- w:= 23; h:= 12;
- tx:= -12; ty:= -5
- end;
- 3: begin
- ox:= 2; oy:= 43;
- w:= 21; h:= 15;
- tx:= 0; ty:= 14
- end;
- 4: begin
- ox:= 29; oy:= 8;
- w:= 19; h:= 19;
- tx:= 0; ty:= 17
- end;
- 5: begin
- ox:= 29; oy:= 32;
- w:= 15; h:= 21;
- tx:= 0; ty:= 20
- end;
- 6: begin
- ox:= 51; oy:= 3;
- w:= 11; h:= 23;
- tx:= 0; ty:= 22
- end;
- 7: begin
- ox:= 51; oy:= 34;
- w:= 7; h:= 24;
- tx:= 0; ty:= 23
- end
- end;
-
- surf:= SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, RMask, GMask, BMask, AMask);
- copyToXYFromRect(SpritesData[sprKnife].Surface, surf, ox, oy, w, h, 0, 0);
- // try to make the knife hit point first
- lx := 0;
- ly := 0;
- if CalcSlopeTangent(Gear, gx, gy, lx, ly, 255) then
- begin
- la:= vector2Angle(int2hwFloat(lx), int2hwFloat(ly));
- ga:= vector2Angle(dX, dY);
- AddFileLog('la: '+inttostr(la)+' ga: '+inttostr(ga)+' Angle: '+inttostr(Angle));
- // change to 0 to 4096 forced by LongWord in Gear
- if la < 0 then la:= 4096+la;
- if ga < 0 then ga:= 4096+ga;
- if ((Angle > ga) and (Angle < la)) or ((Angle < ga) and (Angle > la)) then
- begin
- if Angle >= 2048 then dec(Angle, 2048)
- else if Angle < 2048 then inc(Angle, 2048)
- end;
- AddFileLog('la: '+inttostr(la)+' ga: '+inttostr(ga)+' Angle: '+inttostr(Angle))
- end;
- case Angle div 1024 of
- 0: begin
- flipSurface(surf, true);
- flipSurface(surf, true);
- BlitImageAndGenerateCollisionInfo(gx-(w-tx), gy-(h-ty), w, surf)
- end;
- 1: begin
- flipSurface(surf, false);
- BlitImageAndGenerateCollisionInfo(gx-(w-tx), gy-ty, w, surf)
- end;
- 2: begin // knife was actually drawn facing this way...
- BlitImageAndGenerateCollisionInfo(gx-tx, gy-ty, w, surf)
- end;
- 3: begin
- flipSurface(surf, true);
- BlitImageAndGenerateCollisionInfo(gx-tx, gy-(h-ty), w, surf)
- end
- end;
- SDL_FreeSurface(surf);
- // this needs to calculate actual width/height + land clipping since update texture doesn't.
- // i.e. this will crash if you fire near sides of map, but until I get the blit right, not going to put real values
- UpdateLandTexture(hwRound(X)-32, 64, hwRound(Y)-32, 64, true);
- DeleteGear(Gear);
- exit
- end
- end;
-end;
-*)
-
end.
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uGearsHedgehog.pas
--- a/hedgewars/uGearsHedgehog.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uGearsHedgehog.pas Mon Jan 14 12:35:32 2019 -0500
@@ -487,14 +487,7 @@
PlaySoundV(sndOw1, Team^.voicepack);
cVampiric:= true;
end;
- amPiano: begin
- // Tuck the hedgehog away until the piano attack is completed
- Unplaced:= true;
- X:= _0;
- Y:= _0;
- newGear:= AddGear(TargetPoint.X, -1024, gtPiano, 0, _0, _0, 0);
- PauseMusic
- end;
+ amPiano: newGear:= AddGear(TargetPoint.X, -1024, gtPiano, 0, _0, _0, 0);
amFlamethrower: newGear:= AddGear(hwRound(X), hwRound(Y), gtFlamethrower, 0, xx * _0_5, yy * _0_5, 0);
amLandGun: newGear:= AddGear(hwRound(X), hwRound(Y), gtLandGun, 0, xx * _0_5, yy * _0_5, 0);
amResurrector: begin
@@ -1307,7 +1300,7 @@
else if not isInMultiShoot then
AllInactive:= false;
-if (TurnTimeLeft = 0) or (HHGear^.Damage > 0) or (LuaEndTurnRequested = true) then
+if (TurnTimeLeft = 0) or (HHGear^.Damage > 0) or (((GameFlags and gfKing) <> 0) and (not Hedgehog^.Team^.hasKing)) or (LuaEndTurnRequested = true) then
begin
if (Hedgehog^.CurAmmoType = amKnife) then
LoadHedgehogHat(Hedgehog^, Hedgehog^.Hat);
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uGearsList.pas
--- a/hedgewars/uGearsList.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uGearsList.pas Mon Jan 14 12:35:32 2019 -0500
@@ -771,7 +771,6 @@
procedure DeleteGear(Gear: PGear);
var team: PTeam;
t,i: Longword;
- k: boolean;
cakeData: PCakeData;
iterator: PGear;
begin
@@ -857,19 +856,14 @@
if Gear^.Hedgehog^.King then
begin
- // are there any other kings left? Just doing nil check. Presumably a mortally wounded king will get reaped soon enough
- k:= false;
+ Gear^.Hedgehog^.Team^.hasKing:= false;
for i:= 0 to Pred(team^.Clan^.TeamsNumber) do
- if (team^.Clan^.Teams[i]^.Hedgehogs[0].Gear <> nil) then
- k:= true;
- if not k then
- for i:= 0 to Pred(team^.Clan^.TeamsNumber) do
- with team^.Clan^.Teams[i]^ do
- for t:= 0 to cMaxHHIndex do
- if Hedgehogs[t].Gear <> nil then
- Hedgehogs[t].Gear^.Health:= 0
- else if (Hedgehogs[t].GearHidden <> nil) then
- Hedgehogs[t].GearHidden^.Health:= 0 // hog is still hidden. if tardis should return though, lua, eh...
+ with team^.Clan^.Teams[i]^ do
+ for t:= 0 to cMaxHHIndex do
+ if Hedgehogs[t].Gear <> nil then
+ Hedgehogs[t].Gear^.Health:= 0
+ else if (Hedgehogs[t].GearHidden <> nil) then
+ Hedgehogs[t].GearHidden^.Health:= 0 // hog is still hidden. if tardis should return though, lua, eh...
end;
// should be not CurrentHedgehog, but hedgehog of the last gear which caused damage to this hog
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uGearsRender.pas
--- a/hedgewars/uGearsRender.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uGearsRender.pas Mon Jan 14 12:35:32 2019 -0500
@@ -69,8 +69,6 @@
if (RopePoints.Count > 0) or (Gear^.Elasticity.QWordValue > 0) then
begin
EnableTexture(false);
- //glEnable(GL_LINE_SMOOTH);
-
Tint(Gear^.Tint shr 24 div 3, Gear^.Tint shr 16 and $FF div 3, Gear^.Tint shr 8 and $FF div 3, Gear^.Tint and $FF);
@@ -92,7 +90,6 @@
openglPopMatrix();
EnableTexture(true);
- //glDisable(GL_LINE_SMOOTH)
end
end;
@@ -263,7 +260,7 @@
// Render some informational GUI next to hedgehog, like fuel and alternate weapon
procedure RenderHHGuiExtras(Gear: PGear; ox, oy: LongInt);
var HH: PHedgehog;
- sx, sy: LongInt;
+ sx, sy, sign, m: LongInt;
begin
HH:= Gear^.Hedgehog;
sx:= ox + 1; // this offset is very common
@@ -275,10 +272,32 @@
if (Gear^.State and gstHHGone) <> 0 then
exit;
+ // render crosshair
+ if (CrosshairGear <> nil) and (Gear = CrosshairGear) then
+ begin
+ sign:= hwSign(Gear^.dX);
+ m:= 1;
+ if ((Gear^.State and gstHHHJump) <> 0) and (HH^.Effects[heArtillery] = 0) then
+ m:= -1;
+ setTintAdd(true);
+ Tint(HH^.Team^.Clan^.Color shl 8 or $FF);
+ DrawTextureRotated(CrosshairTexture,
+ 12, 12, CrosshairX + WorldDx, CrosshairY + WorldDy, 0,
+ sign * m * (Gear^.Angle * 180.0) / cMaxAngle);
+ untint;
+ setTintAdd(false);
+ end;
+
+ // render gear-related extras: alt weapon, fuel, other
if ((Gear^.State and gstHHDriven) <> 0) and (CurAmmoGear <> nil) then
begin
case CurAmmoGear^.Kind of
gtJetpack: begin
+ // render jetpack contour if underwater
+ if (((not SuddenDeathDmg) and (WaterOpacity > 179)) or (SuddenDeathDmg and (SDWaterOpacity > 179))) and
+ ((cWaterLine < (hwRound(Gear^.Y) + Gear^.Radius - 16)) or
+ ((WorldEdge = weSea) and ((hwRound(Gear^.X) < LeftX) or (hwRound(Gear^.X) > RightX)))) then
+ DrawSprite(sprJetpack, sx-32, sy-32, 4);
if CurAmmoGear^.Tex <> nil then
DrawTextureCentered(sx, sy - 40, CurAmmoGear^.Tex);
DrawAltWeapon(Gear, sx, sy);
@@ -309,6 +328,7 @@
curhat: PTexture;
begin
HH:= Gear^.Hedgehog;
+ CrosshairGear:= nil;
if HH^.Unplaced then
exit;
if (HH^.CurAmmoType = amKnife) and (HH = CurrentHedgehog) then
@@ -404,7 +424,7 @@
begin
if ((Gear^.State and (gstHHThinking or gstAnimation)) = 0) and
/// If current ammo is active, and current ammo has alt attack and uses a crosshair (rope, basically, right now, with no crosshair for parachute/saucer
- (((CurAmmoGear <> nil) and //((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0) and
+ (((CurAmmoGear <> nil) and
// don't render crosshair/laser during kamikaze
((CurAmmoGear^.AmmoType <> amKamikaze) or ((Gear^.State and gstAttacking) = 0)) and
((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_NoCrossHair) = 0)) or
@@ -491,17 +511,12 @@
DrawLineWrapped(hx, hy, tx, ty, 1.0, (sign*m) < 0, wraps, $FF, $00, $00, $C0);
end;
- // draw crosshair
+
+ // calculate crosshair position
CrosshairX := Round(hwRound(Gear^.X) + dx * 80 + GetLaunchX(HH^.CurAmmoType, sign * m, Gear^.Angle));
CrosshairY := Round(hwRound(Gear^.Y) + dy * 80 + GetLaunchY(HH^.CurAmmoType, Gear^.Angle));
-
- setTintAdd(true);
- Tint(HH^.Team^.Clan^.Color shl 8 or $FF);
- DrawTextureRotated(CrosshairTexture,
- 12, 12, CrosshairX + WorldDx, CrosshairY + WorldDy, 0,
- sign * m * (Gear^.Angle * 180.0) / cMaxAngle);
- untint;
- setTintAdd(false);
+ // crosshair will be rendered in RenderHHGuiExtras
+ CrosshairGear := Gear;
end;
hx:= ox + 8 * sign;
@@ -800,9 +815,6 @@
amSeduction: begin
DrawSpriteRotated(sprHandSeduction, hx, hy, sign, aangle);
DrawCircle(ox, oy, 248, 4, $FF, $00, $00, $AA);
- //Tint($FF, $0, $0, $AA);
- //DrawTexture(ox - 240, oy - 240, SpritesData[sprVampiric].Texture, 10);
- //untint;
end;
amVampiric: DrawSpriteRotatedF(sprHandVamp, hx, hy, (RealTicks div 125) mod 4, sign, aangle);
amRCPlane: begin
@@ -899,17 +911,6 @@
0);
HatVisible:= true;
- (* with HH^ do
- if (HatTex <> nil)
- and (HatVisibility > 0) then
- DrawTextureF(HatTex,
- HatVisibility,
- sx,
- sy - 5,
- 0,
- sign,
- 32,
- 32); *)
end;
defaultPos:= false
@@ -1082,13 +1083,6 @@
if (Gear^.State and gstHHDriven) <> 0 then
begin
- (* if (CurAmmoGear = nil) then
- begin
- amt:= CurrentHedgehog^.CurAmmoType;
- case amt of
- amJetpack: DrawSprite(sprJetpack, sx-32, sy-32, 0);
- end
- end; *)
if (CurAmmoGear = nil) then
begin
if ((Gear^.State and (gstAttacked or gstAnimation or gstHHJumping)) = 0)
@@ -1172,11 +1166,9 @@
tx := ox;
// don't go offscreen
- //tx := round(max(((-cScreenWidth + 16) / cScaleFactor) + SpritesData[sprFinger].Width div 2, min(((cScreenWidth - 16) / cScaleFactor) - SpritesData[sprFinger].Width div 2, tx)));
- //ty := round(max(cScreenHeight div 2 - ((cScreenHeight - 16) / cScaleFactor) + SpritesData[sprFinger].Height div 2, min(cScreenHeight div 2 - ((-cScreenHeight + SpritesData[sprFinger].Height) / (cScaleFactor)) - SpritesData[sprFinger].Width div 2 - 96, ty)));
- t:= 32;//trunc((SpritesData[sprFinger].Width + t) / cScaleFactor);
+ t:= 32;
tx := min(max(tx, ViewLeftX + t), ViewRightX - t);
- t:= 32;//trunc((SpritesData[sprFinger].Height + t) / cScaleFactor);
+ t:= 32;
ty := min(ty, ViewBottomY - 96);
// don't overlap with HH or HH tags
if ty < ViewTopY + t then
@@ -1189,7 +1181,9 @@
dAngle := DxDy2Angle(int2hwfloat(ty - oy), int2hwfloat(tx - ox)) + 90;
+ Tint(Team^.Clan^.Color shl 8 or $FF);
DrawSpriteRotatedF(sprFinger, tx, ty, RealTicks div 32 mod 16, 1, dAngle);
+ untint;
end;
@@ -1253,18 +1247,21 @@
aAngle: real;
startX, endX, startY, endY: LongInt;
begin
- // airmine has its own sprite
+ // airmine has its own sprite
if (Gear^.State and gstFrozen <> 0) and (Gear^.Kind <> gtAirMine) then Tint($A0, $A0, $FF, $FF);
- //if Gear^.State and gstFrozen <> 0 then Tint(IceColor or $FF);
if Gear^.Target.X <> NoPointX then
if Gear^.AmmoType = amBee then
DrawSpriteRotatedF(sprTargetBee, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
else if Gear^.AmmoType = amIceGun then
- //DrawSprite(sprSnowDust, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, (RealTicks shr 2) mod 8)
- //DrawTextureRotatedF(SpritesData[sprSnowDust].Texture, 1, 0, 0, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, (RealTicks shr 2) mod 8, 1, 22, 22, (RealTicks shr 3) mod 360)
DrawTextureRotatedF(SpritesData[sprSnowDust].Texture, 1/(1+(RealTicks shr 8) mod 5), 0, 0, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, (RealTicks shr 2) mod 8, 1, 22, 22, (RealTicks shr 3) mod 360)
else
+ begin
+ if CurrentHedgehog <> nil then
+ Tint(CurrentHedgehog^.Team^.Clan^.Color shl 8 or $FF);
DrawSpriteRotatedF(sprTargetP, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ if CurrentHedgehog <> nil then
+ untint;
+ end;
case Gear^.Kind of
gtGrenade: DrawSpriteRotated(sprBomb, x, y, 0, Gear^.DirAngle);
@@ -1318,9 +1315,7 @@
DrawTextureF(Gear^.Hedgehog^.Team^.GraveTex, 1, x, y, (RealTicks shr 7+Gear^.uid) and 15, 1, 32, 32);
if Gear^.Health > 0 then
begin
- //Tint($33, $33, $FF, max($40, round($FF * abs(1 - (GameTicks mod (6000 div Gear^.Health)) / 750))));
Tint($f5, $db, $35, max($40, round($FF * abs(1 - (RealTicks mod 1500) / (750 + Gear^.Health)))));
- //Tint($FF, $FF, $FF, max($40, round($FF * abs(1 - (RealTicks mod 1500) / 750))));
DrawSprite(sprVampiric, x - 24, y - 24, 0);
untint
end
@@ -1467,7 +1462,13 @@
DrawSpriteRotatedF(sprTeleport, hwRound(HHGear^.X) + 1 + WorldDx, hwRound(HHGear^.Y) - 3 + WorldDy, 11 - Gear^.Pos, hwSign(HHGear^.dX), 0)
end
end;
- gtSwitcher: DrawSprite(sprSwitch, x - 16, y - 56, (RealTicks shr 6) mod 12);
+ gtSwitcher: begin
+ setTintAdd(true);
+ Tint(Gear^.Hedgehog^.Team^.Clan^.Color shl 8 or $FF);
+ DrawSprite(sprSwitch, x - 16, y - 56, (RealTicks shr 6) mod 12);
+ untint;
+ setTintAdd(false);
+ end;
gtTarget: begin
Tint($FF, $FF, $FF, round($FF * Gear^.Timer / 1000));
DrawSprite(sprTarget, x - 16, y - 16, 0);
@@ -1551,8 +1552,6 @@
Tint(Gear^.Tint);
// Needs a nicer white texture to tint
DrawTextureRotatedF(SpritesData[sprSnowDust].Texture, 1, 0, 0, x, y, 0, 1, 8, 8, Gear^.DirAngle);
- //DrawSpriteRotated(sprSnowDust, x, y, 0, Gear^.DirAngle);
- //DrawTexture(x, y, SpritesData[sprVampiric].Texture, 0.1);
untint;
end
else //if not isInLag then
@@ -1567,8 +1566,6 @@
DrawSprite(sprFlake, x, y, Gear^.Timer)
else
DrawSpriteRotatedF(sprFlake, x, y, Gear^.Timer, 1, Gear^.DirAngle);
-//DrawSprite(sprFlake, x-SpritesData[sprFlake].Width div 2, y-SpritesData[sprFlake].Height div 2, Gear^.Timer)
-//DrawSpriteRotatedF(sprFlake, x-SpritesData[sprFlake].Width div 2, y-SpritesData[sprFlake].Height div 2, Gear^.Timer, 1, Gear^.DirAngle);
if Gear^.FlightTime > 0 then
untint;
end;
@@ -1586,11 +1583,6 @@
DrawSprite(sprTardis, x-25, y-64,1);
if Gear^.Pos <> 2 then
untint
-(*
- Tint(Gear^.Hedgehog^.Team^.Clan^.Color shl 8 or max($00, round(Gear^.Power * abs(1 - (RealTicks mod 500) / 250))));
- DrawTexture(x-6, y-70, SpritesData[sprVampiric].Texture, 0.25);
- untint
-*)
end;
gtIceGun: begin
HHGear := Gear^.Hedgehog^.Gear;
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uGearsUtils.pas
--- a/hedgewars/uGearsUtils.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uGearsUtils.pas Mon Jan 14 12:35:32 2019 -0500
@@ -64,6 +64,7 @@
function GetUtility(Hedgehog: PHedgehog): TAmmoType;
function WorldWrap(var Gear: PGear): boolean;
+function HomingWrap(var Gear: PGear): boolean;
function IsHogLocal(HH: PHedgehog): boolean;
@@ -117,11 +118,6 @@
end;
if (Mask and EXPLAutoSound) <> 0 then PlaySound(sndExplosion);
-(*if (Mask and EXPLAllDamageInRadius) = 0 then
- dmgRadius:= Radius shl 1
-else
- dmgRadius:= Radius;
-dmgBase:= dmgRadius + cHHRadius div 2;*)
dmgBase:= Radius shl 1 + cHHRadius div 2;
// we might have to run twice if weWrap is enabled
@@ -135,8 +131,6 @@
while Gear <> nil do
begin
dmg:= 0;
- //dmg:= dmgRadius + cHHRadius div 2 - hwRound(Distance(Gear^.X - int2hwFloat(X), Gear^.Y - int2hwFloat(Y)));
- //if (dmg > 1) and
if (Gear^.State and gstNoDamage) = 0 then
begin
case Gear^.Kind of
@@ -146,7 +140,6 @@
gtMelonPiece,
gtGrenade,
gtClusterBomb,
- // gtCluster, too game breaking I think
gtSMine,
gtAirMine,
gtCase,
@@ -325,11 +318,8 @@
tdy:= -cGravityf;
if random(2) = 0 then
dx := -dx;
- //if random(2) = 0 then
- // dy := -dy;
FrameTicks:= random(500) + 1000;
State:= ord(sprBubbles);
- //Tint:= $bd2f03ff
Tint:= $ff0000ff
end
end
@@ -468,11 +458,6 @@
if ((Gear^.Hedgehog^.Effects[heInvulnerable] <> 0)) then
exit;
- //if _0_6 < Gear^.dY then
- // PlaySound(sndOw4, Gear^.Hedgehog^.Team^.voicepack)
- //else
- // PlaySound(sndOw1, Gear^.Hedgehog^.Team^.voicepack);
-
if Gear^.LastDamage <> nil then
ApplyDamage(Gear, Gear^.LastDamage, dmg, dsFall)
else
@@ -486,7 +471,6 @@
dAngle: real;
begin
// Frac/Round to be kind to JS as of 2012-08-27 where there is yet no int64/uint64
- //dAngle := (Gear^.dX.QWordValue + Gear^.dY.QWordValue) / $80000000;
dAngle := (Gear^.dX.Round + Gear^.dY.Round) / 2 + (Gear^.dX.Frac/$100000000+Gear^.dY.Frac/$100000000);
if not Gear^.dX.isNegative then
Gear^.DirAngle := Gear^.DirAngle + dAngle
@@ -756,7 +740,7 @@
end
else // submersible
begin
- // drown submersible grears if far below map
+ // drown submersible gears if far below map
if (Y > cWaterLine + cVisibleWater*4) then
begin
DrownGear(Gear);
@@ -795,7 +779,8 @@
// splash sound animation and droplets
if isImpact or isSkip then
- addSplashForGear(Gear, isSkip);
+ if (not (((dist2Water + Gear^.Radius div 2) < 0) or (abs(dist2Water + Gear^.Radius) >= Gear^.Radius))) then
+ addSplashForGear(Gear, isSkip);
if isSkip then
ScriptCall('onGearWaterSkip', Gear^.uid);
@@ -1744,7 +1729,7 @@
Trying to make the checks a little broader than on first pass to catch things that don't move normally.
*)
function WorldWrap(var Gear: PGear): boolean;
-//var tdx: hwFloat;
+var bounced: boolean;
begin
WorldWrap:= false;
if WorldEdge = weNone then exit(false);
@@ -1757,58 +1742,79 @@
Gear^.X:= Gear^.X + int2hwfloat(rightX - leftX)
else Gear^.X:= Gear^.X - int2hwfloat(rightX - leftX);
LeftImpactTimer:= 150;
- RightImpactTimer:= 150
+ RightImpactTimer:= 150;
+ WorldWrap:= true;
end
else if WorldEdge = weBounce then
begin
- if (hwRound(Gear^.X) - Gear^.Radius < leftX) then
+ bounced:= false;
+ if (hwRound(Gear^.X) - Gear^.Radius < leftX) and (hwSign(Gear^.dX) = -1) and (not isZero(Gear^.dX)) then
begin
LeftImpactTimer:= 333;
Gear^.dX.isNegative:= false;
- Gear^.X:= int2hwfloat(leftX + Gear^.Radius)
+ Gear^.X:= int2hwfloat(leftX + Gear^.Radius);
+ bounced:= true;
end
- else
+ else if (hwRound(Gear^.X) - Gear^.Radius > rightX) and (hwSign(Gear^.dX) = 1) and (not isZero(Gear^.dX)) then
begin
RightImpactTimer:= 333;
Gear^.dX.isNegative:= true;
- Gear^.X:= int2hwfloat(rightX-Gear^.Radius)
+ Gear^.X:= int2hwfloat(rightX-Gear^.Radius);
+ bounced:= true;
end;
- if (Gear^.Radius > 2) and (Gear^.dX.QWordValue > _0_001.QWordValue) then
- AddBounceEffectForGear(Gear);
- end{
- else if WorldEdge = weSea then
- begin
- if (hwRound(Gear^.Y) > cWaterLine) and (Gear^.State and gstSubmersible <> 0) then
- Gear^.State:= Gear^.State and (not gstSubmersible)
- else
+ if (bounced) then
begin
- Gear^.State:= Gear^.State or gstSubmersible;
- Gear^.X:= int2hwFloat(PlayWidth)*int2hwFloat(min(max(0,hwRound(Gear^.Y)),PlayHeight))/PlayHeight;
- Gear^.Y:= int2hwFloat(cWaterLine+cVisibleWater+Gear^.Radius*2);
- tdx:= Gear^.dX;
- Gear^.dX:= -Gear^.dY;
- Gear^.dY:= tdx;
- Gear^.dY.isNegative:= true
- end
- end};
-(*
-* Window in the sky (Gear moved high into the sky, Y is used to determine X) [unfortunately, not a safe thing to do. shame, I thought aerial bombardment would be kinda neat
-This one would be really easy to freeze game unless it was flagged unfortunately.
-
+ WorldWrap:= true;
+ if (Gear^.dX.QWordValue > _0_001.QWordValue) then
+ AddBounceEffectForGear(Gear);
+ end;
+ end
else
- begin
- Gear^.X:= int2hwFloat(PlayWidth)*int2hwFloat(min(max(0,hwRound(Gear^.Y)),PlayHeight))/PlayHeight;
- Gear^.Y:= -_2048-_256-_256;
- tdx:= Gear^.dX;
- Gear^.dX:= Gear^.dY;
- Gear^.dY:= tdx;
- Gear^.dY.isNegative:= false
- end
-*)
- WorldWrap:= true
+ WorldWrap:= true;
end;
end;
+(*
+Applies wrap-around logic for the target of homing gears.
+
+In wrap-around world edge, the shortest way may to the target might
+be across the border, so the X value of the target would lead the
+gear to the wrong direction across the whole map. This procedure
+changes the target X in this case.
+This function must be called after the gear passed through
+the wrap-around world edge (WorldWrap returned true).
+
+No-op for other world edges.
+
+Returns true if target has been changed.
+*)
+function HomingWrap(var Gear: PGear): boolean;
+var dist_center, dist_right, dist_left: hwFloat;
+begin
+ if WorldEdge = weWrap then
+ begin
+ HomingWrap:= false;
+ // We just check the same target 3 times:
+ // 1) in current section (no change)
+ // 2) clone in the right section
+ // 3) clone in the left section
+ // The gear will go for the target with the shortest distance to the gear.
+ // For simplicity, we only check distance on the X axis.
+ dist_center:= hwAbs(Gear^.X - int2hwFloat(Gear^.Target.X));
+ dist_right:= hwAbs(Gear^.X - int2hwFloat(Gear^.Target.X + (RightX-LeftX)));
+ dist_left:= hwAbs(Gear^.X - int2hwFloat(Gear^.Target.X - (RightX-LeftX)));
+ if (dist_left < dist_right) and (dist_left < dist_center) then
+ begin
+ dec(Gear^.Target.X, RightX-LeftX);
+ HomingWrap:= true;
+ end
+ else if (dist_right < dist_left) and (dist_right < dist_center) then
+ begin
+ inc(Gear^.Target.X, RightX-LeftX);
+ HomingWrap:= true;
+ end;
+ end;
+end;
// Add an audiovisual bounce effect for gear after it bounced from bouncy material.
// Graphical effect is based on speed.
@@ -1821,7 +1827,7 @@
procedure AddBounceEffectForGear(Gear: PGear; imageScale: Single);
var boing: PVisualGear;
begin
- if Gear^.Density < _0_01 then
+ if (Gear^.Density < _0_01) or (Gear^.Radius < 2) then
exit;
boing:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtStraightShot, 0, false, 1);
if boing <> nil then
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uIO.pas
--- a/hedgewars/uIO.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uIO.pas Mon Jan 14 12:35:32 2019 -0500
@@ -171,6 +171,10 @@
if s[2] = '.' then
ParseCommand('campvar ' + copy(s, 3, length(s) - 2), true);
end;
+ 'v': begin
+ if s[2] = '.' then
+ ParseCommand('missvar ' + copy(s, 3, length(s) - 2), true);
+ end;
'I': ParseCommand('pause server', true);
's': if gameType = gmtNet then
ParseChatCommand('chatmsg ', s, 2)
@@ -523,13 +527,14 @@
TargetPoint.X:= CursorPoint.X - WorldDx;
TargetPoint.Y:= cScreenHeight - CursorPoint.Y - WorldDy;
end;
- if (WorldEdge <> weBounce) then
+ if (WorldEdge <> weBounce) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NoWrapTarget) = 0) then
TargetPoint.X:= CalcWorldWrap(TargetPoint.X, 0);
SendIPCXY('p', TargetPoint.X, TargetPoint.Y);
end
else
begin
- TargetPoint.X:= CalcWorldWrap(TargetPoint.X, 0);
+ if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NoWrapTarget) = 0 then
+ TargetPoint.X:= CalcWorldWrap(TargetPoint.X, 0);
TargetPoint.X:= putX;
TargetPoint.Y:= putY
end;
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uInputHandler.pas
--- a/hedgewars/uInputHandler.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uInputHandler.pas Mon Jan 14 12:35:32 2019 -0500
@@ -38,6 +38,10 @@
procedure ProcessKey(event: TSDL_KeyboardEvent); inline;
procedure ProcessKey(code: LongInt; KeyDown: boolean);
+{$IFDEF USE_AM_NUMCOLUMN}
+function CheckDefaultSlotKeys: boolean;
+{$ENDIF}
+
procedure ResetKbd;
procedure ResetMouseWheel;
procedure FreezeEnterKey;
@@ -488,6 +492,27 @@
end;
+{$IFDEF USE_AM_NUMCOLUMN}
+function CheckDefaultSlotKeys: boolean;
+{$IFDEF USE_TOUCH_INTERFACE}
+begin
+ CheckDefaultSlotKeys:= false;
+{$ELSE}
+var i, code: LongInt;
+begin
+ for i:=1 to cMaxSlotIndex do
+ begin
+ code:= KeyNameToCode('f'+IntToStr(i));
+ if CurrentBinds.binds[CurrentBinds.indices[code]] <> 'slot '+char(i+48) then
+ begin
+ CheckDefaultSlotKeys:= false;
+ exit;
+ end;
+ end;
+ CheckDefaultSlotKeys:= true;
+{$ENDIF}
+end;
+{$ENDIF}
{$IFNDEF MOBILE}
procedure SetBinds(var binds: TBinds);
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uLand.pas
--- a/hedgewars/uLand.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uLand.pas Mon Jan 14 12:35:32 2019 -0500
@@ -285,13 +285,13 @@
procedure GenDrawnMap;
begin
- ResizeLand(4096, 2048);
+ ResizeLand((4096 * max(min(cFeatureSize,24),3)) div 12, (2048 * max(min(cFeatureSize,24),3)) div 12);
uLandPainted.Draw;
- MaxHedgehogs:= 48;
+ MaxHedgehogs:= 64;
hasGirders:= true;
- playHeight:= 2048;
- playWidth:= 4096;
+ playHeight:= LAND_HEIGHT;
+ playWidth:= LAND_WIDTH;
leftX:= ((LAND_WIDTH - playWidth) div 2);
rightX:= (playWidth + ((LAND_WIDTH - playWidth) div 2)) - 1;
topY:= LAND_HEIGHT - playHeight;
@@ -300,11 +300,11 @@
function SelectTemplate: LongInt;
var l: LongInt;
begin
- SelectTemplate:= 0;
+ SelectTemplate:= 0;
if (cReducedQuality and rqLowRes) <> 0 then
SelectTemplate:= SmallTemplates[getrandom(Succ(High(SmallTemplates)))]
else
- begin
+ begin
if cTemplateFilter = 0 then
begin
l:= getRandom(GroupedTemplatesCount);
@@ -313,22 +313,22 @@
dec(l, TemplateCounts[cTemplateFilter]);
until l < 0;
end
- else getRandom(1);
+ else getRandom(1);
- case cTemplateFilter of
- 0: OutError('Error selecting TemplateFilter. Ask unC0Rr about what you did wrong', true);
- 1: SelectTemplate:= SmallTemplates[getrandom(TemplateCounts[cTemplateFilter])];
- 2: SelectTemplate:= MediumTemplates[getrandom(TemplateCounts[cTemplateFilter])];
- 3: SelectTemplate:= LargeTemplates[getrandom(TemplateCounts[cTemplateFilter])];
- 4: SelectTemplate:= CavernTemplates[getrandom(TemplateCounts[cTemplateFilter])];
- 5: SelectTemplate:= WackyTemplates[getrandom(TemplateCounts[cTemplateFilter])];
- // For lua only!
- 6: begin
- SelectTemplate:= min(LuaTemplateNumber,High(EdgeTemplates));
- GetRandom(2) // burn 1
- end
- end
- end;
+ case cTemplateFilter of
+ 0: OutError('Error selecting TemplateFilter. Ask unC0Rr about what you did wrong', true);
+ 1: SelectTemplate:= SmallTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ 2: SelectTemplate:= MediumTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ 3: SelectTemplate:= LargeTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ 4: SelectTemplate:= CavernTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ 5: SelectTemplate:= WackyTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ // For lua only!
+ 6: begin
+ SelectTemplate:= min(LuaTemplateNumber,High(EdgeTemplates));
+ GetRandom(2) // burn 1
+ end
+ end
+ end;
WriteLnToConsole('Selected template #'+inttostr(SelectTemplate)+' using filter #'+inttostr(cTemplateFilter));
end;
@@ -886,7 +886,7 @@
mgRandom: GenTemplated(EdgeTemplates[SelectTemplate]);
mgMaze: begin ResizeLand(4096,2048); GenMaze; end;
mgPerlin: begin ResizeLand(4096,2048); GenPerlin; end;
- mgDrawn: GenDrawnMap;
+ mgDrawn: begin cFeatureSize:= 3;GenDrawnMap; end;
mgForts: MakeFortsPreview();
else
OutError('Unknown mapgen', true);
@@ -895,8 +895,16 @@
ScriptSetMapGlobals;
// strict scaling needed here since preview assumes a rectangle
- rh:= max(LAND_HEIGHT,2048);
- rw:= max(LAND_WIDTH,4096);
+ if (cMapGen <> mgDrawn) then
+ begin
+ rh:= max(LAND_HEIGHT, 2048);
+ rw:= max(LAND_WIDTH, 4096);
+ end
+ else
+ begin
+ rh:= LAND_HEIGHT;
+ rw:= LAND_WIDTH
+ end;
ox:= 0;
if rw < rh*2 then
begin
@@ -937,7 +945,7 @@
mgRandom: GenTemplated(EdgeTemplates[SelectTemplate]);
mgMaze: begin ResizeLand(4096,2048); GenMaze; end;
mgPerlin: begin ResizeLand(4096,2048); GenPerlin; end;
- mgDrawn: GenDrawnMap;
+ mgDrawn: begin cFeatureSize:= 3;GenDrawnMap; end;
mgForts: MakeFortsPreview;
else
OutError('Unknown mapgen', true);
@@ -945,9 +953,19 @@
ScriptSetMapGlobals;
+
// strict scaling needed here since preview assumes a rectangle
- rh:= max(LAND_HEIGHT, 2048);
- rw:= max(LAND_WIDTH, 4096);
+ if (cMapGen <> mgDrawn) then
+ begin
+ rh:= max(LAND_HEIGHT, 2048);
+ rw:= max(LAND_WIDTH, 4096);
+ end
+ else
+ begin
+ rh:= LAND_HEIGHT;
+ rw:= LAND_WIDTH
+ end;
+
ox:= 0;
if rw < rh*2 then
begin
@@ -986,9 +1004,9 @@
procedure chSendLandDigest(var s: shortstring);
var i: LongInt;
- landPixelDigest : LongInt;
+ landPixelDigest : LongInt;
begin
- landPixelDigest:= 1;
+ landPixelDigest:= 1;
for i:= 0 to LAND_HEIGHT-1 do
landPixelDigest:= Adler32Update(landPixelDigest, @Land[i,0], LAND_WIDTH*2);
s:= 'M' + IntToStr(syncedPixelDigest)+'|'+IntToStr(landPixelDigest);
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uLandPainted.pas
--- a/hedgewars/uLandPainted.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uLandPainted.pas Mon Jan 14 12:35:32 2019 -0500
@@ -58,6 +58,7 @@
rec:= prec^;
rec.X:= SDLNet_Read16(@rec.X);
rec.Y:= SDLNet_Read16(@rec.Y);
+
if rec.X < -318 then rec.X:= -318;
if rec.X > 4096+318 then rec.X:= 4096+318;
if rec.Y < -318 then rec.Y:= -318;
@@ -81,7 +82,7 @@
var pe: PPointEntry;
prevPoint: PointRec;
radius: LongInt;
- color: Longword;
+ color, Xoffset, Yoffset: Longword;
lineNumber, linePoints: Longword;
begin
// shutup compiler
@@ -89,6 +90,8 @@
prevPoint.Y:= 0;
radius:= 0;
linePoints:= 0;
+ Xoffset:= (LAND_WIDTH-(4096*max(min(cFeatureSize,24),3) div 12)) div 2;
+ Yoffset:= (LAND_HEIGHT-(2048*max(min(cFeatureSize,24),3) div 12));
pe:= pointsListHead;
while (pe <> nil) and (pe^.point.flags and $80 = 0) do
@@ -101,6 +104,8 @@
while(pe <> nil) do
begin
+ pe^.point.X:= (LongInt(pe^.point.X) * max(min(cFeatureSize,24),3)) div 12 + Xoffset;
+ pe^.point.Y:= (LongInt(pe^.point.Y) * max(min(cFeatureSize,24),3)) div 12 + Yoffset;
if (pe^.point.flags and $80 <> 0) then
begin
if (lineNumber > 0) and (linePoints = 0) and cAdvancedMapGenMode then
@@ -113,9 +118,10 @@
else
color:= lfBasic;
radius:= (pe^.point.flags and $3F) * 5 + 3;
+ radius:= (radius * max(min(cFeatureSize,24),3)) div 12;
linePoints:= FillRoundInLand(pe^.point.X, pe^.point.Y, radius, color);
end
- else
+ else
begin
inc(linePoints, DrawThickLine(prevPoint.X, prevPoint.Y, pe^.point.X, pe^.point.Y, radius, color));
end;
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uScript.pas
--- a/hedgewars/uScript.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uScript.pas Mon Jan 14 12:35:32 2019 -0500
@@ -686,11 +686,20 @@
function lc_spawnfakehealthcrate(L: Plua_State) : LongInt; Cdecl;
var gear: PGear;
-begin
- if CheckLuaParamCount(L, 4,'SpawnFakeHealthCrate', 'x, y, explode, poison') then
+ explode, poison: boolean;
+ n: LongInt;
+begin
+ if CheckAndFetchParamCountRange(L, 2, 4, 'SpawnFakeHealthCrate', 'x, y [, explode [, poison]]', n) then
begin
+ explode:= false;
+ poison:= false;
+ if (n >= 3) and (not lua_isnil(L, 3)) then
+ explode:= lua_toboolean(L, 3);
+ if (n = 4) and (not lua_isnil(L, 4)) then
+ poison:= lua_toboolean(L, 4);
+
gear := SpawnFakeCrateAt(Trunc(lua_tonumber(L, 1)), Trunc(lua_tonumber(L, 2)),
- HealthCrate, lua_toboolean(L, 3), lua_toboolean(L, 4));
+ HealthCrate, explode, poison);
if gear <> nil then
lua_pushnumber(L, gear^.uid)
else lua_pushnil(L)
@@ -702,11 +711,20 @@
function lc_spawnfakeammocrate(L: PLua_State): LongInt; Cdecl;
var gear: PGear;
-begin
- if CheckLuaParamCount(L, 4,'SpawnFakeAmmoCrate', 'x, y, explode, poison') then
+ explode, poison: boolean;
+ n: LongInt;
+begin
+ if CheckAndFetchParamCountRange(L, 2, 4, 'SpawnFakeAmmoCrate', 'x, y [, explode [, poison]]', n) then
begin
+ explode:= false;
+ poison:= false;
+ if (n >= 3) and (not lua_isnil(L, 3)) then
+ explode:= lua_toboolean(L, 3);
+ if (n = 4) and (not lua_isnil(L, 4)) then
+ poison:= lua_toboolean(L, 4);
+
gear := SpawnFakeCrateAt(Trunc(lua_tonumber(L, 1)), Trunc(lua_tonumber(L, 2)),
- AmmoCrate, lua_toboolean(L, 3), lua_toboolean(L, 4));
+ AmmoCrate, explode, poison);
if gear <> nil then
lua_pushnumber(L, gear^.uid)
else lua_pushnil(L)
@@ -718,11 +736,20 @@
function lc_spawnfakeutilitycrate(L: PLua_State): LongInt; Cdecl;
var gear: PGear;
-begin
- if CheckLuaParamCount(L, 4,'SpawnFakeUtilityCrate', 'x, y, explode, poison') then
+ explode, poison: boolean;
+ n: LongInt;
+begin
+ if CheckAndFetchParamCountRange(L, 2, 4, 'SpawnFakeUtilityCrate', 'x, y [, explode [, poison]]', n) then
begin
+ explode:= false;
+ poison:= false;
+ if (n >= 3) and (not lua_isnil(L, 3)) then
+ explode:= lua_toboolean(L, 3);
+ if (n = 4) and (not lua_isnil(L, 4)) then
+ poison:= lua_toboolean(L, 4);
+
gear := SpawnFakeCrateAt(Trunc(lua_tonumber(L, 1)), Trunc(lua_tonumber(L, 2)),
- UtilityCrate, lua_toboolean(L, 3), lua_toboolean(L, 4));
+ UtilityCrate, explode, poison);
if gear <> nil then
lua_pushnumber(L, gear^.uid)
else lua_pushnil(L)
@@ -2263,7 +2290,9 @@
else
begin
OutError('Lua error: AddTeam: Invalid ''color'' argument, must be between '+IntToStr(-cClanColors)+' and 0xffffffff!', true);
- lc_addteam:= 0;
+ lua_pushnil(L);
+ lua_pushnil(L);
+ lc_addteam:= 2;
exit;
end;
ParseCommand('addteam x ' + colorStr + ' ' + lua_tostring(L, 1), true, true);
@@ -2271,13 +2300,70 @@
ParseCommand('fort ' + lua_tostring(L, 4), true, true);
ParseCommand('voicepack ' + lua_tostring(L, 5), true, true);
if (np = 6) then ParseCommand('flag ' + lua_tostring(L, 6), true, true);
- CurrentTeam^.Binds:= DefaultBinds
- // fails on x64
- //lua_pushnumber(L, LongInt(CurrentTeam));
+ // If there's a mission team, copy it's control scheme.
+ // So in singleplayer missions, all teams use the player team's controls.
+ if MissionTeam <> nil then
+ CurrentTeam^.Binds:= MissionTeam^.Binds
+ // Default keys otherwise
+ else
+ CurrentTeam^.Binds:= DefaultBinds;
+ // push team name and index
+ lua_pushstring(L, str2pchar(CurrentTeam^.TeamName));
+ lua_pushnumber(L, TeamsCount - 1);
+ end
+ else
+ begin
+ lua_pushnil(L);
+ lua_pushnil(L);
end;
- //else
- //lua_pushnil(L)
- lc_addteam:= 0;//1;
+ lc_addteam:= 2;
+end;
+
+function lc_addmissionteam(L : Plua_State) : LongInt; Cdecl;
+var colorArg: Int64;
+ colorStr: shortstring;
+begin
+ if CheckLuaParamCount(L, 1, 'AddMissionTeam', 'color') then
+ begin
+ if(MissionTeam = nil) then
+ begin
+ OutError('Lua error: AddMissionTeam: Could not add team. Note: This function only works in singleplayer missions!', true);
+ lc_addmissionteam:= 0;
+ exit;
+ end;
+
+ colorArg:= Trunc(lua_tonumber(L, 1));
+ if (colorArg < 0) and (abs(colorArg) <= cClanColors) then
+ // Pick clan color from settings (recommended)
+ colorStr:= IntToStr(ClanColorArray[Pred(abs(colorArg))])
+ else if (colorArg >= 0) and (colorArg <= $ffffffff) then
+ // Specify color directly
+ colorStr:= IntToStr(colorArg)
+ else
+ begin
+ OutError('Lua error: AddMissionTeam: Invalid ''color'' argument, must be between '+IntToStr(-cClanColors)+' and 0xffffffff!', true);
+ lua_pushnil(L);
+ lua_pushnil(L);
+ lc_addmissionteam:= 2;
+ exit;
+ end;
+
+ ParseCommand('addteam x ' + colorStr + ' ' + MissionTeam^.TeamName, true, true);
+ ParseCommand('grave ' + MissionTeam^.GraveName, true, true);
+ ParseCommand('fort ' + MissionTeam^.FortName, true, true);
+ ParseCommand('voicepack ' + MissionTeam^.Voicepack^.name, true, true);
+ ParseCommand('flag ' + MissionTeam^.Flag, true, true);
+ CurrentTeam^.Binds:= MissionTeam^.Binds;
+ // push real team name and team index
+ lua_pushstring(L, str2pchar(CurrentTeam^.TeamName));
+ lua_pushnumber(L, TeamsCount - 1);
+ end
+ else
+ begin
+ lua_pushnil(L);
+ lua_pushnil(L);
+ end;
+ lc_addmissionteam:= 2;
end;
function lc_setteamlabel(L : Plua_State) : LongInt; Cdecl;
@@ -2472,13 +2558,13 @@
function lc_addhog(L : Plua_State) : LongInt; Cdecl;
-var temp: ShortString;
+var hatName: ShortString;
begin
if CheckLuaParamCount(L, 4, 'AddHog', 'hogname, botlevel, health, hat') then
begin
- temp:= lua_tostring(L, 4);
+ hatName:= lua_tostring(L, 4);
ParseCommand('addhh ' + lua_tostring(L, 2) + ' ' + lua_tostring(L, 3) + ' ' + lua_tostring(L, 1), true, true);
- ParseCommand('hat ' + temp, true, true);
+ ParseCommand('hat ' + hatName, true, true);
lua_pushnumber(L, CurrentHedgehog^.Gear^.uid);
end
else
@@ -2486,6 +2572,31 @@
lc_addhog:= 1;
end;
+function lc_addmissionhog(L : Plua_State) : LongInt; Cdecl;
+var hatName: ShortString;
+begin
+ if CheckLuaParamCount(L, 1, 'AddMissionHog', 'health') then
+ begin
+ if(MissionTeam = nil) then
+ begin
+ OutError('Lua error: AddMissionHog: Could not add hog. Mission team is not set!', true);
+ lua_pushnil(L);
+ lc_addmissionhog:= 1;
+ exit;
+ end;
+ with MissionTeam^.Hedgehogs[CurrentTeam^.HedgehogsNumber] do
+ begin
+ hatName:= Hat;
+ ParseCommand('addhh ' + IntToStr(BotLevel) + ' ' + lua_tostring(L, 1) + ' ' + Name, true, true);
+ ParseCommand('hat ' + hatName, true, true);
+ end;
+ lua_pushnumber(L, CurrentHedgehog^.Gear^.uid);
+ end
+ else
+ lua_pushnil(L);
+ lc_addmissionhog:= 1;
+end;
+
function lc_hogturnleft(L : Plua_State) : LongInt; Cdecl;
var gear: PGear;
begin
@@ -2980,6 +3091,21 @@
lc_getcampaignvar := 1;
end;
+function lc_savemissionvar(L : Plua_State): LongInt; Cdecl;
+begin
+ if CheckLuaParamCount(L, 2, 'SaveMissionVar', 'varname, value') then
+ SendIPC('v!' + lua_tostring(L, 1) + ' ' + lua_tostring(L, 2) + #0);
+ lc_savemissionvar := 0;
+end;
+
+function lc_getmissionvar(L : Plua_State): LongInt; Cdecl;
+begin
+ if CheckLuaParamCount(L, 1, 'GetMissionVar', 'varname') then
+ SendIPCAndWaitReply('v?' + lua_tostring(L, 1) + #0);
+ lua_pushstring(L, str2pchar(MissionVariable));
+ lc_getmissionvar := 1;
+end;
+
function lc_hidehog(L: Plua_State): LongInt; Cdecl;
var gear: PGear;
begin
@@ -3276,6 +3402,22 @@
lc_setreadytimeleft:= 0;
end;
+function lc_setturntimepaused(L : Plua_State) : LongInt; Cdecl;
+begin
+ if CheckLuaParamCount(L, 1, 'SetTurnTimePaused', 'isPaused') then
+ LuaClockPaused:= lua_toboolean(L, 1);
+ lc_setturntimepaused:= 0;
+end;
+
+function lc_getturntimepaused(L : Plua_State) : LongInt; Cdecl;
+begin
+ if CheckLuaParamCount(L, 0, 'GetTurnTimePaused', '') then
+ lua_pushboolean(L, LuaClockPaused)
+ else
+ lua_pushnil(L);
+ lc_getturntimepaused:= 1;
+end;
+
function lc_startghostpoints(L : Plua_State) : LongInt; Cdecl;
begin
if CheckLuaParamCount(L, 1, 'StartGhostPoints', 'count') then
@@ -3478,6 +3620,7 @@
ScriptSetInteger('MaxCaseDrops', cMaxCaseDrops);
ScriptSetInteger('HealthCaseProb', cHealthCaseProb);
ScriptSetInteger('HealthCaseAmount', cHealthCaseAmount);
+ScriptSetInteger('InitHealth', cInitHealth);
ScriptSetInteger('DamagePercent', cDamagePercent);
ScriptSetInteger('RopePercent', cRopePercent);
ScriptSetInteger('MinesNum', cLandMines);
@@ -3511,6 +3654,7 @@
cMaxCaseDrops := ScriptGetInteger('MaxCaseDrops');
cHealthCaseProb := ScriptGetInteger('HealthCaseProb');
cHealthCaseAmount:= ScriptGetInteger('HealthCaseAmount');
+cInitHealth := ScriptGetInteger('InitHealth');
cDamagePercent := ScriptGetInteger('DamagePercent');
cRopePercent := ScriptGetInteger('RopePercent');
cLandMines := ScriptGetInteger('MinesNum');
@@ -4165,6 +4309,8 @@
lua_register(luaState, _P'IsHogHidden', @lc_ishoghidden);
lua_register(luaState, _P'SaveCampaignVar', @lc_savecampaignvar);
lua_register(luaState, _P'GetCampaignVar', @lc_getcampaignvar);
+lua_register(luaState, _P'SaveMissionVar', @lc_savemissionvar);
+lua_register(luaState, _P'GetMissionVar', @lc_getmissionvar);
lua_register(luaState, _P'band', @lc_band);
lua_register(luaState, _P'bor', @lc_bor);
lua_register(luaState, _P'bnot', @lc_bnot);
@@ -4229,8 +4375,10 @@
lua_register(luaState, _P'GetTeamIndex', @lc_getteamindex);
lua_register(luaState, _P'GetTeamClan', @lc_getteamclan);
lua_register(luaState, _P'AddTeam', @lc_addteam);
+lua_register(luaState, _P'AddMissionTeam', @lc_addmissionteam);
lua_register(luaState, _P'SetTeamLabel', @lc_setteamlabel);
lua_register(luaState, _P'AddHog', @lc_addhog);
+lua_register(luaState, _P'AddMissionHog', @lc_addmissionhog);
lua_register(luaState, _P'AddAmmo', @lc_addammo);
lua_register(luaState, _P'GetAmmoCount', @lc_getammocount);
lua_register(luaState, _P'HealHog', @lc_healhog);
@@ -4310,6 +4458,8 @@
lua_register(luaState, _P'Explode', @lc_explode);
lua_register(luaState, _P'SetTurnTimeLeft', @lc_setturntimeleft);
lua_register(luaState, _P'SetReadyTimeLeft', @lc_setreadytimeleft);
+lua_register(luaState, _P'SetTurnTimePaused', @lc_setturntimepaused);
+lua_register(luaState, _P'GetTurnTimePaused', @lc_getturntimepaused);
// drawn map functions
lua_register(luaState, _P'AddPoint', @lc_addPoint);
lua_register(luaState, _P'FlushPoints', @lc_flushPoints);
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uSound.pas
--- a/hedgewars/uSound.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uSound.pas Mon Jan 14 12:35:32 2019 -0500
@@ -490,11 +490,11 @@
GetFallbackV := sndUhOh
else if (snd in [sndDrat, sndBugger]) then
GetFallbackV := sndStupid
- else if (snd in [sndGonnaGetYou, sndCutItOut, sndLeaveMeAlone]) then
+ else if (snd in [sndGonnaGetYou, sndIllGetYou, sndJustYouWait, sndCutItOut, sndLeaveMeAlone]) then
GetFallbackV := sndRegret
else if (snd in [sndOhDear, sndSoLong]) then
GetFallbackV := sndByeBye
- else if (snd = sndWhatThe) then
+ else if (snd in [sndWhatThe, sndUhOh]) then
GetFallbackV := sndNooo
else if (snd = sndRunAway) then
GetFallbackV := sndOops
@@ -502,14 +502,19 @@
GetFallbackV := sndReinforce
else if (snd in [sndAmazing, sndBrilliant, sndExcellent]) then
GetFallbackV := sndEnemyDown
- // Hmm is for enemy turn start
- else if snd = sndHmm then
- // these are not ideal fallbacks, but those were the voices which were used in older versions
- // for enemy turn start
- if random(2) = 0 then
- GetFallbackV := sndIllGetYou
- else
- GetFallbackV := sndJustYouWait
+ else if (snd = sndPoisonCough) then
+ GetFallbackV := sndPoisonMoan
+ else if (snd = sndPoisonMoan) then
+ GetFallbackV := sndPoisonCough
+ else if (snd = sndFlawless) then
+ GetFallbackV := sndVictory
+ else if (snd = sndSameTeam) then
+ GetFallbackV := sndTraitor
+ else if (snd = sndMelon) then
+ GetFallbackV := sndCover
+ // sndHmm is used for enemy turn start, so sndHello is an "okay" replacement
+ else if (snd = sndHmm) then
+ GetFallbackV := sndHello
else
GetFallbackV := sndNone;
end;
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uStats.pas
--- a/hedgewars/uStats.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uStats.pas Mon Jan 14 12:35:32 2019 -0500
@@ -67,6 +67,7 @@
HitTargets : LongWord = 0; // Target (gtTarget) hits per turn
AmmoUsedCount : Longword = 0;
AmmoDamagingUsed : boolean = false;
+ FirstBlood : boolean = false;
LeaveMeAlone : boolean = false;
SkippedTurns: LongWord = 0;
isTurnSkipped: boolean = false;
@@ -254,8 +255,11 @@
killsCheck:= 0;
// First blood (first damage, poison or kill)
- if ((DamageTotal > 0) or (KillsTotal > 0) or (PoisonTotal > 0)) and ((CurrentHedgehog^.stats.DamageGiven = DamageTotal) and (CurrentHedgehog^.stats.StepKills = KillsTotal) and (PoisonTotal = PoisonTurn + PoisonClan)) then
- AddVoice(sndFirstBlood, CurrentTeam^.voicepack)
+ if (not FirstBlood) and (ClansCount > 1) and ((DamageTotal > 0) or (KillsTotal > 0) or (PoisonTotal > 0)) and ((CurrentHedgehog^.stats.DamageGiven = DamageTotal) and (CurrentHedgehog^.stats.StepKills = KillsTotal) and (PoisonTotal = PoisonTurn + PoisonClan)) then
+ begin
+ FirstBlood:= true;
+ AddVoice(sndFirstBlood, CurrentTeam^.voicepack);
+ end
// Hog hurts, poisons or kills itself (except sacrifice)
else if (CurrentHedgehog^.stats.Sacrificed = false) and ((CurrentHedgehog^.stats.StepDamageRecv > 0) or (CurrentHedgehog^.stats.StepPoisoned) or (CurrentHedgehog^.stats.StepDied)) then
@@ -549,13 +553,17 @@
// now to console
if winnersClan <> nil then
begin
+ ScriptCall('onGameResult', winnersClan^.ClanIndex);
WriteLnToConsole('WINNERS');
WriteLnToConsole(inttostr(winnersClan^.TeamsNumber));
for t:= 0 to winnersClan^.TeamsNumber - 1 do
WriteLnToConsole(winnersClan^.Teams[t]^.TeamName);
end
else
+ begin
+ ScriptCall('onGameResult', -1);
WriteLnToConsole('DRAW');
+ end;
ScriptCall('onAchievementsDeclaration');
end;
@@ -598,6 +606,7 @@
HitTargets := 0;
AmmoUsedCount := 0;
AmmoDamagingUsed := false;
+ FirstBlood:= false;
LeaveMeAlone := false;
SkippedTurns:= 0;
isTurnSkipped:= false;
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uTeams.pas
--- a/hedgewars/uTeams.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uTeams.pas Mon Jan 14 12:35:32 2019 -0500
@@ -29,6 +29,7 @@
procedure freeModule;
function AddTeam(TeamColor: Longword): PTeam;
+function SetMissionTeam(): PTeam;
procedure SwitchHedgehog;
procedure AfterSwitchHedgehog;
procedure InitTeams;
@@ -476,6 +477,20 @@
end;
end;
+function SetMissionTeam(): PTeam;
+var team: PTeam;
+begin
+New(team);
+if checkFails(team <> nil, 'AddTeam: team = nil', true) then exit(nil);
+FillChar(team^, sizeof(TTeam), 0);
+team^.HedgehogsNumber:= 0;
+team^.Binds:= DefaultBinds;
+
+CurrentTeam:= team;
+MissionTeam:= team;
+SetMissionTeam:= team;
+end;
+
function AddTeam(TeamColor: Longword): PTeam;
var team: PTeam;
c: LongInt;
@@ -560,6 +575,7 @@
// Some initial King buffs
if (GameFlags and gfKing) <> 0 then
begin
+ hasKing:= true;
Hedgehogs[0].King:= true;
Hedgehogs[0].Hat:= 'crown';
Hedgehogs[0].Effects[hePoisoned] := 0;
@@ -678,6 +694,30 @@
end
end;
+procedure chAddMissionHH(var id: shortstring);
+var s: shortstring;
+ Health: LongInt;
+begin
+s:= '';
+if (not isDeveloperMode) then
+ exit;
+if checkFails((CurrentTeam <> nil), 'Can''t add hedgehogs yet, add a team first!', true) then exit;
+with CurrentTeam^ do
+ begin
+ if checkFails(HedgehogsNumber<=cMaxHHIndex, 'Can''t add hedgehog to "' + TeamName + '"! (already ' + intToStr(HedgehogsNumber) + ' hogs)', true) then exit;
+ SplitBySpace(id, s);
+ CurrentHedgehog:= @Hedgehogs[HedgehogsNumber];
+ CurrentHedgehog^.BotLevel:= StrToInt(id);
+ CurrentHedgehog^.Team:= CurrentTeam;
+ SplitBySpace(s, id);
+ Health:= StrToInt(s);
+ if checkFails((Health > 0) and (Health <= cMaxHogHealth), 'Invalid hedgehog health (must be between 1 and '+IntToStr(cMaxHogHealth)+')', true) then exit;
+ CurrentHedgehog^.Name:= id;
+ CurrentHedgehog^.InitialHealth:= Health;
+ inc(HedgehogsNumber)
+ end
+end;
+
procedure chAddHH(var id: shortstring);
var s: shortstring;
Gear: PGear;
@@ -728,6 +768,63 @@
loadBinds('bind', s);
end;
+// Make sure the team name of chTeam is unique.
+// If it isn't, the name is changed to be unique.
+procedure makeTeamNameUnique(chTeam: PTeam);
+var tail: shortstring;
+ t, numLen, numTail: LongInt;
+// valOK: Word; -- see pas2c-related FIXME below
+ nameDupeCheck: boolean;
+ chChar: char;
+begin
+ nameDupeCheck:= false;
+ while(nameDupeCheck = false) do
+ begin
+ nameDupeCheck:= true;
+ for t:=0 to TeamsCount - 1 do
+ begin
+ // Name collision?
+ if (chTeam <> teamsArray[t]) and (TeamsArray[t]^.TeamName = chTeam^.TeamName) then
+ begin
+ // Change the name by appending a sequence number, starting from 2
+ numLen:= 0;
+ chChar:= chTeam^.TeamName[Length(chTeam^.TeamName) - numLen];
+ // Parse number at end of team name (if any)
+ while (chChar >= '0') and (chChar <= '9') and (numLen < Length(chTeam^.TeamName)) do
+ begin
+ inc(numLen);
+ chChar:= chTeam^.TeamName[Length(chTeam^.TeamName) - numLen];
+ end;
+
+ if numLen > 0 then
+ // Number found: Increment it by 1
+ begin
+ tail:= Copy(chTeam^.TeamName, Length(chTeam^.TeamName) - numLen + 1, numLen);
+(* FIXME - pas2c missing 3rd param for val
+ valOK:= 1;
+ Val(tail, numTail, valOK);
+ Inc(numTail);
+ if valOK = 0 then
+ tail:= IntToStr(numTail)
+ else
+ // This should not happen
+ tail:= shortstring('X');
+*)
+ Val(tail, numTail);
+ Inc(numTail);
+ tail:= IntToStr(numTail);
+ chTeam^.TeamName:= Copy(chTeam^.TeamName, 0, Length(chTeam^.TeamName) - numLen) + tail;
+ end
+ else
+ // No number at team end: Just append a '2'
+ chTeam^.TeamName:= chTeam^.TeamName + ' 2';
+ nameDupeCheck:= false;
+ break;
+ end;
+ end;
+ end;
+end;
+
procedure chAddTeam(var s: shortstring);
var Color: Longword;
ts, cs: shortstring;
@@ -743,10 +840,12 @@
// color is always little endian so the mask must be constant also in big endian archs
Color:= Color or $FF000000;
AddTeam(Color);
-
+
if CurrentTeam <> nil then
begin
CurrentTeam^.TeamName:= ts;
+ makeTeamNameUnique(CurrentTeam);
+
CurrentTeam^.PlayerHash:= s;
loadTeamBinds(ts);
@@ -758,6 +857,28 @@
end
end;
+procedure chSetMissionTeam(var s: shortstring);
+var ts, cs: shortstring;
+begin
+cs:= '';
+ts:= '';
+if isDeveloperMode then
+ begin
+ SplitBySpace(s, cs);
+ SplitBySpace(cs, ts);
+
+ SetMissionTeam();
+
+ if CurrentTeam <> nil then
+ begin
+ CurrentTeam^.TeamName:= ts;
+ CurrentTeam^.PlayerHash:= s;
+ loadTeamBinds(ts);
+ CurrentTeam^.voicepack:= AskForVoicepack('Default')
+ end
+ end
+end;
+
procedure chSetHHCoords(var x: shortstring);
var y: shortstring;
t: Longint;
@@ -803,7 +924,8 @@
begin
if (not hasGone) and isGoneFlagPendingToBeSet then
begin
- AddChatString(#7 + Format('* '+shortstring(trmsg[sidTeamGone]), TeamName));
+ if (not TeamsGameOver) then
+ AddChatString(#7 + Format('* '+shortstring(trmsg[sidTeamGone]), TeamName));
if not CurrentTeam^.ExtDriven then SendIPC(_S'f' + s);
hasGone:= true;
skippedTurns:= 0;
@@ -940,7 +1062,9 @@
procedure initModule;
begin
RegisterVariable('addhh', @chAddHH, false);
+RegisterVariable('addmisshh', @chAddMissionHH, false);
RegisterVariable('addteam', @chAddTeam, false);
+RegisterVariable('setmissteam', @chSetMissionTeam, false);
RegisterVariable('hhcoords', @chSetHHCoords, false);
RegisterVariable('bind', @chBind, true );
RegisterVariable('teamgone', @chTeamGone, true );
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uTouch.pas
--- a/hedgewars/uTouch.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uTouch.pas Mon Jan 14 12:35:32 2019 -0500
@@ -22,7 +22,7 @@
interface
-uses SysUtils, uUtils, uConsole, uVariables, SDLh, uFloat, uConsts, uCommands, GLUnit, uTypes, uCaptions, uAmmos, uWorld;
+uses SysUtils, uUtils, uConsole, uVariables, SDLh, uFloat, uConsts, uCommands, GLUnit, uTypes, uCaptions, uWorld, uGearsHedgehog;
procedure initModule;
@@ -89,7 +89,7 @@
procedure onTouchDown(x, y: Single; pointerId: TSDL_FingerId);
var
finger: PTouch_Data;
- xr, yr: LongWord;
+ xr, yr, tmp: LongWord;
begin
xr:= round(x * cScreenWidth);
yr:= round(y * cScreenHeight);
@@ -159,10 +159,35 @@
if(CurrentHedgehog <> nil) then
begin
if Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_Timerable <> 0 then
- ParseTeamCommand('/timer ' + inttostr((GetCurAmmoEntry(CurrentHedgeHog^)^.Timer div 1000) mod 5 + 1));
+ begin
+ tmp:= HHGetTimerMsg(CurrentHedgehog^.Gear);
+ if tmp <> MSGPARAM_INVALID then
+ ParseTeamCommand('/timer ' + inttostr(tmp mod 5 + 1));
+ end;
end;
exit;
end;
+
+if isOnWidget(utilityWidget2, finger^) then
+ begin
+ finger^.pressedWidget:= @utilityWidget2;
+ moveCursor:= false;
+ if(CurrentHedgehog <> nil) then
+ begin
+ if Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_SetBounce <> 0 then
+ begin
+ tmp := HHGetBouncinessMsg(CurrentHedgehog^.Gear);
+ if tmp <> MSGPARAM_INVALID then
+ begin
+ ParseTeamCommand('+precise');
+ ParseTeamCommand('timer ' + inttostr(tmp mod 5 + 1));
+ bounceButtonPressed:= true;
+ end;
+ end;
+ end;
+ exit;
+ end;
+
dec(buttonsDown);//no buttonsDown, undo the inc() above
if buttonsDown = 0 then
begin
@@ -496,6 +521,12 @@
aimingDown:= false;
end;
end;
+
+if bounceButtonPressed then
+ begin
+ ParseTeamCommand('-precise');
+ bounceButtonPressed:= false;
+ end;
end;
function findFinger(id: TSDL_FingerId): PTouch_Data;
@@ -627,6 +658,7 @@
begin
buttonsDown:= 0;
pointerCount:= 0;
+ bounceButtonPressed:= false;
setLength(fingers, 4);
for index := 0 to (Length(fingers)-1) do
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uTypes.pas
--- a/hedgewars/uTypes.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uTypes.pas Mon Jan 14 12:35:32 2019 -0500
@@ -61,7 +61,7 @@
{$IFDEF USE_TOUCH_INTERFACE}
sprFireButton, sprArrowUp, sprArrowDown, sprArrowLeft, sprArrowRight,
sprJumpWidget, sprAMWidget, sprPauseButton, sprTimerButton, sprTargetButton,
- sprSwitchButton,
+ sprSwitchButton, sprBounceButton,
{$ENDIF}
sprFlake, sprHandRope, sprHandBazooka, sprHandShotgun,
sprHandDEagle, sprHandAirAttack, sprHandBaseball, sprPHammer,
@@ -168,6 +168,8 @@
amPiano, amGasBomb, amSineGun, amFlamethrower, amSMine, amHammer, // 48
amResurrector, amDrillStrike, amSnowball, amTardis, amLandGun, // 53
amIceGun, amKnife, amRubber, amAirMine, amCreeper, amMinigun); // 59
+ // NOTE: If we ever reach 126 ammo types, make sure to skip ammo type number 126 because it's
+ // reserved as synonym for amNothing. See also chSetWeapon.
// Different kind of crates that e.g. hedgehogs can pick up
TCrateType = (HealthCrate, AmmoCrate, UtilityCrate);
@@ -444,6 +446,7 @@
voicepack: PVoicepack;
PlayerHash: shortstring; // md5 hash of player name. For temporary enabling of hats as thank you. Hashed for privacy of players
stats: TTeamStats;
+ hasKing: boolean; // true if team has a living king
hasGone: boolean;
skippedTurns: Longword;
isGoneFlagPendingToBeSet, isGoneFlagPendingToBeUnset: boolean;
@@ -511,7 +514,8 @@
sidCmdHeaderTaunts, sidCmdSpeech, sidCmdThink, sidCmdYell,
sidCmdSpeechNumberHint, sidCmdHsa, sidCmdHta, sidCmdHya,
sidCmdHurrah, sidCmdIlovelotsoflemonade, sidCmdJuggle,
- sidCmdRollup, sidCmdShrug, sidCmdWave, sidCmdUnknown);
+ sidCmdRollup, sidCmdShrug, sidCmdWave, sidCmdUnknown,
+ sidCmdHelpRoom, sidCmdHelpRoomFail);
// Events that are important for the course of the game or at least interesting for other reasons
TEventId = (eidDied, eidDrowned, eidRoundStart, eidRoundWin, eidRoundDraw,
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uVariables.pas
--- a/hedgewars/uVariables.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uVariables.pas Mon Jan 14 12:35:32 2019 -0500
@@ -88,6 +88,7 @@
CheckSum : LongWord;
CampaignVariable: shortstring;
+ MissionVariable : shortstring;
GameTicks : LongWord;
OuchTauntTimer : LongWord; // Timer which blocks sndOuch from being played too often and fast
GameState : TGameState;
@@ -104,6 +105,9 @@
IsGetAwayTime : boolean;
GameOver : boolean;
cSuddenDTurns : LongInt;
+ LastSuddenDWarn : LongInt; // last round in which the last SD warning appeared. -2 = no warning so far
+ cInitHealth : LongInt; // initial hedgehog health (from game scheme. note the real hog health is sent directly
+ // from frontend, this is only used to inform Lua scripts)
cDamagePercent : LongInt;
cMineDudPercent : LongWord;
cTemplateFilter : LongInt;
@@ -182,6 +186,7 @@
CrosshairX : LongInt;
CrosshairY : LongInt;
+ CrosshairGear : PGear;
CursorMovementX : LongInt;
CursorMovementY : LongInt;
cWaveHeight : LongInt;
@@ -261,6 +266,9 @@
LuaEndTurnRequested: boolean;
LuaNoEndTurnTaunts: boolean;
+ // whether Lua requested to pause the clock
+ LuaClockPaused: boolean;
+
MaskedSounds : array[TSound] of boolean;
LastVoice : TVoice;
@@ -276,10 +284,12 @@
//Buttons
{$IFDEF USE_TOUCH_INTERFACE}
buttonScale: GLFloat;
+ bounceButtonPressed: boolean;
arrowUp, arrowDown, arrowLeft, arrowRight : TOnScreenWidget;
firebutton, jumpWidget, AMWidget : TOnScreenWidget;
pauseButton, utilityWidget : TOnScreenWidget;
+ utilityWidget2 : TOnScreenWidget;
{$ENDIF}
@@ -480,6 +490,8 @@
Width: 128; Height: 128; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHigh; getDimensions: false; getImageDimensions: true), // sprTargetButton
(FileName: 'switchbutton'; Path: ptButtons; AltPath: ptNone; Texture: nil; Surface: nil;
Width: 128; Height: 128; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHigh; getDimensions: false; getImageDimensions: true), // sprSwitchButton
+ (FileName: 'bouncebutton'; Path: ptButtons; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 128; Height: 128; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHigh; getDimensions: false; getImageDimensions: true), // sprBounceButton
{$ENDIF}
(FileName: 'Flake'; Path:ptCurrTheme; AltPath: ptNone; Texture: nil; Surface: nil;
Width: 64; Height: 64; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHighest; getDimensions: false; getImageDimensions: true),// sprFlake
@@ -946,6 +958,7 @@
Ammo: (Propz: ammoprop_Power or
ammoprop_NeedTarget or
ammoprop_NoTargetAfter or
+ ammoprop_NoWrapTarget or
ammoprop_DontHold or
ammoprop_NeedUpDown;
Count: 2;
@@ -1277,7 +1290,7 @@
Timer: 0;
Pos: 0;
AmmoType: amAirAttack;
- AttackVoice: sndIncoming;
+ AttackVoice: sndNone; // handled in doStepAirAttack
Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
@@ -1305,7 +1318,7 @@
Timer: 0;
Pos: 0;
AmmoType: amMineStrike;
- AttackVoice: sndIncoming;
+ AttackVoice: sndNone; // handled in doStepAirAttack
Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
@@ -1602,7 +1615,7 @@
Timer: 0;
Pos: 0;
AmmoType: amNapalm;
- AttackVoice: sndIncoming;
+ AttackVoice: sndNone; // handled in doStepAirAttack
Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
@@ -2014,7 +2027,7 @@
NumberInCase: 1;
Ammo: (Propz: ammoprop_NoCrosshair or
ammoprop_NeedTarget or
- ammoprop_NoTargetAfter or
+ // NoTargetAfter is handled manually in doStepPiano
ammoprop_AttackingPut or
ammoprop_DontHold or
ammoprop_NotBorder or
@@ -2024,7 +2037,7 @@
Timer: 0;
Pos: 0;
AmmoType: amPiano;
- AttackVoice: sndIncoming;
+ AttackVoice: sndNone; // handled in doStepPiano
Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
@@ -2209,7 +2222,7 @@
Timer: 5000;
Pos: 0;
AmmoType: amDrillStrike;
- AttackVoice: sndIncoming;
+ AttackVoice: sndNone; // handled in doStepAirAttack
Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
@@ -2483,6 +2496,7 @@
CurrentTeam: PTeam;
PreviousTeam: PTeam;
+ MissionTeam: PTeam;
CurrentHedgehog: PHedgehog;
TeamsArray: array[0..Pred(cMaxTeams)] of PTeam;
TeamsCount: Longword; // number of teams on game start
@@ -2798,12 +2812,14 @@
TurnClockActive := true;
TagTurnTimeLeft := 0;
cSuddenDTurns := 15;
+ LastSuddenDWarn := -2;
+ cInitHealth := 100;
cDamagePercent := 100;
cRopePercent := 100;
cGetAwayTime := 100;
cMineDudPercent := 0;
cTemplateFilter := 0;
- cFeatureSize := 50;
+ cFeatureSize := 12;
cMapGen := mgRandom;
cHedgehogTurnTime := 45000;
cMinesTime := 3000;
@@ -2935,6 +2951,7 @@
GearsList:= nil;
CurrentTeam:= nil;
PreviousTeam:= nil;
+ MissionTeam:= nil;
CurrentHedgehog:= nil;
FollowGear:= nil;
lastVisualGearByUID:= nil;
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uVideoRec.pas
--- a/hedgewars/uVideoRec.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uVideoRec.pas Mon Jan 14 12:35:32 2019 -0500
@@ -36,7 +36,7 @@
var flagPrerecording: boolean = false;
function BeginVideoRecording: Boolean;
-function LoadNextCameraPosition(out newRealTicks, newGameTicks: LongInt): Boolean;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
procedure EncodeFrame;
procedure StopVideoRecording;
@@ -190,7 +190,7 @@
inc(numFrames);
end;
-function LoadNextCameraPosition(out newRealTicks, newGameTicks: LongInt): Boolean;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
var frame: TFrame = (realTicks: 0; gameTicks: 0; CamX: 0; CamY: 0; zoom: 0);
begin
// we need to skip or duplicate frames to match target framerate
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uVisualGears.pas
--- a/hedgewars/uVisualGears.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uVisualGears.pas Mon Jan 14 12:35:32 2019 -0500
@@ -74,7 +74,7 @@
for i:= 0 to 6 do
begin
- t:= VisualGearLayers[i];
+ t:= VisualGearLayersStart[i];
while t <> nil do
begin
Gear:= t;
@@ -93,7 +93,7 @@
for i:= 2 to 6 do
if i <> 3 then
begin
- t:= VisualGearLayers[i];
+ t:= VisualGearLayersStart[i];
while t <> nil do
begin
Gear:= t;
@@ -154,7 +154,7 @@
case Layer of
// this layer is very distant in the background when stereo
0: begin
- Gear:= VisualGearLayers[0];
+ Gear:= VisualGearLayersStart[0];
while Gear <> nil do
begin
if Gear^.Tint <> $FFFFFFFF then Tint(Gear^.Tint);
@@ -189,10 +189,9 @@
end;
// this layer is on the land level (which is close but behind the screen plane) when stereo
1: begin
- Gear:= VisualGearLayers[1];
+ Gear:= VisualGearLayersStart[1];
while Gear <> nil do
begin
- //tinted:= false;
if Gear^.Tint <> $FFFFFFFF then
Tint(Gear^.Tint);
case Gear^.Kind of
@@ -227,14 +226,13 @@
if Gear^.Angle <> 0 then
DrawTextureRotatedF(spriteData^.Texture, Gear^.scale, 0, 0, round(Gear^.X + WorldDx + (((spriteData^.Height+8)*Gear^.Scale)/2) * (Gear^.Angle / abs(Gear^.Angle))), round(Gear^.Y + WorldDy), 19 - (Gear^.FrameTicks div Gear^.Timer div 37), 1, spriteData^.Width, spriteData^.Height, Gear^.Angle)
else
- //DrawSprite(sprite, round(Gear^.X) + WorldDx - 40, round(Gear^.Y) + WorldDy - 58, 19 - (Gear^.FrameTicks div 37))
DrawTextureF(spriteData^.Texture, Gear^.scale, round(Gear^.X + WorldDx), round(Gear^.Y + WorldDy - ((spriteData^.Height+8)*Gear^.Scale)/2), 19 - (Gear^.FrameTicks div Gear^.Timer div 37), 1, spriteData^.Width, spriteData^.Height);
end;
vgtDroplet: begin
sprite:= GetSprite(sprDroplet, sprSDDroplet);
DrawSprite(sprite, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, Gear^.Frame);
end;
- vgtBubble: DrawSprite(sprBubbles, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, Gear^.Frame);//(RealTicks div 64 + Gear^.Frame) mod 8);
+ vgtBubble: DrawSprite(sprBubbles, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, Gear^.Frame);
vgtStraightShot: begin
if Gear^.dX < 0 then
i:= -1
@@ -243,7 +241,6 @@
DrawTextureRotatedF(SpritesData[TSprite(Gear^.State)].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, i, SpritesData[TSprite(Gear^.State)].Width, SpritesData[TSprite(Gear^.State)].Height, Gear^.Angle);
end;
end;
- //if (Gear^.Tint <> $FFFFFFFF) or tinted then untint;
if (Gear^.Tint <> $FFFFFFFF) then
untint;
Gear:= Gear^.NextGear
@@ -251,21 +248,13 @@
end;
// this layer is on the screen plane (depth = 0) when stereo
3: begin
- Gear:= VisualGearLayers[3];
+ Gear:= VisualGearLayersStart[3];
while Gear <> nil do
begin
tinted:= false;
if Gear^.Tint <> $FFFFFFFF then
Tint(Gear^.Tint);
case Gear^.Kind of
-(*
- vgtFlake: begin
- sprite:= GetSprite(sprFlake, sprSDFlake);
- if speedlessFlakes then
- DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
- else
- DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
- end;*)
vgtSpeechBubble: if (Gear^.Angle <> 0) then
// ^ Before this gear renders, Angle must be set to mark it ready (e.g. coordinates properly initialized)
if (Gear^.Tex <> nil) and (((Gear^.State = 0) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Team <> CurrentTeam)) or (Gear^.State = 1)) then
@@ -318,7 +307,7 @@
end;
// this layer is outside the screen when stereo
2: begin
- Gear:= VisualGearLayers[2];
+ Gear:= VisualGearLayersStart[2];
while Gear <> nil do
begin
tinted:= false;
@@ -393,7 +382,7 @@
end;
// this layer is half-way between the screen plane (depth = 0) when in stereo, and the land
4: begin
- Gear:= VisualGearLayers[4];
+ Gear:= VisualGearLayersStart[4];
while Gear <> nil do
begin
if Gear^.Tint <> $FFFFFFFF then
@@ -419,7 +408,7 @@
end;
// this layer is on the screen plane (depth = 0) when stereo, but just behind the land
5: begin
- Gear:= VisualGearLayers[5];
+ Gear:= VisualGearLayersStart[5];
while Gear <> nil do
begin
if Gear^.Tint <> $FFFFFFFF then
@@ -445,7 +434,7 @@
end;
// this layer is on the screen plane (depth = 0) when stereo, but just in front of the land
6: begin
- Gear:= VisualGearLayers[6];
+ Gear:= VisualGearLayersStart[6];
while Gear <> nil do
begin
if Gear^.Tint <> $FFFFFFFF then
@@ -485,7 +474,7 @@
exit;
for i:= 0 to 6 do
begin
- vg:= VisualGearLayers[i];
+ vg:= VisualGearLayersStart[i];
while vg <> nil do
if vg^.Kind = vgtCloud then
begin
@@ -525,7 +514,7 @@
exit;
for i:= 0 to 6 do
begin
- vg:= VisualGearLayers[i];
+ vg:= VisualGearLayersStart[i];
while vg <> nil do
if vg^.Kind = vgtFlake then
begin
@@ -548,7 +537,10 @@
begin
VGCounter:= 0;
for i:= 0 to 6 do
- VisualGearLayers[i]:= nil;
+ begin
+ VisualGearLayersStart[i]:= nil;
+ VisualGearLayersEnd[i]:= nil;
+ end;
end;
procedure freeModule;
@@ -556,7 +548,7 @@
begin
VGCounter:= 0;
for i:= 0 to 6 do
- while VisualGearLayers[i] <> nil do DeleteVisualGear(VisualGearLayers[i]);
+ while VisualGearLayersStart[i] <> nil do DeleteVisualGear(VisualGearLayersStart[i]);
end;
end.
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uVisualGearsHandlers.pas
--- a/hedgewars/uVisualGearsHandlers.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uVisualGearsHandlers.pas Mon Jan 14 12:35:32 2019 -0500
@@ -246,7 +246,6 @@
Gear^.X:= Gear^.X + Gear^.dX * s;
Gear^.Y:= Gear^.Y + Gear^.dY * s;
-//Gear^.dY:= Gear^.dY + cGravityf;
if Gear^.FrameTicks <= Steps then
if Gear^.Frame = 0 then
@@ -317,7 +316,7 @@
begin
Gear^.X:= Gear^.X + Gear^.dX * Steps;
-Gear^.Y:= Gear^.Y + Gear^.dY * Steps;// + cGravityf * (Steps * Steps);
+Gear^.Y:= Gear^.Y + Gear^.dY * Steps;
if (Gear^.State and gstTmpFlag) = 0 then
begin
Gear^.dY:= Gear^.dY + cGravityf * Steps;
@@ -421,7 +420,6 @@
Gear^.Y:= Gear^.Y - (cDrownSpeedf + Gear^.dY) * Steps;
Gear^.dX := Gear^.dX + (cWindSpeedf * 0.3 * Steps);
-//Gear^.dY := Gear^.dY - (cDrownSpeedf * 0.995);
if Gear^.FrameTicks <= Steps then
if Gear^.Frame = 0 then
@@ -624,7 +622,6 @@
Gear^.Timer:= cSorterWorkTime;
Gear^.doStep:= @doStepTeamHealthSorterWork;
currsorter:= Gear;
-//doStepTeamHealthSorterWork(Gear, Steps)
end;
////////////////////////////////////////////////////////////////////////////////
@@ -900,6 +897,7 @@
currwindbar: PVisualGear = nil;
procedure doStepSmoothWindBarWork(Gear: PVisualGear; Steps: Longword);
+const maxWindBarWidth = 73;
begin
if currwindbar = Gear then
begin
@@ -912,6 +910,11 @@
inc(WindBarWidth)
else if WindBarWidth > Gear^.Tag then
dec(WindBarWidth);
+ // Prevent wind bar from overflowing
+ if WindBarWidth > maxWindBarWidth then
+ WindBarWidth:= maxWindBarWidth;
+ if WindBarWidth < - maxWindBarWidth then
+ WindBarWidth:= - maxWindBarWidth;
end;
if cWindspeedf > Gear^.dAngle then
begin
@@ -925,7 +928,7 @@
end;
end;
- if ((WindBarWidth = Gear^.Tag) and (cWindspeedf = Gear^.dAngle)) or (currwindbar <> Gear) then
+ if (((WindBarWidth = Gear^.Tag) or (Abs(WindBarWidth) >= maxWindBarWidth)) and (cWindspeedf = Gear^.dAngle)) or (currwindbar <> Gear) then
begin
if currwindbar = Gear then currwindbar:= nil;
DeleteVisualGear(Gear)
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uVisualGearsList.pas
--- a/hedgewars/uVisualGearsList.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uVisualGearsList.pas Mon Jan 14 12:35:32 2019 -0500
@@ -33,7 +33,8 @@
cExplFrameTicks = 110;
var VGCounter: LongWord;
- VisualGearLayers: array[0..6] of PVisualGear;
+ VisualGearLayersStart: array[0..6] of PVisualGear;
+ VisualGearLayersEnd: array[0..6] of PVisualGear;
implementation
uses uCollisions, uFloat, uVariables, uConsts, uTextures, uVisualGearsHandlers, uScript;
@@ -231,9 +232,6 @@
end;
vgtDroplet:
begin
- // old dx & dy calcs
- // dx:= 0.001 * (random(180) - 90);
- // dy:= -0.001 * (random(160) + 40);
// => min speed ~ 0.098, max speed ~ 0.218, speed range ~ 0.120
// => min angle(4096) ~ 129, max angle ~ 1919, angle range ~ 1790
dx:= 0.001 * (98 + random(121)); // speed
@@ -279,7 +277,6 @@
Timer:= 1500;
dY:= -0.08;
dX:= 0;
- //gear^.Z:= 2002;
end;
vgtSmokeTrace,
vgtEvilTrace:
@@ -287,7 +284,6 @@
gear^.X:= gear^.X - 16;
gear^.Y:= gear^.Y - 16;
gear^.State:= 8;
- //gear^.Z:= cSmokeZ
end;
vgtBigExplosion:
begin
@@ -433,12 +429,15 @@
if Layer <> -1 then gear^.Layer:= Layer;
-if VisualGearLayers[gear^.Layer] <> nil then
+if VisualGearLayersStart[gear^.Layer] = nil then
+ VisualGearLayersStart[gear^.Layer]:= gear;
+
+if VisualGearLayersEnd[gear^.Layer] <> nil then
begin
- VisualGearLayers[gear^.Layer]^.PrevGear:= gear;
- gear^.NextGear:= VisualGearLayers[gear^.Layer]
+ VisualGearLayersEnd[gear^.Layer]^.NextGear:= gear;
+ gear^.PrevGear:= VisualGearLayersEnd[gear^.Layer]
end;
-VisualGearLayers[gear^.Layer]:= gear;
+VisualGearLayersEnd[gear^.Layer]:= gear;
AddVisualGear:= gear;
ScriptCall('onVisualGearAdd', gear^.uid);
@@ -449,12 +448,19 @@
ScriptCall('onVisualGearDelete', Gear^.uid);
FreeAndNilTexture(Gear^.Tex);
- if Gear^.NextGear <> nil then
- Gear^.NextGear^.PrevGear:= Gear^.PrevGear;
+ if (Gear^.NextGear = nil) and (Gear^.PrevGear = nil) then
+ begin
+ VisualGearLayersStart[Gear^.Layer]:= nil;
+ VisualGearLayersEnd[Gear^.Layer]:= nil;
+ end;
if Gear^.PrevGear <> nil then
Gear^.PrevGear^.NextGear:= Gear^.NextGear
- else
- VisualGearLayers[Gear^.Layer]:= Gear^.NextGear;
+ else if Gear^.NextGear <> nil then
+ VisualGearLayersStart[Gear^.Layer]:= Gear^.NextGear;
+ if Gear^.NextGear <> nil then
+ Gear^.NextGear^.PrevGear:= Gear^.PrevGear
+ else if Gear^.PrevGear <> nil then
+ VisualGearLayersEnd[Gear^.Layer]:= Gear^.PrevGear;
if lastVisualGearByUID = Gear then
lastVisualGearByUID:= nil;
@@ -477,7 +483,7 @@
// search in an order that is more likely to return layers they actually use. Could perhaps track statistically AddVisualGear in uScript, since that is most likely the ones they want
for i:= 2 to 5 do
begin
- vg:= VisualGearLayers[i mod 4];
+ vg:= VisualGearLayersStart[i mod 4];
while vg <> nil do
begin
if vg^.uid = uid then
diff -r 8736f3a0ff7f -r e54e41554529 hedgewars/uWorld.pas
--- a/hedgewars/uWorld.pas Mon Jan 14 12:34:47 2019 -0500
+++ b/hedgewars/uWorld.pas Mon Jan 14 12:35:32 2019 -0500
@@ -42,6 +42,7 @@
procedure MoveCamera;
procedure onFocusStateChanged;
procedure updateCursorVisibility;
+procedure updateTouchWidgets(ammoType: TAmmoType);
implementation
uses
@@ -63,6 +64,7 @@
, uCommands
, uTeams
, uDebug
+ , uInputHandler
{$IFDEF USE_VIDEO_RECORDING}
, uVideoRec
{$ENDIF}
@@ -395,6 +397,28 @@
source.y:= frame.y;
end;
end;
+
+with utilityWidget2 do
+ begin
+ show:= false;
+ sprite:= sprBounceButton;
+ frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
+ frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
+ frame.x:= utilityWidget.frame.x + Round(frame.w * 1.25);
+ frame.y:= arrowLeft.frame.y - Round(frame.h * 1.25);
+ active.x:= frame.x;
+ active.y:= frame.y;
+ active.w:= frame.w;
+ active.h:= frame.h;
+ with moveAnim do
+ begin
+ target.x:= frame.x;
+ target.y:= frame.y;
+ source.x:= frame.x;
+ source.y:= frame.y;
+ end;
+ end;
+
{$ENDIF}
end;
@@ -415,7 +439,10 @@
STurns: LongInt;
amSurface: PSDL_Surface;
AMRect: TSDL_Rect;
-{$IFDEF USE_AM_NUMCOLUMN}tmpsurf: PSDL_Surface;{$ENDIF}
+{$IFDEF USE_AM_NUMCOLUMN}
+ tmpsurf: PSDL_Surface;
+ usesDefaultSlotKeys: boolean;
+{$ENDIF}
begin
if cOnlyStats then exit(nil);
@@ -451,6 +478,9 @@
x:= AMRect.x;
y:= AMRect.y;
+{$IFDEF USE_AM_NUMCOLUMN}
+ usesDefaultSlotKeys:= CheckDefaultSlotKeys;
+{$ENDIF USE_AM_NUMCOLUMN}
for i:= 0 to cMaxSlotIndex do
if (i <> cHiddenSlotIndex) and (Ammo^[i, 0].Count > 0) then
begin
@@ -460,7 +490,13 @@
x:= AMRect.x;
{$ENDIF}
{$IFDEF USE_AM_NUMCOLUMN}
- tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar('F' + IntToStr(i+1)), cWhiteColorChannels);
+ // Ammo slot number column
+ if usesDefaultSlotKeys then
+ // F1, F2, F3, F4, ...
+ tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar('F'+IntToStr(i+1)), cWhiteColorChannels)
+ else
+ // 1, 2, 3, 4, ...
+ tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(IntToStr(i+1)), cWhiteColorChannels);
copyToXY(tmpsurf, amSurface,
x + AMSlotPadding + (AMSlotSize shr 1) - (tmpsurf^.w shr 1),
y + AMSlotPadding + (AMSlotSize shr 1) - (tmpsurf^.h shr 1));
@@ -600,12 +636,14 @@
if AMState = AMShowingUp then // show ammo menu
begin
- if (cReducedQuality and rqSlowMenu) <> 0 then
+ // No "appear" animation in low quality or playing with very short turn time.
+ if ((cReducedQuality and rqSlowMenu) <> 0) or (cHedgehogTurnTime <= 10000) then
begin
AMShiftX:= 0;
AMShiftY:= 0;
AMState:= AMShowing;
end
+ // "Appear" animation
else
if AMAnimState < 1 then
begin
@@ -625,12 +663,14 @@
end;
if AMState = AMHiding then // hide ammo menu
begin
- if (cReducedQuality and rqSlowMenu) <> 0 then
+ // No "disappear" animation (see above)
+ if ((cReducedQuality and rqSlowMenu) <> 0) or (cHedgehogTurnTime <= 10000) then
begin
AMShiftX:= AMShiftTargetX;
AMShiftY:= AMShiftTargetY;
AMState:= AMHidden;
end
+ // "Disappear" animation
else
if AMAnimState < 1 then
begin
@@ -742,23 +782,7 @@
SetWeapon(Ammo^[Slot, Pos].AmmoType);
bSelected:= false;
FreeAndNilTexture(WeaponTooltipTex);
-{$IFDEF USE_TOUCH_INTERFACE}//show the aiming buttons + animation
- if (Ammo^[Slot, Pos].Propz and ammoprop_NeedUpDown) <> 0 then
- begin
- if (not arrowUp.show) then
- begin
- animateWidget(@arrowUp, true, true);
- animateWidget(@arrowDown, true, true);
- end;
- end
- else
- if arrowUp.show then
- begin
- animateWidget(@arrowUp, true, false);
- animateWidget(@arrowDown, true, false);
- end;
- SetUtilityWidgetState(Ammo^[Slot, Pos].AmmoType);
-{$ENDIF}
+ updateTouchWidgets(Ammo^[Slot, Pos].AmmoType);
exit
end;
end
@@ -1146,7 +1170,9 @@
h:= -NameTagTex^.w - 24;
if OwnerTex <> nil then
h:= h - OwnerTex^.w - 4;
+ Tint(TeamsArray[t]^.Clan^.Color shl 8 or $FF);
DrawSpriteRotatedF(sprFinger, h, cScreenHeight + DrawHealthY + smallScreenOffset + 2 + SpritesData[sprFinger].Width div 4, 0, 1, -90);
+ untint;
end;
end;
end;
@@ -1198,7 +1224,7 @@
r: TSDL_Rect;
s: shortstring;
offsetX, offsetY, screenBottom: LongInt;
- replicateToLeft, replicateToRight, tmp: boolean;
+ replicateToLeft, replicateToRight, tmp, isNotHiddenByCinematic: boolean;
{$IFDEF USE_VIDEO_RECORDING}
a: Byte;
{$ENDIF}
@@ -1336,6 +1362,12 @@
else
DrawWaves(-1, 50, cWaveHeight div 2, cWaveHeight div 2, 0);
+// line at airplane height for certain airstrike types (when spawning height is important)
+with CurrentHedgehog^ do
+ if (isCursorVisible) and ((CurAmmoType = amNapalm) or (CurAmmoType = amMineStrike) or (((GameFlags and gfMoreWind) <> 0) and ((CurAmmoType = amDrillStrike) or (CurAmmoType = amAirAttack)))) then
+ DrawLine(-3000, topY-300, 7000, topY-300, 3.0, (Team^.Clan^.Color shr 16), (Team^.Clan^.Color shr 8) and $FF, Team^.Clan^.Color and $FF, $FF);
+
+// gear HUD extras (fuel indicator, secondary ammo, etc.)
if replicateToLeft then
begin
ShiftWorld(-1);
@@ -1400,7 +1432,10 @@
if CurAmmoType = amBee then
spr:= sprTargetBee
else
+ begin
spr:= sprTargetP;
+ Tint(Team^.Clan^.Color shl 8 or $FF);
+ end;
if replicateToLeft then
begin
ShiftWorld(-1);
@@ -1416,6 +1451,8 @@
end;
DrawSpriteRotatedF(spr, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ if spr = sprTargetP then
+ untint;
end;
end;
@@ -1442,7 +1479,8 @@
// This scale is used to keep the various widgets at the same dimension at all zoom levels
SetScale(cDefaultZoomLevel);
-// Cinematic Mode: Effects
+isNotHiddenByCinematic:= true;
+// Cinematic Mode: Determine effects and state
if CinematicScript or (InCinematicMode and autoCameraOn
and ((CurrentHedgehog = nil) or CurrentHedgehog^.Team^.ExtDriven
or (CurrentHedgehog^.BotLevel <> 0) or (GameType = gmtDemo))) then
@@ -1451,7 +1489,10 @@
begin
inc(CinematicSteps, Lag);
if CinematicSteps > 300 then
- CinematicSteps:= 300;
+ begin
+ CinematicSteps:= 300;
+ isNotHiddenByCinematic:= false;
+ end;
end;
end
else if CinematicSteps > 0 then
@@ -1461,22 +1502,8 @@
CinematicSteps:= 0;
end;
-// Cinematic Mode: Render black bars
-if CinematicSteps > 0 then
- begin
- r.x:= ViewLeftX;
- r.w:= ViewWidth;
- r.y:= ViewTopY;
- CinematicBarH:= (ViewHeight * CinematicSteps) div 2048;
- r.h:= CinematicBarH;
- DrawRect(r, 0, 0, 0, $FF, true);
- r.y:= ViewBottomY - r.h;
- DrawRect(r, 0, 0, 0, $FF, true);
- end;
-
-
// Turn time
-if UIDisplay <> uiNone then
+if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
begin
{$IFDEF USE_TOUCH_INTERFACE}
offsetX:= cScreenHeight - 13;
@@ -1515,34 +1542,14 @@
DrawSprite(sprFrame, -(cScreenWidth shr 1) + t - 4 + offsetY, cScreenHeight - offsetX, 0);
end;
-// Captions
- DrawCaptions
end;
-{$IFDEF USE_TOUCH_INTERFACE}
-// Draw buttons Related to the Touch interface
-DrawScreenWidget(@arrowLeft);
-DrawScreenWidget(@arrowRight);
-DrawScreenWidget(@arrowUp);
-DrawScreenWidget(@arrowDown);
-
-DrawScreenWidget(@fireButton);
-DrawScreenWidget(@jumpWidget);
-DrawScreenWidget(@AMWidget);
-DrawScreenWidget(@pauseButton);
-DrawScreenWidget(@utilityWidget);
-{$ENDIF}
-
// Team bars
-if UIDisplay = uiAll then
+if (UIDisplay = uiAll) and (isNotHiddenByCinematic) then
RenderTeamsHealth;
-// Lag alert
-if isInLag then
- DrawSprite(sprLag, 32 - (cScreenWidth shr 1), 32, (RealTicks shr 7) mod 12);
-
// Wind bar
-if UIDisplay <> uiNone then
+if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
begin
{$IFDEF USE_TOUCH_INTERFACE}
offsetX:= cScreenHeight - 13;
@@ -1555,7 +1562,7 @@
if WindBarWidth > 0 then
begin
{$WARNINGS OFF}
- r.x:= 8 - (RealTicks shr 6) mod 8;
+ r.x:= 8 - (RealTicks shr 6) mod 9;
{$WARNINGS ON}
r.y:= 0;
r.w:= WindBarWidth;
@@ -1566,7 +1573,7 @@
if WindBarWidth < 0 then
begin
{$WARNINGS OFF}
- r.x:= (Longword(WindBarWidth) + RealTicks shr 6) mod 8;
+ r.x:= (Longword(WindBarWidth) + RealTicks shr 6) mod 9;
{$WARNINGS ON}
r.y:= 0;
r.w:= - WindBarWidth;
@@ -1576,7 +1583,7 @@
end;
// Indicators for global effects (extra damage, low gravity)
-if UIDisplay <> uiNone then
+if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
begin
{$IFDEF USE_TOUCH_INTERFACE}
offsetX:= (cScreenWidth shr 1) - 95;
@@ -1603,6 +1610,42 @@
end;
end;
+// Cinematic Mode: Render black bars
+if CinematicSteps > 0 then
+ begin
+ r.x:= ViewLeftX;
+ r.w:= ViewWidth;
+ r.y:= ViewTopY;
+ CinematicBarH:= (ViewHeight * CinematicSteps) div 2048;
+ r.h:= CinematicBarH;
+ DrawRect(r, 0, 0, 0, $FF, true);
+ r.y:= ViewBottomY - r.h;
+ DrawRect(r, 0, 0, 0, $FF, true);
+ end;
+
+// Touchscreen interface widgets
+{$IFDEF USE_TOUCH_INTERFACE}
+DrawScreenWidget(@arrowLeft);
+DrawScreenWidget(@arrowRight);
+DrawScreenWidget(@arrowUp);
+DrawScreenWidget(@arrowDown);
+
+DrawScreenWidget(@fireButton);
+DrawScreenWidget(@jumpWidget);
+DrawScreenWidget(@AMWidget);
+DrawScreenWidget(@utilityWidget);
+DrawScreenWidget(@utilityWidget2);
+DrawScreenWidget(@pauseButton);
+{$ENDIF}
+
+// Captions
+if UIDisplay <> uiNone then
+ DrawCaptions;
+
+// Lag alert
+if isInLag then
+ DrawSprite(sprLag, 32 - (cScreenWidth shr 1), 32, (RealTicks shr 7) mod 12);
+
// Chat
DrawChat;
@@ -1778,8 +1821,6 @@
with CurrentHedgehog^ do
if (Gear <> nil) and ((Gear^.State and gstChooseTarget) <> 0) then
begin
- if (CurAmmoType = amNapalm) or (CurAmmoType = amMineStrike) or (((GameFlags and gfMoreWind) <> 0) and ((CurAmmoType = amDrillStrike) or (CurAmmoType = amAirAttack))) then
- DrawLine(-3000, topY-300, 7000, topY-300, 3.0, (Team^.Clan^.Color shr 16), (Team^.Clan^.Color shr 8) and $FF, Team^.Clan^.Color and $FF, $FF);
i:= GetCurAmmoEntry(CurrentHedgehog^)^.Pos;
with Ammoz[CurAmmoType] do
if PosCount > 1 then
@@ -2077,6 +2118,30 @@
SDL_ShowCursor(ord(GameState = gsConfirm))
end;
+procedure updateTouchWidgets(ammoType: TAmmoType);
+begin
+{$IFDEF USE_TOUCH_INTERFACE}
+//show the aiming buttons + animation
+if (Ammoz[ammoType].Ammo.Propz and ammoprop_NeedUpDown) <> 0 then
+ begin
+ if (not arrowUp.show) then
+ begin
+ animateWidget(@arrowUp, true, true);
+ animateWidget(@arrowDown, true, true);
+ end;
+ end
+else
+ if arrowUp.show then
+ begin
+ animateWidget(@arrowUp, true, false);
+ animateWidget(@arrowDown, true, false);
+ end;
+SetUtilityWidgetState(ammoType);
+{$ELSE}
+ammoType:= ammoType; // avoid hint
+{$ENDIF}
+end;
+
procedure SetUtilityWidgetState(ammoType: TAmmoType);
begin
{$IFDEF USE_TOUCH_INTERFACE}
@@ -2087,20 +2152,32 @@
if ((Ammoz[ammoType].Ammo.Propz and ammoprop_Timerable) <> 0) and (ammoType <> amDrillStrike) then
begin
utilityWidget.sprite:= sprTimerButton;
- animateWidget(@utilityWidget, true, true);
+ if (not utilityWidget.show) then
+ animateWidget(@utilityWidget, true, true);
end
else if (Ammoz[ammoType].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
begin
utilityWidget.sprite:= sprTargetButton;
- animateWidget(@utilityWidget, true, true);
+ if (not utilityWidget.show) then
+ animateWidget(@utilityWidget, true, true);
end
else if ammoType = amSwitch then
begin
utilityWidget.sprite:= sprSwitchButton;
- animateWidget(@utilityWidget, true, true);
+ if (not utilityWidget.show) then
+ animateWidget(@utilityWidget, true, true);
end
else if utilityWidget.show then
animateWidget(@utilityWidget, true, false);
+
+ if ((Ammoz[ammoType].Ammo.Propz and ammoprop_SetBounce) <> 0) then
+ begin
+ utilityWidget2.sprite:= sprBounceButton;
+ if (not utilityWidget2.show) then
+ animateWidget(@utilityWidget2, true, true);
+ end
+ else if utilityWidget2.show then
+ animateWidget(@utilityWidget2, true, false);
{$ELSE}
ammoType:= ammoType; // avoid hint
{$ENDIF}
diff -r 8736f3a0ff7f -r e54e41554529 project_files/Android-build/SDL-android-project/assets/Data/Graphics/Buttons/bouncebutton.png
Binary file project_files/Android-build/SDL-android-project/assets/Data/Graphics/Buttons/bouncebutton.png has changed
diff -r 8736f3a0ff7f -r e54e41554529 project_files/hwc/CMakeLists.txt
--- a/project_files/hwc/CMakeLists.txt Mon Jan 14 12:34:47 2019 -0500
+++ b/project_files/hwc/CMakeLists.txt Mon Jan 14 12:35:32 2019 -0500
@@ -32,12 +32,14 @@
#get the list of pas files that are going to be converted and compiled
file(GLOB engine_sources_pas "${CMAKE_SOURCE_DIR}/hedgewars/*.pas")
-list(REMOVE_ITEM engine_sources_pas "${CMAKE_SOURCE_DIR}/hedgewars/uVideoRec.pas")
list(REMOVE_ITEM engine_sources_pas "${CMAKE_SOURCE_DIR}/hedgewars/uTouch.pas")
list(REMOVE_ITEM engine_sources_pas "${CMAKE_SOURCE_DIR}/hedgewars/PNGh.pas")
list(REMOVE_ITEM engine_sources_pas "${CMAKE_SOURCE_DIR}/hedgewars/pas2cSystem.pas")
list(REMOVE_ITEM engine_sources_pas "${CMAKE_SOURCE_DIR}/hedgewars/pas2cRedo.pas")
list(REMOVE_ITEM engine_sources_pas "${CMAKE_SOURCE_DIR}/hedgewars/hwLibrary.pas")
+if(NOVIDEOREC)
+ list(REMOVE_ITEM engine_sources_pas "${CMAKE_SOURCE_DIR}/hedgewars/uVideoRec.pas")
+endif()
if(NOT GL2)
list(REMOVE_ITEM engine_sources_pas "${CMAKE_SOURCE_DIR}/hedgewars/uMatrix.pas")
endif()
@@ -73,6 +75,12 @@
set(pas2c_args ${pas2c_args} -d GL2)
endif()
+if(LIBAV_FOUND)
+ add_subdirectory(${CMAKE_SOURCE_DIR}/hedgewars/avwrapper ${CMAKE_CURRENT_BINARY_DIR}/avwrapper)
+ list(APPEND HW_LINK_LIBS avwrapper)
+ set(pas2c_args ${pas2c_args} -d USE_VIDEO_RECORDING)
+endif()
+
#invoke pas2c on main module, it will call all the others
add_custom_command(OUTPUT ${engine_sources}
COMMAND "${EXECUTABLE_OUTPUT_PATH}/pas2c${CMAKE_EXECUTABLE_SUFFIX}"
@@ -111,6 +119,7 @@
physfs
physlayer
m
+ ${HW_LINK_LIBS}
#TODO: add other libraries
)
install(PROGRAMS "${EXECUTABLE_OUTPUT_PATH}/hwengine${CMAKE_EXECUTABLE_SUFFIX}" DESTINATION ${target_binary_install_dir})
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/CMakeLists.txt
--- a/qmlfrontend/CMakeLists.txt Mon Jan 14 12:34:47 2019 -0500
+++ b/qmlfrontend/CMakeLists.txt Mon Jan 14 12:35:32 2019 -0500
@@ -15,6 +15,8 @@
"team.cpp" "team.h"
"engine_instance.cpp" "engine_instance.h"
"preview_image_provider.cpp" "preview_image_provider.h"
- "engine_interface.h")
+ "engine_interface.h"
+ "preview_acceptor.cpp" "preview_acceptor.h"
+ )
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick)
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/Page1.qml
--- a/qmlfrontend/Page1.qml Mon Jan 14 12:34:47 2019 -0500
+++ b/qmlfrontend/Page1.qml Mon Jan 14 12:35:32 2019 -0500
@@ -2,24 +2,31 @@
import Hedgewars.Engine 1.0
Page1Form {
+ property var hwEngine
+
+ Component {
+ id: hwEngineComponent
+
+ HWEngine {
+ engineLibrary: "./libhedgewars_engine.so"
+ previewAcceptor: PreviewAcceptor
+ onPreviewImageChanged: previewImage.source = "image://preview/image"
+ onPreviewIsRendering: previewImage.source = "qrc:/res/iconTime.png"
+ }
+ }
+
+ Component.onCompleted: {
+ hwEngine = hwEngineComponent.createObject()
+ }
+
tickButton.onClicked: {
gameView.tick(100)
}
gameButton.onClicked: {
- var engineInstance = HWEngine.runQuickGame()
+ var engineInstance = hwEngine.runQuickGame()
gameView.engineInstance = engineInstance
}
button1.onClicked: {
- HWEngine.getPreview()
- }
-
- Connections {
- target: HWEngine
- onPreviewImageChanged: {
- previewImage.source = "image://preview/image"
- }
- onPreviewIsRendering: {
- previewImage.source = "qrc:/res/iconTime.png"
- }
+ hwEngine.getPreview()
}
}
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/engine_instance.cpp
--- a/qmlfrontend/engine_instance.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/qmlfrontend/engine_instance.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -1,6 +1,7 @@
#include "engine_instance.h"
#include
+#include
#include
#include
@@ -12,37 +13,92 @@
return currentOpenglContext->getProcAddress(fn);
}
-EngineInstance::EngineInstance(QObject* parent)
- : QObject(parent), m_instance(Engine::start_engine()) {}
+EngineInstance::EngineInstance(const QString& libraryPath, QObject* parent)
+ : QObject(parent) {
+ QLibrary hwlib(libraryPath);
+
+ if (!hwlib.load())
+ qWarning() << "Engine library not found" << hwlib.errorString();
+
+ hedgewars_engine_protocol_version =
+ reinterpret_cast(
+ hwlib.resolve("hedgewars_engine_protocol_version"));
+ start_engine =
+ reinterpret_cast(hwlib.resolve("start_engine"));
+ generate_preview = reinterpret_cast(
+ hwlib.resolve("generate_preview"));
+ dispose_preview = reinterpret_cast(
+ hwlib.resolve("dispose_preview"));
+ cleanup = reinterpret_cast(hwlib.resolve("cleanup"));
+
+ send_ipc = reinterpret_cast(hwlib.resolve("send_ipc"));
+ read_ipc = reinterpret_cast(hwlib.resolve("read_ipc"));
-EngineInstance::~EngineInstance() { Engine::cleanup(m_instance); }
+ setup_current_gl_context =
+ reinterpret_cast(
+ hwlib.resolve("setup_current_gl_context"));
+ render_frame =
+ reinterpret_cast(hwlib.resolve("render_frame"));
+ advance_simulation = reinterpret_cast(
+ hwlib.resolve("advance_simulation"));
+
+ m_isValid = hedgewars_engine_protocol_version && start_engine &&
+ generate_preview && dispose_preview && cleanup && send_ipc &&
+ read_ipc && setup_current_gl_context && render_frame &&
+ advance_simulation;
+ emit isValidChanged(m_isValid);
+
+ if (isValid()) {
+ qDebug() << "Loaded engine library with protocol version"
+ << hedgewars_engine_protocol_version();
+
+ m_instance = start_engine();
+ }
+}
+
+EngineInstance::~EngineInstance() {
+ if (m_isValid) cleanup(m_instance);
+}
void EngineInstance::sendConfig(const GameConfig& config) {
for (auto b : config.config()) {
- Engine::send_ipc(m_instance, reinterpret_cast(b.data()),
- static_cast(b.size()));
+ send_ipc(m_instance, reinterpret_cast(b.data()),
+ static_cast(b.size()));
}
}
void EngineInstance::advance(quint32 ticks) {
- Engine::advance_simulation(m_instance, ticks);
+ advance_simulation(m_instance, ticks);
}
-void EngineInstance::renderFrame() { Engine::render_frame(m_instance); }
+void EngineInstance::renderFrame() { render_frame(m_instance); }
void EngineInstance::setOpenGLContext(QOpenGLContext* context) {
currentOpenglContext = context;
auto size = context->surface()->size();
- Engine::setup_current_gl_context(
- m_instance, static_cast(size.width()),
- static_cast(size.height()), &getProcAddress);
+ setup_current_gl_context(m_instance, static_cast(size.width()),
+ static_cast(size.height()),
+ &getProcAddress);
}
-Engine::PreviewInfo EngineInstance::generatePreview() {
+QImage EngineInstance::generatePreview() {
Engine::PreviewInfo pinfo;
- Engine::generate_preview(m_instance, &pinfo);
+ generate_preview(m_instance, &pinfo);
+
+ QVector colorTable;
+ colorTable.resize(256);
+ for (int i = 0; i < 256; ++i) colorTable[i] = qRgba(255, 255, 0, i);
- return pinfo;
+ QImage previewImage(pinfo.land, static_cast(pinfo.width),
+ static_cast(pinfo.height), QImage::Format_Indexed8);
+ previewImage.setColorTable(colorTable);
+
+ // Cannot use it here, since QImage refers to original bytes
+ // dispose_preview(m_instance);
+
+ return previewImage;
}
+
+bool EngineInstance::isValid() const { return m_isValid; }
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/engine_instance.h
--- a/qmlfrontend/engine_instance.h Mon Jan 14 12:34:47 2019 -0500
+++ b/qmlfrontend/engine_instance.h Mon Jan 14 12:35:32 2019 -0500
@@ -1,31 +1,50 @@
#ifndef ENGINEINSTANCE_H
#define ENGINEINSTANCE_H
-#include "engine_interface.h"
-
+#include
#include
#include
+#include "engine_interface.h"
#include "game_config.h"
class EngineInstance : public QObject {
Q_OBJECT
public:
- explicit EngineInstance(QObject* parent = nullptr);
+ explicit EngineInstance(const QString& libraryPath,
+ QObject* parent = nullptr);
~EngineInstance();
+ Q_PROPERTY(bool isValid READ isValid NOTIFY isValidChanged)
+
void sendConfig(const GameConfig& config);
void advance(quint32 ticks);
void renderFrame();
void setOpenGLContext(QOpenGLContext* context);
- Engine::PreviewInfo generatePreview();
+ QImage generatePreview();
+
+ bool isValid() const;
signals:
+ void isValidChanged(bool isValid);
public slots:
private:
Engine::EngineInstance* m_instance;
+
+ Engine::hedgewars_engine_protocol_version_t*
+ hedgewars_engine_protocol_version;
+ Engine::start_engine_t* start_engine;
+ Engine::generate_preview_t* generate_preview;
+ Engine::dispose_preview_t* dispose_preview;
+ Engine::cleanup_t* cleanup;
+ Engine::send_ipc_t* send_ipc;
+ Engine::read_ipc_t* read_ipc;
+ Engine::setup_current_gl_context_t* setup_current_gl_context;
+ Engine::render_frame_t* render_frame;
+ Engine::advance_simulation_t* advance_simulation;
+ bool m_isValid;
};
#endif // ENGINEINSTANCE_H
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/engine_interface.h
--- a/qmlfrontend/engine_interface.h Mon Jan 14 12:34:47 2019 -0500
+++ b/qmlfrontend/engine_interface.h Mon Jan 14 12:35:32 2019 -0500
@@ -18,10 +18,11 @@
unsigned char* land;
} PreviewInfo;
-typedef uint32_t protocol_version_t();
+typedef uint32_t hedgewars_engine_protocol_version_t();
typedef EngineInstance* start_engine_t();
typedef void generate_preview_t(EngineInstance* engine_state,
PreviewInfo* preview);
+typedef void dispose_preview_t(EngineInstance* engine_state);
typedef void cleanup_t(EngineInstance* engine_state);
typedef void send_ipc_t(EngineInstance* engine_state, uint8_t* buf,
@@ -36,18 +37,6 @@
typedef bool advance_simulation_t(EngineInstance* engine_state, uint32_t ticks);
-extern protocol_version_t* protocol_version;
-extern start_engine_t* start_engine;
-extern generate_preview_t* generate_preview;
-extern cleanup_t* cleanup;
-
-extern send_ipc_t* send_ipc;
-extern read_ipc_t* read_ipc;
-
-extern setup_current_gl_context_t* setup_current_gl_context;
-extern render_frame_t* render_frame;
-extern advance_simulation_t* advance_simulation;
-
#ifdef __cplusplus
}
};
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/hwengine.cpp
--- a/qmlfrontend/hwengine.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/qmlfrontend/hwengine.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -1,64 +1,33 @@
+#include "hwengine.h"
+
#include
-#include
-#include
+#include
#include
#include "engine_instance.h"
#include "engine_interface.h"
#include "game_view.h"
-#include "preview_image_provider.h"
-
-#include "hwengine.h"
+#include "preview_acceptor.h"
-HWEngine::HWEngine(QQmlEngine* engine, QObject* parent)
- : QObject(parent),
- m_engine(engine),
- m_previewProvider(new PreviewImageProvider()) {
- m_engine->addImageProvider(QLatin1String("preview"), m_previewProvider);
-}
+HWEngine::HWEngine(QObject* parent) : QObject(parent) {}
HWEngine::~HWEngine() {}
-static QObject* hwengine_singletontype_provider(QQmlEngine* engine,
- QJSEngine* scriptEngine) {
- Q_UNUSED(scriptEngine)
-
- HWEngine* hwengine = new HWEngine(engine);
- return hwengine;
-}
-
-void HWEngine::exposeToQML() {
- qDebug("HWEngine::exposeToQML");
- qmlRegisterSingletonType("Hedgewars.Engine", 1, 0, "HWEngine",
- hwengine_singletontype_provider);
- qmlRegisterType("Hedgewars.Engine", 1, 0, "GameView");
- qmlRegisterUncreatableType("Hedgewars.Engine", 1, 0,
- "EngineInstance",
- "Create by HWEngine run methods");
-}
-
void HWEngine::getPreview() {
emit previewIsRendering();
m_gameConfig = GameConfig();
m_gameConfig.cmdSeed(QUuid::createUuid().toByteArray());
- EngineInstance engine;
+ EngineInstance engine(m_engineLibrary);
+ if (!engine.isValid()) // TODO: error notification
+ return;
+
engine.sendConfig(m_gameConfig);
- Engine::PreviewInfo preview = engine.generatePreview();
-
- QVector colorTable;
- colorTable.resize(256);
- for (int i = 0; i < 256; ++i) colorTable[i] = qRgba(255, 255, 0, i);
+ QImage previewImage = engine.generatePreview();
- QImage previewImage(preview.land, static_cast(preview.width),
- static_cast(preview.height),
- QImage::Format_Indexed8);
- previewImage.setColorTable(colorTable);
- previewImage.detach();
-
- m_previewProvider->setImage(previewImage);
+ if (m_previewAcceptor) m_previewAcceptor->setImage(previewImage);
emit previewImageChanged();
// m_runQueue->queue(m_gameConfig);
@@ -74,9 +43,28 @@
m_gameConfig.cmdTeam(team1);
m_gameConfig.cmdTeam(team2);
- EngineInstance* engine = new EngineInstance(this);
+ EngineInstance* engine = new EngineInstance(m_engineLibrary, this);
+
return engine;
// m_runQueue->queue(m_gameConfig);
}
int HWEngine::previewHedgehogsCount() const { return m_previewHedgehogsCount; }
+
+PreviewAcceptor* HWEngine::previewAcceptor() const { return m_previewAcceptor; }
+
+QString HWEngine::engineLibrary() const { return m_engineLibrary; }
+
+void HWEngine::setPreviewAcceptor(PreviewAcceptor* previewAcceptor) {
+ if (m_previewAcceptor == previewAcceptor) return;
+
+ m_previewAcceptor = previewAcceptor;
+ emit previewAcceptorChanged(m_previewAcceptor);
+}
+
+void HWEngine::setEngineLibrary(const QString& engineLibrary) {
+ if (m_engineLibrary == engineLibrary) return;
+
+ m_engineLibrary = engineLibrary;
+ emit engineLibraryChanged(m_engineLibrary);
+}
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/hwengine.h
--- a/qmlfrontend/hwengine.h Mon Jan 14 12:34:47 2019 -0500
+++ b/qmlfrontend/hwengine.h Mon Jan 14 12:35:32 2019 -0500
@@ -8,25 +8,33 @@
#include "game_config.h"
class QQmlEngine;
-class PreviewImageProvider;
class EngineInstance;
+class PreviewAcceptor;
class HWEngine : public QObject {
Q_OBJECT
Q_PROPERTY(int previewHedgehogsCount READ previewHedgehogsCount NOTIFY
previewHedgehogsCountChanged)
+ Q_PROPERTY(PreviewAcceptor* previewAcceptor READ previewAcceptor WRITE
+ setPreviewAcceptor NOTIFY previewAcceptorChanged)
+ Q_PROPERTY(QString engineLibrary READ engineLibrary WRITE setEngineLibrary
+ NOTIFY engineLibraryChanged)
public:
- explicit HWEngine(QQmlEngine* engine, QObject* parent = nullptr);
+ explicit HWEngine(QObject* parent = nullptr);
~HWEngine();
- static void exposeToQML();
-
Q_INVOKABLE void getPreview();
Q_INVOKABLE EngineInstance* runQuickGame();
int previewHedgehogsCount() const;
+ PreviewAcceptor* previewAcceptor() const;
+ QString engineLibrary() const;
+
+ public slots:
+ void setPreviewAcceptor(PreviewAcceptor* previewAcceptor);
+ void setEngineLibrary(const QString& engineLibrary);
signals:
void previewIsRendering();
@@ -34,12 +42,15 @@
void previewHogCountChanged(int count);
void gameFinished();
void previewHedgehogsCountChanged(int previewHedgehogsCount);
+ void previewAcceptorChanged(PreviewAcceptor* previewAcceptor);
+ void engineLibraryChanged(const QString& engineLibrary);
private:
QQmlEngine* m_engine;
- PreviewImageProvider* m_previewProvider;
GameConfig m_gameConfig;
int m_previewHedgehogsCount;
+ PreviewAcceptor* m_previewAcceptor;
+ QString m_engineLibrary;
};
#endif // HWENGINE_H
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/main.cpp
--- a/qmlfrontend/main.cpp Mon Jan 14 12:34:47 2019 -0500
+++ b/qmlfrontend/main.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -4,66 +4,34 @@
#include
#include "engine_interface.h"
+#include "game_view.h"
#include "hwengine.h"
+#include "preview_acceptor.h"
-namespace Engine {
-protocol_version_t* protocol_version;
-start_engine_t* start_engine;
-generate_preview_t* generate_preview;
-cleanup_t* cleanup;
-send_ipc_t* send_ipc;
-read_ipc_t* read_ipc;
-setup_current_gl_context_t* setup_current_gl_context;
-render_frame_t* render_frame;
-advance_simulation_t* advance_simulation;
-}; // namespace Engine
-
-void loadEngineLibrary() {
-#ifdef Q_OS_WIN
- QLibrary hwlib("./libhedgewars_engine.dll");
-#else
- QLibrary hwlib("./libhedgewars_engine.so");
-#endif
-
- if (!hwlib.load())
- qWarning() << "Engine library not found" << hwlib.errorString();
+namespace Engine {}; // namespace Engine
- Engine::protocol_version = reinterpret_cast(
- hwlib.resolve("protocol_version"));
- Engine::start_engine =
- reinterpret_cast(hwlib.resolve("start_engine"));
- Engine::generate_preview = reinterpret_cast(
- hwlib.resolve("generate_preview"));
- Engine::cleanup =
- reinterpret_cast(hwlib.resolve("cleanup"));
+static QObject* previewacceptor_singletontype_provider(
+ QQmlEngine* engine, QJSEngine* scriptEngine) {
+ Q_UNUSED(scriptEngine)
- Engine::send_ipc =
- reinterpret_cast(hwlib.resolve("send_ipc"));
- Engine::read_ipc =
- reinterpret_cast(hwlib.resolve("read_ipc"));
-
- Engine::setup_current_gl_context =
- reinterpret_cast(
- hwlib.resolve("setup_current_gl_context"));
- Engine::render_frame =
- reinterpret_cast(hwlib.resolve("render_frame"));
- Engine::advance_simulation = reinterpret_cast(
- hwlib.resolve("advance_simulation"));
-
- if (Engine::protocol_version)
- qDebug() << "Loaded engine library with protocol version"
- << Engine::protocol_version();
+ PreviewAcceptor* acceptor = new PreviewAcceptor(engine);
+ return acceptor;
}
int main(int argc, char* argv[]) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
- loadEngineLibrary();
-
QQmlApplicationEngine engine;
- HWEngine::exposeToQML();
+ qmlRegisterSingletonType(
+ "Hedgewars.Engine", 1, 0, "PreviewAcceptor",
+ previewacceptor_singletontype_provider);
+ qmlRegisterType("Hedgewars.Engine", 1, 0, "HWEngine");
+ qmlRegisterType("Hedgewars.Engine", 1, 0, "GameView");
+ qmlRegisterUncreatableType("Hedgewars.Engine", 1, 0,
+ "EngineInstance",
+ "Create by HWEngine run methods");
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty()) return -1;
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/preview_acceptor.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/preview_acceptor.cpp Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,15 @@
+#include "preview_acceptor.h"
+
+#include
+#include
+
+#include "preview_image_provider.h"
+
+PreviewAcceptor::PreviewAcceptor(QQmlEngine *engine, QObject *parent)
+ : QObject(parent), m_previewProvider(new PreviewImageProvider()) {
+ engine->addImageProvider(QLatin1String("preview"), m_previewProvider);
+}
+
+void PreviewAcceptor::setImage(const QImage &preview) {
+ m_previewProvider->setImage(preview);
+}
diff -r 8736f3a0ff7f -r e54e41554529 qmlfrontend/preview_acceptor.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/preview_acceptor.h Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,21 @@
+#ifndef PREVIEW_ACCEPTOR_H
+#define PREVIEW_ACCEPTOR_H
+
+#include
+
+class QQmlEngine;
+class PreviewImageProvider;
+
+class PreviewAcceptor : public QObject {
+ Q_OBJECT
+ public:
+ explicit PreviewAcceptor(QQmlEngine *engine, QObject *parent = nullptr);
+
+ public slots:
+ void setImage(const QImage &preview);
+
+ private:
+ PreviewImageProvider *m_previewProvider;
+};
+
+#endif // PREVIEW_ACCEPTOR_H
diff -r 8736f3a0ff7f -r e54e41554529 rust/chat_sanitizer/Cargo.toml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chat_sanitizer/Cargo.toml Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,9 @@
+[package]
+name = "chat_sanitizer"
+version = "0.1.0"
+authors = ["Andrey Korotaev "]
+edition = "2018"
+
+[dependencies]
+unicode_skeleton = "0.1"
+itertools = "0.8.0"
diff -r 8736f3a0ff7f -r e54e41554529 rust/chat_sanitizer/src/bad_words.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chat_sanitizer/src/bad_words.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,58 @@
+use crate::{normalized_message, MessageChecker, Severity};
+
+use std::marker::PhantomData;
+
+struct BadWordsChecker {
+ blacklist: Vec,
+ whitelist: Vec,
+ player_id_type: PhantomData,
+}
+
+impl BadWordsChecker {
+ pub fn new(blacklist: &[&str], whitelist: &[&str]) -> Self {
+ Self {
+ blacklist: blacklist.iter().map(|s| normalized_message(*s)).collect(),
+ whitelist: whitelist.iter().map(|s| normalized_message(*s)).collect(),
+ player_id_type: PhantomData,
+ }
+ }
+}
+
+impl MessageChecker for BadWordsChecker {
+ fn check(&self, _player_id: T, message: &str) -> Severity {
+ let msg = normalized_message(message);
+
+ // silly implementation, allows bad messages with a single good word
+ for bad_word in &self.blacklist {
+ if msg.contains(bad_word) {
+ if !self
+ .whitelist
+ .iter()
+ .any(|good_word| msg.contains(good_word))
+ {
+ return Severity::Warn;
+ }
+ }
+ }
+
+ Severity::Pass
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn it_works() {
+ let checker = BadWordsChecker::new(&["fsck", "poop"], &["fsck -y"]);
+ assert_eq!(checker.check(0, "group hug"), Severity::Pass);
+ assert_eq!(checker.check(0, "fpoopf"), Severity::Warn);
+ assert_eq!(checker.check(0, "PooP"), Severity::Warn);
+
+ // this one fails
+ //assert_eq!(checker.check(0, "poop 'fsck -y' poop"), Severity::Warn);
+
+ // ideally this one shouldn't fail, need a better confusables check
+ // assert_eq!(checker.check(0, "P00P"), Severity::Warn);
+ }
+}
diff -r 8736f3a0ff7f -r e54e41554529 rust/chat_sanitizer/src/caps_abuse.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chat_sanitizer/src/caps_abuse.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,9 @@
+use crate::{MessageChecker, Severity};
+
+struct CapsAbuseChecker {}
+
+impl MessageChecker for CapsAbuseChecker {
+ fn check(&self, player_id: T, message: &str) -> Severity {
+ Severity::Pass
+ }
+}
diff -r 8736f3a0ff7f -r e54e41554529 rust/chat_sanitizer/src/flood.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chat_sanitizer/src/flood.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,9 @@
+use crate::{MessageChecker, Severity};
+
+struct FloodChecker {}
+
+impl MessageChecker for FloodChecker {
+ fn check(&self, player_id: T, message: &str) -> Severity {
+ Severity::Pass
+ }
+}
diff -r 8736f3a0ff7f -r e54e41554529 rust/chat_sanitizer/src/letter_repeat.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chat_sanitizer/src/letter_repeat.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,45 @@
+use crate::{MessageChecker, Severity};
+
+use itertools::Itertools;
+use std::marker::PhantomData;
+
+struct LetterRepeatChecker {
+ threshold: usize,
+ player_id_type: PhantomData,
+}
+
+impl LetterRepeatChecker {
+ pub fn new(threshold: usize) -> Self {
+ Self {
+ threshold,
+ player_id_type: PhantomData,
+ }
+ }
+}
+
+impl MessageChecker for LetterRepeatChecker {
+ fn check(&self, _player_id: T, message: &str) -> Severity {
+ for (_key, group) in &message.chars().into_iter().group_by(|c| *c) {
+ if group.count() >= self.threshold {
+ return Severity::Warn;
+ }
+ }
+
+ Severity::Pass
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn it_works() {
+ let checker = LetterRepeatChecker::new(3);
+ assert_eq!(checker.check(0, "Hello world!"), Severity::Pass);
+ assert_eq!(checker.check(0, "ooops"), Severity::Warn);
+ assert_eq!(
+ checker.check(0, "жираф - длинношеее животное"),
+ Severity::Warn
+ );
+ }
+}
diff -r 8736f3a0ff7f -r e54e41554529 rust/chat_sanitizer/src/lib.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chat_sanitizer/src/lib.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,26 @@
+pub mod bad_words;
+pub mod letter_repeat;
+
+use unicode_skeleton::UnicodeSkeleton;
+
+#[derive(PartialEq, Debug)]
+enum Severity {
+ Pass,
+ Warn,
+ Silence,
+ Ban,
+}
+
+trait MessageChecker {
+ fn check(&self, player_id: T, message: &str) -> Severity;
+ fn fix(&self, player_id: T, message: &str) -> Option {
+ None
+ }
+}
+
+fn normalized_message(s: &str) -> String {
+ s.chars()
+ .flat_map(|c| c.to_lowercase())
+ .skeleton_chars()
+ .collect::()
+}
diff -r 8736f3a0ff7f -r e54e41554529 rust/chat_sanitizer/src/part_repeat.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chat_sanitizer/src/part_repeat.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,9 @@
+use crate::{MessageChecker, Severity};
+
+struct PartRepeatChecker {}
+
+impl MessageChecker for PartRepeatChecker {
+ fn check(&self, player_id: T, message: &str) -> Severity {
+ Severity::Pass
+ }
+}
diff -r 8736f3a0ff7f -r e54e41554529 rust/chat_sanitizer/src/url.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chat_sanitizer/src/url.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,9 @@
+use crate::{MessageChecker, Severity};
+
+struct URLChecker {}
+
+impl MessageChecker for URLChecker {
+ fn check(&self, player_id: T, message: &str) -> Severity {
+ Severity::Pass
+ }
+}
diff -r 8736f3a0ff7f -r e54e41554529 rust/hedgewars-checker/src/main.rs
--- a/rust/hedgewars-checker/src/main.rs Mon Jan 14 12:34:47 2019 -0500
+++ b/rust/hedgewars-checker/src/main.rs Mon Jan 14 12:35:32 2019 -0500
@@ -175,7 +175,7 @@
fn get_protocol_number(executable: &str) -> std::io::Result {
let output = Command::new(executable).arg("--protocol").output()?;
- Ok(u32::from_str(&String::from_utf8(output.stdout).unwrap().as_str()).unwrap_or(55))
+ Ok(u32::from_str(&String::from_utf8(output.stdout).unwrap().trim()).unwrap_or(55))
}
fn main() {
diff -r 8736f3a0ff7f -r e54e41554529 rust/hedgewars-server/Cargo.toml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/Cargo.toml Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,29 @@
+[package]
+edition = "2018"
+name = "hedgewars-server"
+version = "0.0.1"
+authors = [ "Andrey Korotaev " ]
+
+[features]
+official-server = ["openssl", "mysql"]
+tls-connections = ["openssl"]
+default = []
+
+[dependencies]
+rand = "0.5"
+mio = "0.6"
+slab = "0.4"
+netbuf = "0.4"
+nom = "4.1"
+env_logger = "0.6"
+log = "0.4"
+base64 = "0.10"
+bitflags = "1.0"
+serde = "1.0"
+serde_yaml = "0.8"
+serde_derive = "1.0"
+openssl = { version = "0.10", optional = true }
+mysql = { version = "14.2", optional = true }
+
+[dev-dependencies]
+proptest = "0.8"
diff -r 8736f3a0ff7f -r e54e41554529 rust/hedgewars-server/src/main.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/main.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,65 @@
+#![allow(unused_imports)]
+#![deny(bare_trait_objects)]
+
+//use std::io::*;
+//use rand::Rng;
+//use std::cmp::Ordering;
+use log::*;
+use mio::net::*;
+use mio::*;
+
+mod protocol;
+mod server;
+mod utils;
+
+use crate::server::network::NetworkLayer;
+use std::time::Duration;
+
+fn main() {
+ env_logger::init();
+
+ info!(
+ "Hedgewars game server, protocol {}",
+ utils::PROTOCOL_VERSION
+ );
+
+ let address = "0.0.0.0:46631".parse().unwrap();
+ let listener = TcpListener::bind(&address).unwrap();
+
+ let poll = Poll::new().unwrap();
+ let mut hw_network = NetworkLayer::new(listener, 1024, 512);
+ hw_network.register_server(&poll).unwrap();
+
+ let mut events = Events::with_capacity(1024);
+
+ loop {
+ let timeout = if hw_network.has_pending_operations() {
+ Some(Duration::from_millis(1))
+ } else {
+ None
+ };
+ poll.poll(&mut events, timeout).unwrap();
+
+ for event in events.iter() {
+ if event.readiness() & Ready::readable() == Ready::readable() {
+ match event.token() {
+ utils::SERVER => hw_network.accept_client(&poll).unwrap(),
+ Token(tok) => hw_network.client_readable(&poll, tok).unwrap(),
+ }
+ }
+ if event.readiness() & Ready::writable() == Ready::writable() {
+ match event.token() {
+ utils::SERVER => unreachable!(),
+ Token(tok) => hw_network.client_writable(&poll, tok).unwrap(),
+ }
+ }
+ // if event.kind().is_hup() || event.kind().is_error() {
+ // match event.token() {
+ // utils::SERVER => unreachable!(),
+ // Token(tok) => server.client_error(&poll, tok).unwrap(),
+ // }
+ // }
+ }
+ hw_network.on_idle(&poll).unwrap();
+ }
+}
diff -r 8736f3a0ff7f -r e54e41554529 rust/hedgewars-server/src/protocol.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/protocol.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,43 @@
+use netbuf;
+use nom::{Err, IResult};
+use std::io::{Read, Result};
+
+pub mod messages;
+mod parser;
+#[cfg(test)]
+pub mod test;
+
+pub struct ProtocolDecoder {
+ buf: netbuf::Buf,
+ consumed: usize,
+}
+
+impl ProtocolDecoder {
+ pub fn new() -> ProtocolDecoder {
+ ProtocolDecoder {
+ buf: netbuf::Buf::new(),
+ consumed: 0,
+ }
+ }
+
+ pub fn read_from(&mut self, stream: &mut R) -> Result {
+ self.buf.read_from(stream)
+ }
+
+ pub fn extract_messages(&mut self) -> Vec {
+ let parse_result = parser::extract_messages(&self.buf[..]);
+ match parse_result {
+ Ok((tail, msgs)) => {
+ self.consumed = self.buf.len() - self.consumed - tail.len();
+ msgs
+ }
+ Err(Err::Incomplete(_)) => unreachable!(),
+ Err(Err::Error(_)) | Err(Err::Failure(_)) => unreachable!(),
+ }
+ }
+
+ pub fn sweep(&mut self) {
+ self.buf.consume(self.consumed);
+ self.consumed = 0;
+ }
+}
diff -r 8736f3a0ff7f -r e54e41554529 rust/hedgewars-server/src/protocol/messages.rs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/protocol/messages.rs Mon Jan 14 12:35:32 2019 -0500
@@ -0,0 +1,319 @@
+use crate::server::coretypes::{GameCfg, HedgehogInfo, ServerVar, TeamInfo, VoteType};
+use std::{convert::From, iter::once, ops};
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum HWProtocolMessage {
+ // core
+ Ping,
+ Pong,
+ Quit(Option),
+ //Cmd(String, Vec),
+ Global(String),
+ Watch(String),
+ ToggleServerRegisteredOnly,
+ SuperPower,
+ Info(String),
+ // not entered state
+ Nick(String),
+ Proto(u16),
+ Password(String, String),
+ Checker(u16, String, String),
+ // lobby
+ List,
+ Chat(String),
+ CreateRoom(String, Option),
+ JoinRoom(String, Option),
+ Follow(String),
+ Rnd(Vec),
+ Kick(String),
+ Ban(String, String, u32),
+ BanIP(String, String, u32),
+ BanNick(String, String, u32),
+ BanList,
+ Unban(String),
+ SetServerVar(ServerVar),
+ GetServerVar,
+ RestartServer,
+ Stats,
+ // in room
+ Part(Option),
+ Cfg(GameCfg),
+ AddTeam(Box),
+ RemoveTeam(String),
+ SetHedgehogsNumber(String, u8),
+ SetTeamColor(String, u8),
+ ToggleReady,
+ StartGame,
+ EngineMessage(String),
+ RoundFinished,
+ ToggleRestrictJoin,
+ ToggleRestrictTeams,
+ ToggleRegisteredOnly,
+ RoomName(String),
+ Delegate(String),
+ TeamChat(String),
+ MaxTeams(u8),
+ Fix,
+ Unfix,
+ Greeting(String),
+ CallVote(Option),
+ Vote(bool),
+ ForceVote(bool),
+ Save(String, String),
+ Delete(String),
+ SaveRoom(String),
+ LoadRoom(String),
+ Malformed,
+ Empty,
+}
+
+#[derive(Debug)]
+pub enum HWServerMessage {
+ Ping,
+ Pong,
+ Bye(String),
+ Nick(String),
+ Proto(u16),
+ ServerAuth(String),
+ LobbyLeft(String, String),
+ LobbyJoined(Vec),
+ ChatMsg { nick: String, msg: String },
+ ClientFlags(String, Vec),
+ Rooms(Vec),
+ RoomAdd(Vec),
+ RoomJoined(Vec),
+ RoomLeft(String, String),
+ RoomRemove(String),
+ RoomUpdated(String, Vec),
+ TeamAdd(Vec),
+ TeamRemove(String),
+ TeamAccepted(String),
+ TeamColor(String, u8),
+ HedgehogsNumber(String, u8),
+ ConfigEntry(String, Vec),
+ Kicked,
+ RunGame,
+ ForwardEngineMessage(Vec),
+ RoundFinished,
+
+ ServerMessage(String),
+ Notice(String),
+ Warning(String),
+ Error(String),
+ Connected(u32),
+ Unreachable,
+
+ //Deprecated messages
+ LegacyReady(bool, Vec),
+}
+
+pub fn server_chat(msg: String) -> HWServerMessage {
+ HWServerMessage::ChatMsg {
+ nick: "[server]".to_string(),
+ msg,
+ }
+}
+
+impl GameCfg {
+ pub fn to_protocol(&self) -> (String, Vec) {
+ use crate::server::coretypes::GameCfg::*;
+ match self {
+ FeatureSize(s) => ("FEATURE_SIZE".to_string(), vec![s.to_string()]),
+ MapType(t) => ("MAP".to_string(), vec![t.to_string()]),
+ MapGenerator(g) => ("MAPGEN".to_string(), vec![g.to_string()]),
+ MazeSize(s) => ("MAZE_SIZE".to_string(), vec![s.to_string()]),
+ Seed(s) => ("SEED".to_string(), vec![s.to_string()]),
+ Template(t) => ("TEMPLATE".to_string(), vec![t.to_string()]),
+
+ Ammo(n, None) => ("AMMO".to_string(), vec![n.to_string()]),
+ Ammo(n, Some(s)) => ("AMMO".to_string(), vec![n.to_string(), s.to_string()]),
+ Scheme(n, s) if s.is_empty() => ("SCHEME".to_string(), vec![n.to_string()]),
+ Scheme(n, s) => ("SCHEME".to_string(), {
+ let mut v = vec![n.to_string()];
+ v.extend(s.clone().into_iter());
+ v
+ }),
+ Script(s) => ("SCRIPT".to_string(), vec![s.to_string()]),
+ Theme(t) => ("THEME".to_string(), vec![t.to_string()]),
+ DrawnMap(m) => ("DRAWNMAP".to_string(), vec![m.to_string()]),
+ }
+ }
+
+ pub fn to_server_msg(&self) -> HWServerMessage {
+ use self::HWServerMessage::ConfigEntry;
+ let (name, args) = self.to_protocol();
+ HWServerMessage::ConfigEntry(name, args)
+ }
+}
+
+macro_rules! const_braces {
+ ($e: expr) => {
+ "{}\n"
+ };
+}
+
+macro_rules! msg {
+ [$($part: expr),*] => {
+ format!(concat!($(const_braces!($part)),*, "\n"), $($part),*);
+ };
+}
+
+#[cfg(test)]
+macro_rules! several {
+ [$part: expr] => { once($part) };
+ [$part: expr, $($other: expr),*] => { once($part).chain(several![$($other),*]) };
+}
+
+impl HWProtocolMessage {
+ /** Converts the message to a raw `String`, which can be sent over the network.
+ *
+ * This is the inverse of the `message` parser.
+ */
+ #[cfg(test)]
+ pub(crate) fn to_raw_protocol(&self) -> String {
+ use self::HWProtocolMessage::*;
+ match self {
+ Ping => msg!["PING"],
+ Pong => msg!["PONG"],
+ Quit(None) => msg!["QUIT"],
+ Quit(Some(msg)) => msg!["QUIT", msg],
+ Global(msg) => msg!["CMD", format!("GLOBAL {}", msg)],
+ Watch(name) => msg!["CMD", format!("WATCH {}", name)],
+ ToggleServerRegisteredOnly => msg!["CMD", "REGISTERED_ONLY"],
+ SuperPower => msg!["CMD", "SUPER_POWER"],
+ Info(info) => msg!["CMD", format!("INFO {}", info)],
+ Nick(nick) => msg!("NICK", nick),
+ Proto(version) => msg!["PROTO", version],
+ Password(p, s) => msg!["PASSWORD", p, s],
+ Checker(i, n, p) => msg!["CHECKER", i, n, p],
+ List => msg!["LIST"],
+ Chat(msg) => msg!["CHAT", msg],
+ CreateRoom(name, None) => msg!["CREATE_ROOM", name],
+ CreateRoom(name, Some(password)) => msg!["CREATE_ROOM", name, password],
+ JoinRoom(name, None) => msg!["JOIN_ROOM", name],
+ JoinRoom(name, Some(password)) => msg!["JOIN_ROOM", name, password],
+ Follow(name) => msg!["FOLLOW", name],
+ Rnd(args) => {
+ if args.is_empty() {
+ msg!["CMD", "RND"]
+ } else {
+ msg!["CMD", format!("RND {}", args.join(" "))]
+ }
+ }
+ Kick(name) => msg!["KICK", name],
+ Ban(name, reason, time) => msg!["BAN", name, reason, time],
+ BanIP(ip, reason, time) => msg!["BAN_IP", ip, reason, time],
+ BanNick(nick, reason, time) => msg!("BAN_NICK", nick, reason, time),
+ BanList => msg!["BANLIST"],
+ Unban(name) => msg!["UNBAN", name],
+ //SetServerVar(ServerVar), ???
+ GetServerVar => msg!["GET_SERVER_VAR"],
+ RestartServer => msg!["CMD", "RESTART_SERVER YES"],
+ Stats => msg!["CMD", "STATS"],
+ Part(None) => msg!["PART"],
+ Part(Some(msg)) => msg!["PART", msg],
+ Cfg(config) => {
+ let (name, args) = config.to_protocol();
+ msg!["CFG", name, args.join("\n")]
+ }
+ AddTeam(info) => msg![
+ "ADD_TEAM",
+ info.name,
+ info.color,
+ info.grave,
+ info.fort,
+ info.voice_pack,
+ info.flag,
+ info.difficulty,
+ info.hedgehogs
+ .iter()
+ .flat_map(|h| several![&h.name[..], &h.hat[..]])
+ .collect::>()
+ .join("\n")
+ ],
+ RemoveTeam(name) => msg!["REMOVE_TEAM", name],
+ SetHedgehogsNumber(team, number) => msg!["HH_NUM", team, number],
+ SetTeamColor(team, color) => msg!["TEAM_COLOR", team, color],
+ ToggleReady => msg!["TOGGLE_READY"],
+ StartGame => msg!["START_GAME"],
+ EngineMessage(msg) => msg!["EM", msg],
+ RoundFinished => msg!["ROUNDFINISHED"],
+ ToggleRestrictJoin => msg!["TOGGLE_RESTRICT_JOINS"],
+ ToggleRestrictTeams => msg!["TOGGLE_RESTRICT_TEAMS"],
+ ToggleRegisteredOnly => msg!["TOGGLE_REGISTERED_ONLY"],
+ RoomName(name) => msg!["ROOM_NAME", name],
+ Delegate(name) => msg!["CMD", format!("DELEGATE {}", name)],
+ TeamChat(msg) => msg!["TEAMCHAT", msg],
+ MaxTeams(count) => msg!["CMD", format!("MAXTEAMS {}", count)],
+ Fix => msg!["CMD", "FIX"],
+ Unfix => msg!["CMD", "UNFIX"],
+ Greeting(msg) => msg!["CMD", format!("GREETING {}", msg)],
+ //CallVote(Option<(String, Option