diff --git a/CREDITS.md b/CREDITS.md
index a5c854fdc..c996e63cb 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -7,6 +7,7 @@ This page lists all the individual contributions to the project by their author.
- **Belonit (Gluk-v48)**:
- Check for Changelog/Documentation/Credits in Pull Requests.
- Docs dark theme switcher.
+ - Porting the YR MP spawner from C to C++ and YR++, used as a base for the Vinifera spawner.
- **CCHyper/tomsons26**:
- Vinifera foundations: TS++, game.exe hooker, extension system and other core features
- Implement `CurleyShuffle` for AircraftTypes
@@ -130,6 +131,8 @@ This page lists all the individual contributions to the project by their author.
- Implement various controls to customise target lasers line.
- Implement various controls to show and customise NavCom queue lines.
- Implement customizable mouse cursors and actions.
+- **CnCNet Contributors**:
+ - Tiberian Sun TS-patches spawner, Yuri's Revenge CnCNet spawner that served as a base for Vinifera spawner.
- **Kerbiter (Metadorius)**:
- Initial documentation setup.
- **MarkJFox**:
@@ -200,4 +203,5 @@ This page lists all the individual contributions to the project by their author.
- Update and finalize custom mouse cursors and actions, add customizable weapon & EMP cursors.
- Implement support for a Saved Games subdirectory.
- Fix a bug where if the player loaded a saved game, the score screen timer would report the time since the saved game was loaded, instead of since when the scenario was first started.
+ - Implement the multiplayer spawner.
diff --git a/docs/Miscellaneous.md b/docs/Miscellaneous.md
index e9bf623be..f69bd911e 100644
--- a/docs/Miscellaneous.md
+++ b/docs/Miscellaneous.md
@@ -12,6 +12,117 @@ This page describes every change in Vinifera that wasn't categorized into a prop
- It is no longer required to list all Tiberiums in a map to override some Tiberium's properties.
- It was possible to make a building get built at a different speed than its cost implied by giving the building a `FreeUnit` or `PadAircraft` that was more expensive than the building itself. This functionality has been removed in preference of `BuildTimeCost`.
+## Spawner
+
+- Vinifera implements its own spawner, capable of starting a new singleplayer, skirmish or multiplayer game, as well as loading saved games.
+- To start the game in spawner mode, the `-SPAWN` command line argument must be specified.
+- The spawner's options can be configures in `SPAWN.INI`.
+
+In `SPAWN.INI`:
+```ini
+[Settings]
+; Game Mode Options
+Bases=yes ; boolean, do players start with MCVs/Construction Yards?
+Credits=10000 ; integer, starting amount of credits for the players.
+BridgeDestroy=yes ; boolean, can bridges be destroyed?
+Crates=no ; boolean, are crates enabled?
+ShortGame=no ; boolean, is short game enabled?
+BuildOffAlly=no ; boolean, is building off ally bases allowed?
+GameSpeed=0 ; integer, starting game speed.
+MultiEngineer=no ; boolean, is multi-engineer enabled?
+UnitCount=0 ; integer, starting unit count.
+AIPlayers=0 ; integer, number of AI players.
+AIDifficulty=1 ; integer, AI difficulty.
+AlliesAllowed=no ; boolean, can players form and break alliances in-game?
+HarvesterTruce=no ; boolean, are harvesters invulnerable?
+FogOfWar=no ; boolean, is fog of war enabled?
+MCVRedeploy=yes ; boolean, can MCVs be redeployed?
+
+; Savegame Options
+LoadSaveGame=no ; boolean, should the spawner load a saved game, as opposed to starting a new scenario?
+SavedGamesDir=Saved Games ; string, name (path) of the subfolder containing saved games. Supports nesting, e. g. Saved Games\Tiberian Sun.
+SaveGameName= ; string, name of the saved game to load.
+
+; Scenario Options
+Seed=0 ; integer, random seed.
+TechLevel=10 ; integer, maximum tech level.
+IsCampaign=no ; boolean, is the game that is about to start campaign, as opposed to skirmish?
+CampaignID=-1 ; integer, ID of the campaign (from BATTLE.INI) to start
+Tournament=0 ; integer, WOL Tournament Type
+WOLGameID=3735928559 ; unsigned integer, WOL Game ID
+ScenarioName=spawnmap.ini ; string, name of the scenario (map) to load.
+MapHash= ; string, map hash, only used in statistics collection.
+UIMapName= ; string, name of the map, only used in statistics collection.
+
+; Network Options
+Protocol=2 ; integer, network protocol to use.
+FrameSendRate=4 ; integer, starting FrameSendRate value.
+ReconnectTimeout=2400 ; integer, player reconnection timeout.
+ConnTimeout=3600 ; integer, player connection timeout.
+MaxAhead=-1 ; integer, starting MaxHead value.
+PreCalcMaxAhead=0 ; integer, starting PrecalcMaxHead value.
+MaxLatencyLevel=255 ; unsigned byte, maximum allowed Protocol 0 latency level.
+
+; Tunnel Options
+TunnelId=0 ; integer, tunnel ID.
+TunnelIp=0.0.0.0 ; string, tunnel IP.
+TunnelPort=0 ; integer, tunnel port.
+ListenPort=1234 ; integer, listen port.
+
+; Extra Options
+Firestorm=yes ; boolean, should the game start with Firestorm enabled?
+QuickMatch=no ; boolean, should the game start in Quick Match mode?
+SkipScoreScreen=no ; boolean, should the score screen be skipped once the game is over?
+WriteStatistics=no ; boolean, should statistics be sent?
+AINamesByDifficulty=no ; boolean, should AI players have their difficulty in their name?
+CoachMode=no ; boolean, should defeated players that have allies not have the entire map revealed to them upon death?
+AutoSurrender=yes ; boolean, should players surrender on disconnection, as opposed to turning their base over to the AI?
+```
+
+- Information about the local player is read from the `Settings` section, for all other players - from `OtherX` sections, where `X` ranges from `1` to `7`.
+
+In `SPAWN.INI`:
+```ini
+[PLAYERSECTION]
+IsHuman=no ; boolean, is this a human player?
+Name= ; string, the player's name.
+Color=-1 ; integer, the player's color.
+House=-1 ; integer, the player's house.
+Difficulty=-1 ; integer, the player's difficulty.
+Ip=0.0.0.0 ; string, the player's IP address.
+Port=-1 ; integer, the player's port.
+```
+
+- Additionally, AI players (always come after human players) have these options parsed from sections of the format `MultiX`, where `X` ranges from `1` to `8`.
+
+In `SPAWN.INI`:
+```ini
+[MULTISECTION]
+Color=-1 ; integer, the player's color.
+House=-1 ; integer, the player's house.
+Difficulty=-1 ; integer, the player's difficulty.
+```
+
+- Additionally, the spawner reads configuration for each house. Player houses come first, in the order of their color (increasing), then AI houses.
+- Alliances are read from sections of the format `MultiX_Alliances`, where `X` ranges from `1` to `8`.
+
+In `SPAWN.INI`:
+```ini
+[MULTISECTION]
+IsSpectator=no ; boolean, is this house a spectator (observer)?
+SpawnLocations=-2 ; integer, spawn location of this house. 90 and -1 mean spectator, -2 means random.
+
+[ALLIANCESSECTION]
+HouseAllyOne=-1 ; integer, index of the house this house is allied to, -1 means none.
+HouseAllyTwo=-1 ; integer, index of the house this house is allied to, -1 means none.
+HouseAllyThree=-1 ; integer, index of the house this house is allied to, -1 means none.
+HouseAllyFour=-1 ; integer, index of the house this house is allied to, -1 means none.
+HouseAllyFive=-1 ; integer, index of the house this house is allied to, -1 means none.
+HouseAllySix=-1 ; integer, index of the house this house is allied to, -1 means none.
+HouseAllySeven=-1 ; integer, index of the house this house is allied to, -1 means none.
+HouseAllyEight=-1 ; integer, index of the house this house is allied to, -1 means none.
+```
+
## Quality of Life
- Harvesters are now considered when executing the "Guard" command. They have a special case when assigned with the Guard mission that tells them to find the nearest Tiberium patch and begin harvesting.
@@ -156,7 +267,10 @@ Due to the nature of its use, this feature is only available when Vinifera is ru
### Command Line Options
-- Vinifera adds a number of command-line arguments allowing the user to skip the startup movies, or skip directly to a specific game mode and/or dialog.
+- Vinifera adds a number of command-line arguments.
+
+- `-SPAWN`
+Launch the game in spawner mode.
- `-NO_STARTUP_VIDEO`
Skips all startup movies.
diff --git a/docs/Whats-New.md b/docs/Whats-New.md
index 782fc0b1e..4c7c58a04 100644
--- a/docs/Whats-New.md
+++ b/docs/Whats-New.md
@@ -163,6 +163,7 @@ New:
- Implement customizable mouse cursors and actions (by CCHyper/tomsons26, ZivDero)
- Implement support for a Saved Games subdirectory (by ZivDero)
+- Implemented the multiplayer spawner (by ZivDero)
Vanilla fixes:
- Fix HouseType `Nod` having the `Prefix=B` and `Side=GDI` in vanilla `rules.ini` by setting them to `N` and `Nod`, respectively (by CCHyper/tomsons26)
diff --git a/src/cncnet/cncnet4/cncnet4.cpp b/src/cncnet/cncnet4/cncnet4.cpp
deleted file mode 100644
index 9c6e57842..000000000
--- a/src/cncnet/cncnet4/cncnet4.cpp
+++ /dev/null
@@ -1,413 +0,0 @@
-/*******************************************************************************
-/* O P E N S O U R C E -- V I N I F E R A **
-/*******************************************************************************
- *
- * @project Vinifera
- *
- * @file CNCNET4.CPP
- *
- * @author CCHyper (Based on work by Toni Spets)
- *
- * @brief CnCNet4 replacements for low level networking API.
- *
- * @license Vinifera 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, either version
- * 3 of the License, or (at your option) any later version.
- *
- * Vinifera 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, see .
- *
- ******************************************************************************/
-#include "cncnet4.h"
-#include "cncnet4_net.h"
-#include "cncnet4_globals.h"
-#include "rawfile.h"
-#include "ini.h"
-#include "debughandler.h"
-#include "asserthandler.h"
-
-#include
-#include
-#include
-#include
-#include
-
-
-/**
- * Initialises the CnCNet4 system.
- */
-bool __stdcall CnCNet4::Init()
-{
- RawFileClass file("SUN.INI");
- INIClass ini;
-
- /**
- * Load the CnCNet4 settings.
- */
- ini.Load(file);
- if (ini.Is_Present("CnCNet4")) {
- CnCNet4::IsEnabled = ini.Get_Bool("CnCNet4", "Enabled", CnCNet4::IsEnabled);
- ini.Get_String("CnCNet4", "Host", CnCNet4::Host, sizeof(CnCNet4::Host));
- CnCNet4::Peer2Peer = ini.Get_Bool("CnCNet4", "P2P", CnCNet4::Peer2Peer);
- CnCNet4::UseUDP = ini.Get_Bool("CnCNet4", "UDP", CnCNet4::UseUDP);
- CnCNet4::Port = ini.Get_Int("CnCNet4", "Port", CnCNet4::Port);
- }
-
- if (!CnCNet4::IsEnabled) {
-
- /**
- * Nothing went wrong, but the CnCNet4 interface is not enabled.
- */
- return true;
- }
-
- /**
- * Check to make sure the CnCNet4 DLL is not present in the directory.
- */
- if (RawFileClass("wsock32.dll").Is_Available()) {
- MessageBox(nullptr, "File conflict detected!\nPlease remove WSOCK32.DLL from the game directory!", "Vinifera", MB_OK|MB_ICONEXCLAMATION);
- return false;
- }
- if (RawFileClass("cncnet.dll").Is_Available()) {
- MessageBox(nullptr, "File conflict detected!\nPlease remove CNCNET.DLL from the game directory!", "Vinifera", MB_OK|MB_ICONEXCLAMATION);
- return false;
- }
-
- int s = net_init();
- net_opt_reuse();
-
- DEBUG_INFO("CnCNet4: Initialising...\n");
-
- if (CnCNet4::Port < 1024 || CnCNet4::Port > 65534) {
- CnCNet4::Port = 9001;
- }
-
- DEBUG_INFO("CnCNet4: Broadcasting to \"%s:%d\".\n", CnCNet4::Host, CnCNet4::Port);
-
- CnCNet4::IsDedicated = true;
-
- if (!net_address(&CnCNet4::Server, CnCNet4::Host, CnCNet4::Port)) {
- return false;
- }
-
- if (CnCNet4::Peer2Peer) {
-
- /**
- * Test P2P.
- */
- bool pass = false;
-
- fd_set rfds;
- struct timeval tv;
- struct sockaddr_in to;
- struct sockaddr_in from;
- int start = time(nullptr);
-
- net_write_int8(CMD_TESTP2P);
- net_write_int32(start);
-
- while (time(nullptr) < start + 5) {
-
- net_send_noflush(&to);
-
- FD_ZERO(&rfds);
- FD_SET(s, &rfds);
- tv.tv_sec = 1;
- tv.tv_usec = 0;
-
- if (select(s + 1, &rfds, NULL, NULL, &tv) > -1) {
- if (FD_ISSET(s, &rfds)) {
- net_recv(&from);
-
- if (net_read_int8() == CMD_TESTP2P && net_read_int32() == start) {
- pass = true;
- break;
- }
- }
- }
- }
-
- /**
- * Enable P2P if passed.
- */
- if (pass) {
- DEBUG_INFO("CnCNet4: Peer-to-peer test passed.\n");
- DEBUG_INFO("CnCNet4: Peer-to-peer is enabled.\n");
- net_bind("0.0.0.0", 8054);
-
- } else {
- DEBUG_WARNING("CnCNet4: Peer-to-peer test failed!\n");
- //return false;
- }
-
- }
-
- return true;
-}
-
-
-
-/**
- * Shutdown the CnCNet4 system.
- */
-void __stdcall CnCNet4::Shutdown()
-{
- net_free();
-}
-
-
-SOCKET __stdcall CnCNet4::socket(int af, int type, int protocol)
-{
-#ifndef NDEBUG
- DEV_DEBUG_INFO("CnCNet4: socket(af=%08X, type=%08X, protocol=%08X)\n", af, type, protocol);
-#endif
-
- if (af == AF_IPX) {
- return net_socket;
- }
-
- return ::socket(af, type, protocol);
-}
-
-
-int __stdcall CnCNet4::bind(SOCKET s, const struct sockaddr *name, int namelen)
-{
-#ifndef NDEBUG
- DEV_DEBUG_INFO("CnCNet4: bind(s=%d, name=%p, namelen=%d)\n", s, name, namelen);
-#endif
-
- if (s == net_socket) {
- return 0;
- }
-
- return ::bind(s, name, namelen);
-}
-
-
-int __stdcall CnCNet4::recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)
-{
-#ifndef NDEBUG
- //DEV_DEBUG_INFO("CnCNet4: recvfrom(s=%d, buf=%p, len=%d, flags=%08X, from=%p, fromlen=%p (%d))\n", s, buf, len, flags, from, fromlen, *fromlen);
-#endif
-
- if (s == net_socket) {
- int ret;
- struct sockaddr_in from_in;
-
- ret = net_recv(&from_in);
-
- if (ret > 0) {
-
- if (CnCNet4::IsDedicated) {
-
- if (from_in.sin_addr.s_addr == CnCNet4::Server.sin_addr.s_addr && from_in.sin_port == CnCNet4::Server.sin_port) {
- uint8_t cmd = net_read_int8();
-
- /**
- * Handle keepalive packets from server, very special case.
- */
- if (cmd == CMD_PING) {
- net_write_int8(CMD_PING);
- net_write_int32(net_read_int32());
- net_send(&from_in);
-
- /**
- * #FIXME: returning 0 means disconnected
- */
- return 0;
- }
-
- /**
- * P2p flag.
- */
- from_in.sin_zero[0] = cmd;
-
- from_in.sin_addr.s_addr = net_read_int32();
- from_in.sin_port = net_read_int16();
-
- } else if (CnCNet4::Peer2Peer) {
- /**
- * P2p flag for direct packets.
- */
- from_in.sin_zero[0] = 1;
-
- } else {
- /**
- * Discard p2p packets if not in p2p mode.
- * #FIXME: returning 0 means disconnected
- */
- return 0;
- }
-
- /**
- * Force p2p port.
- */
- if (from_in.sin_zero[0]) {
- from_in.sin_port = htons(8054);
- }
-
- in2ipx(&from_in, (struct sockaddr_ipx *)from);
-
- } else {
- in2ipx(&from_in, (struct sockaddr_ipx *)from);
- }
-
- ret = net_read_data((void *)buf, len);
- }
-
- return ret;
- }
-
- return ::recvfrom(s, buf, len, flags, from, fromlen);
-}
-
-
-int __stdcall CnCNet4::sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen)
-{
-#ifndef NDEBUG
- //DEV_DEBUG_INFO("CnCNet4: sendto(s=%d, buf=%p, len=%d, flags=%08X, to=%p, tolen=%d)\n", s, buf, len, flags, to, tolen);
-#endif
-
- if (to->sa_family == AF_IPX) {
- struct sockaddr_in to_in;
-
- if (CnCNet4::IsDedicated) {
-
- if (is_ipx_broadcast((struct sockaddr_ipx *)to)) {
- net_write_int8(CnCNet4::Peer2Peer ? 1 : 0);
- net_write_int32(0xFFFFFFFF);
- net_write_int16(0xFFFF);
- net_write_data((void *)buf, len);
- net_send(&CnCNet4::Server);
-
- } else {
-
- ipx2in((struct sockaddr_ipx *)to, &to_in);
-
- /**
- * Use p2p only if both clients are in p2p mode.
- */
- if (to_in.sin_zero[0] && CnCNet4::Peer2Peer) {
- net_write_data((void *)buf, len);
- net_send(&to_in);
-
- } else {
- net_write_int8(CnCNet4::Peer2Peer ? 1 : 0);
- net_write_int32(to_in.sin_addr.s_addr);
- net_write_int16(to_in.sin_port);
- net_write_data((void *)buf, len);
- net_send(&CnCNet4::Server);
- }
- }
-
- return len;
- }
-
- ipx2in((struct sockaddr_ipx *)to, &to_in);
- net_write_data((void *)buf, len);
-
- /**
- * Check if it's a broadcast.
- */
- if (is_ipx_broadcast((struct sockaddr_ipx *)to)) {
- net_send(&CnCNet4::Server);
- return len;
-
- } else {
- return net_send(&to_in);
- }
- }
-
- return ::sendto(s, buf, len, flags, to, tolen);
-}
-
-
-int __stdcall CnCNet4::getsockopt(SOCKET s, int level, int optname, char *optval, int *optlen)
-{
-#ifndef NDEBUG
- DEV_DEBUG_INFO("CnCNet4: getsockopt(s=%d, level=%08X, optname=%08X, optval=%p, optlen=%p (%d))\n", s, level, optname, optval, optlen, *optlen);
-#endif
-
- if (level == 0x3E8) {
- *optval = 1;
- *optlen = 1;
- return 0;
- }
-
- if (level == 0xFFFF) {
- *optval = 1;
- *optlen = 1;
- return 0;
- }
-
- return ::getsockopt(s, level, optname, optval, optlen);
-}
-
-
-int __stdcall CnCNet4::setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen)
-{
-#ifndef NDEBUG
- DEV_DEBUG_INFO("CnCNet4: setsockopt(s=%d, level=%08X, optname=%08X, optval=%p, optlen=%d)\n", s, level, optname, optval, optlen);
-#endif
-
- if (level == 0x3E8) {
- return 0;
- }
- if (level == 0xFFFF) {
- return 0;
- }
-
- return ::setsockopt(s, level, optname, optval, optlen);
-}
-
-
-int __stdcall CnCNet4::closesocket(SOCKET s)
-{
-#ifndef NDEBUG
- DEV_DEBUG_INFO("CnCNet4: closesocket(s=%d)\n", s);
-#endif
-
- if (s == net_socket) {
- if (CnCNet4::IsDedicated) {
- net_write_int8(CMD_DISCONNECT);
- net_send(&CnCNet4::Server);
- }
- return 0;
- }
-
- return ::closesocket(s);
-}
-
-
-int __stdcall CnCNet4::getsockname(SOCKET s, struct sockaddr *name, int *namelen)
-{
-#ifndef NDEBUG
- DEV_DEBUG_INFO("CnCNet4: getsockname(s=%d, name=%p, namelen=%p (%d)\n", s, name, namelen, *namelen);
-#endif
-
- if (s == net_socket) {
- struct sockaddr_in name_in;
- char hostname[256];
- struct hostent *he;
-
- gethostname(hostname, 256);
- he = ::gethostbyname(hostname);
-
- DEBUG_INFO("getsockname: local hostname: %s\n", hostname);
-
- if (he) {
- DEBUG_INFO("getsockname: local ip: %s\n", inet_ntoa(*(struct in_addr *)(he->h_addr_list[0])));
- name_in.sin_addr = *(struct in_addr *)(he->h_addr_list[0]);
- in2ipx(&name_in, (struct sockaddr_ipx *)name);
- }
- }
-
- return ::getsockname(s, name, namelen);;
-}
diff --git a/src/cncnet/cncnet4/cncnet4.h b/src/cncnet/cncnet4/cncnet4.h
deleted file mode 100644
index a88d95fea..000000000
--- a/src/cncnet/cncnet4/cncnet4.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*******************************************************************************
-/* O P E N S O U R C E -- V I N I F E R A **
-/*******************************************************************************
- *
- * @project Vinifera
- *
- * @file CNCNET4.H
- *
- * @author CCHyper
- *
- * @brief CnCNet4 replacements for low level networking API.
- *
- * @license Vinifera 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, either version
- * 3 of the License, or (at your option) any later version.
- *
- * Vinifera 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, see .
- *
- ******************************************************************************/
-#pragma once
-
-#include
-#include
-
-
-namespace CnCNet4 {
-
-bool __stdcall Init();
-void __stdcall Shutdown();
-
-int __stdcall bind(SOCKET s, const struct sockaddr *name, int namelen);
-SOCKET __stdcall socket(int af, int type, int protocol);
-int __stdcall recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen);
-int __stdcall sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen);
-int __stdcall getsockopt(SOCKET s, int level, int optname, char *optval, int *optlen);
-int __stdcall setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen);
-int __stdcall closesocket(SOCKET s);
-int __stdcall getsockname(SOCKET s, struct sockaddr *name, int *namelen);
-
-}; // namespace CnCNet4
diff --git a/src/cncnet/cncnet4/cncnet4_hooks.cpp b/src/cncnet/cncnet4/cncnet4_hooks.cpp
deleted file mode 100644
index b0a8e488b..000000000
--- a/src/cncnet/cncnet4/cncnet4_hooks.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/*******************************************************************************
-/* O P E N S O U R C E -- V I N I F E R A **
-/*******************************************************************************
- *
- * @project Vinifera
- *
- * @file CNCNET4_HOOKS.CPP
- *
- * @author CCHyper
- *
- * @brief Contains the hooks for the CnCNet4 system.
- *
- * @license Vinifera 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, either version
- * 3 of the License, or (at your option) any later version.
- *
- * Vinifera 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, see .
- *
- ******************************************************************************/
-#include "cncnet4_hooks.h"
-#include "cncnet4_globals.h"
-#include "cncnet4.h"
-#include "tibsun_globals.h"
-#include "session.h"
-#include "wspudp.h"
-#include "wspipx.h"
-#include "debughandler.h"
-
-#include "hooker.h"
-#include "hooker_macros.h"
-
-
-static int __stdcall bind_intercept(SOCKET s, const struct sockaddr *name, int namelen)
-{
- if (CnCNet4::IsEnabled) {
- return CnCNet4::bind(s, name, namelen);
- } else {
- return ::bind(s, name, namelen);
- }
-}
-
-static int __stdcall closesocket_intercept(SOCKET s)
-{
- if (CnCNet4::IsEnabled) {
- return CnCNet4::closesocket(s);
- } else {
- return ::closesocket(s);
- }
-}
-
-static int __stdcall getsockname_intercept(SOCKET s, struct sockaddr *name, int *namelen)
-{
- if (CnCNet4::IsEnabled) {
- return CnCNet4::getsockname(s, name, namelen);
- } else {
- return ::getsockname(s, name, namelen);
- }
-}
-
-static int __stdcall getsockopt_intercept(SOCKET s, int level, int optname, char *optval, int *optlen)
-{
- if (CnCNet4::IsEnabled) {
- return CnCNet4::getsockopt(s, level, optname, optval, optlen);
- } else {
- return ::getsockopt(s, level, optname, optval, optlen);
- }
-}
-
-static int __stdcall recvfrom_intercept(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)
-{
- if (CnCNet4::IsEnabled) {
- return CnCNet4::recvfrom(s, buf, len, flags, from, fromlen);
- } else {
- return ::recvfrom(s, buf, len, flags, from, fromlen);
- }
-}
-
-static int __stdcall sendto_intercept(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen)
-{
- if (CnCNet4::IsEnabled) {
- return CnCNet4::sendto(s, buf, len, flags, to, tolen);
- } else {
- return ::sendto(s, buf, len, flags, to, tolen);
- }
-}
-
-static int __stdcall setsockopt_intercept(SOCKET s, int level, int optname, const char *optval, int optlen)
-{
- if (CnCNet4::IsEnabled) {
- return CnCNet4::setsockopt(s, level, optname, optval, optlen);
- } else {
- return ::setsockopt(s, level, optname, optval, optlen);
- }
-}
-
-static SOCKET __stdcall socket_intercept(int af, int type, int protocol)
-{
- if (CnCNet4::IsEnabled) {
- return CnCNet4::socket(af, type, protocol);
- } else {
- return ::socket(af, type, protocol);
- }
-}
-
-
-/**
- * #issue-504
- *
- * Create the CnCNet4 UDP interface or standard UDP interface depending
- * on if the CnCNet4 system has been enabled.
- *
- * @author: CCHyper
- */
-static void CnCNet_Create_PacketTransport()
-{
- bool created = false;
-
- if (CnCNet4::IsEnabled && CnCNet4::UseUDP) {
- PacketTransport = new UDPInterfaceClass();
- if (PacketTransport) {
- DEBUG_INFO("UDP PacketTransport for CnCNet4.\n");
- }
-
- created = (PacketTransport != nullptr);
- }
-
- if (!created) {
- PacketTransport = new IPXInterfaceClass();
- if (PacketTransport) {
- DEBUG_INFO("IPX PacketTransport created.\n");
- }
- }
-
- if (!PacketTransport) {
- DEBUG_ERROR("Failed to create PacketTransport!\n");
- }
-}
-
-
-/**
- * #issue-504
- *
- * This patch replaces the call to the IPXInterfaceClass constructor when
- * setting up the PacketTransport for network multiplayer games with
- * conditional code that creates the UDPInterfaceClass when enabled.
- *
- * @author: CCHyper
- */
-DECLARE_PATCH(_Select_Game_Network_Create_PacketTransport_Patch)
-{
- Session.Type = GAME_IPX;
- Session.CommProtocol = COMM_PROTOCOL_MULTI_E_COMP;
-
- if (!PacketTransport) {
- CnCNet_Create_PacketTransport();
- }
-
- JMP(0x004E2698);
-}
-
-
-/**
- * Main function for patching the hooks.
- */
-void CnCNet4_Hooks()
-{
- Patch_Jump(0x006B4D54, &bind_intercept);
- Patch_Jump(0x006B4D4E, &closesocket_intercept);
- //Patch_Jump(0x, &getsockname_intercept);
- Patch_Jump(0x006B4D48, &getsockopt_intercept);
- Patch_Jump(0x006B4D66, &recvfrom_intercept);
- Patch_Jump(0x006B4D6C, &sendto_intercept);
- Patch_Jump(0x006B4D60, &setsockopt_intercept);
- Patch_Jump(0x006B4D5A, &socket_intercept);
-
- Patch_Jump(0x004E2656, &_Select_Game_Network_Create_PacketTransport_Patch);
-}
diff --git a/src/cncnet/cncnet4/cncnet4_net.cpp b/src/cncnet/cncnet4/cncnet4_net.cpp
deleted file mode 100644
index c7b2f664e..000000000
--- a/src/cncnet/cncnet4/cncnet4_net.cpp
+++ /dev/null
@@ -1,310 +0,0 @@
-/*******************************************************************************
-/* O P E N S O U R C E -- V I N I F E R A **
-/*******************************************************************************
- *
- * @project Vinifera
- *
- * @file CNCNET4_NET.CPP
- *
- * @author CCHyper (Based on work by Toni Spets)
- *
- * @brief Network utility functions.
- *
- * @license Vinifera 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, either version
- * 3 of the License, or (at your option) any later version.
- *
- * Vinifera 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, see .
- *
- ******************************************************************************/
-#include
-#include
-#include
-
-#include "cncnet4_net.h"
-
-#include "debughandler.h"
-#include "asserthandler.h"
-
-
-static struct sockaddr_in net_local;
-static uint8_t net_ibuf[NET_BUF_SIZE];
-static uint8_t net_obuf[NET_BUF_SIZE];
-static uint32_t net_ipos;
-static uint32_t net_ilen;
-static uint32_t net_opos;
-int net_socket = 0;
-
-
-void ipx2in(struct sockaddr_ipx *from, struct sockaddr_in *to)
-{
- to->sin_family = AF_INET;
- std::memcpy(&to->sin_addr.s_addr, from->sa_nodenum, 4);
- std::memcpy(&to->sin_port, from->sa_nodenum + 4, 2);
- to->sin_zero[0] = from->sa_netnum[1];
-}
-
-
-void in2ipx(struct sockaddr_in *from, struct sockaddr_ipx *to)
-{
- to->sa_family = AF_IPX;
- *(DWORD *)&to->sa_netnum = 1;
- to->sa_netnum[1] = from->sin_zero[0];
- std::memcpy(to->sa_nodenum, &from->sin_addr.s_addr, 4);
- std::memcpy(to->sa_nodenum + 4, &from->sin_port, 2);
-}
-
-
-bool is_ipx_broadcast(struct sockaddr_ipx *addr)
-{
- unsigned char ff[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
- if (std::memcmp(addr->sa_netnum, ff, 4) == 0 || std::memcmp(addr->sa_nodenum, ff, 6) == 0) {
- return true;
- } else {
- return false;
- }
-}
-
-
-int net_opt_reuse()
-{
- int yes = 1;
- ::setsockopt(net_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes));
- return yes;
-}
-
-
-int net_opt_broadcast()
-{
- int yes = 1;
- ::setsockopt(net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&yes, sizeof(yes));
- return yes;
-}
-
-
-int net_address(struct sockaddr_in *addr, const char *host, uint16_t port)
-{
- struct hostent *hent;
- hent = ::gethostbyname(host);
- if (!hent) {
- DEBUG_ERROR("CnCNet4: gethostbyname failed!\n");
- return 0;
- }
-
- net_address_ex(addr, *(int *)hent->h_addr_list[0], port);
- return 1;
-}
-
-
-void net_address_ex(struct sockaddr_in *addr, uint32_t ip, uint16_t port)
-{
- int size = sizeof(struct sockaddr_in);
- std::memset(addr, 0, size);
- addr->sin_family = AF_INET;
- addr->sin_addr.s_addr = ip;
- addr->sin_port = htons(port);
-}
-
-
-int net_init()
-{
- WSADATA wsaData;
-
- if (net_socket) {
- net_free();
- }
-
- WSAStartup(0x0101, &wsaData);
-
- net_socket = ::socket(AF_INET, SOCK_DGRAM, 0);
- net_ipos = 0;
- net_opos = 0;
- return net_socket;
-}
-
-
-void net_free()
-{
- ::closesocket(net_socket);
- WSACleanup();
- net_socket = 0;
-}
-
-
-int net_bind(const char *ip, int port)
-{
- if (!net_socket) {
- return 0;
- }
-
- net_address(&net_local, ip, port);
-
- return ::bind(net_socket, (struct sockaddr *)&net_local, sizeof(net_local));
-}
-
-
-uint32_t net_read_size()
-{
- return net_ilen - net_ipos;
-}
-
-
-int8_t net_read_int8()
-{
- int8_t tmp;
- if (net_ipos + 1 > net_ilen) {
- return 0;
- }
- std::memcpy(&tmp, net_ibuf + net_ipos, 1);
- net_ipos += 1;
- return tmp;
-}
-
-
-int16_t net_read_int16()
-{
- int16_t tmp;
- if (net_ipos + 2 > net_ilen) {
- return 0;
- }
- std::memcpy(&tmp, net_ibuf + net_ipos, 2);
- net_ipos += 2;
- return tmp;
-}
-
-
-int32_t net_read_int32()
-{
- int32_t tmp;
- if (net_ipos + 4 > net_ilen) {
- return 0;
- }
- std::memcpy(&tmp, net_ibuf + net_ipos, 4);
- net_ipos += 4;
- return tmp;
-}
-
-
-int net_read_data(void *ptr, size_t len)
-{
- if (net_ipos + len > net_ilen) {
- len = net_ilen - net_ipos;
- }
-
- std::memcpy(ptr, net_ibuf + net_ipos, len);
- net_ipos += len;
- return len;
-}
-
-
-int net_read_string(char *str, size_t len)
-{
- int i;
- for (i = net_ipos; i < NET_BUF_SIZE; i++) {
- if (net_ibuf[i] == '\0') {
- break;
- }
- }
-
- if (len > i - net_ipos) {
- len = i - net_ipos;
- }
-
- std::memcpy(str, net_ibuf + net_ipos, len);
- str[len] = '\0';
- net_ipos += len + 1;
- return 0;
-}
-
-
-int net_write_int8(int8_t d)
-{
- ASSERT(net_opos + 1 <= NET_BUF_SIZE);
- std::memcpy(net_obuf + net_opos, &d, 1);
- net_opos += 1;
- return 1;
-}
-
-
-int net_write_int16(int16_t d)
-{
- ASSERT(net_opos + 2 <= NET_BUF_SIZE);
- int16_t tmp = d;
- std::memcpy(net_obuf + net_opos, &tmp, 2);
- net_opos += 2;
- return 1;
-}
-
-
-int net_write_int32(int32_t d)
-{
- ASSERT(net_opos + 4 <= NET_BUF_SIZE);
- int32_t tmp = d;
- std::memcpy(net_obuf + net_opos, &tmp, 4);
- net_opos += 4;
- return 1;
-}
-
-
-int net_write_data(void *ptr, size_t len)
-{
- ASSERT(net_opos + len <= NET_BUF_SIZE);
- std::memcpy(net_obuf + net_opos, ptr, len);
- net_opos += len;
- return 1;
-}
-
-
-int net_write_string(char *str)
-{
- ASSERT(net_opos + strlen(str) + 1 <= NET_BUF_SIZE);
- std::memcpy(net_obuf + net_opos, str, strlen(str) + 1);
- net_opos += strlen(str) + 1;
- return 1;
-}
-
-
-int net_write_string_int32(int32_t d)
-{
- char str[32];
- std::snprintf(str, sizeof(str), "%d", d);
- return net_write_string(str);
-}
-
-
-int net_recv(struct sockaddr_in *src)
-{
- socklen_t l = sizeof(struct sockaddr_in);
- net_ipos = 0;
- net_ilen = ::recvfrom(net_socket, (char *)net_ibuf, NET_BUF_SIZE, 0, (struct sockaddr *)src, &l);
- return net_ilen;
-}
-
-
-int net_send(struct sockaddr_in *dst)
-{
- int ret = net_send_noflush(dst);
- net_send_discard();
- return ret;
-}
-
-
-int net_send_noflush(struct sockaddr_in *dst)
-{
- int ret = ::sendto(net_socket, (char *)net_obuf, net_opos, 0, (struct sockaddr *)dst, sizeof(struct sockaddr_in));
- return ret;
-}
-
-
-void net_send_discard()
-{
- net_opos = 0;
-}
diff --git a/src/cncnet/cncnet4/cncnet4_net.h b/src/cncnet/cncnet4/cncnet4_net.h
deleted file mode 100644
index 862de3391..000000000
--- a/src/cncnet/cncnet4/cncnet4_net.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*******************************************************************************
-/* O P E N S O U R C E -- V I N I F E R A **
-/*******************************************************************************
- *
- * @project Vinifera
- *
- * @file CNCNET4_NET.H
- *
- * @author CCHyper (Based on work by Toni Spets)
- *
- * @brief Network utility functions.
- *
- * @license Vinifera 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, either version
- * 3 of the License, or (at your option) any later version.
- *
- * Vinifera 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, see .
- *
- ******************************************************************************/
-#pragma once
-
-#include
-#include
-#include
-#include
-
-
-#define NET_BUF_SIZE 2048
-
-typedef int socklen_t;
-
-enum {
- CMD_TUNNEL,
- CMD_P2P,
- CMD_DISCONNECT,
- CMD_PING,
- CMD_QUERY,
- CMD_TESTP2P
-};
-
-
-extern int net_socket;
-
-void ipx2in(struct sockaddr_ipx *from, struct sockaddr_in *to);
-void in2ipx(struct sockaddr_in *from, struct sockaddr_ipx *to);
-bool is_ipx_broadcast(struct sockaddr_ipx *addr);
-
-int net_opt_reuse();
-int net_opt_broadcast();
-
-int net_address(struct sockaddr_in *addr, const char *host, uint16_t port);
-void net_address_ex(struct sockaddr_in *addr, uint32_t ip, uint16_t port);
-
-int net_init();
-void net_free();
-
-int net_bind(const char *ip, int port);
-
-uint32_t net_read_size();
-int8_t net_read_int8();
-int16_t net_read_int16();
-int32_t net_read_int32();
-int net_read_data(void *, size_t);
-int net_read_string(char *str, size_t len);
-
-int net_write_int8(int8_t);
-int net_write_int16(int16_t);
-int net_write_int32(int32_t);
-int net_write_data(void *, size_t);
-int net_write_string(char *str);
-int net_write_string_int32(int32_t);
-
-int net_recv(struct sockaddr_in *);
-int net_send(struct sockaddr_in *);
-int net_send_noflush(struct sockaddr_in *dst);
-void net_send_discard();
diff --git a/src/cncnet/cncnet5/cncnet5_hooks.cpp b/src/cncnet/cncnet5/cncnet5_hooks.cpp
deleted file mode 100644
index e4556a5d1..000000000
--- a/src/cncnet/cncnet5/cncnet5_hooks.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*******************************************************************************
-/* O P E N S O U R C E -- V I N I F E R A **
-/*******************************************************************************
- *
- * @project Vinifera
- *
- * @file CNCNET5_HOOKS.CPP
- *
- * @author CCHyper
- *
- * @brief Contains the hooks for implementing the CnCNet5 system.
- *
- * @license Vinifera 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, either version
- * 3 of the License, or (at your option) any later version.
- *
- * Vinifera 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, see .
- *
- ******************************************************************************/
-#include "cncnet5_hooks.h"
-#include "cncnet5_globals.h"
-#include "cncnet5_wspudp.h"
-#include "wsproto.h"
-#include "wspipx.h"
-#include "wspudp.h"
-#include "tibsun_globals.h"
-#include "session.h"
-#include "debughandler.h"
-#include "asserthandler.h"
-#include "hooker.h"
-#include "hooker_macros.h"
-
-
-/**
- * #issue-69
- *
- * Create the CnCNet5 UDP interface or standard UDP interface depending
- * on if the CnCNet5 system has been enabled.
- *
- * @author: CCHyper
- */
-static void Create_PacketTransport()
-{
- if (CnCNet5::IsActive && CnCNet5::TunnelInfo.Is_Valid()) {
- PacketTransport = new CnCNet5UDPInterfaceClass(
- CnCNet5::TunnelInfo.ID,
- CnCNet5::TunnelInfo.IP,
- CnCNet5::TunnelInfo.Port,
- CnCNet5::TunnelInfo.PortHack);
- if (!PacketTransport) {
- DEBUG_ERROR("Failed to create PacketTransport for CnCNet5!\n");
- }
-
- } else {
- PacketTransport = new UDPInterfaceClass();
- if (!PacketTransport) {
- DEBUG_ERROR("Failed to create PacketTransport!\n");
- }
- }
-}
-
-
-/**
- * #issue-69
- *
- * This patch replaces the call to the UDPInterfaceClass constructor when
- * setting up the PacketTransport for network multiplayer games with
- * conditional code that creates the CnCNet5 interface is enabled.
- *
- * @author: CCHyper
- */
-DECLARE_PATCH(_Select_Game_Create_PacketTransport_Patch)
-{
- Create_PacketTransport();
-
- Session.CommProtocol = COMM_PROTOCOL_SINGLE_NO_COMP;
-
- _asm { mov eax, [0x0074C8D8] } // PacketProtocol
- _asm { mov eax, [eax] }
-
- JMP_REG(edx, 0x004E2436);
-}
-
-
-/**
- * Main function for patching the hooks.
- */
-void CnCNet5_Hooks()
-{
- Patch_Jump(0x004E2406, &_Select_Game_Create_PacketTransport_Patch);
-}
diff --git a/src/extensions/building/buildingext_hooks.cpp b/src/extensions/building/buildingext_hooks.cpp
index bcab9f369..b53a7b353 100644
--- a/src/extensions/building/buildingext_hooks.cpp
+++ b/src/extensions/building/buildingext_hooks.cpp
@@ -37,6 +37,7 @@
#include "buildingtypeext.h"
#include "unit.h";
#include "unitext.h"
+#include "factory.h"
#include "technotype.h"
#include "technotypeext.h"
#include "aircraft.h"
@@ -67,6 +68,7 @@
#include "hooker.h"
#include "hooker_macros.h"
+#include "spawner.h"
/**
@@ -84,6 +86,7 @@ static class BuildingClassExt final : public BuildingClass
const InfantryTypeClass* _Crew_Type() const;
int _How_Many_Survivors() const;
int _Shape_Number() const;
+ void _Draw_Overlays(Point2D& coord, Rect& rect);
};
@@ -270,6 +273,118 @@ int BuildingClassExt::_Shape_Number() const
shapenum += largest;
}
}
+ }
+}
+
+
+/**
+ * Reimplements the BuildingClass::Draw_Overlays function.
+ *
+ * @author: ZivDero
+ */
+void BuildingClassExt::_Draw_Overlays(Point2D& coord, Rect& rect)
+{
+ if (BState != BSTATE_CONSTRUCTION)
+ {
+ /**
+ * Draw the repair animation.
+ */
+ if (IsRepairing)
+ {
+ if (!Map.Is_Shrouded(Center_Coord()) && !(Scen->SpecialFlags.IsFogOfWar || IsFogged) && Visual_Character() != VISUAL_HIDDEN)
+ {
+ Point2D xy = coord;
+ if (!IsPowerOn)
+ xy -= Point2D(5, 5);
+
+ int delay = Options.Normalize_Delay(14) / 4;
+ if (delay < 2)
+ delay = 2;
+
+ CC_Draw_Shape(LogicSurface, MouseDrawer, WrenchShape, 6 * (Frame % delay) / (delay - 1), &xy, &rect, SHAPE_ALPHA | SHAPE_WIN_REL | SHAPE_CENTER);
+ }
+ }
+
+ /**
+ * Draw the power off animation.
+ */
+ if (!IsPowerOn && House->Is_Player_Control())
+ {
+ if (!Map.Is_Shrouded(Center_Coord()) && !(Scen->SpecialFlags.IsFogOfWar || IsFogged))
+ {
+ Point2D xy = coord;
+ if (IsRepairing)
+ xy += Point2D(10, 10);
+
+ int delay = Options.Normalize_Delay(14) / 4;
+ if (delay < 2)
+ delay = 2;
+
+ CC_Draw_Shape(LogicSurface, MouseDrawer, PowerOffShape, 6 * (Frame % delay) / (delay - 1), &xy, &rect, SHAPE_ALPHA | SHAPE_WIN_REL | SHAPE_CENTER);
+ }
+ }
+
+ if (IsSelected)
+ {
+ /**
+ * Draw the primary factory pip.
+ */
+ if (House->Is_Ally(PlayerPtr) || SpiedBy & (1 << (PlayerPtr->Class->House)) || (Spawner::Active && Spawner::Get_Config()->Houses[PlayerPtr->Get_Heap_ID()].IsSpectator))
+ {
+ Point2D xy(coord.X - 10, coord.Y + 10);
+ entry_338(xy, coord, rect);
+ }
+
+ /**
+ * If this is a factory, and the player has spied its owner, draw the cameo of what it's currently producing.
+ */
+ if (SpiedBy & (1 << (PlayerPtr->Class->House)) || (Spawner::Active && Spawner::Get_Config()->Houses[PlayerPtr->Get_Heap_ID()].IsSpectator))
+ {
+ FactoryClass* factory = House->Is_Human_Control() ? House->Fetch_Factory(Class->ToBuild) : Factory;
+ if (factory != nullptr)
+ {
+ ObjectClass* obj = factory->Get_Object();
+ if (obj != nullptr)
+ {
+ /**
+ * #issue-487
+ *
+ * Adds support for PCX/PNG cameo icons.
+ *
+ * @author: CCHyper
+ */
+ const auto technotypeext = Extension::Fetch(obj->Techno_Type_Class());
+ if (technotypeext->CameoImageSurface)
+ {
+ /**
+ * Draw the cameo pcx image.
+ */
+ Rect pcxrect;
+ pcxrect.X = rect.X + coord.X;
+ pcxrect.Y = rect.Y + coord.Y;
+ pcxrect.Width = technotypeext->CameoImageSurface->Get_Width();
+ pcxrect.Height = technotypeext->CameoImageSurface->Get_Height();
+
+ SpriteCollection.Draw(pcxrect, *LogicSurface, *technotypeext->CameoImageSurface);
+ }
+ else
+ {
+ const ShapeFileStruct* shape = obj->Techno_Type_Class()->Get_Cameo_Data();
+
+ /**
+ * Draw the cameo shape.
+ *
+ * Original code used NormalDrawer, which is the old Red Alert shape
+ * drawer, so we need to use CameoDrawer here for the correct palette.
+ */
+ CC_Draw_Shape(LogicSurface, CameoDrawer, shape, 0, &coord, &rect, SHAPE_ALPHA | SHAPE_WIN_REL | SHAPE_CENTER);
+ }
+ }
+ }
+ }
+ }
+ }
+}
return shapenum;
}
@@ -771,62 +886,6 @@ DECLARE_PATCH(_BuildingClass_Explode_ShakeScreen_Division_BugFix_Patch)
}
-/**
- * #issue-72
- *
- * Fixes the bug where the wrong palette used to draw the cameo of the object
- * being produced above a enemy spied factory building.
- *
- * @author: CCHyper
- */
-DECLARE_PATCH(_BuildingClass_Draw_Spied_Cameo_Palette_Patch)
-{
- GET_REGISTER_STATIC(TechnoClass *, factory_obj, eax);
- GET_REGISTER_STATIC(Point2D *, pos_xy, edi);
- GET_REGISTER_STATIC(Rect *, window_rect, ebp);
- static TechnoTypeClass *technotype;
- static TechnoTypeClassExtension *technotypeext;
- static const ShapeFileStruct *cameo_shape;
- static Surface *pcx_image;
- static Rect pcxrect;
-
- technotype = factory_obj->Techno_Type_Class();
-
- /**
- * #issue-487
- *
- * Adds support for PCX/PNG cameo icons.
- *
- * @author: CCHyper
- */
- technotypeext = Extension::Fetch(technotype);
- if (technotypeext->CameoImageSurface) {
-
- /**
- * Draw the cameo pcx image.
- */
- pcxrect.X = window_rect->X + pos_xy->X;
- pcxrect.Y = window_rect->Y + pos_xy->Y;
- pcxrect.Width = technotypeext->CameoImageSurface->Get_Width();
- pcxrect.Height = technotypeext->CameoImageSurface->Get_Height();
-
- SpriteCollection.Draw(pcxrect, *LogicSurface, *technotypeext->CameoImageSurface);
-
- } else {
-
- cameo_shape = technotype->Get_Cameo_Data();
-
- /**
- * Draw the cameo shape.
- *
- * Original code used NormalDrawer, which is the old Red Alert shape
- * drawer, so we need to use CameoDrawer here for the correct palette.
- */
- CC_Draw_Shape(LogicSurface, CameoDrawer, cameo_shape, 0, pos_xy, window_rect, ShapeFlagsType(SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_ALPHA|SHAPE_NORMAL));
- }
-
- JMP(0x00428B13);
-}
/**
@@ -1060,7 +1119,6 @@ void BuildingClassExtension_Hooks()
*/
BuildingClassExtension_Init();
- Patch_Jump(0x00428AD3, &_BuildingClass_Draw_Spied_Cameo_Palette_Patch);
Patch_Jump(0x0042B250, &_BuildingClass_Explode_ShakeScreen_Division_BugFix_Patch);
Patch_Jump(0x00433BB5, &_BuildingClass_Mission_Open_Gate_Open_Sound_Patch);
Patch_Jump(0x00433C6F, &_BuildingClass_Mission_Open_Gate_Close_Sound_Patch);
@@ -1084,4 +1142,5 @@ void BuildingClassExtension_Hooks()
Patch_Jump(0x0042F799, &_BuildingClass_Captured_DontScore_Patch);
Patch_Jump(0x0042E5F5, &_BuildingClass_Grand_Opening_Assign_FreeUnit_LastDockedBuilding_Patch);
//Patch_Jump(0x00429220, &BuildingClassExt::_Shape_Number); // It's identical to vanilla, leaving it in in case it's ever needed
+ Patch_Jump(0x00428810, &BuildingClassExt::_Draw_Overlays);
}
diff --git a/src/extensions/extension.cpp b/src/extensions/extension.cpp
index 7925d5fdf..6684bfaa0 100644
--- a/src/extensions/extension.cpp
+++ b/src/extensions/extension.cpp
@@ -139,6 +139,7 @@
#include "warheadtypeext.h"
#include "waveext.h"
#include "weapontypeext.h"
+#include "viniferaevent.h"
#include "rulesext.h"
#include "scenarioext.h"
@@ -1144,8 +1145,8 @@ static bool Print_Event_List(FILE *fp, QueueClass &list)
if (ev) {
char ev_byte_format[4];
Wstring ev_data_buffer;
- int ev_size = EventClass::Event_Length(ev->Type);
- const char *ev_name = EventClass::Event_Name(ev->Type);
+ int ev_size = ViniferaEventClass::Event_Length(ev->Type);
+ const char *ev_name = ViniferaEventClass::Event_Name(ev->Type);
for (int i = 0; i < ev_size; ++i) {
std::snprintf(ev_byte_format, sizeof(ev_byte_format), "%02X", (unsigned char)ev->Data.Array.Byte[i]); // We use this union member so we can do array access.
ev_data_buffer += ev_byte_format;
diff --git a/src/extensions/extension_hooks.cpp b/src/extensions/extension_hooks.cpp
index 361e2ff06..24bc71704 100644
--- a/src/extensions/extension_hooks.cpp
+++ b/src/extensions/extension_hooks.cpp
@@ -153,6 +153,7 @@
#include "hooker.h"
#include "hooker_macros.h"
#include "spawnmanager_hooks.h"
+#include "viniferaevent_hooks.h"
void Extension_Hooks()
@@ -282,6 +283,7 @@ void Extension_Hooks()
MultiScoreExtension_Hooks();
ScoreClassExtension_Hooks();
MultiMissionExtension_Hooks();
+ ViniferaEvent_Hooks();
/**
* Dialogs and associated code.
diff --git a/src/extensions/init/initext_hooks.cpp b/src/extensions/init/initext_hooks.cpp
index 20aa9922f..9a2791e08 100644
--- a/src/extensions/init/initext_hooks.cpp
+++ b/src/extensions/init/initext_hooks.cpp
@@ -1029,6 +1029,6 @@ void GameInit_Hooks()
/**
* TS Client file structure assumes Firestorm is always installed and enabled.
*/
- //Patch_Jump(0x00407050, &Vinifera_Detect_Addons);
+ Patch_Jump(0x00407050, &Vinifera_Detect_Addons);
#endif
}
diff --git a/src/extensions/scenario/scenarioext.cpp b/src/extensions/scenario/scenarioext.cpp
index 9553313e7..3607ccb84 100644
--- a/src/extensions/scenario/scenarioext.cpp
+++ b/src/extensions/scenario/scenarioext.cpp
@@ -47,6 +47,7 @@
#include "vinifera_saveload.h"
#include "asserthandler.h"
#include "debughandler.h"
+#include "spawner.h"
/**
@@ -652,8 +653,8 @@ void ScenarioClassExtension::Assign_Houses()
*/
housep = new HouseClass(HouseTypes[node.Player.House]);
- std::memset((char *)housep->IniName, 0, MPLAYER_NAME_MAX);
- std::strncpy((char *)housep->IniName, node.Name, MPLAYER_NAME_MAX-1);
+ std::memset(housep->IniName, 0, MPLAYER_NAME_MAX);
+ std::strncpy(housep->IniName, node.Name, MPLAYER_NAME_MAX-1);
/**
* Set the house's IsHuman, Credits, ActLike, and RemapTable.
@@ -694,52 +695,60 @@ void ScenarioClassExtension::Assign_Houses()
*/
for (int i = Session.Players.Count(); i < Session.Players.Count() + Session.Options.AIPlayers; ++i) {
+ if (!Spawner::Active)
+ {
#if 0
- if (Percent_Chance(50)) {
- pref_house = HOUSE_GDI;
- } else {
- pref_house = HOUSE_NOD;
- }
+ if (Percent_Chance(50)) {
+ pref_house = HOUSE_GDI;
+ }
+ else {
+ pref_house = HOUSE_NOD;
+ }
#endif
- /**
- * #issue-7
- *
- * Replaces code from above.
- *
- * Fixes a limitation where the AI would only be able to choose
- * between the houses GDI (0) and NOD (1). Now, all houses that
- * have "IsMultiplay" true will be considered for sellection.
- */
- while (true) {
- pref_house = (HousesType)Random_Pick(0, HouseTypes.Count()-1);
- if (HouseTypes[pref_house]->IsMultiplay) {
- break;
+ /**
+ * #issue-7
+ *
+ * Replaces code from above.
+ *
+ * Fixes a limitation where the AI would only be able to choose
+ * between the houses GDI (0) and NOD (1). Now, all houses that
+ * have "IsMultiplay" true will be considered for sellection.
+ */
+ while (true) {
+ pref_house = (HousesType)Random_Pick(0, HouseTypes.Count() - 1);
+ if (HouseTypes[pref_house]->IsMultiplay) {
+ break;
+ }
}
- }
- /**
- * Pick a color for this house; keep looping until we find one.
- */
- while (true) {
- color = Random_Pick(0, (MAX_PLAYERS-1));
- if (color_used[color] == false) {
- break;
+ /**
+ * Pick a color for this house; keep looping until we find one.
+ */
+ while (true) {
+ color = Random_Pick(0, (MAX_PLAYERS - 1));
+ if (color_used[color] == false) {
+ break;
+ }
}
+ color_used[color] = true;
+ }
+ else
+ {
+ color = Spawner::Get_Config()->Players[i].Color;
+ pref_house = static_cast(Spawner::Get_Config()->Players[i].House);
}
- color_used[color] = true;
housep = new HouseClass(HouseTypes[pref_house]);
/**
- * Set the house's IsHuman, Credits, ActLike, and RemapTable.
+ * Set the house's IsHuman, Credits, ActLike, and RemapColor.
*/
housep->IsHuman = false;
- //housep->IsStarted = true;
housep->Control.TechLevel = BuildLevel;
- housep->Init_Data((PlayerColorType)color, pref_house, Session.Options.Credits);
- housep->RemapColor = Session.Player_Color_To_Scheme_Color((PlayerColorType)color);
+ housep->Init_Data(static_cast(color), pref_house, Session.Options.Credits);
+ housep->RemapColor = Session.Player_Color_To_Scheme_Color(static_cast(color));
housep->Init_Remap_Color();
std::strcpy(housep->IniName, Text_String(TXT_COMPUTER));
@@ -751,7 +760,7 @@ void ScenarioClassExtension::Assign_Houses()
DiffType difficulty = Scen->CDifficulty;
if (Session.Players.Count() > 1 && Rule->IsCompEasyBonus && difficulty > DIFF_EASY) {
- difficulty = (DiffType)(difficulty - 1);
+ difficulty = static_cast(difficulty - 1);
}
housep->Assign_Handicap(difficulty);
@@ -819,6 +828,69 @@ void ScenarioClassExtension::Assign_Houses()
housep->Init_Remap_Color();
}
+ if (Spawner::Active)
+ {
+ const int house_count = std::min(Houses.Count(), (int)std::size(Spawner::Get_Config()->Houses));
+ for (int i = 0; i < house_count; i++)
+ {
+ housep = Houses[i];
+
+ if (housep->Class->IsMultiplayPassive)
+ continue;
+
+ const auto house_config = &Spawner::Get_Config()->Houses[i];
+
+ // Set Alliances
+ for (char j = 0; j < (char)std::size(house_config->Alliances); ++j)
+ {
+ const int ally_index = house_config->Alliances[j];
+ if (ally_index != -1)
+ housep->Allies &= 1 << ally_index;
+ }
+
+ constexpr char* AINamesByDifficultyArray[5] = {
+ "Hard AI",
+ "Medium AI",
+ "Easy AI"//,
+ //"Brutal AI",
+ //"Ultimate AI"
+ };
+
+ // Set Handicap and Names for AI
+ {
+ const auto player_config = &Spawner::Get_Config()->Players[i];
+
+ if (player_config->Difficulty >= 0 && player_config->Difficulty < std::size(AINamesByDifficultyArray))
+ {
+ housep->Assign_Handicap(static_cast(player_config->Difficulty));
+ if (Spawner::Get_Config()->AINamesByDifficulty && !housep->IsHuman)
+ {
+ std::strcpy(housep->IniName, AINamesByDifficultyArray[player_config->Difficulty]);
+ }
+ }
+ }
+
+ // Set Spectators
+ enum
+ {
+ SPAWN_OBSERVER = -1,
+ SPAWN_OBSERVER_ALT = 90
+ };
+
+ const int spawn_loc = house_config->SpawnLocation;
+ const bool is_spectator = housep->IsHuman &&
+ (house_config->IsSpectator
+ || spawn_loc == SPAWN_OBSERVER
+ || spawn_loc == SPAWN_OBSERVER_ALT);
+
+ // Spectators are considered defeated
+ if (is_spectator)
+ {
+ housep->IsDefeated = true;
+ }
+ }
+ }
+
DEBUG_INFO("Assign_Houses(exit)\n");
}
@@ -1179,11 +1251,18 @@ static DynamicVectorClass Build_Starting_Waypoint_List(bool official)
* if there are 4 or fewer players. Unofficial maps will pick from all the
* available waypoints.
*/
- int look_for = std::max(min_waypts, Session.Players.Count()+Session.Options.AIPlayers);
+ int look_for = std::max(min_waypts, Session.Players.Count() + Session.Options.AIPlayers);
if (!official) {
look_for = MAX_PLAYERS;
}
+ if (Spawner::Active) {
+ for (int i = 0; i < Session.Players.Count() + Session.Options.AIPlayers; i++) {
+ if (Spawner::Get_Config()->Houses[i].IsSpectator)
+ look_for--;
+ }
+ }
+
for (int waycount = 0; waycount < look_for; ++waycount) {
if (Scen->Is_Valid_Waypoint(waycount)) {
Cell waycell = Scen->Get_Waypoint_Location(waycount);
@@ -1303,6 +1382,11 @@ void ScenarioClassExtension::Create_Units(bool official)
continue;
}
+ if (Spawner::Active && hptr->IsDefeated) {
+ DEV_DEBUG_INFO("House %d is a spectator, skipping.\n", house);
+ continue;
+ }
+
DynamicVectorClass available_infantry;
DynamicVectorClass available_units;
@@ -1372,66 +1456,97 @@ void ScenarioClassExtension::Create_Units(bool official)
}
}
- /**
- * Pick the starting location for this house. The first house just picks
- * one of the valid locations at random. The other houses pick the furthest
- * waypoint from the existing houses.
- */
- if (numtaken == 0) {
- int pick = Random_Pick(0, waypts.Count()-1);
- centroid = waypts[pick];
- taken[pick] = true;
- numtaken++;
-
- } else {
+ bool pick_random = true;
+ if (Spawner::Active) {
+ enum {
+ SPAWN_RANDOM = -2
+ };
+
+ int chosen_spawn = Spawner::Get_Config()->Houses[hptr->Get_Heap_ID()].SpawnLocation;
+
+ if (chosen_spawn != SPAWN_RANDOM) {
+ chosen_spawn = std::clamp(chosen_spawn, 0, 7);
+ if (!taken[chosen_spawn]) {
+ centroid = waypts[chosen_spawn];
+ taken[chosen_spawn] = true;
+ pick_random = false;
+ numtaken++;
+ }
+ }
+ }
- /**
- * Set all waypoints to have a score of zero in preparation for giving
- * a distance score to all waypoints.
- */
- int score[MAX_STORED_WAYPOINTS];
- std::memset(score, '\0', sizeof(score));
+
+ if (pick_random) {
/**
- * Scan through all waypoints and give a score as a value of the sum
- * of the distances from this waypoint to all taken waypoints.
+ * Pick the starting location for this house. The first house just picks
+ * one of the valid locations at random. The other houses pick the furthest
+ * waypoint from the existing houses.
*/
- for (int index = 0; index < waypts.Count(); index++) {
+ if (numtaken == 0) {
+ int pick = Random_Pick(0, waypts.Count() - 1);
+ centroid = waypts[pick];
+ taken[pick] = true;
+ numtaken++;
+
+ if (Spawner::Active) {
+ Spawner::Get_Config()->Houses[hptr->Class->ID].SpawnLocation = pick;
+ }
+
+ }
+ else {
+
+ /**
+ * Set all waypoints to have a score of zero in preparation for giving
+ * a distance score to all waypoints.
+ */
+ int score[MAX_STORED_WAYPOINTS];
+ std::memset(score, '\0', sizeof(score));
/**
- * If this waypoint has not already been taken, then accumulate the
- * sum of the distance between this waypoint and all other taken
- * waypoints.
+ * Scan through all waypoints and give a score as a value of the sum
+ * of the distances from this waypoint to all taken waypoints.
*/
- if (!taken[index]) {
- for (int trypoint = 0; trypoint < waypts.Count(); trypoint++) {
+ for (int index = 0; index < waypts.Count(); index++) {
- if (taken[trypoint]) {
- score[index] += Distance(waypts[index], waypts[trypoint]);
+ /**
+ * If this waypoint has not already been taken, then accumulate the
+ * sum of the distance between this waypoint and all other taken
+ * waypoints.
+ */
+ if (!taken[index]) {
+ for (int trypoint = 0; trypoint < waypts.Count(); trypoint++) {
+
+ if (taken[trypoint]) {
+ score[index] += Distance(waypts[index], waypts[trypoint]);
+ }
}
}
}
- }
- /**
- * Now find the waypoint with the largest score. This waypoint is the one
- * that is furthest from all other taken waypoints.
- */
- int best = 0;
- int bestvalue = 0;
- for (int searchindex = 0; searchindex < waypts.Count(); searchindex++) {
- if (score[searchindex] > bestvalue || bestvalue == 0) {
- bestvalue = score[searchindex];
- best = searchindex;
+ /**
+ * Now find the waypoint with the largest score. This waypoint is the one
+ * that is furthest from all other taken waypoints.
+ */
+ int best = 0;
+ int bestvalue = 0;
+ for (int searchindex = 0; searchindex < waypts.Count(); searchindex++) {
+ if (score[searchindex] > bestvalue || bestvalue == 0) {
+ bestvalue = score[searchindex];
+ best = searchindex;
+ }
}
- }
- /**
- * Assign this best position to the house.
- */
- centroid = waypts[best];
- taken[best] = true;
- numtaken++;
+ /**
+ * Assign this best position to the house.
+ */
+ centroid = waypts[best];
+ taken[best] = true;
+ numtaken++;
+
+ if (Spawner::Active)
+ Spawner::Get_Config()->Houses[hptr->Class->ID].SpawnLocation = best;
+ }
}
/**
diff --git a/src/extensions/scenario/scenarioext_hooks.cpp b/src/extensions/scenario/scenarioext_hooks.cpp
index 16dc762ac..f6daa8685 100644
--- a/src/extensions/scenario/scenarioext_hooks.cpp
+++ b/src/extensions/scenario/scenarioext_hooks.cpp
@@ -311,11 +311,6 @@ void ScenarioClassExtension_Hooks()
*/
ScenarioClassExtension_Init();
- /**
- * For compatibility with the TS Client we need to remove
- * these two reimplementations as they conflict with the spawner.
- */
-#if !defined(TS_CLIENT)
/**
* Hooks in the new Assign_Houses() function.
*
@@ -331,7 +326,6 @@ void ScenarioClassExtension_Hooks()
* @author: CCHyper
*/
Patch_Call(0x005DD320, &ScenarioClassExtension::Create_Units);
-#endif
Patch_Jump(0x005DC9D4, &_Do_Win_Skip_MPlayer_Score_Screen_Patch);
Patch_Jump(0x005DCD92, &_Do_Lose_Skip_MPlayer_Score_Screen_Patch);
diff --git a/src/hooker/setup_hooks.cpp b/src/hooker/setup_hooks.cpp
index 2851cd6ec..81a650626 100644
--- a/src/hooker/setup_hooks.cpp
+++ b/src/hooker/setup_hooks.cpp
@@ -36,8 +36,6 @@
#include "vinifera_hooks.h"
#include "newswizzle_hooks.h"
#include "extension_hooks.h"
-#include "cncnet4_hooks.h"
-#include "cncnet5_hooks.h"
#include "sidebarext_hooks.h"
@@ -50,9 +48,6 @@ void Setup_Hooks()
Vinifera_Hooks();
NewSwizzle_Hooks();
Extension_Hooks();
-
- CnCNet4_Hooks();
- CnCNet5_Hooks();
}
/**
diff --git a/src/new/viniferaevent/viniferaevent.cpp b/src/new/viniferaevent/viniferaevent.cpp
new file mode 100644
index 000000000..4f9f3e3dd
--- /dev/null
+++ b/src/new/viniferaevent/viniferaevent.cpp
@@ -0,0 +1,108 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file VINIFERAEVENT.CPP
+ *
+ * @author ZivDero, Belonit
+ *
+ * @brief Class that mimics vanilla EventClass to allow the creation
+ * of new events in Vinifera.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "viniferaevent.h"
+
+#include "asserthandler.h"
+#include "protocolzero.h"
+
+/**
+ * Arrays with new event lengths and names.
+ */
+unsigned char ViniferaEventLength[VEVENT_COUNT - EVENT_COUNT]
+{
+ sizeof(ViniferaEventClass::Data.ResponseTime2)
+};
+
+const char* ViniferaEventNames[VEVENT_COUNT - EVENT_COUNT]
+{
+ "RESPONSE_TIME_2"
+};
+
+
+/**
+ * Execute our new event.
+ *
+ * @author: ZivDero
+ */
+void ViniferaEventClass::Execute()
+{
+ switch (Type)
+ {
+ case VEVENT_RESPONSE_TIME_2:
+ ProtocolZero::Handle_Response_Time(this);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/**
+ * Get the length of this event.
+ *
+ * @author: ZivDero
+ */
+unsigned char ViniferaEventClass::Event_Length(ViniferaEventType type)
+{
+ ASSERT(type >= 0 && type < VEVENT_COUNT);
+
+ if (type < EVENT_COUNT)
+ return EventClass::Event_Length(static_cast(type));
+
+ return ViniferaEventLength[type - EVENT_COUNT];
+}
+
+
+/**
+ * Get the name of this event.
+ *
+ * @author: ZivDero
+ */
+const char* ViniferaEventClass::Event_Name(ViniferaEventType type)
+{
+ ASSERT(type >= 0 && type < VEVENT_COUNT);
+
+ if (type < EVENT_COUNT)
+ return EventClass::Event_Name(static_cast(type));
+
+ return ViniferaEventNames[type - EVENT_COUNT];
+}
+
+
+/**
+ * Check if this event was added by Vinifera.
+ *
+ * @author: ZivDero
+ */
+bool ViniferaEventClass::Is_Vinifera_Event(ViniferaEventType type)
+{
+ return (type >= VEVENT_FIRST && type < VEVENT_COUNT);
+}
diff --git a/src/new/viniferaevent/viniferaevent.h b/src/new/viniferaevent/viniferaevent.h
new file mode 100644
index 000000000..8a5fdbd10
--- /dev/null
+++ b/src/new/viniferaevent/viniferaevent.h
@@ -0,0 +1,88 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file VINIFERAEVENT.H
+ *
+ * @author ZivDero, Belonit
+ *
+ * @brief Class that mimics vanilla EventClass to allow the creation
+ * of new events in Vinifera.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+#pragma once
+
+#include
+#include
+
+#include "event.h"
+#include "latencylevel.h"
+#include "tibsun_defines.h"
+
+enum ViniferaEventType : unsigned char
+{
+ VEVENT_RESPONSE_TIME_2 = EVENT_COUNT, // Start after the last vanilla event
+
+ VEVENT_COUNT,
+ VEVENT_FIRST = VEVENT_RESPONSE_TIME_2
+};
+
+
+extern unsigned char ViniferaEventLength[VEVENT_COUNT - EVENT_COUNT];
+extern const char* ViniferaEventNames[VEVENT_COUNT - EVENT_COUNT];
+
+
+class ViniferaEventClass
+{
+#pragma pack(push, 1)
+public:
+ ViniferaEventType Type;
+ unsigned Frame;
+ bool IsExecuted;
+ int ID;
+
+ union
+ {
+ char DataBuffer[36];
+
+ struct ResponseTime2
+ {
+ unsigned char MaxAhead;
+ LatencyLevelEnum LatencyLevel;
+ } ResponseTime2;
+
+ } Data;
+
+ void Execute();
+ EventClass& As_Event() { return *reinterpret_cast(this); }
+
+ static unsigned char Event_Length(ViniferaEventType type);
+ static unsigned char Event_Length(EventType type) { return Event_Length(static_cast(type)); }
+
+ static const char* Event_Name(ViniferaEventType type);
+ static const char* Event_Name(EventType type) { return Event_Name(static_cast(type)); }
+
+ static bool Is_Vinifera_Event(ViniferaEventType type);
+#pragma pack(pop)
+};
+
+
+static_assert(sizeof(ViniferaEventClass) == sizeof(EventClass), "ViniferaEventClass doesn't match EventClass in size!");
+static_assert(sizeof(ViniferaEventClass::Data) == sizeof(EventClass::Data), "ViniferaEventClass::Data doesn't match EventClass::Data in size!");
+static_assert(offsetof(ViniferaEventClass, Data) == offsetof(EventClass, Data), "ViniferaEventClass Data is misplaced!");
diff --git a/src/new/viniferaevent/viniferaevent_hooks.cpp b/src/new/viniferaevent/viniferaevent_hooks.cpp
new file mode 100644
index 000000000..7f4ddd8ca
--- /dev/null
+++ b/src/new/viniferaevent/viniferaevent_hooks.cpp
@@ -0,0 +1,289 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file VINIFERAEVENT_HOOKS.H
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for the Vinifera event class.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "viniferaevent_hooks.h"
+
+#include "hooker.h"
+#include "hooker_macros.h"
+#include "viniferaevent.h"
+
+
+/**
+ * Patch EventClass::Execute to execute our new events.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_EventClass_Execute_ViniferaEvent)
+{
+ GET_REGISTER_STATIC(ViniferaEventClass*, vevent, esi);
+ static EventType eventtype;
+ static int id;
+
+ _asm pushad
+
+ if (ViniferaEventClass::Is_Vinifera_Event(vevent->Type))
+ {
+ vevent->Execute();
+ _asm popad
+ JMP(0x00495110); // return from function
+ }
+
+ eventtype = static_cast(vevent->Type);
+ id = vevent->ID;
+
+ _asm popad
+
+ // Stolen instructions
+ _asm mov al, eventtype
+ _asm mov edi, id
+ JMP_REG(ebx, 0x00494299);
+}
+
+
+/**
+ * Patch event length in Add_Compressed_Events.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_Add_Compressed_Events_ViniferaEvent_Length)
+{
+ GET_REGISTER_STATIC(unsigned char, eventtype, cl);
+ static unsigned char eventlength;
+
+ _asm pushad
+
+ if (ViniferaEventClass::Is_Vinifera_Event(static_cast(eventtype)))
+ {
+ eventlength = ViniferaEventClass::Event_Length(static_cast(eventtype));
+ }
+ else
+ {
+ eventlength = EventClass::Event_Length(static_cast(eventtype));
+ }
+
+ if (eventtype == EVENT_ADDPLAYER)
+ {
+ _asm popad
+ _asm mov bl, eventlength
+ JMP_REG(esi, 0x005B45EA);
+ }
+ else
+ {
+ _asm popad
+ _asm mov bl, eventlength
+ JMP_REG(esi, 0x005B45F3);
+ }
+}
+
+
+/**
+ * Extract_Compressed_Events -- extracts events from a packet.
+ *
+ * @author: 11/21/1995 DRD - Created.
+ * ZivDero - Adjustments for Tiberian Sun.
+ */
+static int Vinifera_Extract_Compressed_Events(void* buf, int bufsize)
+{
+ int pos = 0; // current buffer parsing position
+ int leftover = bufsize; // # bytes left to process
+ EventClass* event; // event ptr for parsing buffer
+ int count = 0; // # events processed
+ int datasize = 0; // size of data to copy
+ EventClass eventdata; // stores Frame, ID, etc
+ unsigned char numunits = 0; // # units stored in compressed MegaMissions
+
+ /**
+ * Clear work event structure.
+ */
+ std::memset(&eventdata, 0, sizeof(EventClass));
+
+ /**
+ * Assume the first event is a FRAMEINFO event
+ * Init 'datasize' to the amount of data to copy, minus the EventType value
+ * For the 1st packet only, this will include all info before the Data
+ * union, plus the size of the FrameInfo structure, minus the EventType size.
+ */
+ datasize = (offsetof(EventClass, Data) + sizeof(EventClass::Data.FrameInfo)) - sizeof(EventType);
+ event = reinterpret_cast(static_cast(buf) + pos);
+
+ while (leftover >= datasize + (int)sizeof(EventType))
+ {
+ /**
+ * Add event to the DoList, only if it's not a FRAMESYNC
+ * (but FRAMEINFO's do get added.)
+ */
+ if (event->Type != EVENT_FRAMESYNC)
+ {
+ /**
+ * Initialize the common data from the FRAMEINFO event.
+ * keeping IsExecuted 0
+ */
+ if (event->Type == EVENT_FRAMEINFO)
+ {
+ eventdata.Frame = event->Frame;
+ eventdata.ID = event->ID;
+
+ /**
+ * Adjust position past the common data.
+ */
+ pos += offsetof(EventClass, Data) - sizeof(EventType);
+ leftover -= offsetof(EventClass, Data) - sizeof(EventType);
+ }
+
+ /**
+ * If MEGAMISSION event get the number of units (events to generate).
+ */
+ else if (event->Type == EVENT_MEGAMISSION)
+ {
+ numunits = *(static_cast(buf) + pos + sizeof(eventdata.Type));
+ pos += sizeof(numunits);
+ leftover -= sizeof(numunits);
+ }
+
+ /**
+ * Clear the union data portion of the event.
+ */
+ memset(&eventdata.Data, 0, sizeof(eventdata.Data));
+ eventdata.Type = event->Type;
+ datasize = ViniferaEventClass::Event_Length(eventdata.Type);
+
+ switch (eventdata.Type)
+ {
+ case EVENT_RESPONSE_TIME:
+ memcpy(&eventdata.Data.FrameInfo.Delay, static_cast(buf) + pos + sizeof(EventType), datasize);
+ break;
+
+ case EVENT_ADDPLAYER:
+ memcpy(&eventdata.Data.Variable.Size, static_cast(buf) + pos + sizeof(EventType), datasize);
+
+ eventdata.Data.Variable.Pointer = new char[eventdata.Data.Variable.Size];
+ memcpy(eventdata.Data.Variable.Pointer, static_cast(buf) + pos + sizeof(EventType) + datasize, eventdata.Data.Variable.Size);
+
+ pos += eventdata.Data.Variable.Size;
+ leftover -= eventdata.Data.Variable.Size;
+
+ break;
+
+ case EVENT_MEGAMISSION:
+ memcpy(&eventdata.Data.MegaMission, static_cast(buf) + pos + sizeof(EventType), datasize);
+
+ if (numunits > 1)
+ {
+ pos += datasize + sizeof(EventType);
+ leftover -= datasize + sizeof(EventType);
+ datasize = sizeof(eventdata.Data.MegaMission.Whom);
+
+ while (numunits)
+ {
+ if (!DoList.Add(eventdata))
+ return -1;
+
+ /**
+ * Keep count of how many events we add to the queue.
+ */
+ count++;
+ numunits--;
+ memcpy(&eventdata.Data.MegaMission.Whom, static_cast(buf) + pos, datasize);
+
+ /**
+ * If one unit left fall thru to normal code.
+ */
+ if (numunits == 1)
+ {
+ datasize -= sizeof(EventType);
+ break;
+ }
+ else
+ {
+ pos += datasize;
+ leftover -= datasize;
+ }
+ }
+ }
+ break;
+
+ default:
+ memcpy(&eventdata.Data, static_cast(buf) + pos + sizeof(EventType), datasize);
+ break;
+ }
+
+ if (!DoList.Add(eventdata))
+ {
+ if (eventdata.Type == EVENT_ADDPLAYER)
+ delete[] eventdata.Data.Variable.Pointer;
+
+ return -1;
+ }
+
+ /**
+ * Keep count of how many events we add to the queue.
+ */
+ count++;
+
+ pos += datasize + sizeof(EventType);
+ leftover -= datasize + sizeof(EventType);
+
+ if (leftover)
+ {
+ event = reinterpret_cast(static_cast(buf) + pos);
+ datasize = ViniferaEventClass::Event_Length(event->Type);
+ if (event->Type == EVENT_MEGAMISSION)
+ datasize += sizeof(numunits);
+ }
+ }
+ /**
+ * FRAMESYNC event: This >should< be the only event in the buffer,
+ * and it will be uncompressed.
+ */
+ else
+ {
+ pos += datasize + sizeof(EventType);
+ leftover -= datasize + sizeof(EventType);
+ event = reinterpret_cast(static_cast(buf) + pos);
+
+ /**
+ * Size of FRAMESYNC event - EventType size.
+ */
+ datasize = offsetof(EventClass, Data) + sizeof(EventClass::Data.FrameInfo) - sizeof(EventType);
+ }
+ }
+
+ return count;
+
+}
+
+
+/**
+ * Main function for patching the hooks.
+ */
+void ViniferaEvent_Hooks()
+{
+ Patch_Jump(0x00494294, &_EventClass_Execute_ViniferaEvent);
+ Patch_Jump(0x005B45D5, &_Add_Compressed_Events_ViniferaEvent_Length);
+ Patch_Jump(0x005B4A40, &Vinifera_Extract_Compressed_Events);
+}
diff --git a/src/new/viniferaevent/viniferaevent_hooks.h b/src/new/viniferaevent/viniferaevent_hooks.h
new file mode 100644
index 000000000..1f2f22708
--- /dev/null
+++ b/src/new/viniferaevent/viniferaevent_hooks.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file VINIFERAEVENT_HOOKS.CPP
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for the Vinifera event class.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+#pragma once
+
+
+void ViniferaEvent_Hooks();
diff --git a/src/spawner/modules/autosurrender_hooks.cpp b/src/spawner/modules/autosurrender_hooks.cpp
new file mode 100644
index 000000000..976082433
--- /dev/null
+++ b/src/spawner/modules/autosurrender_hooks.cpp
@@ -0,0 +1,131 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file AUTOSURRENDER_HOOKS.H
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for auto-surrender in multiplayer.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "autosurrender_hooks.h"
+
+#include "hooker.h"
+#include "hooker_macros.h"
+#include "spawner.h"
+#include "session.h"
+#include "tibsun_globals.h"
+
+
+static bool PlayerHasSurrendered = false;
+
+
+/**
+ * Force surrender on abort.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_Standard_Options_Dialog_HANDLER_AutoSurrender)
+{
+ if (Spawner::Active && Spawner::Get_Config()->AutoSurrender)
+ {
+ if (Session.Type == GAME_IPX && !PlayerHasSurrendered)
+ {
+ SpecialDialog = SDLG_SURRENDER;
+ JMP(0x004B6D2A);
+ }
+ }
+
+ // Stolen bytes
+ if (Session.Type != GAME_INTERNET)
+ {
+ JMP(0x004B6D20);
+ }
+
+ JMP(0x004B6D0D);
+}
+
+
+/**
+ * Force surrender on disconnection.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_EventClass_Execute_REMOVE_PLAYER_AutoSurrender)
+{
+ if (Spawner::Active && Spawner::Get_Config()->AutoSurrender)
+ {
+ if (Session.Type == GAME_IPX && Spawner::Get_Config()->AutoSurrender)
+ {
+ JMP(0x00494F16);
+ }
+ }
+
+ // Stolen bytes
+ if (Session.Type != GAME_INTERNET)
+ {
+ JMP(0x00494F28);
+ }
+
+ JMP(0x00494F0D);
+}
+
+
+/**
+ * Two patches to save whether the player has surrendered.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_Special_Dialog_Surrender1)
+{
+ PlayerHasSurrendered = true;
+ _asm mov eax, PlayerPtr
+ _asm mov eax, [eax]
+ JMP_REG(ecx, 0x004627F8);
+}
+
+
+DECLARE_PATCH(_Special_Dialog_Surrender2)
+{
+ GET_REGISTER_STATIC(bool, has_surrendered, al);
+
+ PlayerHasSurrendered = has_surrendered;
+
+ // Stolen bytes
+ if (has_surrendered)
+ {
+ JMP(0x00462842);
+ }
+
+ JMP(0x004628E3)
+}
+
+
+/**
+ * Main function for patching the hooks.
+ */
+void AutoSurrender_Hooks()
+{
+ Patch_Jump(0x004B6D04, &_Standard_Options_Dialog_HANDLER_AutoSurrender);
+ Patch_Jump(0x00494F08, &_EventClass_Execute_REMOVE_PLAYER_AutoSurrender);
+ Patch_Jump(0x004627F3, &_Special_Dialog_Surrender1);
+ Patch_Jump(0x0046283A, &_Special_Dialog_Surrender2);
+}
diff --git a/src/spawner/modules/autosurrender_hooks.h b/src/spawner/modules/autosurrender_hooks.h
new file mode 100644
index 000000000..a761c313c
--- /dev/null
+++ b/src/spawner/modules/autosurrender_hooks.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file AUTOSURRENDER_HOOKS.H
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for auto-surrender in multiplayer.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+#pragma once
+
+
+void AutoSurrender_Hooks();
diff --git a/src/spawner/modules/quickmatch_hooks.cpp b/src/spawner/modules/quickmatch_hooks.cpp
new file mode 100644
index 000000000..2c199ecdd
--- /dev/null
+++ b/src/spawner/modules/quickmatch_hooks.cpp
@@ -0,0 +1,150 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file QUICKMATCH_HOOKS.CPP
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for the quick match mode.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "quickmatch_hooks.h"
+
+#include "hooker.h"
+#include "spawner.h"
+#include "house.h"
+#include "textprint.h"
+#include "ipxmgr.h"
+#include "session.h"
+
+#include "hooker_macros.h"
+
+
+static const char* PLAYER = "Player";
+
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class IPXManagerClassExt : public IPXManagerClass
+{
+public:
+ char* _Connection_Name(int id);
+};
+
+
+/**
+ * Hide the player names when the IPX manager is asked for it.
+ *
+ * @author: ZivDero
+ */
+char* IPXManagerClassExt::_Connection_Name(int id)
+{
+ if (Spawner::Active && Spawner::Get_Config()->QuickMatch)
+ {
+ return const_cast(PLAYER);
+ }
+ else
+ {
+ return IPXManagerClass::Connection_Name(id);
+ }
+}
+
+
+/**
+ * Hide the player names in the in the radar.
+ *
+ * @author: ZivDero
+ */
+static int __cdecl sprintf_RadarClass_Draw_Names_Wrapper(char* buffer, const char* format, char* str)
+{
+ if (Spawner::Active && Spawner::Get_Config()->QuickMatch)
+ {
+ return std::sprintf(buffer, "%s", PLAYER);
+ }
+ else
+ {
+ return std::sprintf(buffer, format, str);
+ }
+}
+
+
+/**
+ * Hide the player names in the on the progress screen.
+ *
+ * @author: ZivDero
+ */
+static Point2D Fancy_Text_Print_ProgressScreenClass_Draw_Graphics_Wrapper(const char* text, XSurface* surface, Rect* rect, Point2D* xy, ColorScheme* fore, unsigned back, TextPrintType flag)
+{
+ if (Spawner::Active && Spawner::Get_Config()->QuickMatch)
+ {
+ return Fancy_Text_Print(PLAYER, surface, rect, xy, fore, back, flag);
+ }
+ else
+ {
+ return Fancy_Text_Print(text, surface, rect, xy, fore, back, flag);
+ }
+}
+
+
+/**
+ * Hide the player anmes in the Kick Player dialog.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_Kick_Player_Dialog_SendMessage_Hide_Name)
+{
+ GET_REGISTER_STATIC(HWND, hWnd, ebp);
+ GET_REGISTER_STATIC(int, index, esi);
+
+ _asm pushad
+
+ if (Spawner::Active && Spawner::Get_Config()->QuickMatch)
+ {
+ SendMessageA(hWnd, WM_SETTEXT, 0, reinterpret_cast(PLAYER));
+ }
+ else
+ {
+ SendMessageA(hWnd, WM_SETTEXT, 0, reinterpret_cast(Session.Players[index]->Name));
+ }
+
+ _asm popad
+
+ JMP(0x005B4038);
+}
+
+
+/**
+ * Main function for patching the hooks.
+ */
+void QuickMatch_Hooks()
+{
+ Patch_Call(0x005B980E, &sprintf_RadarClass_Draw_Names_Wrapper);
+ Patch_Call(0x005ADC8F, &Fancy_Text_Print_ProgressScreenClass_Draw_Graphics_Wrapper);
+ Patch_Jump(0x005B4024, &_Kick_Player_Dialog_SendMessage_Hide_Name);
+ Patch_Call(0x00648EAE, &IPXManagerClassExt::_Connection_Name);
+}
diff --git a/src/cncnet/cncnet5/cncnet5_hooks.h b/src/spawner/modules/quickmatch_hooks.h
similarity index 88%
rename from src/cncnet/cncnet5/cncnet5_hooks.h
rename to src/spawner/modules/quickmatch_hooks.h
index 853ea9b0c..d63cf2ea0 100644
--- a/src/cncnet/cncnet5/cncnet5_hooks.h
+++ b/src/spawner/modules/quickmatch_hooks.h
@@ -4,11 +4,11 @@
*
* @project Vinifera
*
- * @file CNCNET5_HOOKS.H
+ * @file QUICKMATCH_HOOKS.H
*
- * @author CCHyper
+ * @author ZivDero
*
- * @brief Contains the hooks for implementing the CnCNet5 system.
+ * @brief Contains the hooks for the quick match mode.
*
* @license Vinifera is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -28,4 +28,4 @@
#pragma once
-void CnCNet5_Hooks();
+void QuickMatch_Hooks();
diff --git a/src/spawner/modules/savedgamesdir_hooks.cpp b/src/spawner/modules/savedgamesdir_hooks.cpp
new file mode 100644
index 000000000..f4f573699
--- /dev/null
+++ b/src/spawner/modules/savedgamesdir_hooks.cpp
@@ -0,0 +1,219 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file SAVEDGAMESDIR_HOOKS.CPP
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for "Saved Games" directory customization.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "savedgamesdir_hooks.h"
+
+//#include
+//
+//#include "debughandler.h"
+//#include "hooker.h"
+//#include "hooker_macros.h"
+//#include "loadoptions.h"
+//#include "saveload.h"
+//#include "savever.h"
+//#include "spawner.h"
+//
+//
+//namespace SavedGames
+//{
+// static char Buffer[PATH_MAX];
+//
+// /**
+// * Make sure the subdirectory exists, and create it if not
+// *
+// * @author: ZivDero
+// */
+// bool Ensure_Folder_Exists(const char* path)
+// {
+// const DWORD attributes = GetFileAttributes(path);
+//
+// // If path doesn't exist or isn't a directory, try creating it
+// if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY))
+// {
+// // Directory created or already exists
+// if (CreateDirectory(path, nullptr) || GetLastError() == ERROR_ALREADY_EXISTS)
+// return true;
+//
+// DEBUG_ERROR("Error: Could not create directory \"%s\". Error code: %d\n", path, GetLastError());
+// return false;
+// }
+//
+// // Directory already exists
+// return true;
+// }
+//
+//
+// /**
+// * Format the path to contain the subdirectory.
+// *
+// * @author: Belonit
+// */
+// inline void Format_Path(char* buffer, size_t buffer_size, const char* filename)
+// {
+// std::snprintf(buffer, buffer_size, "%s\\%s", Spawner::Get_Config()->SavedGamesDir, filename);
+// }
+//
+// /**
+// * Format the path to contain the subdirectory, if it doesn't already.
+// *
+// * @author: ZivDero
+// */
+// inline void Check_And_Format_Path(char* buffer, size_t buffer_size, const char* filename)
+// {
+// std::strstr(filename, Spawner::Get_Config()->SavedGamesDir) ? std::snprintf(buffer, buffer_size, "%s", filename) : Format_Path(buffer, buffer_size, filename);
+// }
+//}
+//
+//
+///**
+// * A fake class for implementing new member functions which allow
+// * access to the "this" pointer of the intended class.
+// *
+// * @note: This must not contain a constructor or destructor.
+// *
+// * @note: All functions must not be virtual and must also be prefixed
+// * with "_" to prevent accidental virtualization.
+// */
+//class LoadOptionsClassExt : public LoadOptionsClass
+//{
+//public:
+// bool _Delete_File(const char* filename);
+//};
+//
+//
+///**
+// * Make sure the file name contains the subdirectory when deleting games.
+// *
+// * @author: ZivDero
+// */
+//bool LoadOptionsClassExt::_Delete_File(const char* filename)
+//{
+// SavedGames::Check_And_Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), filename);
+// return DeleteFileA(SavedGames::Buffer);
+//}
+//
+//
+///**
+// * Make sure the file name contains the subdirectory in LoadOptionsClass.
+// *
+// * @author: ZivDero
+// */
+//int __cdecl sprintf_LoadOptionsClass_Wrapper1(char* buffer, const char*, int number, char* str)
+//{
+// // First create the format string itself, using our custom folder, e. g. "Saved Games\SAVE%04lX.%3s"
+// SavedGames::Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), "SAVE%04lX.%3s");
+//
+// // Now actually format the path
+// return std::sprintf(buffer, SavedGames::Buffer, number, str);
+//}
+//
+//
+///**
+// * Make sure the file name contains the subdirectory in LoadOptionsClass.
+// *
+// * @author: ZivDero
+// */
+//int __cdecl sprintf_LoadOptionsClass_Wrapper2(char* buffer, const char*, char* str)
+//{
+// // First create the format string itself, using our custom folder, e. g. "Saved Games\*.%3s"
+// SavedGames::Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), "*.%3s");
+//
+// // Now actually format the path
+// return std::sprintf(buffer, SavedGames::Buffer, str);
+//}
+//
+//
+///**
+// * Make sure the file name contains the subdirectory when saving the game.
+// *
+// * @author: ZivDero
+// */
+//void __cdecl DebugString_Save_Game_Wrapper(const char* format, char* file_name, const char* descr)
+//{
+// // Print the string it was going to print
+// DEBUG_INFO(format, file_name, descr);
+//
+// // Format the path
+// SavedGames::Check_And_Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), file_name);
+//
+// // Print it back to the original buffer
+// std::sprintf(file_name, "%s", SavedGames::Buffer);
+//
+// // Make sure the subfolder exists
+// SavedGames::Ensure_Folder_Exists(Spawner::Get_Config()->SavedGamesDir);
+//}
+//
+//
+///**
+// * Make sure the file name contains the subdirectory when loading the game.
+// *
+// * @author: ZivDero
+// */
+//void __cdecl DebugString_Load_Game_Wrapper(const char* format, char* file_name)
+//{
+// // Print the string it was going to print
+// DEBUG_INFO(format, file_name);
+//
+// // Format the path
+// SavedGames::Check_And_Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), file_name);
+//
+// // Print it back to the original buffer
+// std::sprintf(file_name, "%s", SavedGames::Buffer);
+//}
+//
+//
+///**
+// * Make sure the file name contains the subdirectory when reading savefile readers.
+// *
+// * @author: ZivDero
+// */
+//bool Get_Savefile_Info_Wrapper(char* file_name, SaveVersionInfo& wwsaveload)
+//{
+// SavedGames::Check_And_Format_Path(SavedGames::Buffer, std::size(SavedGames::Buffer), file_name);
+//
+// // Print it back to the original buffer
+// std::sprintf(file_name, "%s", SavedGames::Buffer);
+//
+// return Get_Savefile_Info(file_name, wwsaveload);
+//}
+//
+//
+/**
+ * Main function for patching the hooks.
+ */
+void SavedGamesDir_Hooks()
+{
+ //Patch_Call(0x00505001, &sprintf_LoadOptionsClass_Wrapper1);
+ //Patch_Call(0x00505294, &sprintf_LoadOptionsClass_Wrapper1);
+ //Patch_Call(0x00505509, &sprintf_LoadOptionsClass_Wrapper2);
+ //Patch_Call(0x00505863, &sprintf_LoadOptionsClass_Wrapper2);
+ //Patch_Call(0x005D4FF5, &DebugString_Save_Game_Wrapper);
+ //Patch_Call(0x005D6922, &DebugString_Load_Game_Wrapper);
+ //Patch_Jump(0x00505A20, &LoadOptionsClassExt::_Delete_File);
+ //Patch_Call(0x00505A8A, &Get_Savefile_Info_Wrapper);
+}
diff --git a/src/spawner/modules/savedgamesdir_hooks.h b/src/spawner/modules/savedgamesdir_hooks.h
new file mode 100644
index 000000000..2bd2f7033
--- /dev/null
+++ b/src/spawner/modules/savedgamesdir_hooks.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file SAVEDGAMESDIR_HOOKS.H
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for "Saved Games" directory customization.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+#pragma once
+
+
+void SavedGamesDir_Hooks();
diff --git a/src/spawner/modules/spectator_hooks.cpp b/src/spawner/modules/spectator_hooks.cpp
new file mode 100644
index 000000000..d39bc5218
--- /dev/null
+++ b/src/spawner/modules/spectator_hooks.cpp
@@ -0,0 +1,344 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- T S + + **
+/*******************************************************************************
+ *
+ * @project TS++
+ *
+ * @file SPECTATOR_HOOKS.CPP
+ *
+ * @authors ZivDero
+ *
+ * @brief Contains the hooks for spectator mode.
+ *
+ * @license TS++ 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * TS++ 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, see .
+ *
+ ******************************************************************************/
+
+#include "spectator_hooks.h"
+
+#include "display.h"
+#include "hooker.h"
+#include "hooker_macros.h"
+#include "house.h"
+#include "housetype.h"
+#include "session.h"
+#include "spawner.h"
+#include "mouse.h"
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class HouseClassExt : public HouseClass
+{
+public:
+ bool _Is_Spectator() const;
+ bool _Is_Coach() const;
+ bool _Is_Ally_Or_Spectator(const HouseClassExt* house) const;
+ void _Update_Radars();
+ bool _Has_Player_Allies() const;
+};
+
+
+/**
+ * Helper function that returns if the house is a spectator.
+ *
+ * @author: ZivDero
+ */
+bool HouseClassExt::_Is_Spectator() const
+{
+ if (Spawner::Active)
+ {
+ return Spawner::Get_Config()->Houses[Get_Heap_ID()].IsSpectator;
+ }
+
+ return false;
+}
+
+
+/**
+ * Helper function that returns if the house is a coach.
+ *
+ * @author: ZivDero
+ */
+bool HouseClassExt::_Is_Coach() const
+{
+ if (Spawner::Active)
+ {
+ if (Spawner::Get_Config()->CoachMode)
+ {
+ return _Is_Spectator() && _Has_Player_Allies();
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Helper function that returns if the house is allied to the other house, or is a spectator.
+ *
+ * @author: ZivDero
+ */
+bool HouseClassExt::_Is_Ally_Or_Spectator(const HouseClassExt* house) const
+{
+ bool is_ally = HouseClass::Is_Ally(house);
+
+ if (Spawner::Active)
+ {
+ if (is_ally)
+ return is_ally;
+
+ if (Spawner::Get_Config()->CoachMode)
+ {
+ return _Is_Coach() || house->_Is_Coach();
+ }
+
+ return _Is_Spectator() || house->_Is_Spectator();
+ }
+
+ return is_ally;
+}
+
+
+/**
+ * Helper function that returns if the player has any allies.
+ *
+ * @author: ZivDero
+ */
+bool HouseClassExt::_Has_Player_Allies() const
+{
+ const char* SPECIAL = "Special";
+
+ unsigned int allies = Allies;
+
+ // Special is allied to everyone, so we need to exclude it from the allies list to get the real picture
+ int special_house_id = As_Pointer(HouseTypeClass::From_Name(SPECIAL))->Get_Heap_ID();
+ allies &= ~(1 << special_house_id);
+
+ return allies != 0;
+}
+
+
+/**
+ * Enable the radar for spectators.
+ *
+ * @author: ZivDero
+ */
+static bool spectator_radar_enabled = false;
+void HouseClassExt::_Update_Radars()
+{
+ Update_Radars();
+
+ if (Spawner::Active)
+ {
+ if (this == PlayerPtr && !spectator_radar_enabled && _Is_Spectator())
+ {
+ Map.IsRadarAvailable = true;
+ Map.RadarClass::Radar_Activate(1);
+ spectator_radar_enabled = true;
+ }
+ }
+}
+
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class DisplayClassExt : public DisplayClass
+{
+public:
+ void _Encroach_Shadow_Spectator();
+ void _Encroach_Fog_Spectator();
+};
+
+
+/**
+ * Don't encroach shadow for spectators.
+ *
+ * @author: ZivDero
+ */
+void DisplayClassExt::_Encroach_Shadow_Spectator()
+{
+ if (Spawner::Active && reinterpret_cast(PlayerPtr)->_Is_Spectator() && !reinterpret_cast(PlayerPtr)->_Is_Coach())
+ {
+ return;
+ }
+
+ DisplayClass::Encroach_Shadow();
+}
+
+
+/**
+ * Don't encroach fog for spectators.
+ *
+ * @author: ZivDero
+ */
+void DisplayClassExt::_Encroach_Fog_Spectator()
+{
+ if (Spawner::Active && reinterpret_cast(PlayerPtr)->_Is_Spectator() && !reinterpret_cast(PlayerPtr)->_Is_Coach())
+ {
+ return;
+ }
+
+ DisplayClass::Encroach_Fog();
+}
+
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class MapClassExt : public MapClass
+{
+public:
+ void _Reveal_The_Map();
+};
+
+
+/**
+ * Don't reveal the map in coach mode.
+ *
+ * @author: ZivDero
+ */
+void MapClassExt::_Reveal_The_Map()
+{
+ if (Spawner::Active && Spawner::Get_Config()->CoachMode && reinterpret_cast(PlayerPtr)->_Is_Coach())
+ {
+ return;
+ }
+
+ MapClass::Reveal_The_Map();
+}
+
+
+/**
+ * Don't count spectators as defeated players.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_HouseClass_MPlayer_Defeated_Dont_Count_Spectators)
+{
+ GET_REGISTER_STATIC(HouseClassExt*, hptr, eax);
+ _asm pushad
+
+ if (Spawner::Active && hptr != PlayerPtr && Session.Type != GAME_SKIRMISH && hptr->_Is_Spectator())
+ {
+ _asm popad
+ JMP(0x004BF74A);
+ }
+
+ // Vanilla code
+ if (!hptr->IsDefeated && !hptr->Class->IsMultiplayPassive)
+ {
+ _asm popad
+ JMP_REG(ebp, 0x004BF730);
+ }
+
+ _asm popad
+ JMP_REG(ebp, 0x004BF75D)
+}
+
+
+/**
+ * Don't process the radar for spectators.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_HouseClass_Radar_Outage_Spectators)
+{
+ GET_STACK_STATIC8(bool, tactical_availability, esp, 0x4);
+ GET_REGISTER_STATIC(HouseClassExt*, house, esi);
+
+ if (!Spawner::Active || (house == PlayerPtr && !house->_Is_Spectator()))
+ {
+ Map.RadarClass::Toggle_Radar(tactical_availability);
+ }
+
+ // Return
+ JMP(0x004C9693);
+}
+
+
+/**
+ * Reveal the map for spectators.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_RadarClass_Compute_Radar_Image)
+{
+ if (Spawner::Active)
+ {
+ spectator_radar_enabled = false;
+ if (reinterpret_cast(PlayerPtr)->_Is_Spectator() && !reinterpret_cast(PlayerPtr)->_Is_Coach() && PlayerPtr->IsDefeated)
+ {
+ Session.ObiWan = true;
+ Map.Reveal_The_Map();
+ }
+ }
+
+ // Function epilogue
+ _asm
+ {
+ pop edi
+ pop esi
+ pop ebp
+ add esp, 0x14
+ ret
+ }
+}
+
+
+/**
+ * Main function for patching the hooks.
+ */
+void Spectator_Hooks()
+{
+ Patch_Call(0x00506D7B, &DisplayClassExt::_Encroach_Shadow_Spectator);
+ Patch_Call(0x00507291, &DisplayClassExt::_Encroach_Shadow_Spectator);
+ Patch_Call(0x00619AE9, &DisplayClassExt::_Encroach_Shadow_Spectator);
+ Patch_Call(0x0061B985, &DisplayClassExt::_Encroach_Shadow_Spectator);
+ Patch_Call(0x00506DFC, &DisplayClassExt::_Encroach_Fog_Spectator);
+ Patch_Call(0x00507309, &DisplayClassExt::_Encroach_Fog_Spectator);
+ Patch_Jump(0x004BF71B, &_HouseClass_MPlayer_Defeated_Dont_Count_Spectators);
+ Patch_Jump(0x004C9684, &_HouseClass_Radar_Outage_Spectators);
+ Patch_Call(0x0043852B, &HouseClassExt::_Is_Ally_Or_Spectator);
+ Patch_Call(0x00438540, &HouseClassExt::_Is_Ally_Or_Spectator);
+ Patch_Call(0x00633E85, &HouseClassExt::_Is_Ally_Or_Spectator);
+ Patch_Call(0x00633E9F, &HouseClassExt::_Is_Ally_Or_Spectator);
+ Patch_Call(0x0062C6CE, &HouseClassExt::_Is_Ally_Or_Spectator);
+ Patch_Call(0x0062CA26, &HouseClassExt::_Is_Ally_Or_Spectator);
+ Patch_Call(0x00428A23, &HouseClassExt::_Is_Ally_Or_Spectator);
+ Patch_Call(0x0047B0BB, &HouseClassExt::_Is_Ally_Or_Spectator);
+ Patch_Call(0x004BC608, &HouseClassExt::_Update_Radars);
+ Patch_Call(0x004BF5D6, &MapClassExt::_Reveal_The_Map);
+ Patch_Jump(0x005B9CFE, &_RadarClass_Compute_Radar_Image);
+}
diff --git a/src/cncnet/cncnet4/cncnet4_hooks.h b/src/spawner/modules/spectator_hooks.h
similarity index 89%
rename from src/cncnet/cncnet4/cncnet4_hooks.h
rename to src/spawner/modules/spectator_hooks.h
index 5dcc88fec..f1e910b29 100644
--- a/src/cncnet/cncnet4/cncnet4_hooks.h
+++ b/src/spawner/modules/spectator_hooks.h
@@ -4,11 +4,11 @@
*
* @project Vinifera
*
- * @file CNCNET4_HOOKS.H
+ * @file SPECTATOR_HOOKS.H
*
- * @author CCHyper
+ * @author ZivDero
*
- * @brief Contains the hooks for the CnCNet4 system.
+ * @brief Contains the hooks for spectator mode.
*
* @license Vinifera is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -28,4 +28,4 @@
#pragma once
-void CnCNet4_Hooks();
+void Spectator_Hooks();
diff --git a/src/spawner/modules/statistics_hooks.cpp b/src/spawner/modules/statistics_hooks.cpp
new file mode 100644
index 000000000..0e7b2e85b
--- /dev/null
+++ b/src/spawner/modules/statistics_hooks.cpp
@@ -0,0 +1,449 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file STATISTICS_HOOKS.CPP
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for statistics collection.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "statistics_hooks.h"
+
+#include "hooker.h"
+#include "hooker_macros.h"
+#include "packet.h"
+#include "scenario.h"
+#include "session.h"
+#include "spawner.h"
+#include "field.h"
+#include "house.h"
+#include "housetype.h"
+#include "tibsun_globals.h"
+
+
+static bool Is_Statistics_Enabled()
+{
+ if (Spawner::Active)
+ {
+ return Spawner::Get_Config()->WriteStatistics
+ && Session.Type == GAME_IPX;
+ }
+
+ // Vanilla condition
+ return Session.Type == GAME_INTERNET;
+}
+
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class PacketClassExt : public PacketClass
+{
+public:
+ char* _Create_Comms_Packet(int& size);
+ void _Add_Field_SCEN_ACCN_HASH(FieldClass* field);
+ void _Add_Field_Player_Data(FieldClass* field);
+};
+
+
+/**
+ * Write statistics to a file for the client.
+ *
+ * @author: ZivDero
+ */
+char* PacketClassExt::_Create_Comms_Packet(int& size)
+{
+ char* result = Create_Comms_Packet(size);
+
+ if (Is_Statistics_Enabled())
+ {
+ CCFileClass stats_file("stats.dmp");
+ if (stats_file.Open(FILE_ACCESS_WRITE))
+ {
+ stats_file.Write(result, size);
+ stats_file.Close();
+ }
+
+ GameStatisticsPacketSent = true;
+ }
+
+ return result;
+}
+
+
+/**
+ * Add some scenario-related fields to the statistics packet.
+ *
+ * @author: ZivDero
+ */
+void PacketClassExt::_Add_Field_SCEN_ACCN_HASH(FieldClass* field)
+{
+ if (Is_Statistics_Enabled())
+ {
+ FieldClass scen("SCEN", Spawner::Get_Config()->UIMapName);
+ PacketClass::Add_Field(&scen);
+
+ FieldClass accn("ACCN", PlayerPtr->IniName);
+ PacketClass::Add_Field(&accn);
+
+ FieldClass hash("HASH", Spawner::Get_Config()->MapHash);
+ PacketClass::Add_Field(&hash);
+
+ return;
+ }
+
+ PacketClass::Add_Field(field);
+}
+
+
+/**
+ * Add some player-related fields to the statistics packet.
+ *
+ * @author: ZivDero
+ */
+void PacketClassExt::_Add_Field_Player_Data(FieldClass* field)
+{
+ // This is the global string "NAM?"
+ // The game replaces "?" with the player's ID before this call,
+ // so we can grab it from there.
+ // It should be also be the house ID.
+ static char*& field_player_handle = Make_Global(0x0070FCF4);
+
+ if (Is_Statistics_Enabled())
+ {
+ const char id = field_player_handle[3] - '0';
+
+ HouseClass* house = Houses[id];
+
+ if (house == PlayerPtr)
+ {
+ FieldClass myid("MYID", static_cast(id));
+ PacketClass::Add_Field(&myid);
+
+ FieldClass nkey("NKEY", static_cast(0));
+ PacketClass::Add_Field(&nkey);
+
+ FieldClass skey("SKEY", static_cast(0));
+ PacketClass::Add_Field(&skey);
+ }
+
+ static char field_player_allies[] = "ALY?";
+ field_player_allies[3] = id;
+ FieldClass aly (field_player_allies, static_cast(house->Allies));
+ PacketClass::Add_Field(&aly );
+
+ static char field_player_spawn[] = "BSP?";
+ field_player_spawn[3] = id;
+ FieldClass bsp(field_player_spawn, static_cast(Spawner::Get_Config()->Houses[id].SpawnLocation));
+ PacketClass::Add_Field(&bsp);
+
+ static char field_player_spectator[] = "SPC?";
+ field_player_spectator[3] = id;
+ FieldClass spc(field_player_spectator, static_cast(Spawner::Get_Config()->Houses[id].IsSpectator));
+ PacketClass::Add_Field(&spc);
+ }
+
+ PacketClass::Add_Field(field);
+}
+
+
+/**
+ * Numerous patches to enable statistics collection.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_BuildingClass_Captured_SendStatistics)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x0042F7A3);
+ }
+
+ JMP(0x0042F7BB);
+}
+
+
+DECLARE_PATCH(_CellClass_Goodie_Check_SendStatistics)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x00457E83);
+ }
+
+ JMP(0x00457E95);
+}
+
+
+DECLARE_PATCH(_HouseClass_Tracking_Add_SendStatistics1)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x004C2218);
+ }
+
+ JMP(0x004C22FA);
+}
+
+
+DECLARE_PATCH(_HouseClass_Tracking_Add_SendStatistics2)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x004C2262);
+ }
+
+ JMP(0x004C22FA);
+}
+
+
+DECLARE_PATCH(_HouseClass_Tracking_Add_SendStatistics3)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x004C22A8);
+ }
+
+ JMP(0x004C22FA);
+}
+
+
+DECLARE_PATCH(_HouseClass_Tracking_Add_SendStatistics4)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x004C22EE);
+ }
+
+ JMP(0x004C22FA);
+}
+
+
+DECLARE_PATCH(_TechnoClass_Record_The_Kill_SendStatistics1)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x00633893);
+ }
+
+ JMP(0x006338B1);
+}
+
+
+DECLARE_PATCH(_TechnoClass_Record_The_Kill_SendStatistics2)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x006338FD);
+ }
+
+ JMP(0x00633920);
+}
+
+
+DECLARE_PATCH(_TechnoClass_Record_The_Kill_SendStatistics3)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x00633965);
+ }
+
+ JMP(0x00633983);
+}
+
+
+DECLARE_PATCH(_TechnoClass_Record_The_Kill_SendStatistics4)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x00633931);
+ }
+
+ JMP(0x00633954);
+}
+
+
+DECLARE_PATCH(_Print_MP_Stats_Check)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x00463542);
+ }
+
+ JMP(0x0046371F);
+}
+
+
+DECLARE_PATCH(_HouseClass_HouseClass_Create_Unit_Trackers)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x004BAC39);
+ }
+
+ JMP(0x004BADB0);
+}
+
+
+DECLARE_PATCH(_Kick_Player_Now_SendStatistics)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x005B433C);
+ }
+
+ JMP(0x005B439F);
+}
+
+
+DECLARE_PATCH(_Queue_AI_Multiplayer_SendStatistics)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x005B1EA0);
+ }
+
+ JMP(0x005B1F21);
+}
+
+
+DECLARE_PATCH(_Main_Loop_SendStatistics1)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x00509229);
+ }
+
+ JMP(0x0050924B);
+}
+
+
+DECLARE_PATCH(_Main_Loop_SendStatistics2)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x00509283);
+ }
+
+ JMP(0x005092A5);
+}
+
+
+DECLARE_PATCH(_Execute_DoList_SendStatistics1)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x005B4FB9);
+ }
+
+ JMP(0x005B500C);
+}
+
+
+DECLARE_PATCH(_Execute_DoList_SendStatistics2)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x005B4FDE);
+ }
+
+ JMP(0x005B500C);
+}
+
+
+DECLARE_PATCH(_Main_Game_Start_Timer)
+{
+ if (Is_Statistics_Enabled())
+ {
+ JMP(0x00462A2F);
+ }
+
+ JMP(0x00462A46);
+}
+
+
+/**
+ * Don't send statistics for observers.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_Send_Statistics_Packet_Send_AI_Dont_Send_Observers)
+{
+ GET_REGISTER_STATIC(HouseClass*, house, eax);
+ _asm pushad
+
+ if (Spawner::Active && Is_Statistics_Enabled())
+ {
+ if (house->Class->IsMultiplayPassive || Spawner::Get_Config()->Houses[house->Get_Heap_ID()].IsSpectator)
+ {
+ _asm popad
+ JMP_REG(ecx, 0x006098EC);
+ }
+ }
+ else // Vanilla condition
+ {
+ if (!house->IsHuman)
+ {
+ _asm popad
+ JMP_REG(ecx, 0x006098EC);
+ }
+ }
+
+ _asm popad
+ JMP_REG(ecx, 0x006098E6);
+}
+
+
+/**
+ * Main function for patching the hooks.
+ */
+void Statistics_Hooks()
+{
+ Patch_Call(0x0060A797, &PacketClassExt::_Create_Comms_Packet);
+ Patch_Jump(0x0042F799, &_BuildingClass_Captured_SendStatistics);
+ Patch_Jump(0x00457E7A, &_CellClass_Goodie_Check_SendStatistics);
+ Patch_Jump(0x004C220B, &_HouseClass_Tracking_Add_SendStatistics1);
+ Patch_Jump(0x004C2255, &_HouseClass_Tracking_Add_SendStatistics2);
+ Patch_Jump(0x004C229F, &_HouseClass_Tracking_Add_SendStatistics3);
+ Patch_Jump(0x004C22E5, &_HouseClass_Tracking_Add_SendStatistics4);
+ Patch_Jump(0x0063388A, &_TechnoClass_Record_The_Kill_SendStatistics1);
+ Patch_Jump(0x006338F4, &_TechnoClass_Record_The_Kill_SendStatistics2);
+ Patch_Jump(0x0063395C, &_TechnoClass_Record_The_Kill_SendStatistics3);
+ Patch_Jump(0x00633928, &_TechnoClass_Record_The_Kill_SendStatistics4);
+ Patch_Jump(0x0046353C, &_Print_MP_Stats_Check);
+ Patch_Jump(0x004BAC2C, &_HouseClass_HouseClass_Create_Unit_Trackers);
+ Patch_Jump(0x005B4333, &_Kick_Player_Now_SendStatistics);
+ Patch_Jump(0x005B1E94, &_Queue_AI_Multiplayer_SendStatistics);
+ Patch_Jump(0x00509220, &_Main_Loop_SendStatistics1);
+ Patch_Jump(0x0050927A, &_Main_Loop_SendStatistics2);
+ Patch_Jump(0x005B4FAE, &_Execute_DoList_SendStatistics1);
+ Patch_Jump(0x005B4FD3, &_Execute_DoList_SendStatistics2);
+ Patch_Jump(0x00462A26, &_Main_Game_Start_Timer);
+ Patch_Call(0x0060982A, &PacketClassExt::_Add_Field_SCEN_ACCN_HASH);
+ Patch_Call(0x00609DA6, &PacketClassExt::_Add_Field_Player_Data);
+ Patch_Jump(0x006098DC, &_Send_Statistics_Packet_Send_AI_Dont_Send_Observers);
+}
diff --git a/src/cncnet/cncnet5/cncnet5_globals.cpp b/src/spawner/modules/statistics_hooks.h
similarity index 71%
rename from src/cncnet/cncnet5/cncnet5_globals.cpp
rename to src/spawner/modules/statistics_hooks.h
index b30f9fe8a..95b8d8af5 100644
--- a/src/cncnet/cncnet5/cncnet5_globals.cpp
+++ b/src/spawner/modules/statistics_hooks.h
@@ -4,11 +4,11 @@
*
* @project Vinifera
*
- * @file CNCNET_GLOBALS.CPP
+ * @file STATISTICS_HOOKS.H
*
- * @author CCHyper
+ * @author ZivDero
*
- * @brief Global values and types used for the CnCNet5 system.
+ * @brief Contains the hooks for statistics collection.
*
* @license Vinifera is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -25,20 +25,7 @@
* If not, see .
*
******************************************************************************/
-#include "cncnet5_globals.h"
+#pragma once
-/**
- * Has the CnCNet5 system been activated?
- */
-bool CnCNet5::IsActive = false;
-
-/**
- * Is the tunnel system active (set when tunnel information has been provided)?
- */
-bool CnCNet5::IsTunnelActive = false;
-
-/**
- * CnCNet5 UDP Tunnel info.
- */
-CnCNet5::TunnelInfoStruct CnCNet5::TunnelInfo { -1, -1, -1, false };
+void Statistics_Hooks();
diff --git a/src/cncnet/cncnet5/cncnet5_wspudp.cpp b/src/spawner/net/cncnet5_wspudp.cpp
similarity index 89%
rename from src/cncnet/cncnet5/cncnet5_wspudp.cpp
rename to src/spawner/net/cncnet5_wspudp.cpp
index bc6873233..06a0d72ff 100644
--- a/src/cncnet/cncnet5/cncnet5_wspudp.cpp
+++ b/src/spawner/net/cncnet5_wspudp.cpp
@@ -36,7 +36,6 @@
*/
CnCNet5UDPInterfaceClass::CnCNet5UDPInterfaceClass(unsigned short id, unsigned long ip, unsigned short port, bool port_hack) :
UDPInterfaceClass(),
- IsEnabled(false),
AddressList(),
TunnelID(id),
TunnelIP(ip),
@@ -55,14 +54,7 @@ CnCNet5UDPInterfaceClass::CnCNet5UDPInterfaceClass(unsigned short id, unsigned l
*/
LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wParam, LONG lParam)
{
- /**
- * If the CnCNet interface has not been enabled, just use the standard UDP interface.
- */
- if (!IsEnabled) {
- return UDPInterfaceClass::Message_Handler(hWnd, uMsg, wParam, lParam);
- }
-
- struct sockaddr_in addr;
+ sockaddr_in addr;
int rc;
int addr_len;
WinsockBufferType *packet;
@@ -94,9 +86,9 @@ LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wPa
* Call the CnCNet tunnel Receive_From function to get the outstanding packet.
*/
addr_len = sizeof(addr);
- rc = CnCNet5UDPInterfaceClass::Receive_From(Socket, (char*)ReceiveBuffer, sizeof(ReceiveBuffer), 0, (PSOCKADDR_IN)&addr, &addr_len);
+ rc = Receive_From(Socket, reinterpret_cast(ReceiveBuffer), sizeof(ReceiveBuffer), 0, &addr, &addr_len);
if (rc == SOCKET_ERROR) {
- DEBUG_WARNING("CnCNet5: Send_To returned %d!\n", WSAGetLastError());
+ DEBUG_WARNING("CnCNet5: Receive_From returned %d!\n", WSAGetLastError());
Clear_Socket_Error(Socket);
return 0;
}
@@ -143,15 +135,16 @@ LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wPa
* Create a new buffer and store this packet in it.
*/
packet = Get_New_In_Buffer();
- packet->BufferLen = rc;
- std::memcpy(packet->PacketData.Buffer, ReceiveBuffer, rc);
+ packet->BufferLen = rc - sizeof(packet->PacketData.CRC);
+ packet->PacketData.CRC = *reinterpret_cast(ReceiveBuffer);
+ std::memcpy(packet->PacketData.Buffer, ReceiveBuffer + sizeof(packet->PacketData.CRC), rc - sizeof(packet->PacketData.CRC));
if (!Passes_CRC_Check(packet)) {
DEBUG_INFO("CnCNet5: Throwing away malformed packet!\n");
Delete_In_Buffer(packet);
return 0;
}
std::memset(packet->Address, 0, sizeof (packet->Address));
- std::memcpy(packet->Address+4, &addr.sin_addr.s_addr, 4);
+ std::memcpy(packet->Address + 4, &addr.sin_addr.s_addr, 4);
InBuffers.Add(packet);
}
return 0;
@@ -185,7 +178,7 @@ LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wPa
/**
* (CnCNet) pull index.
*/
- int i = addr.sin_addr.s_addr - 1;
+ const int i = *reinterpret_cast(packet->Address + 4) - 1;
/**
* (CnCNet) validate index.
@@ -207,8 +200,8 @@ LRESULT CnCNet5UDPInterfaceClass::Message_Handler(HWND hWnd, UINT uMsg, UINT wPa
* at this time. In this case, we clear the socket error and just exit. Winsock will
* send us another WRITE message when it is ready to receive more data.
*/
- rc = CnCNet5UDPInterfaceClass::Send_To(Socket, (const char *)&packet->PacketData, packet->BufferLen, 0, (PSOCKADDR_IN)&addr, sizeof (addr));
- if (rc == SOCKET_ERROR){
+ rc = Send_To(Socket, reinterpret_cast(&packet->PacketData), packet->BufferLen + sizeof(packet->PacketData.CRC), 0, &addr, sizeof(addr));
+ if (rc == SOCKET_ERROR) {
if (WSAGetLastError() != WSAEWOULDBLOCK) {
Clear_Socket_Error(Socket);
return 0;
@@ -241,7 +234,7 @@ int CnCNet5UDPInterfaceClass::Send_To(SOCKET s, const char *buf, int len, int fl
/**
* No processing if no tunnel.
*/
- if (TunnelPort == -1) {
+ if (TunnelPort == 0) {
DEBUG_WARNING("CnCNet5: TunnelPort is invalid in Send_To!\n");
return sendto(s, buf, len, flags, (const sockaddr *)dest_addr, addrlen);
}
@@ -282,9 +275,9 @@ int CnCNet5UDPInterfaceClass::Receive_From(SOCKET s, char *buf, int len, int fla
/**
* No processing if no tunnel.
*/
- if (TunnelPort == -1) {
+ if (TunnelPort == 0) {
DEBUG_WARNING("CnCNet5: TunnelPort is invalid in Recieve_From!\n");
- return recvfrom(s, buf, len, flags, (sockaddr *)src_addr, addrlen);
+ return recvfrom(s, buf, len, flags, reinterpret_cast(src_addr), addrlen);
}
#ifndef NDEBUG
@@ -294,7 +287,7 @@ int CnCNet5UDPInterfaceClass::Receive_From(SOCKET s, char *buf, int len, int fla
/**
* Call recvfrom first to get the packet.
*/
- int ret = recvfrom(s, tempbuf, sizeof tempbuf, flags, (sockaddr *)src_addr, addrlen);
+ int ret = recvfrom(s, tempbuf, sizeof(tempbuf), flags, reinterpret_cast(src_addr), addrlen);
/**
* No processing if returning error or less than 5 bytes of data.
diff --git a/src/cncnet/cncnet5/cncnet5_wspudp.h b/src/spawner/net/cncnet5_wspudp.h
similarity index 87%
rename from src/cncnet/cncnet5/cncnet5_wspudp.h
rename to src/spawner/net/cncnet5_wspudp.h
index d1e5349a8..38913038b 100644
--- a/src/cncnet/cncnet5/cncnet5_wspudp.h
+++ b/src/spawner/net/cncnet5_wspudp.h
@@ -31,11 +31,11 @@
#include "tibsun_defines.h"
-typedef struct TunnelAddress
+struct TunnelAddress
{
unsigned long IP;
- unsigned long Port;
-} TunnelAddress;
+ unsigned short Port;
+};
/**
@@ -49,7 +49,7 @@ class CnCNet5UDPInterfaceClass : public UDPInterfaceClass
{
public:
CnCNet5UDPInterfaceClass(unsigned short id, unsigned long ip, unsigned short port, bool port_hack = false);
- virtual ~CnCNet5UDPInterfaceClass() {}
+ virtual ~CnCNet5UDPInterfaceClass() override = default;
virtual LRESULT Message_Handler(HWND hWnd, UINT uMsg, UINT wParam, LONG lParam) override;
@@ -58,13 +58,6 @@ class CnCNet5UDPInterfaceClass : public UDPInterfaceClass
int Receive_From(SOCKET s, char *buf, int len, int flags, sockaddr_in *src_addr, int *addrlen);
public:
- /**
- * Should be CnCNet5 tunnel system interface be used over WinSock?
- *
- * @note: This should manually be set after instantiating the class.
- */
- bool IsEnabled;
-
TunnelAddress AddressList[MAX_PLAYERS];
unsigned short TunnelID;
diff --git a/src/spawner/net/latencylevel.cpp b/src/spawner/net/latencylevel.cpp
new file mode 100644
index 000000000..d653526b9
--- /dev/null
+++ b/src/spawner/net/latencylevel.cpp
@@ -0,0 +1,141 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file LATENCYLEVEL.CPP
+ *
+ * @author Belonit, ZivDero
+ *
+ * @brief Protocol zero latency level class.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "latencylevel.h"
+
+#include "colorscheme.h"
+#include "protocolzero.h"
+#include "debughandler.h"
+#include "house.h"
+#include "session.h"
+#include "rules.h"
+
+
+LatencyLevelEnum LatencyLevel::CurentLatencyLevel = LATENCY_LEVEL_INITIAL;
+unsigned char LatencyLevel::NewFrameSendRate = 3;
+
+
+/**
+ * Sets the desired latency level.
+ *
+ * @author: Belonit
+ */
+void LatencyLevel::Apply(LatencyLevelEnum new_latency_level)
+{
+ if (new_latency_level > LATENCY_LEVEL_MAX)
+ new_latency_level = LATENCY_LEVEL_MAX;
+
+ auto max_latency_level = static_cast(ProtocolZero::MaxLatencyLevel);
+ if (new_latency_level > max_latency_level)
+ new_latency_level = max_latency_level;
+
+ if (new_latency_level <= CurentLatencyLevel)
+ return;
+
+ DEBUG_INFO("[Spawner] Player %ls, LatencyMode (%d, %d) Frame = %d\n"
+ , PlayerPtr->IniName
+ , new_latency_level
+ , CurentLatencyLevel
+ , Frame
+ );
+
+ CurentLatencyLevel = new_latency_level;
+ NewFrameSendRate = static_cast(new_latency_level);
+ Session.PrecalcDesiredFrameRate = 60;
+ Session.PrecalcMaxAhead = Get_Max_Ahead(new_latency_level);
+ Session.Messages.Add_Message(nullptr, 0, Get_Latency_Message(new_latency_level), ColorScheme::From_Name("White"), TPF_USE_GRAD_PAL | TPF_FULLSHADOW | TPF_6PT_GRAD, static_cast(Rule->MessageDelay * TICKS_PER_MINUTE / 2));
+}
+
+
+/**
+ * Gets the max ahead for the given latency level.
+ *
+ * @author: Belonit
+ */
+unsigned int LatencyLevel::Get_Max_Ahead(LatencyLevelEnum latency_level)
+{
+ const int maxAhead[] =
+ {
+ /* 0 */ 1,
+
+ /* 1 */ 4,
+ /* 2 */ 6,
+ /* 3 */ 12,
+ /* 4 */ 16,
+ /* 5 */ 20,
+ /* 6 */ 24,
+ /* 7 */ 28,
+ /* 8 */ 32,
+ /* 9 */ 36
+ };
+
+ return maxAhead[latency_level];
+}
+
+
+/**
+ * Gets the chat message for the given latency level.
+ *
+ * @author: Belonit
+ */
+const char* LatencyLevel::Get_Latency_Message(LatencyLevelEnum latency_level)
+{
+ const char* message[] =
+ {
+ /* 0 */ "CnCNet: Latency mode set to: 0 - Initial", // Players should never see this, if they do, then it's a bug
+
+ /* 1 */ "CnCNet: Latency mode set to: 1 - Best",
+ /* 2 */ "CnCNet: Latency mode set to: 2 - Super",
+ /* 3 */ "CnCNet: Latency mode set to: 3 - Excellent",
+ /* 4 */ "CnCNet: Latency mode set to: 4 - Very Good",
+ /* 5 */ "CnCNet: Latency mode set to: 5 - Good",
+ /* 6 */ "CnCNet: Latency mode set to: 6 - Good",
+ /* 7 */ "CnCNet: Latency mode set to: 7 - Default",
+ /* 8 */ "CnCNet: Latency mode set to: 8 - Default",
+ /* 9 */ "CnCNet: Latency mode set to: 9 - Default",
+ };
+
+ return message[latency_level];
+}
+
+
+/**
+ * Gets the latency level for the given response time.
+ *
+ * @author: Belonit
+ */
+LatencyLevelEnum LatencyLevel::From_Response_Time(unsigned int response_time)
+{
+ for (char i = LATENCY_LEVEL_1; i < LATENCY_LEVEL_MAX; i++)
+ {
+ if (response_time <= Get_Max_Ahead(static_cast(i)))
+ return static_cast(i);
+ }
+
+ return LATENCY_LEVEL_MAX;
+}
diff --git a/src/cncnet/cncnet4/cncnet4_globals.h b/src/spawner/net/latencylevel.h
similarity index 50%
rename from src/cncnet/cncnet4/cncnet4_globals.h
rename to src/spawner/net/latencylevel.h
index 098937913..4f4ac7e85 100644
--- a/src/cncnet/cncnet4/cncnet4_globals.h
+++ b/src/spawner/net/latencylevel.h
@@ -4,11 +4,11 @@
*
* @project Vinifera
*
- * @file CNCNET4_GLOBALS.H
+ * @file LATENCYLEVEL.H
*
- * @author CCHyper
+ * @author Belonit, ZivDero
*
- * @brief CnCNet4 global values.
+ * @brief Protocol zero latency level class.
*
* @license Vinifera is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -27,30 +27,42 @@
******************************************************************************/
#pragma once
-#include
-#include
+enum LatencyLevelEnum : unsigned char
+{
+ LATENCY_LEVEL_INITIAL = 0,
-namespace CnCNet4 {
+ LATENCY_LEVEL_1 = 1,
+ LATENCY_LEVEL_2 = 2,
+ LATENCY_LEVEL_3 = 3,
+ LATENCY_LEVEL_4 = 4,
+ LATENCY_LEVEL_5 = 5,
+ LATENCY_LEVEL_6 = 6,
+ LATENCY_LEVEL_7 = 7,
+ LATENCY_LEVEL_8 = 8,
+ LATENCY_LEVEL_9 = 9,
-extern bool IsEnabled;
+ LATENCY_LEVEL_MAX = LATENCY_LEVEL_9,
+ LATENCY_SIZE = 1 + LATENCY_LEVEL_MAX
+};
-extern char Host[256];
-extern unsigned Port;
-extern bool Peer2Peer;
-extern bool IsDedicated;
-extern bool UseUDP;
-extern struct sockaddr_in Server;
-
-}; // namespace CnCNet4
+/**
+ * LatencyLevel
+ *
+ * This class is contains methods for working with Protocol 0 latency levels.
+ */
+class LatencyLevel
+{
+public:
+ LatencyLevel() = delete;
+ static LatencyLevelEnum CurentLatencyLevel;
+ static unsigned char NewFrameSendRate;
-int __stdcall bind_intercept(SOCKET s, const struct sockaddr *name, int namelen);
-int __stdcall closesocket_intercept(SOCKET s);
-int __stdcall getsockname_intercept(SOCKET s, struct sockaddr *name, int *namelen);
-int __stdcall getsockopt_intercept(SOCKET s, int level, int optname, char *optval, int *optlen);
-int __stdcall recvfrom_intercept(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen);
-int __stdcall sendto_intercept(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen);
-int __stdcall setsockopt_intercept(SOCKET s, int level, int optname, const char *optval, int optlen);
-SOCKET __stdcall socket_intercept(int af, int type, int protocol);
+ static void Apply(LatencyLevelEnum new_latency_level);
+ static void Apply(unsigned char new_latency_level) { Apply(static_cast(new_latency_level)); }
+ static unsigned int Get_Max_Ahead(LatencyLevelEnum latency_level);
+ static const char* Get_Latency_Message(LatencyLevelEnum latency_level);
+ static LatencyLevelEnum From_Response_Time(unsigned int response_time);
+};
diff --git a/src/spawner/net/protocolzero.cpp b/src/spawner/net/protocolzero.cpp
new file mode 100644
index 000000000..e223d3abf
--- /dev/null
+++ b/src/spawner/net/protocolzero.cpp
@@ -0,0 +1,156 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file PROTOCOLZERO.CPP
+ *
+ * @author Belonit, ZivDero
+ *
+ * @brief Protocol zero.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "protocolzero.h"
+
+#include "latencylevel.h"
+#include "spawner.h"
+#include "viniferaevent/viniferaevent.h"
+#include "house.h"
+#include "session.h"
+#include "ipxmgr.h"
+
+#include "debughandler.h"
+
+
+bool ProtocolZero::Enable = false;
+bool ProtocolZero::GetRealMaxAhead = false;
+unsigned int ProtocolZero::WorstMaxAhead = 24;
+unsigned char ProtocolZero::MaxLatencyLevel = 0xff;
+
+
+/**
+ * Sends a Response Time event.
+ *
+ * @author: Belonit
+ */
+void ProtocolZero::Send_Response_Time()
+{
+ if (Enable == false || Session.Singleplayer_Game())
+ return;
+
+ static int NextSendFrame = 6 * SendResponseTimeInterval;
+
+ /**
+ * It is not yet time to send a Response Time event.
+ */
+ if (Frame <= NextSendFrame)
+ return;
+
+ /**
+ * IPXManagerClass::Response_Time is patched to return ProtocolZero::MaxAhead,
+ * so to get the real MaxAhead we set this bool to true just for this call.
+ */
+ GetRealMaxAhead = true;
+ const unsigned int ipxResponseTime = Ipx.Response_Time();
+ GetRealMaxAhead = false;
+
+ /**
+ * Create the event.
+ */
+ ViniferaEventClass event;
+ event.Type = VEVENT_RESPONSE_TIME_2;
+ event.ID = PlayerPtr->Get_Heap_ID();
+ event.Frame = Frame + Session.MaxAhead;
+ event.Data.ResponseTime2.MaxAhead = static_cast(ipxResponseTime + 1);
+ event.Data.ResponseTime2.LatencyLevel = LatencyLevel::From_Response_Time(ipxResponseTime);
+
+ /**
+ * Send it!
+ */
+ if (OutList.Add(event.As_Event()))
+ {
+ NextSendFrame = Frame + SendResponseTimeInterval;
+ DEBUG_INFO("[Spawner] Player %d sending response time of %u, LatencyMode = %d, Frame = %d\n"
+ , event.ID
+ , event.Data.ResponseTime2.MaxAhead
+ , event.Data.ResponseTime2.LatencyLevel
+ , Frame
+ );
+ }
+ else
+ {
+ NextSendFrame++;
+ }
+}
+
+
+/**
+ * Executes a Response Time event.
+ *
+ * @author: Belonit
+ */
+void ProtocolZero::Handle_Response_Time(ViniferaEventClass* event)
+{
+ if (Enable == false || Session.Singleplayer_Game())
+ return;
+
+ if (event->Data.ResponseTime2.MaxAhead == 0)
+ {
+ DEBUG_INFO("[Spawner] Returning because event->MaxAhead == 0\n");
+ return;
+ }
+
+ static unsigned int PlayerMaxAheads[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ static unsigned char PlayerLatencyMode[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ static unsigned int PlayerLastTimingFrame[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ /**
+ * Save the info we got from the event.
+ */
+ PlayerMaxAheads[event->ID] = event->Data.ResponseTime2.MaxAhead;
+ PlayerLatencyMode[event->ID] = event->Data.ResponseTime2.LatencyLevel;
+ PlayerLastTimingFrame[event->ID] = event->Frame;
+
+ /**
+ * Now loop all the players and find the worst one latency-wise.
+ */
+ unsigned char latency_mode = 0;
+ unsigned int max_ahead = 0;
+
+ for (size_t i = 0; i < std::size(PlayerMaxAheads); i++)
+ {
+ if (PlayerLastTimingFrame[i] + SendResponseTimeInterval * 4 < Frame)
+ {
+ PlayerMaxAheads[i] = 0;
+ PlayerLatencyMode[i] = 0;
+ }
+ else
+ {
+ max_ahead = PlayerMaxAheads[i] > max_ahead ? PlayerMaxAheads[i] : max_ahead;
+ if (PlayerLatencyMode[i] > latency_mode)
+ latency_mode = PlayerLatencyMode[i];
+ }
+ }
+
+ /**
+ * The worst determines the settings for all the players.
+ */
+ WorstMaxAhead = max_ahead;
+ LatencyLevel::Apply(latency_mode);
+}
diff --git a/src/cncnet/cncnet4/cncnet4_globals.cpp b/src/spawner/net/protocolzero.h
similarity index 65%
rename from src/cncnet/cncnet4/cncnet4_globals.cpp
rename to src/spawner/net/protocolzero.h
index 3dca2d98d..ab5559326 100644
--- a/src/cncnet/cncnet4/cncnet4_globals.cpp
+++ b/src/spawner/net/protocolzero.h
@@ -4,11 +4,11 @@
*
* @project Vinifera
*
- * @file CNCNET4_GLOBALS.CPP
+ * @file PROTOCOLZERO.H
*
- * @author CCHyper
+ * @author Belonit, ZivDero
*
- * @brief CnCNet4 global values.
+ * @brief Protocol zero.
*
* @license Vinifera is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -25,31 +25,27 @@
* If not, see .
*
******************************************************************************/
-#include "cncnet4_globals.h"
-#include "cncnet4.h"
+#pragma once
+class ViniferaEventClass;
-/**
- * Is the CnCNet4 interface active?
- */
-bool CnCNet4::IsEnabled = false;
-
-/**
- * The host name (Must be running a instance of the dedicated server).
- */
-char CnCNet4::Host[256] = { "server.cncnet.org" };
-unsigned CnCNet4::Port = 9001;
/**
- * Clients connect to each other rather than the server?
+ * ProtocolZero
+ *
+ * This class is contains methods and the state of Protocol 0.
*/
-bool CnCNet4::Peer2Peer = false;
-
-bool CnCNet4::IsDedicated = false;
+class ProtocolZero
+{
+private:
+ static constexpr int SendResponseTimeInterval = 30;
-/**
- * Use the UDP interface instead of IPX?
- */
-bool CnCNet4::UseUDP = true;
+public:
+ static bool Enable;
+ static bool GetRealMaxAhead;
+ static unsigned char MaxLatencyLevel;
+ static unsigned int WorstMaxAhead;
-struct sockaddr_in CnCNet4::Server;
+ static void Send_Response_Time();
+ static void Handle_Response_Time(ViniferaEventClass* event);
+};
diff --git a/src/spawner/net/protocolzero_hooks.cpp b/src/spawner/net/protocolzero_hooks.cpp
new file mode 100644
index 000000000..6e3dfbdca
--- /dev/null
+++ b/src/spawner/net/protocolzero_hooks.cpp
@@ -0,0 +1,340 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file PROTOCOLZERO_HOOKS.CPP
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for protocol zero.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "protocolzero_hooks.h"
+
+#include "hooker.h"
+#include "hooker_macros.h"
+#include "latencylevel.h"
+#include "protocolzero.h"
+#include "tibsun_globals.h"
+#include "session.h"
+#include "viniferaevent/viniferaevent.h"
+#include "ipxmgr.h"
+#include "scenario.h"
+#include "spawner.h"
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class IPXManagerClassExt : public IPXManagerClass
+{
+public:
+ void _Set_Timing(unsigned long retrydelta, unsigned long maxretries, unsigned long timeout, bool global = true);
+ unsigned long _Response_Time();
+};
+
+
+/**
+ * Patch to log network parameters.
+ *
+ * @author: ZivDero
+ */
+void IPXManagerClassExt::_Set_Timing(unsigned long retrydelta, unsigned long maxretries, unsigned long timeout, bool global)
+{
+ if (ProtocolZero::Enable) {
+ DEBUG_INFO("[Spawner] NewRetryDelta = %d, NewRetryTimeout = %d, FrameSendRate = %d, CurentLatencyLevel = %d\n"
+ , retrydelta
+ , maxretries
+ , Session.FrameSendRate
+ , LatencyLevel::CurentLatencyLevel
+ );
+ }
+
+ /**
+ * Vanilla function.
+ */
+ DEBUG_INFO("RetryDelta = %d\n", retrydelta);
+ DEBUG_INFO("MaxAhead is %d\n", Session.MaxAhead);
+
+ RetryDelta = retrydelta;
+ MaxRetries = maxretries;
+ Timeout = timeout;
+
+ if (global) {
+ Set_External_Timing(RetryDelta, MaxRetries, Timeout);
+ }
+
+ for (int i = 0; i < NumConnections; i++) {
+ Connection[i]->Set_Retry_Delta(RetryDelta);
+ Connection[i]->Set_Max_Retries(MaxRetries);
+ Connection[i]->Set_TimeOut(Timeout);
+ }
+}
+
+
+/**
+ * Patch IPXManagerClass to return our MaxAhead when Protocol 0 is active.
+ *
+ * @author: ZivDero
+ */
+unsigned long IPXManagerClassExt::_Response_Time()
+{
+ if (ProtocolZero::Enable && !ProtocolZero::GetRealMaxAhead) {
+ return ProtocolZero::WorstMaxAhead;
+ }
+
+ // Vanilla function
+ unsigned long maxresp = 0;
+
+ for (int i = 0; i < NumConnections; i++) {
+ unsigned long resp = Connection[i]->Queue->Avg_Response_Time();
+ if (resp > maxresp) {
+ maxresp = resp;
+ }
+ }
+
+ return maxresp;
+}
+
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class MessageListClassExt : public MessageListClass
+{
+public:
+ bool _Manage();
+};
+
+
+/**
+ * Convenient patch in Main_Loop to send a Response Time event.
+ *
+ * @author: ZivDero
+ */
+bool MessageListClassExt::_Manage()
+{
+ if (ProtocolZero::Enable)
+ ProtocolZero::Send_Response_Time();
+
+ return MessageListClass::Manage();
+}
+
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class EventClassExt : public EventClass
+{
+public:
+ void _Execute_Timing();
+};
+
+
+/**
+ * Executes the adjusted Timing event.
+ *
+ * @author: ZivDero
+ */
+void EventClassExt::_Execute_Timing()
+{
+ if (!ProtocolZero::Enable)
+ Data.Timing.MaxAhead -= Scen->SpecialFlags.IsFogOfWar ? 10 : 0;
+
+ if (Data.Timing.MaxAhead > Session.MaxAhead || Data.Timing.FrameSendRate > Session.FrameSendRate)
+ {
+ NewMaxAheadFrame1 = Frame;
+ NewMaxAheadFrame2 = Data.Timing.FrameSendRate * ((Data.Timing.FrameSendRate + Data.Timing.MaxAhead + Frame - 1) / Data.Timing.FrameSendRate);
+ }
+ else
+ {
+ NewMaxAheadFrame1 = 0;
+ NewMaxAheadFrame2 = 0;
+ }
+
+ Session.DesiredFrameRate = Data.Timing.DesiredFrameRate;
+ Session.MaxAhead = Data.Timing.MaxAhead;
+ if (Session.MaxAhead > Session.MaxMaxAhead)
+ Session.MaxMaxAhead = Session.MaxAhead;
+ Session.FrameSendRate = Data.Timing.FrameSendRate;
+}
+
+
+/**
+ * Patch executing the Timing event, taking Protocol 0 into consideration.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_ProtocolZero_EventClass_Execute)
+{
+ GET_REGISTER_STATIC(EventClassExt*, e, esi);
+
+ e->_Execute_Timing();
+
+ JMP(0x004950AD);
+}
+
+
+/**
+ * Skip checking MySent, if Protocol 0 is active.
+ *
+ * @author: ZivDero
+ */
+static short& MySent = Make_Global(0x008099F0);
+DECLARE_PATCH(_ProtocolZero_Queue_AI_Multiplayer_1)
+{
+ if (ProtocolZero::Enable || MySent >= 5)
+ {
+ JMP(0x005B1A3B);
+ }
+
+ JMP(0x005B1C4C);
+}
+
+
+/**
+ * Adds the adjusted Timing event.
+ *
+ * @author: ZivDero
+ */
+static void Add_Timing_Event()
+{
+ DEBUG_INFO("[Spawner] Sending precalculated network timings on frame %d\n", Frame);
+
+ EventClass ev;
+ ev.Type = EVENT_TIMING;
+ ev.Data.Timing.DesiredFrameRate = Session.PrecalcDesiredFrameRate;
+ ev.Data.Timing.MaxAhead = Session.PrecalcMaxAhead;
+ ev.Data.Timing.FrameSendRate = ProtocolZero::Enable ? LatencyLevel::NewFrameSendRate :
+ Session.PrecalcDesiredFrameRate > 30 ? 10 : 5;
+
+ OutList.Add(ev);
+ Session.PrecalcMaxAhead = 0;
+ Session.PrecalcDesiredFrameRate = 0;
+}
+
+
+/**
+ * Patch adding the Timing event, taking Protocol 0 into consideration.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_ProtocolZero_Queue_AI_Multiplayer_2)
+{
+ Add_Timing_Event();
+ JMP(0x005B1C4C);
+}
+
+
+/**
+ * Adds the adjusted Timing event.
+ *
+ * @author: ZivDero
+ */
+static void Add_Timing_Event_2(int max_ahead)
+{
+ EventClass ev;
+ ev.Type = EVENT_TIMING;
+ ev.Data.Timing.DesiredFrameRate = Session.DesiredFrameRate;
+ ev.Data.Timing.MaxAhead = ProtocolZero::Enable ? Session.MaxAhead : (max_ahead + Scen->SpecialFlags.IsFogOfWar ? 10 : 0);
+ ev.Data.Timing.FrameSendRate = Session.FrameSendRate;
+
+ OutList.Add(ev);
+}
+
+
+/**
+ * Patch adding the Timing event, taking Protocol 0 into consideration.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_ProtocolZero_Queue_AI_Multiplayer_3)
+{
+ GET_REGISTER_STATIC(int, max_ahead, edi);
+ _asm push esi
+
+ Add_Timing_Event_2(max_ahead);
+
+ _asm pop esi
+ JMP(0x005B1BB9);
+}
+
+
+/**
+ * If Protocol 0 is enabled, allow some types of packets to come in late.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_ProtocolZero_ExecuteDoList)
+{
+ GET_REGISTER_STATIC(EventClass*, event, esi)
+
+ if (ProtocolZero::Enable)
+ {
+ if (event->Type == EVENT_EMPTY)
+ goto continue_execution;
+
+ if (event->Type == EVENT_PROCESS_TIME)
+ goto continue_execution;
+
+ if (event->Type == VEVENT_RESPONSE_TIME_2)
+ goto continue_execution;
+ }
+
+ _asm mov eax, Session
+ _asm mov eax, [eax]
+ JMP_REG(ecx, 0x005B4EAA);
+
+ continue_execution:
+ JMP(0x005B4EB7);
+}
+
+
+/**
+ * Main function for patching the hooks.
+ */
+void ProtocolZero_Hooks()
+{
+ Patch_Call(0x005091A5, &MessageListClassExt::_Manage);
+ Patch_Jump(0x005B1A2D, &_ProtocolZero_Queue_AI_Multiplayer_1);
+ Patch_Jump(0x005B1BF1, &_ProtocolZero_Queue_AI_Multiplayer_2);
+ Patch_Jump(0x005B1B7A, &_ProtocolZero_Queue_AI_Multiplayer_3);
+ Patch_Jump(0x00495013, &_ProtocolZero_EventClass_Execute);
+ Patch_Jump(0x005B4EA5, &_ProtocolZero_ExecuteDoList);
+ Patch_Jump(0x004F05B0, &IPXManagerClassExt::_Set_Timing);
+ Patch_Jump(0x004F0F00, &IPXManagerClassExt::_Response_Time);
+}
diff --git a/src/spawner/net/protocolzero_hooks.h b/src/spawner/net/protocolzero_hooks.h
new file mode 100644
index 000000000..d7fe78549
--- /dev/null
+++ b/src/spawner/net/protocolzero_hooks.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file PROTOCOLZERO_HOOKS.H
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for protocol zero.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+#pragma once
+
+
+void ProtocolZero_Hooks();
diff --git a/src/spawner/spawner.cpp b/src/spawner/spawner.cpp
new file mode 100644
index 000000000..f1e2fdd7b
--- /dev/null
+++ b/src/spawner/spawner.cpp
@@ -0,0 +1,530 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file SPAWNER.CPP
+ *
+ * @author Belonit, ZivDero
+ *
+ * @brief Multiplayer spawner class.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "spawner.h"
+#include "protocolzero.h"
+#include "latencylevel.h"
+
+#include "options.h"
+#include "house.h"
+#include "ipxmgr.h"
+#include "loadoptions.h"
+#include "scenario.h"
+#include
+
+#include "addon.h"
+#include "wspudp.h"
+#include "wwmouse.h"
+#include "ccini.h"
+#include "cncnet5_wspudp.h"
+#include "debughandler.h"
+#include "extension_globals.h"
+#include "gscreen.h"
+#include "housetype.h"
+#include "language.h"
+#include "mouse.h"
+#include "ownrdraw.h"
+#include "saveload.h"
+#include "tab.h"
+#include "WinUser.h"
+#include "sessionext.h"
+#include "tibsun_functions.h"
+#include "rules.h"
+
+
+bool Spawner::Active = false;
+std::unique_ptr Spawner::Config = nullptr;
+
+
+/**
+ * Initializes the Spawner.
+ *
+ * @author: ZivDero
+ */
+void Spawner::Init()
+{
+ Config = std::make_unique();
+
+ CCFileClass spawn_file("SPAWN.INI");
+ CCINIClass spawn_ini;
+
+ if (spawn_file.Is_Available()) {
+
+ spawn_ini.Load(spawn_file, false);
+ Config->Read_INI(spawn_ini);
+
+ }
+ else {
+ DEBUG_FATAL("SPAWN.INI not found!\n");
+ }
+}
+
+
+/**
+ * Starts the game.
+ *
+ * @author: ZivDero
+ */
+bool Spawner::Start_Game()
+{
+ if (Active)
+ return false;
+
+ Active = true;
+ GameActive = true;
+
+ Init_UI();
+ Read_Houses_And_Sides();
+
+ const bool result = Start_Scenario(Config->ScenarioName);
+
+ Prepare_Screen();
+
+ return result;
+}
+
+
+/**
+ * Starts a new scenario.
+ *
+ * @author: ZivDero
+ */
+bool Spawner::Start_Scenario(const char* scenario_name)
+{
+ /**
+ * Can't read an unnamed file, bail.
+ */
+ if (scenario_name[0] == 0 && !Config->LoadSaveGame)
+ {
+ DEBUG_INFO("[Spawner] Failed to read scenario [%s]\n", scenario_name);
+ MessageBox(MainWindow, Text_String(TXT_UNABLE_READ_SCENARIO), "Vinifera", MB_OK);
+
+ return false;
+ }
+
+ /**
+ * Turn Firestorm on, if requested.
+ */
+ Disable_Addon(ADDON_ANY);
+ if (Config->Firestorm)
+ {
+ Enable_Addon(ADDON_FIRESTORM);
+ Set_Required_Addon(ADDON_FIRESTORM);
+ }
+
+ /**
+ * Configure session options.
+ */
+ strcpy_s(Session.ScenarioFileName, 0x200, scenario_name);
+ Session.Options.ScenarioIndex = -1;
+ Session.Options.Bases = Config->Bases;
+ Session.Options.Credits = Config->Credits;
+ Session.Options.BridgeDestruction = Config->BridgeDestroy;
+ Session.Options.Goodies = Config->Crates;
+ Session.Options.ShortGame = Config->ShortGame;
+ SessionExtension->ExtOptions.IsBuildOffAlly = Config->BuildOffAlly;
+ Session.Options.GameSpeed = Config->GameSpeed;
+ Session.Options.CrapEngineers = Config->MultiEngineer;
+ Session.Options.UnitCount = Config->UnitCount;
+ Session.Options.AIPlayers = Config->AIPlayers;
+ Session.Options.AIDifficulty = Config->AIDifficulty;
+ Session.Options.AlliesAllowed = Config->AlliesAllowed;
+ Session.Options.HarvesterTruce = Config->HarvesterTruce;
+ // Session.Options.CaptureTheFlag
+ Session.Options.FogOfWar = Config->FogOfWar;
+ Session.Options.RedeployMCV = Config->MCVRedeploy;
+ std::strcpy(Session.Options.ScenarioDescription, Config->UIMapName);
+ Session.ColorIdx = static_cast(Config->Players[0].Color);
+ Session.NumPlayers = Config->HumanPlayers;
+
+ Seed = Config->Seed;
+ BuildLevel = Config->TechLevel;
+ Options.GameSpeed = Config->GameSpeed;
+
+ // Inverted for now as the sidebar hack until we reimplement loading
+ Session.IsGDI = true;// HouseTypes[Config->Players[0].House]->Get_Heap_ID();
+ //Session.IsGDI = HouseTypes[Config->Players[0].House]->Side != SIDE_NOD;
+ DEBUG_INFO("[Spawner] Session.IsGDI = %d\n", Session.IsGDI);
+
+ /**
+ * Create the player node for the local player.
+ */
+ const auto nodename = new NodeNameType();
+ Session.Players.Add(nodename);
+
+ std::strcpy(nodename->Name, Config->Players[0].Name);
+ nodename->Player.House = static_cast(Config->Players[0].House);
+ nodename->Player.Color = static_cast(Config->Players[0].Color);
+ nodename->Player.ProcessTime = -1;
+ nodename->Game.LastTime = 1;
+
+ /**
+ * Set session type.
+ */
+ if (Config->IsCampaign)
+ Session.Type = GAME_NORMAL;
+ else if (Session.NumPlayers > 1)
+ Session.Type = GAME_INTERNET; // HACK: will be set to GAME_IPX later
+ else
+ Session.Type = GAME_SKIRMISH;
+
+
+ Init_Random();
+
+ /**
+ * Start the scenario.
+ */
+ if (Session.Type == GAME_NORMAL)
+ {
+ Session.Options.Goodies = true;
+
+ const bool result = Config->LoadSaveGame ?
+ Load_Game(Config->SaveGameName) : ::Start_Scenario(scenario_name, false, static_cast(Config->CampaignID));
+
+ return result;
+ }
+ else if (Session.Type == GAME_SKIRMISH)
+ {
+ const bool result = Config->LoadSaveGame ?
+ Load_Game(Config->SaveGameName) : ::Start_Scenario(scenario_name, false, CAMPAIGN_NONE);
+
+ return result;
+ }
+ else
+ {
+ Init_Network();
+
+ bool result = Config->LoadSaveGame ?
+ Load_Game(Config->SaveGameName) : ::Start_Scenario(scenario_name, false, CAMPAIGN_NONE);
+
+ if (!result)
+ return false;
+
+ Session.Type = GAME_IPX;
+
+ if (Config->LoadSaveGame && !Reconcile_Players())
+ return false;
+
+ if (!Session.Create_Connections())
+ return false;
+
+ return true;
+ }
+}
+
+
+/**
+ * Loads a saved game.
+ *
+ * @author: ZivDero
+ */
+bool Spawner::Load_Game(const char* file_name)
+{
+ if (!file_name[0] || !::Load_Game(file_name))
+ {
+ DEBUG_INFO("[Spawner] Failed to load savegame [%s]\n", file_name);
+ MessageBox(MainWindow, Text_String(TXT_ERROR_LOADING_GAME), "Vinifera", MB_OK);
+
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Initializes everything necessary for an MP game.
+ *
+ * @author: ZivDero
+ */
+void Spawner::Init_Network()
+{
+ const unsigned short tunnel_id = htons(Config->TunnelId);
+ const unsigned long tunnel_ip = inet_addr(Config->TunnelIp);
+ const unsigned short tunnel_port = htons(Config->TunnelPort);
+
+ /**
+ * Create the UDP interface.
+ * This needs to happen before we set up player nodes,
+ * because it contains player connection data.
+ */
+ const auto udp_interface = new CnCNet5UDPInterfaceClass(tunnel_id, tunnel_ip, tunnel_port, true);
+ PacketTransport = udp_interface;
+
+ PlanetWestwoodPortNumber = tunnel_port ? 0 : Config->ListenPort;
+
+ /**
+ * Set up the player nodes.
+ */
+ const char max_players = std::size(Config->Players);
+ for (char player_index = 1; player_index < max_players; player_index++)
+ {
+ const auto player = &Config->Players[player_index];
+ if (!player->IsHuman)
+ continue;
+
+ const auto nodename = new NodeNameType();
+ Session.Players.Add(nodename);
+
+ std::strcpy(nodename->Name, player->Name);
+ nodename->Player.House = static_cast(player->House);
+ nodename->Player.Color = static_cast(player->Color);
+ nodename->Player.ProcessTime = -1;
+ nodename->Game.LastTime = 1;
+
+ std::memset(&nodename->Address, 0, sizeof(nodename->Address));
+ std::memcpy(&nodename->Address.NetworkNumber, &player_index, sizeof(player_index));
+ std::memcpy(&nodename->Address.NodeAddress, &player_index, sizeof(player_index));
+
+ const auto ip = inet_addr(player->Ip);
+ const auto port = htons(player->Port);
+ udp_interface->AddressList[player_index - 1].IP = ip;
+ udp_interface->AddressList[player_index - 1].Port = port;
+ if (port != Config->ListenPort)
+ udp_interface->PortHack = false;
+ }
+
+ /**
+ * Now set up the rest of the network stuff.
+ */
+ PacketTransport->Init();
+ PacketTransport->Open_Socket(0);
+ PacketTransport->Start_Listening();
+ PacketTransport->Discard_In_Buffers();
+ PacketTransport->Discard_Out_Buffers();
+ Ipx.Set_Timing(60, -1, 600, true);
+
+ PlanetWestwoodStartTime = time(nullptr);
+ GameFSSKU = 0x1C00;
+ GameSKU = 0x1D00;
+
+ /**
+ * Set up protocol stuff.
+ */
+ ProtocolZero::Enable = (Config->Protocol == 0);
+ if (ProtocolZero::Enable)
+ {
+ Session.FrameSendRate = 2;
+ Session.PrecalcMaxAhead = Config->PreCalcMaxAhead;
+ ProtocolZero::MaxLatencyLevel = std::clamp(
+ Config->MaxLatencyLevel,
+ static_cast(LATENCY_LEVEL_1),
+ static_cast(LATENCY_LEVEL_MAX));
+ }
+ else
+ {
+ Session.FrameSendRate = Config->FrameSendRate;
+ }
+
+ Session.MaxAhead = Config->MaxAhead == -1
+ ? Session.FrameSendRate * 6
+ : Config->MaxAhead;
+
+ /**
+ * Miscellaneous network settings.
+ */
+ Session.MaxMaxAhead = 0;
+ Session.CommProtocol = 2;
+ Session.LatencyFudge = 0;
+ Session.DesiredFrameRate = 60;
+ TournamentGameType = static_cast(Config->Tournament);
+ PlanetWestwoodGameID = Config->WOLGameID;
+ FrameSyncSettings[GAME_IPX].Timeout = Config->ReconnectTimeout;
+
+ /**
+ * For Quick Match, make sure MPDebug is off so that players can't cheat with it.
+ */
+ if (Config->QuickMatch)
+ {
+ Session.MPlayerDebug = false;
+ }
+
+ ::Init_Network();
+}
+
+
+/**
+ * Reconciles loaded data with the "Players" vector.
+ *
+ * This function is for supporting loading a saved multiplayer game.
+ * When the game is loaded, we have to figure out which house goes with
+ * which entry in the Players vector. We also have to figure out if
+ * everyone who was originally in the game is still with us, and if not,
+ * turn their stuff over to the computer.
+ */
+bool Spawner::Reconcile_Players()
+{
+ int i;
+ bool found;
+ int house;
+ HouseClass* housep;
+
+ /**
+ * If there are no players, there's nothing to do.
+ */
+ if (Session.Players.Count() == 0)
+ return true;
+
+ /**
+ * Make sure every name we're connected to can be found in a House.
+ */
+ for (i = 0; i < Session.Players.Count(); i++) {
+ found = false;
+ for (house = 0; house < Session.Players.Count(); house++) {
+ housep = Houses[house];
+ if (!housep) {
+ continue;
+ }
+
+ if (!stricmp(Session.Players[i]->Name, housep->IniName)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+
+ /**
+ * Loop through all Houses; if we find a human-owned house that we're
+ * not connected to, turn it over to the computer.
+ */
+ for (house = 0; house < Session.Players.Count(); house++) {
+ housep = Houses[house];
+ if (!housep) {
+ continue;
+ }
+
+ /**
+ * Skip this house if it wasn't human to start with.
+ */
+ if (!housep->IsHuman) {
+ continue;
+ }
+
+ /**
+ * Try to find this name in the Players vector; if it's found, set
+ * its ID to this house.
+ */
+ found = false;
+ for (i = 0; i < Session.Players.Count(); i++) {
+ if (!stricmp(Session.Players[i]->Name, housep->IniName)) {
+ found = true;
+ Session.Players[i]->Player.ID = static_cast(house);
+ break;
+ }
+ }
+
+ /**
+ * If this name wasn't found, remove it
+ */
+ if (!found) {
+
+ /**
+ * Turn the player's house over to the computer's AI
+ */
+ housep->IsHuman = false;
+ housep->IsStarted = true;
+ housep->IQ = Rule->MaxIQ;
+
+ static char buffer[HOUSE_NAME_MAX + 1];
+ std::snprintf(buffer, sizeof(buffer), "%s (AI)", housep->IniName);
+ std::strncpy(housep->IniName, buffer, sizeof(housep->IniName));
+ //strcpy(housep->IniName, Fetch_String(TXT_COMPUTER));
+
+ Session.NumPlayers--;
+ }
+ }
+
+ /**
+ * If all went well, our Session.NumPlayers value should now equal the value
+ * from the saved game, minus any players we removed.
+ */
+ if (Session.NumPlayers == Session.Players.Count()) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+
+/**
+ * Initializes some things for OwnerDraw UI.
+ *
+ * @author: ZivDero
+ */
+void Spawner::Init_UI()
+{
+ OwnerDraw::Init_UI_Color_Stuff_58F060();
+
+ if (!OwnerDraw::UIInitialized)
+ {
+ OwnerDraw::Init_Glow_Colors();
+ OwnerDraw::Load_Graphics();
+ OwnerDraw::UIInitialized = true;
+ }
+}
+
+
+/**
+ * Prepares the screen.
+ *
+ * @author: ZivDero
+ */
+void Spawner::Prepare_Screen()
+{
+ WWMouse->Hide_Mouse();
+
+ HiddenSurface->Fill(TBLACK);
+ GScreenClass::Blit(true, HiddenSurface);
+ LogicSurface = HiddenSurface;
+
+ WWMouse->Show_Mouse();
+
+ Map.MouseClass::Set_Default_Mouse(MOUSE_NO_MOVE, false);
+ Map.MouseClass::Revert_Mouse_Shape();
+
+ Map.TabClass::Activate(1);
+ Map.SidebarClass::Flag_To_Redraw();
+}
+
+
+/**
+ * Reads Houses and Sides to Rules so that we can use them to choose a loading screen.
+ *
+ * @author: ZivDero
+ */
+void Spawner::Read_Houses_And_Sides()
+{
+ Rule->Houses(*RuleINI);
+ Rule->Sides(*RuleINI);
+
+ for (int i = 0; i < Houses.Count(); i++)
+ Houses[i]->Read_INI(*RuleINI);
+}
diff --git a/src/cncnet/cncnet5/cncnet5_globals.h b/src/spawner/spawner.h
similarity index 60%
rename from src/cncnet/cncnet5/cncnet5_globals.h
rename to src/spawner/spawner.h
index 7ad79c15e..016bcd099 100644
--- a/src/cncnet/cncnet5/cncnet5_globals.h
+++ b/src/spawner/spawner.h
@@ -4,11 +4,11 @@
*
* @project Vinifera
*
- * @file CNCNET_GLOBALS.H
+ * @file SPAWNER.H
*
- * @author CCHyper
+ * @author Belonit, ZivDero
*
- * @brief Global values and types used for the CnCNet5 system.
+ * @brief Multiplayer spawner class.
*
* @license Vinifera is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -27,37 +27,37 @@
******************************************************************************/
#pragma once
-#include
+#include "spawnerconfig.h"
+#include
-namespace CnCNet5
-{
-
-typedef struct TunnelInfoStruct
+/**
+ * This class contains all logic for spawning players in-game (usually via the client).
+ */
+class Spawner
{
- unsigned long ID;
- unsigned long IP;
- unsigned short Port;
- bool PortHack;
+public:
+ Spawner() = delete;
- bool Is_Valid() const { return !(ID == -1 || IP == -1 || Port == -1); }
+ static bool Active;
-} TunnelInfoStruct;
+private:
+ static std::unique_ptr Config;
+public:
+ static SpawnerConfig* Get_Config() { return Config.get(); }
-/**
- * Has the CnCNet5 system been activated?
- */
-extern bool IsActive;
+ static void Init();
+ static bool Start_Game();
-/**
- * Is the tunnel system active (set when tunnel information has been provided)?
- */
-extern bool IsTunnelActive;
+private:
+ static bool Start_Scenario(const char* scenario_name);
+ static bool Load_Game(const char* file_name);
-/**
- * CnCNet5 UDP Tunnel info.
- */
-extern TunnelInfoStruct TunnelInfo;
+ static void Init_Network();
+ static bool Reconcile_Players();
+ static void Init_UI();
+ static void Prepare_Screen();
+ static void Read_Houses_And_Sides();
};
diff --git a/src/spawner/spawner_hooks.cpp b/src/spawner/spawner_hooks.cpp
new file mode 100644
index 000000000..136487b9d
--- /dev/null
+++ b/src/spawner/spawner_hooks.cpp
@@ -0,0 +1,177 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file SPAWNER_HOOKS.CPP
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for the multiplayer spawner class.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "spawner_hooks.h"
+
+#include "autosurrender_hooks.h"
+#include "hooker.h"
+#include "hooker_macros.h"
+#include "session.h"
+#include "spawner.h"
+#include "house.h"
+#include "housetype.h"
+#include "multiscore.h"
+#include "protocolzero_hooks.h"
+#include "quickmatch_hooks.h"
+#include "savedgamesdir_hooks.h"
+#include "spectator_hooks.h"
+#include "statistics_hooks.h"
+#include "vinifera_globals.h"
+
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class SessionClassExt : public SessionClass
+{
+public:
+ void _Read_Scenario_Descriptions();
+};
+
+
+/**
+ * Patches Read_Scenario_Descriptions to do nothing when the spawner is active.
+ *
+ * @author: ZivDero
+ */
+void SessionClassExt::_Read_Scenario_Descriptions()
+{
+ if (Spawner::Active)
+ return;
+
+ SessionClass::Read_Scenario_Descriptions();
+}
+
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor.
+ *
+ * @note: All functions must not be virtual and must also be prefixed
+ * with "_" to prevent accidental virtualization.
+ */
+class HouseClassExt : public HouseClass
+{
+public:
+ void _Computer_Paranoid() {}
+};
+
+
+/**
+ * Patches Expert AI not the consider allies as enemies.
+ *
+ * @author: ZivDero
+ */
+DECLARE_PATCH(_HouseClass_Expert_AI_Check_Allies)
+{
+ GET_REGISTER_STATIC(HouseClass*, this_ptr, edi);
+ GET_REGISTER_STATIC(HouseClass*, house, esi);
+
+ if (house != this_ptr && !house->Class->IsMultiplayPassive && !house->IsDefeated && this_ptr->Is_Ally(house))
+ {
+ JMP(0x004C06F7);
+ }
+
+ JMP(0x004C0777);
+}
+
+
+/**
+ * Patches the score screen to be skipped if SkipScoreScreen is set.
+ *
+ * @author: ZivDero
+ */
+static void MultiScore_Wrapper()
+{
+ if (!Spawner::Get_Config()->SkipScoreScreen)
+ MultiScore::Presentation();
+}
+
+
+/**
+ * Main function for patching the hooks.
+ */
+void Spawner_Hooks()
+{
+ Patch_Call(0x004629D1, &Spawner::Start_Game); // Main_Game
+ Patch_Call(0x00462B8B, &Spawner::Start_Game); // Main_Game
+
+ /**
+ * The spawner allows player to jump right into a game, so no need to
+ * show the startup movies.
+ */
+ Vinifera_SkipLogoMovies = true;
+ Vinifera_SkipStartupMovies = true;
+
+ Patch_Dword(0x005DB794 + 1, Spawner::Get_Config()->ConnTimeout); // Set ConnTimeout
+
+ /**
+ * Skip some checks inside HouseClass::MPlayer_Defeated to make the game continue
+ * even if there are only allied AI players left in Skirmish.
+ */
+ Patch_Jump(0x004BF7B6, 0x004BF7BF);
+ Patch_Jump(0x004BF7F0, 0x004BF7F9);
+
+ /**
+ * Remove calls to SessionClass::Read_Scenario_Descriptions() when the
+ * spawner is active. This will speed up the initialisation and loading
+ * process, as PKT and MPR files are not required when using the spawner.
+ */
+ Patch_Call(0x004E8910, &SessionClassExt::_Read_Scenario_Descriptions); // New_Main_Menu
+ Patch_Call(0x00564BAE, &SessionClassExt::_Read_Scenario_Descriptions); // Select_MPlayer_Game
+ Patch_Call(0x0057FE2A, &SessionClassExt::_Read_Scenario_Descriptions); // NewMenuClass::Process_Game_Select
+ Patch_Call(0x0058037C, &SessionClassExt::_Read_Scenario_Descriptions); // NewMenuClass::
+ Patch_Call(0x005ED477, &SessionClassExt::_Read_Scenario_Descriptions); // SessionClass::One_Time
+
+ Patch_Jump(0x004C06EF, &_HouseClass_Expert_AI_Check_Allies);
+ Patch_Jump(0x004C3630, &HouseClassExt::_Computer_Paranoid); // Disable paranoid computer behavior
+
+ /**
+ * SkipScoreScreen feature.
+ */
+ Patch_Call(0x005DC9DA, &MultiScore_Wrapper);
+ Patch_Call(0x005DCD98, &MultiScore_Wrapper);
+
+ /**
+ * Hooks for various sub-modules.
+ */
+ ProtocolZero_Hooks();
+ Spectator_Hooks();
+ QuickMatch_Hooks();
+ AutoSurrender_Hooks();
+ Statistics_Hooks();
+ SavedGamesDir_Hooks();
+}
diff --git a/src/spawner/spawner_hooks.h b/src/spawner/spawner_hooks.h
new file mode 100644
index 000000000..a6db6004e
--- /dev/null
+++ b/src/spawner/spawner_hooks.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file SPAWNER_HOOKS.CPP
+ *
+ * @author ZivDero
+ *
+ * @brief Contains the hooks for the multiplayer spawner class.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+#pragma once
+
+
+void Spawner_Hooks();
diff --git a/src/spawner/spawnerconfig.cpp b/src/spawner/spawnerconfig.cpp
new file mode 100644
index 000000000..b63f89871
--- /dev/null
+++ b/src/spawner/spawnerconfig.cpp
@@ -0,0 +1,231 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file SPAWNERCONFIG.CPP
+ *
+ * @author Belonit, ZivDero
+ *
+ * @brief Configuration of the multiplayer spawner.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+
+#include "spawnerconfig.h"
+
+#include "ccini.h"
+
+
+/**
+ * Reads spawner config from the INI.
+ *
+ * @author: Belonit, ZivDero
+ */
+void SpawnerConfig::Read_INI(CCINIClass& spawn_ini)
+{
+ static char const* const SETTINGS = "Settings";
+ static char const* const TUNNEL = "Tunnel";
+
+ /**
+ * Game Mode Options
+ */
+ Bases = spawn_ini.Get_Bool(SETTINGS, "Bases", Bases);
+ Credits = spawn_ini.Get_Int(SETTINGS, "Credits", Credits);
+ BridgeDestroy = spawn_ini.Get_Bool(SETTINGS, "BridgeDestroy", BridgeDestroy);
+ Crates = spawn_ini.Get_Bool(SETTINGS, "Crates", Crates);
+ ShortGame = spawn_ini.Get_Bool(SETTINGS, "ShortGame", ShortGame);
+ BuildOffAlly = spawn_ini.Get_Bool(SETTINGS, "BuildOffAlly", BuildOffAlly);
+ GameSpeed = spawn_ini.Get_Int(SETTINGS, "GameSpeed", GameSpeed);
+ MultiEngineer = spawn_ini.Get_Bool(SETTINGS, "MultiEngineer", MultiEngineer);
+ UnitCount = spawn_ini.Get_Int(SETTINGS, "UnitCount", UnitCount);
+ AIPlayers = spawn_ini.Get_Int(SETTINGS, "AIPlayers", AIPlayers);
+ AIDifficulty = spawn_ini.Get_Int(SETTINGS, "AIDifficulty", AIDifficulty);
+ AlliesAllowed = spawn_ini.Get_Bool(SETTINGS, "AlliesAllowed", AlliesAllowed);
+ HarvesterTruce = spawn_ini.Get_Bool(SETTINGS, "HarvesterTruce", HarvesterTruce);
+ FogOfWar = spawn_ini.Get_Bool(SETTINGS, "FogOfWar", FogOfWar);
+ MCVRedeploy = spawn_ini.Get_Bool(SETTINGS, "MCVRedeploy", MCVRedeploy);
+
+ /**
+ * Savegame Options
+ */
+ LoadSaveGame = spawn_ini.Get_Bool(SETTINGS, "LoadSaveGame", LoadSaveGame);
+ /* SavedGamesDir */ spawn_ini.Get_String(SETTINGS, "SavedGamesDir", SavedGamesDir, SavedGamesDir, sizeof(SavedGamesDir));
+ /* SaveGameName */ spawn_ini.Get_String(SETTINGS, "SaveGameName", SaveGameName, SaveGameName, sizeof(SaveGameName));
+
+ /**
+ * Scenario Options
+ */
+ Seed = spawn_ini.Get_Int(SETTINGS, "Seed", Seed);
+ TechLevel = spawn_ini.Get_Int(SETTINGS, "TechLevel", TechLevel);
+ IsCampaign = spawn_ini.Get_Bool(SETTINGS, "IsSinglePlayer", IsCampaign);
+ CampaignID = spawn_ini.Get_Int(SETTINGS, "CampaignID", CampaignID);
+ Tournament = spawn_ini.Get_Int(SETTINGS, "Tournament", Tournament);
+ WOLGameID = spawn_ini.Get_Int(SETTINGS, "GameID", WOLGameID);
+ /* ScenarioName */ spawn_ini.Get_String(SETTINGS, "Scenario", ScenarioName, ScenarioName, sizeof(ScenarioName));
+ /* MapHash */ spawn_ini.Get_String(SETTINGS, "MapHash", MapHash, MapHash, sizeof(MapHash));
+ spawn_ini.Get_String(SETTINGS, "UIMapName", UIMapName, UIMapName, sizeof(UIMapName));
+
+ /**
+ * Network Options
+ */
+ Protocol = spawn_ini.Get_Int(SETTINGS, "Protocol", Protocol);
+ FrameSendRate = spawn_ini.Get_Int(SETTINGS, "FrameSendRate", FrameSendRate);
+ ReconnectTimeout = spawn_ini.Get_Int(SETTINGS, "ReconnectTimeout", ReconnectTimeout);
+ ConnTimeout = spawn_ini.Get_Int(SETTINGS, "ConnTimeout", ConnTimeout);
+ MaxAhead = spawn_ini.Get_Int(SETTINGS, "MaxAhead", MaxAhead);
+ PreCalcMaxAhead = spawn_ini.Get_Int(SETTINGS, "PreCalcMaxAhead", PreCalcMaxAhead);
+ MaxLatencyLevel = spawn_ini.Get_Int(SETTINGS, "MaxLatencyLevel", MaxLatencyLevel);
+
+ /**
+ * Tunnel Options
+ */
+ TunnelId = spawn_ini.Get_Int(SETTINGS, "Port", TunnelId);
+ ListenPort = spawn_ini.Get_Int(SETTINGS, "Port", ListenPort);
+ /* TunnelIp */ spawn_ini.Get_String(TUNNEL, "Ip", TunnelIp, TunnelIp, sizeof(TunnelIp));
+ TunnelPort = spawn_ini.Get_Int(TUNNEL, "Port", TunnelPort);
+
+ /**
+ * Player and House Options
+ */
+ for (int i = 0; i < std::size(Players); ++i)
+ {
+ Players[i].Read_INI(spawn_ini, i);
+ if (Players[i].IsHuman)
+ HumanPlayers++;
+
+ Houses[i].Read_INI(spawn_ini, i);
+ }
+
+ /**
+ * Extended Options
+ */
+ Firestorm = spawn_ini.Get_Bool(SETTINGS, "Firestorm", Firestorm);
+ QuickMatch = spawn_ini.Get_Bool(SETTINGS, "QuickMatch", QuickMatch);
+ SkipScoreScreen = spawn_ini.Get_Bool(SETTINGS, "SkipScoreScreen", SkipScoreScreen);
+ WriteStatistics = spawn_ini.Get_Bool(SETTINGS, "WriteStatistics", WriteStatistics);
+ AINamesByDifficulty = spawn_ini.Get_Bool(SETTINGS, "AINamesByDifficulty", AINamesByDifficulty);
+ CoachMode = spawn_ini.Get_Bool(SETTINGS, "CoachMode", CoachMode);
+ AutoSurrender = spawn_ini.Get_Bool(SETTINGS, "AutoSurrender", AutoSurrender);
+}
+
+
+static constexpr char* PlayerSectionArray[8] = {
+ "Settings",
+ "Other1",
+ "Other2",
+ "Other3",
+ "Other4",
+ "Other5",
+ "Other6",
+ "Other7"
+};
+
+
+static constexpr char* MultiTagArray[8] = {
+ "Multi1",
+ "Multi2",
+ "Multi3",
+ "Multi4",
+ "Multi5",
+ "Multi6",
+ "Multi7",
+ "Multi8"
+};
+
+
+static constexpr char* AlliancesSectionArray[8] = {
+ "Multi1_Alliances",
+ "Multi2_Alliances",
+ "Multi3_Alliances",
+ "Multi4_Alliances",
+ "Multi5_Alliances",
+ "Multi6_Alliances",
+ "Multi7_Alliances",
+ "Multi8_Alliances"
+};
+
+
+static constexpr char* AlliancesTagArray[8] = {
+ "HouseAllyOne",
+ "HouseAllyTwo",
+ "HouseAllyThree",
+ "HouseAllyFour",
+ "HouseAllyFive",
+ "HouseAllySix",
+ "HouseAllySeven",
+ "HouseAllyEight"
+};
+
+
+/**
+ * Reads player's config from the INI.
+ *
+ * @author: Belonit, ZivDero
+ */
+void SpawnerConfig::PlayerConfig::Read_INI(CCINIClass& spawn_ini, int index)
+{
+ if (index >= MAX_PLAYERS)
+ return;
+
+ const char* SECTION = PlayerSectionArray[index];
+ const char* MULTI_TAG = MultiTagArray[index];
+
+ if (spawn_ini.Is_Present(SECTION))
+ {
+ this->IsHuman = true;
+ this->Difficulty = -1;
+
+ spawn_ini.Get_String(SECTION, "Name", this->Name, this->Name, sizeof(this->Name));
+
+ this->Color = spawn_ini.Get_Int(SECTION, "Color", this->Color);
+ this->House = spawn_ini.Get_Int(SECTION, "Side", this->House);
+
+ spawn_ini.Get_String(SECTION, "Ip", this->Ip, this->Ip, sizeof(this->Ip));
+ this->Port = spawn_ini.Get_Int(SECTION, "Port", this->Port);
+ }
+ else if (!IsHuman)
+ {
+ this->Color = spawn_ini.Get_Int("HouseColors", MULTI_TAG, this->Color);
+ this->House = spawn_ini.Get_Int("HouseCountries", MULTI_TAG, this->House);
+ this->Difficulty = spawn_ini.Get_Int("HouseHandicaps", MULTI_TAG, this->Difficulty);
+ }
+}
+
+
+/**
+ * Reads house's config from the INI.
+ *
+ * @author: Belonit, ZivDero
+ */
+void SpawnerConfig::HouseConfig::Read_INI(CCINIClass& spawn_ini, int index)
+{
+ if (index >= MAX_PLAYERS)
+ return;
+
+ const char* ALLIANCES = AlliancesSectionArray[index];
+ const char* MULTI_TAG = MultiTagArray[index];
+
+ this->IsSpectator = spawn_ini.Get_Bool("IsSpectator", MULTI_TAG, this->IsSpectator);
+ this->SpawnLocation = spawn_ini.Get_Int("SpawnLocations", MULTI_TAG, SpawnLocation);
+
+ if (spawn_ini.Is_Present(ALLIANCES))
+ {
+ for(int i = 0; i < 8; i++)
+ this->Alliances[i] = spawn_ini.Get_Int(ALLIANCES, AlliancesTagArray[i], this->Alliances[i]);
+ }
+}
diff --git a/src/spawner/spawnerconfig.h b/src/spawner/spawnerconfig.h
new file mode 100644
index 000000000..73cf3dc79
--- /dev/null
+++ b/src/spawner/spawnerconfig.h
@@ -0,0 +1,246 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file SPAWNERCONFIG.H
+ *
+ * @author Belonit, ZivDero
+ *
+ * @brief Configuration of the multiplayer spawner.
+ *
+ * @license Vinifera 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, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera 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, see .
+ *
+ ******************************************************************************/
+#pragma once
+
+#include "abstractext.h"
+
+class CCINIClass;
+
+
+/**
+ * This class contains all the configuration for the spawner, usually read from SPAWN.INI.
+ */
+class SpawnerConfig
+{
+ /**
+ * Used to create NodeNameType
+ * The order of entries may differ from HouseConfig
+ */
+ struct PlayerConfig
+ {
+ bool IsHuman;
+ char Name[20];
+ int Color;
+ int House;
+ int Difficulty;
+ char Ip[0x20];
+ int Port;
+
+ PlayerConfig()
+ : IsHuman { false }
+ , Name { "" }
+ , Color { -1 }
+ , House { -1 }
+ , Difficulty { -1 }
+ , Ip { "0.0.0.0" }
+ , Port { -1 }
+ { }
+
+ void Read_INI(CCINIClass& spawn_ini, int index);
+ };
+
+ /**
+ * Used to configure the generated HouseClass
+ * Must be sorted by respective player color
+ */
+ struct HouseConfig
+ {
+ bool IsSpectator;
+ int SpawnLocation;
+ int Alliances[8];
+
+ HouseConfig()
+ : IsSpectator { false }
+ , SpawnLocation { -2 }
+ , Alliances { -1, -1, -1, -1, -1, -1, -1, -1 }
+ { }
+
+ void Read_INI(CCINIClass& spawn_ini, int index);
+ };
+
+public:
+ /**
+ * Game Mode Options
+ */
+ bool Bases;
+ int Credits;
+ bool BridgeDestroy;
+ bool Crates;
+ bool ShortGame;
+ bool BuildOffAlly;
+ int GameSpeed;
+ bool MultiEngineer;
+ int UnitCount;
+ int AIPlayers;
+ int AIDifficulty;
+ bool AlliesAllowed;
+ bool HarvesterTruce;
+ bool FogOfWar;
+ bool MCVRedeploy;
+
+ /**
+ * Savegame Options
+ */
+ bool LoadSaveGame;
+ char SavedGamesDir[MAX_PATH]; // Nested paths are also supported, e.g. "Saved Games\\Tiberian Sun"
+ char SaveGameName[60];
+
+ /**
+ * Scenario Options
+ */
+ int Seed;
+ int TechLevel;
+ bool IsCampaign;
+ int CampaignID;
+ int Tournament;
+ unsigned int WOLGameID;
+ char ScenarioName[260];
+ char MapHash[0xff];
+ char UIMapName[44];
+
+ /**
+ * Network Options
+ */
+ int Protocol;
+ int FrameSendRate;
+ int ReconnectTimeout;
+ int ConnTimeout;
+ int MaxAhead;
+ int PreCalcMaxAhead;
+ unsigned char MaxLatencyLevel;
+
+ /**
+ * Tunnel Options
+ */
+ int TunnelId;
+ char TunnelIp[0x20];
+ int TunnelPort;
+ int ListenPort;
+
+ /**
+ * Player Options
+ */
+ PlayerConfig Players[8];
+ int HumanPlayers;
+
+ /**
+ * House Options
+ */
+ HouseConfig Houses[8];
+
+ /**
+ * Extended Options
+ */
+ bool Firestorm;
+ bool QuickMatch;
+ bool SkipScoreScreen;
+ bool WriteStatistics;
+ bool AINamesByDifficulty;
+ bool CoachMode;
+ bool AutoSurrender;
+
+ SpawnerConfig()
+ : Bases { true }
+ , Credits { 10000 }
+ , BridgeDestroy { true }
+ , Crates { false }
+ , ShortGame { false }
+ , BuildOffAlly { false }
+ , GameSpeed { 0 }
+ , MultiEngineer { false }
+ , UnitCount { 0 }
+ , AIPlayers { 0 }
+ , AIDifficulty { 1 }
+ , AlliesAllowed { false }
+ , HarvesterTruce { false }
+ , FogOfWar { false }
+ , MCVRedeploy { true }
+
+ , LoadSaveGame { false }
+ , SavedGamesDir { "Saved Games" }
+ , SaveGameName { "" }
+
+ , Seed { 0 }
+ , TechLevel { 10 }
+ , IsCampaign { false }
+ , CampaignID { -1 }
+ , Tournament { 0 }
+ , WOLGameID { 0xDEADBEEF }
+ , ScenarioName { "spawnmap.ini" }
+ , MapHash { "" }
+ , UIMapName { "" }
+
+ , Protocol { 2 }
+ , FrameSendRate { 4 }
+ , ReconnectTimeout { 2400 }
+ , ConnTimeout { 3600 }
+ , MaxAhead { -1 }
+ , PreCalcMaxAhead { 0 }
+ , MaxLatencyLevel { 0xFF }
+
+ , TunnelId { 0 }
+ , TunnelIp { "0.0.0.0" }
+ , TunnelPort { 0 }
+ , ListenPort { 1234 }
+
+ , Players {
+ PlayerConfig(),
+ PlayerConfig(),
+ PlayerConfig(),
+ PlayerConfig(),
+
+ PlayerConfig(),
+ PlayerConfig(),
+ PlayerConfig(),
+ PlayerConfig()
+ }
+ , HumanPlayers(0)
+
+ , Houses {
+ HouseConfig(),
+ HouseConfig(),
+ HouseConfig(),
+ HouseConfig(),
+
+ HouseConfig(),
+ HouseConfig(),
+ HouseConfig(),
+ HouseConfig()
+ }
+
+ , Firestorm { true }
+ , QuickMatch { false }
+ , SkipScoreScreen { false }
+ , WriteStatistics { false }
+ , AINamesByDifficulty { false }
+ , CoachMode { false }
+ , AutoSurrender { true }
+ { }
+
+ void Read_INI(CCINIClass& spawn_ini);
+};
diff --git a/src/vinifera/vinifera_functions.cpp b/src/vinifera/vinifera_functions.cpp
index eeef2a41a..956ff02eb 100644
--- a/src/vinifera/vinifera_functions.cpp
+++ b/src/vinifera/vinifera_functions.cpp
@@ -29,9 +29,6 @@
#include "vinifera_globals.h"
#include "vinifera_newdel.h"
#include "tibsun_globals.h"
-#include "cncnet4.h"
-#include "cncnet4_globals.h"
-#include "cncnet5_globals.h"
#include "rulesext.h"
#include "ccfile.h"
#include "ccini.h"
@@ -60,6 +57,8 @@
#include "rocketlocomotion.h"
#include "setup_hooks.h"
+#include "spawner.h"
+#include "spawner_hooks.h"
static DynamicVectorClass ViniferaSearchPaths;
@@ -297,6 +296,16 @@ bool Vinifera_Parse_Command_Line(int argc, char *argv[])
continue;
}
+ /**
+ * Start in spawner mode.
+ */
+ if (stricmp(string, "-SPAWN") == 0) {
+ DEBUG_INFO(" - Spawner enabled.\n");
+ Spawner::Init();
+ Spawner_Hooks();
+ continue;
+ }
+
/**
* Skip the startup videos.
*/
@@ -501,39 +510,11 @@ bool Vinifera_Startup()
ViniferaSearchPaths.Add("TS3");
#endif
- /**
- * #issue-514:
- *
- * Adds various search paths for loading files locally for the TS-Client builds only.
- *
- * #NOTE: REMOVED: Additional paths must now be set via SearchPaths in VINIFERA.INI!
- *
- * @author: CCHyper
- */
-#if 0 // #if defined(TS_CLIENT)
-
- // Only required for the TS Client builds as most projects will
- // put VINIFERA.INI in this directory.
- ViniferaSearchPaths.Add("INI");
-
- // Required for startup mix files to be found.
- ViniferaSearchPaths.Add("MIX");
-#endif
-
#if !defined(TS_CLIENT)
// Required for startup movies to be found.
ViniferaSearchPaths.Add("MOVIES");
#endif
- // REMOVED: Paths are now set via SearchPaths in VINIFERA.INI
-//#if defined(TS_CLIENT)
-// ViniferaSearchPaths.Add("MUSIC");
-// ViniferaSearchPaths.Add("SOUNDS");
-// ViniferaSearchPaths.Add("MAPS");
-// ViniferaSearchPaths.Add("MAPS\\MULTIPLAYER");
-// ViniferaSearchPaths.Add("MAPS\\MISSION");
-//#endif
-
/**
* Load Vinifera settings and overrides.
*/
@@ -614,32 +595,8 @@ bool Vinifera_Startup()
return false;
}
-#if !defined(TS_CLIENT)
- /**
- * Initialise the CnCNet4 system.
- */
- if (!CnCNet4::Init()) {
- CnCNet4::IsEnabled = false;
- DEBUG_WARNING("Failed to initialise CnCNet4, continuing without CnCNet4 support!\n");
- }
-
- /**
- * Disable CnCNet4 if CnCNet5 is active, they can not co-exist.
- */
- if (CnCNet4::IsEnabled && CnCNet5::IsActive) {
- CnCNet4::Shutdown();
- CnCNet4::IsEnabled = false;
- }
-#else
- /**
- * Client builds can only use CnCNet5.
- */
- CnCNet4::IsEnabled = false;
- //CnCNet5::IsActive = true; // Enable when new Client system is implemented.
-#endif
-
KamikazeTracker = new KamikazeTrackerClass;
-
+
return true;
}
@@ -721,13 +678,6 @@ int Vinifera_Pre_Init_Game(int argc, char *argv[])
DEV_DEBUG_WARNING("UI.INI not found!\n");
}
-#if defined(TS_CLIENT)
- /**
- * The TS Client allows player to jump right into a game, so no need to
- * show the startup movies for these builds.
- */
- Vinifera_SkipStartupMovies = true;
-#endif
/**
* Read the mouse controls and overrides.
diff --git a/src/vinifera/vinifera_hooks.cpp b/src/vinifera/vinifera_hooks.cpp
index 2d5360a95..1e4b38380 100644
--- a/src/vinifera/vinifera_hooks.cpp
+++ b/src/vinifera/vinifera_hooks.cpp
@@ -53,6 +53,7 @@
#include "spawnmanager.h"
#include "armortype.h"
#include "rockettype.h"
+#include "spawner.h"
/**
@@ -708,20 +709,6 @@ void Vinifera_Hooks()
Patch_Byte(0x0070EEAB, num);
Patch_Byte(0x0070EF0F, num);
-#if defined(TS_CLIENT)
- /**
- * Remove calls to SessionClass::Read_Scenario_Descriptions() in TS Client
- * compatable builds. This will speed up the initialisation and loading
- * process, as the reason of PKT and MPR files are not required when using
- * the Client.
- */
- Patch_Byte_Range(0x004E8901, 0x90, 5); // NewMenu::Process
- Patch_Byte_Range(0x004E8910, 0x90, 5); // ^
- Patch_Byte_Range(0x00564BA9, 0x90, 10); // Select_MPlayer_Game
- Patch_Byte_Range(0x0057FE2A, 0x90, 10); // NewMenuClass::Process_Game_Select
- Patch_Byte_Range(0x00580377, 0x90, 10); // NewMenuClass::Process_Game_Select
-#endif
-
/**
* Various patches to intercept the games object tracking and heap processing.
*/
diff --git a/src/vinifera/vinifera_saveload.cpp b/src/vinifera/vinifera_saveload.cpp
index 942ba563f..37bdff548 100644
--- a/src/vinifera/vinifera_saveload.cpp
+++ b/src/vinifera/vinifera_saveload.cpp
@@ -134,6 +134,10 @@
#include "savever.h"
#include "vinifera_savever.h"
#include "windialog.h"
+#include "fetchres.h"
+#include "spawner.h"
+#include "technoext.h"
+#include "verses.h"
/**
diff --git a/src/vinifera/vinifera_util.cpp b/src/vinifera/vinifera_util.cpp
index b9ee29085..1b8f379ff 100644
--- a/src/vinifera/vinifera_util.cpp
+++ b/src/vinifera/vinifera_util.cpp
@@ -38,7 +38,6 @@
#include "spritecollection.h"
#include "filepng.h"
#include "filepcx.h"
-#include "cncnet4_globals.h"
#include "wwfont.h"
#include "msgbox.h"
#include "minidump.h"
@@ -61,26 +60,16 @@ const char *Vinifera_Name_String()
if (_buffer[0] == '\0') {
- /**
- * Append the CnCNet version if enabled.
- */
- char *cncnet_mode = nullptr;
- if (CnCNet4::IsEnabled) {
- cncnet_mode = " (CnCNet4)";
- }
-
char *dev_mode = nullptr;
if (Vinifera_DeveloperMode) {
dev_mode = " (Dev)";
}
- if (!dev_mode && !cncnet_mode) {
+ if (!dev_mode) {
std::snprintf(_buffer, sizeof(_buffer), "Vinifera");
} else {
- std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s%s",
- cncnet_mode != nullptr ? cncnet_mode : "",
- dev_mode != nullptr ? dev_mode : "");
+ std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s", dev_mode != nullptr ? dev_mode : "");
}
#if defined(TS_CLIENT)
@@ -164,24 +153,14 @@ const char *Vinifera_Version_String()
static char _buffer[512] { '\0' };
if (_buffer[0] == '\0') {
-
- /**
- * Append the CnCNet version if enabled.
- */
- char *cncnet_mode = nullptr;
- if (CnCNet4::IsEnabled) {
- cncnet_mode = " (CnCNet4)";
- }
#ifndef RELEASE
- std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s%s - %s %s %s%s %s",
- cncnet_mode != nullptr ? cncnet_mode : "",
+ std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s - %s %s %s%s %s",
Vinifera_DeveloperMode ? " (Dev)" : "",
Vinifera_Git_Branch(), Vinifera_Git_Author(),
Vinifera_Git_Uncommitted_Changes() ? "~" : "", Vinifera_Git_Hash_Short(), Vinifera_Git_DateTime());
#else
- std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s%s - %s%s %s",
- cncnet_mode != nullptr ? cncnet_mode : "",
+ std::snprintf(_buffer, sizeof(_buffer), "Vinifera:%s - %s%s %s",
Vinifera_DeveloperMode ? " (Dev)" : "",
Vinifera_Git_Uncommitted_Changes() ? "~" : "", Vinifera_Git_Hash_Short(), Vinifera_Git_DateTime());
#endif
|