diff --git a/.gitignore b/.gitignore index cc0a1259e..9b7ffeecf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ options_cache.py libgag.a libusl.a src/glob2 +test/TestsRunner +test/WinningConditionsHarness +test/CampaignLoadHarness +testResults.xml .sconf_temp .sconsign.dblite *.o @@ -12,3 +16,10 @@ src/glob2 __pycache__/ build/ vcpkg_installed/ +.DS_Store +compile_commands.json +.cache/ +Glob2.app/ +Glob2-*.dmg +test/CampaignSelectionHarness +test/NetSendOrderDecodeTest diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..3a59c02b1 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,16 @@ +Globulation 2 -- Authors + +Original authors of Globulation 2. Their contact information was previously +included in per-file source headers; sources now carry SPDX short-form +license identifiers (see COPYING for the full GPL v3 text). + +Stephane Magnenat (2001-2008) +Luc-Olivier de Charrière (2001-2008) +Martin S. Nyffenegger (SGSL scripting language) +Bradley Arsenault (2006-2008) +Eli Dupree (2005, AIWarrush) +Leo Wandersleb (2006-2007, terrain generation) +Michiel De Muynck (2010) +Kyle Lutze (2006-2009) + +For ongoing contributors, see the project's git history. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..acbe8c326 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,152 @@ +# glob2 (Legacy C++ Codebase) + +This is the original Globulation 2 C++ codebase. It is legacy code and is not under active development. + +## Build Commands + +```bash +cd glob2 + +# Build (requires SCons, Python 3) +scons -j16 + +# Install (may need root) +scons install + +# Clean +scons -c + +# Build options +scons release=1 # Optimized release build +scons server=1 # Build YOG server only (no GUI/sound) +scons --build=/tmp/out # Out-of-source build +scons mingw=true # Windows cross-compile + +# Custom paths +scons BINDIR=/path/bin INSTALLDIR=/path/share +``` + +**Dependencies:** SDL2, SDL2_net, SDL2_ttf, SDL2_image, libvorbis, libogg, speex, OpenGL, GLU, libepoxy, Boost (thread, date_time, system), zlib, fribidi, pcre. Optional: portaudio (voice chat). See `vcpkg.json` for the full list. + +**Server build gotcha:** Always build the YOG server with `scons server=1` — never with a bare `scons build/src/glob2-server`. The `server=1` flag both selects the server target and defines `YOG_SERVER_ONLY` (which strips out GUI/audio code via `#ifndef` guards) and switches `libgag` to its stripped `libgag_server.a` variant. Without the flag, the .o files are compiled with the full GUI code path but link against the stripped libgag, producing dozens of misleading "undefined symbol" errors that look like fundamental rot. SCons also caches the option in `options_cache.py`, so once you've run `server=1`, subsequent `scons` invocations stay in server mode — pass `server=0` explicitly to switch back. + +**Use `release=1` for headless runs.** The default `scons` build is `-g` with no `-O` flag, so `--nox` / `-test-games-nox` runs a debug binary that's roughly 10× slower than necessary. Always build with `scons release=1 -j16` for replay generation, benchmarking, or any throughput-sensitive headless work. Drop back to the default build only when you need a debugger, sanitizers, or fast incremental rebuilds. The flag is sticky via `options_cache.py`, so once set it persists until you pass `release=0`. + +## Running Tests + +Tests live in `glob2/test/` and are built as part of the SCons build. After `scons -j16`, run the resulting binaries (`TestsRunner`, `WinningConditionsHarness`). + +See [`test/README.md`](test/README.md) for the **Map subclass test pattern** — the trick used in `MapQueryTest.cpp` to unit-test Map predicates without pulling in `globalContainer`, `Bullet`, `Team`, or the full simulation surface. + +## Build System Internals + +SCons-based. `SConstruct` is the main build script with platform detection (Linux, Darwin, Windows/MinGW). Library checks are done via custom configure functions in `scons/`. Build options are cached in `options_cache.py`. + +## CI + +Travis CI runs Docker-based builds on Ubuntu 18.04–21.04. See `.travis.yml` in the repo root. + +## Architecture + +### Engine Loop (40ms tick) + +The engine is **synchronous** — no multithreading in the core. Every 40ms, `Engine::run()` calls `.step()` on the class hierarchy. See `doc/sourceCodeUnderstanding.txt` for the full explanation. + +``` +Engine::run() loop: + gui.step() → handle input + net.pushOrder(gui.getOrder()) → send local player order to network + net.pushOrder(ai.getOrder()) → send AI orders + net.step() → exchange orders over network + gui.executeOrder(...) → execute all received orders + gui.drawAll() → render + sleep() → maintain 40ms frame +``` + +### Class Hierarchy + +``` +Engine +├─ NetGame (network abstraction, order transmission via UDP) +└─ GameGUI (rendering, input) + └─ Game (game state) + ├─ Map (terrain, resources) + ├─ Team[32] (a "colony" — has color, units, buildings) + │ ├─ Unit[1024] + │ └─ Building[1024] + ├─ Player[32] (human interface — keyboard+mouse or AI) + └─ Session (serializable state: BasePlayer[32], BaseTeam[32]) +``` + +**Team vs Player:** A Team is a logical colony (color, units, buildings). A Player is a controller (human or AI). Multiple players can control one team. Only teams can be allied. + +### Deterministic Networking + +All clients compute identical game state. Only Orders (player actions) are transmitted. This requires: +- **Use `Utilities::syncRand()`** instead of `rand()` — all machines must get the same random numbers +- **Avoid `std::set`** — it is non-deterministic across platforms +- Orders are buffered in per-player FIFO queues (256 slots) to handle latency + +### Key Source Directories + +- **`src/`** — Main game (382 files): engine, game logic, AI, networking, GUI screens, YOG online system +- **`libgag/`** — Graphics/GUI toolkit library (widget system, sprites, file I/O, rendering) +- **`libusl/`** — USL scripting language for maps/campaigns +- **`data/`** — Runtime assets (graphics, fonts, music, GUI resources) +- **`maps/`** — Game maps +- **`campaigns/`** — Campaign definitions +- **`doc/`** — Architecture documentation + +### Headless Mode & Replay Generation + +See [docs/headless-replays.md](docs/headless-replays.md) for full details on running headless AI games and generating `.replay` files for cross-codebase testing. + +Quick reference: +```bash +# Random AI-vs-AI game, headless, runs until game over or 90k ticks (loops forever — kill after first game) +./glob2 -test-games-nox + +# Single game from a .game file, headless +./glob2 --nox +# Example: ./glob2 --nox games/my_ai_game.game 5000 1 +``` + +Replays are always written to `replays/last_game.replay` (overwritten each game). + +### AI System + +Multiple AI implementations in `src/`: AICastor, AIEcho, AINicowar, AINumbi, AIToubib, AIWarrush — all inherit from `AI` base class. + +### Orders System + +All player actions are serialized as `Order` objects (see `src/Order.h`). `NullOrder` means "player did nothing this tick" — distinct from "we haven't heard from this player yet." Orders carry complete action data (team, position, type, unit counts). + +### YOG (Online Gaming) + +Server/client architecture for online play. `YOGServer` handles matchmaking, chat channels, ratings, map database. LAN play is peer-to-peer. Build server-only binary with `scons server=1`. + +## Conventions + +**Filenames:** PascalCase (`Game.cpp`, `GameRenderUnits.cpp`). Do NOT introduce snake_case C++ filenames in `glob2/`. When splitting an existing PascalCase file, the new pieces stay PascalCase too. + +**Header guards:** `#pragma once`. We are moving away from legacy `#ifndef` guards. + +**macOS rename caveat:** Case-only filename renames need `git mv -f Foo.cpp foo_tmp.cpp && git mv -f foo_tmp.cpp Foo.cpp` — plain `git mv foo.cpp Foo.cpp` may silently no-op on a case-insensitive filesystem. + +## Logging is dead — do not restore + +`glob2/src/LogFileManager.h` defines `#define fprintf if(false)fprintf`. Every translation unit that includes that header gets all `fprintf` calls rewritten to a runtime `if(false)` branch the compiler dead-code-eliminates. ~60 files include `LogFileManager.h`. Original devs disabled logging "for bugs" (per the deprecation comment) and never came back; the infrastructure has been load-bearing in constructors but doing nothing for 15+ years. + +**Cleanup pattern when refactoring a class with a `FILE* logFile` member and a `globalContainer->logFileManager->getFile(...)` call in its constructor:** drop the field, drop the `getFile()` call, drop every `fprintf(logFile, …)` site (already no-ops), drop the include of `LogFileManager.h`. No replacement needed — there's no live consumer. + +`LogFileManager.h` itself stays — it carries the silencing macro. + +**Don't propose adding `spdlog` or another logging library to "preserve diagnostic intent."** There's no consumer asking for the data and 15 years of silence means there's no demand. Re-introduce only when something concrete needs it. + +## Pathfinding gotcha — chamfer pass cap + +For the chamfer distance transform in `glob2/src/map/gradient/MapGradientGlobal.cpp`, the convergence-pass cap is bounded by the Uint8 value range (256), **not** by the Borgefors 1986 1-pass result. + +Borgefors 1-pass holds only on an obstacle-free grid. With obstacles forcing the propagation path to bend (mountains, water channels, building footprints), each direction change costs ~K/2 passes. Real glob2 maps (128×128, e.g. `G2.game`) need significantly more than 4 passes — empirically 32 was sufficient and 8 was not. + +Use the value-range bound (256). The cap is a tripwire for monotonicity violations, not a real-workload throttle. Do not try to derive a tighter bound from grid geometry — obstacle topology dominates. diff --git a/SConstruct b/SConstruct index b333242f7..378e9877b 100644 --- a/SConstruct +++ b/SConstruct @@ -1,4 +1,4 @@ -EnsureSConsVersion(0, 96, 92) +EnsureSConsVersion(3, 0, 0) import sys import os import glob @@ -8,7 +8,7 @@ import dmg import nsis isWindowsPlatform = sys.platform=='win32' -isLinuxPlatform = sys.platform=='linux2' +isLinuxPlatform = sys.platform.startswith('linux') isDarwinPlatform = sys.platform=='darwin' @@ -17,7 +17,7 @@ def establish_options(env): opts.Add("CXXFLAGS", "Manually add to the CXXFLAGS", "-g") opts.Add("LINKFLAGS", "Manually add to the LINKFLAGS", "-g") if isDarwinPlatform: - opts.Add(PathOption("INSTALLDIR", "Installation Directory", "./")) + opts.Add(PathVariable("INSTALLDIR", "Installation Directory", "./")) else: opts.Add("INSTALLDIR", "Installation Directory", "/usr/local/share") opts.Add("BINDIR", "Binary Installation Directory", "/usr/local/bin") @@ -110,9 +110,9 @@ def configure(env, server_only): missing.append("zlib") else: if conf.CheckLib("z"): - env.Append(LIBS="z") + env.Append(LIBS=["z"]) elif conf.CheckLib("zlib1"): - env.Append(LIBS="zlib1") + env.Append(LIBS=["zlib1"]) else: print("Could not find libz or zlib1.dll") missing.append("zlib") @@ -121,43 +121,23 @@ def configure(env, server_only): print("Could not find regex.h") missing.append("regex") - boost_thread = '' - if conf.CheckLib("boost_thread") and conf.CheckCXXHeader("boost/thread/thread.hpp"): - boost_thread="boost_thread" - elif conf.CheckLib("boost_thread-mt") and conf.CheckCXXHeader("boost/thread/thread.hpp"): - boost_thread="boost_thread-mt" - else: - print("Could not find libboost_thread or libboost_thread-mt or boost/thread/thread.hpp") - missing.append("libboost_thread") - env.Append(LIBS=[boost_thread]) - boost_date_time = '' if conf.CheckLib("boost_date_time") and conf.CheckCXXHeader("boost/date_time/posix_time/posix_time.hpp"): - boost_thread="boost_thread" + boost_date_time="boost_date_time" elif conf.CheckLib("boost_date_time-mt") and conf.CheckCXXHeader("boost/date_time/posix_time/posix_time.hpp"): - boost_thread="boost_thread-mt" + boost_date_time="boost_date_time-mt" else: - print("Could not find libboost_date_time or libboost_date_time-mt or boost/thread/thread.hpp") + print("Could not find libboost_date_time or libboost_date_time-mt or boost/date_time/posix_time/posix_time.hpp") missing.append("libboost_date_time") env.Append(LIBS=[boost_date_time]) - env.Append(LIBS=["boost_system", "pthread"]) + if conf.CheckLib("boost_system"): + env.Append(LIBS=["boost_system"]) + env.Append(LIBS=["pthread"]) - if not conf.CheckCXXHeader("boost/shared_ptr.hpp"): - print("Could not find boost/shared_ptr.hpp") - missing.append("boost/shared_ptr.hpp") - if not conf.CheckCXXHeader("boost/tuple/tuple.hpp"): - print("Could not find boost/tuple/tuple.hpp") - missing.append("boost/tuple/tuple.hpp") - if not conf.CheckCXXHeader("boost/tuple/tuple_comparison.hpp"): - print("Could not find boost/tuple/tuple_comparison.hpp") - missing.append("boost/tuple/tuple_comparison.hpp") if not conf.CheckCXXHeader("boost/logic/tribool.hpp"): print("Could not find boost/logic/tribool.hpp") missing.append("boost/logic/tribool.hpp") - if not conf.CheckCXXHeader("boost/lexical_cast.hpp"): - print("Could not find boost/lexical_cast.hpp") - missing.append("boost/lexical_cast.hpp") #Do checks for OpenGL, which is different on every system gl_libraries = [] @@ -165,7 +145,7 @@ def configure(env, server_only): has_gl = True if isDarwinPlatform: print("Using Apple's OpenGL framework") - env.Append(FRAMEWORKS="OpenGL") + env.Append(FRAMEWORKS=["OpenGL", "CoreFoundation"]) elif conf.CheckLib("GL") and conf.CheckCXXHeader("GL/gl.h"): gl_libraries.append("GL") elif conf.CheckLib("GL") and conf.CheckCXXHeader("OpenGL/gl.h"): @@ -255,6 +235,10 @@ def main(): env = Environment() env["VERSION"] = "0.9.5.0" establish_options(env) + + # Emit compile_commands.json for clangd / IDE LSPs. + env.Tool('compilation_db') + env.CompilationDatabase() if env['mingwcross']: env.Platform('cygwin') @@ -263,10 +247,6 @@ def main(): env['AR'] = 'i586-mingw32msvc-ar' env['RANLIB'] = 'i586-mingw32msvc-ranlib' - try: - env.Clone() - except AttributeError: - env.Clone = env.Copy # Add specific paths. @@ -276,8 +256,18 @@ def main(): env.Append(CPPPATH=["C:/msys/1.0/local/include/SDL", "C:/msys/1.0/local/include", "C:/msys/1.0/include/SDL", "C:/msys/1.0/include"]) env.Append(CPPPATH=['/usr/local/include/SDL']) if isDarwinPlatform: - env.Append(LIBPATH=["/opt/local/lib"]) - env.Append(CPPPATH=["/opt/local/include"]) + import subprocess + try: + brew_prefix = subprocess.check_output(["brew", "--prefix"], text=True).strip() + except (FileNotFoundError, subprocess.CalledProcessError): + brew_prefix = None + if brew_prefix: + env.Append(LIBPATH=[brew_prefix + "/lib"]) + env.Append(CPPPATH=[brew_prefix + "/include"]) + env['ENV']['PATH'] = brew_prefix + "/bin:" + env['ENV'].get('PATH', '') + else: + env.Append(LIBPATH=["/opt/local/lib"]) + env.Append(CPPPATH=["/opt/local/include"]) if env['mingwcross']: if os.path.isabs(env['crossroot']): crossroot_abs = env['crossroot'] @@ -295,7 +285,15 @@ def main(): env.Append(CPPPATH=['#libgag/include', '#']) env.Append(CPPPATH=['#libusl/src', '#']) - env.Append(CXXFLAGS=' -Wall -fPIC') + env.Append(CPPPATH=['#src', '#src/yog', '#src/ai', '#src/building', + '#src/game/entities', + '#src/gui', + '#src/map', '#src/map/edit', '#src/map/generator', + '#src/map/gradient', '#src/map/io', '#src/map/pathfind', + '#src/net', '#src/net/irc', '#src/net/message', + '#src/team', + '#src/unit']) + env.Append(CXXFLAGS=' -std=c++20 -Wall -fPIC') env.Append(LINKFLAGS=' -Wall') env.Append(LIBS=['SDL2_net']) if not server_only: @@ -303,7 +301,7 @@ def main(): if env['release']: env.Append(CXXFLAGS=' -O3 -s') - env.Append(LINKFLAGS=' -O3 -s --fwhole-program') + env.Append(LINKFLAGS=' -O3 -s -fwhole-program') if env['profile']: env.Append(CXXFLAGS=' -pg') env.Append(LINKFLAGS='-pg') @@ -315,11 +313,9 @@ def main(): env.Append(LINKFLAGS=['-mwindows']) env.Append(CPPDEFINES=['-D_GNU_SOURCE=1', '-Dmain=SDL_main']) elif isDarwinPlatform: - env.ParseConfig("/opt/local/bin/sdl-config --cflags") - env.ParseConfig("/opt/local/bin/sdl-config --libs") + env.ParseConfig("pkg-config sdl2 --cflags --libs") else: - env.ParseConfig("sdl2-config --cflags") - env.ParseConfig("sdl2-config --libs") + env.ParseConfig("pkg-config sdl2 --cflags --libs") env["TARFILE"] = env.Dir("#").abspath + "/glob2-" + env["VERSION"] + ".tar.gz" @@ -356,7 +352,8 @@ def main(): dmg.create_dmg("Glob2-%s"%env["VERSION"],"%s.app"%env["BUNDLE_NAME"],env) #TODO mac_bundle should be dependency of Dmg: - arch = os.popen("uname -p").read().strip() + import subprocess + arch = subprocess.check_output(["uname", "-p"], text=True).strip() # mac_packages = env.Dmg('Glob2-%s-%s.dmg'% (fullVersion, arch), env.Dir('Glob2.app/') ) # env.Alias("package", mac_packages) diff --git a/campaigns/SConscript b/campaigns/SConscript index b323b4b59..feacf8b6c 100755 --- a/campaigns/SConscript +++ b/campaigns/SConscript @@ -4,7 +4,7 @@ Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: any=False for file in os.listdir("."): if file.find(".txt") != -1 or file.find(".map") != -1: diff --git a/darwin/SConscript b/darwin/SConscript deleted file mode 100644 index cc7343525..000000000 --- a/darwin/SConscript +++ /dev/null @@ -1,104 +0,0 @@ -from SCons.Defaults import SharedCheck, ProgScan -from SCons.Script.SConscript import SConsEnvironment -def TOOL_BUNDLE(env): - """defines env.LinkBundle() for linking bundles on Darwin/OSX, and - env.MakeBundle() for installing a bundle into its dir. - A bundle has this structure: (filenames are case SENSITIVE) - sapphire.bundle/ - Contents/ - Info.plist (an XML key->value database; defined by BUNDLE_INFO_PLIST) - PkgInfo (trivially short; defined by value of BUNDLE_PKGINFO) - MacOS/ - executable (the executable or shared lib, linked with Bundle()) - Resources/ - """ - if 'BUNDLE' in env['TOOLS']: return - if env['osx'] == 1: - if tools_verbose: - print(" running tool: TOOL_BUNDLE") - env.Append(TOOLS = 'BUNDLE') - # This is like the regular linker, but uses different vars. - # XXX: NOTE: this may be out of date now, scons 0.96.91 has some bundle linker stuff built in. - # Check the docs before using this. - LinkBundle = SCons.Builder.Builder(action=[SharedCheck, "$BUNDLECOM"], - emitter="$SHLIBEMITTER", - prefix = '$BUNDLEPREFIX', - suffix = '$BUNDLESUFFIX', - target_scanner = ProgScan, - src_suffix = '$BUNDLESUFFIX', - src_builder = 'SharedObject') - env['BUILDERS']['LinkBundle'] = LinkBundle - env['BUNDLEEMITTER'] = None - env['BUNDLEPREFIX'] = '' - env['BUNDLESUFFIX'] = '' - env['BUNDLEDIRSUFFIX'] = '.bundle' - env['FRAMEWORKS'] = ['-framework Carbon', '-framework System'] - env['BUNDLE'] = '$SHLINK' - env['BUNDLEFLAGS'] = ' -bundle' - env['BUNDLECOM'] = '$BUNDLE $BUNDLEFLAGS -o ${TARGET} $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $FRAMEWORKS' - # This requires some other tools: - TOOL_WRITE_VAL(env) - TOOL_SUBST(env) - # Common type codes are BNDL for generic bundle and APPL for application. - def MakeBundle(env, bundledir, app, - key, info_plist, - typecode='BNDL', creator='SapP', - icon_file='#macosx-install/sapphire-icon.icns', - subst_dict=None, - resources=[]): - """Install a bundle into its dir, in the proper format""" - # Substitute construction vars: - for a in [bundledir, key, info_plist, icon_file, typecode, creator]: - a = env.subst(a) - if SCons.Util.is_List(app): - app = app[0] - if SCons.Util.is_String(app): - app = env.subst(app) - appbase = basename(app) - else: - appbase = basename(str(app)) - if not ('.' in bundledir): - bundledir += '.$BUNDLEDIRSUFFIX' - bundledir = env.subst(bundledir) # substitute again - suffix=bundledir[string.rfind(bundledir,'.'):] - if (suffix=='.app' and typecode != 'APPL' or - suffix!='.app' and typecode == 'APPL'): - raise Error("MakeBundle: inconsistent dir suffix %s and type code %s: app bundles should end with .app and type code APPL."%(suffix, typecode)) - if subst_dict is None: - subst_dict={'%SHORTVERSION%': '$VERSION_NUM', - '%LONGVERSION%': '$VERSION_NAME', - '%YEAR%': '$COMPILE_YEAR', - '%BUNDLE_EXECUTABLE%': appbase, - '%ICONFILE%': basename(icon_file), - '%CREATOR%': creator, - '%TYPE%': typecode, - '%BUNDLE_KEY%': key} - env.Install(bundledir+'/Contents/MacOS', app) - f=env.SubstInFile(bundledir+'/Contents/Info.plist', info_plist, - SUBST_DICT=subst_dict) - env.Depends(f,SCons.Node.Python.Value(key+creator+typecode+env['VERSION_NUM']+env['VERSION_NAME'])) - env.WriteVal(target=bundledir+'/Contents/PkgInfo', - source=SCons.Node.Python.Value(typecode+creator)) - resources.append(icon_file) - for r in resources: - if SCons.Util.is_List(r): - env.InstallAs(join(bundledir+'/Contents/Resources', - r[1]), - r[0]) - else: - env.Install(bundledir+'/Contents/Resources', r) - return [ SCons.Node.FS.default_fs.Dir(bundledir) ] - # This is not a regular Builder; it's a wrapper function. - # So just make it available as a method of Environment. - SConsEnvironment.MakeBundle = MakeBundle -def TOOL_WRITE_VAL(env): - if tools_verbose: - print(" running tool: TOOL_WRITE_VAL") - env.Append(TOOLS = 'WRITE_VAL') - def write_val(target, source, env): - """Write the contents of the first source into the target. - source is usually a Value() node, but could be a file.""" - f = open(str(target[0]), 'wb') - f.write(source[0].get_contents()) - f.close() - env['BUILDERS']['WriteVal'] = Builder(action=write_val) diff --git a/data/SConscript b/data/SConscript index bd5ab6b6c..bdc2c2921 100644 --- a/data/SConscript +++ b/data/SConscript @@ -3,7 +3,7 @@ import os Import('env') Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".txt") != -1: PackTar(env["TARFILE"], file) diff --git a/data/buildings.default.txt b/data/buildings.default.txt deleted file mode 100644 index 84df0dae2..000000000 --- a/data/buildings.default.txt +++ /dev/null @@ -1,153 +0,0 @@ -// -// Copyright (C) 2001, 2002, 2003 Stephane Magnenat & Luc-Olivier de Charriere -// for any question or comment contact us at -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// - -*default - -type null - -gameSprite ERROR_NO_GAME_SPRITE_DEFINED -gameSpriteImage 0 -gameSpriteCount 1 -miniSprite ERROR_NO_MINI_SPRITE_DEFINED -miniSpriteImage 0 - -hueImage 0 -flagImage 49 -miniImage 0 -crossConnectMultiImage 0 - -upgradeStopWalk 0 -upgradeStopSwim 0 -upgradeStopFly 0 -upgradeWalk 0 -upgradeSwim 0 -upgradeFly 0 -upgradeBuild 0 -upgradeHarvest 0 -upgradeAttackSpeed 0 -upgradeAttackStrength 0 -upgradeMagicAttackAir 0 -upgradeMagicAttackGround 0 -upgradeMagicCreateWood 0 -upgradeMagicCreateCorn 0 -upgradeMagicCreateAlga 0 -upgradeArmor 0 -upgradeHP 0 - -upgradeTimeStopWalk 0 -upgradeTimeStopSwim 0 -upgradeTimeStopFly 0 -upgradeTimeWalk 0 -upgradeTimeSwim 0 -upgradeTimeFly 0 -upgradeTimeBuild 0 -upgradeTimeHarvest 0 -upgradeTimeAttackSpeed 0 -upgradeTimeAttackStrength 0 -upgradeTimeMagicAttackAir 0 -upgradeTimeMagicAttackGround 0 -upgradeTimeMagicCreateWood 0 -upgradeTimeMagicCreateCorn 0 -upgradeTimeMagicCreateAlga 0 -upgradeTimeArmor 0 -upgradeTimeHP 0 - -upgradeInParallel 0 - -foodable 0 -fillable 0 - -zonableWorker 0 -zonableExplorer 0 -zonableWarrior 0 -zonableForbidden 0 - -canFeedUnit 0 -timeToFeedUnit 0 -canHealUnit 0 -timeToHealUnit 0 -insideSpeed 12 -canExchange 0 -useTeamRessources 0 - -width 0 -height 0 -decLeft 0 -decTop 0 -isVirtual 0 -isCloacked 0 -shootingRange 0 -shootDamage 0 -shootSpeed 0 -shootRythme 0 -maxBullets 0 -multiplierStoneToBullets 0 - -unitProductionTime 0 -ressourceForOneUnit 0 - -maxWood 0 -maxCorn 0 -maxPapyrus 0 -maxStone 0 -maxAlgue 0 -maxFruit0 0 -maxFruit1 0 -maxFruit2 0 -maxFruit3 0 -maxFruit4 0 -maxFruit5 0 -maxFruit6 0 -maxFruit7 0 -maxFruit8 0 -maxFruit9 0 - -multiplierWood 1 -multiplierCorn 1 -multiplierPapyrus 1 -multiplierStone 1 -multiplierAlgue 1 -multiplierFruit0 10 -multiplierFruit1 10 -multiplierFruit2 10 -multiplierFruit3 10 -multiplierFruit4 10 -multiplierFruit5 10 -multiplierFruit6 10 -multiplierFruit7 10 -multiplierFruit8 10 -multiplierFruit9 10 - -maxUnitInside 0 -maxUnitWorking 0 - -hpInit 0 -hpMax 0 -hpInc 0 -armor 0 -level 0 -shortTypeNum 0 -isBuildingSite 0 - -defaultUnitStayRange 0 - -viewingRange 1 -regenerationSpeed 0 - -prestige 0 diff --git a/data/buildings.txt b/data/buildings.txt deleted file mode 100644 index 5159cec39..000000000 --- a/data/buildings.txt +++ /dev/null @@ -1,1396 +0,0 @@ -// -// Copyright (C) 2001, 2002, 2003 Stephane Magnenat & Luc-Olivier de Charriere -// for any question or comment contact us at -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// - -*swarm0c - -type swarm - -gameSprite data/gfx/swarm0c -gameSpriteImage 0 -miniSprite data/gfx/miniswarm0c -miniSpriteImage 0 -hueImage 1 - -fillable 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxCorn 35 -hpInit 1 -hpMax 700 -hpInc 20 -level 0 -shortTypeNum 0 -maxUnitWorking 1 -isBuildingSite 1 - - -*swarm0 - -type swarm - -gameSprite data/gfx/swarm0b -gameSpriteImage 0 -miniSprite data/gfx/miniswarm0b -miniSpriteImage 0 -hueImage 1 - -fillable 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -unitProductionTime 150 -ressourceForOneUnit 5 -maxCorn 20 -hpInit 700 -hpMax 700 -level 0 -shortTypeNum 0 -maxUnitWorking 1 - -viewingRange 4 -regenerationSpeed 3 - - -*inn0c - -type inn - -gameSprite data/gfx/inn0c -gameSpriteImage 0 -miniSprite data/gfx/miniinn0c -miniSpriteImage 0 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxWood 3 -hpInit 1 -hpMax 200 -hpInc 67 -level 0 -shortTypeNum 1 -maxUnitWorking 1 -isBuildingSite 1 - - -*inn0 - -type inn - -gameSprite data/gfx/inn0b -gameSpriteImage 0 -gameSpriteCount 2 -miniSprite data/gfx/miniinn0b -miniSpriteImage 0 - -foodable 1 -canFeedUnit 1 -timeToFeedUnit 24 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxCorn 10 -maxFruit0 40 -maxFruit1 40 -maxFruit2 40 -maxUnitInside 4 -hpInit 200 -hpMax 200 -level 0 -shortTypeNum 1 -maxUnitWorking 1 - - -*inn1c - -type inn - -gameSprite data/gfx/inn1c -gameSpriteImage 0 -miniSprite data/gfx/miniinn1c -miniSpriteImage 0 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxWood 8 -hpInit 200 -hpMax 500 -hpInc 38 -level 1 -shortTypeNum 1 -maxUnitWorking 1 -isBuildingSite 1 - - -*inn1 - -type inn - -gameSprite data/gfx/inn1b -gameSpriteImage 0 -gameSpriteCount 2 -miniSprite data/gfx/miniinn1b -miniSpriteImage 0 - -foodable 1 -canFeedUnit 1 -timeToFeedUnit 15 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxCorn 30 -maxFruit0 80 -maxFruit1 80 -maxFruit2 80 -maxUnitInside 7 -hpInit 500 -hpMax 500 -armor 5 -level 1 -shortTypeNum 1 -maxUnitWorking 1 - - -*inn2c - -type inn - -gameSprite data/gfx/inn2c -gameSpriteImage 0 -miniSprite data/gfx/miniinn2c -miniSpriteImage 0 - -fillable 1 - -width 3 -height 3 -decLeft -1 -decTop -1 -maxWood 7 -maxStone 5 -hpInit 500 -hpMax 700 -hpInc 17 -armor 5 -level 2 -shortTypeNum 1 -maxUnitWorking 1 -isBuildingSite 1 - - -*inn2 - -type inn - -gameSprite data/gfx/inn2b -gameSpriteImage 0 -miniSprite data/gfx/miniinn2b -miniSpriteImage 0 - -foodable 1 -canFeedUnit 1 -timeToFeedUnit 9 - -width 3 -height 3 -decLeft -1 -decTop -1 -maxCorn 50 -maxFruit0 200 -maxFruit1 200 -maxFruit2 200 -maxUnitInside 17 -hpInit 700 -hpMax 700 -armor 10 -level 2 -shortTypeNum 1 -maxUnitWorking 1 - - -*hospital0c - -type hospital - -gameSprite data/gfx/hosp0c -gameSpriteImage 0 -miniSprite data/gfx/minihosp0c -miniSpriteImage 0 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxWood 3 -hpInit 1 -hpMax 260 -hpInc 87 -level 0 -shortTypeNum 2 -maxUnitWorking 1 -isBuildingSite 1 - - -*hospital0 - -type hospital - -gameSprite data/gfx/hosp0b -gameSpriteImage 0 -gameSpriteCount 2 -miniSprite data/gfx/minihosp0b -miniSpriteImage 0 - -canHealUnit 1 -timeToHealUnit 30 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxUnitInside 2 -hpInit 260 -hpMax 260 -armor 5 -level 0 -shortTypeNum 2 - - -*hospital1c - -type hospital - -gameSprite data/gfx/hosp1c -gameSpriteImage 0 -miniSprite data/gfx/minihosp1c -miniSpriteImage 0 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxWood 8 -hpInit 260 -hpMax 500 -hpInc 30 -level 1 -shortTypeNum 2 -maxUnitWorking 1 -isBuildingSite 1 - - -*hospital1 - -type hospital - -gameSprite data/gfx/hosp1b -gameSpriteImage 0 -miniSprite data/gfx/minihosp1b -miniSpriteImage 0 - -canHealUnit 1 -timeToHealUnit 18 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxUnitInside 5 -hpInit 500 -hpMax 500 -armor 5 -level 1 -shortTypeNum 2 - - -*hospital2c - -type hospital - -gameSprite data/gfx/hosp2c -gameSpriteImage 0 -miniSprite data/gfx/minihosp2c -miniSpriteImage 0 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxWood 3 -maxStone 5 -hpInit 500 -hpMax 700 -hpInc 25 -armor 5 -level 2 -shortTypeNum 2 -maxUnitWorking 1 -isBuildingSite 1 - - -*hospital2 - -type hospital - -gameSprite data/gfx/hosp2b -gameSpriteImage 0 -miniSprite data/gfx/minihosp2b -miniSpriteImage 0 - -canHealUnit 1 -timeToHealUnit 6 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxUnitInside 7 -hpInit 700 -hpMax 700 -armor 10 -level 2 -shortTypeNum 2 - - -*racetrack0c - -type racetrack - -gameSprite data/gfx/racetrack0c -gameSpriteImage 0 -miniSprite data/gfx/miniracetrack0c -miniSpriteImage 0 - -fillable 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxWood 6 -maxStone 1 -hpInit 1 -hpMax 675 -hpInc 97 -armor 0 -level 0 -shortTypeNum 3 -maxUnitWorking 1 -isBuildingSite 1 - - -*racetrack0 - -type racetrack - -gameSprite data/gfx/racetrack0b -gameSpriteImage 0 -gameSpriteCount 3 -miniSprite data/gfx/miniracetrack0b -miniSpriteImage 0 - -upgradeWalk 1 -upgradeTimeWalk 21 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxUnitInside 2 -hpInit 675 -hpMax 675 -armor 5 -level 0 -shortTypeNum 3 - - -*racetrack1c - -type racetrack - -gameSprite data/gfx/buildingsite -gameSpriteImage 5 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 5 - -fillable 1 - -width 6 -height 6 -decLeft -3 -decTop -3 -maxWood 10 -maxStone 5 -hpInit 675 -hpMax 1000 -hpInc 22 -armor 5 -level 1 -shortTypeNum 3 -maxUnitWorking 1 -isBuildingSite 1 - - -*racetrack1 - -type racetrack - -gameSprite data/gfx/racetrack1b -gameSpriteImage 0 -gameSpriteCount 3 -miniSprite data/gfx/miniracetrack1b -miniSpriteImage 0 - -upgradeWalk 1 -upgradeTimeWalk 21 - -width 6 -height 6 -decLeft -3 -decTop -3 -maxUnitInside 4 -hpInit 1000 -hpMax 1000 -armor 10 -level 1 -shortTypeNum 3 - - -*racetrack2c - -type racetrack - -gameSprite data/gfx/buildingsite -gameSpriteImage 5 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 5 - -fillable 1 - -width 6 -height 6 -decLeft -3 -decTop -3 -maxWood 15 -maxStone 5 -hpInit 1000 -hpMax 1500 -hpInc 25 -armor 10 -level 2 -shortTypeNum 3 -maxUnitWorking 1 -isBuildingSite 1 - - -*racetrack2 - -type racetrack - -gameSprite data/gfx/racetrack2b -gameSpriteImage 0 -miniSprite data/gfx/miniracetrack2b -miniSpriteImage 0 - -upgradeWalk 1 -upgradeTimeWalk 24 - -width 6 -height 6 -decLeft -3 -decTop -3 -maxUnitInside 6 -hpInit 1500 -hpMax 1500 -armor 12 -level 2 -shortTypeNum 3 - - -*swimmingpool0c - -type swimmingpool - -gameSprite data/gfx/pool0c -gameSpriteImage 0 -miniSprite data/gfx/minipool0c -miniSpriteImage 0 - -fillable 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxWood 8 -hpInit 1 -hpMax 675 -hpInc 97 -armor 0 -level 0 -shortTypeNum 4 -maxUnitWorking 1 -isBuildingSite 1 - - -*swimmingpool0 - -type swimmingpool - -gameSprite data/gfx/pool0b -gameSpriteImage 0 -gameSpriteCount 2 -miniSprite data/gfx/minipool0b -miniSpriteImage 0 - -upgradeSwim 1 -upgradeTimeSwim 21 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxUnitInside 2 -hpInit 675 -hpMax 675 -armor 5 -level 0 -shortTypeNum 4 - - -*swimmingpool1c - -type swimmingpool - -gameSprite data/gfx/buildingsite -gameSpriteImage 5 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 5 - -fillable 1 - -width 6 -height 6 -decLeft -3 -decTop -3 -maxCorn 6 -maxWood 12 -hpInit 675 -hpMax 1000 -hpInc 19 -armor 5 -level 1 -shortTypeNum 4 -maxUnitWorking 1 -isBuildingSite 1 - - -*swimmingpool1 - -type swimmingpool - -gameSprite data/gfx/pool1b -gameSpriteImage 0 -miniSprite data/gfx/minipool1b -miniSpriteImage 0 - -upgradeSwim 1 -upgradeTimeSwim 21 - -width 6 -height 6 -decLeft -3 -decTop -3 -maxUnitInside 4 -hpInit 1000 -hpMax 1000 -armor 8 -level 1 -shortTypeNum 4 - - -*swimmingpool2c - -type swimmingpool - -gameSprite data/gfx/buildingsite -gameSpriteImage 5 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 5 - -fillable 1 - -width 6 -height 6 -decLeft -3 -decTop -3 -maxCorn 4 -maxWood 8 -maxStone 6 -maxAlgue 8 -hpInit 1000 -hpMax 1500 -hpInc 20 -armor 8 -level 2 -shortTypeNum 4 -maxUnitWorking 1 -isBuildingSite 1 - - -*swimmingpool2 - -type swimmingpool - -gameSprite data/gfx/pool2b -gameSpriteImage 0 -miniSprite data/gfx/minipool2b -miniSpriteImage 0 - -upgradeSwim 1 -upgradeTimeSwim 24 - -width 6 -height 6 -decLeft -3 -decTop -3 -maxUnitInside 6 -hpInit 1500 -hpMax 1500 -armor 12 -level 2 -shortTypeNum 4 - - -*barracks0c - -type barracks - -gameSprite data/gfx/buildingsite -gameSpriteImage 3 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 3 - -fillable 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxWood 7 -hpInit 1 -hpMax 440 -hpInc 63 -armor 0 -level 0 -shortTypeNum 5 -maxUnitWorking 1 -isBuildingSite 1 - - -*barracks0 - -type barracks - -gameSprite data/gfx/barracks0b -gameSpriteImage 0 -miniSprite data/gfx/minibarracks0b -miniSpriteImage 0 - -upgradeAttackStrength 1 -upgradeTimeAttackStrength 21 -upgradeAttackSpeed 1 -upgradeTimeAttackSpeed 21 -upgradeInParallel 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxUnitInside 2 -hpInit 440 -hpMax 440 -armor 5 -level 0 -shortTypeNum 5 - - -*barracks1c - -type barracks - -gameSprite data/gfx/buildingsite -gameSpriteImage 3 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 3 - -fillable 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxStone 10 -maxWood 3 -hpInit 440 -hpMax 800 -hpInc 28 -armor 5 -level 1 -shortTypeNum 5 -maxUnitWorking 1 -isBuildingSite 1 - - -*barracks1 - -type barracks - -gameSprite data/gfx/barracks1b -gameSpriteImage 0 -miniSprite data/gfx/minibarracks1b -miniSpriteImage 0 - -upgradeAttackStrength 1 -upgradeTimeAttackStrength 30 -upgradeAttackSpeed 1 -upgradeTimeAttackSpeed 30 -upgradeInParallel 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxUnitInside 4 -hpInit 800 -hpMax 800 -armor 10 -level 1 -shortTypeNum 5 - - -*barracks2c - -type barracks - -gameSprite data/gfx/buildingsite -gameSpriteImage 3 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 3 - -fillable 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxWood 10 -maxStone 10 -hpInit 800 -hpMax 1300 -hpInc 25 -armor 10 -level 2 -shortTypeNum 5 -maxUnitWorking 1 -isBuildingSite 1 - - -*barracks2 - -type barracks - -gameSprite data/gfx/barracks2b -gameSpriteImage 0 -miniSprite data/gfx/minibarracks2b -miniSpriteImage 0 - -upgradeAttackStrength 1 -upgradeTimeAttackStrength 42 -upgradeAttackSpeed 1 -upgradeTimeAttackSpeed 42 -upgradeInParallel 1 - -width 4 -height 4 -decLeft -2 -decTop -2 -maxUnitInside 5 -hpInit 1300 -hpMax 1300 -armor 12 -level 2 -shortTypeNum 5 - - -*school0c - -type school - -gameSprite data/gfx/buildingsite -gameSpriteImage 1 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 1 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxWood 7 -maxAlgue 2 -hpInit 1 -hpMax 360 -hpInc 40 -armor 0 -level 0 -shortTypeNum 6 -maxUnitWorking 1 -isBuildingSite 1 - - -*school0 - -type school - -gameSprite data/gfx/school0b -gameSpriteImage 0 -gameSpriteCount 2 -miniSprite data/gfx/minischool0b -miniSpriteImage 0 - -upgradeBuild 1 -upgradeTimeBuild 21 -upgradeHarvest 1 -upgradeTimeHarvest 21 -upgradeInParallel 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxUnitInside 4 -hpInit 360 -hpMax 360 -armor 3 -level 0 -shortTypeNum 6 - - -*school1c - -type school - -gameSprite data/gfx/school1c -gameSpriteImage 0 -miniSprite data/gfx/minischool1c -miniSpriteImage 0 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxStone 5 -maxWood 5 -maxAlgue 12 -hpInit 360 -hpMax 520 -hpInc 8 -armor 3 -level 1 -shortTypeNum 6 -maxUnitWorking 1 -isBuildingSite 1 - - -*school1 - -type school - -gameSprite data/gfx/school1b -gameSpriteImage 0 -gameSpriteCount 3 -miniSprite data/gfx/minischool1b -miniSpriteImage 0 - -upgradeBuild 1 -upgradeTimeBuild 33 -upgradeHarvest 1 -upgradeTimeHarvest 33 -upgradeInParallel 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxUnitInside 7 -hpInit 520 -hpMax 520 -armor 8 -level 1 -shortTypeNum 6 - - -*school2c - -type school - -gameSprite data/gfx/buildingsite -gameSpriteImage 1 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 1 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxCorn 4 -maxWood 7 -maxStone 12 -maxAlgue 10 -hpInit 520 -hpMax 700 -hpInc 6 -armor 8 -level 2 -shortTypeNum 6 -maxUnitWorking 1 -isBuildingSite 1 - - -*school2 - -type school - -gameSprite data/gfx/school2b -gameSpriteImage 0 -miniSprite data/gfx/minischool2b -miniSpriteImage 0 - -upgradeBuild 1 -upgradeTimeBuild 42 -upgradeHarvest 1 -upgradeTimeHarvest 42 -upgradeMagicAttackGround 1 -upgradeTimeMagicAttackGround 42 -upgradeInParallel 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxUnitInside 9 -hpInit 700 -hpMax 700 -armor 12 -level 2 -shortTypeNum 6 -prestige 50 - - -*defencetower0c - -type defencetower - -gameSprite data/gfx/buildingsite -gameSpriteImage 1 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 1 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxWood 6 -hpInit 1 -hpMax 480 -hpInc 80 -armor 0 -level 0 -shortTypeNum 7 -maxUnitWorking 1 -isBuildingSite 1 - - -*defencetower0 - -type defencetower - -gameSprite data/gfx/defencetower0b -gameSpriteImage 0 -miniSprite data/gfx/minidefencetower0b -miniSpriteImage 0 - -fillable 1 -maxStone 4 -maxUnitWorking 1 - -shootingRange 5 -shootDamage 30 -shootSpeed 5000 -shootRythme 1700 -maxBullets 12 -multiplierStoneToBullets 3 - -width 2 -height 2 -decLeft -1 -decTop -1 -hpInit 480 -hpMax 480 -armor 8 -level 0 -shortTypeNum 7 - -viewingRange 6 - - -*defencetower1c - -type defencetower - -gameSprite data/gfx/buildingsite -gameSpriteImage 1 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 1 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxWood 10 -maxStone 14 -hpInit 480 -hpMax 1440 -hpInc 40 -armor 8 -level 1 -shortTypeNum 7 -maxUnitWorking 1 -isBuildingSite 1 - -viewingRange 5 - - -*defencetower1 - -type defencetower - -gameSprite data/gfx/defencetower1b -gameSpriteImage 0 -gameSpriteCount 3 -miniSprite data/gfx/minidefencetower1b -miniSpriteImage 0 - -fillable 1 -maxStone 4 -maxUnitWorking 1 - -shootingRange 7 -shootDamage 40 -shootSpeed 5700 -shootRythme 1800 -maxBullets 16 -multiplierStoneToBullets 4 - -width 2 -height 2 -decLeft -1 -decTop -1 -hpInit 1440 -hpMax 1440 -armor 12 -level 1 -shortTypeNum 7 - -viewingRange 7 - - -*defencetower2c - -type defencetower - -gameSprite data/gfx/buildingsite -gameSpriteImage 1 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 1 - -fillable 1 - -width 2 -height 2 -decLeft -1 -decTop -1 -maxWood 8 -maxStone 14 -maxAlgue 2 -hpInit 1440 -hpMax 2000 -hpInc 24 -armor 12 -level 2 -shortTypeNum 7 -maxUnitWorking 1 -isBuildingSite 1 - -viewingRange 6 - - -*defencetower2 - -type defencetower - -gameSprite data/gfx/defencetower2b -gameSpriteImage 0 -miniSprite data/gfx/minidefencetower2b -miniSpriteImage 0 - -fillable 1 -maxStone 4 -maxUnitWorking 1 - -shootingRange 9 -shootDamage 50 -shootSpeed 7000 -shootRythme 1900 -maxBullets 20 -multiplierStoneToBullets 7 - -width 2 -height 2 -decLeft -1 -decTop -1 -hpInit 2000 -hpMax 2000 -armor 15 -level 2 -shortTypeNum 7 - -viewingRange 8 - - -*explorationflag0 - -type explorationflag - -gameSprite data/gfx/explorationflag -gameSpriteImage 0 -hueImage 1 -miniSpriteImage -1 - -zonableExplorer 1 - -width 1 -height 1 -decLeft 0 -decTop 0 -isVirtual 1 -isCloacked 1 - -maxUnitWorking 1 - -shortTypeNum 8 - -defaultUnitStayRange 10 -maxUnitStayRange 20 - - -*warflag0 - -type warflag - -gameSprite data/gfx/warflag -gameSpriteImage 0 -hueImage 1 -miniSpriteImage -1 - -zonableWarrior 1 - -width 1 -height 1 -decLeft 0 -decTop 0 -isVirtual 1 -isCloacked 1 - -maxUnitWorking 1 - -shortTypeNum 9 - -defaultUnitStayRange 4 -maxUnitStayRange 8 - - -*clearingflag0 - -type clearingflag - -gameSprite data/gfx/clearingflag -gameSpriteImage 0 -hueImage 1 -miniSpriteImage -1 - -zonableWorker 1 - -width 1 -height 1 -decLeft 0 -decTop 0 -isVirtual 1 -isCloacked 1 - -maxUnitWorking 1 - -shortTypeNum 10 - -defaultUnitStayRange 3 -maxUnitStayRange 14 - - -*stonewall0c - -type stonewall - -gameSprite data/gfx/wallc -gameSpriteImage 0 -miniSpriteImage -1 -hueImage 1; -miniSpriteImage -1 - -fillable 1 -maxWood 0 -maxStone 1 - -width 1 -height 1 -decLeft 0 -decTop 0 -hpInit 1 -hpMax 180 -hpInc 180 -level 0 -shortTypeNum 11 -maxUnitWorking 1 -isBuildingSite 1 - - -*stonewall0 - -type stonewall - -gameSprite data/gfx/wall -gameSpriteImage 0 -miniSpriteImage 23 -crossConnectMultiImage 1 -hueImage 1; -miniSpriteImage -1 - -width 1 -height 1 -decLeft 0 -decTop 0 -hpInit 180 -hpMax 180 -level 0 -shortTypeNum 11 -armor 10 - - -*market0c - -type market - -gameSprite data/gfx/buildingsite -gameSpriteImage 2 -miniSprite data/gfx/minibuildingsite -miniSpriteImage 2 - -width 3 -height 3 -decLeft -1 -decTop -1 -maxWood 4 -maxStone 4 -hpInit 1 -hpMax 400 -hpInc 50 -level 0 -shortTypeNum 12 -maxUnitWorking 1 -isBuildingSite 1 -fillable 1 - - -*market0 - -type market - -gameSprite data/gfx/market0b -gameSpriteImage 0 -miniSprite data/gfx/minimarket0b -miniSpriteImage 0 - -width 3 -height 3 -decLeft -1 -decTop -1 -hpInit 400 -hpMax 400 -level 0 -shortTypeNum 12 -armor 6 -fillable 1 -canExchange 1 -useTeamRessources 1 -maxFruit0 200 -maxFruit1 200 -maxFruit2 200 -maxUnitWorking 1 - diff --git a/data/fonts/SConscript b/data/fonts/SConscript index 0bad141df..13795859f 100644 --- a/data/fonts/SConscript +++ b/data/fonts/SConscript @@ -1,7 +1,7 @@ Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: PackTar(env["TARFILE"], "sans.ttf") env.Install(env["INSTALLDIR"]+"/glob2/data/fonts", "sans.ttf") env.Alias("install", env["INSTALLDIR"]+"/glob2/data/fonts") diff --git a/data/gfx/SConscript b/data/gfx/SConscript index bf69e4580..d8cf51b87 100644 --- a/data/gfx/SConscript +++ b/data/gfx/SConscript @@ -4,7 +4,7 @@ Import("env") Import("PackTar") #speed increase, much time waisted doing MD5's on the pictures -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".png") != -1: PackTar(env["TARFILE"], file) diff --git a/data/gfx/cursor/SConscript b/data/gfx/cursor/SConscript index d520b525c..bdd353840 100644 --- a/data/gfx/cursor/SConscript +++ b/data/gfx/cursor/SConscript @@ -3,7 +3,7 @@ import os Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".png") != -1: PackTar(env["TARFILE"], file) diff --git a/data/gui/SConscript b/data/gui/SConscript index ae4b06d12..1b48860c6 100644 --- a/data/gui/SConscript +++ b/data/gui/SConscript @@ -3,7 +3,7 @@ import os Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".png") != -1: PackTar(env["TARFILE"], file) diff --git a/data/icons/SConscript b/data/icons/SConscript index 49f6199b6..5b6d5e560 100644 --- a/data/icons/SConscript +++ b/data/icons/SConscript @@ -3,7 +3,7 @@ import os Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".png") != -1: PackTar(env["TARFILE"], file) diff --git a/data/ressources.txt b/data/ressources.txt deleted file mode 100644 index 1a750b55e..000000000 --- a/data/ressources.txt +++ /dev/null @@ -1,123 +0,0 @@ -// -// Copyright (C) 2001, 2002, 2003 Stephane Magnenat & Luc-Olivier de Charriere -// for any question or comment contact us at -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// - -// default ressource - -terrain = 2 -gfxId = 0 -sizesCount = 5 -varietiesCount = 2 -shrinkable = 1 -expendable = 1 -eternal = 0 -granular = 0 -visibleToBeCollected = 0 - -* - -// Wood -minimapR = 0 -minimapG = 60 -minimapB = 0 - -* - -// Corn -minimapR = 211 -minimapG = 207 -minimapB = 167 -gfxId = 10 -granular = 1 - -* - -// Papyrus -minimapR = 0 -minimapG = 0 -minimapB = 0 -gfxId = 20 -varietiesCount = 1 -expendable = 0 -granular = 1 - -* - -// Stone -minimapR = 104 -minimapG = 112 -minimapB = 124 -gfxId = 30 -shrinkable = 0 -expendable = 0 -eternal = 1 -granular = 1 - -* - -// Alga -minimapR = 41 -minimapG = 157 -minimapB = 165 -terrain = 0 -gfxId = 40 -granular = 1 - -* - -// Cherry -minimapR = 255 -minimapG = 127 -minimapB = 0 -gfxId = 50 -sizesCount = 4 -varietiesCount = 1 -expendable = 0 -eternal = 1 -granular = 1 -visibleToBeCollected = 1 - -* - -// Orange -minimapR = 255 -minimapG = 127 -minimapB = 0 -gfxId = 55 -sizesCount = 4 -varietiesCount = 1 -expendable = 0 -eternal = 1 -granular = 1 -visibleToBeCollected = 1 - -* - -// Prune -minimapR = 255 -minimapG = 127 -minimapB = 0 -gfxId = 60 -sizesCount = 4 -varietiesCount = 1 -expendable = 0 -eternal = 1 -granular = 1 -visibleToBeCollected = 1 - -* diff --git a/data/texts.en.txt b/data/texts.en.txt index b0958716b..119f1f20b 100644 --- a/data/texts.en.txt +++ b/data/texts.en.txt @@ -308,6 +308,8 @@ enter equals [ERROR_CANT_LOAD_MAP] The map couldn't be loaded because the file is either damaged or an incompatible version. +[ERROR_CANT_SAVE_CAMPAIGN] +The campaign couldn't be saved. Check that the destination directory exists, is writable, and has free space. [escape] escape [explorationflag explanation 2] diff --git a/data/texts.keys.txt b/data/texts.keys.txt index 2a125fbde..9b32131e7 100644 --- a/data/texts.keys.txt +++ b/data/texts.keys.txt @@ -153,6 +153,7 @@ [enter] [equals] [ERROR_CANT_LOAD_MAP] +[ERROR_CANT_SAVE_CAMPAIGN] [escape] [explorationflag explanation 2] [explorationflag explanation] diff --git a/data/units.txt b/data/units.txt deleted file mode 100644 index 9bf76e42d..000000000 --- a/data/units.txt +++ /dev/null @@ -1,256 +0,0 @@ -// -// Copyright (C) 2001, 2002, 2003 Stephane Magnenat & Luc-Olivier de Charriere -// for any question or comment contact us at -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// - -hungryness = 425; - -baseWorker -{ - startImageStopWalk = 64; - startImageStopSwim = 128; - startImageStopFly = 0; - - startImageWalk = 64; - startImageSwim = 128; - startImageFly = 0; - startImageBuild = 192; - startImageHarvest = 192; - startImageAttack = 0; - - - stopWalkSpeed = 8; - stopSwimSpeed = 8; - stopFlySpeed = 0; - - flySpeed = 0; - - attackSpeed = 0; - attackForce = 0; - - magicAttackAir = 0; - magicAttackGround = 0; - magicCreateWood = 0; - magicCreateCorn = 0; - magicCreateAlga = 0; - magicActionCooldown = 0; - - armor = 0; - hpMax = 200; - - hungryness = 350; - harvestDamage = 10; - armorReductionPerHappyness = 0; - - experiencePerLevel = 0; -} - -worker -{ - 0 : baseWorker - { - walkSpeed = 16; - swimSpeed = 0; - - buildSpeed = 8; - harvestSpeed = 8; - } - 1 : baseWorker - { - walkSpeed = 21; - swimSpeed = 10; - - buildSpeed = 12; - harvestSpeed = 9; - } - 2 : baseWorker - { - walkSpeed = 26; - swimSpeed = 20; - - buildSpeed = 16; - harvestSpeed = 10; - } - 3 : baseWorker - { - walkSpeed = 30; - swimSpeed = 30; - - buildSpeed = 20; - harvestSpeed = 11; - } -} - -baseExplorer -{ - startImageStopWalk = 0; - startImageStopSwim = 0; - startImageStopFly = 0; - - startImageWalk = 0; - startImageSwim = 0; - startImageFly = 0; - startImageBuild = 0; - startImageHarvest = 0; - startImageAttack = 0; - - - stopWalkSpeed = 8; - stopSwimSpeed = 8; - stopFlySpeed = 0; - - walkSpeed = 0; - swimSpeed = 0; - flySpeed = 28; - - buildSpeed = 0; - harvestSpeed = 0; - - attackSpeed = 0; - attackForce = 0; - - armor = 0; - hpMax = 38; - - hungryness = 350; - harvestDamage = 0; - armorReductionPerHappyness = 1; - - experiencePerLevel = 50; -} - -explorer -{ - 0 : baseExplorer - { - magicAttackAir = 6; - magicAttackGround = 0; - magicCreateWood = 0; - magicCreateCorn = 0; - magicCreateAlga = 0; - - magicActionCooldown = 3; - } - // this level can only be set in editor - 1 : baseExplorer - { - magicAttackAir = 6; - magicAttackGround = 0; - magicCreateWood = 4; - magicCreateCorn = 4; - magicCreateAlga = 4; - - magicActionCooldown = 3; - } - // this level can only be set in editor - 2 : baseExplorer - { - magicAttackAir = 6; - magicAttackGround = 0; - magicCreateWood = 3; - magicCreateCorn = 3; - magicCreateAlga = 3; - - magicActionCooldown = 3; - } - 3 : baseExplorer - { - magicAttackAir = 6; - magicAttackGround = 8; - magicCreateWood = 2; - magicCreateCorn = 2; - magicCreateAlga = 2; - - magicActionCooldown = 3; - } -} - -baseWarrior -{ - startImageStopWalk = 256; - startImageStopSwim = 320; - startImageStopFly = 0; - - startImageWalk = 256; - startImageSwim = 320; - startImageFly = 0; - startImageBuild = 0; - startImageHarvest = 0; - startImageAttack = 384; - - stopWalkSpeed = 8; - stopSwimSpeed = 8; - stopFlySpeed = 0; - - flySpeed = 0; - - buildSpeed = 0; - harvestSpeed = 0; - - magicAttackAir = 0; - magicAttackGround = 0; - magicCreateWood = 0; - magicCreateCorn = 0; - magicCreateAlga = 0; - magicActionCooldown = 0; - - armor = 10; - hpMax = 250; - - hungryness = 350; - harvestDamage = 0; - armorReductionPerHappyness = 10; - - experiencePerLevel = 20; -} - -warrior -{ - 0 : baseWarrior - { - walkSpeed = 16; - swimSpeed = 0; - - attackSpeed = 12; - attackForce = 13; - } - 1 : baseWarrior - { - walkSpeed = 21; - swimSpeed = 8; - - attackSpeed = 16; - attackForce = 14; - } - 2 : baseWarrior - { - walkSpeed = 26; - swimSpeed = 16; - - attackSpeed = 22; - attackForce = 15; - } - 3 : baseWarrior - { - walkSpeed = 30; - swimSpeed = 24; - - attackSpeed = 28; - attackForce = 16; - } -} - diff --git a/data/unitsSkins.txt b/data/unitsSkins.txt deleted file mode 100644 index cf9f4c57e..000000000 --- a/data/unitsSkins.txt +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (C) 2001 - 2006 Stephane Magnenat & Luc-Olivier de Charriere -// for any question or comment contact us at -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// - -worker -{ - spriteName = "data/gfx/unit"; - - startImageStopWalk = 64; - startImageStopSwim = 128; - startImageStopFly = 0; - - startImageWalk = 64; - startImageSwim = 128; - startImageFly = 0; - startImageBuild = 192; - startImageHarvest = 192; - startImageAttack = 0; -} - -explorer -{ - spriteName = "data/gfx/unit"; - - startImageStopWalk = 0; - startImageStopSwim = 0; - startImageStopFly = 0; - - startImageWalk = 0; - startImageSwim = 0; - startImageFly = 0; - startImageBuild = 0; - startImageHarvest = 0; - startImageAttack = 0; -} - -warrior -{ - spriteName = "data/gfx/unit"; - - startImageStopWalk = 256; - startImageStopSwim = 320; - startImageStopFly = 0; - - startImageWalk = 256; - startImageSwim = 320; - startImageFly = 0; - startImageBuild = 0; - startImageHarvest = 0; - startImageAttack = 384; -} diff --git a/data/zik/SConscript b/data/zik/SConscript index 68f9e8ac9..4c3894bbd 100644 --- a/data/zik/SConscript +++ b/data/zik/SConscript @@ -3,7 +3,7 @@ import os Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".ogg") != -1: PackTar(env["TARFILE"], file) diff --git a/data/zik/original/SConscript b/data/zik/original/SConscript index 150ab1e0f..8acfa18eb 100644 --- a/data/zik/original/SConscript +++ b/data/zik/original/SConscript @@ -3,7 +3,7 @@ import os Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".ogg") != -1: PackTar(env["TARFILE"], file) diff --git a/debian/SConscript b/debian/SConscript index aa82f5358..e3a95d8a5 100644 --- a/debian/SConscript +++ b/debian/SConscript @@ -6,7 +6,7 @@ Import('env') Import('PackTar') -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: files = Split("""changelog glob2.6 glob2.docs glob2-server.install README.source compat glob2-data.dirs glob2.install glob2.sgml rules control glob2-data.install glob2.menu SConscript @@ -34,7 +34,7 @@ if 'dist' in COMMAND_LINE_TARGETS and os.path.exists('/etc/debian_version'): env.Alias('dist', 'debmove') if env.GetOption('clean'): - env.Execute('dpkg-buildpackage -Tclean', chdir='..') + env.Execute('cd .. && dpkg-buildpackage -Tclean') env.Execute('rm -f glob2*.deb glob2*.changes') diff --git a/debian/copyright b/debian/copyright index 13e72017b..afc051ce8 100644 --- a/debian/copyright +++ b/debian/copyright @@ -33,9 +33,8 @@ is licensed under the GPLv2. Other parts are copyrighted to: ./libgag/: Copyright Kevlin Henney, 1997. All rights reserved. -./libwee/: Copyright (C) 2004 Martin Voelkle -./src/AIWarrush.cpp: Copyright (C) 2005 Eli Dupree -./src/AIWarrush.h: Copyright (C) 2005 Eli Dupree +./src/AI/AIWarrush.cpp: Copyright (C) 2005 Eli Dupree +./src/AI/AIWarrush.h: Copyright (C) 2005 Eli Dupree ./src/DynamicClouds.cpp: * Copyright 2007 Leo Wandersleb ./src/DynamicClouds.h: * Copyright 2007 Leo Wandersleb ./src/HeightMapGenerator.cpp: * Copyright 2006 Leo Wandersleb diff --git a/doc/aiEchoExamples.txt b/doc/aiEchoExamples.txt new file mode 100644 index 000000000..f8e3d9a3c --- /dev/null +++ b/doc/aiEchoExamples.txt @@ -0,0 +1,171 @@ +AIEcho Conditions/Constraints API Examples + +Source: extracted from a /* ... */ block that lived inside +ReachToInfinity::tick() in src/ai/echo/ReachToInfinity.cpp until +2026-05-09. The original source comment described it as +"demonstration code for the advanced use of Conditions". It was +preserved here as API reference when the surrounding tick() +method was decomposed into per-branch helpers. + +What this example demonstrates: + + - GradientInfo with multiple sources (CHERRY/ORANGE/PRUNE as a + combined fruit gradient) and a separate one with an obstacle + (AnyRessource blocks the building-cluster gradient). + - The full Construction-constraint family: MinimumDistance, + MaximumDistance, MinimizedDistance. + - Composite conditions: EitherCondition wrapping a + NotUnderConstruction predicate with a BuildingDestroyed + fallback, so the chain advances either when the previous link + finishes or when it dies. + - Population gating ("don't fire until colony has 5+ units"). + - ParticularBuilding wrapping arbitrary sub-conditions targeting + a specific building id (ties an order to one building rather + than a type). + - BuildingLevel predicate combined with construction-state + predicates (UnderConstruction, NotUnderConstruction). + - Resource-tracker conditions: RessourceTrackerAge::Greater and + RessourceTrackerAmount::Lesser used together as a "this inn + has been hungry for too long" predicate to drive + DestroyBuilding. + - SendMessage management order, gated on BuildingDestroyed, + used to schedule a rebuild via the AI's own message queue + (the receiving handler is ReachToInfinity::handle_message). + - UpgradeRepair orders chained off building-level checks for + auto-upgrading mid-construction. + - AssignWorkers with different worker counts at different + construction phases. + +Why this is not enabled in the production AI: + +The block builds a *daisy-chain* of 15 inns, each one's construction +gated on the previous link finishing or being destroyed. It is not a +strategy the AI should run — building 15 inns sequentially at tick +100 would self-sabotage early-game economy. It was wrapped in +/* ... */ from the start as a syntactic showcase, not a tunable +strategy. Treat this as documentation of intent, not as latent +functionality awaiting re-enablement. + +Note for the Rust port: do not transcribe verbatim. The condition +and constraint factories will look different in Rust (likely +Box with builder methods rather than `new`-passed +raw pointers), and the daisy-chain pattern is not worth preserving +even as a test fixture. If examples are needed, write fresh ones +against the Rust API. + +---------------------------------------------------------------------- +ORIGINAL CODE (verbatim, as it lived in ReachToInfinity::tick()) + + if(timer==100) + { + for(int g=0; g<1; ++g) + { + int prev_id=-1; + int first_id=-1; + int fifth_id=-1; + for(int n=0; n<15; ++n) + { + //The main order for the inn + BuildingOrder* bo = new BuildingOrder(IntBuildingType::FOOD_BUILDING, 2); + + //Constraints arround the location of wheat + AIEcho::Gradients::GradientInfo gi_wheat; + gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); + //You want to be close to wheat + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, 4)); + //You can't be farther than 10 units from wheat + bo->add_constraint(new AIEcho::Construction::MaximumDistance(gi_wheat, 10)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 3)); + + //Constraints arround the location of fruit + AIEcho::Gradients::GradientInfo gi_fruit; + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); + //You want to be reasnobly close to fruit, closer if possible + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_fruit, 1)); + + if(prev_id!=-1) + { + bo->add_condition(new EitherCondition(new ParticularBuilding(new NotUnderConstruction, prev_id), new BuildingDestroyed(prev_id))); + } + else + bo->add_condition(new Population(true, true, true, 5, Population::Greater)); + + //Add the building order to the list of orders + unsigned int id=echo.add_building_order(bo); + + if(prev_id!=-1) + { + ManagementOrder* mo_upgrade = new UpgradeRepair(id); + mo_upgrade->add_condition(new ParticularBuilding(new NotUnderConstruction, prev_id)); + mo_upgrade->add_condition(new ParticularBuilding(new BuildingLevel(2), prev_id)); + echo.add_management_order(mo_upgrade); + + ManagementOrder* mo_assign=new AssignWorkers(6, id); + mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, id)); + mo_assign->add_condition(new ParticularBuilding(new BuildingLevel(2), id)); + echo.add_management_order(mo_assign); + + ManagementOrder* mo_finish=new AssignWorkers(2, id); + mo_finish->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + mo_finish->add_condition(new ParticularBuilding(new BuildingLevel(2), id)); + echo.add_management_order(mo_finish); + } + if(n==0) + { + first_id=id; + } + if(n==4) + { + fifth_id=id; + } + + ManagementOrder* mo_completion=new AssignWorkers(1, id); + mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_completion); + + ManagementOrder* mo_tracker=new AddRessourceTracker(12, id, CORN); + mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_tracker); + + ManagementOrder* mo_delete=new DestroyBuilding(id); + mo_delete->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + mo_delete->add_condition(new ParticularBuilding(new RessourceTrackerAge(500, RessourceTrackerAge::Greater), id)); + mo_delete->add_condition(new ParticularBuilding(new RessourceTrackerAmount(48, RessourceTrackerAmount::Lesser), id)); + echo.add_management_order(mo_delete); + + ManagementOrder* mo_reconstruct = new SendMessage("construct inn"); + mo_reconstruct->add_condition(new BuildingDestroyed(id)); + echo.add_management_order(mo_reconstruct); + + prev_id=id; + } + + ManagementOrder* mo_upgrade = new UpgradeRepair(first_id); + mo_upgrade->add_condition(new ParticularBuilding(new NotUnderConstruction, fifth_id)); + echo.add_management_order(mo_upgrade); + + ManagementOrder* mo_assign=new AssignWorkers(6, first_id); + mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, first_id)); + mo_assign->add_condition(new ParticularBuilding(new BuildingLevel(2), first_id)); + echo.add_management_order(mo_assign); + + ManagementOrder* mo_finish=new AssignWorkers(2, first_id); + mo_finish->add_condition(new ParticularBuilding(new NotUnderConstruction, first_id)); + mo_finish->add_condition(new ParticularBuilding(new BuildingLevel(2), first_id)); + echo.add_management_order(mo_finish); + } + } diff --git a/docs/duplicate-functions.md b/docs/duplicate-functions.md new file mode 100644 index 000000000..a887481d9 --- /dev/null +++ b/docs/duplicate-functions.md @@ -0,0 +1,116 @@ +# Duplicate Functions in C++ Codebase + +Analysis of function names and bodies across `glob2/src/` using universal-ctags and body hashing. +Generated 2026-04-09. + +## Summary + +- 3,899 total function definitions scanned +- 37 groups of functions with identical bodies across different files +- Key consolidation opportunities for the Rust port noted below + +--- + +## Identical Function Bodies Across Files + +### GameHints / GameObjectives (near-identical classes) + +These two classes have the same structure with only naming differences: + +| GameHints method | GameObjectives method | Files | +|------------------|-----------------------|-------| +| `getNumberOfHints` (line 30) | `getNumberOfObjectives` (line 38) | GameHints.cpp, GameObjectives.cpp | +| `setHintHidden` (line 71) | `setObjectiveHidden` (line 87) | GameHints.cpp, GameObjectives.cpp | +| `setHintVisible` (line 79) | `setObjectiveVisible` (line 95) | GameHints.cpp, GameObjectives.cpp | +| `isHintVisible` (line 87) | `isObjectiveVisible` (line 103) | GameHints.cpp, GameObjectives.cpp | +| `getScriptNumber` (line 105) | `getScriptNumber` (line 196) | GameHints.cpp, GameObjectives.cpp | + +**Rust port note:** Unify into a single generic type (e.g., `ScriptEntry { hidden, script_number }`) with a kind enum or type alias. + +### YOG Listener Pattern (5-6 classes) + +Identical `addListener`/`removeListener` implementations across: + +- YOGClient.cpp (`addEventListener` / `removeEventListener`) +- YOGClientChatChannel.cpp +- YOGClientDownloadableMapList.cpp +- YOGClientGameListManager.cpp +- YOGClientPlayerListManager.cpp +- IRCTextMessageHandler.cpp (`addTextMessageListener`) + +**Rust port note:** Not needed in Rust -- use channels or a simple callback vec utility. + +### YOG Admin Destructors + +Identical destructors in: +- YOGClientCommandManager.cpp:35 +- YOGServerAdministrator.cpp:46 +- YOGServerRouterAdministrator.cpp:39 + +### Login / Register Screens + +`onTimer` is identical in: +- YOGLoginScreen.cpp:145 +- YOGRegisterScreen.cpp:79 + +**Rust port note:** Extract shared login/register timer logic. + +### KeyActions Classes + +`getName` and `getAction` are identical in: +- GameGUIKeyActions.cpp:163, :168 +- MapEditKeyActions.cpp:95, :100 + +**Rust port note:** Unify into a single KeyAction type. + +### ServerPlayer Variants + +`isConnected` and `sendMessage`/`sendNetMessage` identical in: +- YOGServerPlayer.cpp:329, :336 +- YOGServerRouterPlayer.cpp:102, :42 + +### Map/Game Info Getters + +`setNumberOfTeams` / `getNumberOfTeams` duplicated between: +- NetGamePlayerManager.cpp / MapHeader.cpp / YOGGameInfo.cpp + +`setCheckSum` duplicated between: +- ReplayReader.cpp:211 / ReplayWriter.cpp:99 + +### Dialog onAction + +Identical `onAction` in: +- GameGUIDialog.cpp:57 / MapEditDialog.cpp:46 +- CreditScreen.cpp:195 / MainMenuScreen.cpp:76 + +### Upload/Download Progress + +`getPercentUploaded` identical in: +- YOGClientMapDownloader.cpp:90 +- YOGClientMapUploader.cpp:127 + +--- + +## High-Count Duplicate Function Names (non-virtual, non-operator) + +These function names appear in multiple files. Many are simple getters returning a member, but worth reviewing for consolidation: + +| Count | Function Name | +|-------|---------------| +| 9 | `getPlayerID` | +| 9 | `getFileID` | +| 8 | `getMessage` | +| 6 | `getGameID` | +| 5 | `setMapHeader`, `setMapDiscovered`, `getMapHeader`, `getUsername`, `isConnected` | +| 4 | `updateGuardAreasGradient`, `updateGlobalGradient`, `updateForbiddenGradient`, `updateClearAreasGradient` | +| 4 | `stringIP`, `setValues`, `getValue`, `getRessource`, `getReason`, `getPlayerName` | +| 4 | `getName`, `getMapID`, `getIPAddress`, `getGameHeader`, `getError`, `getChatChannel`, `getAction`, `addPlayer` | + +--- + +## Methodology + +1. Extracted all function definitions using `universal-ctags -R --languages=C++ --c++-kinds=f --fields=+nE` +2. For duplicate names: grouped by function name, excluded operators and known virtual/interface methods +3. For identical bodies: extracted source from opening `{` to closing `}`, stripped comments and whitespace, MD5-hashed, grouped by hash, filtered to groups spanning multiple files +4. Trivial functions (< 20 chars normalized body) were excluded from body comparison diff --git a/docs/headless-replays.md b/docs/headless-replays.md new file mode 100644 index 000000000..29354eeae --- /dev/null +++ b/docs/headless-replays.md @@ -0,0 +1,222 @@ +# Headless Mode & Replay Generation + +Run AI games without a GUI to generate `.replay` files for cross-codebase fidelity testing (C++ vs Rust). + +## CLI Flags + +### `--nox ` + +Runs a saved `.game` file headlessly. + +- `` — path to a `.game` save file containing map, players, and AI configuration +- `` — number of simulation ticks to run (0 = run until game over) +- `` — how many times to repeat the game + +```bash +./glob2 --nox games/nicowar_2v2.game 5000 1 +``` + +To create a `.game` file with specific AI players: start the game with GUI, set up a custom game with the desired AI types, then save immediately. That save becomes the `.game` file you pass to `--nox`. + +### `-test-games-nox [count]` + +Runs random AI-vs-AI games headlessly. Each game auto-ends at 90,000 ticks (~60 minutes of game time at 25 ticks/sec). An optional `count` parameter controls how many games to run (default: infinite). + +```bash +./glob2 -test-games-nox 1 # run one game and exit +./glob2 -test-games-nox 5 # run five games and exit +./glob2 -test-games-nox # run forever (kill with Ctrl+C) +``` + +The random game setup (`Engine::createRandomGame`) creates one local player + N AI players with randomly chosen AI types from the map's team count. + +### `--ai-types ` + +Constrains the AI pool that `createRandomGame` draws from when generating +random matchups for `-test-games` / `-test-games-nox`. Comma-separated, +case-insensitive AI names. Default (no flag) is the legacy uniform pick +over `numbi, castor, warrush, reachtoinfinity, nicowar`. + +```bash +# Bias the dataset toward strong AIs only: +./glob2 -test-games-nox 100 --ai-types nicowar,warrush + +# Single-AI self-play replays (every AI slot is Nicowar): +./glob2 -test-games-nox 50 --ai-types nicowar +``` + +Valid names: `numbi`, `castor`, `warrush`, `reachtoinfinity`, `nicowar`, +`toubib`. Unknown names are reported on stderr and skipped (an empty +remaining pool falls back to default behavior). + +### `--map ` and `--matchup ` + +Pin the map and per-team AI assignment for `-test-games-nox`, replacing +the random pieces with explicit choices. Used by the AI-trainer +pipeline to produce curated datasets (exact counts per matchup). + +```bash +# Nicowar (team 0) vs. Warrush (team 1) on the Playground map: +./glob2 -test-games-nox 1 --map Playground --matchup nicowar,warrush + +# Three-team game on a custom map: +./glob2 -test-games-nox 1 --map "BigArena" --matchup nicowar,warrush,numbi +``` + +- `--map ` is the bare map filename without `.map` (resolved as + `maps/.map`). On a typo the binary fails fast with a clear + message; it does **not** silently retry random maps. +- `--matchup ` is one AI name per team. The list length must + match the loaded map's `getNumberOfTeams()` exactly — startup fails + otherwise. +- `--matchup` requires `--map` (we need the map's team count to + validate the matchup before launching). +- `--matchup` is mutually exclusive with `--ai-types` (pool vs. exact). + +### `--save-game-as ` + +Writes the fully-initialised tick-0 game state to `` as a `.game` file before running. Lets a `-test-games-nox` scenario be replayed deterministically later via `--nox `. Pair with `GLOB2_TEST_SEED` for full reproducibility — the seed is mirrored into the saved `GameHeader` so the reloaded run matches the original. + +```bash +GLOB2_TEST_SEED=42 ./glob2 -test-games-nox 1 \ + --map BigArena --matchup reachtoinfinity,nicowar \ + --save-game-as games/cross-replay.game +``` + +`` is resolved by the file manager: relative paths land under `~/.glob2/` (so `--save-game-as games/foo.game` writes to `~/.glob2/games/foo.game`); absolute paths (`/tmp/foo.game`, `C:\foo.game`) are used as-is. Requires `-test-games` or `-test-games-nox`; the save fires at random-game creation time. Without `GLOB2_TEST_SEED`, the wall-clock seed at run-start is captured and the .game file is still reproducible — just not predictable across separate invocations. + +**Local-player quirk:** the engine still creates a passive `P_LOCAL` +player on team 0 in `-test-games-nox` mode (the headless engine +expects one). The `GLOB2_GAME_END players=...` summary will show +`team0:local` alongside the matchup-assigned AI for team 0; both are +expected. Only the matchup AIs issue orders. + +### `-test-games` + +Same as `-test-games-nox` but **with GUI** — useful for visually verifying AI behavior. + +## AI-Trainer Dataset Output + +When `GLOB2_DATASET_PATH` is set, the engine writes one binary record +per executed order to that path, alongside the normal `.replay`. Used +by the `glob2-ai-trainer` pipeline to feed BC training without needing +to re-simulate the replay. + +```bash +GLOB2_DATASET_PATH=/tmp/game.dataset \ +GLOB2_REPLAY_PATH=/tmp/game.replay \ + ./glob2 -test-games-nox 1 --map A_big_pond --matchup nicowar,warrush,numbi +``` + +Format (little-endian): + +``` +HEADER (8 bytes) + [4B] magic "GDS1" + [4B] u32 num_records (patched at close) + +PER-RECORD + [4B] u32 tick + [1B] u8 sender_player_index + [1B] u8 order_type + [4B] u32 state_blob_len + [state_blob_len bytes] state features + [4B] u32 order_payload_len + [order_payload_len bytes] order payload (Order::getData()) +``` + +`state_blob_len` is currently always 0 — observation features land +alongside the trainer's training loop. The wire format doesn't change +shape when that happens; the blob just stops being empty. + +No version field: single producer, single consumer, regenerating +datasets is cheap. If the schema ever changes wire-incompatibly, bump +the magic to `GDS2` and parsers reject by magic mismatch. + +See `glob2/src/DatasetWriter.{h,cpp}` for the writer. + +## Replay Output + +All modes write replays to `~/.glob2/replays/last_game.replay` by default. +**Each new game overwrites the previous replay** — copy it out between runs, +or override the path per-game with the `GLOB2_REPLAY_PATH` env var: + +```bash +GLOB2_REPLAY_PATH=replays/game-001.replay ./glob2 -test-games-nox 1 +``` + +This lets concurrent headless instances write to distinct files (used by the +AI-trainer replay-generation pipeline). + +## Game-End Summary Line + +When `automaticEndingGame` fires (set by `--nox`, `-test-games-nox`, and +`-test-games`), the engine prints a machine-parseable summary line right +after the existing tick/minute log: + +``` +GLOB2_GAME_END ticks=2483 winner_team=1 seed=1777219846 map="Playground" orders=2525 players=team0:local,team1:Nicowar,team2:Warrush +``` + +- `ticks` — total simulation ticks elapsed +- `winner_team` — first team with `hasWon` set, or `-1` on timeout +- `seed` — `GameHeader::getRandomSeed()` value used for this game +- `map` — map name (`MapHeader::getMapName()`); double-quoted to allow + spaces. Value never contains literal `"` characters in practice +- `orders` — count of orders pushed into the replay (excludes voice and + null orders); from `ReplayWriter::getOrderCount()` +- `players` — comma-separated `teamN:type` pairs; type is `local`, `ip`, + `none`, or an AI name from `AINames::getAIText` + +The format is intended for grep/regex consumption — fields are +space-separated key=value, with `map` quoted. + +For cross-codebase testing, the canonical baselines live in `glob2/tests/baselines/`. See [`docs/replay-verification.md`](../../docs/replay-verification.md) at the workspace root for the full verification workflows and the regeneration procedure. + +The `ReplayWriter` records live during gameplay: +- At game start: writes the full game state header via `GameGUI::save()`, then replay version (`VERSION_MAJOR`, `VERSION_MINOR`) +- Each tick: calls `advanceStep()` to track tick deltas +- When an order executes: writes `u16 stepsSinceLastOrder` + the serialized order via `NetSendOrder::encodeData()` +- At game end: writes a final `NullOrder` to terminate the stream + +## Replay File Format + +``` +[GameGUI::save() header] — full game state at tick 0 +[u16 VERSION_MAJOR] — replay format version +[u16 VERSION_MINOR] +[order stream] — repeating until NullOrder: + u16 stepsSinceLastOrder — tick delta since previous order + NetSendOrder: + u32 size — byte count of order data + u8 orderType — order type ID (see Order.h) + [order data bytes] — type-specific payload + u8 sender — player index + u32 checksum — game state checksum at this tick + ... +[u16 0 + NullOrder] — end marker +``` + +## AI Types + +| ID | Name | Enum | Notes | +|----|------|------|-------| +| 0 | None | `AI::NONE` | Does nothing | +| 1 | Numbi | `AI::NUMBI` | Simple beginner AI | +| 2 | Castor | `AI::CASTOR` | Default toggle AI, moderate | +| 3 | Warrush | `AI::WARRUSH` | Aggressive rush strategy | +| 4 | ReachToInfinity | `AI::REACHTOINFINITY` | Expansionist (Echo wrapper) | +| 5 | Nicowar | `AI::NICOWAR` | Strongest all-round AI (Echo wrapper) | +| 6 | Toubib | `AI::TOUBIB` | Simple AI | + +Player types that trigger AI loading: any `BasePlayer::type >= P_AI (5)`. The player type encodes which AI: `P_AI + implementationID` maps to the enum above. + +## Key Source Files + +- `src/Engine.cpp` — `initCustom()` loads `.game` files; `run()` contains the game loop; `createRandomGame()` sets up random AI matches +- `src/ReplayWriter.cpp` — writes replay data live during gameplay +- `src/ReplayReader.cpp` — reads replays for playback +- `src/GlobalContainer.cpp` — `parseArgs()` handles CLI flags +- `src/Glob2.cpp` — `runNoX()` and `runTestGames()` entry points +- `src/Game.cpp` — `executeOrder()` pushes orders to `ReplayWriter` +- `src/AI.cpp` — `AI::save()`/`AI::load()` with implementation dispatch diff --git a/fedora/SConscript b/fedora/SConscript index 2f6407bb2..f0f763fae 100755 --- a/fedora/SConscript +++ b/fedora/SConscript @@ -4,7 +4,7 @@ Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: any=False for file in os.listdir("."): if file.find(".spec")!=-1: diff --git a/games/G2.game b/games/G2.game new file mode 100644 index 000000000..094f5d62f Binary files /dev/null and b/games/G2.game differ diff --git a/games/gd-archipelago.game b/games/gd-archipelago.game new file mode 100644 index 000000000..270aaa142 Binary files /dev/null and b/games/gd-archipelago.game differ diff --git a/games/gd-bigarena-long.game b/games/gd-bigarena-long.game new file mode 100644 index 000000000..51460c636 Binary files /dev/null and b/games/gd-bigarena-long.game differ diff --git a/games/gd-large-4ai.game b/games/gd-large-4ai.game new file mode 100644 index 000000000..8ed431ca8 Binary files /dev/null and b/games/gd-large-4ai.game differ diff --git a/games/gd-small-2ai.game b/games/gd-small-2ai.game new file mode 100644 index 000000000..1a8021ad2 Binary files /dev/null and b/games/gd-small-2ai.game differ diff --git a/glob2.sln b/glob2.sln deleted file mode 100755 index 0e1b32f76..000000000 --- a/glob2.sln +++ /dev/null @@ -1,36 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.33502.453 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glob2", "glob2.vcxproj", "{21BF838A-D64B-4500-92CB-93A7438A3DF0}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug DX9|x64 = Debug DX9|x64 - Debug DX9|x86 = Debug DX9|x86 - Debug SDL|x64 = Debug SDL|x64 - Debug SDL|x86 = Debug SDL|x86 - Release SDL|x64 = Release SDL|x64 - Release SDL|x86 = Release SDL|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Debug DX9|x64.ActiveCfg = Debug DX9|x64 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Debug DX9|x64.Build.0 = Debug DX9|x64 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Debug DX9|x86.ActiveCfg = Debug DX9|Win32 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Debug DX9|x86.Build.0 = Debug DX9|Win32 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Debug SDL|x64.ActiveCfg = Debug SDL|x64 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Debug SDL|x64.Build.0 = Debug SDL|x64 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Debug SDL|x86.ActiveCfg = Debug SDL|Win32 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Debug SDL|x86.Build.0 = Debug SDL|Win32 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Release SDL|x64.ActiveCfg = Release SDL|x64 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Release SDL|x64.Build.0 = Release SDL|x64 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Release SDL|x86.ActiveCfg = Release SDL|Win32 - {21BF838A-D64B-4500-92CB-93A7438A3DF0}.Release SDL|x86.Build.0 = Release SDL|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1C9FA9E0-5917-43FC-90FC-FB8082643C1F} - EndGlobalSection -EndGlobal diff --git a/glob2.vcproj b/glob2.vcproj deleted file mode 100755 index 2a18571ef..000000000 --- a/glob2.vcproj +++ /dev/null @@ -1,882 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/glob2.vcxproj b/glob2.vcxproj deleted file mode 100644 index 44286e055..000000000 --- a/glob2.vcxproj +++ /dev/null @@ -1,625 +0,0 @@ - - - - - Debug DX9 - Win32 - - - Debug DX9 - x64 - - - Debug SDL - Win32 - - - Debug SDL - x64 - - - Release SDL - Win32 - - - Release SDL - x64 - - - - 17.0 - {21BF838A-D64B-4500-92CB-93A7438A3DF0} - Win32Proj - - - - Application - v143 - MultiByte - - - Application - v143 - MultiByte - - - Application - v143 - MultiByte - - - Application - v143 - MultiByte - - - Application - v143 - MultiByte - - - Application - v143 - MultiByte - - - - - - - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>17.0.33312.129 - - - Debug\ - Debug\ - true - - - true - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)vcpkg_installed\x64-windows\x64-windows\include;$(ProjectDir)vcpkg_installed\x64-windows\x64-windows\include\SDL2;$(ProjectDir);$(ProjectDir)src;$(ProjectDir)libwee\include;$(ProjectDir)libgag\include;$(ProjectDir)libusl\src - - - Release\ - Release\ - false - - - false - - - $(Configuration)\ - $(Configuration)\ - true - - - true - - - true - - - - Disabled - libgag\include;WinSDLBuild\Include;WinSDLBuild\Include\freetype2;WinSDLBuild\Include\SDL;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - EnableFastChecks - MultiThreadedDebugDLL - true - - Level3 - EditAndContinue - - - sdl2.lib;sdl2_net.lib;sdl2_image.lib;freetype.lib;sdl2main.lib;ws2_32.lib;ogg.lib;vorbis.lib;vorbisfile.lib;sdl2_ttf.lib;opengl32.lib;speex.lib;glu32.lib;zlib.lib;%(AdditionalDependencies) - $(OutDir)glob2.exe - WinSDLBuild\Lib;%(AdditionalLibraryDirectories) - true - $(OutDir)glob2.pdb - Console - MachineX86 - - - - - Disabled - libgag\include;WinSDLBuild\Include;WinSDLBuild\Include\freetype2;WinSDLBuild\Include\SDL;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebugDLL - true - - - Level3 - ProgramDatabase - true - - - sdl2.lib;sdl2_net.lib;sdl2_image.lib;freetype.lib;sdl2main.lib;ws2_32.lib;ogg.lib;vorbis.lib;vorbisfile.lib;sdl2_ttf.lib;opengl32.lib;speex.lib;glu32.lib;zlib.lib;%(AdditionalDependencies) - $(OutDir)glob2.exe - WinSDLBuild\Lib;%(AdditionalLibraryDirectories) - true - $(OutDir)glob2.pdb - Console - - - - - MaxSpeed - OnlyExplicitInline - true - libgag\include;WinSDLBuild\Include;WinSDLBuild\Include\freetype2;WinSDLBuild\Include\SDL;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - MultiThreadedDLL - true - true - - Level3 - ProgramDatabase - - - sdl2.lib;sdl2_net.lib;sdl2_image.lib;sdl2_ttf.lib;freetype.lib;sdl2main.lib;ws2_32.lib;ogg_static.lib;vorbis_static.lib;vorbisfile_static.lib;opengl32.lib;speex.lib;glu32.lib;zlib.lib;%(AdditionalDependencies) - $(OutDir)glob2.exe - WinSDLBuild\Lib;%(AdditionalLibraryDirectories) - false - Console - true - true - MachineX86 - - - - - MaxSpeed - OnlyExplicitInline - true - libgag\include;WinSDLBuild\Include;WinSDLBuild\Include\freetype2;WinSDLBuild\Include\SDL;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - MultiThreadedDLL - true - true - - - Level3 - ProgramDatabase - - - sdl2.lib;sdl2_net.lib;sdl2_image.lib;sdl2_ttf.lib;freetype.lib;sdl2main.lib;ws2_32.lib;ogg_static.lib;vorbis_static.lib;vorbisfile_static.lib;opengl32.lib;speex.lib;glu32.lib;zlib.lib;%(AdditionalDependencies) - $(OutDir)glob2.exe - WinSDLBuild\Lib;%(AdditionalLibraryDirectories) - false - Console - true - true - - - - - Disabled - libgag\include;WinSDLBuild\Include;WinSDLBuild\Include\freetype2;WinSDLBuild\Include\SDL;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;DX9_BACKEND;%(PreprocessorDefinitions) - true - EnableFastChecks - MultiThreadedDebugDLL - true - - Level3 - EditAndContinue - - - sdl2.lib;sdl2_net.lib;sdl2_image.lib;freetype.lib;sdl2main.lib;ws2_32.lib;ogg.lib;vorbis.lib;vorbisfile.lib;sdl2_ttf.lib;opengl32.lib;%(AdditionalDependencies) - $(OutDir)glob2.exe - WinSDLBuild\Lib;%(AdditionalLibraryDirectories) - true - $(OutDir)glob2.pdb - Console - MachineX86 - - - - - Disabled - libgag\include;WinSDLBuild\Include;WinSDLBuild\Include\freetype2;WinSDLBuild\Include\SDL;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;DX9_BACKEND;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebugDLL - true - - - Level3 - ProgramDatabase - - - sdl2.lib;sdl2_net.lib;sdl2_image.lib;freetype.lib;sdl2main.lib;ws2_32.lib;ogg.lib;vorbis.lib;vorbisfile.lib;sdl2_ttf.lib;opengl32.lib;%(AdditionalDependencies) - $(OutDir)glob2.exe - WinSDLBuild\Lib;%(AdditionalLibraryDirectories) - true - $(OutDir)glob2.pdb - Console - - - - - - - - ZLIB_WINAPI;%(PreprocessorDefinitions) - ZLIB_WINAPI;%(PreprocessorDefinitions) - ZLIB_WINAPI;%(PreprocessorDefinitions) - ZLIB_WINAPI;%(PreprocessorDefinitions) - - - - true - true - - - - - - - - - - - - - - - - - - - - - - - true - true - - - - - ZLIB_WINAPI;%(PreprocessorDefinitions) - ZLIB_WINAPI;%(PreprocessorDefinitions) - ZLIB_WINAPI;%(PreprocessorDefinitions) - ZLIB_WINAPI;%(PreprocessorDefinitions) - - - - - - - - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gnupg/SConscript b/gnupg/SConscript index 71cf1d7fd..8b0f1fadb 100644 --- a/gnupg/SConscript +++ b/gnupg/SConscript @@ -1,7 +1,7 @@ Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: PackTar(env["TARFILE"], "sha1.c") PackTar(env["TARFILE"], "sha1.h") PackTar(env["TARFILE"], "SConscript") diff --git a/libgag/SConscript b/libgag/SConscript index 0267efc3b..47b56c52f 100644 --- a/libgag/SConscript +++ b/libgag/SConscript @@ -1,6 +1,6 @@ Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: PackTar(env["TARFILE"], "SConscript") SConscript("src/SConscript") SConscript("include/SConscript") diff --git a/libgag/include/BinaryStream.h b/libgag/include/BinaryStream.h index c8b9764b3..abdfd26e7 100644 --- a/libgag/include/BinaryStream.h +++ b/libgag/include/BinaryStream.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __BINARYSTREAM_H -#define __BINARYSTREAM_H +#pragma once #include #include @@ -110,5 +93,3 @@ namespace GAGCore virtual bool isValid(void) { return backend->isValid(); } }; } - -#endif diff --git a/libgag/include/CursorManager.h b/libgag/include/CursorManager.h index 25f1cf4d2..a45de6b65 100644 --- a/libgag/include/CursorManager.h +++ b/libgag/include/CursorManager.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __CURSOR_MANAGER_H -#define __CURSOR_MANAGER_H +#pragma once #include @@ -79,5 +62,3 @@ namespace GAGCore void draw(DrawableSurface *ds, int x, int y); }; } - -#endif diff --git a/libgag/include/FileManager.h b/libgag/include/FileManager.h index 5c267e377..f40db181e 100644 --- a/libgag/include/FileManager.h +++ b/libgag/include/FileManager.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __FILEMANAGER_H -#define __FILEMANAGER_H +#pragma once #include "GAGSys.h" #include @@ -129,4 +112,3 @@ namespace GAGCore }; } -#endif diff --git a/libgag/include/FormatableString.h b/libgag/include/FormatableString.h index 38653b246..342b7491d 100644 --- a/libgag/include/FormatableString.h +++ b/libgag/include/FormatableString.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef FORMATABLESTRING_H -#define FORMATABLESTRING_H +#pragma once #include #include @@ -115,5 +98,3 @@ namespace GAGCore { operator const char*() { return this->c_str(); } }; } - -#endif // FORMATABLESTRING_H // diff --git a/libgag/include/GAG.h b/libgag/include/GAG.h index 789882eb9..1749860c0 100644 --- a/libgag/include/GAG.h +++ b/libgag/include/GAG.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GAG_H -#define __GAG_H +#pragma once #include "GAGSys.h" #include "FileManager.h" @@ -39,4 +22,3 @@ #include "GUISelector.h" #include "GUIAnimation.h" -#endif diff --git a/libgag/include/GAGSys.h b/libgag/include/GAGSys.h index 99a15cf83..7716e87a3 100644 --- a/libgag/include/GAGSys.h +++ b/libgag/include/GAGSys.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GAGSYS_H -#define __GAGSYS_H +#pragma once #ifndef MAX_SINT32 #define MAX_SINT32 0x7FFFFFFF @@ -71,5 +54,3 @@ #define VARARRAY(t,n,s) t n[s] #endif #endif - -#endif diff --git a/libgag/include/GUIAnimation.h b/libgag/include/GUIAnimation.h index 3e5b3465a..93cfcc3d6 100644 --- a/libgag/include/GUIAnimation.h +++ b/libgag/include/GUIAnimation.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIANIMATION_H -#define __GUIANIMATION_H +#pragma once #include "GUIBase.h" #include @@ -52,5 +35,3 @@ namespace GAGGUI virtual void paint(void); }; } - -#endif diff --git a/libgag/include/GUIBase.h b/libgag/include/GUIBase.h index fb2d2ccfa..8066a5de9 100644 --- a/libgag/include/GUIBase.h +++ b/libgag/include/GUIBase.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIBASE_H -#define __GUIBASE_H +#pragma once #include "GAGSys.h" #include "GraphicContext.h" @@ -374,5 +357,3 @@ namespace GAGGUI virtual void paint(void); }; } - -#endif diff --git a/libgag/include/GUIButton.h b/libgag/include/GUIButton.h index d9b89ec57..3128c1615 100644 --- a/libgag/include/GUIButton.h +++ b/libgag/include/GUIButton.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIBUTTON_H -#define __GUIBUTTON_H +#pragma once #include "GUIBase.h" #include @@ -180,5 +163,3 @@ namespace GAGGUI virtual void onSDLMouseButtonUp(SDL_Event *event); }; } - -#endif diff --git a/libgag/include/GUICheckList.h b/libgag/include/GUICheckList.h index cbd935c37..485ba486b 100644 --- a/libgag/include/GUICheckList.h +++ b/libgag/include/GUICheckList.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GUICheckList_h -#define GUICheckList_h +#pragma once #include "GUIList.h" @@ -47,7 +31,3 @@ namespace GAGGUI virtual void handleItemClick(size_t element, int mx, int my); }; }; - - - -#endif diff --git a/libgag/include/GUIFileList.h b/libgag/include/GUIFileList.h index 58c2e8824..72d405124 100644 --- a/libgag/include/GUIFileList.h +++ b/libgag/include/GUIFileList.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIFILELIST_H -#define __GUIFILELIST_H +#pragma once #include "FileManager.h" #include "GUIList.h" @@ -73,5 +56,3 @@ namespace GAGGUI void selectionChanged(); }; } - -#endif diff --git a/libgag/include/GUIImage.h b/libgag/include/GUIImage.h index 603c18e81..f170f0b91 100644 --- a/libgag/include/GUIImage.h +++ b/libgag/include/GUIImage.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIIMAGE_H -#define __GUIIMAGE_H +#pragma once #include "GUIBase.h" #include "GraphicContext.h" @@ -38,5 +21,3 @@ namespace GAGGUI virtual void paint(void); }; } - -#endif diff --git a/libgag/include/GUIKeySelector.h b/libgag/include/GUIKeySelector.h index d89e63f28..e5a561180 100644 --- a/libgag/include/GUIKeySelector.h +++ b/libgag/include/GUIKeySelector.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIKeySelector_h -#define __GUIKeySelector_h +#pragma once #include "GUIBase.h" #include "GraphicContext.h" @@ -73,6 +55,3 @@ namespace GAGGUI bool blinkVisible; }; }; - - -#endif diff --git a/libgag/include/GUIList.h b/libgag/include/GUIList.h index 8d8e37524..fdfe5615a 100644 --- a/libgag/include/GUIList.h +++ b/libgag/include/GUIList.h @@ -1,26 +1,10 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUILIST_H -#define __GUILIST_H +#pragma once #include "GUIBase.h" +#include #include #include @@ -107,6 +91,8 @@ namespace GAGGUI //! Return the index of the current selection. Returns -1 if no selection int getSelectionIndex(void) const; + //! Return the current selection as an optional index. std::nullopt if no selection. + std::optional selection(void) const; //! Set the index of the current selection. Set -1 for no selection void setSelectionIndex(int index); @@ -126,5 +112,3 @@ namespace GAGGUI }; } -#endif - diff --git a/libgag/include/GUIMessageBox.h b/libgag/include/GUIMessageBox.h index a13ef88a3..286bb2c0a 100644 --- a/libgag/include/GUIMessageBox.h +++ b/libgag/include/GUIMessageBox.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIMESSAGEBOX_H -#define __GUIMESSAGEBOX_H +#pragma once #include "GUIBase.h" @@ -39,5 +22,3 @@ namespace GAGGUI //! \retval the nummer of the clicked button, -1 on unexpected early-out (CTRL-C, ...) int MessageBox(GAGCore::GraphicContext *parentCtx, const std::string font, MessageBoxType type, std::string title, std::string caption1, std::string caption2 = "", std::string caption3 = ""); } - -#endif diff --git a/libgag/include/GUINumber.h b/libgag/include/GUINumber.h index 1508174f6..f3a1f9c20 100644 --- a/libgag/include/GUINumber.h +++ b/libgag/include/GUINumber.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUI_NUMBER_H -#define __GUI_NUMBER_H +#pragma once #include "GUIBase.h" #include @@ -65,5 +48,3 @@ namespace GAGGUI }; } -#endif - diff --git a/libgag/include/GUIProgressBar.h b/libgag/include/GUIProgressBar.h index 3f003f998..f0ee5d0c3 100644 --- a/libgag/include/GUIProgressBar.h +++ b/libgag/include/GUIProgressBar.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIPROGRESS_BAR_H -#define __GUIPROGRESS_BAR_H +#pragma once #include "GUIBase.h" #include "GraphicContext.h" @@ -46,5 +29,3 @@ namespace GAGGUI virtual void paint(void); }; } - -#endif diff --git a/libgag/include/GUIRatio.h b/libgag/include/GUIRatio.h index f2ea2bb34..8b55c9988 100644 --- a/libgag/include/GUIRatio.h +++ b/libgag/include/GUIRatio.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUI_RATIO_H -#define __GUI_RATIO_H +#pragma once #include "GUIBase.h" #include @@ -81,5 +64,3 @@ namespace GAGGUI virtual void onSDLMouseMotion(SDL_Event *event); }; } - -#endif diff --git a/libgag/include/GUISelector.h b/libgag/include/GUISelector.h index 04377882b..bf55dfe01 100644 --- a/libgag/include/GUISelector.h +++ b/libgag/include/GUISelector.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUISELECTOR_H -#define __GUISELECTOR_H +#pragma once #include "GUIBase.h" #include @@ -62,5 +45,3 @@ namespace GAGGUI virtual void onSDLMouseButtonUp(SDL_Event *event); }; } - -#endif diff --git a/libgag/include/GUIStyle.h b/libgag/include/GUIStyle.h index 3752259ae..b44d27d25 100644 --- a/libgag/include/GUIStyle.h +++ b/libgag/include/GUIStyle.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2007 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2007 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUISTYLE_H -#define __GUISTYLE_H +#pragma once #include "GraphicContext.h" @@ -63,5 +46,3 @@ namespace GAGGUI extern Style defaultStyle; } - -#endif diff --git a/libgag/include/GUITabScreen.h b/libgag/include/GUITabScreen.h index 6e0a2b511..28e3bf371 100644 --- a/libgag/include/GUITabScreen.h +++ b/libgag/include/GUITabScreen.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GUITabScreen_h -#define GUITabScreen_h +#pragma once #include "GUIBase.h" #include @@ -100,5 +82,3 @@ namespace GAGGUI bool longerButtons; }; }; - -#endif diff --git a/libgag/include/GUITabScreenWindow.h b/libgag/include/GUITabScreenWindow.h index a4c89ea6d..403d57c8c 100644 --- a/libgag/include/GUITabScreenWindow.h +++ b/libgag/include/GUITabScreenWindow.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GUITabScreenWindow_h -#define GUITabScreenWindow_h +#pragma once #include "GUIBase.h" @@ -87,5 +69,3 @@ namespace GAGGUI bool activated; }; }; - -#endif diff --git a/libgag/include/GUIText.h b/libgag/include/GUIText.h index c098cecea..329627e29 100644 --- a/libgag/include/GUIText.h +++ b/libgag/include/GUIText.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUITEXT_H -#define __GUITEXT_H +#pragma once #include "GUIBase.h" #include "GraphicContext.h" @@ -60,5 +43,3 @@ namespace GAGGUI void constructor(int x, int y, Uint32 hAlign, Uint32 vAlign, const std::string font, const std::string text, int w, int h); }; } - -#endif diff --git a/libgag/include/GUITextArea.h b/libgag/include/GUITextArea.h index 3f4d8ca6c..4866eac17 100644 --- a/libgag/include/GUITextArea.h +++ b/libgag/include/GUITextArea.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUITEXTAREA_H -#define __GUITEXTAREA_H +#pragma once #include "GUIBase.h" #include @@ -125,5 +108,3 @@ namespace GAGGUI virtual void onSDLTextInput(SDL_Event *event); }; } - -#endif diff --git a/libgag/include/GUITextInput.h b/libgag/include/GUITextInput.h index bcd45a132..453f0be08 100644 --- a/libgag/include/GUITextInput.h +++ b/libgag/include/GUITextInput.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUITEXTINPUT_H -#define __GUITEXTINPUT_H +#pragma once #include "GUIBase.h" #include @@ -106,5 +89,3 @@ namespace GAGGUI virtual void onSDLTextInput(SDL_Event *event); }; } - -#endif diff --git a/libgag/include/GraphicContext.h b/libgag/include/GraphicContext.h index 17745cd3a..961850f19 100644 --- a/libgag/include/GraphicContext.h +++ b/libgag/include/GraphicContext.h @@ -1,26 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GRAPHICCONTEXT_H -#define __GRAPHICCONTEXT_H +#pragma once #include "SDLGraphicContext.h" -#endif - diff --git a/libgag/include/KeyPress.h b/libgag/include/KeyPress.h index c410784aa..3b1171bfe 100644 --- a/libgag/include/KeyPress.h +++ b/libgag/include/KeyPress.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef KeyPress_h -#define KeyPress_h +#pragma once #include "SDL.h" #include @@ -79,6 +63,3 @@ class KeyPress bool meta; bool shift; }; - - -#endif diff --git a/libgag/include/SConscript b/libgag/include/SConscript index 7fc432515..b77f0a9c9 100644 --- a/libgag/include/SConscript +++ b/libgag/include/SConscript @@ -3,7 +3,7 @@ import os Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".h") != -1: PackTar(env["TARFILE"], file) diff --git a/libgag/include/SDLGraphicContext.h b/libgag/include/SDLGraphicContext.h index ce7111732..a8f2ee989 100644 --- a/libgag/include/SDLGraphicContext.h +++ b/libgag/include/SDLGraphicContext.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef INCLUDED_SDL_GRAPHICCONTEXT_H -#define INCLUDED_SDL_GRAPHICCONTEXT_H +#pragma once #include "GAGSys.h" #include "CursorManager.h" @@ -31,7 +14,7 @@ #include #include -#include +#include #include @@ -307,7 +290,7 @@ namespace GAGCore // This holds texts already drawn to the screen and detected, so that pictures aren't taken twice, which would lag the game badly static std::set wroteTexts; // This holds detected texts that will be printed on the next flush - static std::vector > drawSquares; + static std::vector > drawSquares; // This holds the directory the pictures will be stored in. The system is disabled if this string is empty. static std::string translationPicturesDirectory; // This flushes all of the detected texts, making bmp pictures @@ -493,5 +476,3 @@ namespace GAGCore virtual int getFrameCount(void); }; } - -#endif diff --git a/libgag/include/Stream.h b/libgag/include/Stream.h index 84c4fa9ef..6a6c881ee 100644 --- a/libgag/include/Stream.h +++ b/libgag/include/Stream.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __STREAM_H -#define __STREAM_H +#pragma once // For U/SintNN #include "Types.h" @@ -115,5 +98,3 @@ namespace GAGCore bool isEndOfStream(void); }; } - -#endif diff --git a/libgag/include/StreamBackend.h b/libgag/include/StreamBackend.h index 7ae90ca73..e5c2ca6aa 100644 --- a/libgag/include/StreamBackend.h +++ b/libgag/include/StreamBackend.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __STREAMBACKEND_H -#define __STREAMBACKEND_H +#pragma once #include #include @@ -155,5 +138,3 @@ namespace GAGCore virtual Uint32 getHash(void) { return hash; } }; } - -#endif diff --git a/libgag/include/StreamFilter.h b/libgag/include/StreamFilter.h index aa3f90115..c95dc6b94 100644 --- a/libgag/include/StreamFilter.h +++ b/libgag/include/StreamFilter.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __STREAMFILTER_H -#define __STREAMFILTER_H +#pragma once #include "StreamBackend.h" @@ -51,5 +34,3 @@ namespace GAGCore virtual bool isEndOfStream() { return false; } }; } - -#endif diff --git a/libgag/include/StringTable.h b/libgag/include/StringTable.h index 510a11dc8..2d24dc53b 100644 --- a/libgag/include/StringTable.h +++ b/libgag/include/StringTable.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __STRINGTABLE_H -#define __STRINGTABLE_H +#pragma once #include #include @@ -66,5 +49,3 @@ namespace GAGCore } -#endif - diff --git a/libgag/include/SupportFunctions.h b/libgag/include/SupportFunctions.h index cd68b5cc2..52aab9a6a 100644 --- a/libgag/include/SupportFunctions.h +++ b/libgag/include/SupportFunctions.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __SUPPORT_FUNCTION_H -#define __SUPPORT_FUNCTION_H +#pragma once #include "GAGSys.h" #include @@ -51,5 +34,3 @@ namespace GAGCore float fmax(float f1, float f2, float f3); }; -#endif - diff --git a/libgag/include/TextSort.h b/libgag/include/TextSort.h index 49374cbd2..5926a2390 100644 --- a/libgag/include/TextSort.h +++ b/libgag/include/TextSort.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef TextSort_h -#define TextSort_h +#pragma once #include @@ -26,5 +10,3 @@ namespace GAGCore ///This compares two strings using the natsort library created by Martin Pool bool naturalStringSort(const std::string& lhs, const std::string& rhs); }; - -#endif diff --git a/libgag/include/TextStream.h b/libgag/include/TextStream.h index 644bac2fd..7a7709fde 100644 --- a/libgag/include/TextStream.h +++ b/libgag/include/TextStream.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __TEXTSTREAM_H -#define __TEXTSTREAM_H +#pragma once #include #include @@ -137,5 +120,3 @@ namespace GAGCore virtual bool isValid(void) { return true; } }; } - -#endif diff --git a/libgag/include/Toolkit.h b/libgag/include/Toolkit.h index 93d50799a..4b82bf06e 100644 --- a/libgag/include/Toolkit.h +++ b/libgag/include/Toolkit.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __TOOLKIT_H -#define __TOOLKIT_H +#pragma once #include #include @@ -80,6 +63,4 @@ namespace GAGCore static StringTable *strings; }; } - -#endif diff --git a/libgag/include/TrueTypeFont.h b/libgag/include/TrueTypeFont.h index b63f55c95..e452643e3 100644 --- a/libgag/include/TrueTypeFont.h +++ b/libgag/include/TrueTypeFont.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __TRUETYPE_FONT_H -#define __TRUETYPE_FONT_H +#pragma once #include #include "GraphicContext.h" @@ -94,5 +77,3 @@ namespace GAGCore unsigned cacheMiss; }; } - -#endif diff --git a/libgag/include/Types.h b/libgag/include/Types.h index c88f910ed..d989c1e6a 100644 --- a/libgag/include/Types.h +++ b/libgag/include/Types.h @@ -1,29 +1,10 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __TYPES_H -#define __TYPES_H +#pragma once #include namespace GAGCore { } - -#endif diff --git a/libgag/include/win32_dirent.h b/libgag/include/win32_dirent.h index 352a65e3d..3211311f7 100644 --- a/libgag/include/win32_dirent.h +++ b/libgag/include/win32_dirent.h @@ -14,8 +14,7 @@ */ -#ifndef DIRENT_INCLUDED -#define DIRENT_INCLUDED +#pragma once #ifdef WIN32 @@ -44,5 +43,3 @@ void rewinddir(DIR *); #endif #endif - -#endif diff --git a/libgag/src/BinaryStream.cpp b/libgag/src/BinaryStream.cpp index 0d1ababd2..c3f231fde 100644 --- a/libgag/src/BinaryStream.cpp +++ b/libgag/src/BinaryStream.cpp @@ -1,24 +1,9 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include +#include #include #include // For htons/htonl @@ -98,6 +83,11 @@ namespace GAGCore assert(false); } + // Upper bound on any string written by writeText. Beyond this the bits on + // the wire are taken to be garbage rather than a real string. Mirrors the + // 1 MiB cap used by NetSendOrder::decodeData (see MAX_NET_SEND_ORDER_SIZE). + constexpr size_t MAX_BINARY_STRING_LENGTH = 1024 * 1024; + std::string BinaryInputStream::readText(const std::string name) { size_t len = readUint32(""); @@ -105,14 +95,14 @@ namespace GAGCore read(&buffer[0], len, ""); buffer[len] = 0; - // We don't use strings longer than 1024*1024, so if len > 1024*1024 these bits don't represent a string. - if (len > 1024*1024) + // We don't use strings longer than MAX_BINARY_STRING_LENGTH, so beyond that the bits don't represent a string. + if (len > MAX_BINARY_STRING_LENGTH) { // TODO: Make a BadFileFormatException (or similar) class and if necessary update the catch'es at // - ChooseMapScreen.cpp : 167 // - Engine.cpp : 218, 688, 754, 932 // - MapEdit.cpp : 1135 - throw std::ios_base::failure("String "+name+" length > 1024*1024"); + throw std::ios_base::failure("String "+name+" length > "+std::to_string(MAX_BINARY_STRING_LENGTH)); } return std::string(&buffer[0]); diff --git a/libgag/src/CursorManager.cpp b/libgag/src/CursorManager.cpp index cfe4f10b2..129254423 100644 --- a/libgag/src/CursorManager.cpp +++ b/libgag/src/CursorManager.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/DrawableSurface.cpp b/libgag/src/DrawableSurface.cpp new file mode 100644 index 000000000..b607c2c54 --- /dev/null +++ b/libgag/src/DrawableSurface.cpp @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "GraphicContextPrivate.h" +#include +#include +#include +#include +#include +#include +#include + +namespace GAGCore +{ + SDL_Surface *DrawableSurface::convertForUpload(SDL_Surface *source) + { + SDL_Surface *dest; + if (_gc->sdlsurface->format->BitsPerPixel == 32) + { + dest = SDL_ConvertSurfaceFormat(source, SDL_PIXELFORMAT_BGRA32, 0); + } + else + { + dest = SDL_ConvertSurface(source, &_glFormat, 0); + } + assert(dest); + return dest; + } + + // Drawable surface + DrawableSurface::DrawableSurface(const std::string &imageFileName) + { + sdlsurface = NULL; + if (!loadImage(imageFileName)) + setRes(0, 0); + allocateTexture(); + } + + DrawableSurface::DrawableSurface(int w, int h) + { + sdlsurface = NULL; + setRes(w, h); + allocateTexture(); + } + + DrawableSurface::DrawableSurface(const SDL_Surface *sourceSurface) + { + assert(sourceSurface); + // beurk, const cast here becasue SDL API sucks + sdlsurface = convertForUpload(const_cast(sourceSurface)); + assert(sdlsurface); + setClipRect(); + allocateTexture(); + dirty = true; + } + + DrawableSurface *DrawableSurface::clone(void) + { + return new DrawableSurface(sdlsurface); + } + + DrawableSurface::~DrawableSurface(void) + { + SDL_FreeSurface(sdlsurface); + freeGPUTexture(); + } + + template + static T getMinPowerOfTwo(T t) + { + T v = 1; + while (v < t) + v *= 2; + return v; + } + + void DrawableSurface::allocateTexture(void) + { + #ifdef HAVE_OPENGL + if (textureInfo) + return; + if (_gc->optionFlags & GraphicContext::USEGPU) + { + glGenTextures(1, reinterpret_cast(&texture)); + glState.alocatedTextureCount++; + initTextureSize(); + } + #endif + } + + void DrawableSurface::initTextureSize(void) + { + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + { + // only power of two textures are supported + if (!glState.isTextureSRectangle) + { + // TODO : if anyone has a better way to do it, please tell :-) + glState.setTexture(texture); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); + + int w = getMinPowerOfTwo(sdlsurface->w); + int h = getMinPowerOfTwo(sdlsurface->h); + std::valarray zeroBuffer((char)0, w * h * 4); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, &zeroBuffer[0]); + + texMultX = 1.0f / static_cast(w); + texMultY = 1.0f / static_cast(h); + } + else + { + texMultX = 1.0f; + texMultY = 1.0f; + } + } + #endif + } + + void DrawableSurface::uploadToTexture(void) + { + #ifdef HAVE_OPENGL + if (textureInfo) + { + return; + } + if (_gc->optionFlags & GraphicContext::USEGPU) + { + glState.setTexture(texture); + + void *pixelsPtr; + GLenum pixelFormat; + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + std::valarray tempPixels(sdlsurface->w * sdlsurface->h); + Uint32 *sourcePtr = static_cast(sdlsurface->pixels); + for (size_t i=0; i> 24); + sourcePtr++; + } + pixelsPtr = &tempPixels[0]; + pixelFormat = GL_RGBA; + #else + pixelsPtr = sdlsurface->pixels; + pixelFormat = GL_BGRA; + #endif + if (glState.isTextureSRectangle) + { + glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGBA, sdlsurface->w, sdlsurface->h, 0, pixelFormat, GL_UNSIGNED_BYTE, pixelsPtr); + } + else + { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sdlsurface->w, sdlsurface->h, pixelFormat, GL_UNSIGNED_BYTE, pixelsPtr); + } + } + #endif + dirty = false; + } + + void DrawableSurface::freeGPUTexture(void) + { + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + { + glDeleteTextures(1, reinterpret_cast(&texture)); + glState.alocatedTextureCount--; + + // The next line causes a desynchronization between _doScissors and glIsEnabled(GL_SCISSOR_TEST), + // which causes the setClipRect() functions to not reset the clipping the way it should, so many + // things don't get drawn properly and the game appears to "blink". Outcommenting it didn't cause + // any other problems. If you think glState should be reset, feel free to do so, but also call + // functions like glDisable() as required. + + //glState.resetCache(); + } + #endif + } + + void DrawableSurface::setRes(int w, int h) + { + if (sdlsurface) + SDL_FreeSurface(sdlsurface); + + sdlsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, _glFormat.Rmask, _glFormat.Gmask, _glFormat.Bmask, _glFormat.Amask); + assert(sdlsurface); + setClipRect(); + initTextureSize(); + dirty = true; + } + + void DrawableSurface::getClipRect(int *x, int *y, int *w, int *h) + { + assert(x); + assert(y); + assert(w); + assert(h); + + *x = clipRect.x; + *y = clipRect.y; + *w = clipRect.w; + *h = clipRect.h; + } + + void DrawableSurface::setClipRect(int x, int y, int w, int h) + { + assert(sdlsurface); + + clipRect.x = static_cast(x); + clipRect.y = static_cast(y); + clipRect.w = static_cast(w); + clipRect.h = static_cast(h); + + SDL_SetClipRect(sdlsurface, &clipRect); + } + + void DrawableSurface::setClipRect(void) + { + assert(sdlsurface); + + clipRect.x = 0; + clipRect.y = 0; + clipRect.w = static_cast(sdlsurface->w); + clipRect.h = static_cast(sdlsurface->h); + + SDL_SetClipRect(sdlsurface, &clipRect); + } + + bool DrawableSurface::loadImage(const std::string name) + { + if (name.size()) + { + SDL_RWops *imageStream; + if ((imageStream = Toolkit::getFileManager()->open(name, "rb")) != NULL) + { + SDL_Surface *loadedSurface; + loadedSurface = IMG_Load_RW(imageStream, 0); + SDL_RWclose(imageStream); + if (loadedSurface) + { + if (sdlsurface) + SDL_FreeSurface(sdlsurface); + sdlsurface = convertForUpload(loadedSurface); + SDL_FreeSurface(loadedSurface); + setClipRect(); + dirty = true; + return true; + } + } + } + return false; + } + + void DrawableSurface::shiftHSV(float hue, float sat, float lum) + { + Uint32 *mem = (Uint32 *)sdlsurface->pixels; + for (size_t i = 0; i < static_cast(sdlsurface->w * sdlsurface->h); i++) + { + // get values + float h, s, v; + Color c; + c.unpack(*mem); + c.getHSV(&h, &s, &v); + + // shift + h += hue; + s += sat; + v += lum; + + // wrap and saturate + if (h >= 360.0f) + h -= 360.0f; + if (h < 0.0f) + h += 360.0f; + s = std::max(s, 0.0f); + s = std::min(s, 1.0f); + v = std::max(v, 0.0f); + v = std::min(v, 1.0f); + + // set values + c.setHSV(h, s, v); + *mem = c.pack(); + mem++; + } + dirty = true; + } +} diff --git a/libgag/src/DrawableSurfaceCompound.cpp b/libgag/src/DrawableSurfaceCompound.cpp new file mode 100644 index 000000000..f10219d6b --- /dev/null +++ b/libgag/src/DrawableSurfaceCompound.cpp @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "GraphicContextPrivate.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace GAGCore +{ + void DrawableSurface::drawCircle(int x, int y, int radius, const Color& _color) + { + // we want to modify the color + Color color = _color; + + // clip + if ((x+radius < clipRect.x) || (x-radius >= clipRect.x+clipRect.w) || (y+radius < clipRect.y) || (y-radius >= clipRect.y+clipRect.h)) + return; + + // draw + int dx, dy, d; + int rdx, rdy; + int i; + color.a >>= 2; + for (i=0; i<3; i++) + { + dx = 0; + dy = (radius<<1) + i; + d = 0; + + do + { + rdx = (dx>>1); + rdy = (dy>>1); + drawPixel(x+rdx, y+rdy, color); + drawPixel(x+rdx, y-rdy, color); + drawPixel(x-rdx, y+rdy, color); + drawPixel(x-rdx, y-rdy, color); + drawPixel(x+rdy, y+rdx, color); + drawPixel(x+rdy, y-rdx, color); + drawPixel(x-rdy, y+rdx, color); + drawPixel(x-rdy, y-rdx, color); + dx++; + if (d >= 0) + { + dy--; + d += ((dx-dy)<<1)+2; + } + else + { + d += (dx<<1) +1; + } + } + while (dx <= dy); + } + } + + void DrawableSurface::drawCircle(float x, float y, float radius, const Color& color) + { + drawCircle(static_cast(x), static_cast(y), static_cast(radius), color); + } + + // Uint8 (r, g, b, a) compat overloads + void DrawableSurface::drawCircle(int x, int y, int radius, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawCircle(x, y, radius, Color(r, g, b, a)); + } + void DrawableSurface::drawVertLine(int x, int y, int l, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + _drawVertLine(x, y, l, Color(r, g, b, a)); + } + void DrawableSurface::drawHorzLine(int x, int y, int l, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + _drawHorzLine(x, y, l, Color(r, g, b, a)); + } + void DrawableSurface::drawLine(int x1, int y1, int x2, int y2, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawLine(x1, y1, x2, y2, Color(r, g, b, a)); + } + + void DrawableSurface::drawSurface(int x, int y, DrawableSurface *surface, Uint8 alpha) + { + drawSurface(x, y, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); + } + + void DrawableSurface::drawSurface(float x, float y, DrawableSurface *surface, Uint8 alpha) + { + drawSurface(x, y, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); + } + + void DrawableSurface::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, Uint8 alpha) + { + drawSurface(x, y, w, h, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); + } + + void DrawableSurface::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, Uint8 alpha) + { + drawSurface(x, y, w, h, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); + } + + void DrawableSurface::drawSurface(int x, int y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + { + if (alpha == Color::ALPHA_OPAQUE) + { + #ifdef HAVE_OPENGL + if ((surface == _gc) && (_gc->getOptionFlags() & GraphicContext::USEGPU)) + { + if ((x == 0) && (y == 0) && (sdlsurface->w == sw) && (sdlsurface->h == sh)) + { + std::valarray tempPixels(sw*sh); + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + glReadPixels(sx, sy, sdlsurface->w, sdlsurface->h, GL_RGBA, GL_UNSIGNED_BYTE, &tempPixels[0]); + #else + glReadPixels(sx, sy, sdlsurface->w, sdlsurface->h, GL_BGRA, GL_UNSIGNED_BYTE, &tempPixels[0]); + #endif + for (int y = 0; ypixels)[(sh-y-1)*sw]); + for (int x = 0; x> 8) | (*srcPtr << 24); + srcPtr++; + } + #else + *destPtr++ = *srcPtr++; + #endif + } + + } + else + { + std::cerr << "Partial blitting to from framebuffer in GL is forbidden" << std::endl; + assert(false); + } + } + else + { + #endif // HAVE_OPENGL + // well, we *hope* SDL is faster than a handmade code + SDL_Rect sr, dr; + sr.x = static_cast(sx); + sr.y = static_cast(sy); + sr.w = static_cast(sw); + sr.h = static_cast(sh); + dr.x = static_cast(x); + dr.y = static_cast(y); + dr.w = static_cast(sw); + dr.h = static_cast(sh); + SDL_BlitSurface(surface->sdlsurface, &sr, sdlsurface, &dr); + #ifdef HAVE_OPENGL + } + #endif // HAVE_OPENGL + } + else + { + if ((surface == _gc) && (_gc->getOptionFlags() & GraphicContext::USEGPU)) + { + std::cerr << "Blitting with alphablending from framebuffer in GL is forbidden" << std::endl; + assert(false); + } + + // check we assume the source rect is within the source surface + assert((sx >= 0) && (sx < surface->getW())); + assert((sy >= 0) && (sy < surface->getH())); + assert((sw > 0) && (sx + sw <= surface->getW())); + assert((sh > 0) && (sy + sh <= surface->getH())); + + // clip + if (x < clipRect.x) + { + int diff = clipRect.x - x; + sw -= diff; + sx += diff; + x = clipRect.x; + } + if (y < 0) + { + int diff = clipRect.y - y; + sh -= diff; + sy += diff; + y = clipRect.y; + } + if (x + sw >= clipRect.x + clipRect.w) + { + sw = clipRect.x + clipRect.w - x; + } + if (y + sh >= clipRect.y + clipRect.h) + { + sh = clipRect.y + clipRect.h - y; + } + if ((sw <= 0) || (sh <= 0)) + return; + + // draw + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + Uint32 alphaShift = 0; + #else + Uint32 alphaShift = 24; + #endif + for (int dy = 0; dy < sh; dy++) + { + Uint32 *memSrc = ((Uint32 *)surface->sdlsurface->pixels) + (sy + dy)*(surface->sdlsurface->pitch>>2) + sx; + Uint32 *memDest = ((Uint32 *)sdlsurface->pixels) + (y + dy)*(sdlsurface->pitch>>2) + x; + int dw = sw; + do + { + Uint32 srcValue = *memSrc++; + Uint32 srcAlpha = (((srcValue >> alphaShift) & 0xFF) * alpha) >> 8; + Uint32 destAlpha = 255 - srcAlpha; + Uint32 srcPreMult0 = (srcValue & 0x00FF00FF) * srcAlpha; + Uint32 srcPreMult1 = ((srcValue >> 8) & 0x00FF00FF) * srcAlpha; + + Uint32 destValue = *memDest; + Uint32 destPreMult0 = (destValue & 0x00FF00FF) * destAlpha; + Uint32 destPreMult1 = ((destValue >> 8) & 0x00FF00FF) * destAlpha; + + destPreMult0 += srcPreMult0; + destPreMult1 += srcPreMult1; + + *memDest++ = ((destPreMult0 >> 8) & 0x00FF00FF) | (destPreMult1 & 0xFF00FF00); + } + while (--dw); + } + } + dirty = true; + } + + void DrawableSurface::drawSurface(float x, float y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + { + drawSurface(static_cast(x), static_cast(y), surface, sx, sy, sw, sh, alpha); + } + + void DrawableSurface::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + { + // TODO : Implement + } + + void DrawableSurface::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + { + drawSurface(static_cast(x), static_cast(y), static_cast(w), static_cast(h), surface, sx, sy, sw, sh, alpha); + } + + void DrawableSurface::drawSprite(int x, int y, Sprite *sprite, unsigned index, Uint8 alpha) + { + // check bounds + assert(sprite); + if (!sprite->checkBound(index)) + return; + + // draw background + if (sprite->images[index]) + drawSurface(x, y, sprite->images[index], alpha); + + // draw rotation + if (sprite->rotated[index]) + drawSurface(x, y, sprite->getRotatedSurface(index), alpha); + } + + void DrawableSurface::drawSprite(float x, float y, Sprite *sprite, unsigned index, Uint8 alpha) + { + // check bounds + assert(sprite); + if (!sprite->checkBound(index)) + return; + + // draw background + if (sprite->images[index]) + drawSurface(x, y, sprite->images[index], alpha); + + // draw rotation + if (sprite->rotated[index]) + drawSurface(x, y, sprite->getRotatedSurface(index), alpha); + } + + void DrawableSurface::drawSprite(int x, int y, int w, int h, Sprite *sprite, unsigned index, Uint8 alpha) + { + // check bounds + assert(sprite); + if (!sprite->checkBound(index)) + return; + + // draw background + if (sprite->images[index]) + drawSurface(x, y, w, h, sprite->images[index], alpha); + + // draw rotation + if (sprite->rotated[index]) + drawSurface(x, y, w, h, sprite->getRotatedSurface(index), alpha); + } + + void DrawableSurface::drawSprite(float x, float y, float w, float h, Sprite *sprite, unsigned index, Uint8 alpha) + { + // check bounds + assert(sprite); + if (!sprite->checkBound(index)) + return; + + // draw background + if (sprite->images[index]) + drawSurface(x, y, w, h, sprite->images[index], alpha); + + // draw rotation + if (sprite->rotated[index]) + drawSurface(x, y, w, h, sprite->getRotatedSurface(index), alpha); + } + + void DrawableSurface::drawString(int x, int y, Font *font, const std::string &msg, int w, Uint8 alpha) + { + std::string output(msg); + std::string::size_type pos = output.find('\n', 0); + if(pos != std::string::npos) + output = output.substr(0, pos); + + pos = output.find('\r', 0); + if(pos != std::string::npos) + output = output.substr(0, pos); + + font->drawString(this, x, y, w, output, alpha); + + ///////////// The following code is for translation textshots //////////// + if(!translationPicturesDirectory.empty()) + { + for(std::map::iterator i=texts.begin(); i!=texts.end(); ++i) + { + if(output.find(i->first)!=std::string::npos) + { + int width=font->getStringWidth(i->first.c_str()); + int height=font->getStringHeight(i->first.c_str()); + int startx=font->getStringWidth(output.substr(0, output.find(i->first)).c_str()); + drawSquares.push_back(std::make_tuple(SRectangle(x+startx, y, width, height), i->second, this)); + wroteTexts.insert(i->second); + texts.erase(i); + break; + } + } + } + } + + void DrawableSurface::drawString(float x, float y, Font *font, const std::string &msg, float w, Uint8 alpha) + { + std::string output(msg); + std::string::size_type pos = output.find('\n', 0); + if(pos != std::string::npos) + output = output.substr(0, pos); + + pos = output.find('\r', 0); + if(pos != std::string::npos) + output = output.substr(0, pos); + + ///////////// The following code is for translation textshots //////////// + if(!translationPicturesDirectory.empty()) + { + for(std::map::iterator i=texts.begin(); i!=texts.end(); ++i) + { + if(output.find(i->first)!=std::string::npos) + { + int width=font->getStringWidth(i->first.c_str()); + int height=font->getStringHeight(i->first.c_str()); + int startx=font->getStringWidth(output.substr(0, output.find(i->first)).c_str()); + drawSquares.push_back(std::make_tuple(SRectangle(int(x+startx), int(y), width, height), i->second, this)); + wroteTexts.insert(i->second); + texts.erase(i); + break; + } + } + } + font->drawString(this, x, y, w, output, alpha); + + } + + void DrawableSurface::drawAlphaMap(const std::valarray &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color) + { + assert(mapW * mapH <= static_cast(map.size())); + + for (int dy=0; dy < mapH-1; dy++) + for (int dx=0; dx < mapW-1; dx++) + drawFilledRect(x + dx * cellW, y + dy * cellH, cellW, cellH, color.applyMultiplyAlpha((Uint8)(255.0f * map[mapW * dy + dx]))); + } + + void DrawableSurface::drawAlphaMap(const std::valarray &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color) + { + assert(mapW * mapH <= static_cast(map.size())); + + for (int dy=0; dy < mapH-1; dy++) + for (int dx=0; dx < mapW-1; dx++) + drawFilledRect(x + dx * cellW, y + dy * cellH, cellW, cellH, color.applyMultiplyAlpha(map[mapW * dy + dx])); + } + + // compat + void DrawableSurface::drawString(int x, int y, Font *font, int i) + { + std::stringstream str; + str << i; + this->drawString(x, y, font, str.str()); + } + + //This code is for the textshot code + std::map DrawableSurface::texts; + std::set DrawableSurface::wroteTexts; + std::vector > DrawableSurface::drawSquares; + std::string DrawableSurface::translationPicturesDirectory; + + void DrawableSurface::flushTextPictures() + { + using namespace GAGCore; + for(std::vector >::iterator i=drawSquares.begin(); i!=drawSquares.end();) + { + DrawableSurface toPrint(std::get<2>(*i)->getW(), std::get<2>(*i)->getH()); + toPrint.drawSurface(0, 0, std::get<2>(*i)); + int x=std::get<0>(*i).x; + int y=std::get<0>(*i).y; + int width=std::get<0>(*i).w; + int height=std::get<0>(*i).h; + + toPrint.drawRect(x-2, y-2, width+4, height+4, Color(255, 126, 21)); + toPrint.drawRect(x-3, y-3, width+6, height+6, Color(255, 126, 21)); + toPrint.drawCircle(x+width/2, y+height/2, std::max(width+4, height+4)/2+4, Color(255, 126, 21)); + toPrint.drawCircle(x+width/2, y+height/2, std::max(width+4, height+4)/2+5, Color(255, 126, 21)); + toPrint.drawCircle(x+width/2, y+height/2, std::max(width+4, height+4)/2+6, Color(255, 126, 21)); + + // Print it using virtual filesystem + for (size_t i2 = 0; i2 < Toolkit::getFileManager()->getDirCount(); i2++) + { + std::string fullFileName = translationPicturesDirectory + DIR_SEPARATOR_S + "text-" + std::get<1>(*i); + if (SDL_SaveBMP(toPrint.sdlsurface, (fullFileName+".bmp").c_str()) == 0) + { + break; + } + } + i=drawSquares.erase(i); + } + } + + void DrawableSurface::printFinishingText() + { + if(!texts.empty()) + std::cout<<"The following requested translation texts where never drawn to the screen, or too mangled to be detected:"<::iterator i=texts.begin(); i!=texts.end(); ++i) + { + std::cout<<"\t"<second< +#include + +namespace GAGCore +{ + void DrawableSurface::drawPixel(int x, int y, const Color& color) + { + // clip + if ((x=clipRect.x+clipRect.w) || (y=clipRect.y+clipRect.h)) + return; + + // draw + if (color.a == Color::ALPHA_OPAQUE) + { + *(((Uint32 *)sdlsurface->pixels) + y*(sdlsurface->pitch>>2) + x) = color.pack(); + } + else + { + Uint32 a = color.a; + Uint32 na = 255 - a; + Uint32 colorValue = color.applyAlpha(Color::ALPHA_OPAQUE).pack(); + Uint32 colorPreMult0 = (colorValue & 0x00FF00FF) * a; + Uint32 colorPreMult1 = ((colorValue >> 8) & 0x00FF00FF) * a; + + Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + y*(sdlsurface->pitch>>2) + x; + + Uint32 surfaceValue = *mem; + Uint32 surfacePreMult0 = (surfaceValue & 0x00FF00FF) * na; + Uint32 surfacePreMult1 = ((surfaceValue >> 8) & 0x00FF00FF) * na; + + surfacePreMult0 += colorPreMult0; + surfacePreMult1 += colorPreMult1; + + *mem = ((surfacePreMult0 >> 8) & 0x00FF00FF) | (surfacePreMult1 & 0xFF00FF00); + } + dirty = true; + } + + void DrawableSurface::drawPixel(float x, float y, const Color& color) + { + drawPixel(static_cast(x), static_cast(y), color); + } + + // compat + void DrawableSurface::drawPixel(int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawPixel(x, y, Color(r, g, b, a)); + } + + void DrawableSurface::drawRect(int x, int y, int w, int h, const Color& color) + { + _drawHorzLine(x, y, w, color); + _drawHorzLine(x, y+h-1, w, color); + _drawVertLine(x, y, h, color); + _drawVertLine(x+w-1, y, h, color); + } + + void DrawableSurface::drawRect(float x, float y, float w, float h, const Color& color) + { + drawRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); + } + + // compat + void DrawableSurface::drawRect(int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawRect(x, y, w, h, Color(r, g, b, a)); + } + + void DrawableSurface::drawFilledRect(int x, int y, int w, int h, const Color& color) + { + // clip + if (x < clipRect.x) + { + w -= clipRect.x - x; + x = clipRect.x; + } + if (y < 0) + { + h -= clipRect.y - y; + y = clipRect.y; + } + if (x + w >= clipRect.x + clipRect.w) + { + w = clipRect.x + clipRect.w - x; + } + if (y + h >= clipRect.y + clipRect.h) + { + h = clipRect.y + clipRect.h - y; + } + if ((w <= 0) || (h <= 0)) + return; + + // draw + if (color.a == Color::ALPHA_OPAQUE) + { + Uint32 colorValue = color.pack(); + for (int dy = y; dy < y + h; dy++) + { + Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + dy*(sdlsurface->pitch>>2) + x; + int dw = w; + do + { + *mem++ = colorValue; + } + while (--dw); + } + } + else + { + Uint32 a = color.a; + Uint32 na = 255 - a; + Uint32 colorValue = color.applyAlpha(Color::ALPHA_OPAQUE).pack(); + Uint32 colorPreMult0 = (colorValue & 0x00FF00FF) * a; + Uint32 colorPreMult1 = ((colorValue >> 8) & 0x00FF00FF) * a; + + for (int dy = y; dy < y + h; dy++) + { + Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + dy*(sdlsurface->pitch>>2) + x; + int dw = w; + do + { + Uint32 surfaceValue = *mem; + Uint32 surfacePreMult0 = (surfaceValue & 0x00FF00FF) * na; + Uint32 surfacePreMult1 = ((surfaceValue >> 8) & 0x00FF00FF) * na; + surfacePreMult0 += colorPreMult0; + surfacePreMult1 += colorPreMult1; + *mem++ = ((surfacePreMult0 >> 8) & 0x00FF00FF) | (surfacePreMult1 & 0xFF00FF00); + } + while (--dw); + } + } + dirty = true; + } + + void DrawableSurface::drawFilledRect(float x, float y, float w, float h, const Color& color) + { + drawFilledRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); + } + + void DrawableSurface::drawFilledRect(int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawFilledRect(x, y, w, h, Color(r, g, b, a)); + } + + void DrawableSurface::_drawVertLine(int x, int y, int l, const Color& color) + { + // clip + // be sure we have to draw something + if ((x < clipRect.x) || (x >= clipRect.x + clipRect.w)) + return; + + // set l positiv + if (l < 0) + { + y += l; + l = -l; + } + + // clip on y at top + if (y < clipRect.y) + { + l -= clipRect.y - y; + y = clipRect.y; + } + + // clip on y at bottom + if (y + l >= clipRect.y + clipRect.h) + { + l = clipRect.y + clipRect.h - y; + } + + // again, be sure we have to draw something + if (l <= 0) + return; + + // draw + int increment = sdlsurface->pitch >> 2; + Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + y*increment + x; + if (color.a == Color::ALPHA_OPAQUE) + { + Uint32 colorValue = color.pack(); + + do + { + *mem = colorValue; + mem += increment; + } + while (--l); + } + else + { + Uint32 a = color.a; + Uint32 na = 255 - a; + Uint32 colorValue = color.applyAlpha(Color::ALPHA_OPAQUE).pack(); + Uint32 colorPreMult0 = (colorValue & 0x00FF00FF) * a; + Uint32 colorPreMult1 = ((colorValue >> 8) & 0x00FF00FF) * a; + + do + { + Uint32 surfaceValue = *mem; + Uint32 surfacePreMult0 = (surfaceValue & 0x00FF00FF) * na; + Uint32 surfacePreMult1 = ((surfaceValue >> 8) & 0x00FF00FF) * na; + surfacePreMult0 += colorPreMult0; + surfacePreMult1 += colorPreMult1; + *mem = ((surfacePreMult0 >> 8) & 0x00FF00FF) | (surfacePreMult1 & 0xFF00FF00); + mem += increment; + } + while (--l); + } + dirty = true; + } + + void DrawableSurface::_drawHorzLine(int x, int y, int l, const Color& color) + { + // clip + // be sure we have to draw something + if ((y < clipRect.y) || (y >= clipRect.y + clipRect.h)) + return; + + // set l positiv + if (l < 0) + { + x += l; + l = -l; + } + + // clip on x at left + if (x < clipRect.x) + { + l -= clipRect.x - x; + x = clipRect.x; + } + + // clip on x at right + if ( x + l >= clipRect.x + clipRect.w) + { + l = clipRect.x + clipRect.w - x; + } + + // again, be sure we have to draw something + if (l <= 0) + return; + + // draw + Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + y*(sdlsurface->pitch >> 2) + x; + if (color.a == Color::ALPHA_OPAQUE) + { + Uint32 colorValue = color.pack(); + + do + { + *mem++ = colorValue; + } + while (--l); + } + else + { + Uint32 a = color.a; + Uint32 na = 255 - a; + Uint32 colorValue = color.applyAlpha(Color::ALPHA_OPAQUE).pack(); + Uint32 colorPreMult0 = (colorValue & 0x00FF00FF) * a; + Uint32 colorPreMult1 = ((colorValue >> 8) & 0x00FF00FF) * a; + + do + { + Uint32 surfaceValue = *mem; + Uint32 surfacePreMult0 = (surfaceValue & 0x00FF00FF) * na; + Uint32 surfacePreMult1 = ((surfaceValue >> 8) & 0x00FF00FF) * na; + surfacePreMult0 += colorPreMult0; + surfacePreMult1 += colorPreMult1; + *mem++ = ((surfacePreMult0 >> 8) & 0x00FF00FF) | (surfacePreMult1 & 0xFF00FF00); + } + while (--l); + } + dirty = true; + } + + void DrawableSurface::drawLine(int x1, int y1, int x2, int y2, const Color& _color) + { + // we want to modify the color + Color color = _color; + + // compute deltas + int dx = x2 - x1; + if (dx == 0) + { + _drawVertLine(x1, y1, y2-y1, color); + return; + } + int dy = y2 - y1; + if (dy == 0) + { + _drawHorzLine(x1, y1, x2-x1, color); + return; + } + + // clip + int test = 1; + // Y clipping + if (dy < 0) + { + test = -test; + std::swap(x1, x2); + std::swap(y1, y2); + dx = -dx; + dy = -dy; + } + + // the 2 points are Y-sorted. (y1 <= y2) + if (y2 < clipRect.y) + return; + if (y1 >= clipRect.y + clipRect.h) + return; + if (y1 < clipRect.y) + { + x1 = x2 - ( (y2 - clipRect.y)*(x2-x1) ) / (y2-y1); + y1 = clipRect.y; + } + if (y1 == y2) + { + _drawHorzLine(x1, y1, x2-x1, color); + return; + } + if (y2 >= clipRect.y + clipRect.h) + { + x2 = x1 - ( (y1 - (clipRect.y + clipRect.h))*(x1-x2) ) / (y1-y2); + y2 = (clipRect.y + clipRect.h - 1); + } + if (x1 == x2) + { + _drawVertLine(x1, y1, y2-y1, color); + return; + } + + // X clipping + if (dx < 0) + { + test = -test; + std::swap(x1, x2); + std::swap(y1, y2); + dx = -dx; + dy = -dy; + } + // the 2 points are X-sorted. (x1 <= x2) + if (x2 < clipRect.x) + return; + if (x1 >= clipRect.x + clipRect.w) + return; + if (x1 < clipRect.x) + { + y1 = y2 - ( (x2 - clipRect.x)*(y2-y1) ) / (x2-x1); + x1 = clipRect.x; + } + if (x1 == x2) + { + _drawVertLine(x1, y1, y2-y1, color); + return; + } + if (x2 >= clipRect.x + clipRect.w) + { + y2 = y1 - ( (x1 - (clipRect.x + clipRect.w))*(y1-y2) ) / (x1-x2); + x2 = (clipRect.x + clipRect.w - 1); + } + + // last return case + if (x1 >= (clipRect.x + clipRect.w) || y1 >= (clipRect.y + clipRect.h) || (x2 < clipRect.x) || (y2 < clipRect.y)) + return; + + // recompute deltas after clipping + dx = x2-x1; + dy = y2-y1; + + // setup variable to draw alpha in the right direction + #define Sgn(x) (x>0 ? (x == 0 ? 0 : 1) : (x==0 ? 0 : -1)) + Sint32 littleincx; + Sint32 littleincy; + Sint32 bigincx; + Sint32 bigincy; + Sint32 alphadecx; + Sint32 alphadecy; + if (abs(dx) > abs(dy)) + { + littleincx = 1; + littleincy = 0; + bigincx = 1; + bigincy = Sgn(dy); + alphadecx = 0; + alphadecy = Sgn(dy); + } + else + { + // we swap x and y meaning + test = -test; + std::swap(dx, dy); + littleincx = 0; + littleincy = 1; + bigincx = Sgn(dx); + bigincy = 1; + alphadecx = 1; + alphadecy = 0; + } + + if (dx < 0) + { + dx = -dx; + littleincx = 0; + littleincy = -littleincy; + bigincx = -bigincx; + bigincy = -bigincy; + alphadecy = -alphadecy; + } + + // compute initial position + int px, py; + px = x1; + py = y1; + + // variable initialisation for bresenham algo + if (dx == 0) + return; + if (dy == 0) + return; + const int FIXED = 8; + const int I = 255; // number of degree of alpha + const int Ibits = 8; + int m = (abs(dy) << (Ibits+FIXED)) / abs(dx); + int w = (I << FIXED) - m; + int e = 1 << (FIXED-1); + + // first point + color.a = I - (e >> FIXED); + drawPixel(px, py, color); + + // main loop + int x = dx+1; + if (x <= 0) + return; + while (--x) + { + if (e < w) + { + px+=littleincx; + py+=littleincy; + e+= m; + } + else + { + px+=bigincx; + py+=bigincy; + e-= w; + } + color.a = I - (e >> FIXED); + drawPixel(px, py, color); + color.a = e >> FIXED; + drawPixel(px + alphadecx, py + alphadecy, color); + } + } + + void DrawableSurface::drawLine(float x1, float y1, float x2, float y2, const Color& color) + { + drawRect(static_cast(x1), static_cast(y1), static_cast(x2), static_cast(y2), color); + } + + void DrawableSurface::drawVertLine(int x, int y, int l, const Color& color) + { + _drawVertLine(x, y, l, color); + } + + void DrawableSurface::drawHorzLine(int x, int y, int l, const Color& color) + { + _drawHorzLine(x, y, l, color); + } +} diff --git a/libgag/src/FileManager.cpp b/libgag/src/FileManager.cpp index cbcfba00f..3a19197e7 100644 --- a/libgag/src/FileManager.cpp +++ b/libgag/src/FileManager.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include @@ -62,6 +46,26 @@ namespace GAGCore { + // Detect paths that should bypass the dirList search and be used as-is. + // The dirList loop unconditionally prepends a search root + DIR_SEPARATOR, + // which turns "/tmp/foo" into "//tmp/foo" and never opens. Callers + // passing absolute paths (e.g. --save-game-as /tmp/foo.game) need direct + // access. POSIX absolutes start with '/'; Windows absolutes can also be + // drive-letter ("C:\..." / "C:/...") or UNC ("\\server\share"). + static bool isAbsolutePath(const std::string& path) + { + if (path.empty()) return false; + if (path[0] == '/') return true; +#ifdef WIN32 + if (path[0] == '\\') return true; + if (path.size() >= 3 && path[1] == ':' && + ((path[0] >= 'A' && path[0] <= 'Z') || + (path[0] >= 'a' && path[0] <= 'z'))) + return true; +#endif + return false; + } + FileManager::FileManager(const std::string gameName) { #ifndef WIN32 @@ -206,23 +210,36 @@ namespace GAGCore StreamBackend *FileManager::openOutputStreamBackend(const std::string filename) { + if (isAbsolutePath(filename)) + { + FILE *fp = fopen(filename.c_str(), "wb"); + if (fp) + return new FileStreamBackend(fp); + return new FileStreamBackend(NULL); + } for (size_t i = 0; i < dirList.size(); ++i) { std::string path(dirList[i]); path += DIR_SEPARATOR; path += filename; - + FILE *fp = fopen(path.c_str(), "wb"); if (fp) return new FileStreamBackend(fp); } - + return new FileStreamBackend(NULL); } StreamBackend *FileManager::openInputStreamBackend(const std::string filename) - { - + { + if (isAbsolutePath(filename)) + { + FILE *fp = fopen(filename.c_str(), "rb"); + if (fp) + return new FileStreamBackend(fp); + return new FileStreamBackend(NULL); + } for (size_t i = 0; i < dirList.size(); ++i) { std::string path(dirList[i]); @@ -239,6 +256,17 @@ namespace GAGCore StreamBackend *FileManager::openCompressedOutputStreamBackend(const std::string filename) { + if (isAbsolutePath(filename)) + { + //Test if it can be opened first + FILE *fp = fopen(filename.c_str(), "wb"); + if (fp) + { + fclose(fp); + return new ZLibStreamBackend(filename, false); + } + return new ZLibStreamBackend("", false); + } for (size_t i = 0; i < dirList.size(); ++i) { std::string path(dirList[i]); @@ -259,6 +287,16 @@ namespace GAGCore StreamBackend *FileManager::openCompressedInputStreamBackend(const std::string filename) { + if (isAbsolutePath(filename)) + { + FILE *fp = fopen(filename.c_str(), "rb"); + if (fp) + { + fclose(fp); + return new ZLibStreamBackend(filename, true); + } + return new ZLibStreamBackend("", false); + } for (size_t i = 0; i < dirList.size(); ++i) { std::string path(dirList[i]); @@ -278,6 +316,13 @@ namespace GAGCore SDL_RWops *FileManager::open(const std::string filename, const std::string mode) { + if (isAbsolutePath(filename)) + { + SDL_RWops *fp = openWithbackup(filename.c_str(), mode.c_str()); + if (fp) + return fp; + return NULL; + } for (size_t i = 0; i < dirList.size(); ++i) { std::string path(dirList[i]); @@ -295,6 +340,13 @@ namespace GAGCore FILE *FileManager::openFP(const std::string filename, const std::string mode) { + if (isAbsolutePath(filename)) + { + FILE *fp = openWithbackupFP(filename.c_str(), mode.c_str()); + if (fp) + return fp; + return NULL; + } for (size_t i = 0; i < dirList.size(); ++i) { std::string path(dirList[i]); @@ -311,6 +363,14 @@ namespace GAGCore std::ifstream *FileManager::openIFStream(const std::string &fileName) { + if (isAbsolutePath(fileName)) + { + std::ifstream *fp = new std::ifstream(fileName.c_str()); + if (fp->good()) + return fp; + delete fp; + return NULL; + } for (size_t i = 0; i < dirList.size(); ++i) { std::string path(dirList[i]); @@ -356,6 +416,13 @@ namespace GAGCore time_t FileManager::mtime(const std::string filename) { + if (isAbsolutePath(filename)) + { + struct stat stats; + if (stat(filename.c_str(), &stats) == 0) + return stats.st_mtime; + return 0; + } for (size_t i = 0; i < dirList.size(); ++i) { std::string path(dirList[i]); @@ -372,6 +439,11 @@ namespace GAGCore void FileManager::remove(const std::string filename) { + if (isAbsolutePath(filename)) + { + std::remove(filename.c_str()); + return; + } for (size_t i = 0; i < dirList.size(); ++i) { std::string path(dirList[i]); @@ -426,13 +498,23 @@ namespace GAGCore // Compress srcStream->read(&buffer[0], fileLength); gzFile gzStream = gzdopen(fileno(destStream), "wb"); - gzwrite(gzStream, &buffer[0], fileLength); - + if (gzStream == NULL) + { + std::cerr << "FileManager::gzip: gzdopen failed for " << dest << std::endl; + fclose(destStream); + delete srcStream; + return false; + } + int written = gzwrite(gzStream, &buffer[0], fileLength); + if (written == 0) + std::cerr << "FileManager::gzip: gzwrite failed for " << dest << std::endl; + // Close - gzclose(gzStream); + if (gzclose(gzStream) != Z_OK) + std::cerr << "FileManager::gzip: gzclose failed for " << dest << std::endl; delete srcStream; - - return true; + + return (written > 0); } bool FileManager::gunzip(const std::string &source, const std::string &dest) @@ -448,29 +530,41 @@ namespace GAGCore return false; } - // Preapare source + // Prepare source gzFile gzStream = gzdopen(fileno(srcStream), "rb"); - #define BLOCK_SIZE 1024*1024 + if (gzStream == NULL) + { + std::cerr << "FileManager::gunzip: gzdopen failed for " << source << std::endl; + fclose(srcStream); + delete destStream; + return false; + } + #define BLOCK_SIZE (1024*1024) std::string buffer; size_t len = 0; - size_t bufferLength = 0; - + // Uncompress - while (gzeof(gzStream) == 0) + int bytesRead; + do { - buffer.resize(bufferLength + BLOCK_SIZE); - len += gzread(gzStream, const_cast(static_cast(buffer.data() + bufferLength)), BLOCK_SIZE); - bufferLength += BLOCK_SIZE; - } - + buffer.resize(len + BLOCK_SIZE); + bytesRead = gzread(gzStream, &buffer[len], BLOCK_SIZE); + if (bytesRead > 0) + len += bytesRead; + } while (bytesRead > 0); + + if (bytesRead < 0) + std::cerr << "FileManager::gunzip: gzread error for " << source << std::endl; + // Write destStream->write(buffer.c_str(), len); - + // Close - gzclose(gzStream); + if (gzclose(gzStream) != Z_OK) + std::cerr << "FileManager::gunzip: gzclose error for " << source << std::endl; delete destStream; - - return true; + + return (bytesRead >= 0); } bool FileManager::addListingForDir(const std::string realDir, const std::string extension, const bool dirs) diff --git a/libgag/src/FormatableString.cpp b/libgag/src/FormatableString.cpp index bc5c358e8..b47baf22c 100644 --- a/libgag/src/FormatableString.cpp +++ b/libgag/src/FormatableString.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUIAnimation.cpp b/libgag/src/GUIAnimation.cpp index 844140559..19b340f7c 100644 --- a/libgag/src/GUIAnimation.cpp +++ b/libgag/src/GUIAnimation.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUIBase.cpp b/libgag/src/GUIBase.cpp index 36277e6bc..0241cc5da 100644 --- a/libgag/src/GUIBase.cpp +++ b/libgag/src/GUIBase.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUIButton.cpp b/libgag/src/GUIButton.cpp index 005808896..e7a3f6206 100644 --- a/libgag/src/GUIButton.cpp +++ b/libgag/src/GUIButton.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUICheckList.cpp b/libgag/src/GUICheckList.cpp index 15d7c0572..5b698adbf 100644 --- a/libgag/src/GUICheckList.cpp +++ b/libgag/src/GUICheckList.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "GUICheckList.h" #include "GUIStyle.h" diff --git a/libgag/src/GUIFileList.cpp b/libgag/src/GUIFileList.cpp index 8b41c0b53..0671dbeab 100644 --- a/libgag/src/GUIFileList.cpp +++ b/libgag/src/GUIFileList.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUIImage.cpp b/libgag/src/GUIImage.cpp index 8b3ea1c5c..294d6c27b 100644 --- a/libgag/src/GUIImage.cpp +++ b/libgag/src/GUIImage.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUIKeySelector.cpp b/libgag/src/GUIKeySelector.cpp index 30e361932..ccf200868 100644 --- a/libgag/src/GUIKeySelector.cpp +++ b/libgag/src/GUIKeySelector.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "GUIKeySelector.h" #include "Toolkit.h" diff --git a/libgag/src/GUIList.cpp b/libgag/src/GUIList.cpp index 57eee8d4c..dfea143a3 100644 --- a/libgag/src/GUIList.cpp +++ b/libgag/src/GUIList.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include @@ -441,6 +425,11 @@ namespace GAGGUI { return nth; } + + std::optional List::selection(void) const + { + return nth >= 0 ? std::optional(static_cast(nth)) : std::nullopt; + } void List::setSelectionIndex(int index) { diff --git a/libgag/src/GUIMessageBox.cpp b/libgag/src/GUIMessageBox.cpp index ceb602551..d101886bb 100644 --- a/libgag/src/GUIMessageBox.cpp +++ b/libgag/src/GUIMessageBox.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUINumber.cpp b/libgag/src/GUINumber.cpp index 231eeb5f7..772bc0461 100644 --- a/libgag/src/GUINumber.cpp +++ b/libgag/src/GUINumber.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUIProgressBar.cpp b/libgag/src/GUIProgressBar.cpp index 416ca9f4f..d182ec846 100644 --- a/libgag/src/GUIProgressBar.cpp +++ b/libgag/src/GUIProgressBar.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUIRatio.cpp b/libgag/src/GUIRatio.cpp index 121dc39ae..2c2796fd2 100644 --- a/libgag/src/GUIRatio.cpp +++ b/libgag/src/GUIRatio.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUISelector.cpp b/libgag/src/GUISelector.cpp index 53541981d..734c621c0 100644 --- a/libgag/src/GUISelector.cpp +++ b/libgag/src/GUISelector.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUIStyle.cpp b/libgag/src/GUIStyle.cpp index 23d13a2b3..83a324d08 100644 --- a/libgag/src/GUIStyle.cpp +++ b/libgag/src/GUIStyle.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUITabScreen.cpp b/libgag/src/GUITabScreen.cpp index 50f1b0343..fc98e7e37 100644 --- a/libgag/src/GUITabScreen.cpp +++ b/libgag/src/GUITabScreen.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "GUITabScreen.h" @@ -214,12 +197,8 @@ namespace GAGGUI void TabScreen::onTimer(Uint32 tick) { - int last = -1; - int first = -1; for(std::map::iterator i = windows.begin(); i!=windows.end();) { - if(first == -1) - first = i->first; if(!i->second->isStillExecuting()) { std::map::iterator ni = i; @@ -234,15 +213,14 @@ namespace GAGGUI } else if(n == activated) { - if(first!=-1) - { - activateGroup(first); - } + // Fall back to the leftmost surviving tab. Looked up + // after removeGroup so it can never be the just-erased + // key. + activateGroup(windows.begin()->first); } } else { - last=i->first; i->second->onTimer(tick); i++; } diff --git a/libgag/src/GUITabScreenWindow.cpp b/libgag/src/GUITabScreenWindow.cpp index 45490e574..815fd76fc 100644 --- a/libgag/src/GUITabScreenWindow.cpp +++ b/libgag/src/GUITabScreenWindow.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "GUITabScreenWindow.h" #include "GUITabScreen.h" diff --git a/libgag/src/GUIText.cpp b/libgag/src/GUIText.cpp index 192d1aa50..b29278d70 100644 --- a/libgag/src/GUIText.cpp +++ b/libgag/src/GUIText.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUITextArea.cpp b/libgag/src/GUITextArea.cpp index 5ee8c898c..5a2d1e020 100644 --- a/libgag/src/GUITextArea.cpp +++ b/libgag/src/GUITextArea.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GUITextInput.cpp b/libgag/src/GUITextInput.cpp index afa4f5770..6de54f316 100644 --- a/libgag/src/GUITextInput.cpp +++ b/libgag/src/GUITextInput.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/GraphicContext.cpp b/libgag/src/GraphicContext.cpp index 86efd406d..581c78d47 100644 --- a/libgag/src/GraphicContext.cpp +++ b/libgag/src/GraphicContext.cpp @@ -1,2009 +1,162 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include +#include "GraphicContextPrivate.h" #include #include #include #include -#include -#include +#include #include -#ifdef HAVE_OPENGL -#include -#ifdef _MSC_VER -#include -#else -#include -#endif // _MSC_VER -#endif // HAVE_OPENGL -#include "SDL_ttf.h" -#include -#include -#include -#include -#include #include +#include +#include "SDL_ttf.h" +#include -#ifdef HAVE_CONFIG_H -#include -#endif - -#ifdef HAVE_OPENGL -#define GL_GLEXT_PROTOTYPES - #if defined(__APPLE__) || defined(OPENGL_HEADER_DIRECTORY_OPENGL) - #include - #include - #include - #define GL_TEXTURE_RECTANGLE_NV GL_TEXTURE_RECTANGLE_EXT - #else - #include - #include - #endif -#endif - -#ifdef WIN32 - #include -#endif - -//extern "C" { SDL_PixelFormat *SDL_AllocFormat(int bpp, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); } - -namespace GAGCore -{ - // static local pointer to the actual graphic context - static GraphicContext *_gc = NULL; - static SDL_PixelFormat _glFormat; - //EXPERIMENTAL is a bit buggy and "not EXPERIMENTAL" is bugfree but slow - //when rendering clouds or other density layers (GraphicContext::drawAlphaMap). - static const bool EXPERIMENTAL=false; - - // Color - Uint32 Color::pack() const - { - //return (SDL_MapRGB(&_glFormat, r, g, b) & 0x00ffffff) | (a << 24); - return SDL_MapRGBA(&_glFormat, r, g, b, a); - } - - void Color::unpack(const Uint32 packedValue) - { - //SDL_GetRGB(packedValue, &_glFormat, &r, &g, &b); - //a = packedValue >> 24; - SDL_GetRGBA(packedValue, &_glFormat, &r, &g, &b, &a); - } - - void Color::getHSV(float *hue, float *sat, float *lum) - { - RGBtoHSV(static_cast(r)/255.0f, static_cast(g)/255.0f, static_cast(b)/255.0f, hue, sat, lum); - } - - void Color::setHSV(float hue, float sat, float lum) - { - float fr, fg, fb; - HSVtoRGB(&fr, &fg, &fb, hue, sat, lum); - r = static_cast(255.0f*fr); - g = static_cast(255.0f*fg); - b = static_cast(255.0f*fb); - } - - Color Color::applyMultiplyAlpha(Uint8 _a) const - { - Color c; - c.r = r; - c.g = g; - c.b = b; - c.a = _a; - return c; - } - - // Predefined colors - Color Color::black = Color(0, 0, 0); - Color Color::white = Color(255,255,255); - - #ifdef HAVE_OPENGL - // Cache for GL state, call gl only if necessary. GL optimisations - static struct GLState - { - static const bool verbose = false; - bool _doBlend; - bool _doTexture; - bool _doScissor; - GLint _texture; - GLenum _sfactor, _dfactor; - bool isTextureSRectangle; - bool useATIWorkaround; - unsigned alocatedTextureCount; - - GLState(void) - { - resetCache(); - isTextureSRectangle = false; - useATIWorkaround = false; - alocatedTextureCount = 0; - } - - void resetCache(void) - { - _doBlend = false; - _doTexture = false; - _doScissor = false; - _texture = -1; - _sfactor = 0xffffffff; - _dfactor = 0xffffffff; - } - - void checkExtensions(void) - { - const char *glExtensions = (const char *)glGetString(GL_EXTENSIONS); - isTextureSRectangle = (strstr(glExtensions, "GL_NV_texture_rectangle") != NULL); - isTextureSRectangle = isTextureSRectangle || (strstr(glExtensions, "GL_EXT_texture_rectangle") != NULL); - isTextureSRectangle = isTextureSRectangle || (strstr(glExtensions, "GL_ARB_texture_rectangle") != NULL); - - const char *glVendor= (const char *)glGetString(GL_VENDOR); - if(strstr(glVendor,"ATI")) - useATIWorkaround = true; // ugly temporary bug fix for bug 13823. We think it is an ATI driver bug - - if (verbose) - { - if (isTextureSRectangle) - { - std::cout << "Toolkit : GL_NV_texture_rectangle or GL_EXT_texture_rectangle extension present, optimal texture size will be used" << std::endl; - } else { - std::cout << "Toolkit : GL_NV_texture_rectangle or GL_EXT_texture_rectangle extension not present, power of two texture will be used" << std::endl; - } - } - } - - bool doBlend(bool on) - { - if (_doBlend == on) - return on; - if (on) - glEnable(GL_BLEND); - else - glDisable(GL_BLEND); - _doBlend = on; - return !on; - } - - bool doTexture(bool on) - { - if (_doTexture == on) - return on; - GLenum cap; - if (isTextureSRectangle) - cap = GL_TEXTURE_RECTANGLE_NV; - else - cap = GL_TEXTURE_2D; - - if (on) - glEnable(cap); - else - glDisable(cap); - _doTexture = on; - return !on; - } - - void setTexture(int tex) - { - if (_texture == tex) - return; - - if (isTextureSRectangle) - { - if(useATIWorkaround) - glBindTexture(GL_TEXTURE_RECTANGLE_NV, 0); - glBindTexture(GL_TEXTURE_RECTANGLE_NV, tex); - } - else - glBindTexture(GL_TEXTURE_2D, tex); - _texture = tex; - } - - bool doScissor(bool on) - { - // The glIsEnabled is function is quite expensive. That's why we have a _doScissor variable. - // I'm quite sure that this assert should never fail, so I've outcommented it, partially - // because we don't do #define NDEBUG in most of our releases (so far). - - //assert(_doScissor == glIsEnabled(GL_SCISSOR_TEST)); - - if (_doScissor == on) - return on; - - if (on) - glEnable(GL_SCISSOR_TEST); - else - glDisable(GL_SCISSOR_TEST); - _doScissor = on; - return !on; - } - - void blendFunc(GLenum sfactor, GLenum dfactor) - { - if ((sfactor == _sfactor) && (dfactor == _dfactor)) - return; - - glBlendFunc(sfactor, dfactor); - - _sfactor = sfactor; - _dfactor = dfactor; - } - } glState; - #endif - - SDL_Surface *DrawableSurface::convertForUpload(SDL_Surface *source) - { - SDL_Surface *dest; - if (_gc->sdlsurface->format->BitsPerPixel == 32) - { - dest = SDL_ConvertSurfaceFormat(source, SDL_PIXELFORMAT_BGRA32, 0); - } - else - { - dest = SDL_ConvertSurface(source, &_glFormat, 0); - } - assert(dest); - return dest; - } - - // Drawable surface - DrawableSurface::DrawableSurface(const std::string &imageFileName) - { - sdlsurface = NULL; - if (!loadImage(imageFileName)) - setRes(0, 0); - allocateTexture(); - } - - DrawableSurface::DrawableSurface(int w, int h) - { - sdlsurface = NULL; - setRes(w, h); - allocateTexture(); - } - - DrawableSurface::DrawableSurface(const SDL_Surface *sourceSurface) - { - assert(sourceSurface); - // beurk, const cast here becasue SDL API sucks - sdlsurface = convertForUpload(const_cast(sourceSurface)); - assert(sdlsurface); - setClipRect(); - allocateTexture(); - dirty = true; - } - - DrawableSurface *DrawableSurface::clone(void) - { - return new DrawableSurface(sdlsurface); - } - - DrawableSurface::~DrawableSurface(void) - { - SDL_FreeSurface(sdlsurface); - freeGPUTexture(); - } - - template - T getMinPowerOfTwo(T t) - { - T v = 1; - while (v < t) - v *= 2; - return v; - } - - void DrawableSurface::allocateTexture(void) - { - #ifdef HAVE_OPENGL - if (textureInfo) - return; - if (_gc->optionFlags & GraphicContext::USEGPU) - { - glGenTextures(1, reinterpret_cast(&texture)); - glState.alocatedTextureCount++; - initTextureSize(); - } - #endif - } - - void DrawableSurface::initTextureSize(void) - { - #ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) - { - // only power of two textures are supported - if (!glState.isTextureSRectangle) - { - // TODO : if anyone has a better way to do it, please tell :-) - glState.setTexture(texture); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); - - int w = getMinPowerOfTwo(sdlsurface->w); - int h = getMinPowerOfTwo(sdlsurface->h); - std::valarray zeroBuffer((char)0, w * h * 4); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, &zeroBuffer[0]); - - texMultX = 1.0f / static_cast(w); - texMultY = 1.0f / static_cast(h); - } - else - { - texMultX = 1.0f; - texMultY = 1.0f; - } - } - #endif - } - - void DrawableSurface::uploadToTexture(void) - { - #ifdef HAVE_OPENGL - if (textureInfo) - { - return; - } - if (_gc->optionFlags & GraphicContext::USEGPU) - { - glState.setTexture(texture); - - void *pixelsPtr; - GLenum pixelFormat; - #if SDL_BYTEORDER == SDL_BIG_ENDIAN - std::valarray tempPixels(sdlsurface->w * sdlsurface->h); - Uint32 *sourcePtr = static_cast(sdlsurface->pixels); - for (size_t i=0; i> 24); - sourcePtr++; - } - pixelsPtr = &tempPixels[0]; - pixelFormat = GL_RGBA; - #else - pixelsPtr = sdlsurface->pixels; - pixelFormat = GL_BGRA; - #endif - if (glState.isTextureSRectangle) - { - glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGBA, sdlsurface->w, sdlsurface->h, 0, pixelFormat, GL_UNSIGNED_BYTE, pixelsPtr); - } - else - { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sdlsurface->w, sdlsurface->h, pixelFormat, GL_UNSIGNED_BYTE, pixelsPtr); - } - } - #endif - dirty = false; - } - - void DrawableSurface::freeGPUTexture(void) - { - #ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) - { - glDeleteTextures(1, reinterpret_cast(&texture)); - glState.alocatedTextureCount--; - - // The next line causes a desynchronization between _doScissors and glIsEnabled(GL_SCISSOR_TEST), - // which causes the setClipRect() functions to not reset the clipping the way it should, so many - // things don't get drawn properly and the game appears to "blink". Outcommenting it didn't cause - // any other problems. If you think glState should be reset, feel free to do so, but also call - // functions like glDisable() as required. - - //glState.resetCache(); - } - #endif - } - - void DrawableSurface::setRes(int w, int h) - { - if (sdlsurface) - SDL_FreeSurface(sdlsurface); - - sdlsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, _glFormat.Rmask, _glFormat.Gmask, _glFormat.Bmask, _glFormat.Amask); - assert(sdlsurface); - setClipRect(); - initTextureSize(); - dirty = true; - } - - void DrawableSurface::getClipRect(int *x, int *y, int *w, int *h) - { - assert(x); - assert(y); - assert(w); - assert(h); - - *x = clipRect.x; - *y = clipRect.y; - *w = clipRect.w; - *h = clipRect.h; - } - - void DrawableSurface::setClipRect(int x, int y, int w, int h) - { - assert(sdlsurface); - - clipRect.x = static_cast(x); - clipRect.y = static_cast(y); - clipRect.w = static_cast(w); - clipRect.h = static_cast(h); - - SDL_SetClipRect(sdlsurface, &clipRect); - } - - void DrawableSurface::setClipRect(void) - { - assert(sdlsurface); - - clipRect.x = 0; - clipRect.y = 0; - clipRect.w = static_cast(sdlsurface->w); - clipRect.h = static_cast(sdlsurface->h); - - SDL_SetClipRect(sdlsurface, &clipRect); - } - - bool DrawableSurface::loadImage(const std::string name) - { - if (name.size()) - { - SDL_RWops *imageStream; - if ((imageStream = Toolkit::getFileManager()->open(name, "rb")) != NULL) - { - SDL_Surface *loadedSurface; - loadedSurface = IMG_Load_RW(imageStream, 0); - SDL_RWclose(imageStream); - if (loadedSurface) - { - if (sdlsurface) - SDL_FreeSurface(sdlsurface); - sdlsurface = convertForUpload(loadedSurface); - SDL_FreeSurface(loadedSurface); - setClipRect(); - dirty = true; - return true; - } - } - } - return false; - } - - void DrawableSurface::shiftHSV(float hue, float sat, float lum) - { - Uint32 *mem = (Uint32 *)sdlsurface->pixels; - for (size_t i = 0; i < static_cast(sdlsurface->w * sdlsurface->h); i++) - { - // get values - float h, s, v; - Color c; - c.unpack(*mem); - c.getHSV(&h, &s, &v); - - // shift - h += hue; - s += sat; - v += lum; - - // wrap and saturate - if (h >= 360.0f) - h -= 360.0f; - if (h < 0.0f) - h += 360.0f; - s = std::max(s, 0.0f); - s = std::min(s, 1.0f); - v = std::max(v, 0.0f); - v = std::min(v, 1.0f); - - // set values - c.setHSV(h, s, v); - *mem = c.pack(); - mem++; - } - dirty = true; - } - - void DrawableSurface::drawPixel(int x, int y, const Color& color) - { - // clip - if ((x=clipRect.x+clipRect.w) || (y=clipRect.y+clipRect.h)) - return; - - // draw - if (color.a == Color::ALPHA_OPAQUE) - { - *(((Uint32 *)sdlsurface->pixels) + y*(sdlsurface->pitch>>2) + x) = color.pack(); - } - else - { - Uint32 a = color.a; - Uint32 na = 255 - a; - Uint32 colorValue = color.applyAlpha(Color::ALPHA_OPAQUE).pack(); - Uint32 colorPreMult0 = (colorValue & 0x00FF00FF) * a; - Uint32 colorPreMult1 = ((colorValue >> 8) & 0x00FF00FF) * a; - - Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + y*(sdlsurface->pitch>>2) + x; - - Uint32 surfaceValue = *mem; - Uint32 surfacePreMult0 = (surfaceValue & 0x00FF00FF) * na; - Uint32 surfacePreMult1 = ((surfaceValue >> 8) & 0x00FF00FF) * na; - - surfacePreMult0 += colorPreMult0; - surfacePreMult1 += colorPreMult1; - - *mem = ((surfacePreMult0 >> 8) & 0x00FF00FF) | (surfacePreMult1 & 0xFF00FF00); - } - dirty = true; - } - - void DrawableSurface::drawPixel(float x, float y, const Color& color) - { - drawPixel(static_cast(x), static_cast(y), color); - } - - // compat - void DrawableSurface::drawPixel(int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - drawPixel(x, y, Color(r, g, b, a)); - } - - void DrawableSurface::drawRect(int x, int y, int w, int h, const Color& color) - { - _drawHorzLine(x, y, w, color); - _drawHorzLine(x, y+h-1, w, color); - _drawVertLine(x, y, h, color); - _drawVertLine(x+w-1, y, h, color); - } - - void DrawableSurface::drawRect(float x, float y, float w, float h, const Color& color) - { - drawRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); - } - - // compat - void DrawableSurface::drawRect(int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - drawRect(x, y, w, h, Color(r, g, b, a)); - } - - void DrawableSurface::drawFilledRect(int x, int y, int w, int h, const Color& color) - { - // clip - if (x < clipRect.x) - { - w -= clipRect.x - x; - x = clipRect.x; - } - if (y < 0) - { - h -= clipRect.y - y; - y = clipRect.y; - } - if (x + w >= clipRect.x + clipRect.w) - { - w = clipRect.x + clipRect.w - x; - } - if (y + h >= clipRect.y + clipRect.h) - { - h = clipRect.y + clipRect.h - y; - } - if ((w <= 0) || (h <= 0)) - return; - - // draw - if (color.a == Color::ALPHA_OPAQUE) - { - Uint32 colorValue = color.pack(); - for (int dy = y; dy < y + h; dy++) - { - Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + dy*(sdlsurface->pitch>>2) + x; - int dw = w; - do - { - *mem++ = colorValue; - } - while (--dw); - } - } - else - { - Uint32 a = color.a; - Uint32 na = 255 - a; - Uint32 colorValue = color.applyAlpha(Color::ALPHA_OPAQUE).pack(); - Uint32 colorPreMult0 = (colorValue & 0x00FF00FF) * a; - Uint32 colorPreMult1 = ((colorValue >> 8) & 0x00FF00FF) * a; - - for (int dy = y; dy < y + h; dy++) - { - Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + dy*(sdlsurface->pitch>>2) + x; - int dw = w; - do - { - Uint32 surfaceValue = *mem; - Uint32 surfacePreMult0 = (surfaceValue & 0x00FF00FF) * na; - Uint32 surfacePreMult1 = ((surfaceValue >> 8) & 0x00FF00FF) * na; - surfacePreMult0 += colorPreMult0; - surfacePreMult1 += colorPreMult1; - *mem++ = ((surfacePreMult0 >> 8) & 0x00FF00FF) | (surfacePreMult1 & 0xFF00FF00); - } - while (--dw); - } - } - dirty = true; - } - - void DrawableSurface::drawFilledRect(float x, float y, float w, float h, const Color& color) - { - drawFilledRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); - } - - void DrawableSurface::drawFilledRect(int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - drawFilledRect(x, y, w, h, Color(r, g, b, a)); - } - - void DrawableSurface::_drawVertLine(int x, int y, int l, const Color& color) - { - // clip - // be sure we have to draw something - if ((x < clipRect.x) || (x >= clipRect.x + clipRect.w)) - return; - - // set l positiv - if (l < 0) - { - y += l; - l = -l; - } - - // clip on y at top - if (y < clipRect.y) - { - l -= clipRect.y - y; - y = clipRect.y; - } - - // clip on y at bottom - if (y + l >= clipRect.y + clipRect.h) - { - l = clipRect.y + clipRect.h - y; - } - - // again, be sure we have to draw something - if (l <= 0) - return; - - // draw - int increment = sdlsurface->pitch >> 2; - Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + y*increment + x; - if (color.a == Color::ALPHA_OPAQUE) - { - Uint32 colorValue = color.pack(); - - do - { - *mem = colorValue; - mem += increment; - } - while (--l); - } - else - { - Uint32 a = color.a; - Uint32 na = 255 - a; - Uint32 colorValue = color.applyAlpha(Color::ALPHA_OPAQUE).pack(); - Uint32 colorPreMult0 = (colorValue & 0x00FF00FF) * a; - Uint32 colorPreMult1 = ((colorValue >> 8) & 0x00FF00FF) * a; - - do - { - Uint32 surfaceValue = *mem; - Uint32 surfacePreMult0 = (surfaceValue & 0x00FF00FF) * na; - Uint32 surfacePreMult1 = ((surfaceValue >> 8) & 0x00FF00FF) * na; - surfacePreMult0 += colorPreMult0; - surfacePreMult1 += colorPreMult1; - *mem = ((surfacePreMult0 >> 8) & 0x00FF00FF) | (surfacePreMult1 & 0xFF00FF00); - mem += increment; - } - while (--l); - } - dirty = true; - } - - void DrawableSurface::_drawHorzLine(int x, int y, int l, const Color& color) - { - // clip - // be sure we have to draw something - if ((y < clipRect.y) || (y >= clipRect.y + clipRect.h)) - return; - - // set l positiv - if (l < 0) - { - x += l; - l = -l; - } - - // clip on x at left - if (x < clipRect.x) - { - l -= clipRect.x - x; - x = clipRect.x; - } - - // clip on x at right - if ( x + l >= clipRect.x + clipRect.w) - { - l = clipRect.x + clipRect.w - x; - } - - // again, be sure we have to draw something - if (l <= 0) - return; - - // draw - Uint32 *mem = ((Uint32 *)sdlsurface->pixels) + y*(sdlsurface->pitch >> 2) + x; - if (color.a == Color::ALPHA_OPAQUE) - { - Uint32 colorValue = color.pack(); - - do - { - *mem++ = colorValue; - } - while (--l); - } - else - { - Uint32 a = color.a; - Uint32 na = 255 - a; - Uint32 colorValue = color.applyAlpha(Color::ALPHA_OPAQUE).pack(); - Uint32 colorPreMult0 = (colorValue & 0x00FF00FF) * a; - Uint32 colorPreMult1 = ((colorValue >> 8) & 0x00FF00FF) * a; - - do - { - Uint32 surfaceValue = *mem; - Uint32 surfacePreMult0 = (surfaceValue & 0x00FF00FF) * na; - Uint32 surfacePreMult1 = ((surfaceValue >> 8) & 0x00FF00FF) * na; - surfacePreMult0 += colorPreMult0; - surfacePreMult1 += colorPreMult1; - *mem++ = ((surfacePreMult0 >> 8) & 0x00FF00FF) | (surfacePreMult1 & 0xFF00FF00); - } - while (--l); - } - dirty = true; - } - - void DrawableSurface::drawLine(int x1, int y1, int x2, int y2, const Color& _color) - { - // we want to modify the color - Color color = _color; - - // compute deltas - int dx = x2 - x1; - if (dx == 0) - { - _drawVertLine(x1, y1, y2-y1, color); - return; - } - int dy = y2 - y1; - if (dy == 0) - { - _drawHorzLine(x1, y1, x2-x1, color); - return; - } - - // clip - int test = 1; - // Y clipping - if (dy < 0) - { - test = -test; - std::swap(x1, x2); - std::swap(y1, y2); - dx = -dx; - dy = -dy; - } - - // the 2 points are Y-sorted. (y1 <= y2) - if (y2 < clipRect.y) - return; - if (y1 >= clipRect.y + clipRect.h) - return; - if (y1 < clipRect.y) - { - x1 = x2 - ( (y2 - clipRect.y)*(x2-x1) ) / (y2-y1); - y1 = clipRect.y; - } - if (y1 == y2) - { - _drawHorzLine(x1, y1, x2-x1, color); - return; - } - if (y2 >= clipRect.y + clipRect.h) - { - x2 = x1 - ( (y1 - (clipRect.y + clipRect.h))*(x1-x2) ) / (y1-y2); - y2 = (clipRect.y + clipRect.h - 1); - } - if (x1 == x2) - { - _drawVertLine(x1, y1, y2-y1, color); - return; - } - - // X clipping - if (dx < 0) - { - test = -test; - std::swap(x1, x2); - std::swap(y1, y2); - dx = -dx; - dy = -dy; - } - // the 2 points are X-sorted. (x1 <= x2) - if (x2 < clipRect.x) - return; - if (x1 >= clipRect.x + clipRect.w) - return; - if (x1 < clipRect.x) - { - y1 = y2 - ( (x2 - clipRect.x)*(y2-y1) ) / (x2-x1); - x1 = clipRect.x; - } - if (x1 == x2) - { - _drawVertLine(x1, y1, y2-y1, color); - return; - } - if (x2 >= clipRect.x + clipRect.w) - { - y2 = y1 - ( (x1 - (clipRect.x + clipRect.w))*(y1-y2) ) / (x1-x2); - x2 = (clipRect.x + clipRect.w - 1); - } - - // last return case - if (x1 >= (clipRect.x + clipRect.w) || y1 >= (clipRect.y + clipRect.h) || (x2 < clipRect.x) || (y2 < clipRect.y)) - return; - - // recompute deltas after clipping - dx = x2-x1; - dy = y2-y1; - - // setup variable to draw alpha in the right direction - #define Sgn(x) (x>0 ? (x == 0 ? 0 : 1) : (x==0 ? 0 : -1)) - Sint32 littleincx; - Sint32 littleincy; - Sint32 bigincx; - Sint32 bigincy; - Sint32 alphadecx; - Sint32 alphadecy; - if (abs(dx) > abs(dy)) - { - littleincx = 1; - littleincy = 0; - bigincx = 1; - bigincy = Sgn(dy); - alphadecx = 0; - alphadecy = Sgn(dy); - } - else - { - // we swap x and y meaning - test = -test; - std::swap(dx, dy); - littleincx = 0; - littleincy = 1; - bigincx = Sgn(dx); - bigincy = 1; - alphadecx = 1; - alphadecy = 0; - } - - if (dx < 0) - { - dx = -dx; - littleincx = 0; - littleincy = -littleincy; - bigincx = -bigincx; - bigincy = -bigincy; - alphadecy = -alphadecy; - } - - // compute initial position - int px, py; - px = x1; - py = y1; - - // variable initialisation for bresenham algo - if (dx == 0) - return; - if (dy == 0) - return; - const int FIXED = 8; - const int I = 255; // number of degree of alpha - const int Ibits = 8; - int m = (abs(dy) << (Ibits+FIXED)) / abs(dx); - int w = (I << FIXED) - m; - int e = 1 << (FIXED-1); - - // first point - color.a = I - (e >> FIXED); - drawPixel(px, py, color); - - // main loop - int x = dx+1; - if (x <= 0) - return; - while (--x) - { - if (e < w) - { - px+=littleincx; - py+=littleincy; - e+= m; - } - else - { - px+=bigincx; - py+=bigincy; - e-= w; - } - color.a = I - (e >> FIXED); - drawPixel(px, py, color); - color.a = e >> FIXED; - drawPixel(px + alphadecx, py + alphadecy, color); - } - } - - void DrawableSurface::drawLine(float x1, float y1, float x2, float y2, const Color& color) - { - drawRect(static_cast(x1), static_cast(y1), static_cast(x2), static_cast(y2), color); - } - - void DrawableSurface::drawVertLine(int x, int y, int l, const Color& color) - { - _drawVertLine(x, y, l, color); - } - - void DrawableSurface::drawHorzLine(int x, int y, int l, const Color& color) - { - _drawHorzLine(x, y, l, color); - } - - // compat - void DrawableSurface::drawVertLine(int x, int y, int l, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - _drawVertLine(x, y, l, Color(r, g, b, a)); - } - // compat - void DrawableSurface::drawHorzLine(int x, int y, int l, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - _drawHorzLine(x, y, l, Color(r, g, b, a)); - } - // compat - void DrawableSurface::drawLine(int x1, int y1, int x2, int y2, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - drawLine(x1, y1, x2, y2, Color(r, g, b, a)); - } - - void DrawableSurface::drawCircle(int x, int y, int radius, const Color& _color) - { - // we want to modify the color - Color color = _color; - - // clip - if ((x+radius < clipRect.x) || (x-radius >= clipRect.x+clipRect.w) || (y+radius < clipRect.y) || (y-radius >= clipRect.y+clipRect.h)) - return; - - // draw - int dx, dy, d; - int rdx, rdy; - int i; - color.a >>= 2; - for (i=0; i<3; i++) - { - dx = 0; - dy = (radius<<1) + i; - d = 0; - - do - { - rdx = (dx>>1); - rdy = (dy>>1); - drawPixel(x+rdx, y+rdy, color); - drawPixel(x+rdx, y-rdy, color); - drawPixel(x-rdx, y+rdy, color); - drawPixel(x-rdx, y-rdy, color); - drawPixel(x+rdy, y+rdx, color); - drawPixel(x+rdy, y-rdx, color); - drawPixel(x-rdy, y+rdx, color); - drawPixel(x-rdy, y-rdx, color); - dx++; - if (d >= 0) - { - dy--; - d += ((dx-dy)<<1)+2; - } - else - { - d += (dx<<1) +1; - } - } - while (dx <= dy); - } - } - - void DrawableSurface::drawCircle(float x, float y, float radius, const Color& color) - { - drawCircle(static_cast(x), static_cast(y), static_cast(radius), color); - } - - // compat - void DrawableSurface::drawCircle(int x, int y, int radius, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - drawCircle(x, y, radius, Color(r, g, b, a)); - } - - void DrawableSurface::drawSurface(int x, int y, DrawableSurface *surface, Uint8 alpha) - { - drawSurface(x, y, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); - } - - void DrawableSurface::drawSurface(float x, float y, DrawableSurface *surface, Uint8 alpha) - { - drawSurface(x, y, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); - } - - void DrawableSurface::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, Uint8 alpha) - { - drawSurface(x, y, w, h, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); - } - - void DrawableSurface::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, Uint8 alpha) - { - drawSurface(x, y, w, h, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); - } - - void DrawableSurface::drawSurface(int x, int y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) - { - if (alpha == Color::ALPHA_OPAQUE) - { - #ifdef HAVE_OPENGL - if ((surface == _gc) && (_gc->getOptionFlags() & GraphicContext::USEGPU)) - { - if ((x == 0) && (y == 0) && (sdlsurface->w == sw) && (sdlsurface->h == sh)) - { - std::valarray tempPixels(sw*sh); - #if SDL_BYTEORDER == SDL_BIG_ENDIAN - glReadPixels(sx, sy, sdlsurface->w, sdlsurface->h, GL_RGBA, GL_UNSIGNED_BYTE, &tempPixels[0]); - #else - glReadPixels(sx, sy, sdlsurface->w, sdlsurface->h, GL_BGRA, GL_UNSIGNED_BYTE, &tempPixels[0]); - #endif - for (int y = 0; ypixels)[(sh-y-1)*sw]); - for (int x = 0; x> 8) | (*srcPtr << 24); - srcPtr++; - } - #else - *destPtr++ = *srcPtr++; - #endif - } - - } - else - { - std::cerr << "Partial blitting to from framebuffer in GL is forbidden" << std::endl; - assert(false); - } - } - else - { - #endif // HAVE_OPENGL - // well, we *hope* SDL is faster than a handmade code - SDL_Rect sr, dr; - sr.x = static_cast(sx); - sr.y = static_cast(sy); - sr.w = static_cast(sw); - sr.h = static_cast(sh); - dr.x = static_cast(x); - dr.y = static_cast(y); - dr.w = static_cast(sw); - dr.h = static_cast(sh); - SDL_BlitSurface(surface->sdlsurface, &sr, sdlsurface, &dr); - #ifdef HAVE_OPENGL - } - #endif // HAVE_OPENGL - } - else - { - if ((surface == _gc) && (_gc->getOptionFlags() & GraphicContext::USEGPU)) - { - std::cerr << "Blitting with alphablending from framebuffer in GL is forbidden" << std::endl; - assert(false); - } - - // check we assume the source rect is within the source surface - assert((sx >= 0) && (sx < surface->getW())); - assert((sy >= 0) && (sy < surface->getH())); - assert((sw > 0) && (sx + sw <= surface->getW())); - assert((sh > 0) && (sy + sh <= surface->getH())); - - // clip - if (x < clipRect.x) - { - int diff = clipRect.x - x; - sw -= diff; - sx += diff; - x = clipRect.x; - } - if (y < 0) - { - int diff = clipRect.y - y; - sh -= diff; - sy += diff; - y = clipRect.y; - } - if (x + sw >= clipRect.x + clipRect.w) - { - sw = clipRect.x + clipRect.w - x; - } - if (y + sh >= clipRect.y + clipRect.h) - { - sh = clipRect.y + clipRect.h - y; - } - if ((sw <= 0) || (sh <= 0)) - return; - - // draw - #if SDL_BYTEORDER == SDL_BIG_ENDIAN - Uint32 alphaShift = 0; - #else - Uint32 alphaShift = 24; - #endif - for (int dy = 0; dy < sh; dy++) - { - Uint32 *memSrc = ((Uint32 *)surface->sdlsurface->pixels) + (sy + dy)*(surface->sdlsurface->pitch>>2) + sx; - Uint32 *memDest = ((Uint32 *)sdlsurface->pixels) + (y + dy)*(sdlsurface->pitch>>2) + x; - int dw = sw; - do - { - Uint32 srcValue = *memSrc++; - Uint32 srcAlpha = (((srcValue >> alphaShift) & 0xFF) * alpha) >> 8; - Uint32 destAlpha = 255 - srcAlpha; - Uint32 srcPreMult0 = (srcValue & 0x00FF00FF) * srcAlpha; - Uint32 srcPreMult1 = ((srcValue >> 8) & 0x00FF00FF) * srcAlpha; - - Uint32 destValue = *memDest; - Uint32 destPreMult0 = (destValue & 0x00FF00FF) * destAlpha; - Uint32 destPreMult1 = ((destValue >> 8) & 0x00FF00FF) * destAlpha; - - destPreMult0 += srcPreMult0; - destPreMult1 += srcPreMult1; - - *memDest++ = ((destPreMult0 >> 8) & 0x00FF00FF) | (destPreMult1 & 0xFF00FF00); - } - while (--dw); - } - } - dirty = true; - } - - void DrawableSurface::drawSurface(float x, float y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) - { - drawSurface(static_cast(x), static_cast(y), surface, sx, sy, sw, sh, alpha); - } - - void DrawableSurface::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) - { - // TODO : Implement - } - - void DrawableSurface::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) - { - drawSurface(static_cast(x), static_cast(y), static_cast(w), static_cast(h), surface, sx, sy, sw, sh, alpha); - } - - void DrawableSurface::drawSprite(int x, int y, Sprite *sprite, unsigned index, Uint8 alpha) - { - // check bounds - assert(sprite); - if (!sprite->checkBound(index)) - return; - - // draw background - if (sprite->images[index]) - drawSurface(x, y, sprite->images[index], alpha); - - // draw rotation - if (sprite->rotated[index]) - drawSurface(x, y, sprite->getRotatedSurface(index), alpha); - } - - void DrawableSurface::drawSprite(float x, float y, Sprite *sprite, unsigned index, Uint8 alpha) - { - // check bounds - assert(sprite); - if (!sprite->checkBound(index)) - return; - - // draw background - if (sprite->images[index]) - drawSurface(x, y, sprite->images[index], alpha); - - // draw rotation - if (sprite->rotated[index]) - drawSurface(x, y, sprite->getRotatedSurface(index), alpha); - } - - void DrawableSurface::drawSprite(int x, int y, int w, int h, Sprite *sprite, unsigned index, Uint8 alpha) - { - // check bounds - assert(sprite); - if (!sprite->checkBound(index)) - return; - - // draw background - if (sprite->images[index]) - drawSurface(x, y, w, h, sprite->images[index], alpha); - - // draw rotation - if (sprite->rotated[index]) - drawSurface(x, y, w, h, sprite->getRotatedSurface(index), alpha); - } - - void DrawableSurface::drawSprite(float x, float y, float w, float h, Sprite *sprite, unsigned index, Uint8 alpha) - { - // check bounds - assert(sprite); - if (!sprite->checkBound(index)) - return; - - // draw background - if (sprite->images[index]) - drawSurface(x, y, w, h, sprite->images[index], alpha); - - // draw rotation - if (sprite->rotated[index]) - drawSurface(x, y, w, h, sprite->getRotatedSurface(index), alpha); - } - - void DrawableSurface::drawString(int x, int y, Font *font, const std::string &msg, int w, Uint8 alpha) - { - std::string output(msg); - std::string::size_type pos = output.find('\n', 0); - if(pos != std::string::npos) - output = output.substr(0, pos); - - pos = output.find('\r', 0); - if(pos != std::string::npos) - output = output.substr(0, pos); - - font->drawString(this, x, y, w, output, alpha); - - ///////////// The following code is for translation textshots //////////// - if(!translationPicturesDirectory.empty()) - { - for(std::map::iterator i=texts.begin(); i!=texts.end(); ++i) - { - if(output.find(i->first)!=std::string::npos) - { - int width=font->getStringWidth(i->first.c_str()); - int height=font->getStringHeight(i->first.c_str()); - int startx=font->getStringWidth(output.substr(0, output.find(i->first)).c_str()); - drawSquares.push_back(boost::make_tuple(SRectangle(x+startx, y, width, height), i->second, this)); - wroteTexts.insert(i->second); - texts.erase(i); - break; - } - } - } - } - - void DrawableSurface::drawString(float x, float y, Font *font, const std::string &msg, float w, Uint8 alpha) - { - std::string output(msg); - std::string::size_type pos = output.find('\n', 0); - if(pos != std::string::npos) - output = output.substr(0, pos); - - pos = output.find('\r', 0); - if(pos != std::string::npos) - output = output.substr(0, pos); - - ///////////// The following code is for translation textshots //////////// - if(!translationPicturesDirectory.empty()) - { - for(std::map::iterator i=texts.begin(); i!=texts.end(); ++i) - { - if(output.find(i->first)!=std::string::npos) - { - int width=font->getStringWidth(i->first.c_str()); - int height=font->getStringHeight(i->first.c_str()); - int startx=font->getStringWidth(output.substr(0, output.find(i->first)).c_str()); - drawSquares.push_back(boost::make_tuple(SRectangle(int(x+startx), int(y), width, height), i->second, this)); - wroteTexts.insert(i->second); - texts.erase(i); - break; - } - } - } - font->drawString(this, x, y, w, output, alpha); - - } - - void DrawableSurface::drawAlphaMap(const std::valarray &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color) - { - assert(mapW * mapH <= static_cast(map.size())); - - for (int dy=0; dy < mapH-1; dy++) - for (int dx=0; dx < mapW-1; dx++) - drawFilledRect(x + dx * cellW, y + dy * cellH, cellW, cellH, color.applyMultiplyAlpha((Uint8)(255.0f * map[mapW * dy + dx]))); - } - - void DrawableSurface::drawAlphaMap(const std::valarray &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color) - { - assert(mapW * mapH <= static_cast(map.size())); +namespace GAGCore +{ + // Storage for the static globals declared in graphic_context_private.h. + GraphicContext *_gc = NULL; + SDL_PixelFormat _glFormat; + const bool EXPERIMENTAL = false; - for (int dy=0; dy < mapH-1; dy++) - for (int dx=0; dx < mapW-1; dx++) - drawFilledRect(x + dx * cellW, y + dy * cellH, cellW, cellH, color.applyMultiplyAlpha(map[mapW * dy + dx])); - } +#ifdef HAVE_OPENGL + GLState glState; - // compat - void DrawableSurface::drawString(int x, int y, Font *font, int i) + void GLState::checkExtensions(void) { - std::stringstream str; - str << i; - this->drawString(x, y, font, str.str()); - } + const char *glExtensions = (const char *)glGetString(GL_EXTENSIONS); + isTextureSRectangle = (strstr(glExtensions, "GL_NV_texture_rectangle") != NULL); + isTextureSRectangle = isTextureSRectangle || (strstr(glExtensions, "GL_EXT_texture_rectangle") != NULL); + isTextureSRectangle = isTextureSRectangle || (strstr(glExtensions, "GL_ARB_texture_rectangle") != NULL); - //This code is for the textshot code - std::map DrawableSurface::texts; - std::set DrawableSurface::wroteTexts; - std::vector > DrawableSurface::drawSquares; - std::string DrawableSurface::translationPicturesDirectory; + const char *glVendor = (const char *)glGetString(GL_VENDOR); + if (strstr(glVendor, "ATI")) + useATIWorkaround = true; // ugly temporary bug fix for bug 13823. We think it is an ATI driver bug - void DrawableSurface::flushTextPictures() - { - using namespace GAGCore; - for(std::vector >::iterator i=drawSquares.begin(); i!=drawSquares.end();) + if (verbose) { - DrawableSurface toPrint(i->get<2>()->getW(), i->get<2>()->getH()); - toPrint.drawSurface(0, 0, i->get<2>()); - int x=i->get<0>().x; - int y=i->get<0>().y; - int width=i->get<0>().w; - int height=i->get<0>().h; - - toPrint.drawRect(x-2, y-2, width+4, height+4, Color(255, 126, 21)); - toPrint.drawRect(x-3, y-3, width+6, height+6, Color(255, 126, 21)); - toPrint.drawCircle(x+width/2, y+height/2, std::max(width+4, height+4)/2+4, Color(255, 126, 21)); - toPrint.drawCircle(x+width/2, y+height/2, std::max(width+4, height+4)/2+5, Color(255, 126, 21)); - toPrint.drawCircle(x+width/2, y+height/2, std::max(width+4, height+4)/2+6, Color(255, 126, 21)); - - // Print it using virtual filesystem - for (size_t i2 = 0; i2 < Toolkit::getFileManager()->getDirCount(); i2++) + if (isTextureSRectangle) { - std::string fullFileName = translationPicturesDirectory + DIR_SEPARATOR_S + "text-" + i->get<1>(); - if (SDL_SaveBMP(toPrint.sdlsurface, (fullFileName+".bmp").c_str()) == 0) - { - break; - } + std::cout << "Toolkit : GL_NV_texture_rectangle or GL_EXT_texture_rectangle extension present, optimal texture size will be used" << std::endl; + } else { + std::cout << "Toolkit : GL_NV_texture_rectangle or GL_EXT_texture_rectangle extension not present, power of two texture will be used" << std::endl; } - i=drawSquares.erase(i); - } - } - - void DrawableSurface::printFinishingText() - { - if(!texts.empty()) - std::cout<<"The following requested translation texts where never drawn to the screen, or too mangled to be detected:"<::iterator i=texts.begin(); i!=texts.end(); ++i) - { - std::cout<<"\t"<second<optionFlags & GraphicContext::USEGPU) - { - glState.doScissor(true); - glScissor(clipRect.x, getH() - clipRect.y - clipRect.h, clipRect.w, clipRect.h); - } - #endif - } - - void GraphicContext::setClipRect(void) - { - DrawableSurface::setClipRect(); - #ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) - glState.doScissor(false); - #endif - } - - // drawing, reimplementation for GL - - void GraphicContext::drawPixel(int x, int y, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - GraphicContext::drawPixel(static_cast(x), static_cast(y), color); - else - #endif - DrawableSurface::drawPixel(x, y, color); - } - - void GraphicContext::drawPixel(float x, float y, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - drawFilledRect(x, y, 1.0f, 1.0f, color); - else - #endif - DrawableSurface::drawPixel(static_cast(x), static_cast(y), color); - } - - - void GraphicContext::drawRect(int x, int y, int w, int h, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - GraphicContext::drawRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); - else - #endif - DrawableSurface::drawRect(x, y, w, h, color); - } - - void GraphicContext::drawRect(float x, float y, float w, float h, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - { - // state change - glState.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glState.doBlend(true); - glState.doTexture(false); - - // draw - glBegin(GL_LINES); - if (color.a < 255) - glColor4ub(color.r, color.g, color.b, color.a); - else - glColor3ub(color.r, color.g, color.b); - glVertex2f(x, y); glVertex2f(x+w, y); - glVertex2f(x+w, y); glVertex2f(x+w, y+h); - glVertex2f(x+w, y+h); glVertex2f(x, y+h); - glVertex2f(x, y+h); glVertex2f(x, y); - glEnd(); - } - else - #endif - DrawableSurface::drawRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); - } - - - void GraphicContext::drawFilledRect(int x, int y, int w, int h, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - GraphicContext::drawFilledRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); - else - #endif - DrawableSurface::drawFilledRect(x, y, w, h, color); - } - - void GraphicContext::drawFilledRect(float x, float y, float w, float h, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - { - // state change - if (color.a < 255) - glState.doBlend(true); - else - glState.doBlend(false); - glState.doTexture(false); - - // draw - glBegin(GL_QUADS); - if (color.a < 255) - glColor4ub(color.r, color.g, color.b, color.a); - else - glColor3ub(color.r, color.g, color.b); - glVertex2f(x, y); - glVertex2f(x+w, y); - glVertex2f(x+w, y+h); - glVertex2f(x, y+h); - glEnd(); - } - else - #endif - DrawableSurface::drawFilledRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); - } - - - void GraphicContext::drawLine(int x1, int y1, int x2, int y2, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - GraphicContext::drawLine(static_cast(x1), static_cast(y1), static_cast(x2), static_cast(y2), color); - else - #endif - DrawableSurface::drawLine(x1, y1, x2, y2, color); - } - - void GraphicContext::drawLine(float x1, float y1, float x2, float y2, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - { - // state change - glState.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glState.doBlend(true); - glState.doTexture(false); - - // draw - glBegin(GL_LINES); - if (color.a < 255) - glColor4ub(color.r, color.g, color.b, color.a); - else - glColor3ub(color.r, color.g, color.b); - glVertex2f(x1, y1); - glVertex2f(x2, y2); - glEnd(); } - else - #endif - DrawableSurface::drawLine(static_cast(x1), static_cast(y1), static_cast(x2), static_cast(y2), color); - } - - - void GraphicContext::drawCircle(int x, int y, int radius, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - drawCircle(static_cast(x), static_cast(y), static_cast(radius), color); - else - #endif - DrawableSurface::drawCircle(x, y, radius, color); } - void GraphicContext::drawCircle(float x, float y, float radius, const Color& color) + bool GLState::doBlend(bool on) { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - { - glState.doBlend(true); - glState.doTexture(false); - glLineWidth(2); - - double tot = radius; - double fx = x; - double fy = y; - double fray = radius; - - glBegin(GL_LINES); - if (color.a < 255) - glColor4ub(color.r, color.g, color.b, color.a); - else - glColor3ub(color.r, color.g, color.b); - for (int i=0; i(x), static_cast(y), static_cast(radius), color); - } - - void GraphicContext::drawSurface(int x, int y, DrawableSurface *surface, Uint8 alpha) - { - drawSurface(x, y, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); - } - - void GraphicContext::drawSurface(float x, float y, DrawableSurface *surface, Uint8 alpha) - { - drawSurface(x, y, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); - } - - void GraphicContext::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, Uint8 alpha) - { - drawSurface(x, y, w, h, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); - } - - void GraphicContext::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, Uint8 alpha) - { - drawSurface(x, y, w, h, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); + glDisable(GL_BLEND); + _doBlend = on; + return !on; } - void GraphicContext::drawSurface(int x, int y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + bool GLState::doTexture(bool on) { - #ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) - drawSurface(x, y, sw, sh, surface, sx, sy, sw, sh, alpha); + if (_doTexture == on) + return on; + GLenum cap; + if (isTextureSRectangle) + cap = GL_TEXTURE_RECTANGLE_NV; else - #endif - DrawableSurface::drawSurface(x, y, surface, sx, sy, sw, sh, alpha); - } + cap = GL_TEXTURE_2D; - void GraphicContext::drawSurface(float x, float y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) - { - #ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) - drawSurface(x, y, static_cast(sw), static_cast(sh), surface, sx, sy, sw, sh, alpha); + if (on) + glEnable(cap); else - #endif - DrawableSurface::drawSurface(static_cast(x), static_cast(y), surface, sx, sy, sw, sh, alpha); + glDisable(cap); + _doTexture = on; + return !on; } - void GraphicContext::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + void GLState::setTexture(int tex) { - #ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) - GraphicContext::drawSurface(static_cast(x), static_cast(y), static_cast(w), static_cast(h), surface, sx, sy, sw, sh, alpha); - else - #endif - DrawableSurface::drawSurface(x, y, w, h, surface, sx, sy, sw, sh, alpha); - } + if (_texture == tex) + return; - void GraphicContext::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) - { - #ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) + if (isTextureSRectangle) { - // upload - if (surface->dirty) - surface->uploadToTexture(); - - // state change - glState.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glState.doBlend(true); - glState.doTexture(true); - glColor4ub(255, 255, 255, alpha); - - // draw - glState.setTexture(surface->texture); - if (surface->textureInfo && surface->textureInfo->sprite) - { - Sprite* sprite = surface->textureInfo->sprite; - std::vector oldVertices, oldCoords; - // If drawing with transparency, save vectors, draw immediately, then restore - if (alpha != Color::ALPHA_OPAQUE) - { - // Fix bug #124 - School renders as white square when placing building and there is no room - oldVertices = sprite->vertices; - oldCoords = sprite->texCoords; - sprite->vertices.clear(); - sprite->texCoords.clear(); - } - // Queue this draw call until finishDrawingSprite is called. - sprite->vertices.insert(sprite->vertices.end(), { x, y, x + w, y, x + w, y + h, x, y + h }); - sprite->texCoords.insert(sprite->texCoords.end(), { - static_cast(sx) * surface->texMultX, static_cast(sy) * surface->texMultY, - static_cast(sx + sw) * surface->texMultX, static_cast(sy) * surface->texMultY, - static_cast(sx + sw) * surface->texMultX, static_cast(sy + sh) * surface->texMultY, - static_cast(sx) * surface->texMultX, static_cast(sy + sh) * surface->texMultY - }); - if (alpha != Color::ALPHA_OPAQUE) - { - finishDrawingSprite(sprite, alpha); - sprite->vertices = oldVertices; - sprite->texCoords = oldCoords; - } - } - else - { - glBegin(GL_QUADS); - glTexCoord2f(static_cast(sx) * surface->texMultX, static_cast(sy) * surface->texMultY); - glVertex2f(x, y); - glTexCoord2f(static_cast(sx + sw) * surface->texMultX, static_cast(sy) * surface->texMultY); - glVertex2f(x + w, y); - glTexCoord2f(static_cast(sx + sw) * surface->texMultX, static_cast(sy + sh) * surface->texMultY); - glVertex2f(x + w, y + h); - glTexCoord2f(static_cast(sx) * surface->texMultX, static_cast(sy + sh) * surface->texMultY); - glVertex2f(x, y + h); - glEnd(); - } + if (useATIWorkaround) + glBindTexture(GL_TEXTURE_RECTANGLE_NV, 0); + glBindTexture(GL_TEXTURE_RECTANGLE_NV, tex); } else - #endif - DrawableSurface::drawSurface(static_cast(x), static_cast(y), static_cast(w), static_cast(h), surface, sx, sy, sw, sh, alpha); - } - - // Lets us efficiently draw terrain and water. - void GraphicContext::finishDrawingSprite(Sprite* sprite, Uint8 alpha) - { -#ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) - { - if (!sprite->atlas) - { - // No sprite sheet, so we have nothing to draw. - assert(sprite->vertices.empty()); - assert(sprite->texCoords.empty()); - return; - } - if (sprite->vertices.empty() || sprite->texCoords.empty()) - { - // No data. - return; - } - // state change - glState.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glState.doBlend(true); - glState.doTexture(true); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glColor4ub(255, 255, 255, alpha); - glState.setTexture(sprite->atlas->texture); - glBindBuffer(GL_ARRAY_BUFFER, sprite->vbo); - glBufferData(GL_ARRAY_BUFFER, sprite->vertices.size() * sizeof(float), sprite->vertices.data(), GL_STREAM_DRAW); - glVertexPointer(2, GL_FLOAT, 0, 0); - glBindBuffer(GL_ARRAY_BUFFER, sprite->texCoordBuffer); - glBufferData(GL_ARRAY_BUFFER, sprite->texCoords.size() * sizeof(float), sprite->texCoords.data(), GL_STREAM_DRAW); - glTexCoordPointer(2, GL_FLOAT, 0, 0); - glDrawArrays(GL_QUADS, 0, sprite->vertices.size() / 2); - - sprite->vertices.clear(); - sprite->texCoords.clear(); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - } -#endif + glBindTexture(GL_TEXTURE_2D, tex); + _texture = tex; } - void GraphicContext::drawAlphaMap(const std::valarray &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color) + bool GLState::doScissor(bool on) { - #ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) - { - assert(mapW * mapH <= static_cast(map.size())); - float fr = 255.0f*(float)color.r; - float fg = 255.0f*(float)color.g; - float fb = 255.0f*(float)color.b; - if (EXPERIMENTAL) { - GLuint texture[1]; - GLboolean old_blend; //var to store blend state - glGetBooleanv(GL_BLEND,&old_blend); //store blend state - glEnable(GL_BLEND); //enable blend - GLboolean old_texture_2d; - glGetBooleanv(GL_TEXTURE_2D,&old_texture_2d); - glEnable(GL_TEXTURE_2D); - std::valarray image(mapW*mapH); - for (int i=0; i &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color) + void GLState::blendFunc(GLenum sfactor, GLenum dfactor) { - #ifdef HAVE_OPENGL - if (_gc->optionFlags & GraphicContext::USEGPU) - { - assert(mapW * mapH <= static_cast(map.size())); - if(EXPERIMENTAL) { - glPushMatrix(); - glEnable(GL_BLEND); - glEnable(GL_TEXTURE_2D); -/* glState.resetCache(); - bool oldBlend=glState.doBlend(true); - bool oldTexture=glState.doTexture(true);*/ - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - std::valarray image(mapW*mapH); - for (int i=0; i(r)/255.0f, static_cast(g)/255.0f, static_cast(b)/255.0f, hue, sat, lum); } - void GraphicContext::drawLine(int x1, int y1, int x2, int y2, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + void Color::setHSV(float hue, float sat, float lum) { - drawLine(x1, y1, x2, y2, Color(r, g, b, a)); + float fr, fg, fb; + HSVtoRGB(&fr, &fg, &fb, hue, sat, lum); + r = static_cast(255.0f*fr); + g = static_cast(255.0f*fg); + b = static_cast(255.0f*fb); } - void GraphicContext::drawVertLine(int x, int y, int l, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - drawLine(x, y, x, y+l, Color(r, g, b, a)); - else - #endif - _drawVertLine(x, y, l, Color(r, g, b, a)); - } - - void GraphicContext::drawVertLine(int x, int y, int l, const Color& color) + Color Color::applyMultiplyAlpha(Uint8 _a) const { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - drawLine(x, y, x, y+l, color); - else - #endif - _drawVertLine(x, y, l, color); + Color c; + c.r = r; + c.g = g; + c.b = b; + c.a = _a; + return c; } - void GraphicContext::drawHorzLine(int x, int y, int l, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - drawLine(x, y, x+l, y, Color(r, g, b, a)); - else - #endif - _drawHorzLine(x, y, l, Color(r, g, b, a)); - } - - void GraphicContext::drawHorzLine(int x, int y, int l, const Color& color) - { - #ifdef HAVE_OPENGL - if (optionFlags & GraphicContext::USEGPU) - drawLine(x, y, x+l, y, color); - else - #endif - _drawHorzLine(x, y, l, color); - } + // Predefined colors + Color Color::black = Color(0, 0, 0); + Color Color::white = Color(255, 255, 255); - void GraphicContext::drawCircle(int x, int y, int radius, Uint8 r, Uint8 g, Uint8 b, Uint8 a) - { - drawCircle(x, y, radius, Color(r, g, b, a)); - } + // GraphicContext lifecycle and window management void GraphicContext::setMinRes(int w, int h) { @@ -2138,16 +291,29 @@ namespace GAGCore } // create the new window and the surface window = SDL_CreateWindow(windowTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, sdlFlags); - sdlsurface = window != nullptr ? SDL_GetWindowSurface(window) : nullptr; - - // check surface - if (!sdlsurface) + if (!window) { - fprintf(stderr, "Toolkit : can't set screen to %dx%d at 32 bpp\n", w, h); + fprintf(stderr, "Toolkit : can't create window %dx%d\n", w, h); fprintf(stderr, "Toolkit : %s\n", SDL_GetError()); return false; } + // SDL_GetWindowSurface is incompatible with SDL_WINDOW_OPENGL; + // in GPU mode, create a small dummy surface so format-dependent code works. + if (optionFlags & USEGPU) + { + sdlsurface = SDL_CreateRGBSurface(0, w, h, 32, + 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000); + } else + { + sdlsurface = SDL_GetWindowSurface(window); + } + if (!sdlsurface) + { + fprintf(stderr, "Toolkit : can't get surface for %dx%d at 32 bpp\n", w, h); + fprintf(stderr, "Toolkit : %s\n", SDL_GetError()); + return false; + } { _gc = this; // enable GL context @@ -2183,9 +349,6 @@ namespace GAGCore _glFormat.Gloss = 0; _glFormat.Bloss = 0; _glFormat.Aloss = 0; - //_glFormat.colorkey = 0; - //_glFormat.alpha = 255; - //_glFormat = *SDL_AllocFormat(32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000); } else { @@ -2264,7 +427,6 @@ namespace GAGCore { Sprite::checkAllSpritesDrawn(); SDL_GL_SwapWindow(window); - //fprintf(stderr, "%d allocated GPU textures\n", glState.alocatedTextureCount); } else #endif @@ -2303,34 +465,4 @@ namespace GAGCore } } } - - // Font stuff - - int Font::getStringWidth(const int i) - { - std::ostringstream temp; - temp << i; - return getStringWidth(temp.str()); - } - - int Font::getStringWidth(const std::string string, int len) - { - std::string temp; - temp.append(string.c_str(), len); - return getStringWidth(temp); - } - - int Font::getStringHeight(const std::string string, int len) - { - std::string temp; - temp.append(string.c_str(), len); - return getStringHeight(temp); - } - - int Font::getStringHeight(const int i) - { - std::ostringstream temp; - temp << i; - return getStringHeight(temp.str()); - } } diff --git a/libgag/src/GraphicContextCompound.cpp b/libgag/src/GraphicContextCompound.cpp new file mode 100644 index 000000000..5354b123e --- /dev/null +++ b/libgag/src/GraphicContextCompound.cpp @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "GraphicContextPrivate.h" +#include +#include +#include + +namespace GAGCore +{ + void GraphicContext::drawSurface(int x, int y, DrawableSurface *surface, Uint8 alpha) + { + drawSurface(x, y, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); + } + + void GraphicContext::drawSurface(float x, float y, DrawableSurface *surface, Uint8 alpha) + { + drawSurface(x, y, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); + } + + void GraphicContext::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, Uint8 alpha) + { + drawSurface(x, y, w, h, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); + } + + void GraphicContext::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, Uint8 alpha) + { + drawSurface(x, y, w, h, surface, surface->getTexX(), surface->getTexY(), surface->getW(), surface->getH(), alpha); + } + + void GraphicContext::drawSurface(int x, int y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + { + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + drawSurface(x, y, sw, sh, surface, sx, sy, sw, sh, alpha); + else + #endif + DrawableSurface::drawSurface(x, y, surface, sx, sy, sw, sh, alpha); + } + + void GraphicContext::drawSurface(float x, float y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + { + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + drawSurface(x, y, static_cast(sw), static_cast(sh), surface, sx, sy, sw, sh, alpha); + else + #endif + DrawableSurface::drawSurface(static_cast(x), static_cast(y), surface, sx, sy, sw, sh, alpha); + } + + void GraphicContext::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + { + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + GraphicContext::drawSurface(static_cast(x), static_cast(y), static_cast(w), static_cast(h), surface, sx, sy, sw, sh, alpha); + else + #endif + DrawableSurface::drawSurface(x, y, w, h, surface, sx, sy, sw, sh, alpha); + } + + void GraphicContext::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) + { + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + { + // upload + if (surface->dirty) + surface->uploadToTexture(); + + // state change + glState.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glState.doBlend(true); + glState.doTexture(true); + glColor4ub(255, 255, 255, alpha); + + // draw + glState.setTexture(surface->texture); + if (surface->textureInfo && surface->textureInfo->sprite) + { + Sprite* sprite = surface->textureInfo->sprite; + std::vector oldVertices, oldCoords; + // If drawing with transparency, save vectors, draw immediately, then restore + if (alpha != Color::ALPHA_OPAQUE) + { + // Fix bug #124 - School renders as white square when placing building and there is no room + oldVertices = sprite->vertices; + oldCoords = sprite->texCoords; + sprite->vertices.clear(); + sprite->texCoords.clear(); + } + // Queue this draw call until finishDrawingSprite is called. + sprite->vertices.insert(sprite->vertices.end(), { x, y, x + w, y, x + w, y + h, x, y + h }); + sprite->texCoords.insert(sprite->texCoords.end(), { + static_cast(sx) * surface->texMultX, static_cast(sy) * surface->texMultY, + static_cast(sx + sw) * surface->texMultX, static_cast(sy) * surface->texMultY, + static_cast(sx + sw) * surface->texMultX, static_cast(sy + sh) * surface->texMultY, + static_cast(sx) * surface->texMultX, static_cast(sy + sh) * surface->texMultY + }); + if (alpha != Color::ALPHA_OPAQUE) + { + finishDrawingSprite(sprite, alpha); + sprite->vertices = oldVertices; + sprite->texCoords = oldCoords; + } + } + else + { + glBegin(GL_QUADS); + glTexCoord2f(static_cast(sx) * surface->texMultX, static_cast(sy) * surface->texMultY); + glVertex2f(x, y); + glTexCoord2f(static_cast(sx + sw) * surface->texMultX, static_cast(sy) * surface->texMultY); + glVertex2f(x + w, y); + glTexCoord2f(static_cast(sx + sw) * surface->texMultX, static_cast(sy + sh) * surface->texMultY); + glVertex2f(x + w, y + h); + glTexCoord2f(static_cast(sx) * surface->texMultX, static_cast(sy + sh) * surface->texMultY); + glVertex2f(x, y + h); + glEnd(); + } + } + else + #endif + DrawableSurface::drawSurface(static_cast(x), static_cast(y), static_cast(w), static_cast(h), surface, sx, sy, sw, sh, alpha); + } + + // Lets us efficiently draw terrain and water. + void GraphicContext::finishDrawingSprite(Sprite* sprite, Uint8 alpha) + { +#ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + { + if (!sprite->atlas) + { + // No sprite sheet, so we have nothing to draw. + assert(sprite->vertices.empty()); + assert(sprite->texCoords.empty()); + return; + } + if (sprite->vertices.empty() || sprite->texCoords.empty()) + { + // No data. + return; + } + // state change + glState.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glState.doBlend(true); + glState.doTexture(true); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glColor4ub(255, 255, 255, alpha); + glState.setTexture(sprite->atlas->texture); + glBindBuffer(GL_ARRAY_BUFFER, sprite->vbo); + glBufferData(GL_ARRAY_BUFFER, sprite->vertices.size() * sizeof(float), sprite->vertices.data(), GL_STREAM_DRAW); + glVertexPointer(2, GL_FLOAT, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, sprite->texCoordBuffer); + glBufferData(GL_ARRAY_BUFFER, sprite->texCoords.size() * sizeof(float), sprite->texCoords.data(), GL_STREAM_DRAW); + glTexCoordPointer(2, GL_FLOAT, 0, 0); + glDrawArrays(GL_QUADS, 0, sprite->vertices.size() / 2); + + sprite->vertices.clear(); + sprite->texCoords.clear(); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } +#endif + } + + void GraphicContext::drawAlphaMap(const std::valarray &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color) + { + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + { + assert(mapW * mapH <= static_cast(map.size())); + float fr = 255.0f*(float)color.r; + float fg = 255.0f*(float)color.g; + float fb = 255.0f*(float)color.b; + if (EXPERIMENTAL) { + GLuint texture[1]; + GLboolean old_blend; //var to store blend state + glGetBooleanv(GL_BLEND,&old_blend); //store blend state + glEnable(GL_BLEND); //enable blend + GLboolean old_texture_2d; + glGetBooleanv(GL_TEXTURE_2D,&old_texture_2d); + glEnable(GL_TEXTURE_2D); + std::valarray image(mapW*mapH); + for (int i=0; i &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color) + { + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + { + assert(mapW * mapH <= static_cast(map.size())); + if(EXPERIMENTAL) { + glPushMatrix(); + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); +/* glState.resetCache(); + bool oldBlend=glState.doBlend(true); + bool oldTexture=glState.doTexture(true);*/ + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + std::valarray image(mapW*mapH); + for (int i=0; i + +namespace GAGCore +{ + void GraphicContext::setClipRect(int x, int y, int w, int h) + { + DrawableSurface::setClipRect(x, y, w, h); + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + { + glState.doScissor(true); + glScissor(clipRect.x, getH() - clipRect.y - clipRect.h, clipRect.w, clipRect.h); + } + #endif + } + + void GraphicContext::setClipRect(void) + { + DrawableSurface::setClipRect(); + #ifdef HAVE_OPENGL + if (_gc->optionFlags & GraphicContext::USEGPU) + glState.doScissor(false); + #endif + } + + // drawing, reimplementation for GL + + void GraphicContext::drawPixel(int x, int y, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + GraphicContext::drawPixel(static_cast(x), static_cast(y), color); + else + #endif + DrawableSurface::drawPixel(x, y, color); + } + + void GraphicContext::drawPixel(float x, float y, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + drawFilledRect(x, y, 1.0f, 1.0f, color); + else + #endif + DrawableSurface::drawPixel(static_cast(x), static_cast(y), color); + } + + + void GraphicContext::drawRect(int x, int y, int w, int h, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + GraphicContext::drawRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); + else + #endif + DrawableSurface::drawRect(x, y, w, h, color); + } + + void GraphicContext::drawRect(float x, float y, float w, float h, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + { + // state change + glState.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glState.doBlend(true); + glState.doTexture(false); + + // draw + glBegin(GL_LINES); + if (color.a < 255) + glColor4ub(color.r, color.g, color.b, color.a); + else + glColor3ub(color.r, color.g, color.b); + glVertex2f(x, y); glVertex2f(x+w, y); + glVertex2f(x+w, y); glVertex2f(x+w, y+h); + glVertex2f(x+w, y+h); glVertex2f(x, y+h); + glVertex2f(x, y+h); glVertex2f(x, y); + glEnd(); + } + else + #endif + DrawableSurface::drawRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); + } + + + void GraphicContext::drawFilledRect(int x, int y, int w, int h, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + GraphicContext::drawFilledRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); + else + #endif + DrawableSurface::drawFilledRect(x, y, w, h, color); + } + + void GraphicContext::drawFilledRect(float x, float y, float w, float h, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + { + // state change + if (color.a < 255) + glState.doBlend(true); + else + glState.doBlend(false); + glState.doTexture(false); + + // draw + glBegin(GL_QUADS); + if (color.a < 255) + glColor4ub(color.r, color.g, color.b, color.a); + else + glColor3ub(color.r, color.g, color.b); + glVertex2f(x, y); + glVertex2f(x+w, y); + glVertex2f(x+w, y+h); + glVertex2f(x, y+h); + glEnd(); + } + else + #endif + DrawableSurface::drawFilledRect(static_cast(x), static_cast(y), static_cast(w), static_cast(h), color); + } + + + void GraphicContext::drawLine(int x1, int y1, int x2, int y2, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + GraphicContext::drawLine(static_cast(x1), static_cast(y1), static_cast(x2), static_cast(y2), color); + else + #endif + DrawableSurface::drawLine(x1, y1, x2, y2, color); + } + + void GraphicContext::drawLine(float x1, float y1, float x2, float y2, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + { + // state change + glState.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glState.doBlend(true); + glState.doTexture(false); + + // draw + glBegin(GL_LINES); + if (color.a < 255) + glColor4ub(color.r, color.g, color.b, color.a); + else + glColor3ub(color.r, color.g, color.b); + glVertex2f(x1, y1); + glVertex2f(x2, y2); + glEnd(); + } + else + #endif + DrawableSurface::drawLine(static_cast(x1), static_cast(y1), static_cast(x2), static_cast(y2), color); + } + + + void GraphicContext::drawCircle(int x, int y, int radius, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + drawCircle(static_cast(x), static_cast(y), static_cast(radius), color); + else + #endif + DrawableSurface::drawCircle(x, y, radius, color); + } + + void GraphicContext::drawCircle(float x, float y, float radius, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + { + glState.doBlend(true); + glState.doTexture(false); + glLineWidth(2); + + double tot = radius; + double fx = x; + double fy = y; + double fray = radius; + + glBegin(GL_LINES); + if (color.a < 255) + glColor4ub(color.r, color.g, color.b, color.a); + else + glColor3ub(color.r, color.g, color.b); + for (int i=0; i(x), static_cast(y), static_cast(radius), color); + } + + // compat... this is there because it sems gcc is not able to do function overloading with several levels of inheritance + void GraphicContext::drawPixel(int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawPixel(x, y, Color(r, g, b, a)); + } + + void GraphicContext::drawRect(int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawRect(x, y, w, h, Color(r, g, b, a)); + } + + void GraphicContext::drawFilledRect(int x, int y, int w, int h, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawFilledRect(x, y, w, h, Color(r, g, b, a)); + } + + void GraphicContext::drawLine(int x1, int y1, int x2, int y2, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawLine(x1, y1, x2, y2, Color(r, g, b, a)); + } + + void GraphicContext::drawVertLine(int x, int y, int l, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + drawLine(x, y, x, y+l, Color(r, g, b, a)); + else + #endif + _drawVertLine(x, y, l, Color(r, g, b, a)); + } + + void GraphicContext::drawVertLine(int x, int y, int l, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + drawLine(x, y, x, y+l, color); + else + #endif + _drawVertLine(x, y, l, color); + } + + void GraphicContext::drawHorzLine(int x, int y, int l, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + drawLine(x, y, x+l, y, Color(r, g, b, a)); + else + #endif + _drawHorzLine(x, y, l, Color(r, g, b, a)); + } + + void GraphicContext::drawHorzLine(int x, int y, int l, const Color& color) + { + #ifdef HAVE_OPENGL + if (optionFlags & GraphicContext::USEGPU) + drawLine(x, y, x+l, y, color); + else + #endif + _drawHorzLine(x, y, l, color); + } + + void GraphicContext::drawCircle(int x, int y, int radius, Uint8 r, Uint8 g, Uint8 b, Uint8 a) + { + drawCircle(x, y, radius, Color(r, g, b, a)); + } +} diff --git a/libgag/src/GraphicContextPrivate.h b/libgag/src/GraphicContextPrivate.h new file mode 100644 index 000000000..198fc3d85 --- /dev/null +++ b/libgag/src/GraphicContextPrivate.h @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// Internal header shared between graphic_context*.cpp and drawable_surface*.cpp. +// Not part of the public libgag API. + +#pragma once + +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_OPENGL +#if defined(__APPLE__) +#include +#include +#include +#define GL_TEXTURE_RECTANGLE_NV GL_TEXTURE_RECTANGLE_EXT +#else +#include +#ifdef _MSC_VER +#include +#else +#include +#endif +#endif // defined(__APPLE__) +#endif // HAVE_OPENGL + +#ifdef HAVE_OPENGL +#define GL_GLEXT_PROTOTYPES + #if defined(__APPLE__) || defined(OPENGL_HEADER_DIRECTORY_OPENGL) + #include + #include + #include + #define GL_TEXTURE_RECTANGLE_NV GL_TEXTURE_RECTANGLE_EXT + #else + #include + #include + #endif +#endif + +#ifdef WIN32 + #include +#endif + +namespace GAGCore +{ + // The active graphic context. Set by GraphicContext::setRes. + extern GraphicContext *_gc; + // SDL pixel format used for GL uploads. Configured by GraphicContext::setRes. + extern SDL_PixelFormat _glFormat; + // EXPERIMENTAL is a bit buggy and "not EXPERIMENTAL" is bugfree but slow + // when rendering clouds or other density layers (GraphicContext::drawAlphaMap). + extern const bool EXPERIMENTAL; + +#ifdef HAVE_OPENGL + // Cache for GL state, call gl only if necessary. GL optimisations. + struct GLState + { + static const bool verbose = false; + bool _doBlend; + bool _doTexture; + bool _doScissor; + GLint _texture; + GLenum _sfactor, _dfactor; + bool isTextureSRectangle; + bool useATIWorkaround; + unsigned alocatedTextureCount; + + GLState(void) + { + resetCache(); + isTextureSRectangle = false; + useATIWorkaround = false; + alocatedTextureCount = 0; + } + + void resetCache(void) + { + _doBlend = false; + _doTexture = false; + _doScissor = false; + _texture = -1; + _sfactor = 0xffffffff; + _dfactor = 0xffffffff; + } + + void checkExtensions(void); + bool doBlend(bool on); + bool doTexture(bool on); + void setTexture(int tex); + bool doScissor(bool on); + void blendFunc(GLenum sfactor, GLenum dfactor); + }; + + extern GLState glState; +#endif // HAVE_OPENGL +} diff --git a/libgag/src/KeyPress.cpp b/libgag/src/KeyPress.cpp index 228891f0e..3b78fd2e0 100644 --- a/libgag/src/KeyPress.cpp +++ b/libgag/src/KeyPress.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "KeyPress.h" #include "Toolkit.h" diff --git a/libgag/src/SConscript b/libgag/src/SConscript index 7e227754f..187205868 100644 --- a/libgag/src/SConscript +++ b/libgag/src/SConscript @@ -1,13 +1,15 @@ libgag_sources = Split(""" BinaryStream.cpp CursorManager.cpp FileManager.cpp FormatableString.cpp -GraphicContext.cpp GUIAnimation.cpp GUIBase.cpp GUIButton.cpp +GraphicContext.cpp GraphicContextDraw.cpp GraphicContextCompound.cpp +DrawableSurface.cpp DrawableSurfaceDraw.cpp DrawableSurfaceCompound.cpp +GUIAnimation.cpp GUIBase.cpp GUIButton.cpp GUIFileList.cpp GUIKeySelector.cpp GUIList.cpp GUIMessageBox.cpp GUINumber.cpp GUIRatio.cpp GUISelector.cpp GUIStyle.cpp GUITextArea.cpp GUIText.cpp GUITextInput.cpp GUIImage.cpp GUIProgressBar.cpp KeyPress.cpp Sprite.cpp StreamBackend.cpp Stream.cpp StreamFilter.cpp StringTable.cpp SupportFunctions.cpp TextStream.cpp Toolkit.cpp TrueTypeFont.cpp win32_dirent.cpp -GUITabScreen.cpp GUITabScreenWindow.cpp TextSort.cpp GUICheckList.cpp +GUITabScreen.cpp GUITabScreenWindow.cpp TextSort.cpp GUICheckList.cpp """) libgag_just_server = Split(""" @@ -33,7 +35,7 @@ Import("env") Import("PackTar") import os -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".cpp") != -1: PackTar(env["TARFILE"], file) diff --git a/libgag/src/Sprite.cpp b/libgag/src/Sprite.cpp index ef89d1b8c..b18c71b04 100644 --- a/libgag/src/Sprite.cpp +++ b/libgag/src/Sprite.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include @@ -132,6 +116,8 @@ namespace GAGCore bool Sprite::createTextureAtlas() { #ifdef HAVE_OPENGL + if (!Toolkit::gc || !(Toolkit::gc->getOptionFlags() & GraphicContext::USEGPU)) + return false; #ifdef DEBUG_SPRITE_NOT_DRAWN sprites.push_back(this); #endif diff --git a/libgag/src/Stream.cpp b/libgag/src/Stream.cpp index b2843ef3e..1c5bf1e24 100644 --- a/libgag/src/Stream.cpp +++ b/libgag/src/Stream.cpp @@ -1,83 +1,67 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charri�e - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include - -namespace GAGCore -{ - OutputLineStream::OutputLineStream(StreamBackend *backend) - { - this->backend = backend; - } - - OutputLineStream::~OutputLineStream() - { - delete backend; - } - - InputLineStream::InputLineStream(StreamBackend *backend) - { - this->backend = backend; - } - - InputLineStream::~InputLineStream() - { - delete backend; - } - - void OutputLineStream::writeLine(const std::string &s) - { - backend->write(s.c_str(), s.length()); - backend->putc('\n'); - } - - void OutputLineStream::writeLine(const char *s) - { - backend->write(s, strlen(s)); - backend->putc('\n'); - } - - std::string InputLineStream::readLine() - { - std::string s; - while (1) - { - int c = backend->getChar(); - if(c=='\r') - continue; - if ((c >= 0) && (c != '\n')) - s += c; - else - break; - } - return s; - } - - bool OutputLineStream::isEndOfStream(void) - { - return backend->isEndOfStream(); - } - - bool InputLineStream::isEndOfStream(void) - { - return backend->isEndOfStream(); - } -} +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include + +namespace GAGCore +{ + OutputLineStream::OutputLineStream(StreamBackend *backend) + { + this->backend = backend; + } + + OutputLineStream::~OutputLineStream() + { + delete backend; + } + + InputLineStream::InputLineStream(StreamBackend *backend) + { + this->backend = backend; + } + + InputLineStream::~InputLineStream() + { + delete backend; + } + + void OutputLineStream::writeLine(const std::string &s) + { + backend->write(s.c_str(), s.length()); + backend->putc('\n'); + } + + void OutputLineStream::writeLine(const char *s) + { + backend->write(s, strlen(s)); + backend->putc('\n'); + } + + std::string InputLineStream::readLine() + { + std::string s; + while (1) + { + int c = backend->getChar(); + if(c=='\r') + continue; + if ((c >= 0) && (c != '\n')) + s += c; + else + break; + } + return s; + } + + bool OutputLineStream::isEndOfStream(void) + { + return backend->isEndOfStream(); + } + + bool InputLineStream::isEndOfStream(void) + { + return backend->isEndOfStream(); + } +} diff --git a/libgag/src/StreamBackend.cpp b/libgag/src/StreamBackend.cpp index 58a12c04c..22baae4f5 100644 --- a/libgag/src/StreamBackend.cpp +++ b/libgag/src/StreamBackend.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include @@ -32,12 +16,20 @@ namespace GAGCore if(isRead && isValid()) { gzFile fp = gzopen(file.c_str(), "rb"); - while(!gzeof(fp)) + if (fp == NULL) + { + std::cerr << "ZLibStreamBackend: failed to open " << file << " for reading" << std::endl; + return; + } + unsigned char b[1024]; + int amount; + while ((amount = gzread(fp, b, sizeof(b))) > 0) { - unsigned char b[1024]; - long ammount = gzread(fp, b, 1024); - buffer->write(b, ammount); + buffer->write(b, amount); } + if (amount < 0) + std::cerr << "ZLibStreamBackend: error reading " << file << std::endl; + gzclose(fp); buffer->seekFromStart(0); } } @@ -50,8 +42,16 @@ namespace GAGCore long size = buffer->getPosition(); buffer->seekFromStart(0); gzFile fp = gzopen(file.c_str(), "wb9"); - gzwrite(fp, buffer->getBuffer(), size); - gzclose(fp); + if (fp == NULL) + { + std::cerr << "ZLibStreamBackend: failed to open " << file << " for writing" << std::endl; + return; + } + int written = gzwrite(fp, buffer->getBuffer(), size); + if (written == 0) + std::cerr << "ZLibStreamBackend: error writing " << file << std::endl; + if (gzclose(fp) != Z_OK) + std::cerr << "ZLibStreamBackend: error closing " << file << std::endl; } } diff --git a/libgag/src/StreamFilter.cpp b/libgag/src/StreamFilter.cpp index 11dd4bf3e..aea39420c 100644 --- a/libgag/src/StreamFilter.cpp +++ b/libgag/src/StreamFilter.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière #include #include @@ -42,7 +26,9 @@ namespace GAGCore delete stream; // decompress - uncompress(&dest[0], &destLength, &source[0], compressedLength); + int zret = uncompress(&dest[0], &destLength, &source[0], compressedLength); + if (zret != Z_OK) + std::cerr << "CompressedInputStreamBackendFilter: uncompress failed with error " << zret << std::endl; assert(destLength == uncompressedLength); this->write(&dest[0], uncompressedLength); @@ -67,7 +53,9 @@ namespace GAGCore std::valarray dest(compressedLength); this->read(&source[0], uncompressedLength); - compress(&dest[0], (uLongf *)&compressedLength, &source[0], uncompressedLength); + int zret = compress2(&dest[0], (uLongf *)&compressedLength, &source[0], uncompressedLength, Z_DEFAULT_COMPRESSION); + if (zret != Z_OK) + std::cerr << "CompressedOutputStreamBackendFilter: compress2 failed with error " << zret << std::endl; std::cout << "Compressing " << uncompressedLength << " bytes into " << compressedLength << " bytes." << std::endl; BinaryOutputStream *stream = new BinaryOutputStream(backend); diff --git a/libgag/src/StringTable.cpp b/libgag/src/StringTable.cpp index 48a735aeb..158a36f3e 100644 --- a/libgag/src/StringTable.cpp +++ b/libgag/src/StringTable.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/SupportFunctions.cpp b/libgag/src/SupportFunctions.cpp index a40f96080..e5e9f886c 100644 --- a/libgag/src/SupportFunctions.cpp +++ b/libgag/src/SupportFunctions.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/TextSort.cpp b/libgag/src/TextSort.cpp index 9c8fd9657..ab3cbfc12 100644 --- a/libgag/src/TextSort.cpp +++ b/libgag/src/TextSort.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2008 Stephane Magnenat & Luc-Olivier de Charrière #include "TextSort.h" diff --git a/libgag/src/TextStream.cpp b/libgag/src/TextStream.cpp index 6847f18a7..cdff55070 100644 --- a/libgag/src/TextStream.cpp +++ b/libgag/src/TextStream.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/Toolkit.cpp b/libgag/src/Toolkit.cpp index 6de1c2c7d..4bc34af5c 100644 --- a/libgag/src/Toolkit.cpp +++ b/libgag/src/Toolkit.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include diff --git a/libgag/src/TrueTypeFont.cpp b/libgag/src/TrueTypeFont.cpp index 6cd995e58..566fef033 100644 --- a/libgag/src/TrueTypeFont.cpp +++ b/libgag/src/TrueTypeFont.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "TrueTypeFont.h" #include diff --git a/libusl/SConscript b/libusl/SConscript index e27dae88f..1d12809ac 100644 --- a/libusl/SConscript +++ b/libusl/SConscript @@ -1,7 +1,7 @@ Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: PackTar(env["TARFILE"], "SConscript") SConscript("src/SConscript") SConscript("test/SConscript") diff --git a/libusl/src/SConscript b/libusl/src/SConscript index 776bf4555..59dc46f17 100644 --- a/libusl/src/SConscript +++ b/libusl/src/SConscript @@ -1,14 +1,14 @@ usl_sources = Split(""" -code.cpp debug.cpp interpreter.cpp lexer.cpp -memory.cpp parser.cpp position.cpp token.cpp -tokenizer.cpp tree.cpp types.cpp usl.cpp +code.cpp debug.cpp interpreter.cpp lexer.cpp +parser.cpp position.cpp token.cpp +tree.cpp types.cpp usl.cpp """) Import("env") Import("PackTar") import os -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".cpp") != -1 or file.find(".h") != -1: PackTar(env["TARFILE"], file) diff --git a/libusl/src/code.cpp b/libusl/src/code.cpp index 6772de159..d48218b26 100644 --- a/libusl/src/code.cpp +++ b/libusl/src/code.cpp @@ -1,9 +1,8 @@ #include "code.h" -#include "native.h" #include "interpreter.h" #include "tree.h" #include "debug.h" -#include "error.h" +#include "position.h" #include "usl.h" #include diff --git a/libusl/src/code.h b/libusl/src/code.h index f7a4a039e..4d0a4ed0e 100644 --- a/libusl/src/code.h +++ b/libusl/src/code.h @@ -1,5 +1,4 @@ -#ifndef CODE_H -#define CODE_H +#pragma once #include #include @@ -117,4 +116,3 @@ struct CreateCode: Code typename ThunkType::Prototype* prototype; }; -#endif // ndef CODE_H diff --git a/libusl/src/debug.h b/libusl/src/debug.h index bb8ba24c8..368078a7b 100644 --- a/libusl/src/debug.h +++ b/libusl/src/debug.h @@ -1,5 +1,4 @@ -#ifndef DEBUG_H -#define DEBUG_H +#pragma once #include "position.h" @@ -32,4 +31,3 @@ struct DebugInfo std::string unmangle(const std::string& name); -#endif // ndef DEBUG_H diff --git a/libusl/src/error.h b/libusl/src/error.h deleted file mode 100644 index 4d21a3a97..000000000 --- a/libusl/src/error.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ERROR_H -#define ERROR_H - -#include "position.h" - -#include - -struct Exception: std::runtime_error -{ - Exception(const Position& position, const std::string& message): std::runtime_error(message), position(position) {} - ~Exception() throw() {} - Position position; -}; - -#endif // ndef ERROR_H diff --git a/libusl/src/interpreter.h b/libusl/src/interpreter.h index f86252243..2f75b1092 100644 --- a/libusl/src/interpreter.h +++ b/libusl/src/interpreter.h @@ -1,5 +1,4 @@ -#ifndef INTERPRETER_H -#define INTERPRETER_H +#pragma once #include #include @@ -53,4 +52,3 @@ struct Thread void markForGC(); }; -#endif // ndef INTERPRETER_H diff --git a/libusl/src/lexer.cpp b/libusl/src/lexer.cpp index 3d79b4292..64c08f90c 100644 --- a/libusl/src/lexer.cpp +++ b/libusl/src/lexer.cpp @@ -1,5 +1,5 @@ #include "lexer.h" -#include "error.h" +#include "position.h" #include #include @@ -8,6 +8,36 @@ using std::ostringstream; using std::string; using std::endl; +Tokenizer::Tokenizer(const Token::Type *tokenTypes, const size_t tokenTypesSize, + const std::string& filename, const char* text): + tokenTypes(tokenTypes), + tokenTypesSize(tokenTypesSize), + text(text), + position(filename, 1, 1) +{ } + +const Token Tokenizer::next() +{ + const Token::Type* type = NULL; + ssize_t length = -1; + for (size_t i = 0; i < tokenTypesSize; i++) + { + const Token::Type& newType = tokenTypes[i]; + ssize_t newLength = newType.match(text); + if(newLength > length) + { + type = &newType; + length = newLength; + } + } + if (length == -1) + throw Exception(position, "syntax error"); + Token token(position, type, text, length); + position.move(text, length); + text += length; + return token; +} + const Token::Type Lexer::tokenTypes[] = { Token::Type(SPACE, "a space", "[[:blank:]]+"), diff --git a/libusl/src/lexer.h b/libusl/src/lexer.h index d578657dd..4eb6f42e4 100644 --- a/libusl/src/lexer.h +++ b/libusl/src/lexer.h @@ -1,7 +1,19 @@ -#ifndef LEXER_H -#define LEXER_H +#pragma once -#include "tokenizer.h" +#include "token.h" + +class Tokenizer +{ +public: + Tokenizer(const Token::Type *tokenTypes, const size_t tokenTypesSize, const std::string& filename, const char* text); + const Token next(); + +private: + const Token::Type *tokenTypes; + const size_t tokenTypesSize; + const char* text; + Position position; +}; class Lexer: Tokenizer { @@ -73,4 +85,3 @@ class Lexer: Tokenizer Token token; }; -#endif // ndef LEXER_H diff --git a/libusl/src/memory.cpp b/libusl/src/memory.cpp deleted file mode 100644 index 46fdb5c5f..000000000 --- a/libusl/src/memory.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "memory.h" -#include "interpreter.h" -#include "types.h" - -using std::for_each; -using std::mem_fun; - -void Heap::collectGarbage() -{ - // filter copy, delete unrefs - Values marked; - for (size_t i = 0; i < values.size(); i++) - { - if (values[i]->marked) - marked.push_back(values[i]); - else - delete values[i]; - } - - // clean heap - swap(values, marked); - for_each(values.begin(), values.end(), mem_fun(&Value::clearGCMark)); -} diff --git a/libusl/src/memory.h b/libusl/src/memory.h deleted file mode 100644 index efec9834c..000000000 --- a/libusl/src/memory.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef MEMORY_H -#define MEMORY_H - -#include - -struct Value; - -struct Heap -{ - typedef std::vector Values; - - Values values; - - void collectGarbage(); -}; - -#endif // ndef MEMORY_H diff --git a/libusl/src/native.h b/libusl/src/native.h index 876cbf4a3..bb1bc5a1f 100644 --- a/libusl/src/native.h +++ b/libusl/src/native.h @@ -1,5 +1,4 @@ -#ifndef NATIVE_H -#define NATIVE_H +#pragma once #include "usl.h" #include "interpreter.h" @@ -30,7 +29,7 @@ template struct NativeValuePrototype: Prototype { NativeValuePrototype(): - Prototype(heap) + Prototype(nullptr) // heap is set later by NativeValue ctor; can't use the inherited member here, it's not constructed yet { } @@ -315,4 +314,3 @@ inline void NativeValuePrototype::initialize() addMethod("!=", boost::lambda::_1 != boost::lambda::_2); } -#endif // ndef NATIVE_H diff --git a/libusl/src/parser.cpp b/libusl/src/parser.cpp index e11667b8b..68e5b99e5 100644 --- a/libusl/src/parser.cpp +++ b/libusl/src/parser.cpp @@ -1,6 +1,6 @@ #include "parser.h" #include "native.h" -#include "error.h" +#include "position.h" #include using std::string; diff --git a/libusl/src/parser.h b/libusl/src/parser.h index 379463197..51a295d42 100644 --- a/libusl/src/parser.h +++ b/libusl/src/parser.h @@ -1,5 +1,4 @@ -#ifndef PARSER_H -#define PARSER_H +#pragma once #include "lexer.h" #include "tree.h" @@ -35,4 +34,3 @@ struct Parser: Lexer Heap* heap; }; -#endif // ndef PARSER_H diff --git a/libusl/src/position.h b/libusl/src/position.h index 73c960908..d291c527a 100644 --- a/libusl/src/position.h +++ b/libusl/src/position.h @@ -1,20 +1,20 @@ -#ifndef POSITION_H -#define POSITION_H +#pragma once #include #include +#include struct Position { std::string filename; size_t line; size_t column; - + Position(): filename(), line(0), column(0) {} Position(const std::string& filename, size_t line, size_t column): filename(filename), line(line), column(column) {} - + bool operator<(const Position& that) const; - + void operator+=(char c); void move(const std::string text, size_t length); }; @@ -24,4 +24,10 @@ inline std::ostream& operator<<(std::ostream& stream, const Position& position) return stream << position.filename << ":" << position.line << ":" << position.column; } -#endif // ndef POSITION_H +struct Exception: std::runtime_error +{ + Exception(const Position& position, const std::string& message): std::runtime_error(message), position(position) {} + ~Exception() throw() {} + Position position; +}; + diff --git a/libusl/src/token.h b/libusl/src/token.h index ea8e879d5..8f692c1a6 100644 --- a/libusl/src/token.h +++ b/libusl/src/token.h @@ -1,5 +1,4 @@ -#ifndef TOKEN_H -#define TOKEN_H +#pragma once #include "position.h" #include @@ -41,4 +40,3 @@ struct Token size_t length; }; -#endif // ndef TOKEN_H diff --git a/libusl/src/tokenizer.cpp b/libusl/src/tokenizer.cpp deleted file mode 100644 index e4e2e7961..000000000 --- a/libusl/src/tokenizer.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "tokenizer.h" -#include "error.h" - -Tokenizer::Tokenizer(const Token::Type *tokenTypes, const size_t tokenTypesSize, - const std::string& filename, const char* text): - tokenTypes(tokenTypes), - tokenTypesSize(tokenTypesSize), - text(text), - position(filename, 1, 1) -{ } - -const Token Tokenizer::next() -{ - const Token::Type* type = NULL; - ssize_t length = -1; - for (size_t i = 0; i < tokenTypesSize; i++) - { - const Token::Type& newType = tokenTypes[i]; - ssize_t newLength = newType.match(text); - if(newLength > length) - { - type = &newType; - length = newLength; - } - } - if (length == -1) - throw Exception(position, "syntax error"); - Token token(position, type, text, length); - position.move(text, length); - text += length; - return token; -} diff --git a/libusl/src/tokenizer.h b/libusl/src/tokenizer.h deleted file mode 100644 index 841ead1b2..000000000 --- a/libusl/src/tokenizer.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef TOKENIZER_H -#define TOKENIZER_H - -#include "token.h" -#include - -class Tokenizer -{ -public: - Tokenizer(const Token::Type *tokenTypes, const size_t tokenTypesSize, const std::string& filename, const char* text); - const Token next(); - -private: - const Token::Type *tokenTypes; - const size_t tokenTypesSize; - const char* text; - Position position; -}; - -#endif // ndef TOKENIZER_H diff --git a/libusl/src/tree.cpp b/libusl/src/tree.cpp index aeed470a3..0bf190ddf 100644 --- a/libusl/src/tree.cpp +++ b/libusl/src/tree.cpp @@ -1,7 +1,7 @@ #include "tree.h" #include "code.h" #include "debug.h" -#include "error.h" +#include "position.h" #include "types.h" #include @@ -10,14 +10,14 @@ using std::ostringstream; using std::endl; -void Node::generate(ThunkPrototype* thunk, DebugInfo* debug, Code* code) +void Node::emit(ThunkPrototype* thunk, CodeGen& cg, Code* code) { thunk->body.push_back(code); - - if (debug != 0) + + if (cg.debug != 0) { size_t address = thunk->body.size(); - ThunkDebugInfo* scopeDebug = debug->get(thunk); + ThunkDebugInfo* scopeDebug = cg.debug->get(thunk); ThunkDebugInfo::Source2Address::iterator it = scopeDebug->source2Address.find(position); if (it != scopeDebug->source2Address.end()) { @@ -46,15 +46,15 @@ void Node::dumpSpecific(std::ostream &stream, unsigned indent) const } -void ExpressionNode::generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void ExpressionNode::generate(ScopePrototype* scope, CodeGen& cg) { - generate(static_cast(scope), debug, heap); + generate(static_cast(scope), cg); } -void ConstNode::generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap) +void ConstNode::generate(ThunkPrototype* thunk, CodeGen& cg) { - Node::generate(thunk, debug, new ConstCode(value)); + emit(thunk, cg, new ConstCode(value)); } void ConstNode::dumpSpecific(std::ostream &stream, unsigned indent) const @@ -70,11 +70,11 @@ SelectNode::~SelectNode() delete receiver; } -void SelectNode::generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap) +void SelectNode::generate(ThunkPrototype* thunk, CodeGen& cg) { - receiver->generate(thunk, debug, heap); - Node::generate(thunk, debug, new SelectCode(name)); - Node::generate(thunk, debug, new EvalCode()); + receiver->generate(thunk, cg); + emit(thunk, cg, new SelectCode(name)); + emit(thunk, cg, new EvalCode()); } void SelectNode::dumpSpecific(std::ostream &stream, unsigned indent) const @@ -91,15 +91,15 @@ ApplyNode::~ApplyNode() delete argument; } -void ApplyNode::generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap) +void ApplyNode::generate(ThunkPrototype* thunk, CodeGen& cg) { - ThunkPrototype* arg = new ThunkPrototype(heap, thunk); - argument->generate(arg, debug, heap); - - function->generate(thunk, debug, heap); - Node::generate(thunk, debug, new ThunkCode()); - Node::generate(thunk, debug, new CreateCode(arg)); - Node::generate(thunk, debug, new ApplyCode()); + ThunkPrototype* arg = new ThunkPrototype(cg.heap, thunk); + argument->generate(arg, cg); + + function->generate(thunk, cg); + emit(thunk, cg, new ThunkCode()); + emit(thunk, cg, new CreateCode(arg)); + emit(thunk, cg, new ApplyCode()); } void ApplyNode::dumpSpecific(std::ostream &stream, unsigned indent) const @@ -115,33 +115,33 @@ DecNode::~DecNode() delete body; } -void DecNode::declare(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void DecNode::declare(ScopePrototype* scope, CodeGen& cg) { - scope->members[name] = new ThunkPrototype(heap, scope); - + scope->members[name] = new ThunkPrototype(cg.heap, scope); + if (type == VAR) { // TODO: setter } } -void DecNode::generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap) +void DecNode::generate(ThunkPrototype* thunk, CodeGen& cg) { ScopePrototype* scope = dynamic_cast(thunk); assert(scope); - generate(scope, debug, heap); + generate(scope, cg); } -void DecNode::generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void DecNode::generate(ScopePrototype* scope, CodeGen& cg) { switch (type) { case DEF: { ThunkPrototype* def = scope->members[name]; assert(def); - body->generate(def, debug, heap); + body->generate(def, cg); - Node::generate(scope, debug, new ConstCode(&nil)); + emit(scope, cg, new ConstCode(&nil)); } break; case AUTO: // TODO: optimize constant AUTO @@ -150,15 +150,15 @@ void DecNode::generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap) { size_t index = scope->locals.size(); scope->locals.push_back(name); - - body->generate(scope, debug, heap); - Node::generate(scope, debug, new DupCode(0)); - Node::generate(scope, debug, new ValCode(index)); - + + body->generate(scope, cg); + emit(scope, cg, new DupCode(0)); + emit(scope, cg, new ValCode(index)); + ThunkPrototype* getter = scope->members[name]; - Node::generate(getter, debug, new ThunkCode()); - Node::generate(getter, debug, new ParentCode()); - Node::generate(getter, debug, new ValRefCode(index)); + emit(getter, cg, new ThunkCode()); + emit(getter, cg, new ParentCode()); + emit(getter, cg, new ValRefCode(index)); } break; } @@ -189,18 +189,18 @@ void BlockNode::dumpSpecific(std::ostream &stream, unsigned indent) const } } -void BlockNode::generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap) +void BlockNode::generate(ThunkPrototype* thunk, CodeGen& cg) { - ScopePrototype* scope = new ScopePrototype(heap, thunk); + ScopePrototype* scope = new ScopePrototype(cg.heap, thunk); scope->members["this"] = thisMember(scope); - generateMembers(scope, debug, heap); - Node::generate(thunk, debug, new ThunkCode()); - Node::generate(thunk, debug, new CreateCode(scope)); - Node::generate(thunk, debug, new EvalCode()); + generateMembers(scope, cg); + emit(thunk, cg, new ThunkCode()); + emit(thunk, cg, new CreateCode(scope)); + emit(thunk, cg, new EvalCode()); } -void ExecutionBlock::generateMembers(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void ExecutionBlock::generateMembers(ScopePrototype* scope, CodeGen& cg) { if (!elements.empty()) { @@ -208,32 +208,32 @@ void ExecutionBlock::generateMembers(ScopePrototype* scope, DebugInfo* debug, He { DecNode* dec = dynamic_cast(*it); if (dec != 0) - dec->declare(scope, debug, heap); + dec->declare(scope, cg); } - + for (Elements::const_iterator it = elements.begin(); it != elements.end() - 1; ++it) { Node* element = *it; - element->generate(scope, debug, heap); - Node::generate(scope, debug, new PopCode()); + element->generate(scope, cg); + emit(scope, cg, new PopCode()); } - - elements.back()->generate(scope, debug, heap); + + elements.back()->generate(scope, cg); } else { - Node::generate(scope, debug, new ConstCode(&nil)); + emit(scope, cg, new ConstCode(&nil)); } } -void RecordBlock::generateMembers(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void RecordBlock::generateMembers(ScopePrototype* scope, CodeGen& cg) { for (Elements::const_iterator it = elements.begin(); it != elements.end(); ++it) { DecNode* dec = dynamic_cast(*it); if (dec != 0) - dec->declare(scope, debug, heap); + dec->declare(scope, cg); } for (Elements::const_iterator it = elements.begin(); it != elements.end(); ++it) @@ -241,26 +241,26 @@ void RecordBlock::generateMembers(ScopePrototype* scope, DebugInfo* debug, Heap* size_t index = scope->locals.size(); Node* element = *it; - element->generate(scope, debug, heap); + element->generate(scope, cg); ThunkPrototype* getter; DecNode* dec = dynamic_cast(element); if (dec) { - Node::generate(scope, debug, new PopCode()); - + emit(scope, cg, new PopCode()); + getter = scope->members[dec->name]; } else { scope->locals.push_back(""); - Node::generate(scope, debug, new ValCode(index)); + emit(scope, cg, new ValCode(index)); - getter = new ThunkPrototype(heap, scope); - Node::generate(getter, debug, new ThunkCode()); - Node::generate(getter, debug, new ParentCode()); - Node::generate(getter, debug, new ValRefCode(index)); + getter = new ThunkPrototype(cg.heap, scope); + emit(getter, cg, new ThunkCode()); + emit(getter, cg, new ParentCode()); + emit(getter, cg, new ValRefCode(index)); } stringstream str; @@ -268,14 +268,14 @@ void RecordBlock::generateMembers(ScopePrototype* scope, DebugInfo* debug, Heap* scope->members[str.str()] = getter; } - Node::generate(scope, debug, new ThunkCode()); + emit(scope, cg, new ThunkCode()); } -void DefLookupNode::generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap) +void DefLookupNode::generate(ThunkPrototype* thunk, CodeGen& cg) { - Node::generate(thunk, debug, new ThunkCode()); - + emit(thunk, cg, new ThunkCode()); + ThunkPrototype* member; Prototype* prototype = thunk; while (true) @@ -283,14 +283,14 @@ void DefLookupNode::generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap member = prototype->lookup(name); if (member != 0) break; - + ThunkPrototype* t = dynamic_cast(prototype); assert(t != 0); - - Node::generate(thunk, debug, new ParentCode()); - + + emit(thunk, cg, new ParentCode()); + prototype = t->outer; - + if (prototype == 0) { ostringstream message; @@ -298,9 +298,9 @@ void DefLookupNode::generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap throw Exception(position, message.str()); } } - - Node::generate(thunk, debug, new CreateCode(member)); - Node::generate(thunk, debug, new EvalCode()); + + emit(thunk, cg, new CreateCode(member)); + emit(thunk, cg, new EvalCode()); } void DefLookupNode::dumpSpecific(std::ostream &stream, unsigned indent) const @@ -310,33 +310,33 @@ void DefLookupNode::dumpSpecific(std::ostream &stream, unsigned indent) const } -void IgnorePatternNode::generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void IgnorePatternNode::generate(ScopePrototype* scope, CodeGen& cg) { - Node::generate(scope, debug, new PopCode()); + emit(scope, cg, new PopCode()); } -void NilPatternNode::generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void NilPatternNode::generate(ScopePrototype* scope, CodeGen& cg) { // TODO: check we really got nil - Node::generate(scope, debug, new PopCode()); + emit(scope, cg, new PopCode()); } -void ValPatternNode::generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void ValPatternNode::generate(ScopePrototype* scope, CodeGen& cg) { size_t index = scope->locals.size(); scope->locals.push_back(name); - - Node::generate(scope, debug, new EvalCode()); - Node::generate(scope, debug, new ValCode(index)); - - ScopePrototype* getter = new ScopePrototype(heap, scope); + + emit(scope, cg, new EvalCode()); + emit(scope, cg, new ValCode(index)); + + ScopePrototype* getter = new ScopePrototype(cg.heap, scope); scope->members[name] = getter; - - Node::generate(getter, debug, new ThunkCode()); - Node::generate(getter, debug, new ParentCode()); - Node::generate(getter, debug, new ValRefCode(index)); + + emit(getter, cg, new ThunkCode()); + emit(getter, cg, new ParentCode()); + emit(getter, cg, new ValRefCode(index)); } void ValPatternNode::dumpSpecific(std::ostream &stream, unsigned indent) const @@ -346,20 +346,20 @@ void ValPatternNode::dumpSpecific(std::ostream &stream, unsigned indent) const } -void DefPatternNode::generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void DefPatternNode::generate(ScopePrototype* scope, CodeGen& cg) { size_t index = scope->members.size(); scope->locals.push_back(name); - - Node::generate(scope, debug, new ValCode(index)); - - ScopePrototype* getter = new ScopePrototype(heap, scope); + + emit(scope, cg, new ValCode(index)); + + ScopePrototype* getter = new ScopePrototype(cg.heap, scope); scope->members[name] = getter; - - Node::generate(getter, debug, new ThunkCode()); - Node::generate(getter, debug, new ParentCode()); - Node::generate(getter, debug, new ValRefCode(index)); - Node::generate(getter, debug, new EvalCode()); + + emit(getter, cg, new ThunkCode()); + emit(getter, cg, new ParentCode()); + emit(getter, cg, new ValRefCode(index)); + emit(getter, cg, new EvalCode()); } void DefPatternNode::dumpSpecific(std::ostream &stream, unsigned indent) const @@ -377,21 +377,21 @@ TuplePatternNode::~TuplePatternNode() } } -void TuplePatternNode::generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap) +void TuplePatternNode::generate(ScopePrototype* scope, CodeGen& cg) { - Node::generate(scope, debug, new EvalCode()); + emit(scope, cg, new EvalCode()); int index = 0; for (Members::iterator it = members.begin(); it != members.end(); ++it) { stringstream str; str << index; - Node::generate(scope, debug, new DupCode(0)); - Node::generate(scope, debug, new SelectCode(str.str())); - (*it)->generate(scope, debug, heap); + emit(scope, cg, new DupCode(0)); + emit(scope, cg, new SelectCode(str.str())); + (*it)->generate(scope, cg); ++index; } - Node::generate(scope, debug, new PopCode()); + emit(scope, cg, new PopCode()); } void TuplePatternNode::dumpSpecific(std::ostream &stream, unsigned indent) const @@ -410,18 +410,18 @@ FunNode::~FunNode() delete body; } -void FunNode::generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap) +void FunNode::generate(ThunkPrototype* thunk, CodeGen& cg) { - ScopePrototype* scope = new ScopePrototype(heap, thunk); - arg->generate(scope, debug, heap); + ScopePrototype* scope = new ScopePrototype(cg.heap, thunk); + arg->generate(scope, cg); BlockNode* block = dynamic_cast(body); if (block == 0) - body->generate(scope, debug, heap); + body->generate(scope, cg); else - block->generateMembers(scope, debug, heap); - - Node::generate(thunk, debug, new ThunkCode()); - Node::generate(thunk, debug, new CreateCode(scope)); + block->generateMembers(scope, cg); + + emit(thunk, cg, new ThunkCode()); + emit(thunk, cg, new CreateCode(scope)); } void FunNode::dumpSpecific(std::ostream &stream, unsigned indent) const diff --git a/libusl/src/tree.h b/libusl/src/tree.h index 396fdb33e..75c248637 100644 --- a/libusl/src/tree.h +++ b/libusl/src/tree.h @@ -1,5 +1,4 @@ -#ifndef TREE_H -#define TREE_H +#pragma once #include "position.h" @@ -13,18 +12,24 @@ struct Code; struct Value; struct Heap; +struct CodeGen +{ + DebugInfo* debug; + Heap* heap; +}; + struct Node { Position position; - + Node(const Position& position): position(position) {} virtual ~Node() {} - - void generate(ThunkPrototype* scope, DebugInfo* debug, Code* code); - virtual void generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap) = 0; - + + void emit(ThunkPrototype* thunk, CodeGen& cg, Code* code); + virtual void generate(ScopePrototype* scope, CodeGen& cg) = 0; + void dump(std::ostream &stream, unsigned indent = 0) const; virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; }; @@ -34,9 +39,9 @@ struct ExpressionNode: Node ExpressionNode(const Position& position): Node(position) {} - - virtual void generate(ScopePrototype* thunk, DebugInfo* debug, Heap* heap); - virtual void generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap) = 0; + + virtual void generate(ScopePrototype* thunk, CodeGen& cg); + virtual void generate(ThunkPrototype* thunk, CodeGen& cg) = 0; }; struct ConstNode: ExpressionNode @@ -45,10 +50,10 @@ struct ConstNode: ExpressionNode ExpressionNode(position), value(value) {} - - virtual void generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap); + + virtual void generate(ThunkPrototype* thunk, CodeGen& cg); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - + Value* value; }; @@ -59,11 +64,11 @@ struct SelectNode: ExpressionNode receiver(receiver), name(name) {} - + virtual ~SelectNode(); - virtual void generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap); + virtual void generate(ThunkPrototype* thunk, CodeGen& cg); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - + ExpressionNode* receiver; const std::string name; }; @@ -75,11 +80,11 @@ struct ApplyNode: ExpressionNode function(function), argument(argument) {} - + virtual ~ApplyNode(); - virtual void generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap); + virtual void generate(ThunkPrototype* thunk, CodeGen& cg); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - + ExpressionNode* function; ExpressionNode* argument; }; @@ -92,20 +97,20 @@ struct DecNode: Node VAL, VAR, }; - + DecNode(const Position& position, Type type, const std::string& name, ExpressionNode* body): Node(position), type(type), name(name), body(body) {} - + virtual ~DecNode(); - void declare(ScopePrototype* scope, DebugInfo* debug, Heap* heap); - virtual void generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap); - virtual void generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap); + void declare(ScopePrototype* scope, CodeGen& cg); + virtual void generate(ThunkPrototype* thunk, CodeGen& cg); + virtual void generate(ScopePrototype* scope, CodeGen& cg); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - + Type type; std::string name; ExpressionNode* body; @@ -114,16 +119,16 @@ struct DecNode: Node struct BlockNode: ExpressionNode { typedef std::vector Elements; - + BlockNode(const Position& position): ExpressionNode(position) {} - + virtual ~BlockNode(); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - virtual void generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap); - virtual void generateMembers(ScopePrototype* scope, DebugInfo* debug, Heap* heap) = 0; - + virtual void generate(ThunkPrototype* thunk, CodeGen& cg); + virtual void generateMembers(ScopePrototype* scope, CodeGen& cg) = 0; + Elements elements; }; @@ -132,8 +137,8 @@ struct ExecutionBlock: BlockNode ExecutionBlock(const Position& position): BlockNode(position) {} - - virtual void generateMembers(ScopePrototype* scope, DebugInfo* debug, Heap* heap); + + virtual void generateMembers(ScopePrototype* scope, CodeGen& cg); }; struct RecordBlock: BlockNode @@ -141,8 +146,8 @@ struct RecordBlock: BlockNode RecordBlock(const Position& position): BlockNode(position) {} - - virtual void generateMembers(ScopePrototype* scope, DebugInfo* debug, Heap* heap); + + virtual void generateMembers(ScopePrototype* scope, CodeGen& cg); }; struct DefLookupNode: ExpressionNode @@ -151,10 +156,10 @@ struct DefLookupNode: ExpressionNode ExpressionNode(position), name(name) {} - - virtual void generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap); + + virtual void generate(ThunkPrototype* thunk, CodeGen& cg); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - + std::string name; }; @@ -170,8 +175,8 @@ struct IgnorePatternNode: PatternNode IgnorePatternNode(const Position& position): PatternNode(position) {} - - virtual void generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap); + + virtual void generate(ScopePrototype* scope, CodeGen& cg); }; struct NilPatternNode: PatternNode @@ -179,8 +184,8 @@ struct NilPatternNode: PatternNode NilPatternNode(const Position& position): PatternNode(position) {} - - virtual void generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap); + + virtual void generate(ScopePrototype* scope, CodeGen& cg); }; struct ValPatternNode: PatternNode @@ -189,10 +194,10 @@ struct ValPatternNode: PatternNode PatternNode(position), name(name) {} - - virtual void generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap); + + virtual void generate(ScopePrototype* scope, CodeGen& cg); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - + std::string name; }; @@ -202,25 +207,25 @@ struct DefPatternNode: PatternNode PatternNode(position), name(name) {} - - virtual void generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap); + + virtual void generate(ScopePrototype* scope, CodeGen& cg); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - + std::string name; }; struct TuplePatternNode: PatternNode { typedef std::vector Members; - + TuplePatternNode(const Position& position): PatternNode(position) {} - + virtual ~TuplePatternNode(); - virtual void generate(ScopePrototype* scope, DebugInfo* debug, Heap* heap); + virtual void generate(ScopePrototype* scope, CodeGen& cg); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - + Members members; }; @@ -231,13 +236,11 @@ struct FunNode: ExpressionNode arg(arg), body(body) {} - + virtual ~FunNode(); - virtual void generate(ThunkPrototype* thunk, DebugInfo* debug, Heap* heap); + virtual void generate(ThunkPrototype* thunk, CodeGen& cg); virtual void dumpSpecific(std::ostream &stream, unsigned indent) const; - + PatternNode* arg; ExpressionNode* body; }; - -#endif // ndef TREE_H diff --git a/libusl/src/types.cpp b/libusl/src/types.cpp index 9f13769f1..e23a2dacd 100644 --- a/libusl/src/types.cpp +++ b/libusl/src/types.cpp @@ -4,10 +4,31 @@ #include "interpreter.h" #include "debug.h" #include "usl.h" -#include "native.h" #include #include +#include + + +void Heap::collectGarbage() +{ + using std::for_each; + using std::mem_fn; + + // filter copy, delete unrefs + Values marked; + for (size_t i = 0; i < values.size(); i++) + { + if (values[i]->marked) + marked.push_back(values[i]); + else + delete values[i]; + } + + // clean heap + swap(values, marked); + for_each(values.begin(), values.end(), mem_fn(&Value::clearGCMark)); +} void Value::dump(std::ostream &stream) const diff --git a/libusl/src/types.h b/libusl/src/types.h index 600f7d6e7..c15109759 100644 --- a/libusl/src/types.h +++ b/libusl/src/types.h @@ -1,11 +1,7 @@ -#ifndef TYPES_H -#define TYPES_H - -#include "memory.h" +#pragma once #include #include -#include #include #include #include @@ -13,6 +9,17 @@ #include #include +struct Value; + +struct Heap +{ + typedef std::vector Values; + + Values values; + + void collectGarbage(); +}; + struct Prototype; struct Value { @@ -74,12 +81,11 @@ struct Prototype: Value transform(members.begin(), members.end(), ostream_iterator(stream, " "), [](auto& member) {return member.first; }); } - virtual void propagateMarkForGC() - { - using std::for_each; - for_each(members.begin(), members.end(), [this](auto& member) {dynamic_cast(member.second)->markForGC(); }); - } - + // Defined out-of-line below ThunkPrototype: the lambda dynamic_cast's a + // ThunkPrototype* (Members::value_type::second_type), which C++17+ requires + // to be a complete type at the point the body is parsed. + virtual void propagateMarkForGC(); + virtual ThunkPrototype* lookup(const std::string& name) const { Members::const_iterator method = members.find(name); @@ -114,6 +120,12 @@ struct ThunkPrototype: Prototype } }; +inline void Prototype::propagateMarkForGC() +{ + using std::for_each; + for_each(members.begin(), members.end(), [](auto& member) {dynamic_cast(member.second)->markForGC(); }); +} + struct Thunk: Value { typedef ThunkPrototype Prototype; @@ -163,8 +175,8 @@ struct Scope: Thunk virtual void propagateMarkForGC() { using std::for_each; - using std::mem_fun; - for_each(locals.begin(), locals.end(), mem_fun(&Value::markForGC)); + using std::mem_fn; + for_each(locals.begin(), locals.end(), mem_fn(&Value::markForGC)); } ScopePrototype* scopePrototype() const @@ -190,4 +202,3 @@ struct Function: MetaPrototype Function(Heap* heap, Prototype* prototype, Value* outer); }; -#endif // ndef TYPES_H diff --git a/libusl/src/usl.cpp b/libusl/src/usl.cpp index 0561566e1..b9463e1b8 100644 --- a/libusl/src/usl.cpp +++ b/libusl/src/usl.cpp @@ -1,7 +1,7 @@ #include "usl.h" #include "code.h" #include "parser.h" -#include "error.h" +#include "position.h" #include "interpreter.h" #include "native.h" #include @@ -249,7 +249,8 @@ Scope* Usl::compile(const std::string& name, std::istream& stream) #endif ScopePrototype* prototype = new ScopePrototype(&heap, root->prototype); - block.generateMembers(prototype, &debug, &heap); + CodeGen cg{&debug, &heap}; + block.generateMembers(prototype, cg); Scope* scope = new Scope(&heap, prototype, root.get()); return scope; diff --git a/libusl/src/usl.h b/libusl/src/usl.h index 99e8d4afe..8ac9a5cfb 100644 --- a/libusl/src/usl.h +++ b/libusl/src/usl.h @@ -1,7 +1,5 @@ -#ifndef USL_H -#define USL_H +#pragma once -#include "memory.h" #include "debug.h" #include "types.h" @@ -42,4 +40,3 @@ struct Usl friend struct Load; }; -#endif // ndef USL_H diff --git a/libusl/test/SConscript b/libusl/test/SConscript index 1e8de854a..eb45b9e3d 100644 --- a/libusl/test/SConscript +++ b/libusl/test/SConscript @@ -1,10 +1,13 @@ Import("env") Import("PackTar") import os - -if 'dist' or 'install' in COMMAND_LINE_TARGETS: + +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".usl") != -1: PackTar(env["TARFILE"], file) PackTar(env["TARFILE"], "SConscript") - + +env.Program("usl_runner", ["usl_runner.cpp"], + LIBS=["usl"], + LIBPATH=["#build/libusl/src"]) diff --git a/libusl/test/usl_runner.cpp b/libusl/test/usl_runner.cpp new file mode 100644 index 000000000..8f7242f51 --- /dev/null +++ b/libusl/test/usl_runner.cpp @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Standalone runner for libusl/test/*.usl fixtures. +// +// Wraps each script in `result := { }`, includes it via Usl, and +// prints the bound `result` value. Pointers from Value::dump are stripped +// before diffing two runs (use sed 's/0x[0-9a-f]*/PTR/g'). +// +// Use `-p ` (repeatable) to load a prelude via Usl::includeScript +// before each test. Preludes' top-level definitions become available in +// every subsequent test's scope. + +#include "usl.h" + +#include +#include +#include +#include +#include + +namespace { + +int runFile(const char* path, const std::vector& preludes) +{ + Usl usl; + + for (const char* prelude : preludes) + { + std::ifstream pin(prelude); + if (!pin) + { + std::cerr << "cannot open prelude " << prelude << '\n'; + return 1; + } + usl.includeScript(prelude, pin); + } + + std::ifstream in(path); + if (!in) + { + std::cerr << "cannot open " << path << '\n'; + return 1; + } + + std::stringstream wrapped; + wrapped << "result := {\n" << in.rdbuf() << "\n}\n"; + usl.includeScript(path, wrapped); + + Value* result = usl.getConstant("result"); + if (result) + result->dump(std::cout); + else + std::cout << "(no result)"; + std::cout << '\n'; + return 0; +} + +} // namespace + +int main(int argc, char* argv[]) +{ + std::vector preludes; + std::vector tests; + + for (int i = 1; i < argc; ++i) + { + std::string a = argv[i]; + if (a == "-p" && i + 1 < argc) + preludes.push_back(argv[++i]); + else + tests.push_back(argv[i]); + } + + if (tests.empty()) + { + std::cerr << "usage: " << argv[0] << " [-p ]... ...\n"; + return 1; + } + + int rc = 0; + for (const char* path : tests) + { + std::cout << "=== " << path << " ===\n"; + try + { + if (runFile(path, preludes) != 0) + rc = 1; + } + catch (const std::exception& e) + { + std::cerr << "ERROR in " << path << ": " << e.what() << '\n'; + rc = 1; + } + } + return rc; +} diff --git a/libwee/include/asm.h b/libwee/include/asm.h deleted file mode 100644 index e2532740c..000000000 --- a/libwee/include/asm.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef ASM_H -#define ASM_H - -#include "container.h" - -namespace Asm { - - struct Instruction { - // returns next instruction to evaluate -#define ASM_INSTRUCTION_NEXT (const Instruction*)(((const uint8_t*)this) + sizeof(*this)) - virtual const Instruction* Eval(Stack* stack) const = 0; - virtual ~Instruction() { } - }; - - namespace Instructions { - - struct Nop: Instruction { - const Instruction* Eval(Stack* stack) const { - return ASM_INSTRUCTION_NEXT; - } - }; - - // Nop instruction with metadata - template - struct Meta: Instruction { - const T data; - Meta(const T& data): data(data) {} - const Instruction* Eval(Stack* stack) const { - return ASM_INSTRUCTION_NEXT; // Nop - } - }; - - // Load immediate constant - // ... => ..., value - struct Const: Instruction { - const size_t size; - const uint8_t* source; - Const(size_t size, uint8_t* source): size(size), source(source) {} - const Instruction* Eval(Stack* stack) const { - uint8_t* dest = stack->Alloc(size); - std::copy(source, source + size, dest); - return ASM_INSTRUCTION_NEXT; - } - }; - - // Duplicate value - // ..., value => ..., value, value - struct Duplicate: Instruction { - const size_t size; - Duplicate(size_t size): size(size) {} - const Instruction* Eval(Stack* stack) const { - const uint8_t* source = stack->Get(); - uint8_t* dest = stack->Alloc(size); - std::copy(source, source + size, dest); - return ASM_INSTRUCTION_NEXT; - } - }; - - // Load from address - // ..., address => ..., value - struct Load: Instruction { - const size_t size; - Load(size_t size): size(size) {} - const Instruction* Eval(Stack* stack) const { - const uint8_t* source; - stack->Pop(&source); - uint8_t* dest = stack->Alloc(size); - std::copy(source, source + size, dest); - return ASM_INSTRUCTION_NEXT; - } - }; - - // Store to address - // ..., address, value => ... - struct Store: Instruction { - const size_t size; - Store(size_t size): size(size) {} - const Instruction* Eval(Stack* stack) const { - const uint8_t* source = stack->Get(); - uint8_t* dest = *(uint8_t**)stack->Get(size); - std::copy(source, source + size, dest); - stack->Free(size + sizeof(dest)); - return ASM_INSTRUCTION_NEXT; - } - }; - - // Copy data - // ..., destAddress, srcAddress => ... - struct Copy: Instruction { - const size_t size; - Copy(size_t size): size(size) {} - const Instruction* Eval(Stack* stack) const { - const uint8_t* source; - stack->Pop(&source); - uint8_t* dest; - stack->Pop(&dest); - std::copy(source, source + size, dest); - return ASM_INSTRUCTION_NEXT; - } - }; - - // Build stack reference - // ... => ..., address - struct Reference: Instruction { - const size_t offset; - Reference(size_t offset): offset(offset) {} - const Instruction* Eval(Stack* stack) const { - stack->Push(stack->Get(offset)); - return ASM_INSTRUCTION_NEXT; - } - }; - - // Grow stack - // ... => ..., space - struct Alloc: Instruction { - const size_t size; - Alloc(size_t size): size(size) {} - const Instruction* Eval(Stack* stack) const { - stack->Alloc(size); - return ASM_INSTRUCTION_NEXT; - } - }; - - // Shrink stack - // ..., space => ... - struct Free: Instruction { - const size_t size; - Free(size_t size): size(size) {} - const Instruction* Eval(Stack* stack) const { - stack->Free(size); - return ASM_INSTRUCTION_NEXT; - } - }; - - // Jump to target instruction - // ..., target => ... - struct Jump: Instruction { - const Instruction* Eval(Stack* stack) const { - const Instruction* next; - stack->Pop(&next); - return next; - } - }; - - // Call function - // ..., target => ..., returnAddress - struct Call: Instruction { - const Instruction* Eval(Stack* stack) const { - const Instruction* next; - stack->Pop(&next); - stack->Push(ASM_INSTRUCTION_NEXT); - return next; - } - }; - - // Return from function - // ..., returnAddress => ... - typedef Jump Return; - - }; - -}; - -#endif // ndef ASM_H diff --git a/libwee/include/container.h b/libwee/include/container.h deleted file mode 100644 index b93809cb8..000000000 --- a/libwee/include/container.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef CONTAINER_H -#define CONTAINER_H - -#include "type.h" - -class Container { -public: - uint8_t* min; - uint8_t* top; - uint8_t* max; -public: - Container(size_t pages); - virtual ~Container() { } - void Free(); - size_t Capacity() { return max - min; } - bool Contains(void* ptr) { return ptr >= min && ptr < max; } - virtual size_t Size() = 0; - virtual uint8_t* Get(size_t offset = 0) = 0; - virtual uint8_t* Alloc(size_t size) = 0; -private: - void Init(size_t size); -protected: - virtual bool Grow(size_t size) = 0; -}; - -class Heap: public Container { -public: - Heap(size_t pages = 1): Container(pages) { top = min; } - size_t Size() { return top - min; } - uint8_t* Get(size_t offset = 0) { - return min + offset; - } - uint8_t* Alloc(size_t size) { - uint8_t* newTop = top + size; - while(newTop > max) { - if(!Grow(Capacity())) { - return NULL; - } - } - uint8_t* ret = top; - top = newTop; - return ret; - } - bool Grow(size_t size); - Object* Push(const Object* object) { - size_t size = sizeof(Object) + object->type->Size(&object->value); - uint8_t* objectMem = (uint8_t*)object; - uint8_t* newObjectMem = Alloc(size); - std::copy(objectMem, objectMem + size, newObjectMem); - return (Object*)newObjectMem; - } -}; - -class Stack: public Container { -public: - Stack(size_t pages = 1): Container(pages) { top = max; } - size_t Size() { return max - top; } - uint8_t* Get(size_t offset = 0) { - return top + offset; - } - uint8_t* Alloc(size_t size) { - uint8_t* newTop = top - size; - while(newTop < min) { - if(!Grow(Capacity())) { - return NULL; - } - } - top = newTop; - return top; - } - uint8_t* Free(size_t size) { - uint8_t* ret = top; - top += size; - return ret; - } - bool Grow(size_t size); - template - T* Push(const T& src) { - T* ret = (T*)Alloc(sizeof(T)); - *ret = src; - return ret; - } - template - void Pop(T* dest) { - *dest = *(const T*)Free(sizeof(T)); - } -}; - -#endif // ndef CONTAINER_H diff --git a/libwee/include/forward.h b/libwee/include/forward.h deleted file mode 100644 index 0aa71b974..000000000 --- a/libwee/include/forward.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef FORWARD_H -#define FORWARD_H - -#include -#include - -//#define self (*this) - -struct Value; -struct Object; -struct Type; -namespace Types { - struct ConstSize; - struct VarSize; -}; -struct Instruction; -typedef Instruction* Function; -typedef Function* VTable; - -#endif // ndef FORWARD_H diff --git a/libwee/include/function.h b/libwee/include/function.h deleted file mode 100644 index 6b1b4a37b..000000000 --- a/libwee/include/function.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef FUNCTION_H -#define FUNCTION_H - -#include "container.h" - -struct Function { - virtual void call(Value* frame) = 0; -}; - -namespace Functions { - - struct Native { - void call(Stack* stack) { code(stack); } - void (*code)(Stack*); - }; - - struct Interpreted { - void call(Stack* stack); - uint8_t* code; - }; - -}; - -#endif // ndef FUNCTION_H diff --git a/libwee/include/gc.h b/libwee/include/gc.h deleted file mode 100644 index 5f4098803..000000000 --- a/libwee/include/gc.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GC_H -#define GC_H - -#include "container.h" -#include "type.h" -//#include "asm.h" - -class GC { -public: - virtual ~GC() { } - virtual Object* New(const Type* type, size_t allocSize) = 0; - virtual void Collect() = 0; -}; - -class CopyGC { - Heap heap; -public: - CopyGC(): heap() {} - CopyGC(size_t heapPages): heap(heapPages) {} -public: - Object* New(const Type* type, size_t allocSize); - void Collect(); -private: - //void Resize(size_t newSize); - const Value* Scan(const Type* type, const Value* value, Heap& dest); - const Value* Scan(const Types::Builtin* type, const Values::Builtin* value, Heap& dest); - const Value* Scan(const Types::Compound* type, const Values::Compound* value, Heap& dest); - const Value* Scan(const Types::Array* type, const Values::Array* value, Heap& dest); - const Value* Scan(const Types::VarArray* type, const Values::VarArray* value, Heap& dest); -}; - -#endif // ndef GC_H diff --git a/libwee/include/type.h b/libwee/include/type.h deleted file mode 100644 index 7a8686ed1..000000000 --- a/libwee/include/type.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef TYPE_H -#define TYPE_H - -#include "value.h" -#include -#include - -struct Interface: Value { -}; - -struct Type: Interface { - // meta type - // TODO: this enum is redundant with the virtual table - // find a way to switch on real types (maybe with RTTI) - // A visitor pattern is too slow (2 virtual calls) - const enum Meta { - Builtin, - Compound, - Array, - VarArray, - } meta; - virtual size_t Size(const Value* value) const = 0; - Type(Meta meta): Interface(), meta(meta) {} - virtual ~Type() {} - __gnu_cxx::hash_map interfaces; -}; - -namespace Types { - - // Fixed size type - struct ConstSize: Type { - // storage size in bytes - const size_t size; - size_t Size(const Value* value) const { return size; } - - ConstSize(Meta meta, size_t size): Type(meta), size(size) {} - ::Value* Copy(const Value* src, Value* dest) const { - const uint8_t* srcBegin = (const uint8_t*)src; - uint8_t* destBegin = (uint8_t*)dest; - std::copy(srcBegin, srcBegin + size, destBegin); - return (::Value*)destBegin + size; - } - }; - - // Builtin types - struct Builtin: ConstSize { - // builtin types - static const Builtin* Void; - static const Builtin* Bool; - static const Builtin* Char; - static const Builtin* Nat; - static const Builtin* Int; - static const Builtin* Nat8; - static const Builtin* Int8; - static const Builtin* Nat16; - static const Builtin* Int16; - static const Builtin* Nat32; - static const Builtin* Int32; - static const Builtin* Nat64; - static const Builtin* Int64; - static const Builtin* ValueRef; - static const Builtin* ObjectRef; - private: - Builtin(size_t size): ConstSize(Type::Builtin, size) {} - }; - - // Compound type - struct Compound: ConstSize { - // for reflection & garbage collection - const size_t fieldsCount; - struct Field { - ConstSize* type; - }; - const Field* fields; - private: - template - static size_t TotalSize(Iter begin, Iter end) { - size_t acc = 0; - for(Iter iter = begin; iter != end; ++iter) { - acc += (*iter).type->size; - } - return acc; - } - Compound(size_t fieldsCount, const Field* fields): ConstSize(Type::Compound, TotalSize(fields, fields + fieldsCount)), fieldsCount(fieldsCount), fields(fields) {} - }; - - // Array type - struct Array: ConstSize { - const ConstSize* elemsType; - const size_t elemsCount; - private: - Array(const ConstSize* elemsType, const size_t elemsCount): ConstSize(Type::Array, elemsCount * elemsType->size), elemsType(elemsType), elemsCount(elemsCount) {} - }; - - // Variable size type - struct VarSize: Type { - // storage size in bytes - virtual size_t Size(const Value* value) const = 0; - - VarSize(Meta meta): Type(meta) {} - ::Value* Copy(const Value* src, Value* dest) const { - size_t size = Size(src); - const uint8_t* srcBegin = (const uint8_t*)src; - uint8_t* destBegin = (uint8_t*)dest; - std::copy(srcBegin, srcBegin + size, destBegin); - return (::Value*)destBegin + size; - } - }; - - // Variable length array type - struct VarArray: VarSize { - const ConstSize* elemsType; - private: - VarArray(const ConstSize* elemsType): VarSize(Type::VarArray), elemsType(elemsType) {} - }; - -}; - -#endif // ndef TYPE_H diff --git a/libwee/include/value.h b/libwee/include/value.h deleted file mode 100644 index 0c9d68998..000000000 --- a/libwee/include/value.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef VALUE_H -#define VALUE_H - -#include "forward.h" - -// builtin representations -typedef bool Bool; -typedef uint_fast32_t Char; -typedef void* Ptr; -typedef unsigned long Nat; -typedef long Int; -typedef uint_fast8_t Nat8; -typedef int_fast8_t Int8; -typedef uint_fast16_t Nat16; -typedef int_fast16_t Int16; -typedef uint_fast32_t Nat32; -typedef int_fast32_t Int32; -typedef uint_fast64_t Nat64; -typedef int_fast64_t Int64; -struct ValueRef { - const Object* object; - const Value* value; - ValueRef(const Object* object, const Value* value): object(object), value(value) {} -}; -struct ObjectRef { - const Object* object; - const VTable vtable; - ObjectRef(const Object* object, const VTable vtable): object(object), vtable(vtable) {} -}; - -struct Value { -}; - -namespace Values { - - // Builtin values - struct Builtin: Value { - }; - struct Void: Builtin {}; - #define VALUES_BUILTIN_VALUE(T) struct T: Builtin { const ::T value; T(const ::T& value): value(value) {} } - VALUES_BUILTIN_VALUE(Bool); - VALUES_BUILTIN_VALUE(Char); - VALUES_BUILTIN_VALUE(Nat); - VALUES_BUILTIN_VALUE(Int); - VALUES_BUILTIN_VALUE(Nat8); - VALUES_BUILTIN_VALUE(Int8); - VALUES_BUILTIN_VALUE(Nat16); - VALUES_BUILTIN_VALUE(Int16); - VALUES_BUILTIN_VALUE(Nat32); - VALUES_BUILTIN_VALUE(Int32); - VALUES_BUILTIN_VALUE(Nat64); - VALUES_BUILTIN_VALUE(Int64); - VALUES_BUILTIN_VALUE(ValueRef); - VALUES_BUILTIN_VALUE(ObjectRef); - - struct Compound: Value { - }; - - struct Array: Value { - Value elems[0]; - }; - - struct VarArray: Value { - const size_t elemsCount; - Value elems[0]; - VarArray(size_t elemsCount): elemsCount(elemsCount) {} - }; - -}; - -struct Object { - const Type* type; - Value value; - Object(const Type* type): type(type) {} -}; - -#endif // ndef VALUE_H diff --git a/libwee/include/wee.h b/libwee/include/wee.h deleted file mode 100644 index 31e579580..000000000 --- a/libwee/include/wee.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef WEE_H -#define WEE_H - -#include "value.h" -#include "type.h" -#include "object.h" -#include "container.h" -#include "gc.h" - -#endif // ndef WEE_H diff --git a/libwee/src/container.cpp b/libwee/src/container.cpp deleted file mode 100644 index 4dbbed1dd..000000000 --- a/libwee/src/container.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "container.h" -#include - -#define PAGE_SIZE sysconf(_SC_PAGESIZE) -#define DEFAULT_STACK_SIZE (PAGE_SIZE*8) - -Container::Container(size_t pages) { - Init(pages * PAGE_SIZE); -} - -void Container::Free() { - munmap(min, Capacity()); -} - -void Container::Init(size_t size) { - min = (uint8_t*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS, 0, 0); - max = min + size; -} - -bool Heap::Grow(size_t size) { - void* result = mmap(max, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS, 0, 0); - if(result != (void*)-1) { - max += size; - return true; - } - else { - return false; - } -} - -bool Stack::Grow(size_t size) { - void* result = mmap(min - size, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS, 0, 0); - if(result != (void*)-1) { - min -= size; - return true; - } - else { - return false; - } -} diff --git a/libwee/src/gc.cpp b/libwee/src/gc.cpp deleted file mode 100644 index e90c7d93c..000000000 --- a/libwee/src/gc.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "gc.h" -#include "asm.h" - -Object* CopyGC::New(const Type* type, size_t allocSize) { - Object* object = (Object*)heap.Alloc(allocSize); - if(object == NULL) { - Collect(); - object = (Object*)heap.Alloc(allocSize); - if(object == NULL) { - //if(!Resize(newHeapEnd - heap.begin)) { - abort(); - //} - } - } - ::new(static_cast(object)) Object(type); - return object; -} - -const Value* CopyGC::Scan(const Type* type, const Value* value, Heap& dest) { - switch(type->meta) { - case Type::Builtin: - return Scan((const Types::Builtin*)type, (const Values::Builtin*)value, dest); - case Type::Compound: - return Scan((const Types::Compound*)type, (const Values::Compound*)value, dest); - case Type::Array: - return Scan((const Types::Array*)type, (const Values::Array*)value, dest); - case Type::VarArray: - return Scan((const Types::VarArray*)type, (const Values::VarArray*)value, dest); - default: - abort(); - } -} - -const Value* CopyGC::Scan(const Types::Builtin* type, const Values::Builtin* value, Heap& dest) { - if(type == Types::Builtin::ObjectRef) { - Object*& objectRef = const_cast(((const Values::ObjectRef*)value)->value.object); - Object* object = objectRef; - if(heap.Contains(object)) { - Object* newObject = *(Object**)(void*)object; - if(dest.Contains(newObject)) { - objectRef = newObject; - } - else { - Object* newObject = dest.Push(object); - *(Object**)(void*)object = newObject; - } - } - } - /*else if(type == Types::Builtin::ValueRef) { - // TODO: scan value references - abort(); - }*/ - return value + type->size; -} - -const Value* CopyGC::Scan(const Types::Compound* type, const Values::Compound* value, Heap& dest) { - const Value* iter = value; - for(size_t i = 0; i < type->fieldsCount; ++i) { - iter = Scan(type->fields[i].type, iter, dest); - } - return iter; -} - -const Value* CopyGC::Scan(const Types::Array* type, const Values::Array* value, Heap& dest) { - const Value* iter = value; - for(size_t i = 0; i < type->elemsCount; ++i) { - iter = Scan(type->elemsType, iter, dest); - } - return iter; -} - -const Value* CopyGC::Scan(const Types::VarArray* type, const Values::VarArray* value, Heap& dest) { - const Value* iter = value->elems; - for(size_t i = 0; i < value->elemsCount; ++i) { - iter = Scan(type->elemsType, iter, dest); - } - return iter; -} - -void CopyGC::Collect() { - Heap newHeap(heap.Capacity()); - // scan root - ObjectRef root((Object*)heap.min, NULL); - Values::ObjectRef rootValue(root); - Scan(Types::Builtin::ObjectRef, &rootValue, newHeap); - // scan heap - for(const Object* iter = (const Object*)newHeap.min; iter < (const Object*)newHeap.top; iter = (const Object*)Scan(iter->type, &iter->value, newHeap)); - // replace heap - heap.Free(); - std::swap(heap, newHeap); -} diff --git a/libwee/src/type.cpp b/libwee/src/type.cpp deleted file mode 100644 index 21a05c860..000000000 --- a/libwee/src/type.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright (C) 2004 Martin Voelkle - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "type.h" - -#define TYPES_VALUES_BUILTIN(x) const Types::Builtin* Types::Builtin::x = new Types::Builtin(sizeof(Values::x)) -TYPES_VALUES_BUILTIN(Void); -TYPES_VALUES_BUILTIN(Bool); -TYPES_VALUES_BUILTIN(Char); -TYPES_VALUES_BUILTIN(Nat); -TYPES_VALUES_BUILTIN(Int); -TYPES_VALUES_BUILTIN(Nat8); -TYPES_VALUES_BUILTIN(Int8); -TYPES_VALUES_BUILTIN(Nat16); -TYPES_VALUES_BUILTIN(Int16); -TYPES_VALUES_BUILTIN(Nat32); -TYPES_VALUES_BUILTIN(Int32); -TYPES_VALUES_BUILTIN(Nat64); -TYPES_VALUES_BUILTIN(Int64); -TYPES_VALUES_BUILTIN(ValueRef); -TYPES_VALUES_BUILTIN(ObjectRef); diff --git a/maps/SConscript b/maps/SConscript index 6ba70f8c7..1abe0c4e3 100644 --- a/maps/SConscript +++ b/maps/SConscript @@ -3,7 +3,7 @@ import os Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: any=False for file in os.listdir("."): if file.find(".map") != -1: diff --git a/natsort/SConscript b/natsort/SConscript index 81930c19b..20c7438a6 100755 --- a/natsort/SConscript +++ b/natsort/SConscript @@ -4,7 +4,7 @@ Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: any=False for file in os.listdir("."): if file.find(".c") != -1 or file.find(".h") != -1 or file.find(".py") != -1: diff --git a/scons/nsis.py b/scons/nsis.py index c4bf6bf50..e152e7fd6 100644 --- a/scons/nsis.py +++ b/scons/nsis.py @@ -19,7 +19,7 @@ def generate(env) : def winToLocalReformat(path) : return os.path.join(*path.split("\\")) def scanNsisContent(node, env, path, arg): - contents = node.get_contents() + contents = node.get_text_contents() includes = nsisFiles_re.findall(contents) includes = [ winToLocalReformat(include) for include in includes ] return [x for x in includes if x.rfind('*')==-1] diff --git a/scripts/SConscript b/scripts/SConscript index cf88354a7..5ade7eabb 100644 --- a/scripts/SConscript +++ b/scripts/SConscript @@ -3,7 +3,7 @@ import os Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: for file in os.listdir("."): if file.find(".sgsl") != -1: PackTar(env["TARFILE"], file) diff --git a/src/AICastor.cpp b/src/AICastor.cpp deleted file mode 100644 index a075c8780..000000000 --- a/src/AICastor.cpp +++ /dev/null @@ -1,3250 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include -#include - -#include "AICastor.h" -#include "Game.h" -#include "GlobalContainer.h" -#include "LogFileManager.h" -#include "Order.h" -#include "Player.h" -#include "Unit.h" -#include "Utilities.h" - -#define AI_FILE_MIN_VERSION 1 -#define AI_FILE_VERSION 2 - -using boost::shared_ptr; - -// AICastor::Project part: - -AICastor::Project::Project(IntBuildingType::Number shortTypeNum, const char *suffix) -{ - this->shortTypeNum=shortTypeNum; - init(suffix); -} -AICastor::Project::Project(IntBuildingType::Number shortTypeNum, int amount, Sint32 mainWorkers, const char *suffix) -{ - this->shortTypeNum=shortTypeNum; - init(suffix); - this->amount=amount; - this->mainWorkers=mainWorkers; -} -void AICastor::Project::init(const char *suffix) -{ - amount=1; - food=(this->shortTypeNum==IntBuildingType::SWARM_BUILDING - || this->shortTypeNum==IntBuildingType::FOOD_BUILDING); - defense=(this->shortTypeNum==IntBuildingType::DEFENSE_BUILDING); - - debugStdName += IntBuildingType::typeFromShortNumber(this->shortTypeNum); - debugStdName += "-"; - debugStdName += suffix; - this->debugName=debugStdName.c_str(); - - //printf("new project(%s)\n", debugName); - - subPhase=0; - - successWait=0; - blocking=true; - critical=false; - priority=1; - triesLeft=64; - - mainWorkers=-1; - foodWorkers=-1; - otherWorkers=-1; - - multipleStart=false; - waitFinished=false; - finalWorkers=-1; - - finished=false; - - timer=(Uint32)-1; -} - - -// AICastor::Strategy part: - -AICastor::Strategy::Strategy() -{ - defined=false; - - successWait=0; - - warLevelTrigger=0; - warTimeTrigger=0; - maxAmountGoal=0; -}; - -// AICastor main class part: - -void AICastor::firstInit() -{ - obstacleUnitMap=NULL; - obstacleBuildingMap=NULL; - spaceForBuildingMap=NULL; - buildingNeighbourMap=NULL; - - workPowerMap=NULL; - workRangeMap=NULL; - workAbilityMap=NULL; - hydratationMap=NULL; - notGrassMap=NULL; - wheatGrowthMap=NULL; - for (int i=0; i<4; i++) - oldWheatGradient[i]=NULL; - for (int i=0; i<2; i++) - wheatCareMap[i]=NULL; - - goodBuildingMap=NULL; - - enemyWarriorsMap=NULL; - enemyPowerMap=NULL; - enemyRangeMap=NULL; - - ressourcesCluster=NULL; -} - -AICastor::AICastor(Player *player) -{ - logFile=globalContainer->logFileManager->getFile("AICastor.log"); - //logFile=stdout; - - firstInit(); - init(player); -} - -AICastor::AICastor(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - logFile=globalContainer->logFileManager->getFile("AICastor.log"); - - firstInit(); - bool goodLoad=load(stream, player, versionMinor); - assert(goodLoad); -} - -void AICastor::init(Player *player) -{ - assert(player); - - // Logical : - timer=0; - canSwim=false; - needSwim=false; - lastFreeWorkersComputed=(Uint32)-1; - lastWheatGrowthMapComputed=(Uint32)-1; - lastEnemyRangeMapComputed=(Uint32)-1; - lastEnemyPowerMapComputed=(Uint32)-1; - lastEnemyWarriorsMapComputed=(Uint32)-1; - computeNeedSwimTimer=0; - controlSwarmsTimer=0; - expandFoodTimer=0; - controlFoodTimer=0; - controlUpgradeTimer=0; - controlUpgradeDelay=32; - controlStrikesTimer=0; - - warLevel=0; - warTimeTriggerLevel=0; - warLevelTriggerLevel=0; - warAmountTriggerLevel=0; - - onStrike=false; - strikeTimeTrigger=0; - strikeTeamSelected=false; - strikeTeam=0; - - foodWarning=false; - foodLock=false; - foodSurplus=false; - foodLockStats[0]=0; - foodLockStats[1]=0; - overWorkers=false; - starvingWarning=false; - starvingWarningStats[0]=0; - starvingWarningStats[1]=0; - buildsAmount=0; - - - for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) - delete *pi; - projects.clear(); - - // Structural: - this->player=player; - this->team=player->team; - this->game=player->game; - this->map=player->map; - - assert(this->team); - assert(this->game); - assert(this->map); - - size_t size=map->w*map->h; - assert(size>0); - - computeBoot=0; - - if (obstacleUnitMap!=NULL) - delete[] obstacleUnitMap; - obstacleUnitMap=new Uint8[size]; - - if (obstacleBuildingMap!=NULL) - delete[] obstacleBuildingMap; - obstacleBuildingMap=new Uint8[size]; - - if (spaceForBuildingMap!=NULL) - delete[] spaceForBuildingMap; - spaceForBuildingMap=new Uint8[size]; - - if (buildingNeighbourMap!=NULL) - delete[] buildingNeighbourMap; - buildingNeighbourMap=new Uint8[size]; - - - if (workPowerMap!=NULL) - delete[] workPowerMap; - workPowerMap=new Uint8[size]; - - if (workRangeMap!=NULL) - delete[] workRangeMap; - workRangeMap=new Uint8[size]; - - if (workAbilityMap!=NULL) - delete[] workAbilityMap; - workAbilityMap=new Uint8[size]; - - if (hydratationMap!=NULL) - delete[] hydratationMap; - hydratationMap=new Uint8[size]; - - if (notGrassMap!=NULL) - delete[] notGrassMap; - notGrassMap=new Uint8[size]; - - if (wheatGrowthMap!=NULL) - delete[] wheatGrowthMap; - wheatGrowthMap=new Uint8[size]; - - for (int i=0; i<4; i++) - { - if (oldWheatGradient[i]!=NULL) - delete[] oldWheatGradient[i]; - oldWheatGradient[i]=new Uint8[size]; - } - - for (int i=0; i<2; i++) - { - if (wheatCareMap[i]!=NULL) - delete[] wheatCareMap[i]; - wheatCareMap[i]=new Uint8[size]; - } - - if (goodBuildingMap!=NULL) - delete[] goodBuildingMap; - goodBuildingMap=new Uint8[size]; - - if (enemyPowerMap!=NULL) - delete[] enemyPowerMap; - enemyPowerMap=new Uint8[size]; - - if (enemyRangeMap!=NULL) - delete[] enemyRangeMap; - enemyRangeMap=new Uint8[size]; - - if (enemyWarriorsMap!=NULL) - delete[] enemyWarriorsMap; - enemyWarriorsMap=new Uint8[size]; - - if (ressourcesCluster!=NULL) - delete[] ressourcesCluster; - ressourcesCluster=new Uint16[size]; -} - -AICastor::~AICastor() -{ - if (obstacleUnitMap!=NULL) - delete[] obstacleUnitMap; - - if (obstacleBuildingMap!=NULL) - delete[] obstacleBuildingMap; - - if (spaceForBuildingMap!=NULL) - delete[] spaceForBuildingMap; - - if (buildingNeighbourMap!=NULL) - delete[] buildingNeighbourMap; - - - if (workPowerMap!=NULL) - delete[] workPowerMap; - - if (workRangeMap!=NULL) - delete[] workRangeMap; - - if (workAbilityMap!=NULL) - delete[] workAbilityMap; - - if (hydratationMap!=NULL) - delete[] hydratationMap; - - if (notGrassMap!=NULL) - delete[] notGrassMap; - - if (wheatGrowthMap!=NULL) - delete[] wheatGrowthMap; - - for (int i=0; i<4; i++) - if (oldWheatGradient[i]!=NULL) - delete[] oldWheatGradient[i]; - - for (int i=0; i<2; i++) - if (wheatCareMap[i]!=NULL) - delete[] wheatCareMap[i]; - - if (goodBuildingMap!=NULL) - delete[] goodBuildingMap; - - if (enemyPowerMap!=NULL) - delete[] enemyPowerMap; - - if (enemyRangeMap!=NULL) - delete[] enemyRangeMap; - - if (enemyWarriorsMap!=NULL) - delete[] enemyWarriorsMap; - - if (ressourcesCluster!=NULL) - delete[] ressourcesCluster; - - for(std::list::iterator i=projects.begin(); i!=projects.end(); ++i) - { - delete *i; - } - -} - -bool AICastor::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - fprintf(logFile, "load(%d)\n", versionMinor); - init(player); - assert(game); - - stream->readEnterSection("AICastor"); - Sint32 aiFileVersion = stream->readSint32("aiFileVersion"); - if (aiFileVersionreadLeaveSection(); - return false; - } - if (aiFileVersion>=1) - timer = stream->readUint32("timer"); - else - timer=0; - - stream->readLeaveSection(); - fprintf(logFile, "load success\n"); - return true; -} - -void AICastor::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("AICastor"); - stream->writeSint32(AI_FILE_VERSION, "aiFileVersion"); - stream->writeUint32(timer, "timer"); - stream->writeLeaveSection(); -} - -boost::shared_ptrAICastor::getOrder() -{ - timer++; - - if (!strategy.defined) - defineStrategy(); - - if (computeBoot<32) - { - computeBoot++; - return shared_ptr(new NullOrder()); - } - else if (computeBoot<17+32) - { - switch (computeBoot-32) - { - case 0: - computeHydratationMap(); - break; - case 1: - computeNotGrassMap(); - break; - case 2: - computeCanSwim(); - break; - case 3: - computeNeedSwim(); - break; - case 4: - computeBuildingSum(); - break; - case 5: - computeWarLevel(); - break; - case 6: - computeObstacleUnitMap(); - break; - case 7: - computeObstacleBuildingMap(); - break; - case 8: - computeWorkPowerMap(); - break; - case 9: - computeWorkRangeMap(); - break; - case 10: - computeWorkAbilityMap(); - break; - case 11: - computeHydratationMap(); - break; - case 12: - //computeWheatCareMap(); - { - size_t size=map->w*map->h; - Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; - for (int i=0; i<4; i++) - memcpy(oldWheatGradient[i], wheatGradient, size); - for (int i=0; i<2; i++) - memset(wheatCareMap[i], 1, size); - } - break; - case 13: - computeWheatGrowthMap(); - break; - case 14: - computeEnemyPowerMap(); - break; - case 15: - computeEnemyRangeMap(); - break; - case 16: - computeEnemyWarriorsMap(); - break; - default: - assert(false); - } - computeBoot++; - return shared_ptr(new NullOrder()); - } - - if ((timer&511)==0) - { - Uint8 *temp=oldWheatGradient[3]; - for (int i=3; i>0; i--) - oldWheatGradient[i]=oldWheatGradient[i-1]; - oldWheatGradient[0]=temp; - Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; - memcpy(oldWheatGradient[0], wheatGradient, map->w*map->h); - computeObstacleUnitMap(); - computeWheatCareMap(); - } - - /*// Defense, we check it first, because it will only return true if there is an attack and free warriors - { - boost::shared_ptrorder = controlBaseDefense(); - if (order) - return order; - }*/ - - //printf("getOrder(), %d projects\n", projects.size()); - for (std::list::iterator pi=projects.begin(); pi!=projects.end();) - if ((*pi)->finished) - { - //printf("deleting project (%s)\n", (*pi)->debugName); - delete *pi; - pi=projects.erase(pi); - } - else - pi++; - bool blocking=false; - for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) - if ((*pi)->blocking) - blocking=true; - - computeBuildingSum(); - - if (!blocking)// No blocking project, we can start a new one: - addProjects(); - Sint32 priority=0x7FFFFFFF; - for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) - if (priority>(*pi)->priority && (*pi)->critical) - priority=(*pi)->priority; - - if (timer>controlSwarmsTimer) - { - computeWarLevel(); - controlSwarmsTimer=timer+256; // each 10s - boost::shared_ptrorder=controlSwarms(); - if (order) - return order; - } - - //bool critical=false; - //for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) - // if ((*pi)->critical) - // critical=true; - - int minReal=1024; - for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) - if ((*pi)->priority<=priority) - { - int real=buildingSum[(*pi)->shortTypeNum][0]; - if (minReal>real) - minReal=real; - } - for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) - if ((*pi)->priority<=priority) - { - int real=buildingSum[(*pi)->shortTypeNum][0]; - if (real<=minReal) - { - boost::shared_ptrorder=continueProject(*pi); - if (order) - return order; - } - } - for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) - if ((*pi)->priority<=priority) - { - int real=buildingSum[(*pi)->shortTypeNum][0]; - if (real>minReal) - { - boost::shared_ptrorder=continueProject(*pi); - if (order) - return order; - } - } - - if (priority>0 && timer>expandFoodTimer) - { - expandFoodTimer=timer+256; // each 10s - boost::shared_ptrorder=expandFood(); - if (order) - return order; - } - - if (timer>lastEnemyRangeMapComputed+1024) // each 41s - { - computeEnemyRangeMap(); - } - if (timer>lastEnemyWarriorsMapComputed+1024) // each 41s - { - computeEnemyWarriorsMap(); - } - - /*if (onStrike) - { - if (timer>lastEnemyPowerMapComputed+128) // each 5s - computeEnemyPowerMap(); - } - else - { - if (timer>lastEnemyPowerMapComputed+4096) // each 2min44s - computeEnemyPowerMap(); - }*/ - - if (priority>0) - { - boost::shared_ptrorder=controlFood(); - if (order) - return order; - } - - if (priority>0) - { - boost::shared_ptrorder=controlUpgrades(); - if (order) - return order; - } - - if (timer>controlStrikesTimer) - { - boost::shared_ptrorder=controlStrikes(); - if (order) - return order; - } - - return shared_ptr(new NullOrder()); -} - -void AICastor::defineStrategy() -{ - strategy.defined=true; - - for (int bi=0; biAICastor::controlSwarms() -{ - Sint32 warriorGoal=warLevel; - - int unitSum[NB_UNIT_TYPE]; - for (int i=0; imyUnits; - for (int i=0; itypeNum]++; - } - int foodSum=0; - Building **myBuildings=team->myBuildings; - for (int i=0; imaxUnitWorking && b->type->canFeedUnit) - foodSum+=b->type->maxUnitInside; - } - - int unitSumAll=unitSum[0]+unitSum[1]+unitSum[2]; - - foodWarning=((unitSumAll+11)>=(foodSum<<1)); - foodLock=((unitSumAll+3)>=(foodSum<<1)); - foodLockStats[foodLock]++; - - foodSurplus=(unitSumAll+4>5)+3)stats.getStarvingUnits()); - starvingWarningStats[starvingWarning]++; - fprintf(logFile, "starvingWarning=%d\n", starvingWarning); - - bool realFoodLock; - - if (warriorGoal>1) - realFoodLock=((unitSumAll)>=(foodSum*3)); - else - realFoodLock=((unitSumAll)>=(foodSum*2)); - - fprintf(logFile, "unitSum=[%d, %d, %d], unitSumAll=%d, foodSum=%d, foodWarning=%d, foodLock=%d, realFoodLock=%d, foodLockStats=[%d, %d]\n", - unitSum[0], unitSum[1], unitSum[2], unitSumAll, foodSum, foodWarning, foodLock, realFoodLock, foodLockStats[0], foodLockStats[1]); - - if ((timer>2048) && (realFoodLock || starvingWarning || starvingWarningStats[1]>starvingWarningStats[0])) - { - // Stop making any units! - Building **myBuildings=team->myBuildings; - for (int bi=0; bitype->unitProductionTime) - for (int ri=0; riratio[ri]!=0) - { - for (int ri=0; riratio[ri]=0; - b->ratioLocal[ri]=0; - } - b->update(); - return shared_ptr(new OrderModifySwarm(b->gid, b->ratioLocal)); - } - } - - return shared_ptr(); - } - - size_t size=map->w*map->h; - int discovered=0; - int seeable=0; - Uint32 *mapDiscovered=&(map->mapDiscovered[0]); - Uint32 *fogOfWar=&map->fogOfWar[0]; - Uint32 me=team->me; - for (size_t i=0; itype->unitProductionTime) - { - if (b->ratio[EXPLORER]!=explorerGoal - || b->ratio[WORKER]!=workerGoal - || b->ratio[WARRIOR]!=warriorGoal) - { - b->ratio[EXPLORER]=explorerGoal; - b->ratioLocal[EXPLORER]=explorerGoal; - b->ratio[WORKER]=workerGoal; - b->ratioLocal[WORKER]=workerGoal; - b->ratio[WARRIOR]=warriorGoal; - b->ratioLocal[WARRIOR]=warriorGoal; - b->update(); - return shared_ptr(new OrderModifySwarm(b->gid, b->ratioLocal)); - } - } - } - - return shared_ptr(); -} - -boost::shared_ptrAICastor::expandFood() -{ - if (foodSurplus - || (!foodWarning && !enoughFreeWorkers()) - || buildingSum[IntBuildingType::FOOD_BUILDING][1]>buildingSum[IntBuildingType::FOOD_BUILDING][0]+1) - return shared_ptr(); - - Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum("inn", 0, true); - int bw=globalContainer->buildingsTypes.get(typeNum)->width; - int bh=globalContainer->buildingsTypes.get(typeNum)->height; - assert(bw==bh); - - computeCanSwim(); - computeObstacleBuildingMap(); - computeSpaceForBuildingMap(bw); - computeBuildingNeighbourMap(bw, bh); - computeObstacleUnitMap(); - computeWheatGrowthMap(); - computeObstacleUnitMap(); - computeWorkPowerMap(); - computeWorkRangeMap(); - computeWorkAbilityMap(); - - return findGoodBuilding(typeNum, true, false, false); -} - -boost::shared_ptrAICastor::controlFood() -{ - //int w=map->w; - //int h=map->h; - int wMask=map->wMask; - int hMask=map->hMask; - //int hDec=map->hDec; - int wDec=map->wDec; - //size_t size=w*h; - - int bi=(controlFoodTimer++)&1023; - Building **myBuildings=team->myBuildings; - Building *b=myBuildings[bi]; - for (int i=0; i<8; i++) - if (b==NULL) - { - bi=(controlFoodTimer++)&1023; - b=myBuildings[bi]; - } - if (b==NULL) - return shared_ptr(); - if (b->type->shortTypeNum!=IntBuildingType::FOOD_BUILDING && b->type->shortTypeNum!=IntBuildingType::SWARM_BUILDING) - return shared_ptr(); - - int bx=b->posX; - int by=b->posY; - int bw=b->type->width; - int bh=b->type->height; - - Uint8 worstCare=0; - for (int xi=bx-1; xi4) - { - if (b->maxUnitWorking!=0) - { - b->maxUnitWorking=0; - b->maxUnitWorkingLocal=0; - b->update(); - if (verbose) - printf("controlFood(), worstCare=%d\n", worstCare); - return shared_ptr(new OrderModifyBuilding(b->gid, 0)); - } - } - else if (worstCare>2) - { - if (b->maxUnitWorking>1) - { - b->maxUnitWorking=1; - b->maxUnitWorkingLocal=1; - b->update(); - if (verbose) - printf("controlFood(), beta, worstCare=%d\n", worstCare); - return shared_ptr(new OrderModifyBuilding(b->gid, 1)); - } - } - else - { - if (b->type->shortTypeNum==IntBuildingType::FOOD_BUILDING) - { - Sint32 workers; - if (foodWarning && b->type->isBuildingSite) - workers=3+b->type->level; //TODO: random 2 or 3 - else - workers=1+b->type->level; - b->maxUnitWorking=workers; - b->maxUnitWorkingLocal=workers; - b->update(); - return shared_ptr(new OrderModifyBuilding(b->gid, workers)); - } - else if (b->type->shortTypeNum==IntBuildingType::SWARM_BUILDING) - { - Sint32 workers; - if (foodWarning) - workers=1; - else - workers=2; - b->maxUnitWorking=workers; - b->maxUnitWorkingLocal=workers; - b->update(); - return shared_ptr(new OrderModifyBuilding(b->gid, workers)); - } - else - assert(false); - } - return shared_ptr(); -} - -boost::shared_ptrAICastor::controlUpgrades() -{ - //printf("controlUpgrades(), controlUpgradeTimer=%d, controlUpgradeDelay=%d, buildsAmount=%d\n", - // controlUpgradeTimer, controlUpgradeDelay, buildsAmount); - if (controlUpgradeDelay!=0) - { - controlUpgradeDelay--; - return shared_ptr(); - } - if (buildsAmount<1 || !enoughFreeWorkers()) - return shared_ptr(); - int bi=((controlUpgradeTimer++)&1023); - Building **myBuildings=team->myBuildings; - Building *b=myBuildings[bi]; - if (b==NULL) - return shared_ptr(); - if (b->type->isVirtual) - return shared_ptr(); - if (b->maxUnitWorking<1) - return shared_ptr(new OrderModifyBuilding(b->gid, 1)); - int numberOfFreeWorkers = team->stats.getLatestStat()->isFree[WORKER]; - int numberOfAbleWorkers = team->stats.getLatestStat()->upgradeState[BUILD][b->type->level]; - if (numberOfAbleWorkers <= 2 || numberOfFreeWorkers <= 4 || numberOfAbleWorkers <= (numberOfFreeWorkers/8)) - return shared_ptr(); - // Is it any repair: - if (!b->type->isBuildingSite) - { - if (b->type->type == "defencetower") - { - if (b->hp*4type->hpMax*1) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - else if (b->type->maxUnitInside) - { - if (b->hp*4type->hpMax*3) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - else - { - if (b->hp*4type->hpMax*2) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - } - // Do we want to upgrade it: - // We compute the number of buildings satifying the strategy: - int shortTypeNum=b->type->shortTypeNum; - if (shortTypeNum>=NB_HARD_BUILDING) - return shared_ptr(); - int level=b->type->level; - int upgradeLevelGoal=((buildsAmount+1)>>1); - if (upgradeLevelGoal>3) - upgradeLevelGoal=3; - if (level>=upgradeLevelGoal) - return shared_ptr(); - int sumOver=0; - for (int li=(level+1); li<4; li++) - for (int si=0; si<2; si++) - sumOver+=buildingLevels[shortTypeNum][si][li]; - - int upgradeAmountGoal=strategy.build[shortTypeNum].baseUpgrade; - for (int ai=1; ai<=upgradeLevelGoal; ai++) - upgradeAmountGoal+=strategy.build[shortTypeNum].newUpgrade; - - fprintf(logFile, "controlUpgrades(%d)\n", bi); - fprintf(logFile, " shortTypeNum=%d\n", shortTypeNum); - fprintf(logFile, " sumOver=%d\n", sumOver); - fprintf(logFile, " upgradeAmountGoal=%d\n", upgradeAmountGoal); - //fprintf(logFile, "controlUpgrades(%d), shortTypeNum=%d, sumOver=%d, upgradeAmountGoal=%d\n", - // bi, shortTypeNum, sumOver, upgradeAmountGoal); - - if (sumOver>=upgradeAmountGoal) - return shared_ptr(); - - if (shortTypeNum==IntBuildingType::SCIENCE_BUILDING) - { - int buildBase=team->stats.getWorkersLevel(0); - int buildSum=0; - for (int i=0; i<4; i++) - buildSum+=team->stats.getWorkersLevel(i); - fprintf(logFile, " buildBase=%d, buildSum=%d\n", buildBase, buildSum); - if (buildBase>buildSum) - return shared_ptr(); - int sumEqual=0; - for (int li=level; li<4; li++) - sumEqual+=buildingLevels[shortTypeNum][0][li]; - fprintf(logFile, " sumEqual=%d\n", sumEqual); - if (sumEqual<2) - { - fprintf(logFile, " not another building level %d\n", level); - return shared_ptr(); - } - } - controlUpgradeDelay=32; - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); -} - - -// WARNING : Using wasEvent is *NOT* safe, and will *NOT* work through the network -/*boost::shared_ptrAICastor::controlBaseDefense() -{ - int freeWarriors = team->stats.getFreeUnits(WARRIOR); - if (team->wasEvent(Team::BUILDING_UNDER_ATTACK_EVENT) && (freeWarriors>0)) - { - int x, y; - team->getEventPos(&x, &y); - Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum(IntBuildingType::WAR_FLAG, 0, false); - fprintf(logFile, "controlBaseDefense()\n I'm, under attack !\n Defense war flag set at (%d,%d)\n", x, y); - onStrike = true; - return shared_ptr(new OrderCreate(team->teamNumber, x, y, typeNum)); - } - return NULL; -}*/ - - -boost::shared_ptrAICastor::controlStrikes() -{ - controlStrikesTimer=timer+64; - - if (!onStrike) - return shared_ptr(); - fprintf(logFile, "controlStrikes()\n"); - - int warriors=team->stats.getTotalUnits(WARRIOR); - int warFlagsGoal=(warriors+16)/32; - int warFlagsReal=buildingSum[IntBuildingType::WAR_FLAG][0]; - fprintf(logFile, " warriors=%d, warFlagsGoal=%d, warFlagsReal=%d\n", warriors, warFlagsGoal, warFlagsReal); - - if (!strikeTeamSelected) - { - int bestLevel=-1; - for (int ti=0; timapHeader.getNumberOfTeams(); ti++) - { - Team *enemyTeam=game->teams[ti]; - Uint32 me=team->me; - if ((team->enemies&enemyTeam->me)==0) - continue; - Building **enemyBuildings=enemyTeam->myBuildings; - for (int bi=0; biseenByMask&me)==0) || b->locked[canSwim]) - continue; - int level=b->type->level; - if (bestLevelmapHeader.getNumberOfTeams(); ti++) - { - int score=0; - Team *enemyTeam=game->teams[ti]; - Uint32 me=team->me; - if ((team->enemies&enemyTeam->me)==0) - continue; - Building **enemyBuildings=enemyTeam->myBuildings; - for (int bi=0; biseenByMask&me)==0) || b->locked[canSwim] || b->type->leveltype->shortTypeNum; - if (shortTypeNum==IntBuildingType::ATTACK_BUILDING - || shortTypeNum==IntBuildingType::SCIENCE_BUILDING) - score+=2; - else - score++; - } - if (bestScorew; - //int h=map->h; - int wMask=map->wMask; - int hMask=map->hMask; - //int hDec=map->hDec; - int wDec=map->wDec; - - Uint32 bestScore=0; - Building *bestBuilding=NULL; - Team *enemyTeam=game->teams[strikeTeam]; - Uint32 me=team->me; - Building **enemyBuildings=enemyTeam->myBuildings; - for (int bi=0; biseenByMask&me)==0) || b->locked[canSwim]) - continue; - int x=b->posX; - int y=b->posY; - size_t index=(x&wMask)+((y&hMask)<type->level; - Uint32 score=(1+workRange)*(1+level); - if (b->type->isBuildingSite) - score=(score>>2); - int shortTypeNum=b->type->shortTypeNum; - if (shortTypeNum==IntBuildingType::ATTACK_BUILDING - ||shortTypeNum==IntBuildingType::SCIENCE_BUILDING) - score=(score<<1); - if (bestScore *virtualBuildings=&team->virtualBuildings; - if (bestBuilding!=NULL) - { - Sint32 x=bestBuilding->posX+1; - Sint32 y=bestBuilding->posY+1; - - fprintf(logFile, " target found bestScore=%d, p=(%d, %d)\n", bestScore, x, y); - - if (warFlagsRealbuildingsTypes.getTypeNum("warflag", 0, false); - fprintf(logFile, " create\n"); - return shared_ptr(new OrderCreate(team->teamNumber, x, y, typeNum, 1, 1)); - } - else - { - Sint32 maxSqDist=0; - Building *maxFlag=NULL; - for (std::list::iterator it=virtualBuildings->begin(); it!=virtualBuildings->end(); ++it) - if ((*it)->type->shortTypeNum==IntBuildingType::WAR_FLAG) - { - Sint32 dx=x-(*it)->posX; - Sint32 dy=y-(*it)->posY; - Sint32 sqDist=dx*dx+dy*dy; - if (maxSqDist2 && maxFlag!=NULL) - { - fprintf(logFile, " move %d\n", maxFlag->gid); - return shared_ptr(new OrderMoveFlag(maxFlag->gid, x, y, true)); - } - for (std::list::iterator it=virtualBuildings->begin(); it!=virtualBuildings->end(); ++it) - if ((*it)->type->shortTypeNum==IntBuildingType::WAR_FLAG - && (*it)->maxUnitWorking<20) - { - fprintf(logFile, " modify %d\n", (*it)->gid); - return shared_ptr(new OrderModifyBuilding((*it)->gid, 20)); - } - } - } - else - { - for (std::list::iterator it=virtualBuildings->begin(); it!=virtualBuildings->end(); ++it) - if ((*it)->type->shortTypeNum==IntBuildingType::WAR_FLAG) - { - fprintf(logFile, " removed %d\n", (*it)->gid); - return shared_ptr(new OrderDelete((*it)->gid)); - } - strikeTeamSelected=false; - onStrike=false; - } - - return shared_ptr(); -} - - - -bool AICastor::addProject(Project *project) -{ - if (buildingSum[project->shortTypeNum][0]>=project->amount) - { - fprintf(logFile, "will not add project (%s x%d) as it already succeded\n", project->debugName, project->amount); - delete project; - return false; - } - for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) - if (project->shortTypeNum==(*pi)->shortTypeNum) - { - if (project->amount<=(*pi)->amount) - { - //fprintf(logFile, "will not add project (%s x%d) as project (%s x%d) has shortTypeNum (%d) too\n", - // project->debugName, project->amount, (*pi)->debugName, (*pi)->amount, project->shortTypeNum); - (*pi)->timer=timer; - delete project; - return false; - } - else - { - fprintf(logFile, "adding project (%s x%d) as project (%s x%d) has shortTypeNum (%d) too will replace it\n", - project->debugName, project->amount, (*pi)->debugName, (*pi)->amount, project->shortTypeNum); - delete (*pi); - projects.erase(pi); - projects.push_back(project); - return true; - } - } - projects.push_back(project); - return true; -} - -void AICastor::addProjects() -{ - //printf(" canFeedUnit=%d, swarms=%d, pool=%d+%d, attaque=%d+%d, speed=%d+%d\n", - // canFeedUnit, swarms, pool, poolSite, attaque, attaqueSite, speed, speedSite); - - buildsAmount=-1; - - if (buildingSum[IntBuildingType::FOOD_BUILDING][0]==0) - { - Project *project=new Project(IntBuildingType::FOOD_BUILDING, "boot"); - - project->successWait=strategy.successWait; - project->critical=true; - project->priority=0; - project->food=true; - - project->mainWorkers=3; - project->foodWorkers=2; - project->otherWorkers=0; - - project->multipleStart=true; - project->waitFinished=true; - project->finalWorkers=1; - - if (addProject(project)) - return; - } - if (buildingSum[IntBuildingType::SWARM_BUILDING][0]+buildingSum[IntBuildingType::SWARM_BUILDING][1]==0) - { - Project *project=new Project(IntBuildingType::SWARM_BUILDING, "boot"); - - project->successWait=strategy.successWait; - project->critical=true; - project->priority=0; - project->food=true; - - project->mainWorkers=10; - project->foodWorkers=1; - project->otherWorkers=0; - - project->multipleStart=false; - project->waitFinished=true; - project->finalWorkers=2; - - if (addProject(project)) - return; - } - if (buildingSum[IntBuildingType::SWIMSPEED_BUILDING][0]+buildingSum[IntBuildingType::SWIMSPEED_BUILDING][1]==0) - { - if (timer>computeNeedSwimTimer) - { - computeNeedSwimTimer=timer+1024;// every 41s - computeNeedSwim(); - } - if (needSwim) - { - Project *project=new Project(IntBuildingType::SWIMSPEED_BUILDING, 1, 2, "boot"); - project->successWait=strategy.successWait; - project->critical=true; - project->priority=0; - if (addProject(project)) - return; - } - } - if (buildingSum[IntBuildingType::ATTACK_BUILDING][0]+buildingSum[IntBuildingType::ATTACK_BUILDING][1]==0) - { - Project *project=new Project(IntBuildingType::ATTACK_BUILDING, 1, 2, "boot"); - project->successWait=strategy.successWait; - project->critical=true; - if (addProject(project)) - return; - } - /*if (buildingSum[IntBuildingType::WALKSPEED_BUILDING][0]+buildingSum[IntBuildingType::WALKSPEED_BUILDING][1]==0) - { - Project *project=new Project(IntBuildingType::WALKSPEED_BUILDING, 1, 7, "boot"); - project->successWait=strategy.successWait; - project->critical=true; - if (addProject(project)) - return; - } - if (buildingSum[IntBuildingType::HEAL_BUILDING][0]+buildingSum[IntBuildingType::HEAL_BUILDING][1]==0) - { - Project *project=new Project(IntBuildingType::HEAL_BUILDING, 1, 3, "boot"); - project->successWait=strategy.successWait; - project->critical=true; - project->multipleStart=true; - if (addProject(project)) - return; - } - if (buildingSum[IntBuildingType::SCIENCE_BUILDING][0]+buildingSum[IntBuildingType::SCIENCE_BUILDING][1]==0) - { - Project *project=new Project(IntBuildingType::SCIENCE_BUILDING, 1, 5, "boot"); - project->successWait=strategy.successWait; - project->critical=true; - if (addProject(project)) - return; - }*/ - // all critical projects succeded. - - // enough workers - //Strategy::Builds buildsCurrent=strategy.buildsBase; - buildsAmount=0; - if (!enoughFreeWorkers()) - return; - - for (int bpi=0; bpifoodLockStats[0] - || starvingWarning - || starvingWarningStats[1]>starvingWarningStats[0])) - continue; - Project *project=new Project((IntBuildingType::Number)bi, - strategy.build[bi].base, strategy.build[bi].baseWorkers, "base"); - project->successWait=strategy.successWait; - project->finalWorkers=strategy.build[bi].finalWorkers; - if (addProject(project)) - return; - } - buildsAmount=1; - - for (int bi=0; bifoodLockStats[0] - || starvingWarning - || starvingWarningStats[1]>starvingWarningStats[0])) - continue; - Project *project=new Project((IntBuildingType::Number)bi, - amountGoal[bi], strategy.build[bi].newWorkers+(agi-1), "loop"); - project->successWait=strategy.successWait; - project->finalWorkers=strategy.build[bi].finalWorkers; - if (addProject(project)) - return; - } - buildsAmount=1+(agi<<1); - - for (int bi=0; biAICastor::continueProject(Project *project) -{ - // Phase alpha will make a new Food Building at any price. - //printf("(%s)(stn=%d, f=%d, w=[%d, %d, %d], ms=%d, wf=%d), sp=%d\n", - // project->debugName, - // project->shortTypeNum, project->food, - // project->mainWorkers, project->foodWorkers, project->otherWorkers, - // project->multipleStart, project->waitFinished, project->subPhase); - - if (timertimer+32) - return shared_ptr(); - - if (foodLock && !project->critical && project->shortTypeNum==IntBuildingType::SWARM_BUILDING) - { - fprintf(logFile, "(%s) (give up by foodLock [%d, %d])\n", project->debugName, project->blocking, project->critical); - if (starvingWarning) - project->timer=timer+8192; // 5min28s - else - project->timer=timer+2048; // 1min22s - project->blocking=false; - project->critical=false; - } - - if (project->subPhase==0) - { - // boot phase - project->subPhase=2; - fprintf(logFile, "(%s) (boot) (switching to subphase 2)\n", project->debugName); - } - else if (project->subPhase==1) - { - if (!project->critical && !enoughFreeWorkers()) - { - project->timer=timer; - return shared_ptr(); - } - // find any good building place - - Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum(IntBuildingType::typeFromShortNumber(project->shortTypeNum), 0, true); - int bw=globalContainer->buildingsTypes.get(typeNum)->width; - int bh=globalContainer->buildingsTypes.get(typeNum)->height; - assert(bw==bh); - - computeCanSwim(); - computeObstacleBuildingMap(); - computeSpaceForBuildingMap(bw); - computeBuildingNeighbourMap(bw, bh); - computeObstacleUnitMap(); - computeWheatGrowthMap(); - computeWorkPowerMap(); - computeWorkRangeMap(); - computeWorkAbilityMap(); - - boost::shared_ptrgfbm=findGoodBuilding(typeNum, project->food, project->defense, project->critical); - project->timer=timer; - if (gfbm) - { - if (project->successWait>0) - { - fprintf(logFile, "(%s) (successWait [%d])\n", project->debugName, project->successWait); - project->successWait--; - } - else - { - project->subPhase=2; - fprintf(logFile, "(%s) (one construction site placed) (switching to next subphase 2)\n", project->debugName); - return gfbm; - } - } - else if (project->triesLeft>0) - { - project->triesLeft--; - } - else - { - fprintf(logFile, "(%s) (give up by failures [%d, %d])\n", project->debugName, project->blocking, project->critical); - project->timer=timer+8192; // 5min27s - project->blocking=false; - project->critical=false; - } - } - else if (project->subPhase==2) - { - // do we have enough building sites ? - - int real=buildingSum[project->shortTypeNum][0]; - int site=buildingSum[project->shortTypeNum][1]; - int sum=real+site; - - if (real>=project->amount) - { - project->subPhase=6; - fprintf(logFile, "(%s) ([%d>=%d] building finished) (switching to subphase 6).\n", - project->debugName, real, project->amount); - if (!project->waitFinished) - { - fprintf(logFile, "(%s) (deblocking [%d, %d])\n", project->debugName, project->blocking, project->critical); - project->blocking=false; - project->critical=false; - } - } - else if (sumamount) - { - project->subPhase=1; - fprintf(logFile, "(%s) (need more construction site [%d+%d<%d]) (switching back to subphase 1)\n", - project->debugName, real, site, project->amount); - } - else - { - project->subPhase=3; - fprintf(logFile, "(%s) (enough real building site found [%d+%d>=%d]) (switching to next subphase 3)\n", - project->debugName, real, site, project->amount); - if (!project->waitFinished) - { - fprintf(logFile, "(%s) (deblocking [%d, %d])\n", project->debugName, project->blocking, project->critical); - project->blocking=false; - project->critical=false; - } - } - } - else if (project->subPhase==3) - { - // balance workers: - - int isFree=team->stats.getWorkersBalance(); - Sint32 mainWorkers=project->mainWorkers; - Sint32 finalWorkers=project->finalWorkers; - if (isFree<=3) - { - if (mainWorkers>3) - mainWorkers=((3+mainWorkers)>>1); - //if (finalWorkers>3) - // finalWorkers=3; - } - else - { - if (mainWorkers>isFree) - mainWorkers=((isFree+mainWorkers)>>1); - //if (finalWorkers>isFree) - // finalWorkers=isFree; - } - - Building **myBuildings=team->myBuildings; - for (int i=0; itype->shortTypeNum==project->shortTypeNum) - { - if (b->type->isBuildingSite) - { - // a main building site - if (mainWorkers>=0 && b->maxUnitWorking!=mainWorkers) - { - b->maxUnitWorking=mainWorkers; - b->maxUnitWorkingLocal=mainWorkers; - b->update(); - project->timer=timer; - return shared_ptr(new OrderModifyBuilding(b->gid, mainWorkers)); - } - } - else - { - // a main building - if (finalWorkers>=0 && b->maxUnitWorking!=finalWorkers) - { - b->maxUnitWorking=finalWorkers; - b->maxUnitWorkingLocal=finalWorkers; - b->update(); - project->timer=timer; - return shared_ptr(new OrderModifyBuilding(b->gid, finalWorkers)); - } - } - } - else if (b->type->shortTypeNum==IntBuildingType::SWARM_BUILDING - || b->type->shortTypeNum==IntBuildingType::FOOD_BUILDING) - { - // food buildings - if (project->foodWorkers>=0 && b->maxUnitWorking!=project->foodWorkers) - { - b->maxUnitWorking=project->foodWorkers; - b->maxUnitWorkingLocal=project->foodWorkers; - b->update(); - project->timer=timer; - return shared_ptr(new OrderModifyBuilding(b->gid, project->foodWorkers)); - } - } - else if (b->type->maxUnitWorking!=0) - { - // others buildings: - if (project->otherWorkers>=0 && b->maxUnitWorking!=project->otherWorkers) - { - b->maxUnitWorking=project->otherWorkers; - b->maxUnitWorkingLocal=project->otherWorkers; - b->update(); - project->timer=timer; - return shared_ptr(new OrderModifyBuilding(b->gid, project->otherWorkers)); - } - } - } - } - - int real=buildingSum[project->shortTypeNum][0]; - int site=buildingSum[project->shortTypeNum][1]; - int sum=real+site; - - //printf("(%s) (all maxUnitWorking set)\n", project->debugName); - - if (real>=project->amount) - { - project->subPhase=6; - fprintf(logFile, "(%s) (building finished [%d+%d>=%d]) (switching to subphase 6).\n", - project->debugName, real, site, project->amount); - } - else if (sumamount) - { - project->subPhase=1; - fprintf(logFile, "(%s) (need more construction site [%d+%d<%d]) (switching back to subphase 1)\n", - project->debugName, real, site, project->amount); - } - else if (project->multipleStart) - { - fprintf(logFile, "(%s) (want more construction site [%d+%d>=%d])\n", - project->debugName, real, site, project->amount); - if (isFree>1) - { - project->subPhase=1; - fprintf(logFile, "(%s) (enough free workers %d) (switching back to subphase 1)\n", project->debugName, isFree); - } - else - { - project->subPhase=5; - fprintf(logFile, "(%s) (no more free workers) (switching to next subphase 5)\n", project->debugName); - } - } - else - { - project->subPhase=5; - fprintf(logFile, "(%s) (enough construction site [%d+%d>=%d]) (switching to next subphase 5)\n", - project->debugName, real, site, project->amount); - } - } - else if (project->subPhase==5) - { - // We simply wait for the building to be finished, - // and add free workers if available and project.waitFinished: - - if ((project->waitFinished || overWorkers) && enoughFreeWorkers()) - { - Building **myBuildings=team->myBuildings; - for (int i=0; itype->shortTypeNum==project->shortTypeNum && b->maxUnitWorkingmainWorkers) - { - //printf("(%s) (incrementing workers) isFree=%d, current=%d\n", - // project->debugName, isFree, b->maxUnitWorking); - b->maxUnitWorking++; - b->maxUnitWorkingLocal=b->maxUnitWorking; - b->update(); - project->timer=timer; - return shared_ptr(new OrderModifyBuilding(b->gid, b->maxUnitWorking)); - } - } - } - - int real=buildingSum[project->shortTypeNum][0]; - int site=buildingSum[project->shortTypeNum][1]; - int sum=real+site; - - if (real>=project->amount) - { - project->subPhase=6; - fprintf(logFile, "(%s) (building finished [%d+%d>=%d]) (switching to subphase 6).\n", - project->debugName, real, site, project->amount); - } - else if (sumamount) - { - project->subPhase=2; - fprintf(logFile, "(%s) (building destroyed! [%d+%d<%d]) (switching to subphase 2).\n", - project->debugName, real, site, project->amount); - } - } - else if (project->subPhase==6) - { - // balance final workers: - - if (project->blocking) - { - fprintf(logFile, "(%s) (deblocking [%d, %d])\n", project->debugName, project->blocking, project->critical); - project->blocking=false; - project->critical=false; - } - - if (project->finalWorkers>=0) - { - Sint32 finalWorkers=project->finalWorkers; - - Building **myBuildings=team->myBuildings; - for (int i=0; itype->shortTypeNum==project->shortTypeNum && b->maxUnitWorking!=finalWorkers) - { - assert(b->type->maxUnitWorking!=0); - fprintf(logFile, "(%s) (set finalWorkers [current=%d, final=%d])\n", - project->debugName, b->maxUnitWorking, finalWorkers); - b->maxUnitWorking=finalWorkers; - b->maxUnitWorkingLocal=finalWorkers; - b->update(); - project->timer=timer; - return shared_ptr(new OrderModifyBuilding(b->gid, finalWorkers)); - } - } - } - if (buildingSum[project->shortTypeNum][1]==0) - { - project->finished=true; - fprintf(logFile, "(%s) (all finalWorkers set) (project succeded)\n", project->debugName); - } - } - else - assert(false); - - return shared_ptr(); -} - -bool AICastor::enoughFreeWorkers() -{ - int totalWorkers=team->stats.getTotalUnits(WORKER); - int workersBalance=team->stats.getWorkersBalance(); - int partFree=(totalWorkers/strategy.isFreePart); - int minBalance; - if (buildsAmount<=0) - minBalance=-partFree; - else if (buildsAmount<=2) - minBalance=0; - else if (buildsAmount<=4) - minBalance=partFree; - else - minBalance=(partFree<<1); - if (foodLock) - minBalance+=3; - int minOverWorkers=minBalance+partFree; - - bool enough=(workersBalance>minBalance); - overWorkers=(workersBalance>minOverWorkers); - - assert(buildsAmount<1024); - static int oldEnough[1024]; - static bool first=true; - if (first) - { - memset(oldEnough, 2, 1024*sizeof(*oldEnough)); - first=false; - } - if ((oldEnough[buildsAmount]==2) || (enough!=oldEnough[buildsAmount])) - { - fprintf(logFile, "enoughFreeWorkers()=%d, workersBalance=%d, totalWorkers=%d, partFree=%d, buildsAmount=%d, minBalance=%d\n", - enough, workersBalance, totalWorkers, partFree, buildsAmount, minBalance); - oldEnough[buildsAmount]=enough; - } - return enough; -} - -void AICastor::computeCanSwim() -{ - //printf("computeCanSwim()...\n"); - // If our population has more healthy-working-units able to swimm than healthy-working-units - // unable to swimm then we choose to be able to go trough water: - Unit **myUnits=team->myUnits; - int sumCanSwim=0; - int sumCantSwim=0; - for (int i=0; itypeNum==WORKER && u->medical==0) - { - if (u->performance[SWIM]>0) - sumCanSwim++; - else - sumCantSwim++; - } - } - - canSwim=(sumCanSwim>sumCantSwim); - //printf("...computeCanSwim() done\n"); -} - -void AICastor::computeNeedSwim() -{ - int w=map->w; - int h=map->h; - size_t size=w*h; - - canSwim=false; - computeObstacleUnitMap(); - computeWorkRangeMap(); - - Sint32 baseCount=0; - for (size_t i=0; i(7*extendedCount)); - fprintf(logFile, "needSwim=%d\n", needSwim); - - computeCanSwim(); -} - -void AICastor::computeBuildingSum() -{ - for (int bi=0; bimyBuildings; - for (int i=0; ibuildingState==Building::WAITING_FOR_CONSTRUCTION && b->constructionResultState==Building::UPGRADE) - buildingLevels[b->type->shortTypeNum][1][b->type->level+1]++; - else - buildingLevels[b->type->shortTypeNum][b->type->isBuildingSite][b->type->level]++; - } - } - for (int bi=0; bi0) - if ((timer&8191)==0) - if (verbose) - printf("buildingLevels[%d][%d][%d]=%d\n", bi, si, li, buildingLevels[bi][si][li]); -} - -void AICastor::computeWarLevel() -{ - if (timer>strategy.warTimeTrigger) - { - fprintf(logFile, "timer=%d, strategy.warTimeTrigger=%d\n", timer, strategy.warTimeTrigger); - warTimeTriggerLevel++; - strategy.warTimeTrigger=strategy.warTimeTrigger+((1+strategy.warTimeTrigger)>>1); - } - int warTimeTriggerLevelUse=warTimeTriggerLevel; - if (warTimeTriggerLevelUse>2) - warTimeTriggerLevelUse=2; - - int sum=0; - for (int si=0; si<2; si++) - for (int li=strategy.warLevelTrigger; li<4; li++) - sum+=buildingLevels[IntBuildingType::ATTACK_BUILDING][si][li]; - if (sum>1) - warLevelTriggerLevel=2; - else if (sum>0) - warLevelTriggerLevel=1; - else - warLevelTriggerLevel=0; - - if (buildsAmount>strategy.warAmountTrigger) - warAmountTriggerLevel=2; - else if (buildsAmount>=strategy.warAmountTrigger) - warAmountTriggerLevel=1; - else - warAmountTriggerLevel=0; - warLevel=warTimeTriggerLevelUse+warLevelTriggerLevel+warAmountTriggerLevel; - - static int oldWarLevel=-1; - if (oldWarLevel!=warLevel) - { - fprintf(logFile, "warLevel=%d, warTimeTriggerLevelUse=%d, warLevelTriggerLevel=%d, warAmountTriggerLevel=%d\n", - warLevel, warTimeTriggerLevelUse, warLevelTriggerLevel, warAmountTriggerLevel); - oldWarLevel=warLevel; - } - - if (warLevel==0) - return; - - int warPowerSum=0; - Unit **myUnits=team->myUnits; - for (int i=0; imedical==Unit::MED_FREE && u->typeNum==WARRIOR) - warPowerSum+=u->performance[ATTACK_SPEED]*u->performance[ATTACK_STRENGTH]; - } - static int oldWarPowerSum=-1; - if (oldWarPowerSum!=warPowerSum) - { - fprintf(logFile, "warPowerSum=%d\n", warPowerSum); - oldWarPowerSum=warPowerSum; - } - - if (warPowerSumstrikeTimeTrigger || warPowerSum>strategy.strikeWarPowerTriggerUp) - { - onStrike=true; - } -} - -void AICastor::computeObstacleUnitMap() -{ - //printf("computeObstacleUnitMap()...\n"); - int w=map->w; - int h=map->h; - //int wMask=map->wMask; - //int hMask=map->hMask; - size_t size=w*h; - const auto& cases=map->cases; - Uint32 teamMask=team->me; - for (size_t i=0; i=256) && (c.terrain<256+16)) // !canSwim && isWatter ? - obstacleUnitMap[i]=0; - else - obstacleUnitMap[i]=1; - } - //printf("...computeObstacleUnitMap() done\n"); -} - - -void AICastor::computeObstacleBuildingMap() -{ - //printf("computeObstacleBuildingMap()...\n"); - int w=map->w; - int h=map->h; - //int wMask=map->wMask; - //int hMask=map->hMask; - //int hDec=map->hDec; - //int wDec=map->wDec; - size_t size=w*h; - const auto& cases=map->cases; - for (size_t i=0; i=16) // if (!isGrass) - obstacleBuildingMap[i]=0; - else if (c.ressource.type!=NO_RES_TYPE) - obstacleBuildingMap[i]=0; - else - obstacleBuildingMap[i]=1; - } - //printf("...computeObstacleBuildingMap() done\n"); -} - -void AICastor::computeSpaceForBuildingMap(int max) -{ - //printf("computeSpaceForBuildingMap()...\n"); - int w=map->w; - int h=map->h; - int wMask=map->wMask; - int hMask=map->hMask; - //int hDec=map->hDec; - //int wDec=map->wDec; - size_t size=w*h; - - memcpy(spaceForBuildingMap, obstacleBuildingMap, size); - - for (int i=1; iobs[i]) - min=obs[i]; - if (min!=0) - spaceForBuildingMap[wyx[0]]=min+1; - } - } - } - //printf("...computeSpaceForBuildingMap() done\n"); -} - -void AICastor::computeBuildingNeighbourMapOfBuilding(int bx, int by, int bw, int bh, int dw, int dh) -{ - //int w=map->w; - //int h=map->h; - int wMask=map->wMask; - int hMask=map->hMask; - //int hDec=map->hDec; - int wDec=map->wDec; - - //size_t size=w*h; - Uint8 *gradient=buildingNeighbourMap; - const auto& cases=map->cases; - - //Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; - - /*int bx=b->posX; - int by=b->posY; - int bw=b->type->width; - int bh=b->type->height;*/ - - // we skip building with already a neighbour: - bool neighbour=false; - //bool wheat=false; - for (int xi=bx-1; xi<=bx+bw; xi++) - { - int index; - index=(xi&wMask)+(((by-1 )&hMask)<w; - int h=map->h; - //size_t size=w*h; - - //int hDec=map->hDec; - int wDec=map->wDec; - - int wMask=map->wMask; - int hMask=map->hMask; - - Uint8 *gradient=buildingNeighbourMap; - //memset(gradient, 0, size); - Uint32 visionMask=team->me; - for (int y=0; ymapDiscovered[index]&visionMask)) - goto doubleBreak; - } - gradient[(y<game; - for (Sint32 ti=0; timapHeader.getNumberOfTeams(); ti++) - { - Team *team=game->teams[ti]; - assert(team); - if (!team) - continue; - Building **myBuildings=team->myBuildings; - for (int i=0; itype->isVirtual) - { - int bx=b->posX; - int by=b->posY; - int bw=b->type->width; - int bh=b->type->height; - computeBuildingNeighbourMapOfBuilding(bx, by, bw, bh, dw, dh); - } - } - } - - for (std::list::iterator bpi=game->buildProjects.begin(); bpi!=game->buildProjects.end(); bpi++) - { - int bx=bpi->posX&map->getMaskW(); - int by=bpi->posY&map->getMaskH(); - //int teamNumber=bpi->teamNumber; - Sint32 typeNum=(bpi->typeNum); - BuildingType *bt=globalContainer->buildingsTypes.get(typeNum); - int bw=bt->width; - int bh=bt->height; - computeBuildingNeighbourMapOfBuilding(bx, by, bw, bh, dw, dh); - } -} - -void AICastor::computeWorkPowerMap() -{ - int w=map->w; - int h=map->h; - int wMask=map->wMask; - int hMask=map->hMask; - //int hDec=map->hDec; - int wDec=map->wDec; - size_t size=w*h; - Uint8 *gradient=workPowerMap; - Uint8 maxRange=64; - if (maxRange>w/2) - maxRange=w/2; - if (maxRange>h/2) - maxRange=h/2; - - memset(gradient, 0, size); - - Unit **myUnits=team->myUnits; - for (int i=0; itypeNum==WORKER && u->medical==0 && u->activity!=Unit::ACT_UPGRADING) - { - int range=((u->hungry-u->trigHungry)>>1)/u->race->hungryness; - if (range<0) - continue; - //printf(" range=%d\n", range); - if (range>maxRange) - range=maxRange; - int ux=u->posX; - int uy=u->posY; - static const int reducer=3; - { - Uint8 *gp=&gradient[(ux&wMask)+((uy&hMask)<>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - for (int r=1; r>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - for (int dx=-r; dx<=r; dx++) - { - Uint8 *gp=&gradient[((ux+dx)&wMask)+(((uy +r)&hMask)<>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - for (int dy=(1-r); dy>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - for (int dy=(1-r); dy>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - } - } - } -} - - -void AICastor::computeWorkRangeMap() -{ - int w=map->w; - int h=map->h; - int wMask=map->wMask; - int hMask=map->hMask; - //int hDec=map->hDec; - int wDec=map->wDec; - size_t size=w*h; - Uint8 *gradient=workRangeMap; - - memcpy(gradient, obstacleUnitMap, size); - - Unit **myUnits=team->myUnits; - for (int i=0; itypeNum==WORKER && u->medical==0 && u->activity!=Unit::ACT_UPGRADING) - { - int range=((u->hungry-u->trigHungry)>>1)/u->race->hungryness; - if (range<0) - continue; - //printf(" range=%d\n", range); - if (range>255) - range=255; - int index=(u->posX&wMask)+((u->posY&hMask)<w; - int h=map->h; - //int wMask=map->wMask; - //int hMask=map->hMask; - //int hDec=map->hDec; - //int wDec=map->wDec; - size_t size=w*h; - - for (size_t i=0; i>5); - if (workAbility>255) - workAbility=255; - - workAbilityMap[i]=(Uint8)workAbility; - } -} - -void AICastor::computeHydratationMap() -{ -fprintf(logFile, "computeHydratationMap()...\n"); - int w=map->w; - int h=map->h; - int wMask=map->wMask; - int hMask=map->hMask; - //int hDec=map->hDec; - int wDec=map->wDec; - size_t size=w*h; - - Uint16 *gradient=(Uint16 *)malloc(2*size); - memset(gradient, 0, 2*size); - const auto& cases=map->cases; - static const int range=16; - for (int y=0; y=256)&&(t<256+16)) // if SAND - for (int r=1; r>4; - if (value<255) - hydratationMap[i]=value; - else - hydratationMap[i]=255; - } - free(gradient); - fprintf(logFile, "...computeHydratationMap() done\n"); -} - -void AICastor::computeNotGrassMap() -{ - fprintf(logFile, "computeNotGrassMap()...\n"); - int w=map->w; - int h=map->h; - //int wMask=map->wMask; - //int hMask=map->hMask; - size_t size=w*h; - - memset(notGrassMap, 0, size); - - const auto& cases=map->cases; - for (size_t i=0; i16)// if !GRASS - notGrassMap[i]=16; - } - - updateGlobalGradientNoObstacle(notGrassMap); - fprintf(logFile, "...computeNotGrassMap() done\n"); -} - -void AICastor::computeWheatCareMap() -{ - int w=map->w; - int h=map->h; - //int wMask=map->wMask; - //int hMask=map->hMask; - //int hDec=map->hDec; - //int wDec=map->wDec; - size_t size=w*h; - size_t sizeMask=(size-1); - //Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; - //Case *cases=map->cases; - //Uint32 teamMask=team->me; - - Uint8 *temp=wheatCareMap[1]; - wheatCareMap[1]=wheatCareMap[0]; - wheatCareMap[0]=temp; - - memcpy(wheatCareMap[0], obstacleUnitMap, size); - for (size_t i=0; i<=sizeMask; i++) - if (wheatCareMap[0][i]!=0 && notGrassMap[i]==15 && hydratationMap[i]>0 - && ((wheatCareMap[1][i]>7) - || ((oldWheatGradient[3][i]==255 || oldWheatGradient[2][i]==255) && (oldWheatGradient[1][i]<255 || oldWheatGradient[0][i]<255)))) - { - if (oldWheatGradient[1][i]<254 || oldWheatGradient[0][i]<254) - wheatCareMap[0][i]=10; - else - wheatCareMap[0][i]=8; - } - map->updateGlobalGradientSlow(wheatCareMap[0]); -} - -void AICastor::computeWheatGrowthMap() -{ - if (lastWheatGrowthMapComputed==timer) - return; - - int w=map->w; - int h=map->h; - //int wMask=map->wMask; - //int hMask=map->hMask; - //int hDec=map->hDec; - //int wDec=map->wDec; - size_t size=w*h; - Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; - - memcpy(wheatGrowthMap, obstacleBuildingMap, size); - - for (size_t i=0; i>3); - - map->updateGlobalGradientSlow(wheatGrowthMap); - - for (size_t i=0; i1) - { - Uint8 *p=&wheatGrowthMap[i]; - Uint8 growth=*p; - if (growth>care) - (*p)=growth-care; - else - (*p)=1; - } - } - lastWheatGrowthMapComputed=timer; -} - -void AICastor::computeEnemyPowerMap() -{ - if (lastEnemyPowerMapComputed==timer) - return; - lastEnemyPowerMapComputed=timer; - - int w=map->w; - int h=map->h; - int wMask=map->wMask; - int hMask=map->hMask; - //int hDec=map->hDec; - int wDec=map->wDec; - size_t size=w*h; - Uint8 *gradient=enemyPowerMap; - - memset(gradient, 0, size); - - for (int ti=0; timapHeader.getNumberOfTeams(); ti++) - { - Team *enemyTeam=game->teams[ti]; - Uint32 me=team->me; - if ((team->enemies&enemyTeam->me)==0) - continue; - Building **enemyBuildings=enemyTeam->myBuildings; - for (int bi=0; biseenByMask&me)==0)) - continue; - int bx=b->posX; - int by=b->posY; - static const int reducer=3; - static const int range=32; // max 32 - { - Uint8 *gp=&gradient[(bx&wMask)+((by&hMask)<>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - for (int r=1; r>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - for (int dx=-r; dx<=r; dx++) - { - Uint8 *gp=&gradient[((bx+dx)&wMask)+(((by +r)&hMask)<>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - for (int dy=(1-r); dy>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - for (int dy=(1-r); dy>reducer); - if (sum>255) - sum=255; - *gp=sum; - } - } - } - } -} - -void AICastor::computeEnemyRangeMap() -{ - if (lastEnemyRangeMapComputed==timer) - return; - lastEnemyRangeMapComputed=timer; - - int w=map->w; - int h=map->h; - int wMask=map->wMask; - int hMask=map->hMask; - //int hDec=map->hDec; - int wDec=map->wDec; - size_t size=w*h; - Uint8 *gradient=enemyRangeMap; - - memcpy(gradient, obstacleUnitMap, size); - - for (int ti=0; timapHeader.getNumberOfTeams(); ti++) - { - Team *enemyTeam=game->teams[ti]; - Uint32 me=team->me; - - if ((team->enemies & enemyTeam->me)==0) - continue; - Building **enemyBuildings=enemyTeam->myBuildings; - for (int bi=0; biseenByMask&me)==0) || b->type->isBuildingSite) - continue; - int bx=b->posX; - int by=b->posY; - int bw=b->type->width; - int bh=b->type->height; - for (int dy=by; dyupdateGlobalGradientSlow(gradient); -} - -void AICastor::computeEnemyWarriorsMap() -{ - if (lastEnemyWarriorsMapComputed==timer) - return; - lastEnemyWarriorsMapComputed=timer; - if (verbose) - printf("computeEnemyWarriorsMap()\n"); - - int w=map->w; - int h=map->h; - //int wMask=map->wMask; - //int hMask=map->hMask; - //int hDec=map->hDec; - //int wDec=map->wDec; - size_t size=w*h; - Uint8 *gradient=enemyWarriorsMap; - - memcpy(gradient, obstacleUnitMap, size); - for (size_t i=0; ifogOfWar[i]&team->me)==0) - continue; - Uint16 guid=map->cases[i].groundUnit; - if (guid==NOGUID) - continue; - Uint32 teamMask=(1<<(guid>>10)); - if ((teamMask&team->enemies)==0) - continue; - gradient[i]=32; - } - map->updateGlobalGradientSlow(gradient); -} - -boost::shared_ptrAICastor::findGoodBuilding(Sint32 typeNum, bool food, bool defense, bool critical) -{ - int w=map->w; - int h=map->h; - int bw=globalContainer->buildingsTypes.get(typeNum)->width; - int bh=globalContainer->buildingsTypes.get(typeNum)->height; - assert(bw==bh); - //int hDec=map->hDec; - int wDec=map->wDec; - int wMask=map->wMask; - int hMask=map->hMask; - size_t size=w*h; - Uint32 *mapDiscovered=&(map->mapDiscovered[0]); - Uint32 me=team->me; - fprintf(logFile, "findGoodBuilding(%d, %d, %d) b=(%d, %d)\n", typeNum, food, critical, bw, bh); - - // minWork computation: - Sint32 bestWorkScore=2; - for (size_t i=0; i15*4) - minWork=15*4; - } - else - { - if (minWork>30*4) - minWork=30*4; - } - fprintf(logFile, " bestWorkScore=%d, minWork=%d\n", bestWorkScore, minWork/4); - - // wheatGradientLimit computation: - Uint32 wheatGradientLimit; - if (food) - { - if (critical) - wheatGradientLimit=(255-16)*4; - else - wheatGradientLimit=(255-4)*4; - } - else - { - if (critical) - wheatGradientLimit=(255-5)*4; - else - wheatGradientLimit=(255-7)*4; - } - fprintf(logFile, " wheatGradientLimit=%d\n", wheatGradientLimit/4); - - // we find the best place possible: - size_t bestIndex=0; - Sint32 bestScore=0; - - //wheatLimit=(wheatLimit<<2); - //printf(" (scaled) minWork=%d, wheatLimit=%d\n", minWork, wheatLimit); - - Uint8 *wheatGradientMap=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; - memset(goodBuildingMap, 0, size); - - for (int y=0; ywheatGradientLimit) - continue; - //if (wheatGrowth>wheatGrowthLimit) - // continue; - } - } - //goodBuildingMap[corner0]=4; - - Uint32 enemyRange=enemyRangeMap[corner0]+enemyRangeMap[corner1]+enemyRangeMap[corner2]+enemyRangeMap[corner3]; - if (enemyRange>4*(255-8)) - continue; - //goodBuildingMap[corner0]=5; - - Sint32 wheatGrowth=wheatGrowthMap[corner0]+wheatGrowthMap[corner1]+wheatGrowthMap[corner2]+wheatGrowthMap[corner3]; - - Uint8 neighbour=buildingNeighbourMap[corner0]; - Uint8 directNeighboursCount=(neighbour>>1)&7; // [0, 7] - Uint8 farNeighboursCount=(neighbour>>5)&7; // [0, 7] - if ((neighbour&1)||(directNeighboursCount>1)) - continue; - - //goodBuildingMap[corner0]=6; - - Sint32 score; - if (defense) - score=((work<<1)+wheatGradient+(enemyRange<<4))*(16+(directNeighboursCount<<2)+farNeighboursCount); - else if (food) - score=((wheatGrowth<<8)+work+(wheatGradient>>1)-enemyRange)*(8+(directNeighboursCount<<2)+farNeighboursCount); - else - score=(4096+work-(wheatGrowth<<8)-enemyRange)*(8+(directNeighboursCount<<2)+farNeighboursCount); - - if (defense) - { - if (score<0) - goodBuildingMap[corner0]=0; - else if ((score>>12)>=255) - goodBuildingMap[corner0]=255; - else - goodBuildingMap[corner0]=(score>>12); - } - - if (bestScore0) - { - fprintf(logFile, " found a cool place"); - fprintf(logFile, " score=%d, wheatGrowth=%d, wheatGradientMap=%d, work=%d\n", - bestScore, wheatGrowthMap[bestIndex], wheatGradientMap[bestIndex], workAbilityMap[bestIndex]); - - Uint8 neighbour=buildingNeighbourMap[bestIndex]; - Uint8 directNeighboursCount=(neighbour>>1)&7; // [0, 7] - Uint8 farNeighboursCount=(neighbour>>5)&7; // [0, 7] - - fprintf(logFile, " directNeighboursCount=%d, farNeighboursCount=%d\n", - directNeighboursCount, farNeighboursCount); - - Sint32 x=(bestIndex&map->wMask); - Sint32 y=((bestIndex>>map->wDec)&map->hMask); - return shared_ptr(new OrderCreate(team->teamNumber, x, y, typeNum, 1, 1)); - } - - return shared_ptr(); -} - -void AICastor::computeRessourcesCluster() -{ - fprintf(logFile, "computeRessourcesCluster()\n"); - int w=map->w; - int h=map->h; - //int wMask=map->wMask; - int hMask=map->hMask; - size_t size=w*h; - - memset(ressourcesCluster, 0, size*2); - - //int i=0; - Uint8 old=0xFF; - Uint16 id=0; - bool usedid[65536]; - memset(usedid, 0, 65536*sizeof(bool)); - for (int y=0; ycases[map->coordToIndex(x, y)]; // case - const auto& r=c.ressource; // ressource - Uint8 rt=r.type; // ressources type - - int rci=x+y*w; // ressource cluster index - Uint16 *rcp=&ressourcesCluster[rci]; // ressource cluster pointer - Uint16 rc=*rcp; // ressource cluster - - if (rt==0xFF) - { - *rcp=0; - old=0xFF; - } - else - { - fprintf(logFile, "ressource rt=%d, at (%d, %d)\n", rt, x, y); - if (rt!=old) - { - fprintf(logFile, " rt!=old\n"); - id=1; - while (usedid[id]) - id++; - if (id) - usedid[id]=true; - old=rt; - fprintf(logFile, " id=%d\n", id); - } - if (rc!=id) - { - if (rc==0) - { - *rcp=id; - fprintf(logFile, " wrote.\n"); - } - else - { - Uint16 oldid=id; - usedid[oldid]=false; - id=rc; // newid - fprintf(logFile, " cleaning oldid=%d to id=%d.\n", oldid, id); - // We have to correct last ressourcesCluster values: - *rcp=id; - while (*rcp==oldid) - { - *rcp=id; - rcp--; - } - } - } - } - } - memcpy(ressourcesCluster+((y+1)&hMask)*w, ressourcesCluster+y*w, w*2); - } - - int used=0; - for (int id=1; id<65536; id++) - if (usedid[id]) - used++; - fprintf(logFile, "computeRessourcesCluster(), used=%d\n", used); -} - -void AICastor::updateGlobalGradientNoObstacle(Uint8 *gradient) -{ - //In this algotithm, "l" stands for one case at Left, "r" for one case at Right, "u" for Up, and "d" for Down. - // Warning, this is *nearly* a copy-past, 4 times, once for each direction. - int w=map->w; - int h=map->h; - int hMask=map->hMask; - int wMask=map->wMask; - //int hDec=map->hDec; - int wDec=map->wDec; - - for (int yi=0; yimax) - max=side[i]; - if (max==0) - gradient[wy+x]=0; - else - gradient[wy+x]=max-1; - } - } - } - - for (int y=hMask; y>=0; y--) - { - int wy=(y<max) - max=side[i]; - if (max==0) - gradient[wy+x]=0; - else - gradient[wy+x]=max-1; - } - } - } - - for (int x=0; xmax) - max=side[i]; - if (max==0) - gradient[wy+x]=0; - else - gradient[wy+x]=max-1; - } - } - } - - for (int x=wMask; x>=0; x--) - { - int xr=(x+1)&wMask; - for (int yi=x; yi<(x+h); yi++) - { - int wy=((yi&hMask)<max) - max=side[i]; - if (max==0) - gradient[wy+x]=0; - else - gradient[wy+x]=max-1; - } - } - } -} - -void AICastor::updateGlobalGradient(Uint8 *gradient) -{ - //In this algotithm, "l" stands for one case at Left, "r" for one case at Right, "u" for Up, and "d" for Down. - // Warning, this is *nearly* a copy-past, 4 times, once for each direction. - - int w=map->w; - int h=map->h; - int hMask=map->hMask; - int wMask=map->wMask; - //int hDec=map->hDec; - int wDec=map->wDec; - - for (int yi=0; yimax) - max=side[i]; - if (max==1) - gradient[wy+x]=1; - else - gradient[wy+x]=max-1; - } - } - } - - for (int y=hMask; y>=0; y--) - { - int wy=(y<max) - max=side[i]; - if (max==1) - gradient[wy+x]=1; - else - gradient[wy+x]=max-1; - } - } - } - - for (int x=0; xmax) - max=side[i]; - if (max==1) - gradient[wy+x]=1; - else - gradient[wy+x]=max-1; - } - } - } - - for (int x=wMask; x>=0; x--) - { - int xr=(x+1)&wMask; - for (int yi=x; yi<(x+h); yi++) - { - int wy=((yi&hMask)<max) - max=side[i]; - if (max==1) - gradient[wy+x]=1; - else - gradient[wy+x]=max-1; - } - } - } -} diff --git a/src/AIDescriptionScreen.h b/src/AIDescriptionScreen.h deleted file mode 100644 index f6dbbe9cb..000000000 --- a/src/AIDescriptionScreen.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef AIDescriptionScreen_h -#define AIDescriptionScreen_h - -#include "Glob2Screen.h" - -namespace GAGGUI -{ - class TextButton; - class TextArea; - class List; - class Text; -}; - -///This screen shows descriptions for the various types of AI -class AIDescriptionScreen : public Glob2Screen -{ -public: - ///This shows descriptions for the various types of AI - AIDescriptionScreen(); - - virtual void onAction(Widget *source, Action action, int par1, int par2); - - enum - { - OK, - }; - -private: - TextButton* ok; - TextArea *description; - List *ailist; - Text *title; -}; - -#endif diff --git a/src/AIEcho.cpp b/src/AIEcho.cpp deleted file mode 100644 index c9d5df579..000000000 --- a/src/AIEcho.cpp +++ /dev/null @@ -1,5931 +0,0 @@ -/* - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "AIEcho.h" -#include "Building.h" -#include -#include -#include -#include -#include -#include "BuildingsTypes.h" -#include "IntBuildingType.h" -#include "Game.h" -#include "GlobalContainer.h" -#include "Order.h" -#include -#include "Utilities.h" -#include "boost/tuple/tuple_io.hpp" -#include "Brush.h" - -using namespace AIEcho; -using namespace AIEcho::Gradients; -using namespace AIEcho::Construction; -using namespace AIEcho::Management; -using namespace AIEcho::Conditions; -using namespace AIEcho::SearchTools; -using namespace boost::logic; -using boost::shared_ptr; - - - -void AIEcho::signature_write(GAGCore::OutputStream *stream) -{ - stream->write("EchoSig", 7, "signature"); -} - - - -void AIEcho::signature_check(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - char signature[7]; - stream->read(signature, 7, "signature"); - if (memcmp(signature,"EchoSig", 7)!=0) - { - - std::cerr<<"Signature match failed. Expected \"EchoSig\", recieved \""<readEnterSection("Entity"); - EntityType type = static_cast(stream->readUint32("type")); - Entity* entity = NULL; - switch(type) - { - case Entities::EBuilding: - entity = new Entities::Building; - entity->load(stream, player, versionMinor); - break; - case Entities::EAnyTeamBuilding: - entity = new Entities::AnyTeamBuilding; - entity->load(stream, player, versionMinor); - break; - case Entities::EAnyBuilding: - entity = new Entities::AnyBuilding; - entity->load(stream, player, versionMinor); - break; - case Entities::ERessource: - entity = new Entities::Ressource; - entity->load(stream, player, versionMinor); - break; - case Entities::EAnyRessource: - entity = new Entities::AnyRessource; - entity->load(stream, player, versionMinor); - break; - case Entities::EWater: - entity = new Entities::Water; - entity->load(stream, player, versionMinor); - break; - case Entities::EPosition: - entity = new Entities::Position; - entity->load(stream, player, versionMinor); - break; - case Entities::ESand: - entity = new Entities::Sand; - entity->load(stream, player, versionMinor); - break; - }; - stream->readLeaveSection(); - return entity; -} - - - -void Entities::Entity::save_entity(Entity* entity, GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Entity"); - stream->writeUint32(entity->get_type(), "type"); - entity->save(stream); - stream->writeLeaveSection(); -} - - - -Entities::Building::Building(int building_type, int team, bool under_construction) : building_type(building_type), team(team), under_construction(under_construction) -{ - -} - - -bool Entities::Building::is_entity(Map* map, int posx, int posy) -{ - int building_id=map->getBuilding(posx, posy); - if(building_id!=NOGBID) - { - int team_id=::Building::GIDtoTeam(building_id); - if(team_id==team && - map->game->teams[team_id]->myBuildings[::Building::GIDtoID(building_id)]->typeNum==building_type && - (map->game->teams[team_id]->myBuildings[::Building::GIDtoID(building_id)]->constructionResultState==::Building::NO_CONSTRUCTION || under_construction) - ) - { - return true; - } - } - return false; -} - -bool Entities::Building::operator==(const Entity& rhs) -{ - if(typeid(rhs)==typeid(Entities::Building) && - static_cast(rhs).building_type==building_type && - static_cast(rhs).team==team && - static_cast(rhs).under_construction==under_construction - ) - return true; - return false; -} - - - -bool Entities::Building::can_change() -{ - return true; -} - - - -Entities::EntityType Entities::Building::get_type() -{ - return Entities::EBuilding; -} - - - -bool Entities::Building::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("Building"); - building_type = stream->readSint32("building_type"); - team = stream->readSint32("team"); - under_construction = stream->readUint8("under_construction"); - stream->readLeaveSection(); - return true; -} - - - -void Entities::Building::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Building"); - stream->writeSint32(building_type, "building_type"); - stream->writeSint32(team, "team"); - stream->writeUint8(under_construction, "under_construction"); - stream->writeLeaveSection(); -} - - - -Entities::AnyTeamBuilding::AnyTeamBuilding(int team, bool under_construction) : team(team), under_construction(under_construction) -{ - -} - - - -bool Entities::AnyTeamBuilding::is_entity(Map* map, int posx, int posy) -{ - int building_id=map->getBuilding(posx, posy); - if(building_id!=NOGBID) - { - int team_id=::Building::GIDtoTeam(building_id); - if(team_id==team && - (map->game->teams[team_id]->myBuildings[::Building::GIDtoID(building_id)]->constructionResultState==::Building::NO_CONSTRUCTION || under_construction) - ) - { - return true; - } - } - return false; -} - - - -bool Entities::AnyTeamBuilding::operator==(const Entity& rhs) -{ - if(typeid(rhs)==typeid(Entities::AnyTeamBuilding) && - static_cast(rhs).team==team && - static_cast(rhs).under_construction==under_construction - ) - return true; - return false; -} - - - -bool Entities::AnyTeamBuilding::can_change() -{ - return true; -} - - - -Entities::EntityType Entities::AnyTeamBuilding::get_type() -{ - return Entities::EAnyTeamBuilding; -} - - - -bool Entities::AnyTeamBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("AnyTeamBuilding"); - team = stream->readSint32("team"); - under_construction = stream->readUint8("under_construction"); - stream->readLeaveSection(); - return true; -} - - - -void Entities::AnyTeamBuilding::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("AnyTeamBuilding"); - stream->writeSint32(team, "team"); - stream->writeUint8(under_construction, "under_construction"); - stream->writeLeaveSection(); -} - - - -Entities::AnyBuilding::AnyBuilding(bool under_construction) : under_construction(under_construction) -{ -} - - - -bool Entities::AnyBuilding::is_entity(Map* map, int posx, int posy) -{ - int building_id=map->getBuilding(posx, posy); - if(building_id!=NOGBID) - { - int team_id=::Building::GIDtoTeam(building_id); - if(map->game->teams[team_id]->myBuildings[::Building::GIDtoID(building_id)]->constructionResultState==::Building::NO_CONSTRUCTION || under_construction) - return true; - } - return false; -} - - - -bool Entities::AnyBuilding::operator==(const Entity& rhs) -{ - if(typeid(rhs)==typeid(Entities::AnyBuilding) && - static_cast(rhs).under_construction==under_construction - ) - return true; - return false; -} - - - -bool Entities::AnyBuilding::can_change() -{ - return true; -} - - - -Entities::EntityType Entities::AnyBuilding::get_type() -{ - return Entities::EAnyBuilding; -} - - - -bool Entities::AnyBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("AnyBuilding"); - under_construction = stream->readUint8("under_construction"); - stream->readLeaveSection(); - return true; -} - - - -void Entities::AnyBuilding::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("AnyBuilding"); - stream->writeUint8(under_construction, "under_construction"); - stream->writeLeaveSection(); -} - - - -Entities::Ressource::Ressource(int ressource_type) : ressource_type(ressource_type) -{ - -} - - - -bool Entities::Ressource::is_entity(Map* map, int posx, int posy) -{ - if(map->isRessourceTakeable(posx, posy, ressource_type)) - { - return true; - } - return false; -} - - - -bool Entities::Ressource::operator==(const Entity& rhs) -{ - if(typeid(rhs)==typeid(Entities::Ressource) && - static_cast(rhs).ressource_type==ressource_type - ) - return true; - return false; -} - - - -bool Entities::Ressource::can_change() -{ - if(ressource_type==WOOD || ressource_type==CORN || ressource_type==ALGA) - return true; - return false; -} - - - -Entities::EntityType Entities::Ressource::get_type() -{ - return Entities::ERessource; -} - - - -bool Entities::Ressource::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("Ressource"); - ressource_type = stream->readSint32("ressource_type"); - stream->readLeaveSection(); - return true; -} - - - -void Entities::Ressource::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Ressource"); - stream->writeSint32(ressource_type, "ressource_type"); - stream->writeLeaveSection(); -} - - - -Entities::AnyRessource:: AnyRessource() -{ - -} - - - -bool Entities::AnyRessource:: is_entity(Map* map, int posx, int posy) -{ - if(map->isRessource(posx, posy)) - { - return true; - } - return false; -} - - - -bool Entities::AnyRessource::operator==(const Entity& rhs) -{ - if(typeid(rhs)==typeid(Entities::AnyRessource)) - return true; - return false; -} - - - -bool Entities::AnyRessource::can_change() -{ - return true; -} - - - -Entities::EntityType Entities::AnyRessource::get_type() -{ - return Entities::EAnyRessource; -} - - - -bool Entities::AnyRessource::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("AnyRessource"); - stream->readLeaveSection(); - return true; -} - - - -void Entities::AnyRessource::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("AnyRessource"); - stream->writeLeaveSection(); -} - - - -Entities::Water::Water() -{ - -} - - - -bool Entities::Water::is_entity(Map* map, int posx, int posy) -{ - if(map->isWater(posx, posy)) - { - return true; - } - return false; -} - - - -bool Entities::Water::operator==(const Entity& rhs) -{ - if(typeid(rhs)==typeid(Entities::Water)) - return true; - return false; -} - - - -bool Entities::Water::can_change() -{ - return false; -} - - - -Entities::EntityType Entities::Water::get_type() -{ - return Entities::EWater; -} - - - -bool Entities::Water::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("Water"); - stream->readLeaveSection(); - return true; -} - - - -void Entities::Water::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Water"); - stream->writeLeaveSection(); -} - - -Entities::Position::Position(int x, int y) : x(x), y(y) -{ - -} - - -bool Entities::Position::is_entity(Map* map, int posx, int posy) -{ - if(x==posx && y==posy) - { - return true; - } - return false; -} - - -bool Entities::Position::operator==(const Entity& rhs) -{ - if(typeid(rhs)==typeid(Entities::Position) && - static_cast(rhs).x==x && - static_cast(rhs).y==y) - return true; - return false; -} - - -bool Entities::Position::can_change() -{ - return false; -} - - -Entities::EntityType Entities::Position::get_type() -{ - return Entities::EPosition; -} - - -bool Entities::Position::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("Position"); - x=stream->readSint32("posX"); - y=stream->readSint32("posY"); - stream->readLeaveSection(); - return false; -} - - -void Entities::Position::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Position"); - stream->writeSint32(x, "posX"); - stream->writeSint32(y, "posy"); - stream->writeLeaveSection(); -} - - - -Entities::Sand::Sand() -{ - -} - - - -bool Entities::Sand::is_entity(Map* map, int posx, int posy) -{ - if(map->hasSand(posx, posy)) - { - return true; - } - return false; -} - - - -bool Entities::Sand::operator==(const Entity& rhs) -{ - if(typeid(rhs)==typeid(Entities::Sand)) - return true; - return false; -} - - - -bool Entities::Sand::can_change() -{ - return false; -} - - - -Entities::EntityType Entities::Sand::get_type() -{ - return Entities::ESand; -} - - - -bool Entities::Sand::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("Sand"); - stream->readLeaveSection(); - return true; -} - - - -void Entities::Sand::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Sand"); - stream->writeLeaveSection(); -} - - - -GradientInfo::GradientInfo() -{ - needs_updated=indeterminate; -} - - -GradientInfo::~GradientInfo() -{ - -} - - -void GradientInfo::add_source(Entities::Entity* source) -{ - sources.push_back(boost::shared_ptr(source)); -} - - -void GradientInfo::add_obstacle(Entities::Entity* obstacle) -{ - obstacles.push_back(boost::shared_ptr(obstacle)); -} - - -bool GradientInfo::match_source(Map* map, int posx, int posy) -{ - for(unsigned int x=0; xis_entity(map, posx, posy)) - return true; - return false; -} - - -bool GradientInfo::match_obstacle(Map* map, int posx, int posy) -{ - for(unsigned int x=0; xis_entity(map, posx, posy)) - return true; - return false; -} - - -bool GradientInfo::operator==(const GradientInfo& rhs) const -{ - if(sources.size()!=rhs.sources.size() || obstacles.size() != rhs.obstacles.size()) - return false; - for(unsigned int i=0; ican_change()) - { - needs_updated=true; - return true; - } - } - - for(unsigned int i=0; ican_change()) - { - needs_updated=true; - return true; - } - } - } - return false; -} - - - -bool GradientInfo::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("GradientInfo"); - - stream->readEnterSection("sources"); - int size=stream->readUint32("size"); - sources.resize(size); - for(int n=0; nreadEnterSection(n); - sources[n]=boost::shared_ptr(Entities::Entity::load_entity(stream, player, versionMinor)); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->readEnterSection("obstacles"); - size=stream->readUint32("size"); - obstacles.resize(size); - for(int n=0; nreadEnterSection(n); - obstacles[n]=boost::shared_ptr(Entities::Entity::load_entity(stream, player, versionMinor)); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->readLeaveSection(); - return true; -} - - - -void GradientInfo::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("GradientInfo"); - - stream->writeEnterSection("sources"); - stream->writeUint32(sources.size(), "size"); - for(unsigned n=0; nwriteEnterSection(n); - Entities::Entity::save_entity(sources[n].get(), stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->writeEnterSection("obstacles"); - stream->writeUint32(obstacles.size(), "size"); - for(unsigned n=0; nwriteEnterSection(n); - Entities::Entity::save_entity(obstacles[n].get(), stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->writeLeaveSection(); -} - - - -GradientInfo make_gradient_info(Entities::Entity* source) -{ - GradientInfo gi; - gi.add_source(source); - return gi; -} - - - -GradientInfo make_gradient_info_obstacle(Entities::Entity* source, Entities::Entity* obstacle) -{ - GradientInfo gi; - gi.add_source(source); - gi.add_obstacle(obstacle); - return gi; -} - - - -GradientInfo make_gradient_info(Entities::Entity* source1, Entities::Entity* source2) -{ - GradientInfo gi; - gi.add_source(source1); - gi.add_source(source2); - return gi; -} - - - -GradientInfo make_gradient_info_obstacle(Entities::Entity* source1, Entities::Entity* source2, Entities::Entity* obstacle) -{ - GradientInfo gi; - gi.add_source(source1); - gi.add_source(source2); - gi.add_obstacle(obstacle); - return gi; -} - - - -Gradient::Gradient(const GradientInfo& gi) -{ - gradient_info=gi; - width=0; -} - - -void Gradient::recalculate(Map* map) -{ - width=map->getW(); -// if(gradient==NULL) -// gradient=new Sint16[map->getW()*map->getH()]; -// std::fill(gradient, gradient+(map->getW()*map->getH()),0); - - gradient.resize(map->getW()*map->getH()); - std::fill(gradient.begin(), gradient.end(),0); - - std::queue positions; - for(int x=0; xgetW(); ++x) - { - for(int y=0; ygetH(); ++y) - { - if(gradient_info.match_source(map, x, y)) - { - gradient[get_pos(x, y)]=2; - positions.push(position(x, y)); - } - else if(gradient_info.match_obstacle(map, x, y)) - gradient[get_pos(x, y)]=1; - } - } - while(!positions.empty()) - { - position p=positions.front(); - positions.pop(); - - int left=p.x-1; - if(left<0) - left+=map->getW(); - int right=p.x+1; - if(right>=map->getW()) - right-=map->getW(); - int up=p.y-1; - if(up<0) - up+=map->getH(); - int down=p.y+1; - if(down>=map->getH()) - down-=map->getH(); - int center_h=p.x; - int center_y=p.y; - int n=gradient[get_pos(center_h, center_y)]; - - if(gradient[get_pos(left, up)]==0) - { - gradient[get_pos(left, up)]=n+1; - positions.push(position(left, up)); - } - - if(gradient[get_pos(center_h, up)]==0) - { - gradient[get_pos(center_h, up)]=n+1; - positions.push(position(center_h, up)); - } - - if(gradient[get_pos(right, up)]==0) - { - gradient[get_pos(right, up)]=n+1; - positions.push(position(right, up)); - } - - if(gradient[get_pos(left, center_y)]==0) - { - gradient[get_pos(left, center_y)]=n+1; - positions.push(position(left, center_y)); - } - - if(gradient[get_pos(right, center_y)]==0) - { - gradient[get_pos(right, center_y)]=n+1; - positions.push(position(right, center_y)); - } - - if(gradient[get_pos(left, down)]==0) - { - gradient[get_pos(left, down)]=n+1; - positions.push(position(left, down)); - } - - if(gradient[get_pos(center_h, down)]==0) - { - gradient[get_pos(center_h, down)]=n+1; - positions.push(position(center_h, down)); - } - - if(gradient[get_pos(right, down)]==0) - { - gradient[get_pos(right, down)]=n+1; - positions.push(position(right, down)); - } - - } -} - - -int Gradient::get_height(int posx, int posy) const -{ - return gradient[get_pos(posx, posy)]-2; -} - - - -GradientManager::GradientManager(Map* map) : map(map), cur_update(0), timer(0) -{ -} - - -Gradient& GradientManager::get_gradient(const GradientInfo& gi) -{ - for(std::vector >::iterator i=gradients.begin(); i!=gradients.end(); ++i) - { - if((*i)->get_gradient_info() == gi) - { - if(ticks_since_update[i-gradients.begin()]>150) - { - ticks_since_update[i-gradients.begin()]=0; - (*i)->recalculate(map); - } - return **i; - } - } - - //Did not find a matching gradient - gradients.push_back(boost::shared_ptr(new Gradient(gi))); - (*(gradients.end()-1))->recalculate(map); - ticks_since_update.push_back(0); - return **(gradients.end()-1); -} - - -void GradientManager::queue_gradient(const GradientInfo& gi) -{ - for(unsigned i=0; iget_gradient_info() == gi) - { - if(gi.needs_updating()) - { - queuedGradients.push(i); - } - return; - } - } - //Did not find a matching gradient - gradients.push_back(boost::shared_ptr(new Gradient(gi))); - ticks_since_update.push_back(200); - queuedGradients.push(gradients.size()-1); -} - - -bool GradientManager::is_updated(const GradientInfo& gi) -{ - for(std::vector >::iterator i=gradients.begin(); i!=gradients.end(); ++i) - { - if((*i)->get_gradient_info() == gi) - { - if(ticks_since_update[i-gradients.begin()]>150 && (*i)->get_gradient_info().needs_updating()) - { - return false; - } - return true; - } - } - //If the gradient hasn't been queued to be updated, consider it updated, - //and it will be calculated on request - return true; -} - - -void GradientManager::update() -{ - timer++; - std::transform(ticks_since_update.begin(), ticks_since_update.end(), ticks_since_update.begin(), increment); - - if((timer%1)==0 && !queuedGradients.empty()) - { - int g=queuedGradients.front(); - if(ticks_since_update[g]>50) - { - gradients[g]->recalculate(map); - ticks_since_update[g]=0; - } - queuedGradients.pop(); - return; - } -} - - - -Constraint* Constraint::load_constraint(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("Constraint"); - ConstraintType type=static_cast(stream->readUint32("type")); - Constraint* constraint=NULL; - switch(type) - { - case CTMinimumDistance: - constraint=new MinimumDistance; - constraint->load(stream, player, versionMinor); - break; - case CTMaximumDistance: - constraint=new MaximumDistance; - constraint->load(stream, player, versionMinor); - break; - case CTMinimizedDistance: - constraint=new MinimizedDistance; - constraint->load(stream, player, versionMinor); - break; - case CTMaximizedDistance: - constraint=new MaximizedDistance; - constraint->load(stream, player, versionMinor); - break; - case CTCenterOfBuilding: - constraint=new CenterOfBuilding; - constraint->load(stream, player, versionMinor); - break; - case CTSinglePosition: - constraint=new SinglePosition; - constraint->load(stream, player, versionMinor); - break; - } - stream->readLeaveSection(); - return constraint; -} - - - -void Constraint::save_constraint(Constraint* constraint, GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Constraint"); - stream->writeUint32(constraint->get_type(), "type"); - constraint->save(stream); - stream->writeLeaveSection(); -} - - - -MinimumDistance::MinimumDistance(const Gradients::GradientInfo& gi, int distance) : gi(gi), gradient_cache(NULL), distance(distance) -{ - -} - - -int MinimumDistance::calculate_constraint(Echo& echo, int x, int y) -{ - return 0; -} - - -bool MinimumDistance::passes_constraint(Echo& echo, int x, int y) -{ - if(gradient_cache==NULL) - gradient_cache=&echo.get_gradient_manager().get_gradient(gi); - int height=gradient_cache->get_height(x, y); - if(height==-2) - return false; - if(height>=distance) - return true; - return false; -} - - -ConstraintType MinimumDistance::get_type() -{ - return CTMinimumDistance; -} - - - -bool MinimumDistance::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("MinimumDistance"); - distance = stream->readSint32("distance"); - gi.load(stream, player, versionMinor); - stream->readLeaveSection(); - return true; -} - - - -void MinimumDistance::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("MinimumDistance"); - stream->writeSint32(distance, "distance"); - gi.save(stream); - stream->writeLeaveSection(); -} - - - -MaximumDistance::MaximumDistance(const Gradients::GradientInfo& gi, int distance) : gi(gi), gradient_cache(NULL), distance(distance) -{ - -} - - -int MaximumDistance::calculate_constraint(Echo& echo, int x, int y) -{ - return 0; -} - - -bool MaximumDistance::passes_constraint(Echo& echo, int x, int y) -{ - if(gradient_cache==NULL) - gradient_cache=&echo.get_gradient_manager().get_gradient(gi); - int height=gradient_cache->get_height(x, y); - if(height==-2) - return false; - if(height<=distance) - return true; - return false; -} - - -ConstraintType MaximumDistance::get_type() -{ - return CTMaximumDistance; -} - - - -bool MaximumDistance::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("MaximumDistance"); - distance = stream->readSint32("distance"); - gi.load(stream, player, versionMinor); - stream->readLeaveSection(); - return true; -} - - - -void MaximumDistance::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("MaximumDistance"); - stream->writeSint32(distance, "distance"); - gi.save(stream); - stream->writeLeaveSection(); -} - - - -MinimizedDistance::MinimizedDistance(const Gradients::GradientInfo& gi, int weight) : gi(gi), gradient_cache(NULL), weight(weight) -{ - -} - - -int MinimizedDistance::calculate_constraint(Echo& echo, int x, int y) -{ - if(gradient_cache==NULL) - gradient_cache=&echo.get_gradient_manager().get_gradient(gi); - return -(gradient_cache->get_height(x, y) * weight); -} - - -bool MinimizedDistance::passes_constraint(Echo& echo, int x, int y) -{ - if(gradient_cache==NULL) - gradient_cache=&echo.get_gradient_manager().get_gradient(gi); - return gradient_cache->get_height(x, y)!=-2; -} - - -ConstraintType MinimizedDistance::get_type() -{ - return CTMinimizedDistance; -} - - - -bool MinimizedDistance::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("MinimizedDistance"); - weight = stream->readSint32("weight"); - gi.load(stream, player, versionMinor); - stream->readLeaveSection(); - return true; -} - - - -void MinimizedDistance::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("MinimizedDistance"); - stream->writeSint32(weight, "weight"); - gi.save(stream); - stream->writeLeaveSection(); -} - - - -MaximizedDistance::MaximizedDistance(const Gradients::GradientInfo& gi, int weight) : gi(gi), gradient_cache(NULL), weight(weight) -{ - -} - - -int MaximizedDistance::calculate_constraint(Echo& echo, int x, int y) -{ - if(gradient_cache==NULL) - gradient_cache=&echo.get_gradient_manager().get_gradient(gi); - return gradient_cache->get_height(x, y) * weight; -} - - -bool MaximizedDistance::passes_constraint(Echo& echo, int x, int y) -{ - if(gradient_cache==NULL) - gradient_cache=&echo.get_gradient_manager().get_gradient(gi); - return gradient_cache->get_height(x, y)!=-2; -} - - -ConstraintType MaximizedDistance::get_type() -{ - return CTMaximizedDistance; -} - - - -bool MaximizedDistance::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("MaximizedDistance"); - weight = stream->readSint32("weight"); - gi.load(stream, player, versionMinor); - stream->readLeaveSection(); - return true; -} - - - -void MaximizedDistance::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("MaximizedDistance"); - stream->writeSint32(weight, "weight"); - gi.save(stream); - stream->writeLeaveSection(); -} - - - -CenterOfBuilding::CenterOfBuilding(int gbid) : gbid(gbid) -{ - -} - - - -int CenterOfBuilding::calculate_constraint(Echo& echo, int x, int y) -{ - return 0; -} - - - -bool CenterOfBuilding::passes_constraint(Echo& echo, int x, int y) -{ - Building* b=echo.player->game->teams[Building::GIDtoTeam(gbid)]->myBuildings[Building::GIDtoID(gbid)]; - if(b) - { - if((b->posX+b->type->width/2)==x && (b->posY+b->type->height/2)==y) - { - return true; - } - } - return false; -} - - -ConstraintType CenterOfBuilding::get_type() -{ - return CTCenterOfBuilding; -} - - - -bool CenterOfBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("CenterOfBuilding"); - gbid = stream->readSint32("gbid"); - stream->readLeaveSection(); - return true; -} - - - -void CenterOfBuilding::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("CenterOfBuilding"); - stream->writeSint32(gbid, "gbid"); - stream->writeLeaveSection(); -} - - - -SinglePosition::SinglePosition(int posx, int posy) : posx(posx), posy(posy) -{ - -} - - - -int SinglePosition::calculate_constraint(Echo& echo, int x, int y) -{ - return 0; -} - - - -bool SinglePosition::passes_constraint(Echo& echo, int x, int y) -{ - if(posx==x && posy==y) - return true; - return false; -} - - - -ConstraintType SinglePosition::get_type() -{ - return CTSinglePosition; -} - - - -bool SinglePosition::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("SinglePosition"); - posx = stream->readSint32("posx"); - posy = stream->readSint32("posy"); - stream->readLeaveSection(); - return true; -} - - - -void SinglePosition::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("SinglePosition"); - stream->writeSint32(posx, "posx"); - stream->writeSint32(posy, "posy"); - stream->writeLeaveSection(); -} - - - -BuildingOrder::BuildingOrder(int building_type, int number_of_workers) : building_type(building_type), number_of_workers(number_of_workers) -{ - -} - - - -bool BuildingOrder::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("BuildingOrder"); - - building_type=stream->readUint32("building_type"); - number_of_workers=stream->readUint32("number_of_workers"); - - stream->readEnterSection("constraints"); - Uint32 size = stream->readUint32("size"); - constraints.resize(size); - for(unsigned x=0; xreadEnterSection(x); - constraints[x] = boost::shared_ptr(Constraint::load_constraint(stream, player, versionMinor)); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - - stream->readEnterSection("conditions"); - size = stream->readUint32("size"); - conditions.resize(size); - for(unsigned x=0; xreadEnterSection(x); - conditions[x] = boost::shared_ptr(Condition::load_condition(stream, player, versionMinor)); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - stream->readLeaveSection(); - return true; -} - - - -void BuildingOrder::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("BuildingOrder"); - - stream->writeUint32(building_type, "building_type"); - stream->writeUint32(number_of_workers, "number_of_workers"); - - stream->writeEnterSection("constraints"); - stream->writeUint32(constraints.size(), "size"); - for(unsigned x=0; xwriteEnterSection(x); - Constraint::save_constraint(constraints[x].get(), stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->writeEnterSection("conditions"); - stream->writeUint32(conditions.size(), "size"); - for(unsigned x=0; xwriteEnterSection(x); - Condition::save_condition(conditions[x].get(), stream); - stream->writeLeaveSection(); - } - - stream->writeLeaveSection(); - stream->writeLeaveSection(); -} - - - -void BuildingOrder::add_constraint(Constraint* constraint) -{ - constraints.push_back(boost::shared_ptr(constraint)); -} - - -void BuildingOrder::add_condition(Condition* condition) -{ - conditions.push_back(boost::shared_ptr(condition)); -} - - - -position BuildingOrder::find_location(Echo& echo, Map* map, GradientManager& manager) -{ - position best(0,0); - Player* player=echo.player; - int best_score=std::numeric_limits::min(); - BuildingType* type=globalContainer->buildingsTypes.getByType(IntBuildingType::typeFromShortNumber(building_type), 0, true); - bool check_flag=false; - //If theres no type for a construction zone, then this is a flag - if(type==NULL) - { - type=globalContainer->buildingsTypes.getByType(IntBuildingType::typeFromShortNumber(building_type), 0, false); - check_flag=true; - } - - for(int x=0; xgetW(); ++x) - { - for(int y=0; ygetH(); ++y) - { - if(!check_flag && !map->isHardSpaceForBuilding(x, y, type->width, type->height)) - continue; - - if(check_flag && echo.get_flag_map().get_flag(x, y)!=NOGBID) - continue; - int score=0; - bool passes=true; - for(std::vector >::iterator i=constraints.begin(); i!=constraints.end(); ++i) - { - for(int x2=0; x2width && passes; ++x2) - for(int y2=0; y2height && passes; ++y2) - if((x2==0 || y2==0 || x2==type->width-1 || y2==type->height-1)) - { - if(!(*i)->passes_constraint(echo, map->normalizeX(x+x2), map->normalizeY(y+y2))) - { - passes=false; - } - } - if(!passes) - { - break; - } - - if(!check_flag && (!map->isMapDiscovered(x, y, player->team->allies) || - !map->isMapDiscovered(x+type->width-1, y+type->height-1, player->team->allies)) - ) - { - passes=false; - break; - } - score+=(*i)->calculate_constraint(echo, map->normalizeX(x), map->normalizeY(y)); - score+=(*i)->calculate_constraint(echo, map->normalizeX(x+type->width-1), map->normalizeY(y+type->height-1)); - score+=(*i)->calculate_constraint(echo, map->normalizeX(x), map->normalizeY(y+type->height-1)); - score+=(*i)->calculate_constraint(echo, map->normalizeX(x+type->width-1), map->normalizeY(y)); - } - if(!passes) - continue; - if(score>best_score) - { - best=position(x, y); - best_score=score; - } - } - } - - return best; -} - - - -boost::logic::tribool BuildingOrder::passes_conditions(Echo& echo) -{ - for(unsigned int i=0; ipasses(echo); - if(passes) - continue; - else if(!passes) - return false; - else - return indeterminate; - - } - - for(unsigned n=0; nget_gradient_info()) - { - bool is_updated=echo.get_gradient_manager().is_updated(*constraints[n]->get_gradient_info()); - if(!is_updated) - return false; - } - } - - return true; -} - - - -void BuildingOrder::queue_gradients(Gradients::GradientManager& manager) -{ - for(unsigned n=0; nget_gradient_info()) - { - manager.queue_gradient(*constraints[n]->get_gradient_info()); - } - } -} - - -FlagMap::FlagMap(Echo& echo) : flagmap(echo.player->map->getW()*echo.player->map->getH(), NOGBID), width(echo.player->map->getW()), echo(echo) -{ -} - - - -int FlagMap::get_flag(int x, int y) -{ - return flagmap[y*width+x]; -} - - - -void FlagMap::set_flag(int x, int y, int gid) -{ - flagmap[y*width+x]=gid; -} - - - -bool FlagMap::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("FlagMap"); - stream->readEnterSection("flagmap"); - Uint32 size=stream->readUint32("size"); - flagmap.resize(size); - for (Uint32 flagmap_index = 0; flagmap_index < size; flagmap_index++) - { - stream->readEnterSection(flagmap_index); - flagmap[flagmap_index]=stream->readUint32("gid"); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - width=stream->readUint32("width"); - stream->readLeaveSection(); - return true; -} - - - -void FlagMap::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("FlagMap"); - stream->writeEnterSection("flagmap"); - stream->writeUint32(flagmap.size(), "size"); - for (Uint32 flagmap_index = 0; flagmap_index < flagmap.size(); flagmap_index++) - { - stream->writeEnterSection(flagmap_index); - stream->writeUint32(flagmap[flagmap_index], "gid"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - stream->writeUint32(width, "width"); - stream->writeLeaveSection(); -} - - - -BuildingRegister::BuildingRegister(Player* player, Echo& echo) : building_id(0), player(player), echo(echo) -{ - -} - - - -void BuildingRegister::initiate() -{ - for(int i=0; iteam->myBuildings[i]; - if(b!=NULL) - { - found_buildings[building_id++]=boost::make_tuple(b->posX, b->posY, b->type->shortTypeNum, b->gid, false); - } - } -} - - - -unsigned int BuildingRegister::register_building() -{ - pending_buildings[building_id]=boost::make_tuple(-1, -1, -1, -1); - return building_id++; -} - - - -void BuildingRegister::issue_order(int id, int x, int y, int building_type) -{ - pending_buildings[id]=boost::make_tuple(x, y, building_type, 0); -} - - - -void BuildingRegister::remove_building(int id) -{ - pending_buildings.erase(id); -} - - - -bool BuildingRegister::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("BuildingRegister"); - - stream->readEnterSection("pending_buildings"); - Uint32 pending_size=stream->readUint32("size"); - for(Uint32 pending_index=0; pending_indexreadEnterSection(pending_index); - Uint32 id=stream->readSint32("echo_building_id"); - Uint32 x=stream->readSint32("xpos"); - Uint32 y=stream->readSint32("ypos"); - Uint32 type=stream->readSint32("building_type"); - Uint32 ticks=stream->readSint32("ticks_since_registered"); - pending_buildings[id]=boost::make_tuple(x, y, type, ticks); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->readEnterSection("found_buildings"); - Uint32 found_size=stream->readUint32("size"); - for(Uint32 found_index=0; found_indexreadEnterSection(found_index); - Uint32 id=stream->readUint32("echo_building_id"); - Uint32 xpos=stream->readUint32("xpos"); - Uint32 ypos=stream->readUint32("ypos"); - Uint32 building_type=stream->readUint32("building_type"); - Uint32 gid=stream->readUint32("gid"); - Uint8 upgrade_status=stream->readUint8("upgrade_status"); - boost::logic::tribool t; - if(upgrade_status==0) - t=false; - else if(upgrade_status==1) - t=true; - else - t=indeterminate; - found_buildings[id]=boost::make_tuple(xpos, ypos, building_type, gid, t); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - building_id=stream->readUint32("building_id"); - stream->readLeaveSection(); - return true; -} - - - -void BuildingRegister::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("BuildingRegister"); - - stream->writeEnterSection("pending_buildings"); - unsigned int pending_size=0; - stream->writeUint32(pending_buildings.size(), "size"); - for(pending_iterator i=pending_buildings.begin(); i!=pending_buildings.end(); ++i) - { - stream->writeEnterSection(pending_size); - stream->writeSint32(i->first, "echo_building_id"); - stream->writeSint32(i->second.get<0>(), "xpos"); - stream->writeSint32(i->second.get<1>(), "ypos"); - stream->writeSint32(i->second.get<2>(), "building_type"); - stream->writeSint32(i->second.get<3>(), "ticks_since_registered"); - stream->writeLeaveSection(); - pending_size++; - } - stream->writeLeaveSection(); - - stream->writeEnterSection("found_buildings"); - unsigned int found_size=0; - stream->writeUint32(found_buildings.size(), "size"); - for(found_iterator i=found_buildings.begin(); i!=found_buildings.end(); ++i) - { - stream->writeEnterSection(found_size); - stream->writeUint32(i->first, "echo_building_id"); - stream->writeUint32(i->second.get<0>(), "xpos"); - stream->writeUint32(i->second.get<1>(), "ypos"); - stream->writeUint32(i->second.get<2>(), "building_type"); - stream->writeUint32(i->second.get<3>(), "gid"); - if(i->second.get<4>()) - stream->writeUint8(1, "upgrade_status"); - else if(!i->second.get<4>()) - stream->writeUint8(0, "upgrade_status"); - else - stream->writeUint8(2, "upgrade_status"); - stream->writeLeaveSection(); - found_size++; - } - stream->writeLeaveSection(); - - stream->writeUint32(building_id, "building_id"); - stream->writeLeaveSection(); -} - - - -void BuildingRegister::set_upgrading(unsigned int id) -{ - found_buildings[id].get<4>()=indeterminate; -} - - - - -void BuildingRegister::tick() -{ - for(pending_iterator i=pending_buildings.begin(); i!=pending_buildings.end();) - { - //When get<3>() is -1, it means that the building order hasen't been sent to the glob2 engine yet. - //This is used when the building is registered, but awaiting conditions to be satisfied. - if(i->second.get<3>()!=-1) - { - i->second.get<3>()++; - if(i->second.get<3>() > 300) - { - pending_iterator current=i; - ++i; - pending_buildings.erase(current); - continue; - } - int gbid=NOGBID; - if(i->second.get<2>() > IntBuildingType::DEFENSE_BUILDING && i->second.get<2>() < IntBuildingType::STONE_WALL) - { - gbid=is_flag(echo, i->second.get<0>(), i->second.get<1>()); - } - else - { - gbid=player->map->getBuilding(i->second.get<0>(), i->second.get<1>()); - } - if(gbid!=NOGBID) - { - if(i->second.get<2>() > IntBuildingType::DEFENSE_BUILDING && i->second.get<2>() < IntBuildingType::STONE_WALL) - { - echo.get_flag_map().set_flag(i->second.get<0>(), i->second.get<1>(), gbid); - } - found_buildings[i->first]=boost::make_tuple(i->second.get<0>(), i->second.get<1>(), i->second.get<2>(), gbid, false); - pending_iterator current=i; - ++i; - pending_buildings.erase(current); - continue; - } - } - ++i; - } - for(found_iterator i = found_buildings.begin(); i!=found_buildings.end();) - { - if(i->second.get<2>() > IntBuildingType::DEFENSE_BUILDING && i->second.get<2>() < IntBuildingType::STONE_WALL) - { - if(echo.get_flag_map().get_flag(i->second.get<0>(), i->second.get<1>())==NOGBID) - { - found_iterator current=i; - ++i; - found_buildings.erase(current); - continue; - } - if(player->team->myBuildings[::Building::GIDtoID(i->second.get<3>())]==NULL) - { - echo.get_flag_map().set_flag(i->second.get<0>(), i->second.get<1>(), NOGBID); - found_iterator current=i; - ++i; - found_buildings.erase(current); - continue; - } - } - else - { - const int gbid=player->map->getBuilding(i->second.get<0>(), i->second.get<1>()); - if(gbid==NOGBID || gbid != i->second.get<3>()) - { - found_iterator current=i; - ++i; - found_buildings.erase(current); - continue; - } - Building* b=player->team->myBuildings[::Building::GIDtoID(gbid)]; - if(b==NULL) - { - found_iterator current=i; - ++i; - found_buildings.erase(current); - continue; - } - //True - if(i->second.get<4>()) - { - i->second.get<0>()=b->posX; - i->second.get<1>()=b->posY; - if(b->constructionResultState==::Building::NO_CONSTRUCTION) - { - i->second.get<4>()=false; - } - } - //False - else if(!i->second.get<4>()) - { - - } - //Indeterminate - else - { - if(b->constructionResultState!=::Building::NO_CONSTRUCTION) - { - i->second.get<4>()=true; - } - } - } - ++i; - } -} - -bool BuildingRegister::is_building_pending(unsigned int id) -{ - if(pending_buildings.find(id)!=pending_buildings.end()) - { - return true; - } - return false; -} - - - -bool BuildingRegister::is_building_found(unsigned int id) -{ - if(found_buildings.find(id)!=found_buildings.end()) - { - return true; - } - return false; -} - - - - -bool BuildingRegister::is_building_upgrading(unsigned int id) -{ - if(found_buildings.find(id)==found_buildings.end()) - { - return false; - } - - tribool v=found_buildings[id].get<4>(); - if(v) - return true; - else if(!v) - return false; - return true; -} - - - -Building* BuildingRegister::get_building(unsigned int id) -{ - if(found_buildings.find(id)==found_buildings.end()) - { - return NULL; - } - return player->team->myBuildings[::Building::GIDtoID(found_buildings[id].get<3>())]; -} - - - -BuildingType* BuildingRegister::get_building_type(unsigned int id) -{ - if(found_buildings.find(id)==found_buildings.end()) - { - return NULL; - } - return player->team->myBuildings[::Building::GIDtoID(found_buildings[id].get<3>())]->type; -} - - - -int BuildingRegister::get_type(unsigned int id) -{ - if(found_buildings.find(id)==found_buildings.end()) - { - return 0; - } - return found_buildings[id].get<2>(); -} - - - -int BuildingRegister::get_level(unsigned int id) -{ - if(found_buildings.find(id)==found_buildings.end()) - { - return 0; - } - return get_building(id)->type->level+1; -} - - - -int BuildingRegister::get_assigned(unsigned int id) -{ - if(found_buildings.find(id)==found_buildings.end()) - { - return 0; - } - return get_building(id)->maxUnitWorking; -} - - - -Condition* Condition::load_condition(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("Condition"); - ConditionType type=static_cast(stream->readUint32("type")); - Condition* condition=NULL; - switch(type) - { - case CParticularBuilding: - condition=new ParticularBuilding; - condition->load(stream, player, versionMinor); - break; - case CBuildingDestroyed: - condition=new BuildingDestroyed; - condition->load(stream, player, versionMinor); - break; - case CEnemyBuildingDestroyed: - condition=new EnemyBuildingDestroyed; - condition->load(stream, player, versionMinor); - break; - case CEitherCondition: - condition=new EitherCondition; - condition->load(stream, player, versionMinor); - break; - case CAllConditions: - condition=new AllConditions; - condition->load(stream, player, versionMinor); - break; - case CPopulation: - condition=new Population; - condition->load(stream, player, versionMinor); - break; - } - stream->readLeaveSection(); - return condition; -} - - - -void Condition::save_condition(Condition* condition, GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Condition"); - stream->writeUint32(condition->get_type(), "type"); - condition->save(stream); - stream->writeLeaveSection(); -} - - - -ParticularBuilding::ParticularBuilding() : condition(NULL), id(-1) -{ - -} - - - -ParticularBuilding::ParticularBuilding(BuildingCondition* condition, int id) : condition(condition), id(id) -{ - -} - - - -ParticularBuilding::~ParticularBuilding() -{ - if(condition) - delete condition; -} - - - -boost::logic::tribool ParticularBuilding::passes(Echo& echo) -{ - if(!echo.get_building_register().is_building_found(id) && !echo.get_building_register().is_building_pending(id)) - { - return indeterminate; - } - if(echo.get_building_register().is_building_found(id)) - { - bool passes=condition->passes(echo, id); - return passes; - } - return false; -} - - - -ConditionType ParticularBuilding::get_type() -{ - return CParticularBuilding; -} - - - -bool ParticularBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("ParticularBuilding"); - id=stream->readSint32("id"); - condition=BuildingCondition::load_condition(stream, player, versionMinor); - stream->readLeaveSection(); - return true; -} - - - -void ParticularBuilding::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("ParticularBuilding"); - stream->writeSint32(id, "id"); - BuildingCondition::save_condition(condition, stream); - stream->writeLeaveSection(); -} - - -BuildingDestroyed::BuildingDestroyed(int id) : id(id) -{ - -} - - - -boost::logic::tribool BuildingDestroyed::passes(Echo& echo) -{ - if(!echo.get_building_register().is_building_found(id) && !echo.get_building_register().is_building_pending(id)) - { - return true; - } - return false; -} - - - -ConditionType BuildingDestroyed::get_type() -{ - return CBuildingDestroyed; -} - - - -bool BuildingDestroyed::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("BuildingDestroyed"); - id=stream->readSint32("id"); - stream->readLeaveSection(); - return true; -} - - - -void BuildingDestroyed::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("BuildingDestroyed"); - stream->writeSint32(id, "id"); - stream->writeLeaveSection(); -} - - - -EnemyBuildingDestroyed::EnemyBuildingDestroyed(Echo& echo, int gbid) : gbid(gbid) -{ - Building* b=echo.player->game->teams[Building::GIDtoTeam(gbid)]->myBuildings[Building::GIDtoID(gbid)]; - type=b->type->shortTypeNum; - level=b->type->level; - location=position(b->posX, b->posY); -} - - - -boost::logic::tribool EnemyBuildingDestroyed::passes(Echo& echo) -{ - Building* b=echo.player->game->teams[Building::GIDtoTeam(gbid)]->myBuildings[Building::GIDtoID(gbid)]; - if(b==NULL) - { - return true; - } - if(b->posX != location.x || b->posY != location.y) - { - return true; - } - if(b->type->shortTypeNum != type) - { - return true; - } - return false; -} - - - -bool EnemyBuildingDestroyed::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("EnemyBuildingDestroyed"); - gbid=stream->readUint32("gbid"); - type=stream->readUint32("type"); - level=stream->readUint32("level"); - int posx=stream->readUint32("posx"); - int posy=stream->readUint32("posy"); - location=position(posx, posy); - stream->readLeaveSection(); - return true; -} - - - -void EnemyBuildingDestroyed::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("EnemyBuildingDestroyed"); - stream->writeUint32(gbid, "gbid"); - stream->writeUint32(type, "type"); - stream->writeUint32(level, "level"); - stream->writeUint32(location.x, "posx"); - stream->writeUint32(location.y, "posy"); - stream->writeLeaveSection(); -} - - -EitherCondition::EitherCondition(Condition* condition1, Condition* condition2) : condition1(condition1), condition2(condition2) -{ - -} - - - -EitherCondition::~EitherCondition() -{ - delete condition1; - delete condition2; -} - - - -boost::logic::tribool EitherCondition::passes(Echo& echo) -{ - tribool p1=condition1->passes(echo); - tribool p2=condition2->passes(echo); - if(p1 || p2) - return true; - else if(!p1 || !p2) - return false; - else - return indeterminate; -} - - - -ConditionType EitherCondition::get_type() -{ - return CEitherCondition; -} - - - -bool EitherCondition::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("EitherCondition"); - condition1=Condition::load_condition(stream, player, versionMinor); - condition2=Condition::load_condition(stream, player, versionMinor); - stream->readLeaveSection(); - return true; -} - - - -void EitherCondition::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("EitherCondition"); - Condition::save_condition(condition1, stream); - Condition::save_condition(condition2, stream); - stream->writeLeaveSection(); -} - - - -EitherCondition::EitherCondition() -{ - -} - - -AllConditions::AllConditions(Condition* a, Condition* b, Condition* c, Condition* d) : a(a), b(b), c(c), d(d) -{ - -} - - - -AllConditions::~AllConditions() -{ - if(a) - delete a; - if(b) - delete b; - if(c) - delete c; - if(d) - delete d; - -} - - - -boost::logic::tribool AllConditions::passes(Echo& echo) -{ - tribool a2=true; - tribool b2=true; - tribool c2=true; - tribool d2=true; - - if(a) - a2=a->passes(echo); - if(b) - b2=b->passes(echo); - if(c) - c2=c->passes(echo); - if(d) - d2=d->passes(echo); - - if(a2 && b2 && c2 && d2) - return true; - - if(a2==indeterminate) - return indeterminate; - if(b2==indeterminate) - return indeterminate; - if(c2==indeterminate) - return indeterminate; - if(d2==indeterminate) - return indeterminate; - - return false; -} - - - -ConditionType AllConditions::get_type() -{ - return CAllConditions; -} - - - -bool AllConditions::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("EitherCondition"); - bool condition_is_null=stream->readUint8("condition_is_null"); - if(condition_is_null) - a=NULL; - else - a=Condition::load_condition(stream, player, versionMinor); - - condition_is_null=stream->readUint8("condition_is_null"); - if(condition_is_null) - b=NULL; - else - b=Condition::load_condition(stream, player, versionMinor); - - condition_is_null=stream->readUint8("condition_is_null"); - if(condition_is_null) - c=NULL; - else - c=Condition::load_condition(stream, player, versionMinor); - - condition_is_null=stream->readUint8("condition_is_null"); - if(condition_is_null) - d=NULL; - else - d=Condition::load_condition(stream, player, versionMinor); - - stream->readLeaveSection(); - return true; -} - - - -void AllConditions::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("AllConditions"); - if(a) - { - stream->writeUint8(false, "condition_is_null"); - Condition::save_condition(a, stream); - } - else - stream->writeUint8(true, "condition_is_null"); - - if(b) - { - stream->writeUint8(false, "condition_is_null"); - Condition::save_condition(b, stream); - } - else - stream->writeUint8(true, "condition_is_null"); - - if(c) - { - stream->writeUint8(false, "condition_is_null"); - Condition::save_condition(c, stream); - } - else - stream->writeUint8(true, "condition_is_null"); - - if(d) - { - stream->writeUint8(false, "condition_is_null"); - Condition::save_condition(d, stream); - } - else - stream->writeUint8(true, "condition_is_null"); - - stream->writeLeaveSection(); -} - - - -AllConditions::AllConditions() -{ - -} - - - -Population::Population(bool workers, bool explorers, bool warriors, int num, PopulationMethod method) : workers(workers), explorers(explorers), warriors(warriors), num(num), method(method) -{ - -} - - - -Population::~Population() -{ - -} - - - -boost::logic::tribool Population::passes(Echo& echo) -{ - int amount=0; - if(workers) - amount+=echo.player->team->stats.getLatestStat()->numberUnitPerType[WORKER]; - if(explorers) - amount+=echo.player->team->stats.getLatestStat()->numberUnitPerType[EXPLORER]; - if(warriors) - amount+=echo.player->team->stats.getLatestStat()->numberUnitPerType[WARRIOR]; - if(method==Greater) - { - return (amount >= num); - } - else if(method==Lesser) - { - return (amount <= num); - } - return false; -} - - - -ConditionType Population::get_type() -{ - return CPopulation; -} - - - -bool Population::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("Population"); - workers=stream->readUint8("workers"); - explorers=stream->readUint8("explorers"); - warriors=stream->readUint8("warriors"); - num=stream->readSint32("num"); - method=static_cast(stream->readUint32("method")); - stream->readLeaveSection(); - return true; -} - - - -void Population::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Population"); - stream->writeUint8(workers, "workers"); - stream->writeUint8(explorers, "explorers"); - stream->writeUint8(warriors, "warriors"); - stream->writeSint32(num, "num"); - stream->writeUint32(static_cast(method), "method"); - stream->writeLeaveSection(); -} - - - -Population::Population() -{ - -} - - - -BuildingCondition* BuildingCondition::load_condition(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("BuildingCondition"); - BuildingConditionType type=static_cast(stream->readUint32("type")); - BuildingCondition* condition=NULL; - switch(type) - { - case CNotUnderConstruction: - condition=new NotUnderConstruction; - condition->load(stream, player, versionMinor); - break; - case CUnderConstruction: - condition=new UnderConstruction; - condition->load(stream, player, versionMinor); - break; - case CBeingUpgraded: - condition=new BeingUpgraded; - condition->load(stream, player, versionMinor); - break; - case CBeingUpgradedTo: - condition=new BeingUpgradedTo; - condition->load(stream, player, versionMinor); - break; - case CSpecificBuildingType: - condition=new SpecificBuildingType; - condition->load(stream, player, versionMinor); - break; - case CNotSpecificBuildingType: - condition=new NotSpecificBuildingType; - condition->load(stream, player, versionMinor); - break; - case CBuildingLevel: - condition=new BuildingLevel; - condition->load(stream, player, versionMinor); - break; - case CUpgradable: - condition=new Upgradable; - condition->load(stream, player, versionMinor); - break; - case CRessourceTrackerAmount: - condition=new RessourceTrackerAmount; - condition->load(stream, player, versionMinor); - break; - case CRessourceTrackerAge: - condition=new RessourceTrackerAge; - condition->load(stream, player, versionMinor); - break; - case CTicksPassed: - condition=new TicksPassed; - condition->load(stream, player, versionMinor); - break; - - } - stream->readLeaveSection(); - return condition; -} - - - -void BuildingCondition::save_condition(BuildingCondition* condition, GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("BuildingCondition"); - stream->writeUint32(condition->get_type(), "type"); - condition->save(stream); - stream->writeLeaveSection(); -} - - - -bool NotUnderConstruction::passes(Echo& echo, int id) -{ - Building* building = echo.get_building_register().get_building(id); - bool result=building->constructionResultState==::Building::NO_CONSTRUCTION && !echo.get_building_register().is_building_upgrading(id); - return result; -} - - - -bool NotUnderConstruction::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("NotUnderConstruction"); - stream->readLeaveSection(); - return true; -} - - - -void NotUnderConstruction::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("NotUnderConstruction"); - stream->writeLeaveSection(); - -} - - - -bool UnderConstruction::passes(Echo& echo, int id) -{ - Building* building = echo.get_building_register().get_building(id); - return building->constructionResultState!=::Building::NO_CONSTRUCTION && building->buildingState==Building::ALIVE; -} - - - -bool UnderConstruction::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("UnderConstruction"); - stream->readLeaveSection(); - return true; -} - - - -void UnderConstruction::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("UnderConstruction"); - stream->writeLeaveSection(); -} - - - -SpecificBuildingType::SpecificBuildingType(int building_type) : building_type(building_type) -{ - -} - - - -bool SpecificBuildingType::passes(Echo& echo, int id) -{ - if(echo.get_building_register().get_type(id)==building_type) - return true; - return false; -} - -bool SpecificBuildingType::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("SpecificBuildingType"); - building_type=stream->readUint32("building_type"); - stream->readLeaveSection(); - return true; -} - - - -void SpecificBuildingType::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("SpecificBuildingType"); - stream->writeUint32(building_type, "building_type"); - stream->writeLeaveSection(); -} - - - - - -NotSpecificBuildingType::NotSpecificBuildingType(int building_type) : building_type(building_type) -{ - -} - - - -bool NotSpecificBuildingType::passes(Echo& echo, int id) -{ - if(echo.get_building_register().get_type(id)!=building_type) - return true; - return false; -} - -bool NotSpecificBuildingType::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("NotSpecificBuildingType"); - building_type=stream->readUint32("building_type"); - stream->readLeaveSection(); - return true; -} - - - -void NotSpecificBuildingType::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("NotSpecificBuildingType"); - stream->writeUint32(building_type, "building_type"); - stream->writeLeaveSection(); -} - - - - - -bool BeingUpgraded::passes(Echo& echo, int id) -{ - return echo.get_building_register().is_building_upgrading(id); -} - -bool BeingUpgraded::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("BeingUpgraded"); - stream->readLeaveSection(); - return true; -} - - - -void BeingUpgraded::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("BeingUpgraded"); - stream->writeLeaveSection(); -} - - - - -BeingUpgradedTo::BeingUpgradedTo(int level) : level(level) -{ - -} - - - -bool BeingUpgradedTo::passes(Echo& echo, int id) -{ - Building* b= echo.get_building_register().get_building(id); - if(!echo.get_building_register().is_building_upgrading(id)) - return false; - if(b->type->isBuildingSite) - { - if(b->type->level==(level-1)) - { - return true; - } - } - else if(b->type->level==(level-2)) - { - return true; - } - return false; -} - - -bool BeingUpgradedTo::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("BeingUpgradedTo"); - level=stream->readUint32("level"); - stream->readLeaveSection(); - return true; -} - - - -void BeingUpgradedTo::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("BeingUpgradedTo"); - stream->writeUint32(level, "level"); - stream->writeLeaveSection(); -} - - - - -BuildingLevel::BuildingLevel(int building_level) : building_level(building_level) -{ - -} - - - -bool BuildingLevel::passes(Echo& echo, int id) -{ - Building* building = echo.get_building_register().get_building(id); - if(building->type->level==building_level-1) - return true; - return false; -} - - -bool BuildingLevel::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("BuildingLevel"); - building_level=stream->readUint32("building_level"); - stream->readLeaveSection(); - return true; -} - - - -void BuildingLevel::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("BuildingLevel"); - stream->writeUint32(building_level, "building_level"); - stream->writeLeaveSection(); -} - - - - -bool Upgradable::passes(Echo& echo, int id) -{ - Building* building = echo.get_building_register().get_building(id); - if((building->type->shortTypeNum==IntBuildingType::FOOD_BUILDING || - building->type->shortTypeNum==IntBuildingType::HEAL_BUILDING || - building->type->shortTypeNum==IntBuildingType::SWIMSPEED_BUILDING || - building->type->shortTypeNum==IntBuildingType::WALKSPEED_BUILDING || - building->type->shortTypeNum==IntBuildingType::ATTACK_BUILDING || - building->type->shortTypeNum==IntBuildingType::SCIENCE_BUILDING || - building->type->shortTypeNum==IntBuildingType::DEFENSE_BUILDING) && - building->constructionResultState==Building::NO_CONSTRUCTION && - building->type->level!=2 && - building->isHardSpaceForBuildingSite(Building::UPGRADE) && - building->hp == building->type->hpMax - ) - return true; - return false; -} - - - -bool Upgradable::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("Upgradable"); - stream->readLeaveSection(); - return true; -} - - - -void Upgradable::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Upgradable"); - stream->writeLeaveSection(); -} - - - -RessourceTrackerAmount::RessourceTrackerAmount(int amount, TrackerMethod tracker_method) : amount(amount), tracker_method(tracker_method) -{ - -} - - - -RessourceTrackerAmount::RessourceTrackerAmount() -{ - -} - - - -bool RessourceTrackerAmount::passes(Echo& echo, int id) -{ - if(tracker_method==Greater) - { - return echo.get_ressource_tracker(id)->get_total_level() > amount; - } - else if(tracker_method==Lesser) - { - return echo.get_ressource_tracker(id)->get_total_level() < amount; - } - return false; -} - - - -BuildingConditionType RessourceTrackerAmount::get_type() -{ - return CRessourceTrackerAmount; -} - - - -bool RessourceTrackerAmount::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("RessourceTrackerAmount"); - amount=stream->readUint32("amount"); - tracker_method=static_cast(stream->readUint32("tracker_method")); - stream->readLeaveSection(); - return true; -} - - - -void RessourceTrackerAmount::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("RessourceTrackerAmount"); - stream->writeUint32(amount, "amount"); - stream->writeUint32(static_cast(tracker_method), "tracker_method"); - stream->writeLeaveSection(); -} - - - -RessourceTrackerAge::RessourceTrackerAge(int age, TrackerMethod tracker_method) : age(age), tracker_method(tracker_method) -{ - -} - - - -RessourceTrackerAge::RessourceTrackerAge() -{ - -} - - - -bool RessourceTrackerAge::passes(Echo& echo, int id) -{ - if(tracker_method==Greater) - { - return echo.get_ressource_tracker(id)->get_age() > age; - } - else if(tracker_method==Lesser) - { - return echo.get_ressource_tracker(id)->get_age() < age; - } - return false; -} - - - -BuildingConditionType RessourceTrackerAge::get_type() -{ - return CRessourceTrackerAge; -} - - - -bool RessourceTrackerAge::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("RessourceTrackerAge"); - age=stream->readUint32("age"); - tracker_method=static_cast(stream->readUint32("tracker_method")); - stream->readLeaveSection(); - return true; -} - - - -void RessourceTrackerAge::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("RessourceTrackerAge"); - stream->writeUint32(age, "age"); - stream->writeUint32(static_cast(tracker_method), "tracker_method"); - stream->writeLeaveSection(); -} - - - -bool ManagementOrder::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("ManagementOrder"); - stream->readEnterSection("conditions"); - Uint32 size = stream->readUint32("size"); - conditions.resize(size); - for(unsigned x=0; xreadEnterSection(x); - conditions[x] = boost::shared_ptr(Condition::load_condition(stream, player, versionMinor)); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - stream->readLeaveSection(); - return true; -} - - - -void ManagementOrder::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("ManagementOrder"); - stream->writeEnterSection("conditions"); - stream->writeUint32(conditions.size(), "size"); - for(unsigned x=0; xwriteEnterSection(x); - Condition::save_condition(conditions[x].get(), stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - stream->writeLeaveSection(); -} - - - -void ManagementOrder::add_condition(Condition* condition) -{ - conditions.push_back(boost::shared_ptr(condition)); -} - - - -boost::logic::tribool ManagementOrder::passes_conditions(Echo& echo) -{ - for(unsigned int i=0; ipasses(echo); - if(passes) - continue; - else if(!passes) - return false; - else - return indeterminate; - - } - - boost::logic::tribool passes=wait(echo); - if(passes) - return true; - if(!passes) - return false; - else - return indeterminate; - - return true; -} - - - -ManagementOrder* ManagementOrder::load_order(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("ManagementOrder"); - ManagementOrderType mot=static_cast(stream->readUint32("type")); - ManagementOrder* mo=NULL; - switch(mot) - { - case MAssignWorkers: - mo=new AssignWorkers; - mo->load(stream, player, versionMinor); - break; - case MChangeSwarm: - mo=new ChangeSwarm; - mo->load(stream, player, versionMinor); - break; - case MDestroyBuilding: - mo=new DestroyBuilding; - mo->load(stream, player, versionMinor); - break; - case MAddRessourceTracker: - mo=new AddRessourceTracker; - mo->load(stream, player, versionMinor); - break; - case MPauseRessourceTracker: - mo=new PauseRessourceTracker; - mo->load(stream, player, versionMinor); - break; - case MUnPauseRessourceTracker: - mo=new UnPauseRessourceTracker; - mo->load(stream, player, versionMinor); - break; - case MChangeFlagSize: - mo=new ChangeFlagSize; - mo->load(stream, player, versionMinor); - break; - case MChangeFlagMinimumLevel: - mo=new ChangeFlagMinimumLevel; - mo->load(stream, player, versionMinor); - break; - case MAddArea: - mo=new AddArea; - mo->load(stream, player, versionMinor); - break; - case MRemoveArea: - mo=new RemoveArea; - mo->load(stream, player, versionMinor); - break; - case MChangeAlliances: - mo=new ChangeAlliances; - mo->load(stream, player, versionMinor); - break; - case MUpgradeRepair: - mo=new UpgradeRepair; - mo->load(stream, player, versionMinor); - break; - case MSendMessage: - mo=new SendMessage; - mo->load(stream, player, versionMinor); - break; - case MChangeFlagPosition: - mo=new ChangeFlagPosition; - mo->load(stream, player, versionMinor); - break; - case MAdjustPriority: - mo=new AdjustPriority; - mo->load(stream, player, versionMinor); - break; - } - return mo; -} - - - -void ManagementOrder::save_order(ManagementOrder* mo, GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("ManagementOrder"); - stream->writeUint32(mo->get_type(), "type"); - mo->save(stream); - stream->writeLeaveSection(); -} - - - -AssignWorkers::AssignWorkers(int number_of_workers, int building_id) : number_of_workers(number_of_workers), building_id(building_id) -{ - -} - - -void AssignWorkers::modify(Echo& echo) -{ - echo.push_order(shared_ptr(new OrderModifyBuilding(echo.get_building_register().get_building(building_id)->gid, number_of_workers))); -} - - - -boost::logic::tribool AssignWorkers::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - return true; - else if(echo.get_building_register().is_building_pending(building_id)) - return false; - else - return indeterminate; -} - - - -bool AssignWorkers::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("AssignWorkers"); - ManagementOrder::load(stream, player, versionMinor); - number_of_workers=stream->readUint32("number_of_workers"); - building_id=stream->readUint32("building_id"); - stream->readLeaveSection(); - return true; -} - - - -void AssignWorkers::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("AssignWorkers"); - ManagementOrder::save(stream); - stream->writeUint32(number_of_workers, "number_of_workers"); - stream->writeUint32(building_id, "building_id"); - stream->writeLeaveSection(); -} - - - -ChangeSwarm::ChangeSwarm(int worker_ratio, int explorer_ratio, int warrior_ratio, int building_id) : worker_ratio(worker_ratio), explorer_ratio(explorer_ratio), warrior_ratio(warrior_ratio), building_id(building_id) -{ - -} - - -void ChangeSwarm::modify(Echo& echo) -{ - Sint32 ratio[NB_UNIT_TYPE]; - ratio[0]=worker_ratio; - ratio[1]=explorer_ratio; - ratio[2]=warrior_ratio; - echo.push_order(shared_ptr(new OrderModifySwarm(echo.get_building_register().get_building(building_id)->gid, ratio))); -} - - - -boost::logic::tribool ChangeSwarm::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - return true; - else if(echo.get_building_register().is_building_pending(building_id)) - return false; - else - return indeterminate; -} - - - -bool ChangeSwarm::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("ChangeSwarm"); - ManagementOrder::load(stream, player, versionMinor); - worker_ratio=stream->readUint32("worker_ratio"); - explorer_ratio=stream->readUint32("explorer_ratio"); - warrior_ratio=stream->readUint32("warrior_ratio"); - building_id=stream->readUint32("building_id"); - stream->readLeaveSection(); - return true; - -} - - - -void ChangeSwarm::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("ChangeSwarm"); - ManagementOrder::save(stream); - stream->writeUint32(worker_ratio, "worker_ratio"); - stream->writeUint32(explorer_ratio, "explorer_ratio"); - stream->writeUint32(warrior_ratio, "warrior_ratio"); - stream->writeUint32(building_id, "building_id"); - stream->writeLeaveSection(); -} - - - -DestroyBuilding::DestroyBuilding(int building_id) : building_id(building_id) -{ - -} - - - -void DestroyBuilding::modify(Echo& echo) -{ - echo.push_order(shared_ptr(new OrderDelete(echo.get_building_register().get_building(building_id)->gid))); -} - - - -boost::logic::tribool DestroyBuilding::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - return true; - else if(echo.get_building_register().is_building_pending(building_id)) - return false; - else - return indeterminate; -} - - - -bool DestroyBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("DestroyBuilding"); - ManagementOrder::load(stream, player, versionMinor); - building_id=stream->readUint32("building_id"); - stream->readLeaveSection(); - return true; -} - - - -void DestroyBuilding::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("DestroyBuilding"); - ManagementOrder::save(stream); - stream->writeUint32(building_id, "building_id"); - stream->writeLeaveSection(); -} - - - -RessourceTracker::RessourceTracker(Echo& echo, int building_id, int length, int ressource) : record(length, 0), position(0), timer(0), length(length), echo(echo), building_id(building_id), ressource(ressource) -{ - -} - - - -void RessourceTracker::tick() -{ - timer++; - if((timer%10)==0) - { - Building* b = echo.get_building_register().get_building(building_id); - record[position]=b->ressources[ressource]; - position++; - if(position>=record.size()) - position=0; - } -} - - -int RessourceTracker::get_total_level() -{ - int sum=0; - for(unsigned int n=0; nreadEnterSection("RessourceTracker"); - stream->readEnterSection("record"); - Uint32 recordsize=stream->readUint32("size"); - record.resize(recordsize); - for(unsigned int record_index=0; record_indexreadEnterSection(record_index); - record[record_index]=stream->readUint32("quantity_of_ressources"); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - position=stream->readUint32("position"); - timer=stream->readUint32("timer"); - building_id=stream->readUint32("building_id"); - length=stream->readUint32("length"); - ressource=stream->readUint32("ressource"); - stream->readLeaveSection(); - return true; -} - - - -void RessourceTracker::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("RessourceTracker"); - stream->writeEnterSection("record"); - stream->writeUint32(record.size(), "size"); - for(unsigned int record_index=0; record_indexwriteEnterSection(record_index); - stream->writeUint32(record[record_index], "quantity_of_ressources"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - stream->writeUint32(position, "position"); - stream->writeUint32(timer, "timer"); - stream->writeUint32(building_id, "building_id"); - stream->writeUint32(length, "length"); - stream->writeUint32(ressource, "ressource"); - stream->writeLeaveSection(); -} - - - -AddRessourceTracker::AddRessourceTracker(int length, int ressource, int building_id) : length(length), building_id(building_id), ressource(ressource) -{ - -} - - - -void AddRessourceTracker::modify(Echo& echo) -{ - echo.add_ressource_tracker(new RessourceTracker(echo, building_id, length, ressource), building_id); -} - - - -boost::logic::tribool AddRessourceTracker::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - return true; - else if(echo.get_building_register().is_building_pending(building_id)) - return false; - else - return indeterminate; -} - - - -bool AddRessourceTracker::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("AddRessourceTracker"); - ManagementOrder::load(stream, player, versionMinor); - length=stream->readUint32("length"); - building_id=stream->readUint32("building_id"); - ressource=stream->readUint32("ressource"); - stream->readLeaveSection(); - return true; -} - - - -void AddRessourceTracker::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("AddRessourceTracker"); - ManagementOrder::save(stream); - stream->writeUint32(length, "length"); - stream->writeUint32(building_id, "building_id"); - stream->writeUint32(ressource, "ressource"); - stream->writeLeaveSection(); -} - - - -PauseRessourceTracker::PauseRessourceTracker(int building_id) : building_id(building_id) -{ - -} - - - -void PauseRessourceTracker::modify(Echo& echo) -{ - echo.pause_ressource_tracker(building_id); -} - - - -boost::logic::tribool PauseRessourceTracker::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - return true; - else if(echo.get_building_register().is_building_pending(building_id)) - return false; - else - return indeterminate; -} - - - -bool PauseRessourceTracker::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("PauseRessourceTracker"); - ManagementOrder::load(stream, player, versionMinor); - building_id=stream->readUint32("building_id"); - stream->readLeaveSection(); - return true; -} - - - -void PauseRessourceTracker::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("PauseRessourceTracker"); - ManagementOrder::save(stream); - stream->writeUint32(building_id, "building_id"); - stream->writeLeaveSection(); -} - - - -UnPauseRessourceTracker::UnPauseRessourceTracker(int building_id) : building_id(building_id) -{ - -} - - - -void UnPauseRessourceTracker::modify(Echo& echo) -{ - echo.unpause_ressource_tracker(building_id); -} - - - -boost::logic::tribool UnPauseRessourceTracker::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - return true; - else if(echo.get_building_register().is_building_pending(building_id)) - return false; - else - return indeterminate; -} - - - -bool UnPauseRessourceTracker::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("UnPauseRessourceTracker"); - ManagementOrder::load(stream, player, versionMinor); - building_id=stream->readUint32("building_id"); - stream->readLeaveSection(); - return true; -} - - - -void UnPauseRessourceTracker::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("UnPauseRessourceTracker"); - ManagementOrder::save(stream); - stream->writeUint32(building_id, "building_id"); - stream->writeLeaveSection(); -} - - - -ChangeFlagSize::ChangeFlagSize(int size, int building_id) : size(size), building_id(building_id) -{ - -} - - - -void ChangeFlagSize::modify(Echo& echo) -{ - echo.push_order(shared_ptr(new OrderModifyFlag(echo.get_building_register().get_building(building_id)->gid, size))); -} - - - -boost::logic::tribool ChangeFlagSize::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - { - return true; - } - else if(echo.get_building_register().is_building_pending(building_id)) - { - return false; - } - else - { - return indeterminate; - } -} - - - -bool ChangeFlagSize::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("ChangeFlagSize"); - ManagementOrder::load(stream, player, versionMinor); - size=stream->readUint32("size"); - building_id=stream->readUint32("building_id"); - stream->readLeaveSection(); - return true; -} - - - -void ChangeFlagSize::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("ChangeFlagSize"); - ManagementOrder::save(stream); - stream->writeUint32(size, "size"); - stream->writeUint32(building_id, "building_id"); - stream->writeLeaveSection(); -} - - - -ChangeFlagMinimumLevel::ChangeFlagMinimumLevel(int minimum_level, int building_id) : minimum_level(minimum_level), building_id(building_id) -{ - -} - - - -void ChangeFlagMinimumLevel::modify(Echo& echo) -{ - echo.push_order(shared_ptr(new OrderModifyMinLevelToFlag(echo.get_building_register().get_building(building_id)->gid, minimum_level-1))); -} - - - -boost::logic::tribool ChangeFlagMinimumLevel::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - return true; - else if(echo.get_building_register().is_building_pending(building_id)) - return false; - else - return indeterminate; -} - - - -bool ChangeFlagMinimumLevel::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("ChangeFlagMinimumLevel"); - ManagementOrder::load(stream, player, versionMinor); - minimum_level=stream->readUint32("minimum_level"); - building_id=stream->readUint32("building_id"); - stream->readLeaveSection(); - return true; -} - - - -void ChangeFlagMinimumLevel::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("ChangeFlagMinimumLevel"); - ManagementOrder::save(stream); - stream->writeUint32(minimum_level, "minimum_level"); - stream->writeUint32(building_id, "building_id"); - stream->writeLeaveSection(); -} - - - -ChangeFlagPosition::ChangeFlagPosition(int x, int y, int building_id) - : x(x), y(y), building_id(building_id) -{ - -} - - -void ChangeFlagPosition::modify(Echo& echo) -{ - echo.push_order(shared_ptr(new OrderMoveFlag(echo.get_building_register().get_building(building_id)->gid, x, y, true))); -} - - - -boost::logic::tribool ChangeFlagPosition::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - return true; - else if(echo.get_building_register().is_building_pending(building_id)) - return false; - else - return indeterminate; -} - - - -ManagementOrderType ChangeFlagPosition::get_type() -{ - return MChangeFlagPosition; -} - - - -bool ChangeFlagPosition::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("ChangeFlagPosition"); - ManagementOrder::load(stream, player, versionMinor); - building_id=stream->readUint32("building_id"); - x=stream->readUint32("x"); - y=stream->readUint32("y"); - stream->readLeaveSection(); - return true; -} - - - -void ChangeFlagPosition::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("ChangeFlagPosition"); - ManagementOrder::save(stream); - stream->writeUint32(building_id, "building_id"); - stream->writeUint32(x, "x"); - stream->writeUint32(y, "y"); - stream->writeLeaveSection(); -} - - - -AdjustPriority::AdjustPriority(int building_id, AdjustPriority::BuildingPriority priority) - : building_id(building_id), priority(priority) -{ - -} - - -void AdjustPriority::modify(Echo& echo) -{ - int p=0; - if(priority == Low) - p=-1; - else if(priority == Medium) - p=0; - else if(priority == High) - p=1; - echo.push_order(shared_ptr(new OrderChangePriority(echo.get_building_register().get_building(building_id)->gid, p))); -} - - - -boost::logic::tribool AdjustPriority::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(building_id)) - return true; - else if(echo.get_building_register().is_building_pending(building_id)) - return false; - else - return indeterminate; -} - - - -ManagementOrderType AdjustPriority::get_type() -{ - return MAdjustPriority; -} - - - -bool AdjustPriority::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("AdjustPriority"); - ManagementOrder::load(stream, player, versionMinor); - building_id=stream->readUint32("building_id"); - int p = stream->readSint32("p"); - if(p==-1) - priority = Low; - else if(p==0) - priority = Medium; - else if(p==1) - priority = High; - stream->readLeaveSection(); - return true; -} - - - -void AdjustPriority::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("AdjustPriority"); - ManagementOrder::save(stream); - stream->writeUint32(building_id, "building_id"); - int p=0; - if(priority == Low) - p=-1; - else if(priority == Medium) - p=0; - else if(priority == High) - p=1; - stream->writeSint32(p, "priority"); - stream->writeLeaveSection(); -} - - - - -AddArea::AddArea(AreaType areatype) : areatype(areatype) -{ - -} - - - -void AddArea::add_location(int x, int y) -{ - locations.push_back(position(x, y)); -} - - - -void AddArea::modify(Echo& echo) -{ - BrushAccumulator acc; - for(std::vector::iterator i=locations.begin(); i!=locations.end(); ++i) - { - acc.applyBrush(BrushApplication(echo.player->map->normalizeX(i->x), echo.player->map->normalizeY(i->y), 0), echo.player->map); - } - if(acc.getApplicationCount()>0) - { - switch(areatype) - { - case ClearingArea: - echo.push_order(shared_ptr(new OrderAlterateClearArea(echo.player->team->teamNumber, BrushTool::MODE_ADD, &acc, echo.player->map))); - break; - case ForbiddenArea: - echo.push_order(shared_ptr(new OrderAlterateForbidden(echo.player->team->teamNumber, BrushTool::MODE_ADD, &acc, echo.player->map))); - break; - case GuardArea: - echo.push_order(shared_ptr(new OrderAlterateGuardArea(echo.player->team->teamNumber, BrushTool::MODE_ADD, &acc, echo.player->map))); - break; - } - } -} - - - -boost::logic::tribool AddArea::wait(Echo& echo) -{ - return true; -} - - - -bool AddArea::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("AddArea"); - ManagementOrder::load(stream, player, versionMinor); - areatype=static_cast(stream->readUint32("area_type")); - stream->readEnterSection("locations"); - Uint32 size=stream->readUint32("size"); - locations.resize(size); - for(Uint32 location_index=0; location_indexreadEnterSection(location_index); - locations[location_index]=position(stream->readUint32("posx"), stream->readUint32("posy")); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - stream->readLeaveSection(); - return true; -} - - - -void AddArea::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("AddArea"); - ManagementOrder::save(stream); - stream->writeUint32(areatype, "area_type"); - stream->writeEnterSection("locations"); - stream->writeUint32(locations.size(), "size"); - for(Uint32 location_index=0; location_indexwriteEnterSection(location_index); - stream->writeUint32(locations[location_index].x, "posx"); - stream->writeUint32(locations[location_index].y, "posy"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - stream->writeLeaveSection(); -} - - - -RemoveArea::RemoveArea(AreaType areatype) : areatype(areatype) -{ - -} - - - -void RemoveArea::add_location(int x, int y) -{ - locations.push_back(position(x, y)); -} - - - -void RemoveArea::modify(Echo& echo) -{ - BrushAccumulator acc; - for(std::vector::iterator i=locations.begin(); i!=locations.end(); ++i) - { - acc.applyBrush(BrushApplication(echo.player->map->normalizeX(i->x), echo.player->map->normalizeY(i->y), 0), echo.player->map); - } - if(acc.getApplicationCount()>0) - { - switch(areatype) - { - case ClearingArea: - echo.push_order(shared_ptr(new OrderAlterateClearArea(echo.player->team->teamNumber, BrushTool::MODE_DEL, &acc, echo.player->map))); - break; - case ForbiddenArea: - echo.push_order(shared_ptr(new OrderAlterateForbidden(echo.player->team->teamNumber, BrushTool::MODE_DEL, &acc, echo.player->map))); - break; - case GuardArea: - echo.push_order(shared_ptr(new OrderAlterateGuardArea(echo.player->team->teamNumber, BrushTool::MODE_DEL, &acc, echo.player->map))); - break; - } - } -} - - - -boost::logic::tribool RemoveArea::wait(Echo& echo) -{ - return true; -} - - - -bool RemoveArea::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("RemoveArea"); - ManagementOrder::load(stream, player, versionMinor); - areatype=static_cast(stream->readUint32("area_type")); - stream->readEnterSection("locations"); - Uint32 size=stream->readUint32("size"); - locations.resize(size); - for(Uint32 location_index=0; location_indexreadEnterSection(location_index); - locations[location_index]=position(stream->readUint32("posx"), stream->readUint32("posy")); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - stream->readLeaveSection(); - return true; -} - - - -void RemoveArea::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("RemoveArea"); - ManagementOrder::save(stream); - stream->writeUint32(areatype, "area_type"); - stream->writeEnterSection("locations"); - stream->writeUint32(locations.size(), "size"); - for(Uint32 location_index=0; location_indexwriteEnterSection(location_index); - stream->writeUint32(locations[location_index].x, "posx"); - stream->writeUint32(locations[location_index].y, "posy"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - stream->writeLeaveSection(); -} - - -ChangeAlliances::ChangeAlliances(int team, boost::logic::tribool is_allied, boost::logic::tribool is_enemy, boost::logic::tribool view_market, boost::logic::tribool view_inn, boost::logic::tribool view_other) : team(team), is_allied(is_allied), is_enemy(is_enemy), view_market(view_market), view_inn(view_inn), view_other(view_other) -{ - -} - - - -void ChangeAlliances::modify(Echo& echo) -{ - Uint32 alliedmask=echo.allies; - Uint32 enemymask=echo.enemies; - Uint32 market_mask=echo.market_view; - Uint32 inn_mask=echo.inn_view; - Uint32 other_mask=echo.other_view; - Team* t=echo.player->game->teams[team]; - if(is_allied) - alliedmask|=t->me; - else if(!is_allied) - if(alliedmask&t->me) - alliedmask^=t->me; - - if(is_enemy) - enemymask|=t->me; - else if(!is_enemy) - if(enemymask&t->me) - enemymask^=t->me; - - if(view_market) - market_mask|=t->me; - else if(!view_market) - if(market_mask&t->me) - market_mask^=t->me; - - if(view_inn) - inn_mask|=t->me; - else if(!view_inn) - if(inn_mask&t->me) - inn_mask^=t->me; - - if(view_other) - other_mask|=t->me; - else if(!view_other) - if(other_mask&t->me) - other_mask^=t->me; - - echo.allies=alliedmask; - echo.enemies=enemymask; - echo.market_view=market_mask; - echo.inn_view=inn_mask; - echo.other_view=other_mask; - - echo.push_order(shared_ptr(new SetAllianceOrder(echo.player->team->teamNumber, alliedmask, enemymask, market_mask, inn_mask, other_mask))); -} - - - -boost::logic::tribool ChangeAlliances::wait(Echo& echo) -{ - return true; -} - - - -bool ChangeAlliances::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("ChangeAlliances"); - ManagementOrder::load(stream, player, versionMinor); - team=stream->readUint32("team"); - - Uint8 tmp=stream->readUint8("is_allied"); - if(tmp==1) - is_allied=true; - else if(tmp==0) - is_allied=false; - else if(tmp==2) - is_allied=indeterminate; - - tmp=stream->readUint8("is_enemy"); - if(tmp==1) - is_enemy=true; - else if(tmp==0) - is_enemy=false; - else if(tmp==2) - is_enemy=indeterminate; - - tmp=stream->readUint8("view_market"); - if(tmp==1) - view_market=true; - else if(tmp==0) - view_market=false; - else if(tmp==2) - view_market=indeterminate; - - tmp=stream->readUint8("view_inn"); - if(tmp==1) - view_inn=true; - else if(tmp==0) - view_inn=false; - else if(tmp==2) - view_inn=indeterminate; - - tmp=stream->readUint8("view_other"); - if(tmp==1) - view_other=true; - else if(tmp==0) - view_other=false; - else if(tmp==2) - view_other=indeterminate; - - return true; -} - - - -void ChangeAlliances::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("ChangeAlliances"); - ManagementOrder::save(stream); - stream->writeUint32(team, "team"); - - if(is_allied) - stream->writeUint8(1, "is_allied"); - else if(!is_allied) - stream->writeUint8(0, "is_allied"); - else - stream->writeUint8(2, "is_allied"); - - if(is_enemy) - stream->writeUint8(1, "is_enemy"); - else if(!is_enemy) - stream->writeUint8(0, "is_enemy"); - else - stream->writeUint8(2, "is_enemy"); - - if(view_market) - stream->writeUint8(1, "view_market"); - else if(!view_market) - stream->writeUint8(0, "view_market"); - else - stream->writeUint8(2, "view_market"); - - if(view_inn) - stream->writeUint8(1, "view_inn"); - else if(!view_inn) - stream->writeUint8(0, "view_inn"); - else - stream->writeUint8(2, "view_inn"); - - if(view_other) - stream->writeUint8(1, "view_other"); - else if(!view_other) - stream->writeUint8(0, "view_other"); - else - stream->writeUint8(2, "view_other"); - - stream->writeLeaveSection(); -} - -UpgradeRepair::UpgradeRepair(int id) : id(id) -{ - -} - - - -void UpgradeRepair::modify(Echo& echo) -{ - echo.push_order(shared_ptr(new OrderConstruction(echo.get_building_register().get_building(id)->gid,1,1))); - echo.get_building_register().set_upgrading(id); -} - - - -boost::logic::tribool UpgradeRepair::wait(Echo& echo) -{ - if(echo.get_building_register().is_building_found(id)) - return true; - else if(echo.get_building_register().is_building_pending(id)) - return false; - else - return indeterminate; -} - - - -ManagementOrderType UpgradeRepair::get_type() -{ - return MUpgradeRepair; -} - - - -bool UpgradeRepair::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("UpgradeRepair"); - ManagementOrder::load(stream, player, versionMinor); - id=stream->readUint32("id"); - stream->readLeaveSection(); - return true; -} - - - -void UpgradeRepair::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("UpgradeRepair"); - ManagementOrder::save(stream); - stream->writeUint32(id, "id"); - stream->writeLeaveSection(); -} - - -SendMessage::SendMessage(const std::string& message) : message(message) -{ - -} - - - -void SendMessage::modify(Echo& echo) -{ - echo.echoai->handle_message(echo, message); -} - - - -boost::logic::tribool SendMessage::wait(Echo& echo) -{ - return true; -} - - - -ManagementOrderType SendMessage::get_type() -{ - return MSendMessage; -} - - - -bool SendMessage::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("SendMessage"); - ManagementOrder::load(stream, player, versionMinor); - message=stream->readText("message"); - stream->readLeaveSection(); - return true; -} - - - -void SendMessage::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("SendMessage"); - ManagementOrder::save(stream); - stream->writeText(message, "message"); - stream->writeLeaveSection(); -} - - - -building_search_iterator::building_search_iterator() : found_id(-1), is_end(true), search(NULL) -{ - -} - - -building_search_iterator::building_search_iterator(BuildingSearch& search) : found_id(-1), is_end(false), search(&search) -{ - set_to_next(); -} - - - -const unsigned int building_search_iterator::operator*() -{ - return found_id; -} - - - -building_search_iterator& building_search_iterator::operator++() -{ - set_to_next(); - return *this; -} - - - -building_search_iterator building_search_iterator::operator++(int) -{ - building_search_iterator copy(*this); - set_to_next(); - return copy; -} - - - -bool building_search_iterator::operator!=(const building_search_iterator& rhs) const -{ - if(is_end==rhs.is_end) - return false; - return is_end!=rhs.is_end || position!=rhs.position || found_id!=rhs.found_id ; -} - - - -void building_search_iterator::set_to_next() -{ - Construction::BuildingRegister::found_iterator positionSaved = position; - if(is_end) - return; - if(found_id==-1) - { - position=search->echo.get_building_register().begin(); - } - else - position++; - for(; position!=search->echo.get_building_register().end() && !search->passes_conditions(position->first); position++) - { - } - if(position==search->echo.get_building_register().end()) - { - is_end=true; - return; - } - if(position->first==-1 && positionSaved==position) - { // This fixes an infinit loop. - is_end=true; // In some special cases the program Logic - return; // must have been wrong. - } - found_id=position->first; -} - - - -BuildingSearch::BuildingSearch(Echo& echo) : echo(echo) -{ - -} - - - -void BuildingSearch::add_condition(Conditions::BuildingCondition* condition) -{ - conditions.push_back(boost::shared_ptr(condition)); -} - - - -int BuildingSearch::count_buildings() -{ - int count=0; - for(Construction::BuildingRegister::found_iterator i=echo.get_building_register().begin(); i!=echo.get_building_register().end(); ++i) - { - if(passes_conditions(i->first)) - { - count++; - } - } - return count; -} - - - -building_search_iterator BuildingSearch::begin() -{ - return building_search_iterator(*this); -} - - - -building_search_iterator BuildingSearch::end() -{ - return building_search_iterator(); -} - - - -bool BuildingSearch::passes_conditions(int b) -{ - for(std::vector >::iterator i = conditions.begin(); i!=conditions.end(); ++i) - { - if(!(*i)->passes(echo, b)) - return false; - } - return true; -} - - -enemy_team_iterator::enemy_team_iterator(Echo& echo) : team_number(-1), is_end(false), echo(&echo) -{ - set_to_next(); -} - - -enemy_team_iterator::enemy_team_iterator() : team_number(-1), is_end(true), echo(NULL) -{ - -} - - -const unsigned int enemy_team_iterator::operator*() -{ - return team_number; -} - - -enemy_team_iterator& enemy_team_iterator::operator++() -{ - set_to_next(); - return *this; -} - - -enemy_team_iterator enemy_team_iterator::operator++(int) -{ - enemy_team_iterator copy(*this); - set_to_next(); - return copy; -} - - -bool enemy_team_iterator::operator!=(const enemy_team_iterator& rhs) const -{ - if(rhs.is_end && is_end) - return false; - return rhs.is_end != is_end || rhs.team_number!=team_number; -} - - -void enemy_team_iterator::set_to_next() -{ - if(is_end) - return; - if(team_number==-1) - { - team_number=0; - } - else - team_number++; - for(; echo->player->team->game->teams[team_number]!=NULL && !(echo->player->team->enemies & echo->player->team->game->teams[team_number]->me); team_number++) - { - } - - if(echo->player->team->game->teams[team_number]==NULL) - { - is_end=true; - return; - } - -} - - -int SearchTools::is_flag(Echo& echo, int x, int y) -{ - Building** buildings=echo.player->team->myBuildings; - for(int n=0; nposX==x && b->posY==y) - { - if(b->type->shortTypeNum > (int)(IntBuildingType::DEFENSE_BUILDING) && b->type->shortTypeNum < (int)(IntBuildingType::STONE_WALL)) - { - return b->gid; - } - } - } - } - return NOGBID; -} - - - - -enemy_building_iterator::enemy_building_iterator() : is_end(true) -{ - -} - - - -enemy_building_iterator::enemy_building_iterator(Echo& echo, int team, int building_type, int level, boost::logic::tribool construction_site) : current_gid(-1), team(team), building_type(building_type), level(level), construction_site(construction_site), is_end(false), echo(&echo) -{ - set_to_next(); -} - - - -const unsigned int enemy_building_iterator::operator*() -{ - return current_gid; -} - - - -enemy_building_iterator& enemy_building_iterator::operator++() -{ - set_to_next(); - return *this; -} - - - -enemy_building_iterator enemy_building_iterator::operator++(int) -{ - enemy_building_iterator copy; - set_to_next(); - return copy; -} - - - -bool enemy_building_iterator::operator!=(const enemy_building_iterator& rhs) const -{ - if(is_end && rhs.is_end) - return false; - return is_end!=rhs.is_end || team!=rhs.team || building_type!=rhs.building_type || level!=rhs.level || bool(construction_site!=rhs.construction_site); -} - - - -void enemy_building_iterator::set_to_next() -{ - if(current_gid==-1) - { - current_index=0; - } - else - current_index++; - - while(current_indexplayer->game->teams[team]->myBuildings[current_index]; - if(b) - { - if( (b->seenByMask&echo->player->team->me - // Don't allow AIs to cheat!!!!!! - // || echo->get_starting_buildings().find(b->gid)!=echo->get_starting_buildings().end() - ) && - (building_type==-1 || b->type->shortTypeNum==building_type) && - (level==-1 || b->type->level==(level-1))) - { - if(construction_site) - { - if(b->type->isBuildingSite) - { - current_gid=b->gid; - break; - } - } - else if(!construction_site) - { - if(!b->type->isBuildingSite) - { - current_gid=b->gid; - break; - } - } - else - { - current_gid=b->gid; - break; - } - } - } - current_index++; - } - - if(current_index==Building::MAX_COUNT) - is_end=true; -} - - - - -MapInfo::MapInfo(Echo& echo) : echo(echo) -{ - -} - - - -int MapInfo::get_width() -{ - return echo.player->map->getW(); -} - - - -int MapInfo::get_height() -{ - return echo.player->map->getH(); -} - - - -bool MapInfo::is_forbidden_area(int x, int y) -{ - return echo.player->map->isForbidden(x, y, echo.player->team->me); -} - - - -bool MapInfo::is_guard_area(int x, int y) -{ - return echo.player->map->isGuardArea(x, y, echo.player->team->me); -} - - - -bool MapInfo::is_clearing_area(int x, int y) -{ - return echo.player->map->isClearArea(x, y, echo.player->team->me); -} - - - -bool MapInfo::is_discovered(int x, int y) -{ - return echo.player->map->isMapDiscovered(x, y, echo.player->team->me); -} - - - -bool MapInfo::is_ressource(int x, int y, int type) -{ - return echo.player->map->isRessourceTakeable(x, y, type); -} - - - -bool MapInfo::is_ressource(int x, int y) -{ - return echo.player->map->isRessource(x, y); -} - - - -bool MapInfo::is_water(int x, int y) -{ - return echo.player->map->isWater(x, y); -} - - - -bool MapInfo::is_sand(int x, int y) -{ - return echo.player->map->isSand(x, y); -} - - - -bool MapInfo::is_grass(int x, int y) -{ - return echo.player->map->isGrass(x, y); -} - - - -bool MapInfo::backs_onto_sand(int x, int y) -{ - if(echo.player->map->hasSand(x-1, y)) - return true; - if(echo.player->map->hasSand(x+1, y)) - return true; - if(echo.player->map->hasSand(x-1, y-1)) - return true; - if(echo.player->map->hasSand(x, y-1)) - return true; - if(echo.player->map->hasSand(x+1, y-1)) - return true; - if(echo.player->map->hasSand(x-1, y+1)) - return true; - if(echo.player->map->hasSand(x, y+1)) - return true; - if(echo.player->map->hasSand(x+1, y+1)) - return true; - return false; -} - - - -int MapInfo::get_ammount_ressource(int x, int y) -{ - return echo.player->map->getRessource(x, y).amount; -} - - - -Echo::Echo(EchoAI* echoai, Player* player) : player(player), echoai(echoai), gm(), br(player, *this), fm(*this), timer(0) -{ - previous_building_id=-1; - from_load_timer=0; - is_fruit=false; -} - - -unsigned int Echo::add_building_order(Construction::BuildingOrder* bo) -{ - building_orders.push_back(boost::shared_ptr(bo)); - bo->queue_gradients(get_gradient_manager()); - unsigned int id=br.register_building(); - bo->id=id; - return id; -} - - -void Echo::add_management_order(Management::ManagementOrder* mo) -{ - management_orders.push_back(boost::shared_ptr(mo)); -} - - -void Echo::update_management_orders() -{ - for(std::vector >::iterator i=management_orders.begin(); i!=management_orders.end();) - { - boost::logic::tribool passes=(*i)->passes_conditions(*this); - if(passes) - { - size_t pos = i - management_orders.begin(); - (*i)->modify(*this); - management_orders.erase(management_orders.begin() + pos); - i = management_orders.begin() + pos; - continue; - } - else if(!passes) - { - } - else - { - size_t pos = i - management_orders.begin(); - management_orders.erase(i); - i = management_orders.begin() + pos; - continue; - } - ++i; - } -} - - - -void Echo::add_ressource_tracker(Management::RessourceTracker* rt, int building_id) -{ - ressource_trackers[building_id]=boost::make_tuple(boost::shared_ptr(rt), true); -} - - - -boost::shared_ptr Echo::get_ressource_tracker(int building_id) -{ - if(ressource_trackers.find(building_id)==ressource_trackers.end()) - return boost::shared_ptr(); - return ressource_trackers[building_id].get<0>(); -} - - - -void Echo::pause_ressource_tracker(int building_id) -{ - ressource_trackers[building_id].get<1>()=false; -} - - - -void Echo::unpause_ressource_tracker(int building_id) -{ - ressource_trackers[building_id].get<1>()=true; -} - - - -void Echo::update_ressource_trackers() -{ - for(std::map, bool> >::iterator i = ressource_trackers.begin(); i!=ressource_trackers.end();) - { - if(!br.is_building_found(i->first) && !br.is_building_pending(i->first)) - { - std::map, bool> >::iterator current=i; - ++i; - ressource_trackers.erase(current); - continue; - } - else if(br.is_building_found(i->first)) - { - if(i->second.get<1>()) - i->second.get<0>()->tick(); - } - ++i; - } -} - - - -void Echo::update_building_orders() -{ - for(std::vector >::iterator i=building_orders.begin(); i!=building_orders.end();) - { - boost::logic::tribool passes=(*i)->passes_conditions(*this); - if(passes) - { - if(!(previous_building_id==-1 || br.is_building_found(previous_building_id) || !br.is_building_pending(previous_building_id))) - break; - position p=(*i)->find_location(*this, player->map, *gm); - if(p.x != 0 || p.y != 0) - { - br.issue_order((*i)->id, p.x, p.y, (*i)->get_building_type()); - Sint32 type=-1; - if((*i)->get_building_type()>IntBuildingType::DEFENSE_BUILDING && (*i)->get_building_type() buildingsTypes.getTypeNum(IntBuildingType::reverseConversionMap[(*i)->get_building_type()], 0, false); - ManagementOrder* mo_flag=new AssignWorkers((*i)->get_number_of_workers(), (*i)->id); - add_management_order(mo_flag); - } - else - { - type=globalContainer->buildingsTypes.getTypeNum(IntBuildingType::reverseConversionMap[(*i)->get_building_type()], 0, true); - ManagementOrder* mo_during_construction=new AssignWorkers((*i)->get_number_of_workers(), (*i)->id); - mo_during_construction->add_condition(new ParticularBuilding(new UnderConstruction, (*i)->id)); - add_management_order(mo_during_construction); - } - orders.push_back(shared_ptr(new OrderCreate(player->team->teamNumber, p.x, p.y, type, 1, 1))); - previous_building_id=(*i)->id; - i=building_orders.erase(i); - break; - } - else - { - br.remove_building((*i)->id); - i=building_orders.erase(i); - continue; - } - } - else if(!passes) - { - } - else - { - br.remove_building((*i)->id); - i=building_orders.erase(i); - continue; - } - ++i; - } -} - - - -void Echo::init_starting_buildings() -{ - for(int t=0; tgame->teams[t]) - { - for(int bu=0; bugame->teams[t]->myBuildings[bu]; - if(b) - { - starting_buildings.insert(b->gid); - } - } - } - } -} - -void Echo::check_fruit() -{ - MapInfo mi(*this); - for(int x=0; xreadEnterSection("EchoAI"); - signature_check(stream, player, versionMinor); - - stream->readEnterSection("orders"); - Uint32 ordersSize = stream->readUint32("size"); - for (Uint32 ordersIndex = 0; ordersIndex < ordersSize; ordersIndex++) - { - stream->readEnterSection(ordersIndex); - size_t size=stream->readUint32("size"); - Uint8* buffer = new Uint8[size+1]; - stream->read(buffer, size+1, "data"); - orders.push_back(Order::getOrder(buffer, size+1, versionMinor)); - // FIXME : clear the container before load - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - signature_check(stream, player, versionMinor); - - br.load(stream, player, versionMinor); - - signature_check(stream, player, versionMinor); - - fm.load(stream, player, versionMinor); - - signature_check(stream, player, versionMinor); - - - stream->readEnterSection("management_orders"); - Uint32 managementSize=stream->readUint32("size"); - for(Uint32 managementIndex = 0; managementIndex < managementSize; ++managementIndex) - { - stream->readEnterSection(managementIndex); - signature_check(stream, player, versionMinor); - signature_check(stream, player, versionMinor); - boost::shared_ptr mo=boost::shared_ptr(ManagementOrder::load_order(stream, player, versionMinor)); - management_orders.push_back(mo); - signature_check(stream, player, versionMinor); - signature_check(stream, player, versionMinor); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - signature_check(stream, player, versionMinor); - - stream->readEnterSection("building_orders"); - Uint32 buildingSize=stream->readUint32("size"); - building_orders.resize(buildingSize); - for(Uint32 buildingIndex = 0; buildingIndex < buildingSize; ++buildingIndex) - { - stream->readEnterSection(buildingIndex); - building_orders[buildingIndex]=boost::shared_ptr(new BuildingOrder); - building_orders[buildingIndex]->load(stream, player, versionMinor); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - - signature_check(stream, player, versionMinor); - - stream->readEnterSection("ressource_trackers"); - Uint32 ressourceTrackerSize=stream->readUint32("size"); - for(Uint32 ressourceTrackerIndex=0; ressourceTrackerIndexreadEnterSection(ressourceTrackerIndex); - int id=stream->readUint32("echo_building_id"); - boost::shared_ptr rt(new RessourceTracker(*this, stream, player, versionMinor)); - bool activated=stream->readUint8("active"); - ressource_trackers[id]=boost::make_tuple(rt, activated); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - signature_check(stream, player, versionMinor); - - stream->readEnterSection("starting_buildings"); - Uint32 startingBuildingSize=stream->readUint32("size"); - for(Uint32 startingBuildingIndex=0; startingBuildingIndexreadEnterSection(startingBuildingIndex); - starting_buildings.insert(stream->readUint32("gid")); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - - signature_check(stream, player, versionMinor); - - timer=stream->readUint32("timer"); - update_gm=stream->readUint8("update_gm"); - - allies=stream->readUint32("allies"); - enemies=stream->readUint32("enemies"); - inn_view=stream->readUint32("inn_view"); - market_view=stream->readUint32("market_view"); - other_view=stream->readUint32("other_view"); - - signature_check(stream, player, versionMinor); - - echoai->load(stream, player, versionMinor); - - - signature_check(stream, player, versionMinor); - - stream->readLeaveSection(); - signature_check(stream, player, versionMinor); - - - return true; -} - - - -void Echo::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("EchoAI"); - - signature_write(stream); - - stream->writeEnterSection("orders"); - stream->writeUint32((Uint32)orders.size(), "size"); - Uint32 ordersIndex = 0; - for (std::list >::iterator i = orders.begin(); i!=orders.end(); ++i) - { - stream->writeEnterSection(ordersIndex); - stream->writeUint32((*i)->getDataLength(), "size"); - ///one byte indicating the type is required to be written for order. - stream->writeUint8((*i)->getOrderType(), "type"); - stream->write((*i)->getData(), (*i)->getDataLength(), "data"); - stream->writeLeaveSection(); - ordersIndex++; - } - stream->writeLeaveSection(); - - signature_write(stream); - - br.save(stream); - - signature_write(stream); - - fm.save(stream); - - signature_write(stream); - - - stream->writeEnterSection("management_orders"); - stream->writeUint32(management_orders.size(), "size"); - for(Uint32 managementIndex = 0; managementIndex < management_orders.size(); ++managementIndex) - { - stream->writeEnterSection(managementIndex); - signature_write(stream); - signature_write(stream); - Management::ManagementOrder::save_order(management_orders[managementIndex].get(), stream); - signature_write(stream); - signature_write(stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - signature_write(stream); - - stream->writeEnterSection("building_orders"); - stream->writeUint32(building_orders.size(), "size"); - for(Uint32 buildingIndex = 0; buildingIndex < building_orders.size(); ++buildingIndex) - { - stream->writeEnterSection(buildingIndex); - building_orders[buildingIndex]->save(stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - signature_write(stream); - - stream->writeEnterSection("ressource_trackers"); - stream->writeUint32(ressource_trackers.size(), "size"); - Uint32 ressourceTrackerIndex=0; - for(tracker_iterator i=ressource_trackers.begin(); i!=ressource_trackers.end(); ++ressourceTrackerIndex, ++i) - { - stream->writeEnterSection(ressourceTrackerIndex); - stream->writeUint32(i->first, "echo_building_id"); - i->second.get<0>()->save(stream); - stream->writeUint8(i->second.get<1>(), "active"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - signature_write(stream); - - stream->writeEnterSection("starting_buildings"); - Uint32 startingBuildingIndex=0; - stream->writeUint32(starting_buildings.size(), "size"); - for(std::set::iterator i=starting_buildings.begin(); i!=starting_buildings.end(); ++i, ++startingBuildingIndex) - { - stream->writeEnterSection(startingBuildingIndex); - stream->writeUint32(*i, "gid"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - signature_write(stream); - - stream->writeUint32(timer, "timer"); - stream->writeUint8(update_gm, "update_gm"); - - stream->writeUint32(allies, "allies"); - stream->writeUint32(enemies, "enemies"); - stream->writeUint32(inn_view, "inn_view"); - stream->writeUint32(market_view, "market_view"); - stream->writeUint32(other_view, "other_view"); - - signature_write(stream); - - echoai->save(stream); - - - signature_write(stream); - - stream->writeLeaveSection(); - signature_write(stream); -} - -#include "TextStream.h" - -boost::shared_ptr Echo::getOrder(void) -{ -// for(int x=0; xmap->getW(); ++x) -// { -// for(int y=0; ymap->getH(); ++y) -// { -// player->map->setMapDiscovered(x, y, player->team->me); -// } -// } -/* - if(timer%128==0) - { - OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend("glob2.world-desynchronization.dump.txt")); - player->game->save(stream, false, "glob2.world-desynchronization.dump.txt"); - delete stream; - } -*/ - if(!gm) - { - gm.reset(new GradientManager(player->map)); - update_gm=true; - for(int x=0; xteam->game->gameHeader.getNumberOfPlayers(); ++x) - { - if(player->team->game->players[x]!=NULL) - { - if(player->team->game->players[x]->type>=BasePlayer::P_AI) - { - Echo* other=dynamic_cast(player->team->game->players[x]->ai->aiImplementation); - if(other) - { - if(!other->gm) - { - other->gm=gm; - other->update_gm=false; -// std::cout<<"Linked with another AI, number "<team->allies; - enemies=player->team->enemies; - market_view=player->team->sharedVisionExchange; - inn_view=player->team->sharedVisionFood; - other_view=player->team->sharedVisionOther; - } - - if(!orders.empty()) - { - boost::shared_ptr order=orders.front(); - orders.erase(orders.begin()); - return order; - } - if(update_gm) - gm->update(); - br.tick(); - update_ressource_trackers(); - update_management_orders(); - echoai->tick(*this); - update_management_orders(); - update_building_orders(); - timer++; - from_load_timer++; - return boost::shared_ptr(new NullOrder()); -} - - - -ReachToInfinity::ReachToInfinity() -{ - timer=0; - flag_on_cherry=false; - flag_on_orange=false; - flag_on_prune=false; -} - - -bool ReachToInfinity::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - stream->readEnterSection("ReachToInfinity"); - timer=stream->readUint32("timer"); - flag_on_cherry=stream->readUint32("flag_on_cherry"); - flag_on_orange=stream->readUint32("flag_on_orange"); - flag_on_prune=stream->readUint32("flag_on_prune"); - - stream->readEnterSection("flags_on_enemy"); - Uint32 flagsOnEnemySize=stream->readUint32("size"); - for(Uint32 flagsOnEnemyIndex=0; flagsOnEnemyIndexreadEnterSection(flagsOnEnemyIndex); - flags_on_enemy.insert(stream->readUint32("gid")); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->readLeaveSection(); - return true; -} - - -void ReachToInfinity::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("ReachToInfinity"); - stream->writeUint32(timer, "timer"); - stream->writeUint32(flag_on_cherry, "flag_on_cherry"); - stream->writeUint32(flag_on_orange, "flag_on_orange"); - stream->writeUint32(flag_on_prune, "flag_on_prune"); - - stream->writeEnterSection("flags_on_enemy"); - Uint32 flagsOnEnemyIndex=0; - stream->writeUint32(flags_on_enemy.size(), "size"); - for(std::set::iterator i=flags_on_enemy.begin(); i!=flags_on_enemy.end(); ++i, ++flagsOnEnemyIndex) - { - stream->writeEnterSection(flagsOnEnemyIndex); - stream->writeUint32(*i, "gid"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->writeLeaveSection(); -} - - -void ReachToInfinity::tick(Echo& echo) -{ - timer++; - if(timer==1) - { - BuildingSearch bs(echo); - for(building_search_iterator i = bs.begin(); i!=bs.end(); ++i) - { - if(echo.get_building_register().get_type(*i)==IntBuildingType::SWARM_BUILDING) - { - ManagementOrder* mo_completion=new AssignWorkers(5, *i); - echo.add_management_order(mo_completion); - - ManagementOrder* mo_ratios=new ChangeSwarm(15, 1, 0, *i); - mo_ratios->add_condition(new ParticularBuilding(new NotUnderConstruction, *i)); - echo.add_management_order(mo_ratios); - - ManagementOrder* mo_tracker=new AddRessourceTracker(12, CORN, *i); - echo.add_management_order(mo_tracker); - } - if(echo.get_building_register().get_type(*i)==IntBuildingType::FOOD_BUILDING) - { - ManagementOrder* mo_tracker=new AddRessourceTracker(12, CORN, *i); - echo.add_management_order(mo_tracker); - } - } - } - -/* - ///This is demonstration code for the advanced use of Conditions - if(timer==100) - { - for(int g=0; g<1; ++g) - { - int prev_id=-1; - int first_id=-1; - int fifth_id=-1; - for(int n=0; n<15; ++n) - { - //The main order for the inn - BuildingOrder* bo = new BuildingOrder(IntBuildingType::FOOD_BUILDING, 2); - - //Constraints arround the location of wheat - AIEcho::Gradients::GradientInfo gi_wheat; - gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); - //You want to be close to wheat - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, 4)); - //You can't be farther than 10 units from wheat - bo->add_constraint(new AIEcho::Construction::MaximumDistance(gi_wheat, 10)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 3)); - - //Constraints arround the location of fruit - AIEcho::Gradients::GradientInfo gi_fruit; - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); - //You want to be reasnobly close to fruit, closer if possible - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_fruit, 1)); - - if(prev_id!=-1) - { - bo->add_condition(new EitherCondition(new ParticularBuilding(new NotUnderConstruction, prev_id), new BuildingDestroyed(prev_id))); - } - else - bo->add_condition(new Population(true, true, true, 5, Population::Greater)); - - //Add the building order to the list of orders - unsigned int id=echo.add_building_order(bo); - - if(prev_id!=-1) - { - ManagementOrder* mo_upgrade = new UpgradeRepair(id); - mo_upgrade->add_condition(new ParticularBuilding(new NotUnderConstruction, prev_id)); - mo_upgrade->add_condition(new ParticularBuilding(new BuildingLevel(2), prev_id)); - echo.add_management_order(mo_upgrade); - - ManagementOrder* mo_assign=new AssignWorkers(6, id); - mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, id)); - mo_assign->add_condition(new ParticularBuilding(new BuildingLevel(2), id)); - echo.add_management_order(mo_assign); - - ManagementOrder* mo_finish=new AssignWorkers(2, id); - mo_finish->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - mo_finish->add_condition(new ParticularBuilding(new BuildingLevel(2), id)); - echo.add_management_order(mo_finish); - } - if(n==0) - { - first_id=id; - } - if(n==4) - { - fifth_id=id; - } - - ManagementOrder* mo_completion=new AssignWorkers(1, id); - mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_completion); - - ManagementOrder* mo_tracker=new AddRessourceTracker(12, id, CORN); - mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_tracker); - - ManagementOrder* mo_delete=new DestroyBuilding(id); - mo_delete->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - mo_delete->add_condition(new ParticularBuilding(new RessourceTrackerAge(500, RessourceTrackerAge::Greater), id)); - mo_delete->add_condition(new ParticularBuilding(new RessourceTrackerAmount(48, RessourceTrackerAmount::Lesser), id)); - echo.add_management_order(mo_delete); - - ManagementOrder* mo_reconstruct = new SendMessage("construct inn"); - mo_reconstruct->add_condition(new BuildingDestroyed(id)); - echo.add_management_order(mo_reconstruct); - - prev_id=id; - } - - ManagementOrder* mo_upgrade = new UpgradeRepair(first_id); - mo_upgrade->add_condition(new ParticularBuilding(new NotUnderConstruction, fifth_id)); - echo.add_management_order(mo_upgrade); - - ManagementOrder* mo_assign=new AssignWorkers(6, first_id); - mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, first_id)); - mo_assign->add_condition(new ParticularBuilding(new BuildingLevel(2), first_id)); - echo.add_management_order(mo_assign); - - ManagementOrder* mo_finish=new AssignWorkers(2, first_id); - mo_finish->add_condition(new ParticularBuilding(new NotUnderConstruction, first_id)); - mo_finish->add_condition(new ParticularBuilding(new BuildingLevel(2), first_id)); - echo.add_management_order(mo_finish); - } - } - -*/ - - - - //Explorer flags on the three nearest fruit trees - if((timer%100)==0) - { - if(echo.is_fruit_on_map()) - { - // BuildingSearch bs_flag(echo); - // bs_flag.add_condition(new SpecificBuildingType(IntBuildingType::EXPLORATION_FLAG)); - // const int number=bs_flag.count_buildings(); - if(echo.get_team_stats().numberUnitPerType[EXPLORER]>=6 && !flag_on_cherry && !flag_on_orange && !flag_on_prune) - { - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - - if(!flag_on_cherry) - { - //The main order for the exploration flag - BuildingOrder* bo_cherry = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 2); - - //You want the closest fruit to your settlement possible - bo_cherry->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); - - //Constraint arround the location of fruit - AIEcho::Gradients::GradientInfo gi_cherry; - gi_cherry.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); - //You want to be ontop of the cherry trees - bo_cherry->add_constraint(new AIEcho::Construction::MaximumDistance(gi_cherry, 0)); - - //Add the building order to the list of orders - unsigned int id_cherry=echo.add_building_order(bo_cherry); - - if(id_cherry!=INVALID_BUILDING) - { - ManagementOrder* mo_completion=new ChangeFlagSize(4, id_cherry); - echo.add_management_order(mo_completion); - flag_on_cherry=true; - - for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) - { - ManagementOrder* mo_alliance=new ChangeAlliances(*i, indeterminate, indeterminate, indeterminate, true, indeterminate); - echo.add_management_order(mo_alliance); - } - } - } - - if(!flag_on_orange) - { - //The main order for the exploration flag - BuildingOrder* bo_orange = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 2); - - //You want the closest fruit to your settlement possible - bo_orange->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); - - //Constraints arround the location of fruit - AIEcho::Gradients::GradientInfo gi_orange; - gi_orange.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); - //You want to be ontop of the orange trees - bo_orange->add_constraint(new AIEcho::Construction::MaximumDistance(gi_orange, 0)); - - unsigned int id_orange=echo.add_building_order(bo_orange); - - if(id_orange!=INVALID_BUILDING) - { - ManagementOrder* mo_completion=new ChangeFlagSize(4, id_orange); - echo.add_management_order(mo_completion); - flag_on_orange=true; - - for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) - { - ManagementOrder* mo_alliance=new ChangeAlliances(*i, indeterminate, indeterminate, indeterminate, true, indeterminate); - echo.add_management_order(mo_alliance); - } - } - } - - if(!flag_on_prune) - { - //The main order for the exploration flag - BuildingOrder* bo_prune = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 2); - - //You want the closest fruit to your settlement possible - bo_prune->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); - - AIEcho::Gradients::GradientInfo gi_prune; - gi_prune.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); - //You want to be ontop of the prune trees - bo_prune->add_constraint(new AIEcho::Construction::MaximumDistance(gi_prune, 0)); - - //Add the building order to the list of orders - unsigned int id_prune=echo.add_building_order(bo_prune); - - if(id_prune!=INVALID_BUILDING) - { - ManagementOrder* mo_completion=new ChangeFlagSize(4, id_prune); - echo.add_management_order(mo_completion); - flag_on_prune=true; - - for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) - { - ManagementOrder* mo_alliance=new ChangeAlliances(*i, indeterminate, indeterminate, indeterminate, true, indeterminate); - echo.add_management_order(mo_alliance); - } - } - } - } - } - } - - //Place exploration flags on the enemy swarms - if((timer%120)==0) - { - if(echo.get_team_stats().numberUnitPerType[EXPLORER]>=3) - { - for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) - { - for(enemy_building_iterator ebi(echo, *i, IntBuildingType::SWARM_BUILDING, -1, false); ebi!=enemy_building_iterator(); ++ebi) - { - if(flags_on_enemy.find(*i)!=flags_on_enemy.end()) - continue; - - BuildingOrder* bo = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 1); - bo->add_constraint(new CenterOfBuilding(*ebi)); - unsigned int id=echo.add_building_order(bo); - - if(id!=INVALID_BUILDING) - { - ManagementOrder* mo_completion=new ChangeFlagSize(12, id); - echo.add_management_order(mo_completion); - - ManagementOrder* mo_destroyed=new DestroyBuilding(id); - mo_destroyed->add_condition(new EnemyBuildingDestroyed(echo, *ebi)); - echo.add_management_order(mo_destroyed); - - flags_on_enemy.insert(*i); - } - } - } - } - } - - - - //Standard Inns near wheat - if((timer%200)==0 && (timer%2000)!=0) - { - BuildingSearch bs_level1(echo); - bs_level1.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); - bs_level1.add_condition(new BuildingLevel(1)); - const int number1=bs_level1.count_buildings(); - - BuildingSearch bs_level2(echo); - bs_level2.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); - bs_level2.add_condition(new BuildingLevel(2)); - const int number2=bs_level2.count_buildings(); - - BuildingSearch bs_level3(echo); - bs_level3.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); - bs_level3.add_condition(new BuildingLevel(3)); - const int number3=bs_level3.count_buildings(); - - if((echo.player->team->stats.getLatestStat()->totalUnit)>=(number1*8 + number2*12 + number3*16)) - { - //The main order for the inn - BuildingOrder* bo = new BuildingOrder(IntBuildingType::FOOD_BUILDING, 2); - - //Constraints arround the location of wheat - AIEcho::Gradients::GradientInfo gi_wheat; - gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); - //You want to be close to wheat - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, 4)); - //You can't be farther than 10 units from wheat - bo->add_constraint(new AIEcho::Construction::MaximumDistance(gi_wheat, 10)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 3)); - - if(echo.is_fruit_on_map()) - { - //Constraints arround the location of fruit - AIEcho::Gradients::GradientInfo gi_fruit; - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); - //You want to be reasnobly close to fruit, closer if possible - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_fruit, 1)); - } - - //Add the building order to the list of orders - unsigned int id=echo.add_building_order(bo); - -// std::cout<<"inn ordered, id="<add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_completion); - - ManagementOrder* mo_tracker=new AddRessourceTracker(12, CORN, id); - mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_tracker); - } - } - - //Standard swarms near wheat. Uses special mechanism, builds more swarms early on. - if((timer%2000)==0) - { - BuildingSearch bs(echo); - bs.add_condition(new SpecificBuildingType(IntBuildingType::SWARM_BUILDING)); - const int number=bs.count_buildings(); - if((number<=3 && (echo.player->team->stats.getLatestStat()->totalUnit/20)>=number) || - (echo.player->team->stats.getLatestStat()->totalUnit/50)>=number) - { -// std::cout<<"Constructing swarm"<add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, 4)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 3)); - - //Add the building order to the list of orders - unsigned int id=echo.add_building_order(bo); - -// std::cout<<"Swarm ordered, id="<add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_completion); - - //Change the ratio of the swarm when its finished - ManagementOrder* mo_ratios=new ChangeSwarm(15, 1, 0, id); - mo_ratios->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_ratios); - - //Add a tracker - ManagementOrder* mo_tracker=new AddRessourceTracker(12, CORN, id); - mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_tracker); - - } - } - - //Standard racetrack near stone and wood - if((timer%2000)==500) - { - BuildingSearch bs(echo); - bs.add_condition(new SpecificBuildingType(IntBuildingType::WALKSPEED_BUILDING)); - const int number=bs.count_buildings(); - if((echo.player->team->stats.getLatestStat()->totalUnit/60)>=number && number<3) - { - //The main order for the racetrack - BuildingOrder* bo = new BuildingOrder(IntBuildingType::WALKSPEED_BUILDING, 6); - - //Constraints arround the location of wood - AIEcho::Gradients::GradientInfo gi_wood; - gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); - //You want to be close to wood - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, 4)); - - //Constraints arround the location of stone - AIEcho::Gradients::GradientInfo gi_stone; - gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); - //You want to be close to stone - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_stone, 1)); - //But not to close, so you have room to upgrade - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_stone, 2)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 4)); - - //Add the building order to the list of orders - echo.add_building_order(bo); - } - } - - //Standard swimming pool near wheat and wood - if((timer%2000)==1000) - { - BuildingSearch bs(echo); - bs.add_condition(new SpecificBuildingType(IntBuildingType::SWIMSPEED_BUILDING)); - const int number=bs.count_buildings(); - if((echo.player->team->stats.getLatestStat()->totalUnit/60)>=number && number<3) - { - //The main order for the swimmingpool - BuildingOrder* bo = new BuildingOrder(IntBuildingType::SWIMSPEED_BUILDING, 6); - - //Constraints arround the location of wood - AIEcho::Gradients::GradientInfo gi_wood; - gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); - //You want to be close to wood - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, 4)); - - //Constraints arround the location of wheat - AIEcho::Gradients::GradientInfo gi_wheat; - gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); - //You want to be close to wheat - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, 1)); - - //Constraints arround the location of stone - AIEcho::Gradients::GradientInfo gi_stone; - gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); - //You don't want to be too close, so you have room to upgrade - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_stone, 2)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 4)); - - //Add the building order to the list of orders - echo.add_building_order(bo); - } - } - - - //Standard school inland away from the enemies - if((timer%2000)==1500) - { - BuildingSearch bs(echo); - bs.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - const int number=bs.count_buildings(); - if((echo.player->team->stats.getLatestStat()->totalUnit/60)>=number && number<4) - { - //The main order for the school - BuildingOrder* bo = new BuildingOrder(IntBuildingType::SCIENCE_BUILDING, 5); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 4)); - - //Constraints arround the enemy - AIEcho::Gradients::GradientInfo gi_enemy; - for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) - { - gi_enemy.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(*i, false)); - } - gi_enemy.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - bo->add_constraint(new AIEcho::Construction::MaximizedDistance(gi_enemy, 3)); - - //Add the building order to the list of orders - echo.add_building_order(bo); - } - } - - - //Level 1 to level 2 upgrades - if((timer%300)==0) - { - BuildingSearch level_twos(echo); - level_twos.add_condition(new BeingUpgradedTo(2)); - const int level_two_counts=level_twos.count_buildings(); - - BuildingSearch schools(echo); - schools.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - schools.add_condition(new NotUnderConstruction); - const int school_counts=schools.count_buildings(); - - BuildingSearch buildings(echo); - buildings.add_condition(new BuildingLevel(1)); - const int total_buildings=buildings.count_buildings(); - if(level_two_counts<=(total_buildings/15) && school_counts>0) - { - BuildingSearch bs(echo); - bs.add_condition(new Upgradable); - bs.add_condition(new BuildingLevel(1)); - if(school_counts<2) - bs.add_condition(new NotSpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - std::vector buildings; - std::copy(bs.begin(), bs.end(), std::back_insert_iterator >(buildings)); - - if(buildings.size()!=0) - { - int chosen=syncRand()%buildings.size(); - ManagementOrder* uro = new UpgradeRepair(buildings[chosen]); - echo.add_management_order(uro); - - int assigned=echo.get_building_register().get_assigned(buildings[chosen]); - - ManagementOrder* mo_assign=new AssignWorkers(8, buildings[chosen]); - mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, buildings[chosen])); - echo.add_management_order(mo_assign); - - if(echo.get_building_register().get_type(buildings[chosen])==IntBuildingType::FOOD_BUILDING) - { - ManagementOrder* mo_tracker_pause=new PauseRessourceTracker(buildings[chosen]); - mo_tracker_pause->add_condition(new ParticularBuilding(new UnderConstruction, buildings[chosen])); - echo.add_management_order(mo_tracker_pause); - - ManagementOrder* mo_tracker_unpause=new UnPauseRessourceTracker(buildings[chosen]); - mo_tracker_unpause->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); - echo.add_management_order(mo_tracker_unpause); - - ManagementOrder* mo_completion=new AssignWorkers(3, buildings[chosen]); - mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); - echo.add_management_order(mo_completion); - } - else - { - ManagementOrder* mo_assign=new AssignWorkers(assigned, buildings[chosen]); - mo_assign->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); - echo.add_management_order(mo_assign); - } - } - } - } - - //Level 2 to level 3 upgrades - if((timer%300)==0) - { - BuildingSearch level_threes(echo); - level_threes.add_condition(new BeingUpgradedTo(3)); - const int level_three_counts=level_threes.count_buildings(); - - BuildingSearch schools(echo); - schools.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - schools.add_condition(new NotUnderConstruction); - schools.add_condition(new BuildingLevel(2)); - int school_counts=schools.count_buildings(); - - BuildingSearch schools2(echo); - schools2.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - schools2.add_condition(new NotUnderConstruction); - schools2.add_condition(new BuildingLevel(3)); - school_counts+=schools2.count_buildings(); - - BuildingSearch buildings(echo); - buildings.add_condition(new BuildingLevel(2)); - const int total_buildings=buildings.count_buildings(); - if(level_three_counts<=(total_buildings/15) && school_counts>0) - { - BuildingSearch bs(echo); - bs.add_condition(new Upgradable); - bs.add_condition(new BuildingLevel(2)); - if(school_counts<2) - bs.add_condition(new NotSpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - std::vector buildings; - std::copy(bs.begin(), bs.end(), std::back_insert_iterator >(buildings)); - - if(buildings.size()!=0) - { - int chosen=syncRand()%buildings.size(); - ManagementOrder* uro = new UpgradeRepair(buildings[chosen]); - echo.add_management_order(uro); - - int assigned=echo.get_building_register().get_assigned(buildings[chosen]); - - ManagementOrder* mo_assign=new AssignWorkers(8, buildings[chosen]); - mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, buildings[chosen])); - echo.add_management_order(mo_assign); - - if(echo.get_building_register().get_type(buildings[chosen])==IntBuildingType::FOOD_BUILDING) - { - ManagementOrder* mo_tracker_pause=new PauseRessourceTracker(buildings[chosen]); - mo_tracker_pause->add_condition(new ParticularBuilding(new UnderConstruction, buildings[chosen])); - echo.add_management_order(mo_tracker_pause); - - ManagementOrder* mo_tracker_unpause=new UnPauseRessourceTracker(buildings[chosen]); - mo_tracker_unpause->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); - echo.add_management_order(mo_tracker_unpause); - - ManagementOrder* mo_completion=new AssignWorkers(6, buildings[chosen]); - mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); - echo.add_management_order(mo_completion); - } - else - { - ManagementOrder* mo_assign=new AssignWorkers(assigned, buildings[chosen]); - mo_assign->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); - echo.add_management_order(mo_assign); - } - } - } - } - - - - //Delete old inns and swarms that are hard to keep full of wheat - if((timer%500)==0) - { - BuildingSearch inns(echo); - inns.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); - inns.add_condition(new NotUnderConstruction); - for(building_search_iterator i=inns.begin(); i!=inns.end(); ++i) - { - boost::shared_ptr rt=echo.get_ressource_tracker(*i); - if(rt) - { - if(rt->get_age()>1500) - { - if(rt->get_total_level() < 24*echo.get_building_register().get_level(*i)) - { - ManagementOrder* mo_destroy=new DestroyBuilding(*i); - echo.add_management_order(mo_destroy); - } - } - } - } - - - BuildingSearch swarms(echo); - swarms.add_condition(new SpecificBuildingType(IntBuildingType::SWARM_BUILDING)); - swarms.add_condition(new NotUnderConstruction); - for(building_search_iterator i=swarms.begin(); i!=swarms.end(); ++i) - { - boost::shared_ptr rt=echo.get_ressource_tracker(*i); - if(rt) - { - if(rt->get_age()>2500) - { - if(rt->get_total_level() < 18) - { - ManagementOrder* mo_destroy=new DestroyBuilding(*i); - echo.add_management_order(mo_destroy); - } - } - } - } - } - - //Farming wheat and wood near water - if((timer%250)==0) - { - AddArea* mo_farming=new AddArea(ForbiddenArea); - RemoveArea* mo_non_farming=new RemoveArea(ForbiddenArea); - AIEcho::Gradients::GradientInfo gi_water; - gi_water.add_source(new Entities::Water); - Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_water); - MapInfo mi(echo); - for(int x=0; xadd_location(x, y); - } - else - { - if((mi.is_ressource(x, y, WOOD) || - mi.is_ressource(x, y, CORN)) && - mi.is_discovered(x, y) && - !mi.is_forbidden_area(x, y) && - gradient.get_height(x, y)<10) - { - mo_farming->add_location(x, y); - } - } - } - } - } - echo.add_management_order(mo_farming); - echo.add_management_order(mo_non_farming); - } -} - - -void ReachToInfinity::handle_message(Echo& echo, const std::string& message) -{ - if(message=="construct inn") - { - //The main order for the inn - BuildingOrder* bo = new BuildingOrder(IntBuildingType::FOOD_BUILDING, 2); - - //Constraints arround the location of wheat - AIEcho::Gradients::GradientInfo gi_wheat; - gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); - //You want to be close to wheat - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, 4)); - //You can't be farther than 10 units from wheat - bo->add_constraint(new AIEcho::Construction::MaximumDistance(gi_wheat, 10)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 3)); - - //Constraints arround the location of fruit - if(echo.is_fruit_on_map()) - { - AIEcho::Gradients::GradientInfo gi_fruit; - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); - //You want to be reasnobly close to fruit, closer if possible - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_fruit, 1)); - } - - //Add the building order to the list of orders - unsigned int id=echo.add_building_order(bo); - -// std::cout<<"inn ordered, id="<add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_completion); - - ManagementOrder* mo_tracker=new AddRessourceTracker(12, CORN, id); - mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_tracker); - } -} - diff --git a/src/AIEcho.h b/src/AIEcho.h deleted file mode 100644 index e3a2054c3..000000000 --- a/src/AIEcho.h +++ /dev/null @@ -1,1926 +0,0 @@ -/* - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef AIEcho_h -#define AIEcho_h - - -#include "Map.h" -#include "AIImplementation.h" -#include "BuildingType.h" -#include "Player.h" -#include "TeamStat.h" -#include "Order.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace AIEcho -{ - class position; - - namespace Gradients - { - namespace Entities - { - class Entity; - class Building; - class AnyTeamBuilding; - class AnyBuilding; - class Ressource; - class AnyRessource; - class Water; - }; - class GradientInfo; - class Gradient; - class GradientManager; - - }; - - namespace Construction - { - class Constraint; - class MinimumDistance; - class MaximumDistance; - class MinimizedDistance; - class MaximizedDistance; - class CenterOfBuilding; - class BuildingOrder; - class FlagMap; - class BuildingRegister; - }; - - namespace Conditions - { - class Condition; - class ParticularBuilding; - class BuildingDestroyed; - class EnemyBuildingDestroyed; - class EitherCondition; - class AllConditions; - class BuildingCondition; - class NotUnderConstruction; - class UnderConstruction; - class BeingUpgraded; - class BeingUpgradedTo; - class SpecificBuildingType; - class NotSpecificBuildingType; - class BuildingLevel; - class Upgradable; - class TicksPassed; - }; - - namespace Management - { - class ManagementOrder; - class AssignWorkers; - class ChangeSwarm; - class DestroyBuilding; - class RessourceTracker; - class AddRessourceTracker; - class PauseRessourceTracker; - class UnPauseRessourceTracker; - class ChangeFlagSize; - class ChangeFlagMinimumLevel; - class GlobalManagementOrder; - class AddArea; - class RemoveArea; - class ChangeAlliances; - class UpgradeRepair; - }; - - namespace SearchTools - { - class building_search_iterator; - class BuildingSearch; - class enemy_team_iterator; - class enemy_building_iterator; - }; - - class Echo; - class EchoAI; - class ReachToInfinity; -}; - - -namespace AIEcho -{ - ///A position on a map. Simple x and y cordinates, and a comparison operator for stoarge and maps and sets - class position - { - public: - position() : x(0), y(0) {} - position(int x, int y) : x(x), y(y) {} - int x; - int y; - bool operator<(const position& rhs) const - { - if(x!=rhs.x) - return x > sources; - std::vector > obstacles; - mutable boost::logic::tribool needs_updated; - }; - - ///Heres a few convience functions for creating a Gradient Info - ///@{ - GradientInfo make_gradient_info(Entities::Entity* source); - GradientInfo make_gradient_info_obstacle(Entities::Entity* source, Entities::Entity* obstacle); - GradientInfo make_gradient_info(Entities::Entity* source1, Entities::Entity* source2); - GradientInfo make_gradient_info_obstacle(Entities::Entity* source1, Entities::Entity* source2, Entities::Entity* obstacle); - ///@} - - - - ///A generic, all purpose gradient. The gradient is referenced by its GradientInfo, which it uses continually in its computation. - ///Echo gradients are probably the slowest gradients in the game. However, they have one key difference compared to other gradinents, - ///they can be shared, and they are generic, even more so than Nicowar gradients (which where decently generic, but not entirely). - class Gradient - { - public: - explicit Gradient(const GradientInfo& gi); - ///Gets the distance of the provided position from the nearest source - int get_height(int posx, int posy) const; - private: - friend class AIEcho::Gradients::GradientManager; - - ///Causes the gradient to be updated - void recalculate(Map* map); - ///Returns the gradient info for comparison - const GradientInfo& get_gradient_info() const { return gradient_info; } - int width; - int get_pos(int x, int y) const { return y*width + x; } - GradientInfo gradient_info; - std::vector gradient; -// Sint16* gradient; - }; - - ///The gradient manager is a very important part of the system, just like the gradient itself is. The gradient manager takes upon the task - ///of managing and updating various gradients in the game. It returns a matching gradient when provided a GradientInfo. - ///This object is shared among all Echo AI's, which means gradients that aren't specific to a particular team (such as most Ressource - ///gradients) don't have to be recalculated for every Echo AI seperately. This saves allot of cpu time when their are multiple Echo AI's. - class GradientManager - { - public: - explicit GradientManager(Map* map); - ///A simple function, returns the Gradient that matches the GradientInfo. Its garunteed to be up to date within the last 150 ticks. - ///If a matching gradient isn't found, a new one is created. 150 ticks may sound like a large amount of leeway, however, most - ///gradients are updated sooner than that. As well, at normal game speed, 150 ticks is only 6 seconds, and you can count it yourself, - ///not much changes in the game in six seconds. - Gradient& get_gradient(const GradientInfo& gi); - ///Queues up a gradient with GradientInfo to be updated. This gradient will be updated once and then never again. - void queue_gradient(const GradientInfo& gi); - ///Returns true if the gradient GradientInfo has been updated recently. - bool is_updated(const GradientInfo& gi); - private: - friend class AIEcho::Echo; - void update(); - static int increment(const int x) { return x+1; } - std::vector > gradients; - std::queue queuedGradients; - std::vector ticks_since_update; - Map* map; - unsigned int cur_update; - int timer; - }; - }; - - ///This namespace stores all things related to the construction of new buildings. - namespace Construction - { - enum ConstraintType - { - CTMinimumDistance, - CTMaximumDistance, - CTMinimizedDistance, - CTMaximizedDistance, - CTCenterOfBuilding, - CTSinglePosition, - }; - - ///A generic constraint serves two purposes, one, to compute a score for a particular position, and two, - ///to verify that a particular position matches the requirements of the constraint. Most constraints - ///are passed a GradientInfo, as they use the distances on various gradients to do their work. - ///Keep in mind that the verifications that the position satisfies the constraint must be satisfied - ///for all points on a newly placed building, not just one (with the exception of points that aren't - ///touching the outside of the building) - class Constraint - { - public: - virtual ~Constraint(){} - protected: - friend class AIEcho::Construction::BuildingOrder; - virtual int calculate_constraint(Echo& echo, int x, int y)=0; - virtual bool passes_constraint(Echo& echo, int x, int y)=0; - ///This function is meant for the registering of GradientInfo, return NULL if the Constraint doesn't use a gradient - virtual Gradients::GradientInfo* get_gradient_info()=0; - virtual ConstraintType get_type()=0; - virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; - virtual void save(GAGCore::OutputStream *stream)=0; - static Constraint* load_constraint(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - static void save_constraint(Constraint* constraint, GAGCore::OutputStream *stream); - }; - - ///This constraint keeps buildings from being placed too close to a particular source - class MinimumDistance : public Constraint - { - public: - MinimumDistance(const Gradients::GradientInfo& gi, int distance); - protected: - MinimumDistance() :gradient_cache(NULL), distance(0) {} - friend class Constraint; - int calculate_constraint(Echo& echo, int x, int y); - bool passes_constraint(Echo& echo, int x, int y); - Gradients::GradientInfo* get_gradient_info() { return &gi; } - ConstraintType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - Gradients::GradientInfo gi; - Gradients::Gradient* gradient_cache; - int distance; - }; - - ///This constraint keeps buildings from being placed to far from a particular source - class MaximumDistance: public Constraint - { - public: - MaximumDistance(const Gradients::GradientInfo& gi, int distance); - protected: - MaximumDistance() :gradient_cache(NULL), distance(0) {} - friend class Constraint; - int calculate_constraint(Echo& echo, int x, int y); - bool passes_constraint(Echo& echo, int x, int y); - Gradients::GradientInfo* get_gradient_info() { return &gi; } - ConstraintType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - Gradients::GradientInfo gi; - Gradients::Gradient* gradient_cache; - int distance; - }; - - ///This constraint tries to make buildings closer to a particular source. It can be given a weight, - ///changing the effect the constraint has on the final position of the building - class MinimizedDistance : public Constraint - { - public: - MinimizedDistance(const Gradients::GradientInfo& gi, int weight); - protected: - MinimizedDistance() :gradient_cache(NULL), weight(0) {} - friend class Constraint; - int calculate_constraint(Echo& echo, int x, int y); - bool passes_constraint(Echo& echo, int x, int y); - Gradients::GradientInfo* get_gradient_info() { return &gi; } - ConstraintType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - Gradients::GradientInfo gi; - Gradients::Gradient* gradient_cache; - int weight; - }; - - ///This constraint tries to make buildings farther from a particular source. It can be given a weight, - ///changing the effect the constraint has on the final position of the building - class MaximizedDistance : public Constraint - { - public: - MaximizedDistance(const Gradients::GradientInfo& gi, int weight); - protected: - MaximizedDistance() :gradient_cache(NULL), weight(0) {} - friend class Constraint; - int calculate_constraint(Echo& echo, int x, int y); - bool passes_constraint(Echo& echo, int x, int y); - Gradients::GradientInfo* get_gradient_info() { return &gi; } - ConstraintType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - Gradients::GradientInfo gi; - Gradients::Gradient* gradient_cache; - int weight; - }; - - ///This constraint doesn't use gradients, unlike the other ones. In particular, it only allows one - ///position to be allowed, the center of the building with the provided GBID. Notice this is not - ///like other building ID's, it can only be obtained with enemy_building_iterator or a similair - ///method. - class CenterOfBuilding : public Constraint - { - public: - explicit CenterOfBuilding(int gbid); - protected: - CenterOfBuilding() : gbid(0) {} - friend class Constraint; - int calculate_constraint(Echo& echo, int x, int y); - bool passes_constraint(Echo& echo, int x, int y); - Gradients::GradientInfo* get_gradient_info() { return NULL; } - ConstraintType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int gbid; - }; - - - ///This constraint, againt unlike the others, does not use gradients. It only allows the given - ///position to be allowed. The resulting building will *not* be centered on it except if it is - ///a 1x1 building - class SinglePosition : public Constraint - { - public: - SinglePosition(int posx, int posy); - protected: - SinglePosition() : posx(0), posy(0) {} - friend class Constraint; - int calculate_constraint(Echo& echo, int x, int y); - bool passes_constraint(Echo& echo, int x, int y); - Gradients::GradientInfo* get_gradient_info() { return NULL; } - ConstraintType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int posx; - int posy; - }; - - - ///An order for new buildings to be constructed. It takes the type of building from IntBuildingType.h, - ///and the number of workers that should be used to construct it. - class BuildingOrder - { - public: - BuildingOrder(int building_type, int number_of_workers); - ///Adds a constraint to be used in finding a location of the building. This class takes ownership of the constraint. - void add_constraint(Constraint* constraint); - ///Adds a new condition to the building order. This assumes ownership of the condition. - void add_condition(Conditions::Condition* condition); - private: - friend class AIEcho::Echo; - BuildingOrder() {} - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - ///An internal function used to find the location to place the building - position find_location(Echo& echo, Map* map, Gradients::GradientManager& manager); - boost::logic::tribool passes_conditions(Echo& echo); - ///An internal function that has all of the constraints register their respective Gradients with the GradientManager - void queue_gradients(Gradients::GradientManager& manager); - int get_building_type() const { return building_type; } - int get_number_of_workers() const { return number_of_workers; } - int building_type; - int number_of_workers; - int id; - std::vector > constraints; - std::vector > conditions; - }; - - ///This class is used for quick lookup of flags, which aren't stored in Map like other buildings. - class FlagMap - { - public: - explicit FlagMap(Echo& echo); - int get_flag(int x, int y); - private: - friend class AIEcho::Construction::BuildingRegister; - friend class AIEcho::Echo; - void set_flag(int x, int y, int gid); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - std::vector flagmap; - int width; - Echo& echo; - }; - - ///The building register is a very important sub system of Echo. It keeps track of buildings. - ///A seemingly simple process, but very, very important. Buildings you construct are looked for, - ///found, recorded, etc. Allot of seemingly odd code is found here, meant to work arround some - ///of the difficulties of other parts of glob2, so that the AI programmer can have a seemless, - ///comfortable interface. Nothing here is directly important to an AI programmer. - ///The system puts buildings through three stages. The first is where the building order has been - ///issued by the ai, but it hasn't satisfied its conditions, and thus hasn't been sent to the glob2 - ///engine. The second is where the building conditions are satisfied and the building order - ///has been sent, but the engine is awaiting the pertimiter of the building to be cleared before - ///it sets the building in place. The third stage is where the building has been set in place, - ///and was detected on the map. In this stage, an engine gid has been found and a pointer to - ///the building in memory secured. The fourth stage is where the building is being upgraded. - ///This is to solve a very minor bug where a building is destroyed, then a different one - ///rebuilt in the same spot fast enough that the building register couldn't detect the change. - ///If the register knows when a building is being upgraded, it knows when the building is - ///expected to change in size and to what size, and this bug is solved. - ///Another unmentioned part is that during the second stage, the building can be timed out if - ///it was unable to be set for various reasons (ressources grew into its area) - class BuildingRegister - { - public: - BuildingRegister(Player* player, Echo& echo); - bool is_building_pending(unsigned int id); - bool is_building_found(unsigned int id); - bool is_building_upgrading(unsigned int id); - int get_type(unsigned int id); - int get_level(unsigned int id); - int get_assigned(unsigned int id); - Building* get_building(unsigned int id); - BuildingType* get_building_type(unsigned int id); - private: - friend class AIEcho::SearchTools::building_search_iterator; - friend class AIEcho::SearchTools::BuildingSearch; - friend class AIEcho::Construction::BuildingOrder; - friend class AIEcho::Echo; - - friend class AIEcho::Conditions::NotUnderConstruction; - friend class AIEcho::Conditions::UnderConstruction; - friend class AIEcho::Conditions::BeingUpgraded; - friend class AIEcho::Conditions::BeingUpgradedTo; - friend class AIEcho::Conditions::SpecificBuildingType; - friend class AIEcho::Conditions::NotSpecificBuildingType; - friend class AIEcho::Conditions::BuildingLevel; - friend class AIEcho::Conditions::Upgradable; - friend class AIEcho::Conditions::EnemyBuildingDestroyed; - friend class AIEcho::Conditions::TicksPassed; - - friend class AIEcho::Management::AssignWorkers; - friend class AIEcho::Management::ChangeSwarm; - friend class AIEcho::Management::DestroyBuilding; - friend class AIEcho::Management::RessourceTracker; - friend class AIEcho::Management::AddRessourceTracker; - friend class AIEcho::Management::PauseRessourceTracker; - friend class AIEcho::Management::UnPauseRessourceTracker; - friend class AIEcho::Management::ChangeFlagSize; - friend class AIEcho::Management::ChangeFlagMinimumLevel; - friend class AIEcho::Management::GlobalManagementOrder; - friend class AIEcho::Management::AddArea; - friend class AIEcho::Management::RemoveArea; - friend class AIEcho::Management::ChangeAlliances; - friend class AIEcho::Management::UpgradeRepair; - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - - ///This function initiates the BuildingRegister with any buildings that already exist on the map. - void initiate(); - ///This function registers a new building. When the building orders conditions are satisfied and the order - ///for the construction is sent to the game engine, call issue_order. - unsigned int register_building(); - ///After registering a building, this tells the register that an order for the construction has commenced - void issue_order(int id, int x, int y, int building_type); - ///Removes the building from the list of pending buildings. This may been to be done in the event that the - ///conditions for the buildings constructed can never be satisfied. - void remove_building(int id); - void set_upgrading(unsigned int id); - void tick(); - - typedef std::map >::iterator pending_iterator; - typedef std::map >::iterator found_iterator; - - found_iterator begin() { return found_buildings.begin(); } - found_iterator end() { return found_buildings.end(); } - ///The last variables in both of these is simply a "this exists" variable. Its used to combat the fact - ///that pending_buildings[id] may create a new object, and the system can't tell the difference between it and something - ///real. So bassically, the last variable is set to true when the object is supposed to be there, false is - ///the default value if its accidentilly created. - std::map > pending_buildings; - std::map > found_buildings; - unsigned int building_id; - Player* player; - Echo& echo; - }; - - }; - - ///These are all conditions on a particular Building. They are used in several places, such as when counting numbers of buildings, or - ///for setting a condition on an order to change the number of units assigned, making them very usefull. Its important to note that - ///none of the conditions work on enemies buildings, they only work on buildings on you're own team. - namespace Conditions - { - ///This is used for loading and saving purposes only - enum ConditionType - { - CParticularBuilding, - CBuildingDestroyed, - CEnemyBuildingDestroyed, - CEitherCondition, - CAllConditions, - CPopulation, - }; - - ///This is a generic condition. It can be attached to many parts of the code - class Condition - { - public: - virtual ~Condition() {} - protected: - friend class Management::ManagementOrder; - friend class Construction::BuildingOrder; - friend class EitherCondition; - friend class AllConditions; - ///This function checks if the condition passes. The third state, indeterminate, means that the condition - ///is impossible to fullfill. For example, a condition on a particular building could never pass if that - ///building is destroyed. - virtual boost::logic::tribool passes(Echo& echo)=0; - virtual ConditionType get_type()=0; - virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; - virtual void save(GAGCore::OutputStream *stream)=0; - static Condition* load_condition(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - static void save_condition(Condition* condition, GAGCore::OutputStream *stream); - }; - - ///This converts a BuildingCondition into a standard condition simply by supplying the id of the building - ///to be checked. - class ParticularBuilding : public Condition - { - public: - friend class Condition; - ParticularBuilding(BuildingCondition* condition, int id); - ~ParticularBuilding(); - boost::logic::tribool passes(Echo& echo); - ConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - ParticularBuilding(); - BuildingCondition* condition; - int id; - }; - - ///This condition matches when one of your own buildings are destroyed. It also matches when the building - ///is timed out and removed. - class BuildingDestroyed : public Condition - { - public: - BuildingDestroyed(int id); - protected: - friend class Condition; - BuildingDestroyed() {} - boost::logic::tribool passes(Echo& echo); - ConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int id; - }; - - ///This condition matches when the provided gid of the enemy building, obtained from an enemy_building_iterator, - ///is destroyed. It's meant for use with war flags or exploration flags. - class EnemyBuildingDestroyed : public Condition - { - public: - EnemyBuildingDestroyed(Echo& echo, int gbid); - protected: - friend class Condition; - EnemyBuildingDestroyed() {} - boost::logic::tribool passes(Echo& echo); - ConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int gbid; - int type; - int level; - position location; - }; - - ///Matches if either condition is true, does not require both of them - class EitherCondition : public Condition - { - public: - EitherCondition(Condition* condition1, Condition* condition2); - protected: - friend class Condition; - ~EitherCondition(); - boost::logic::tribool passes(Echo& echo); - ConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - EitherCondition(); - Condition* condition1; - Condition* condition2; - }; - - ///Matches if the given conditions are true. Anywhere between 1 and 4 conditions can be given. - ///This is made to be used in conjuction with EitherCondition - class AllConditions : public Condition - { - public: - AllConditions(Condition* a, Condition* b=NULL, Condition* c=NULL, Condition* d=NULL); - protected: - friend class Condition; - ~AllConditions(); - boost::logic::tribool passes(Echo& echo); - ConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - AllConditions(); - Condition* a; - Condition* b; - Condition* c; - Condition* d; - }; - - ///Matches when the population of the specified group of units is reached in the given method - class Population : public Condition - { - public: - enum PopulationMethod - { - Greater, - Lesser, - }; - - Population(bool workers, bool explorers, bool warriors, int num, PopulationMethod method); - protected: - friend class Condition; - ~Population(); - boost::logic::tribool passes(Echo& echo); - ConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - Population(); - bool workers; - bool explorers; - bool warriors; - int num; - PopulationMethod method; - }; - - ///This is used for loading and saving purposes only - enum BuildingConditionType - { - CNotUnderConstruction, - CUnderConstruction, - CBeingUpgraded, - CBeingUpgradedTo, - CSpecificBuildingType, - CNotSpecificBuildingType, - CBuildingLevel, - CUpgradable, - CRessourceTrackerAmount, - CRessourceTrackerAge, - CTicksPassed - }; - - ///A generic building condition has one important function, one that checks whether the condition is satisfied - class BuildingCondition - { - public: - virtual ~BuildingCondition() {} - friend class AIEcho::Management::ManagementOrder; - friend class AIEcho::Construction::BuildingOrder; - friend class AIEcho::SearchTools::BuildingSearch; - friend class ParticularBuilding; - protected: - virtual bool passes(Echo& echo, int id)=0; - virtual BuildingConditionType get_type()=0; - virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; - virtual void save(GAGCore::OutputStream *stream)=0; - static BuildingCondition* load_condition(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - static void save_condition(BuildingCondition* condition, GAGCore::OutputStream *stream); - }; - - ///This condition waits for a building not to be under construction. - class NotUnderConstruction : public BuildingCondition - { - public: - protected: - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - }; - - ///This condition waits for a building to be under construction - class UnderConstruction : public BuildingCondition - { - public: - protected: - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - }; - - ///This condition tells whether a building is being upgraded - class BeingUpgraded : public BuildingCondition - { - public: - protected: - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - }; - - ///Similair to BeingUpgraded, but this also takes a level, in which the building is being upgraded - ///to a particular level. When possible, use this instead od combining BeingUpgraded and BuildingLevel - class BeingUpgradedTo : public BuildingCondition - { - public: - BeingUpgradedTo() : level(0) {} - explicit BeingUpgradedTo(int level); - protected: - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int level; - }; - - ///This condition tells whether a building is a particular type, as defined in IntBuildingType.h - class SpecificBuildingType : public BuildingCondition - { - public: - SpecificBuildingType() : building_type(0) {} - explicit SpecificBuildingType(int building_type); - protected: - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int building_type; - }; - - ///This condition matches any building that isn't of a particular type - class NotSpecificBuildingType : public BuildingCondition - { - public: - NotSpecificBuildingType() : building_type(0) {} - explicit NotSpecificBuildingType(int building_type); - protected: - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int building_type; - }; - - ///This building matches buildings of a particular level - class BuildingLevel : public BuildingCondition - { - public: - BuildingLevel() : building_level(0) {} - explicit BuildingLevel(int building_level); - protected: - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int building_level; - }; - - ///This condition matches a building that can be upgraded - class Upgradable : public BuildingCondition - { - public: - protected: - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - }; - - ///This class compares the total amount of ressources recorded by a ressource tracker. - class RessourceTrackerAmount : public BuildingCondition - { - public: - enum TrackerMethod - { - Greater, - Lesser, - }; - - explicit RessourceTrackerAmount(int amount, TrackerMethod tracker_method); - private: - friend class BuildingCondition; - RessourceTrackerAmount(); - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - int amount; - int tracker_method; - }; - - ///This class compares the age provided by a ressource tracker - class RessourceTrackerAge : public BuildingCondition - { - public: - enum TrackerMethod - { - Greater, - Lesser, - }; - - explicit RessourceTrackerAge(int age, TrackerMethod tracker_method); - private: - friend class BuildingCondition; - RessourceTrackerAge(); - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - int age; - int tracker_method; - }; - - ///This is a debug condition that waits for a certain number of tries before it passes. - ///Not to be used under normal circumstances. Only for debugging! - class TicksPassed : public BuildingCondition - { - public: - TicksPassed() : num(0) {} - explicit TicksPassed(int num) : num(num) {} - protected: - bool passes(Echo& echo, int id); - BuildingConditionType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int num; - }; - }; - - ///This namespace stores anything related to managing you're buildings, flags and areas. - namespace Management - { - enum ManagementOrderType - { - MAssignWorkers, - MChangeSwarm, - MDestroyBuilding, - MAddRessourceTracker, - MPauseRessourceTracker, - MUnPauseRessourceTracker, - MChangeFlagSize, - MChangeFlagMinimumLevel, - MAddArea, - MRemoveArea, - MChangeAlliances, - MUpgradeRepair, - MSendMessage, - MChangeFlagPosition, - MAdjustPriority, - }; - - - ///A generic management order can have conditions attached to it. This makes management orders - ///both convinient and usefull. They will wait for the conditions to be satisfied before - ///performing their change. - class ManagementOrder - { - public: - virtual ~ManagementOrder() {} - ///Adds a new condition to the management order. This assumes ownership of the condition. - void add_condition(Conditions::Condition* condition); - protected: - virtual void modify(Echo& echo)=0; - ///This acts somewhat like a condition tester of its own. Like passes_conditions, this one - ///checks for the conditions for the management order to execute at all. indeterminate means - ///that its impossible to execute, false means wait some more and true means ready to execute - ///For example, the ChangeFlagSize order requires that the building be in existance, and - ///that its a flag. - virtual boost::logic::tribool wait(Echo& echo)=0; - - virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - virtual void save(GAGCore::OutputStream *stream); - virtual ManagementOrderType get_type()=0; - - private: - friend class AIEcho::Echo; - boost::logic::tribool passes_conditions(Echo& echo); - static ManagementOrder* load_order(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - static void save_order(ManagementOrder* mo, GAGCore::OutputStream *stream); - - std::vector > conditions; - }; - - ///Assigns a particular number of workers to a building - class AssignWorkers : public ManagementOrder - { - public: - AssignWorkers() : number_of_workers(0), building_id(0) {} - explicit AssignWorkers(int number_of_workers, int building_id); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int number_of_workers; - int building_id; - }; - - ///Changes the ratios on a swarm - class ChangeSwarm : public ManagementOrder - { - public: - ChangeSwarm() : worker_ratio(0), explorer_ratio(0), warrior_ratio(0), building_id(0) {} - ChangeSwarm(int worker_ratio, int explorer_ratio, int warrior_ratio, int building_id); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int worker_ratio; - int explorer_ratio; - int warrior_ratio; - int building_id; - }; - - ///Orders the destruction of a building - class DestroyBuilding : public ManagementOrder - { - public: - DestroyBuilding() : building_id(0) {} - DestroyBuilding(int building_id); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - int building_id; - }; - - - ///A ressource tracker is generally used for management, like most other things. A ressource trackers job is to keep - ///track of the number of ressources in a particular building, and returning averages over a small period of time. - ///Its better to use a ressource tracker than getting the ressource amounts directly, because a ressource tracker - ///returns trends, and small anomalies like an Inn running out of food for only a second don't impact its result greatly. - class RessourceTracker - { - public: - RessourceTracker(Echo& echo, GAGCore::InputStream* stream, Player* player, Sint32 versionMinor) : echo(echo) - { load(stream, player, versionMinor); } - RessourceTracker(Echo& echo, int building_id, int length, int ressource); - ///Returns the total ressources the building possessed within the time frame - int get_total_level(); - ///Returns the number of ticks the ressource tracker has been tracking. - int get_age(); - private: - friend class AIEcho::Echo; - void tick(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - std::vector record; - unsigned int position; - int timer; - int length; - Echo& echo; - int building_id; - int ressource; - }; - - ///This adds a ressource tracker to a building - class AddRessourceTracker : public ManagementOrder - { - public: - AddRessourceTracker(int length, int ressource, int building_id); - AddRessourceTracker() : length(0), building_id(0), ressource(0) {} - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - int length; - int building_id; - int ressource; - }; - - ///This pauses a ressource tracker. This is mainly done when a building is about to be upgraded. - class PauseRessourceTracker : public ManagementOrder - { - public: - PauseRessourceTracker() : building_id(0) {} - PauseRessourceTracker(int building_id); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - int building_id; - }; - - ///This unpauses a ressource tracker. This should be done when a building is done being upgraded. - class UnPauseRessourceTracker : public ManagementOrder - { - public: - UnPauseRessourceTracker() : building_id(0) {} - UnPauseRessourceTracker(int building_id); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - int building_id; - }; - - ///This changes the radius of a flag. - class ChangeFlagSize : public ManagementOrder - { - public: - ChangeFlagSize() : size(0), building_id(0) {} - explicit ChangeFlagSize(int size, int building_id); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int size; - int building_id; - }; - - ///This changes the minimum_level required to attend a flag. Used mainly for War Flags, but this - ///can be used to control whether ground attack explorers come to a particular flag. To have only - ///ground attack explorers come, use level 4. Levels 2 and 3 can only be set by the map editor. - class ChangeFlagMinimumLevel : public ManagementOrder - { - public: - ChangeFlagMinimumLevel() : minimum_level(0), building_id(0) {} - explicit ChangeFlagMinimumLevel(int minimum_level, int building_id); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int minimum_level; - int building_id; - }; - - ///This changes a flags position - class ChangeFlagPosition : public ManagementOrder - { - public: - ChangeFlagPosition() : x(0), y(0), building_id(0) {} - explicit ChangeFlagPosition(int x, int y, int building_id); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int x; - int y; - int building_id; - }; - - ///This order adjusts the priority on a building - class AdjustPriority : public ManagementOrder - { - public: - enum BuildingPriority - { - Low, - Medium, - High, - }; - - AdjustPriority() : building_id(0) {} - AdjustPriority(int building_id, BuildingPriority priority); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int building_id; - BuildingPriority priority; - }; - - ///This management order adds a particular type of "area" to the ground. - ///The three types of areas are in the AreaType enum, and are passed to - ///the constructor. To have this change multiple areas, its nesseccary - ///to call the add_location function multiple times. - class AddArea : public ManagementOrder - { - public: - AddArea() {} - explicit AddArea(AreaType areatype); - void add_location(int x, int y); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - AreaType areatype; - std::vector locations; - }; - - ///This management order removes an area from the ground. Its exactly - ///the same as AddArea, with the exception that it removes areas, - ///instead of adding them. - class RemoveArea : public ManagementOrder - { - public: - RemoveArea() {} - explicit RemoveArea(AreaType areatype); - void add_location(int x, int y); - protected: - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - AreaType areatype; - std::vector locations; - }; - - ///This class allows you to adjust alliances with other teams. - class ChangeAlliances : public ManagementOrder - { - public: - ChangeAlliances() {} - ///You pass in a team number, that can be retrieved from enemy_team_iterator or a similar method. Then you pass in modifiers - ///on each of the possible alliances. If you pass in true, that alliance mode is set. If you pass in false, that alliance - ///mode is unset. If you pass in undeterminate, that alliance mode is not changed, keeping whatever value it had before. - ChangeAlliances(int team, boost::logic::tribool is_allied, boost::logic::tribool is_enemy, boost::logic::tribool view_market, boost::logic::tribool view_inn, boost::logic::tribool view_other); - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - protected: - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int team; - boost::logic::tribool is_allied; - boost::logic::tribool is_enemy; - boost::logic::tribool view_market; - boost::logic::tribool view_inn; - boost::logic::tribool view_other; - }; - - ///This order calls for a particular building to be upgraded or repaired with the provided number of workers. - class UpgradeRepair : public ManagementOrder - { - public: - UpgradeRepair(int id); - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - protected: - friend class ManagementOrder; - UpgradeRepair() {} - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - private: - int id; - }; - - #ifdef SendMessage - #undef SendMessage - #endif - - ///This sends a message to the AI's handle_message function. - class SendMessage : public ManagementOrder - { - public: - SendMessage(const std::string& message); - void modify(Echo& echo); - boost::logic::tribool wait(Echo& echo); - protected: - friend class ManagementOrder; - SendMessage() {} - ManagementOrderType get_type(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - std::string message; - }; - }; - - ///This namespace stores anything related to search and iterating through buildings or teams that satisfy particular conditions. - namespace SearchTools - { - ///This is a standards complying iterator that iterates over buildings that satisfy conditions. Can only be - ///obtained from a BuildingSearch object. - class building_search_iterator - { - public: - const unsigned int operator*(); - building_search_iterator& operator++(); - building_search_iterator operator++(int); - bool operator!=(const building_search_iterator& rhs) const; - - typedef std::forward_iterator_tag iterator_category; - typedef unsigned int value_type; - typedef size_t difference_type; - typedef unsigned int* pointer; - typedef unsigned int& reference; - private: - friend class AIEcho::SearchTools::BuildingSearch; - building_search_iterator(); - explicit building_search_iterator(BuildingSearch& search); - void set_to_next(); - int found_id; - Construction::BuildingRegister::found_iterator position; - bool is_end; - BuildingSearch* search; - }; - - ///This class holds all of the conditions for a search of buildings. Its much preferred to use this building search system - ///than to manually go over the buildings yourself, or record building ID's in your AI for future use. It has a begin() and - ///end() function like standard containers - class BuildingSearch - { - public: - explicit BuildingSearch(Echo& echo); - ///This adds a condition that the building has to pass in order to be examined. - void add_condition(Conditions::BuildingCondition* condition); - ///This counts up all the buildings that satisfy the conditions - int count_buildings(); - ///Returns the begininng iterator - building_search_iterator begin(); - ///Returns the one-past-the-end iterator - building_search_iterator end(); - private: - friend class AIEcho::SearchTools::building_search_iterator; - Echo& echo; - bool passes_conditions(int b); - std::vector > conditions; - }; - - ///This class is a standard iterator that is used to iterate over teams that qualify as "enemies". - ///It returns an integer corrosponding to the teams id. - class enemy_team_iterator - { - public: - explicit enemy_team_iterator(Echo& echo); - enemy_team_iterator(); - const unsigned int operator*(); - enemy_team_iterator& operator++(); - enemy_team_iterator operator++(int); - bool operator!=(const enemy_team_iterator& rhs) const; - - typedef std::forward_iterator_tag iterator_category; - typedef unsigned int value_type; - typedef size_t difference_type; - typedef unsigned int* pointer; - typedef unsigned int& reference; - private: - void set_to_next(); - int team_number; - bool is_end; - Echo* echo; - }; - - ///This function returns whether there is a flag at the given position, and if so, its GID, if not, NOGBID - int is_flag(Echo& echo, int x, int y); - - ///This is an iterator that is used to iterate over enemy buildings. You only get so much information - ///about enemy buildings, which is why you can't use the standard Conditions. It returns standard GBIDs, - ///which are different from the building ID's you get in other portions of the system. If a class or function - ///requires a GBID instead of a standard building id, it has to come from here. You also don't get information - ///on buildings you can't see, with one exception, you can get information about buildings you don't see, - ///as long as those buildings existed when the game started (this simulates a human looking at the map before - ///a game) - class enemy_building_iterator - { - public: - enemy_building_iterator(); - ///These are the three pieces of information you are provided with. If building_type or level are -1, - ///they are considered a wildcard, any building will match. If construction_site is indeterminate, - ///the same thing applies, its a wildcard, any building will match. - enemy_building_iterator(Echo& echo, int team, int building_type, int level, boost::logic::tribool construction_site); - - const unsigned int operator*(); - enemy_building_iterator& operator++(); - enemy_building_iterator operator++(int); - bool operator!=(const enemy_building_iterator& rhs) const; - - typedef std::forward_iterator_tag iterator_category; - typedef unsigned int value_type; - typedef size_t difference_type; - typedef unsigned int* pointer; - typedef unsigned int& reference; - - private: - void set_to_next(); - int current_gid; - int current_index; - int team; - int building_type; - int level; - boost::logic::tribool construction_site; - bool is_end; - Echo* echo; - }; - - - ///This class is used to get information about the map. - class MapInfo - { - public: - MapInfo(Echo& echo); - int get_width(); - int get_height(); - bool is_forbidden_area(int x, int y); - bool is_guard_area(int x, int y); - bool is_clearing_area(int x, int y); - bool is_discovered(int x, int y); - bool is_ressource(int x, int y, int type); - bool is_ressource(int x, int y); - bool is_water(int x, int y); - bool is_sand(int x, int y); - bool is_grass(int x, int y); - bool backs_onto_sand(int x, int y); - int get_ammount_ressource(int x, int y); - private: - Echo& echo; - }; - }; - - ///This is a base class for all EchoAI's - class EchoAI - { - public: - virtual ~EchoAI(){} - ///Your AI must implement the load function that loads all of its data from a stream - virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; - ///Your AI must implement a save function that saves all of its data to a stream - virtual void save(GAGCore::OutputStream *stream)=0; - ///This function is called every tick, about 25 times per second. This is where you put - ///all of you AI's logic - virtual void tick(Echo& echo)=0; - ///Handles a message sent from the AI to itself if certain conditions are satisfied. - virtual void handle_message(Echo& echo, const std::string& message)=0; - }; - - ///Reach To Infinity is a simple economic test AI for Echo. - class ReachToInfinity : public EchoAI - { - public: - ReachToInfinity(); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - void tick(Echo& echo); - void handle_message(Echo& echo, const std::string& message); - private: - int timer; - bool flag_on_cherry; - bool flag_on_orange; - bool flag_on_prune; - std::set flags_on_enemy; - }; - - ///This is the part that ties everything together. This bridges the interface between the game and the AI system. - ///This is where you send all of you're orders. - class Echo : public AIImplementation - { - public: - Echo(EchoAI* echoai, Player* player); - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - - boost::shared_ptr getOrder(void); - - unsigned int add_building_order(Construction::BuildingOrder* bo); - void add_management_order(Management::ManagementOrder* mo); - void add_ressource_tracker(Management::RessourceTracker* rt, int building_id); - boost::shared_ptr get_ressource_tracker(int building_id); - - TeamStat& get_team_stats(); - void flare(int x, int y); - Construction::BuildingRegister& get_building_register(); - Construction::FlagMap& get_flag_map(); - void push_order(boost::shared_ptr order); - Gradients::GradientManager& get_gradient_manager(); - std::set& get_starting_buildings(); - - bool is_fruit_on_map() { return is_fruit; } - - Player* player; - private: - - friend class AIEcho::Management::AddRessourceTracker; - friend class AIEcho::Management::PauseRessourceTracker; - friend class AIEcho::Management::UnPauseRessourceTracker; - friend class AIEcho::Management::ChangeAlliances; - friend class AIEcho::Management::SendMessage; - - - Uint32 allies; - Uint32 enemies; - Uint32 inn_view; - Uint32 market_view; - Uint32 other_view; - - void update_management_orders(); - void pause_ressource_tracker(int building_id); - void unpause_ressource_tracker(int building_id); - void init_starting_buildings(); - void update_ressource_trackers(); - void update_building_orders(); - void check_fruit(); - - std::list > orders; - boost::shared_ptr echoai; - boost::shared_ptr gm; - Construction::BuildingRegister br; - Construction::FlagMap fm; - std::vector > building_orders; - std::vector > management_orders; - std::map, bool> > ressource_trackers; - typedef std::map, bool> >::iterator tracker_iterator; - std::set starting_buildings; - int timer; - ///This to keep multiuple buildings from being constructed on the same tick. - ///Before the next building is constructed, the previous building must be - ///found on the BuildingRegister - int previous_building_id; - bool update_gm; - bool is_fruit; - - int from_load_timer; - }; - - const unsigned int INVALID_BUILDING=65535; - - void signature_write(GAGCore::OutputStream *stream); - void signature_check(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); -}; - - - -inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::Upgradable::get_type() -{ - return CUpgradable; -} - - - -inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::NotUnderConstruction::get_type() -{ - return CNotUnderConstruction; -} - - - -inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::UnderConstruction::get_type() -{ - return CUnderConstruction; -} - - - -inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::BeingUpgraded::get_type() -{ - return CBeingUpgraded; -} - - - -inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::BeingUpgradedTo::get_type() -{ - return CBeingUpgradedTo; -} - - - -inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::SpecificBuildingType::get_type() -{ - return CSpecificBuildingType; -} - - - -inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::NotSpecificBuildingType::get_type() -{ - return CNotSpecificBuildingType; -} - - - -inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::BuildingLevel::get_type() -{ - return CBuildingLevel; -} - - - -inline AIEcho::Conditions::ConditionType AIEcho::Conditions::EnemyBuildingDestroyed::get_type() -{ - return CEnemyBuildingDestroyed; -} - - - -inline bool AIEcho::Conditions::TicksPassed::passes(Echo& echo, int id) -{ - num--; if(num==0) return true; return false; -} - - - -inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::TicksPassed::get_type() -{ - return CTicksPassed; -} - - - -inline bool AIEcho::Conditions::TicksPassed::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) -{ - return false; -} - - - -inline void AIEcho::Conditions::TicksPassed::save(GAGCore::OutputStream *stream) -{ - -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::AssignWorkers::get_type() -{ - return MAssignWorkers; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::ChangeSwarm::get_type() -{ - return MChangeSwarm; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::DestroyBuilding::get_type() -{ - return MDestroyBuilding; -} - - -inline int AIEcho::Management::RessourceTracker::get_age() -{ - return timer; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::AddRessourceTracker::get_type() -{ - return MAddRessourceTracker; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::PauseRessourceTracker::get_type() -{ - return MPauseRessourceTracker; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::UnPauseRessourceTracker::get_type() -{ - return MUnPauseRessourceTracker; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::ChangeFlagSize::get_type() -{ - return MChangeFlagSize; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::ChangeFlagMinimumLevel::get_type() -{ - return MChangeFlagMinimumLevel; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::AddArea::get_type() -{ - return MAddArea; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::RemoveArea::get_type() -{ - return MRemoveArea; -} - - - -inline AIEcho::Management::ManagementOrderType AIEcho::Management::ChangeAlliances::get_type() -{ - return MChangeAlliances; -} - - - -inline TeamStat& AIEcho::Echo::get_team_stats() -{ - return *player->team->stats.getLatestStat(); -} - - - -inline void AIEcho::Echo::flare(int x, int y) -{ - orders.push_back(boost::shared_ptr(new MapMarkOrder(player->team->teamNumber, x, y))); -} - - - -inline AIEcho::Construction::BuildingRegister& AIEcho::Echo::get_building_register() -{ - return br; -} - - - -inline AIEcho::Construction::FlagMap& AIEcho::Echo::get_flag_map() -{ - return fm; -} - - - -inline void AIEcho::Echo::push_order(boost::shared_ptr order) -{ - orders.push_back(order); -} - - - -inline AIEcho::Gradients::GradientManager& AIEcho::Echo::get_gradient_manager() -{ - return *gm; -} - - - -inline std::set& AIEcho::Echo::get_starting_buildings() -{ - return starting_buildings; -} - - - - -#endif diff --git a/src/AINames.h b/src/AINames.h deleted file mode 100644 index dc32da7f0..000000000 --- a/src/AINames.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "AI.h" - -namespace AINames -{ - std::string getAIText(int id); - std::string getAIDescription(int id); -} diff --git a/src/AINicowar.cpp b/src/AINicowar.cpp deleted file mode 100644 index 931f7931d..000000000 --- a/src/AINicowar.cpp +++ /dev/null @@ -1,2756 +0,0 @@ -/* - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "AINicowar.h" -#include "GlobalContainer.h" -#include "FormatableString.h" -#include "boost/lexical_cast.hpp" -#include "Utilities.h" -#include "Game.h" -#include "Unit.h" - -using namespace AIEcho; -using namespace AIEcho::Gradients; -using namespace AIEcho::Construction; -using namespace AIEcho::Management; -using namespace AIEcho::Conditions; -using namespace AIEcho::SearchTools; -using namespace boost::logic; - - -NicowarStrategy::NicowarStrategy() -{ - -} - - - -void NicowarStrategy::loadFromConfigFile(const ConfigBlock *configBlock) -{ - configBlock->load(growth_phase_unit_max, "growth_phase_unit_max"); - configBlock->load(skilled_work_phase_unit_min, "skilled_work_phase_unit_min"); - configBlock->load(upgrading_phase_1_school_min, "upgrading_phase_1_school_min"); - configBlock->load(upgrading_phase_1_unit_min, "upgrading_phase_1_unit_min"); - configBlock->load(upgrading_phase_1_trained_worker_min, "upgrading_phase_1_trained_worker_min"); - configBlock->load(upgrading_phase_2_school_min, "upgrading_phase_2_school_min"); - configBlock->load(upgrading_phase_2_unit_min, "upgrading_phase_2_unit_min"); - configBlock->load(upgrading_phase_2_trained_worker_min, "upgrading_phase_2_trained_worker_min"); - configBlock->load(minimum_warrior_level_for_trained, "minimum_warrior_level_for_trained"); - configBlock->load(war_preperation_phase_unit_min, "war_preperation_phase_unit_min"); - configBlock->load(war_preperation_phase_barracks_max, "war_preperation_phase_barracks_max"); - configBlock->load(war_preperation_phase_trained_warrior_max, "war_preperation_phase_trained_warrior_max"); - configBlock->load(war_phase_trained_warrior_min, "war_phase_trained_warrior_min"); - configBlock->load(fruit_phase_unit_min, "fruit_phase_unit_min"); - configBlock->load(starvation_recovery_phase_starving_no_inn_min_percent, "starvation_recovery_phase_starving_no_inn_min_percent"); - configBlock->load(starving_recovery_phase_unfed_per_new_inn, "starving_recovery_phase_unfed_per_new_inn"); - configBlock->load(no_workers_phase_free_worker_minimum_percent, "no_workers_phase_free_worker_minimum_percent"); - configBlock->load(level_1_inn_units_can_feed, "level_1_inn_units_can_feed"); - configBlock->load(level_2_inn_units_can_feed, "level_2_inn_units_can_feed"); - configBlock->load(level_3_inn_units_can_feed, "level_3_inn_units_can_feed"); - configBlock->load(growth_phase_units_per_swarm, "growth_phase_units_per_swarm"); - configBlock->load(non_growth_phase_units_per_swarm, "non_growth_phase_units_per_swarm"); - configBlock->load(growth_phase_maximum_swarms, "growth_phase_maximum_swarms"); - configBlock->load(skilled_work_phase_number_of_racetracks, "skilled_work_phase_number_of_racetracks"); - configBlock->load(skilled_work_phase_number_of_swimmingpools, "skilled_work_phase_number_of_swimmingpools"); - configBlock->load(skilled_work_phase_number_of_schools, "skilled_work_phase_number_of_schools"); - configBlock->load(war_preparation_phase_number_of_barracks, "war_preparation_phase_number_of_barracks"); - configBlock->load(base_number_of_hospitals, "base_number_of_hospitals"); - configBlock->load(war_preperation_phase_warriors_per_hospital, "war_preperation_phase_warriors_per_hospital"); - configBlock->load(base_number_of_construction_sites, "base_number_of_construction_sites"); - configBlock->load(starving_recovery_phase_number_of_extra_construction_sites, "starving_recovery_phase_number_of_extra_construction_sites"); - configBlock->load(level_1_inn_low_wheat_trigger_ammount, "level_1_inn_low_wheat_trigger_ammount"); - configBlock->load(level_2_inn_low_wheat_trigger_ammount, "level_2_inn_low_wheat_trigger_ammount"); - configBlock->load(level_3_inn_low_wheat_trigger_ammount, "level_3_inn_low_wheat_trigger_ammount"); - configBlock->load(level_1_inn_units_assigned_normal_wheat, "level_1_inn_units_assigned_normal_wheat"); - configBlock->load(level_2_inn_units_assigned_normal_wheat, "level_2_inn_units_assigned_normal_wheat"); - configBlock->load(level_3_inn_units_assigned_normal_wheat, "level_3_inn_units_assigned_normal_wheat"); - configBlock->load(level_1_inn_units_assigned_low_wheat, "level_1_inn_units_assigned_low_wheat"); - configBlock->load(level_2_inn_units_assigned_low_wheat, "level_2_inn_units_assigned_low_wheat"); - configBlock->load(level_3_inn_units_assigned_low_wheat, "level_3_inn_units_assigned_low_wheat"); - configBlock->load(base_swarm_units_assigned, "base_swarm_units_assigned"); - configBlock->load(base_swarm_low_wheat_trigger_ammount, "base_swarm_low_wheat_trigger_ammount"); - configBlock->load(base_swarm_hungry_reduce_trigger_percent, "base_swarm_hungry_reduce_trigger_percent"); - configBlock->load(growth_phase_swarm_worker_ratio, "growth_phase_swarm_worker_ratio"); - configBlock->load(non_growth_phase_swarm_worker_ratio, "non_growth_phase_swarm_worker_ratio"); - configBlock->load(base_number_of_explorers, "base_number_of_explorers"); - configBlock->load(fruit_phase_extra_number_of_explorers, "fruit_phase_extra_number_of_explorers"); - configBlock->load(base_swarm_explorer_ratio, "base_swarm_explorer_ratio"); - configBlock->load(war_preperation_swarm_warrior_ratio, "war_preperation_swarm_warrior_ratio"); - configBlock->load(defense_explorer_population_percent, "defense_explorer_population_percent"); - configBlock->load(offense_explorer_number, "offense_explorer_number"); - configBlock->load(offense_explorer_minimum, "offense_explorer_minimum"); - configBlock->load(offense_explorer_flag_number, "offense_explorer_flag_number"); - configBlock->load(offense_explorer_flag_assigned, "offense_explorer_flag_assigned"); - configBlock->load(upgrading_phase_1_inn_chance, "upgrading_phase_1_inn_chance"); - configBlock->load(upgrading_phase_1_hospital_chance, "upgrading_phase_1_hospital_chance"); - configBlock->load(upgrading_phase_1_racetrack_chance, "upgrading_phase_1_racetrack_chance"); - configBlock->load(upgrading_phase_1_swimmingpool_chance, "upgrading_phase_1_swimmingpool_chance"); - configBlock->load(upgrading_phase_1_barracks_chance, "upgrading_phase_1_barracks_chance"); - configBlock->load(upgrading_phase_1_school_chance, "upgrading_phase_1_school_chance"); - configBlock->load(upgrading_phase_1_tower_chance, "upgrading_phase_1_tower_chance"); - configBlock->load(upgrading_phase_2_inn_chance, "upgrading_phase_2_inn_chance"); - configBlock->load(upgrading_phase_2_hospital_chance, "upgrading_phase_2_hospital_chance"); - configBlock->load(upgrading_phase_2_racetrack_chance, "upgrading_phase_2_racetrack_chance"); - configBlock->load(upgrading_phase_2_swimmingpool_chance, "upgrading_phase_2_swimmingpool_chance"); - configBlock->load(upgrading_phase_2_barracks_chance, "upgrading_phase_2_barracks_chance"); - configBlock->load(upgrading_phase_2_school_chance, "upgrading_phase_2_school_chance"); - configBlock->load(upgrading_phase_2_tower_chance, "upgrading_phase_2_tower_chance"); - configBlock->load(upgrading_phase_1_units_assigned, "upgrading_phase_1_units_assigned"); - configBlock->load(upgrading_phase_2_units_assigned, "upgrading_phase_2_units_assigned"); - configBlock->load(upgrading_phase_1_num_units, "upgrading_phase_1_num_units"); - configBlock->load(upgrading_phase_2_num_units, "upgrading_phase_2_num_units"); - configBlock->load(war_phase_war_flag_units_assigned, "war_phase_war_flag_units_assigned"); - configBlock->load(war_phase_num_attack_flags, "war_phase_num_attack_flags"); - -} - - -std::string NicowarStrategy::getStrategyName() -{ - return name; -} - - -void NicowarStrategy::setStrategyName(const std::string& name) -{ - this->name=name; -} - - -NicowarStrategyLoader::NicowarStrategyLoader() -{ - ConfigVector::load("data/nicowar.default.txt", true); - ConfigVector::load("data/nicowar.txt"); -} - - - -NicowarStrategy NicowarStrategyLoader::chooseRandomStrategy() -{ - int chosen = syncRand() % entries.size(); - entries[chosen]->setStrategyName(entriesToName[chosen]); - return *entries[chosen]; -} - - - -NicowarStrategy NicowarStrategyLoader::getParticularStrategy(const std::string& name) -{ - entries[nameToEntries[name]]->setStrategyName(name); - return *entries[nameToEntries[name]]; -} - - - -NewNicowar::NewNicowar() -{ - timer=0; - buildings_under_construction=0; - growth_phase=false; - skilled_work_phase=0; - upgrading_phase_1=false; - upgrading_phase_2=false; - war_preperation=false; - war=false; - fruit_phase=false; - starving_recovery=false; - no_workers_phase=false; - can_swim=false; - defend_explorers=false; - explorer_attack_preperation_phase=false; - explorer_attack_phase=false; - starving_recovery_inns = 0; - exploration_on_fruit=false; - for(int n=0; nreadEnterSection("NewNicowar"); - timer=stream->readUint32("timer"); - if(versionMinor >= 59) - { - if(versionMinor >= 60) - { - std::string strategyName = stream->readText("strategy_name"); - NicowarStrategyLoader loader; - strategy = loader.getParticularStrategy(strategyName); - } - else - { - NicowarStrategyLoader loader; - strategy = loader.getParticularStrategy("default"); - } - growth_phase=stream->readUint8("growth_phase"); - skilled_work_phase=stream->readUint8("skilled_work_phase"); - upgrading_phase_1=stream->readUint8("upgrading_phase_1"); - upgrading_phase_2=stream->readUint8("upgrading_phase_2"); - war_preperation=stream->readUint8("war_preperation"); - war=stream->readUint8("war"); - fruit_phase=stream->readUint8("fruit_phase"); - starving_recovery=stream->readUint8("starving_recovery"); - no_workers_phase=stream->readUint8("no_workers_phase"); - if(versionMinor >= 60) - can_swim=stream->readUint8("can_swim"); - - starving_recovery_inns=stream->readUint8("starving_recovery_inns"); - buildings_under_construction=stream->readUint32("buildings_under_construction"); - for(int n=0; nreadUint8(FormatableString("buildings_under_construction_per_type[%0]").arg(n).c_str()); - } - - stream->readEnterSection("placement_queue"); - size_t size = stream->readUint16("size"); - for(size_t n = 0; nreadEnterSection(n); - BuildingPlacement bp = static_cast(stream->readUint8("placement")); - placement_queue.push_back(bp); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->readEnterSection("construction_queue"); - size = stream->readUint16("size"); - for(size_t n = 0; nreadEnterSection(n); - BuildingPlacement bp = static_cast(stream->readUint8("placement")); - construction_queue.push_back(bp); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - target = stream->readSint8("target"); - is_digging_out = stream->readUint8("is_digging_out"); - - stream->readEnterSection("attack_flags"); - size = stream->readUint16("size"); - for(size_t n = 0; nreadEnterSection(n); - int flag = stream->readUint32("flag"); - attack_flags.push_back(flag); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - if(versionMinor >= 66) - { - stream->readEnterSection("defense_flags"); - size = stream->readUint16("size"); - for(size_t n = 0; nreadEnterSection(n); - int flag = stream->readUint32("flag"); - defense_flags.push_back(flag); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->readEnterSection("explorer_attack_flags"); - size = stream->readUint16("size"); - for(size_t n = 0; nreadEnterSection(n); - int flag = stream->readUint32("flag"); - explorer_attack_flags.push_back(flag); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - } - - exploration_on_fruit=stream->readUint8("exploration_on_fruit"); - stream->readLeaveSection(); - } - return true; -} - - -void NewNicowar::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("NewNicowar"); - stream->writeUint32(timer, "timer"); - stream->writeText(strategy.getStrategyName(), "strategy_name"); - stream->writeUint8(growth_phase, "growth_phase"); - stream->writeUint8(skilled_work_phase, "skilled_work_phase"); - stream->writeUint8(upgrading_phase_1, "upgrading_phase_1"); - stream->writeUint8(upgrading_phase_2, "upgrading_phase_2"); - stream->writeUint8(war_preperation, "war_preperation"); - stream->writeUint8(war, "war"); - stream->writeUint8(fruit_phase, "fruit_phase"); - stream->writeUint8(starving_recovery, "starving_recovery"); - stream->writeUint8(no_workers_phase, "no_workers_phase"); - stream->writeUint8(can_swim, "can_swim"); - stream->writeUint8(starving_recovery_inns, "starving_recovery_inns"); - stream->writeUint32(buildings_under_construction, "buildings_under_construction"); - for(int n=0; nwriteUint8(buildings_under_construction_per_type[n], FormatableString("buildings_under_construction_per_type[%0]").arg(n).c_str()); - } - - stream->writeEnterSection("placement_queue"); - stream->writeUint16(placement_queue.size(), "size"); - size_t n = 0; - for(std::list::iterator i = placement_queue.begin(); i!=placement_queue.end(); ++i) - { - stream->writeEnterSection(n); - stream->writeUint8(static_cast(*i), "placement"); - stream->writeLeaveSection(); - n+=1; - } - stream->writeLeaveSection(); - - stream->writeEnterSection("construction_queue"); - stream->writeUint16(construction_queue.size(), "size"); - n = 0; - for(std::list::iterator i = construction_queue.begin(); i!=construction_queue.end(); ++i) - { - stream->writeEnterSection(n); - stream->writeUint8(static_cast(*i), "placement"); - stream->writeLeaveSection(); - n+=1; - } - stream->writeLeaveSection(); - - stream->writeUint8(target, "target"); - stream->writeUint8(is_digging_out, "is_digging_out"); - - stream->writeEnterSection("attack_flags"); - stream->writeUint16(attack_flags.size(), "size"); - for(n = 0; nwriteEnterSection(n); - stream->writeUint32(attack_flags[n], "flag"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->writeEnterSection("defense_flags"); - stream->writeUint16(defense_flags.size(), "size"); - for(n = 0; nwriteEnterSection(n); - stream->writeUint32(defense_flags[n], "flag"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->writeEnterSection("explorer_attack_flags"); - stream->writeUint16(explorer_attack_flags.size(), "size"); - for(n = 0; nwriteEnterSection(n); - stream->writeUint32(explorer_attack_flags[n], "flag"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->writeUint8(exploration_on_fruit, "exploration_on_fruit"); - stream->writeLeaveSection(); -} - - -void NewNicowar::tick(Echo& echo) -{ - timer++; - if(timer==1) - { - selectStrategy(); - check_phases(echo); - initialize(echo); - } - if(timer%100 == 0) - { - queue_buildings(echo); - } - if(timer%100 == 17) - { - check_phases(echo); - } - if(timer%100 == 33) - { - manage_buildings(echo); - } - if(timer%100 == 50) - { - upgrade_buildings(echo); - } - if(timer%100 == 67) - { - control_attacks(echo); - } - if(timer%100 == 84) - { - compute_defense_flag_positioning(echo); - } - if(timer%250 == 0) - { - update_farming(echo); - } - if(timer%250 == 85) - { - update_fruit_flags(echo); - } - if(timer%1000 == 570) - { - compute_explorer_flag_attack_positioning(echo); - } - - order_buildings(echo); -} - - -void NewNicowar::handle_message(Echo& echo, const std::string& message) -{ - if(message.substr(0,19) == "building completed ") - { - int placement_num=boost::lexical_cast(message.substr(19, message.size()-1)); - buildings_under_construction-=1; - buildings_under_construction_per_type[placement_num]-=1; - } - if(message.substr(0,22) == "update clearing zone1 ") - { - MapInfo mi(echo); - int id=boost::lexical_cast(message.substr(22, message.size()-1)); - Building* b = echo.get_building_register().get_building(id); - AddArea* mo_clearing=new AddArea(ClearingArea); - RemoveArea* mo_remove_clearing=new RemoveArea(ClearingArea); - mo_remove_clearing->add_condition(new BuildingDestroyed(id)); - for(int nx=-1; nxtype->width+1; ++nx) - { - for(int ny=-1; nytype->height+1; ++ny) - { - if(!mi.is_forbidden_area(b->posX+nx, b->posY+ny)) - { - mo_clearing->add_location(b->posX+nx, b->posY+ny); - mo_remove_clearing->add_location(b->posX+nx, b->posY+ny); - } - } - } - echo.add_management_order(mo_clearing); - echo.add_management_order(mo_remove_clearing); - } - if(message.substr(0,22) == "update clearing zone2 ") - { - MapInfo mi(echo); - int id=boost::lexical_cast(message.substr(22, message.size()-1)); - Building* b = echo.get_building_register().get_building(id); - AddArea* mo_clearing=new AddArea(ClearingArea); - RemoveArea* mo_remove_clearing=new RemoveArea(ClearingArea); - mo_remove_clearing->add_condition(new BuildingDestroyed(id)); - for(int nx=-1; nxtype->width+1; ++nx) - { - for(int ny=-1; nytype->height+1; ++ny) - { - mo_clearing->add_location(b->posX+nx, b->posY+ny); - mo_remove_clearing->add_location(b->posX+nx, b->posY+ny); - } - } - echo.add_management_order(mo_clearing); - echo.add_management_order(mo_remove_clearing); - } - if(message.substr(0,13) == "update swarm ") - { - int id=boost::lexical_cast(message.substr(13, message.size()-1)); - manage_swarm(echo, id); - } - if(message.substr(0,11) == "update inn ") - { - int id=boost::lexical_cast(message.substr(11, message.size()-1)); - manage_inn(echo, id); - } - if(message.substr(0,16) == "attack finished ") - { - int id=boost::lexical_cast(message.substr(16, message.size()-1)); - attack_flags.erase(std::find(attack_flags.begin(), attack_flags.end(), id)); - } - if(message.substr(0,19) == "guard flag deleted ") - { - int id=boost::lexical_cast(message.substr(19, message.size()-1)); - defense_flags.erase(std::find(defense_flags.begin(), defense_flags.end(), id)); - } - if(message.substr(0,29) == "explorer attack flag deleted ") - { - int id=boost::lexical_cast(message.substr(29, message.size()-1)); - explorer_attack_flags.erase(std::find(explorer_attack_flags.begin(), explorer_attack_flags.end(), id)); - } - if(message == "finished digging out") - { - is_digging_out=false; - } - if(message == "finished starving recovery inn") - { - starving_recovery_inns-=1; - } -} - - - -void NewNicowar::selectStrategy() -{ - NicowarStrategyLoader loader; - strategy = loader.chooseRandomStrategy(); - //strategy = loader.getParticularStrategy("default"); -} - - - -void NewNicowar::initialize(Echo& echo) -{ - BuildingSearch bs(echo); - for(building_search_iterator i = bs.begin(); i!=bs.end(); ++i) - { - if(echo.get_building_register().get_type(*i)==IntBuildingType::SWARM_BUILDING) - { - ManagementOrder* mo_tracker=new AddRessourceTracker(25, CORN, *i); - mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, *i)); - echo.add_management_order(mo_tracker); - } - if(echo.get_building_register().get_type(*i)==IntBuildingType::FOOD_BUILDING) - { - ManagementOrder* mo_tracker=new AddRessourceTracker(25, CORN, *i); - mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, *i)); - echo.add_management_order(mo_tracker); - } - } - - manage_buildings(echo); -} - - -void NewNicowar::check_phases(Echo& echo) -{ - TeamStat* stat=echo.player->team->stats.getLatestStat(); - - ///Qualifications for the growth phase: - ///1) Less than strategy.growth_phase_unit_max units - if(stat->totalUnittotalUnit>=strategy.skilled_work_phase_unit_min) - { - skilled_work_phase=true; - } - else - { - skilled_work_phase=false; - } - - ///Qualifications for the upgrading phase 1: - ///1) Atleast strategy.upgrading_phase_1_school_min schools - ///2) Atleast strategy.upgrading_phase_1_unit_min units - ///3) Atleast strategy.upgrading_phase_1_trained_worker_min of them are trained for upgrading to level 2 - BuildingSearch schools(echo); - schools.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - schools.add_condition(new NotUnderConstruction); - const int school_counts=schools.count_buildings(); - const int trained_count=stat->upgradeState[BUILD][1] + stat->upgradeState[BUILD][2] + stat->upgradeState[BUILD][3]; - - if(stat->totalUnit>=strategy.upgrading_phase_1_unit_min && school_counts>=strategy.upgrading_phase_1_school_min && trained_count>strategy.upgrading_phase_1_trained_worker_min) - { - upgrading_phase_1=true; - } - else - { - upgrading_phase_1=false; - } - - ///Qualifications for the upgrading phase 2: - ///1) Atleast strategy.upgrading_phase_2_school_min level 2 or level 3 schools - ///2) Atleast strategy.upgrading_phase_2_unit_min units - ///3) Atleast strategy.upgrading_phase_2_trained_worker_min of them are trained for upgrading to level 3 - BuildingSearch schools_2(echo); - schools_2.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - schools_2.add_condition(new NotUnderConstruction); - schools_2.add_condition(new BuildingLevel(2)); - BuildingSearch schools_3(echo); - schools_3.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - schools_3.add_condition(new NotUnderConstruction); - schools_3.add_condition(new BuildingLevel(3)); - const int school_counts_2=schools_2.count_buildings() + schools_3.count_buildings(); - const int trained_count_2=echo.get_team_stats().upgradeState[BUILD][2] + stat->upgradeState[BUILD][3]; - - if(stat->totalUnit>=strategy.upgrading_phase_2_unit_min && school_counts_2>=strategy.upgrading_phase_2_school_min && trained_count_2>strategy.upgrading_phase_2_trained_worker_min) - { - upgrading_phase_2=true; - } - else - { - upgrading_phase_2=false; - } - - ///Qualifications for the war preperation phase: - ///1) Atleast strategy.war_preperation_phase_unit_min units - ///2) Less than strategy.war_preperation_phase_barracks_max barracks OR - ///3) Less than strategy.war_preperation_phase_trained_warrior_max trained warriors - BuildingSearch barracks(echo); - barracks.add_condition(new SpecificBuildingType(IntBuildingType::ATTACK_BUILDING)); - int barracks_count=barracks.count_buildings(); - - int warrior_count=0; - for(int i=strategy.minimum_warrior_level_for_trained; i<=3; ++i) - { - warrior_count += stat->upgradeState[ATTACK_SPEED][i]; - } - - if(stat->totalUnit>=strategy.war_preperation_phase_unit_min && (warrior_count < strategy.war_preperation_phase_trained_warrior_max || barracks_count= strategy.war_phase_trained_warrior_min) - { - war=true; - } - else - { - war=false; - } - - ///Qualifcations for the fruit phase: - ///Atleast strategy.fruit_phase_unit_min units, and fruits on the map - if(echo.is_fruit_on_map() && stat->totalUnit >= strategy.fruit_phase_unit_min) - { - fruit_phase=true; - } - else - { - fruit_phase=false; - } - - ///Qualifications for the starving recovery phase: - ///1) More than strategy.starvation_recovery_phase_starving_no_inn_min_percent % units hungry but not able to eat - ///2) Atleast one unit (because of devision by 0) - if(stat->totalUnit > 1) - { - int total_starving_percent = stat->needFoodNoInns * 100 / stat->totalUnit; - if(total_starving_percent >= strategy.starvation_recovery_phase_starving_no_inn_min_percent) - { - starving_recovery=true; - } - else - { - starving_recovery=false; - } - } - else - { - starving_recovery=false; - } - - ///Qualifications for the no worker phase: - ///1) More than strategy.no_workers_phase_free_worker_minimum_percen % workers free - ///2) No needed jobs - ///3) Atleast one worker (because of devision by 0) - if(stat->numberUnitPerType[WORKER] > 0) - { - const int workers_free = (stat->isFree[WORKER] - stat->totalNeeded) * 100 / stat->numberUnitPerType[WORKER]; - if(workers_free > strategy.no_workers_phase_free_worker_minimum_percent) - { - no_workers_phase=true; - } - else - { - no_workers_phase=false; - } - } - else - { - no_workers_phase=false; - } - - ///Qualifications for the can swim phase: - ///1) Atleast one worker that can swim - int total_can_swim=0; - for(int i=0; i<4; ++i) - total_can_swim += stat->upgradeStatePerType[WORKER][SWIM][i]; - if(total_can_swim>0) - { - can_swim=true; - } - else - { - can_swim=false; - } - - ///Qualifications for the defend explorers phase - ///1) Prestige, not counting this teams prestige, is more than 0, indicating that ground attacking explorers are being created - if(echo.player->game->totalPrestige - echo.player->team->prestige > 0) - { - defend_explorers=true; - } - else - { - defend_explorers=false; - } - - ///Qualifications for the explorer attack preperation phase - //1) This teams prestige greater than 0 - if(echo.player->team->prestige > 0) - { - explorer_attack_preperation_phase = true; - } - else - { - explorer_attack_preperation_phase = false; - } - - ///Qualifications for the explorer attack phase - //1) The minimum number of trained explorers is greater than offense_explorer_minimum - if(stat->upgradeStatePerType[EXPLORER][MAGIC_ATTACK_GROUND][3] > strategy.offense_explorer_minimum) - { - explorer_attack_phase = true; - } - else - { - explorer_attack_phase = false; - } -} - - -void NewNicowar::queue_buildings(Echo& echo) -{ - queue_racetracks(echo); - queue_swimmingpools(echo); - queue_schools(echo); - queue_barracks(echo); - queue_hospitals(echo); - queue_inns(echo); - queue_swarms(echo); -} - - -void NewNicowar::queue_inns(Echo& echo) -{ - //Get some statistics - TeamStat* stat=echo.player->team->stats.getLatestStat(); - int total_workers=stat->numberUnitPerType[WORKER]; - int total_explorers=stat->numberUnitPerType[EXPLORER]; - int total_warriors=stat->numberUnitPerType[WARRIOR]; - - //Count the number of inns there are at each level - BuildingSearch bs_level1(echo); - bs_level1.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); - bs_level1.add_condition(new BuildingLevel(1)); - bs_level1.add_condition(new NotUnderConstruction); - const int number1=bs_level1.count_buildings() + buildings_under_construction_per_type[RegularInn]; - - BuildingSearch bs_level2(echo); - bs_level2.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); - bs_level2.add_condition(new BuildingLevel(2)); - const int number2=bs_level2.count_buildings(); - - BuildingSearch bs_level3(echo); - bs_level3.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); - bs_level3.add_condition(new BuildingLevel(3)); - const int number3=bs_level3.count_buildings(); - - const int score = - number1*strategy.level_1_inn_units_can_feed - + number2*strategy.level_2_inn_units_can_feed - + number3*strategy.level_3_inn_units_can_feed; - - ///(by default), A level 1 Inn can handle 8 units, a level 2 can handle 12 and a level 3 can handle 16 - if((total_workers+total_explorers+total_warriors)>=score) - { - placement_queue.push_back(RegularInn); - } - - //Place for starving recovery inns - if(starving_recovery) - { - int total_starving = stat->needFoodNoInns; - int required_inns = total_starving / strategy.starving_recovery_phase_unfed_per_new_inn; - if(starving_recovery_inns < required_inns) - { - starving_recovery_inns += 1; - placement_queue.push_back(StarvingRecoveryInn); - } - } -} - - -void NewNicowar::queue_swarms(Echo& echo) -{ - BuildingSearch bs(echo); - bs.add_condition(new SpecificBuildingType(IntBuildingType::SWARM_BUILDING)); - bs.add_condition(new NotUnderConstruction); - const int swarm_count = bs.count_buildings() + buildings_under_construction_per_type[RegularSwarm]; - const int total_unit = echo.player->team->stats.getLatestStat()->totalUnit; - int demand=0; - if(growth_phase) - { - demand = std::min(strategy.growth_phase_maximum_swarms, 1 + total_unit/strategy.growth_phase_units_per_swarm); - } - else - { - demand = (total_unit/strategy.non_growth_phase_units_per_swarm); - } - - if(demand > swarm_count) - { - placement_queue.push_back(RegularSwarm); - } -} - - -void NewNicowar::queue_racetracks(Echo& echo) -{ - BuildingSearch bs_finished(echo); - bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::WALKSPEED_BUILDING)); - bs_finished.add_condition(new NotUnderConstruction); - - BuildingSearch bs_upgrading(echo); - bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::WALKSPEED_BUILDING)); - bs_upgrading.add_condition(new BeingUpgraded); - - const int racetrack_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularRacetrack]; - //const int total_unit = echo.player->team->stats.getLatestStat()->totalUnit; - int demand=0; - if(skilled_work_phase) - { - demand=strategy.skilled_work_phase_number_of_racetracks; - } - - if(demand > racetrack_count) - { - placement_queue.push_back(RegularRacetrack); - } -} - - -void NewNicowar::queue_swimmingpools(Echo& echo) -{ - BuildingSearch bs_finished(echo); - bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::SWIMSPEED_BUILDING)); - bs_finished.add_condition(new NotUnderConstruction); - - BuildingSearch bs_upgrading(echo); - bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::SWIMSPEED_BUILDING)); - bs_upgrading.add_condition(new BeingUpgraded); - - const int swimmingpool_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularSwimmingpool]; - //const int total_unit = echo.player->team->stats.getLatestStat()->totalUnit; - int demand=0; - if(skilled_work_phase) - { - demand=strategy.skilled_work_phase_number_of_swimmingpools; - } - - if(demand > swimmingpool_count) - { - placement_queue.push_back(RegularSwimmingpool); - } -} - - -void NewNicowar::queue_schools(Echo& echo) -{ - BuildingSearch bs_finished(echo); - bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - bs_finished.add_condition(new NotUnderConstruction); - - BuildingSearch bs_upgrading(echo); - bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - bs_upgrading.add_condition(new BeingUpgraded); - - const int school_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularSchool]; - //const int total_unit = echo.player->team->stats.getLatestStat()->totalUnit; - int demand=0; - if(skilled_work_phase) - { - demand=strategy.skilled_work_phase_number_of_schools; - } - - if(demand > school_count) - { - placement_queue.push_back(RegularSchool); - } -} - - -void NewNicowar::queue_barracks(Echo& echo) -{ - BuildingSearch bs_finished(echo); - bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::ATTACK_BUILDING)); - bs_finished.add_condition(new NotUnderConstruction); - - BuildingSearch bs_upgrading(echo); - bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::ATTACK_BUILDING)); - bs_upgrading.add_condition(new BeingUpgraded); - - const int barracks_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularBarracks]; - - int demand=0; - if(war_preperation) - { - demand=strategy.war_preparation_phase_number_of_barracks; - ///This only kicks in right at the start, so that it doesn't build barracks when it doesn't need to - demand = std::min(demand, echo.player->team->stats.getLatestStat()->isFree[WARRIOR] / 2); - } - - if(demand > barracks_count) - { - placement_queue.push_back(RegularBarracks); - } -} - - -void NewNicowar::queue_hospitals(Echo& echo) -{ - BuildingSearch bs_finished(echo); - bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::HEAL_BUILDING)); - bs_finished.add_condition(new NotUnderConstruction); - - BuildingSearch bs_upgrading(echo); - bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::HEAL_BUILDING)); - bs_upgrading.add_condition(new BeingUpgraded); - - const int hospital_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularHospital]; - const int total_warrior = echo.player->team->stats.getLatestStat()->numberUnitPerType[WARRIOR]; - - int demand=0; - if(echo.player->team->stats.getLatestStat()->needHeal > 0) - demand += strategy.base_number_of_hospitals; - if(war_preperation || war) - { - demand+=total_warrior/strategy.war_preperation_phase_warriors_per_hospital; - } - - if(demand > hospital_count) - { - placement_queue.push_back(RegularHospital); - } -} - - - -void NewNicowar::order_buildings(Echo& echo) -{ - while(!placement_queue.empty()) - { - BuildingPlacement b=placement_queue.front(); - placement_queue.erase(placement_queue.begin()); - construction_queue.push_back(b); - buildings_under_construction_per_type[int(b)]+=1; - } - ///Increase the maximum number of buildings under construction when starving recovery is active - int maximum_under_construction = strategy.base_number_of_construction_sites; - if(starving_recovery) - maximum_under_construction += strategy.starving_recovery_phase_number_of_extra_construction_sites; - - while(!construction_queue.empty() && buildings_under_construction < maximum_under_construction) - { - int id=-1; - BuildingPlacement b=construction_queue.front(); - construction_queue.erase(construction_queue.begin()); - if(b==RegularInn) - { - id=order_regular_inn(echo); - } - if(b==StarvingRecoveryInn) - { - id=order_regular_inn(echo); - ManagementOrder* mo_completion_message=new SendMessage("finished starving recovery inn"); - mo_completion_message->add_condition(new EitherCondition( - new ParticularBuilding(new NotUnderConstruction, id), - new BuildingDestroyed(id))); - echo.add_management_order(mo_completion_message); - } - if(b==RegularSwarm) - { - id=order_regular_swarm(echo); - } - if(b==RegularRacetrack) - { - id=order_regular_racetrack(echo); - } - if(b==RegularSwimmingpool) - { - id=order_regular_swimmingpool(echo); - } - if(b==RegularSchool) - { - id=order_regular_school(echo); - } - if(b==RegularBarracks) - { - id=order_regular_barracks(echo); - } - if(b==RegularHospital) - { - id=order_regular_hospital(echo); - } - - ///This code keeps track of the number of buildings that are under construction at any one point - buildings_under_construction+=1; - ManagementOrder* mo_completion_message=new SendMessage("building completed "+boost::lexical_cast(int(b))); - mo_completion_message->add_condition(new EitherCondition( - new ParticularBuilding(new NotUnderConstruction, id), - new BuildingDestroyed(id))); - echo.add_management_order(mo_completion_message); - if(b == RegularInn || b==RegularSwarm) - { - ManagementOrder* mo_construction_completion_message=new SendMessage("update clearing zone1 "+boost::lexical_cast(int(id))); - mo_construction_completion_message->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_construction_completion_message); - } - else - { - ManagementOrder* mo_construction_completion_message=new SendMessage("update clearing zone2 "+boost::lexical_cast(int(id))); - mo_construction_completion_message->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_construction_completion_message); - } - } -} - - -int NewNicowar::order_regular_inn(Echo& echo) -{ - //The main order for the inn - BuildingOrder* bo = new BuildingOrder(IntBuildingType::FOOD_BUILDING, 2); - - //Constraints arround the location of wheat - AIEcho::Gradients::GradientInfo gi_wheat; - gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); - //You want to be close to wheat - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, 8)); - //You can't be farther than 10 units from wheat - bo->add_constraint(new AIEcho::Construction::MaximumDistance(gi_wheat, 10)); - - //Constraints about the distance to water. - AIEcho::Gradients::GradientInfo gi_water; - gi_water.add_source(new AIEcho::Gradients::Entities::Water); - //You dont want to be too close to water, so that farm can develop between it and water - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, 6)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 4)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 4)); - - ///Add constraints for all enemy teams to keep distance - AIEcho::Gradients::GradientInfo gi_enemy; - for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) - { - gi_enemy.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(*i, false)); - } - bo->add_constraint(new AIEcho::Construction::MaximizedDistance(gi_enemy, 1)); - - if(echo.is_fruit_on_map()) - { - //Constraints arround the location of fruit - AIEcho::Gradients::GradientInfo gi_fruit; - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); - gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); - //You want to be reasnobly close to fruit, closer if possible - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_fruit, 1)); - } - - //Add the building order to the list of orders - unsigned int id=echo.add_building_order(bo); - - //Change the number of workers assigned when the building is finished - ManagementOrder* mo_completion=new SendMessage(FormatableString("update inn %0").arg(id)); - mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_completion); - - ManagementOrder* mo_tracker=new AddRessourceTracker(25, CORN, id); - mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_tracker); - - return id; -} - - -int NewNicowar::order_regular_swarm(Echo& echo) -{ - //The main order for the swarm - BuildingOrder* bo = new BuildingOrder(IntBuildingType::SWARM_BUILDING, 4); - - //Constraints arround the location of wheat - AIEcho::Gradients::GradientInfo gi_wheat; - gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); - //You want to be close to wheat - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, 6)); - - //Constraints about the distance to water. - AIEcho::Gradients::GradientInfo gi_water; - gi_water.add_source(new AIEcho::Gradients::Entities::Water); - //You dont want to be too close to water, so that farm can develop between it and water - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, 6)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 2)); - - //Add the building order to the list of orders - unsigned int id=echo.add_building_order(bo); - - //Change the number of workers assigned when the building is finished - ManagementOrder* mo_completion=new SendMessage(FormatableString("update swarm %0").arg(id)); - mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_completion); - - ManagementOrder* mo_tracker=new AddRessourceTracker(25, CORN, id); - mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_tracker); - - return id; -} - - -int NewNicowar::order_regular_racetrack(Echo& echo) -{ - //The main order for the racetrack - BuildingOrder* bo = new BuildingOrder(IntBuildingType::WALKSPEED_BUILDING, 6); - - //Constraints arround the location of wood - AIEcho::Gradients::GradientInfo gi_wood; - gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); - //You want to be close to wood - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, 4)); - - //Constraints about the distance to water. - AIEcho::Gradients::GradientInfo gi_water; - gi_water.add_source(new AIEcho::Gradients::Entities::Water); - //You dont want to be too close to water. allows farms to develop - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, 6)); - - //Constraints arround the location of stone - AIEcho::Gradients::GradientInfo gi_stone; - gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); - //You want to be close to stone - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_stone, 1)); - //But not to close, so you have room to upgrade - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_stone, 2)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - //Constraints arround water. Can't be too close to sand. - AIEcho::Gradients::GradientInfo gi_sand; - gi_sand.add_source(new AIEcho::Gradients::Entities::Sand); - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_sand, 2)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 4)); - - //Add the building order to the list of orders - int id = echo.add_building_order(bo); - - return id; -} - - -int NewNicowar::order_regular_swimmingpool(Echo& echo) -{ - //The main order for the swimmingpool - BuildingOrder* bo = new BuildingOrder(IntBuildingType::SWIMSPEED_BUILDING, 6); - - //Constraints arround the location of wood - AIEcho::Gradients::GradientInfo gi_wood; - gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); - //You want to be close to wood - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, 4)); - - //Constraints about the distance to water. - AIEcho::Gradients::GradientInfo gi_water; - gi_water.add_source(new AIEcho::Gradients::Entities::Water); - //You dont want to be too close to water. allows farms to develop - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, 6)); - - //Constraints arround the location of wheat - AIEcho::Gradients::GradientInfo gi_wheat; - gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); - //You want to be close to wheat - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, 1)); - - //Constraints arround the location of stone - AIEcho::Gradients::GradientInfo gi_stone; - gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); - //You don't want to be too close, so you have room to upgrade - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_stone, 2)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You want to be close to other buildings, but wheat is more important - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - //Constraints arround water. Can't be too close to sand. - AIEcho::Gradients::GradientInfo gi_sand; - gi_sand.add_source(new AIEcho::Gradients::Entities::Sand); - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_sand, 2)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 4)); - - //Add the building order to the list of orders - int id = echo.add_building_order(bo); - - return id; -} - - -int NewNicowar::order_regular_school(Echo& echo) -{ - //The main order for the school - BuildingOrder* bo = new BuildingOrder(IntBuildingType::SCIENCE_BUILDING, 5); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You want to be close to other buildings - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - //Constraints about the distance to water. - AIEcho::Gradients::GradientInfo gi_water; - gi_water.add_source(new AIEcho::Gradients::Entities::Water); - //You dont want to be too close to water. allows farms to develop - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, 6)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 4)); - - //Constraints arround the enemy - AIEcho::Gradients::GradientInfo gi_enemy; - for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) - { - gi_enemy.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(*i, false)); - } -// gi_enemy.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - bo->add_constraint(new AIEcho::Construction::MaximizedDistance(gi_enemy, 3)); - - //Add the building order to the list of orders - int id = echo.add_building_order(bo); - - return id; -} - - -int NewNicowar::order_regular_barracks(Echo& echo) -{ - //The main order for the barracks - BuildingOrder* bo = new BuildingOrder(IntBuildingType::ATTACK_BUILDING, 6); - - //Constraints about the distance to water. - AIEcho::Gradients::GradientInfo gi_water; - gi_water.add_source(new AIEcho::Gradients::Entities::Water); - //You dont want to be too close to water. allows farms to develop - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, 6)); - - //Constraints arround the location of stone - AIEcho::Gradients::GradientInfo gi_stone; - gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); - //You want to be close to stone - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_stone, 5)); - - //Constraints arround the location of wood - AIEcho::Gradients::GradientInfo gi_wood; - gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); - //You want to be close to wood - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, 2)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You want to be close to other buildings - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 2)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 2)); - - //Add the building order to the list of orders - int id = echo.add_building_order(bo); - - return id; -} - - -int NewNicowar::order_regular_hospital(Echo& echo) -{ - //The main order for the hospital - BuildingOrder* bo = new BuildingOrder(IntBuildingType::HEAL_BUILDING, 2); - - //Constraints arround the location of wood - AIEcho::Gradients::GradientInfo gi_wood; - gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); - //You want to be close to wood - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, 2)); - - //Constraints about the distance to water. - AIEcho::Gradients::GradientInfo gi_water; - gi_water.add_source(new AIEcho::Gradients::Entities::Water); - //You dont want to be too close to water. allows farms to develop - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, 6)); - - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You want to be close to other buildings - bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 3)); - - AIEcho::Gradients::GradientInfo gi_building_construction; - gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); - if(!can_swim) - gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); - //You don't want to be too close - bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, 2)); - - //Add the building order to the list of orders - int id = echo.add_building_order(bo); - - return id; - -} - - -void NewNicowar::manage_buildings(Echo& echo) -{ - BuildingSearch bs(echo); - bs.add_condition(new NotUnderConstruction); - for(building_search_iterator i = bs.begin(); i!=bs.end(); ++i) - { - if(echo.get_building_register().get_type(*i)==IntBuildingType::SWARM_BUILDING) - { - manage_swarm(echo, *i); - } - if(echo.get_building_register().get_type(*i)==IntBuildingType::FOOD_BUILDING) - { - manage_inn(echo, *i); - } - } -} - - -void NewNicowar::manage_inn(Echo& echo, int id) -{ - int level=echo.get_building_register().get_level(id); - int assigned=echo.get_building_register().get_assigned(id); - - //Do nothing if the ressource_tracker order hasn't been processed yet - if(! echo.get_ressource_tracker(id)) - return; - int total_ressource_level = echo.get_ressource_tracker(id)->get_total_level(); - - int to_assign = 0; - if(level==1 && total_ressource_level>(strategy.level_1_inn_low_wheat_trigger_ammount*25)) - to_assign=strategy.level_1_inn_units_assigned_normal_wheat; - else if(level==1 && total_ressource_level<=(strategy.level_1_inn_low_wheat_trigger_ammount*25)) - to_assign=strategy.level_1_inn_units_assigned_low_wheat; - - if(level==2 && total_ressource_level>(strategy.level_2_inn_low_wheat_trigger_ammount*25)) - to_assign=strategy.level_2_inn_units_assigned_normal_wheat; - else if(level==2 && total_ressource_level<=(strategy.level_2_inn_low_wheat_trigger_ammount*25)) - to_assign=strategy.level_2_inn_units_assigned_low_wheat; - - if(level==3 && total_ressource_level>(strategy.level_3_inn_low_wheat_trigger_ammount*25)) - to_assign=strategy.level_3_inn_units_assigned_normal_wheat; - else if(level==3 && total_ressource_level<=(strategy.level_3_inn_low_wheat_trigger_ammount*25)) - to_assign=strategy.level_3_inn_units_assigned_low_wheat; - - ///The number of units assigned to an Inn depends entirely on its level - if(to_assign != assigned) - { - ManagementOrder* mo_assign=new AssignWorkers(to_assign, id); - echo.add_management_order(mo_assign); - } -} - - -void NewNicowar::manage_swarm(Echo& echo, int id) -{ - //Get some statistics - TeamStat* stat=echo.player->team->stats.getLatestStat(); - int total_explorers=stat->numberUnitPerType[EXPLORER]; - if(stat->totalUnit == 0) - return; - int total_starving_percent = stat->needFoodCritical * 100 / stat->totalUnit; - int total_hungry_percent = stat->needFood * 100 / stat->totalUnit; - - int assigned=echo.get_building_register().get_assigned(id); - int to_assign=0; - - //Do nothing if the ressource_tracker order hasn't been processed yet - if(! echo.get_ressource_tracker(id)) - return; - int total_ressource_level = echo.get_ressource_tracker(id)->get_total_level(); - - int worker_ratio=0; - int explorer_ratio=0; - int warrior_ratio=0; - - - to_assign=strategy.base_swarm_units_assigned; - - ///Double units when ressource level is low - if(total_ressource_level <= (strategy.base_swarm_low_wheat_trigger_ammount * 25)) - to_assign*=2; - - ///Half units if world is hungry - if((total_starving_percent + total_hungry_percent) > strategy.base_swarm_hungry_reduce_trigger_percent) - to_assign/=2; - - ///No units when the world is starving - if(starving_recovery) - to_assign=0; - - - ///The ratio of workers during the growth phase is different, due to the fact - ///that most explorers are made during the growth phase - if(growth_phase) - { - worker_ratio=strategy.growth_phase_swarm_worker_ratio; - - } - else - { - if(no_workers_phase) - worker_ratio=0; - else - worker_ratio=strategy.non_growth_phase_swarm_worker_ratio; - } - - //Base needed explorers never exceed 1/10 of population - int needed_explorers=std::min(strategy.base_number_of_explorers, stat->totalUnit/10+1); - if(fruit_phase) - needed_explorers+=strategy.fruit_phase_extra_number_of_explorers; - if(defend_explorers) - needed_explorers+=(stat->totalUnit * strategy.defense_explorer_population_percent) / 100; - if(explorer_attack_preperation_phase) - needed_explorers+=strategy.offense_explorer_number; - - if(total_explorers0) - school_chance=0; - - return choose_building_upgrade_type(echo, 1, strategy.upgrading_phase_1_inn_chance, strategy.upgrading_phase_1_hospital_chance, strategy.upgrading_phase_1_racetrack_chance, strategy.upgrading_phase_1_swimmingpool_chance, strategy.upgrading_phase_1_barracks_chance, school_chance, strategy.upgrading_phase_1_tower_chance); -} - - - -int NewNicowar::choose_building_upgrade_type_level2(Echo& echo) -{ - BuildingSearch schools_upgrading(echo); - schools_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - schools_upgrading.add_condition(new BeingUpgradedTo(3)); - const int school_counts_upgrading=schools_upgrading.count_buildings(); - - BuildingSearch schools_lvl2(echo); - schools_lvl2.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - schools_lvl2.add_condition(new BuildingLevel(2)); - schools_lvl2.add_condition(new NotUnderConstruction); - const int school_counts_level2=schools_lvl2.count_buildings(); - - BuildingSearch schools_lvl3(echo); - schools_lvl3.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); - schools_lvl3.add_condition(new BuildingLevel(3)); - schools_lvl3.add_condition(new NotUnderConstruction); - const int school_counts_level3=schools_lvl3.count_buildings(); - - ///Schools are only upgraded one at a time - int school_chance = strategy.upgrading_phase_2_school_chance; - if(school_counts_upgrading>0 || (school_counts_level2 + school_counts_level3)<2) - school_chance=0; - - return choose_building_upgrade_type(echo, 2, strategy.upgrading_phase_2_inn_chance, strategy.upgrading_phase_2_hospital_chance, strategy.upgrading_phase_2_racetrack_chance, strategy.upgrading_phase_2_swimmingpool_chance, strategy.upgrading_phase_2_barracks_chance, school_chance, strategy.upgrading_phase_2_tower_chance); -} - - -int NewNicowar::choose_building_upgrade_type(Echo& echo, int level, int inn_ratio, int hospital_ratio, int racetrack_ratio, int swimmingpool_ratio, int barracks_ratio, int school_ratio, int tower_ratio) -{ - ///First count the types of buildings that are available to us for upgrading - ///you wouldn't want to choose a Barracks to be upgraded if there are none - int building_count[IntBuildingType::NB_BUILDING]; - std::fill(building_count, building_count+IntBuildingType::NB_BUILDING, 0); - - BuildingSearch bs(echo); - bs.add_condition(new NotUnderConstruction); - bs.add_condition(new BuildingLevel(level)); - bs.add_condition(new Upgradable); - for(building_search_iterator i = bs.begin(); i!=bs.end(); ++i) - { - building_count[echo.get_building_register().get_type(*i)]+=1; - } - - ///Next, add in the n slices for each of the buildings with respect to their ratio - std::vector buildings; - buildings.reserve(100); - if(building_count[IntBuildingType::FOOD_BUILDING] > 0) - { - for(int n=0; n 0) - { - for(int n=0; n 0) - { - for(int n=0; n 0) - { - for(int n=0; n 0) - { - for(int n=0; n 0) - { - for(int n=0; n 0) - { - for(int n=0; n buildings; - std::copy(bs.begin(), bs.end(), std::back_insert_iterator >(buildings)); - int random=syncRand() % buildings.size(); - int id=buildings[random]; - - return id; -} - - -void NewNicowar::upgrade_buildings(Echo& echo) -{ - TeamStat* stat=echo.player->team->stats.getLatestStat(); - int can_upgrade_level1 = stat->upgradeState[BUILD][1] + stat->upgradeState[BUILD][2] + stat->upgradeState[BUILD][3]; - int can_upgrade_level2 = stat->upgradeState[BUILD][2] + stat->upgradeState[BUILD][3]; - - int num_to_upgrade_level1=0; - int num_to_upgrade_level2=0; - if(upgrading_phase_1) - { - //rounded up - num_to_upgrade_level1=(can_upgrade_level1 + strategy.upgrading_phase_1_num_units/2) / (strategy.upgrading_phase_1_num_units); - } - else - { - num_to_upgrade_level1=0; - } - - if(upgrading_phase_2) - { - //rounded up - num_to_upgrade_level2=(can_upgrade_level2 + strategy.upgrading_phase_2_num_units/2) / (strategy.upgrading_phase_2_num_units); - } - else - { - num_to_upgrade_level2=0; - } - - BuildingSearch bs_lvl1(echo); - bs_lvl1.add_condition(new BeingUpgradedTo(2)); - int num_upgrading_level1=bs_lvl1.count_buildings(); - - BuildingSearch bs_lvl2(echo); - bs_lvl2.add_condition(new BeingUpgradedTo(3)); - int num_upgrading_level2=bs_lvl2.count_buildings(); - - ///Level one upgrades - if(num_upgrading_level1 < num_to_upgrade_level1) - { - int building_type=choose_building_upgrade_type_level1(echo); - if(building_type!=-1) - { - std::string type=IntBuildingType::typeFromShortNumber(building_type); - - int id=choose_building_for_upgrade(echo, building_type, 1); - - ManagementOrder* uro = new UpgradeRepair(id); - echo.add_management_order(uro); - - ManagementOrder* mo_assign=new AssignWorkers(strategy.upgrading_phase_1_units_assigned, id); - mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, id)); - echo.add_management_order(mo_assign); - - //Cause the building to be updated after its completion. Not all buildings need - //to be updated, in which case the order will simply be ignored - ManagementOrder* mo_completion=new SendMessage(FormatableString("update %0 %1").arg(type).arg(id)); - mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_completion); - } - } - - ///Level two upgrades - if(num_upgrading_level2 < num_to_upgrade_level2) - { - int building_type=choose_building_upgrade_type_level2(echo); - if(building_type!=-1) - { - std::string type=IntBuildingType::typeFromShortNumber(building_type); - - int id=choose_building_for_upgrade(echo, building_type, 2); - ManagementOrder* uro = new UpgradeRepair(id); - echo.add_management_order(uro); - - ManagementOrder* mo_assign=new AssignWorkers(strategy.upgrading_phase_2_units_assigned, id); - mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, id)); - echo.add_management_order(mo_assign); - - //Cause the building to be updated after its completion. Not all buildings need - //to be updated, in which case the order will simply be ignored - ManagementOrder* mo_completion=new SendMessage(FormatableString("update %0 %1").arg(type).arg(id)); - mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); - echo.add_management_order(mo_completion); - } - } -} - - -int NewNicowar::choose_building_to_attack(Echo& echo) -{ - std::vector buildings_to_attack; - buildings_to_attack.reserve(100); - - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new Entities::AnyRessource); - Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_building); - - for(enemy_building_iterator ebi(echo, target, -1, -1, indeterminate); ebi!=enemy_building_iterator(); ++ebi) - { - Building* b=echo.player->game->teams[target]->myBuildings[Building::GIDtoID(*ebi)]; - if(gradient.get_height(b->posX, b->posY) != -2) - buildings_to_attack.push_back(*ebi); - } - - if(buildings_to_attack.size() == 0) - return -1; - - int num=syncRand() % buildings_to_attack.size(); - return buildings_to_attack[num]; -} - - -void NewNicowar::attack_building(Echo& echo) -{ - int building=choose_building_to_attack(echo); - if(building==-1) - { - if(!is_digging_out) - if(!dig_out_enemy(echo)) - { - target = -1; - } - return; - } - BuildingOrder* bo = new BuildingOrder(IntBuildingType::WAR_FLAG, strategy.war_phase_war_flag_units_assigned); - bo->add_constraint(new CenterOfBuilding(building)); - unsigned int id=echo.add_building_order(bo); - - ManagementOrder* mo_minimum=new ChangeFlagMinimumLevel(2,id); - echo.add_management_order(mo_minimum); - - ManagementOrder* mo_destroyed_1=new DestroyBuilding(id); - mo_destroyed_1->add_condition(new EnemyBuildingDestroyed(echo, building)); - echo.add_management_order(mo_destroyed_1); - - ManagementOrder* mo_destroyed_2=new SendMessage("attack finished "+boost::lexical_cast(id)); - mo_destroyed_2->add_condition(new BuildingDestroyed(id)); - echo.add_management_order(mo_destroyed_2); - - attack_flags.push_back(id); -} - - -void NewNicowar::control_attacks(Echo& echo) -{ - choose_enemy_target(echo); - - if(target!=-1) - { - unsigned number_attacks=0; - if(war) - { - number_attacks=strategy.war_phase_num_attack_flags; - } - - if(attack_flags.size() < number_attacks) - { - attack_building(echo); - } - } - - BuildingSearch bs_pool(echo); - bs_pool.add_condition(new SpecificBuildingType(IntBuildingType::SWIMSPEED_BUILDING)); - int num_pool=bs_pool.count_buildings(); - - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new Entities::AnyRessource); - if(num_pool == 0) - gi_building.add_obstacle(new Entities::Water); - Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_building); - - for(unsigned i=0; iposX, b->posY) == -2) - { - ManagementOrder* mo_destroy=new DestroyBuilding(attack_flags[i]); - echo.add_management_order(mo_destroy); - } - } - } -} - - - -void NewNicowar::choose_enemy_target(Echo& echo) -{ - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new Entities::AnyRessource); - Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_building); - - if(target==-1 || !echo.player->game->teams[target]->isAlive) - { - std::vector available_reachable_targets; - std::vector available_targets; - for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) - { - if(echo.player->game->teams[*i]->isAlive) - { - available_targets.push_back(*i); - enemy_building_iterator ebi(echo, *i, -1, -1, indeterminate); - /* Make sure we know of at least one - building that we can directly attack - before committing to a particular enemy. - It used to be that we did not (normally) - need to test this, because all starting - buildings were known. But that was - cheating and has been fixed. */ - for(; ebi != enemy_building_iterator(); ++ebi) - { - Building* b=echo.player->game->teams[*i]->myBuildings[Building::GIDtoID(*ebi)]; - if(gradient.get_height(b->posX, b->posY) != -2) - { - available_reachable_targets.push_back(*i); - break; - } - } - } - } - if(available_reachable_targets.size()!=0) - target=available_reachable_targets[syncRand() % available_reachable_targets.size()]; - else if(available_targets.size()!=0) - target=available_targets[syncRand() % available_targets.size()]; - else - target=-1; - } -} - - - -bool NewNicowar::dig_out_enemy(Echo& echo) -{ - ///First choose an enemy building to dig out - std::vector buildings_to_attack; - buildings_to_attack.reserve(100); - - MapInfo mi(echo); - - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - gi_building.add_obstacle(new Entities::AnyRessource); - Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_building); - - for(enemy_building_iterator ebi(echo, target, -1, -1, indeterminate); ebi!=enemy_building_iterator(); ++ebi) - { - Building* b=echo.player->game->teams[target]->myBuildings[Building::GIDtoID(*ebi)]; - int bx = (b->posX + mi.get_width()) % mi.get_width(); - int by = (b->posY + mi.get_height()) % mi.get_height(); - if(gradient.get_height(bx, by) == -2) - buildings_to_attack.push_back(*ebi); - } - - if(buildings_to_attack.size() == 0) - return false; - - int num=syncRand() % buildings_to_attack.size(); - - - int building=buildings_to_attack[num]; - const int bx=(echo.player->game->teams[target]->myBuildings[Building::GIDtoID(building)]->posX) % mi.get_width(); - const int by=(echo.player->game->teams[target]->myBuildings[Building::GIDtoID(building)]->posY) % mi.get_height(); - - AIEcho::Gradients::GradientInfo gi_pathfind; - gi_pathfind.add_source(new Entities::Position(bx, by)); - gi_pathfind.add_obstacle(new Entities::Ressource(STONE)); - Gradient& gradient_pathfind=echo.get_gradient_manager().get_gradient(gi_pathfind); - - ///Next, find the closest point manhattan distance wise, to the building that is accessible - int closest_x=0; - int closest_y=0; - int closest_distance=10000; - for(int x=0; x= 0) - { - int dist=gradient_pathfind.get_height(x, y); - if(dist < closest_distance) - { - closest_x=x; - closest_y=y; - closest_distance=dist; - } - } - } - } - - ///Next, follow a path arround stone between the closest point and the buildings position, - ///placing Clearing flags as you go - - int xpos=closest_x; - int ypos=closest_y; - - int flag_dist_count=3; - - int w=mi.get_width(); - int h=mi.get_height(); - - while(xpos != bx || ypos!=by) - { - int nxpos = xpos; - int nypos = ypos; - int rx=(xpos+1+w) % w; - int lx=(xpos-1+w) % w; - int dy=(ypos+1+h) % h; - int uy=(ypos-1+h) % h; - int lowest_entity=gradient_pathfind.get_height(xpos, ypos)+2; - - if(lowest_entity == 0) - break; - - //Test diagnols first, then the horizontals and verticals. - if(gradient_pathfind.get_height(lx, uy) < lowest_entity && gradient_pathfind.get_height(lx, uy)>=0) - { - lowest_entity=gradient_pathfind.get_height(lx, uy); - nxpos=lx; - nypos=uy; - } - if(gradient_pathfind.get_height(rx, uy) < lowest_entity && gradient_pathfind.get_height(rx, uy)>=0) - { - lowest_entity=gradient_pathfind.get_height(rx, uy); - nxpos=rx; - nypos=uy; - } - if(gradient_pathfind.get_height(lx, dy) < lowest_entity && gradient_pathfind.get_height(lx, dy)>=0) - { - lowest_entity=gradient_pathfind.get_height(lx, dy); - nxpos=lx; - nypos=dy; - } - if(gradient_pathfind.get_height(rx, dy) < lowest_entity && gradient_pathfind.get_height(rx, dy)>=0) - { - lowest_entity=gradient_pathfind.get_height(rx, dy); - nxpos=rx; - nypos=dy; - } - - if(gradient_pathfind.get_height(xpos, uy) < lowest_entity && gradient_pathfind.get_height(xpos, uy)>=0) - { - lowest_entity=gradient_pathfind.get_height(xpos, uy); - nxpos=xpos; - nypos=uy; - } - if(gradient_pathfind.get_height(lx, ypos) < lowest_entity && gradient_pathfind.get_height(lx, ypos)>=0) - { - lowest_entity=gradient_pathfind.get_height(lx, ypos); - nxpos=lx; - nypos=ypos; - } - if(gradient_pathfind.get_height(rx, ypos) < lowest_entity && gradient_pathfind.get_height(rx, ypos)>=0) - { - lowest_entity=gradient_pathfind.get_height(rx, ypos); - nxpos=rx; - nypos=ypos; - } - if(gradient_pathfind.get_height(xpos, dy) < lowest_entity && gradient_pathfind.get_height(xpos, dy)>=0) - { - lowest_entity=gradient_pathfind.get_height(xpos, dy); - nxpos=xpos; - nypos=dy; - } - - - flag_dist_count+=1; - - - if(flag_dist_count>3) - { - flag_dist_count=0; - //The main order for the clearing flag - BuildingOrder* bo_flag = new BuildingOrder(IntBuildingType::CLEARING_FLAG, 10); - //Place it on the current point - bo_flag->add_constraint(new Construction::SinglePosition(xpos, ypos)); - //Add the building order to the list of orders - unsigned int id_flag=echo.add_building_order(bo_flag); - - ManagementOrder* mo_destroyed=new DestroyBuilding(id_flag); - mo_destroyed->add_condition(new EnemyBuildingDestroyed(echo, building)); - echo.add_management_order(mo_destroyed); - - - ManagementOrder* mo_completion=new ChangeFlagSize(3, id_flag); - echo.add_management_order(mo_completion); - } - xpos = nxpos; - ypos = nypos; - - } - - ManagementOrder* mo_destroyed=new SendMessage("finished digging out"); - mo_destroyed->add_condition(new EnemyBuildingDestroyed(echo, building)); - echo.add_management_order(mo_destroyed); - - is_digging_out=true; - - return true; -} - - - -void NewNicowar::compute_defense_flag_positioning(AIEcho::Echo& echo) -{ - //This algorithm works by finding all units and buildings under attack, and creating a potential - //field by adding 1 to all squares within range of the units or buildings under attack. The result - //will be that the highest square will have the largest number of buildings or units that need - //defending within range. A flag is put onto the highest square, and the same concept is repeated, - //except that all under-attack units or buildings that are within range of a placed defense flag - //are ignored. - - //This algorithm does that, except optimized. A list is maintained to keep track of squares - //that have a value other than 0 as these are the only ones we want to place a flag on, and - //when a defense flag position is chosen, all units or buildings within range of the flag - //have all squares within their range -1, effectivly doing the same as recalculating all - //squares excluding those units now covered by a defense flag - MapInfo mi(echo); - const int w = mi.get_width(); - const int h = mi.get_height(); - const int RADIUS = 4; - - Uint16* counts = new Uint16[w * h]; - Uint16* buildingGID = new Uint16[w * h]; - Uint16* unitGID = new Uint16[w * h]; - memset(counts, 0, sizeof(Uint16) * w * h); - memset(buildingGID, NOGBID, sizeof(Uint16) * w * h); - memset(unitGID, NOGUID, sizeof(Uint16) * w * h); - std::list locations; - - //For every unit thats under attack, increment in the squares surrounding it. - //Use the 'locations' list to keep track of non-zero squares - for(int i=0; iteam->myUnits[i]; - if(unit && unit->underAttackTimer && unit->movement != Unit::MOV_ATTACKING_TARGET && unit->typeNum != EXPLORER && unitGID[(unit->posX+w)%w * h + (unit->posY+h)%h] == NOGUID) - { - unitGID[(unit->posX+w)%w * h + (unit->posY+h)%h] = unit->gid; - modify_points(counts, w, h, (unit->posX+w)%w, (unit->posY+h)%h, RADIUS, 1, locations); - } - } - for(int i=0; iteam->myBuildings[i]; - if(building && building->underAttackTimer && buildingGID[building->posX * h + building->posY] == NOGBID) - { - int nx = (building->posX - building->type->decLeft + w) %w; - int ny = (building->posY - building->type->decTop + h) %h; - buildingGID[building->posX * h + building->posY] = building->gid; - modify_points(counts, w, h, nx, ny, RADIUS, 1, locations); - } - } - - ///Choose the highest location, remove all units and buildings within a flags radius of that location, - ///and add that location to the list - std::vector flagLocations; - std::vector enemyUnits; - while(!locations.empty()) - { - //Find the square with the highest value, a flag is put here - int max = 0; - int maxPos = 0; - for(std::list::iterator i = locations.begin(); i!=locations.end(); ++i) - { - int pos = *i; - int n = counts[pos]; - if(n > max) - { - maxPos = pos; - max = n; - } - } - - // Inserting twice the same flag is a bug and may lead to an - // infinite loop. The most probable cause is an insufficient - // margin in the loop on all units and buildings below. - for (std::vector::const_iterator i = flagLocations.begin(); - i != flagLocations.end(); - ++i) - assert (*i != maxPos); - flagLocations.push_back(maxPos); - - int max_x = maxPos / h; - int max_y = maxPos % h; - - //test(echo, counts, w, h, squareProtected, locations); - - //For all units and buildings that are under attack and within the radius of the flag, - //decrement the values surrounding them. At the same time, count the number of enemy - //warriors in this zone - int enemy_count = 0; - // We need to loop over an area slightly bigger than RADIUS - // because buildings are taken into account in an offset - // location - for(int px = -RADIUS-3; px <= RADIUS+3; ++px) - { - int nx = (max_x + px + w)%w; - for(int py = -RADIUS-3; py<=RADIUS+3; ++py) - { - int ny = (max_y + py + h)%h; - if(unitGID[nx * h + ny] != NOGUID) - { - Unit* unit = echo.player->team->myUnits[Unit::GIDtoID(unitGID[nx * h + ny])]; - modify_points(counts, w, h, (unit->posX+w)%w, (unit->posY+h)%h, RADIUS, -1, locations); - unitGID[nx * h + ny] = NOGUID; - } - if(buildingGID[nx * h + ny] != NOGBID) - { - Building* building = echo.player->team->myBuildings[Building::GIDtoID(buildingGID[nx * h + ny])]; - int nx2 = (building->posX - building->type->decLeft + w) %w; - int ny2 = (building->posY - building->type->decTop + h) %h; - modify_points(counts, w, h, nx2, ny2, RADIUS, -1, locations); - buildingGID[nx * h + ny] = NOGBID; - } - - // Take enemy units into account only if they are - // within RADIUS of the flag (remember that we loop - // over a bigger area). - if ((px >= -RADIUS) && (px <= RADIUS) && (py >= -RADIUS) && (py <= RADIUS)) { - Uint16 guid = echo.player->map->getGroundUnit(nx, ny); - if(guid != NOGUID && (1<team->enemies) - { - Unit* unit = echo.player->game->teams[Unit::GIDtoTeam(guid)]->myUnits[Unit::GIDtoID(guid)]; - if(unit->typeNum == WARRIOR) - { - enemy_count += 1; - } - } - } - } - } - enemyUnits.push_back(std::min(20, enemy_count)); - } - - //Remove all flags with an enemy_count of 0 - for(std::vector::iterator i=flagLocations.begin(); i!=flagLocations.end();) - { - int n = i-flagLocations.begin(); - if(enemyUnits[n] == 0) - { - i = flagLocations.erase(i); - enemyUnits.erase(enemyUnits.begin() + n); - } - else - { - ++i; - } - } - - //Take all existing defense flags, and move them to the nearest new flag position - std::vector existing_defense_flags(defense_flags); - while(!existing_defense_flags.empty()) - { - int min_dist = INT_MAX; - int min_flag = 0; - int min_pos = 0; - int min_pos_x = 0; - int min_pos_y = 0; - int min_enemy = 0; - ///Choose the flag <-> flag location combination that has the lowest distance, start from it - for(std::vector::iterator i = existing_defense_flags.begin(); i!=existing_defense_flags.end(); ++i) - { - if(echo.get_building_register().is_building_found(*i)) - { - Building* b = echo.get_building_register().get_building(*i); - for(std::vector::iterator j = flagLocations.begin(); j!=flagLocations.end(); ++j) - { - int flag_x = (*j) / h; - int flag_y = (*j) % h; - int d = echo.player->map->warpDistSquare(flag_x, flag_y, b->posX, b->posY); - if(d < min_dist) - { - min_dist = d; - min_flag = i - existing_defense_flags.begin(); - min_pos = j - flagLocations.begin(); - min_pos_x = flag_x; - min_pos_y = flag_y; - min_enemy = enemyUnits[j - flagLocations.begin()]; - } - } - } - } - //Don't move flags more than 8 squares - if(min_dist < (8*8)) - { - int id_flag = existing_defense_flags[min_flag]; - existing_defense_flags.erase(existing_defense_flags.begin() + min_flag); - flagLocations.erase(flagLocations.begin() + min_pos); - enemyUnits.erase(enemyUnits.begin() + min_pos); - - if(min_dist>0) - { - ManagementOrder* mo_move=new ChangeFlagPosition(min_pos_x, min_pos_y, id_flag); - echo.add_management_order(mo_move); - } - if(min_enemy != echo.get_building_register().get_assigned(id_flag)) - { - ManagementOrder* mo_assign=new AssignWorkers(min_enemy, id_flag); - echo.add_management_order(mo_assign); - } - } - else - { - break; - } - } - //If there are remaining flags, its because these flags don't have a new position - //on the map to go to, so delete them - for(std::vector::iterator i = existing_defense_flags.begin(); i!=existing_defense_flags.end(); ++i) - { - if(echo.get_building_register().is_building_found(*i)) - { - Building* b = echo.get_building_register().get_building(*i); - int enemy_count = 0; - for(int px = -3; px <= 3; ++px) - { - int nx = (b->posX + px + w)%w; - for(int py = -3; py<=3; ++py) - { - int ny = (b->posY + py + h)%h; - Uint16 guid = echo.player->map->getGroundUnit(nx, ny); - if(guid != NOGUID && (1<team->enemies) - { - Unit* unit = echo.player->game->teams[Unit::GIDtoTeam(guid)]->myUnits[Unit::GIDtoID(guid)]; - if(unit->typeNum == WARRIOR) - { - enemy_count += 1; - } - } - } - } - if(enemy_count == 0) - { - ManagementOrder* mo_destroyed=new DestroyBuilding(*i); - echo.add_management_order(mo_destroyed); - } - else - { - if(enemy_count != echo.get_building_register().get_assigned(*i)) - { - ManagementOrder* mo_assign=new AssignWorkers(std::min(20, enemy_count), *i); - echo.add_management_order(mo_assign); - } - } - } - } - - //If there are remaining positions on the map, it is because we didn't have enough existing - //flags to cover them, so create new ones - for(std::vector::iterator i = flagLocations.begin(); i!=flagLocations.end(); ++i) - { - int enemy = enemyUnits[i - flagLocations.begin()]; - int flag_x = *i / h; - int flag_y = *i % h; - - //The main order for the war flag - BuildingOrder* bo_flag = new BuildingOrder(IntBuildingType::WAR_FLAG, enemy); - bo_flag->add_constraint(new Construction::SinglePosition(flag_x, flag_y)); - unsigned int id_flag=echo.add_building_order(bo_flag); - defense_flags.push_back(id_flag); - - ManagementOrder* mo_completion=new ChangeFlagSize(4, id_flag); - echo.add_management_order(mo_completion); - - ManagementOrder* mo_destroyed=new SendMessage("guard flag deleted " + boost::lexical_cast(id_flag)); - mo_destroyed->add_condition(new BuildingDestroyed(id_flag)); - echo.add_management_order(mo_destroyed); - } - - delete[] counts; - delete[] unitGID; - delete[] buildingGID; -} - - - -void NewNicowar::modify_points(Uint16* counts, int w, int h, int x, int y, int dist, int value, std::list& locations) -{ - for(int px = -dist; px <= dist; ++px) - { - int nx = (x + px + w)%w; - for(int py = -dist; py <= dist; ++py) - { - int ny = (y + py + h)%h; - if(px * px + py * py <= dist * dist) - { - if(value>0) - { - if(counts[nx * h + ny] == 0) - locations.push_back(nx * h + ny); - counts[nx * h + ny] += value; - } - else if(value<0) - { - counts[nx * h + ny] += value; - if(counts[nx * h + ny] == 0) - locations.remove(nx * h + ny); - } - } - } - } -} - - - -void NewNicowar::compute_explorer_flag_attack_positioning(AIEcho::Echo& echo) -{ - //The algorithm here is interesting. Bassically, an enemy unit is selected. Every enemy unit within 4 squares of this unit - //is counted as part of the larger group, and every unit 4 squares from those and so on, as long as it doesn't go past - //6 squares from the average. Flags are put on the average x and y of largest groups - MapInfo mi(echo); - const int w = mi.get_width(); - const int h = mi.get_height(); - - std::vector > groups; - - if(explorer_attack_phase && target!=-1) - { - Unit** units = new Unit*[Unit::MAX_COUNT]; - Unit* first = NULL; - for(int i=0; igame->teams[target]->myUnits[i]; - if(unit && mi.is_discovered(unit->posX, unit->posY) && unit->typeNum != EXPLORER && unit->activity != Unit::ACT_UPGRADING) - { - if(!first) - first = unit; - units[i] = unit; - } - else - { - units[i] = NULL; - } - } - - while(true) - { - int group_x = 0; - int group_y = 0; - int group_size = 0; - - std::queue proccess; - std::queue xposs; - std::queue yposs; - for(int i=0; iposX; - group_y += units[i]->posY; - proccess.push(units[i]); - xposs.push(units[i]->posX); - yposs.push(units[i]->posY); - units[i] = NULL; - group_size+=1; - break; - } - } - - if(group_size == 0) - break; - - while(!proccess.empty()) - { - Unit* top = proccess.front(); - int ix = xposs.front(); - int iy = yposs.front(); - proccess.pop(); - xposs.pop(); - yposs.pop(); - for(int dx = -4; dx<=4; ++dx) - { - int nx = (top->posX + dx + w) % w; - for(int dy = -4; dy<=4; ++dy) - { - int ny = (top->posY + dy + h) % h; - if(echo.player->map->warpDistSquare(group_x / group_size, group_y / group_size, nx, ny) < (6*6)) - { - Uint16 guid = echo.player->map->getGroundUnit(nx, ny); - if(guid != NOGUID && Unit::GIDtoTeam(guid) == target) - { - int id = Unit::GIDtoID(guid); - if(units[id]) - { - group_x += ix + dx; - group_y += iy + dy; - proccess.push(units[id]); - xposs.push(ix + dx); - yposs.push(iy + dy); - units[id] = NULL; - group_size+=1; - } - } - } - } - } - } - group_x = (group_x / group_size + w)%w; - group_y = (group_y / group_size + h)%h; - - groups.push_back(boost::make_tuple(group_size, group_x, group_y)); - } - } - - std::sort(groups.begin(), groups.end(), std::greater >()); - int total_attacks = strategy.offense_explorer_flag_number; - if(!explorer_attack_phase) - total_attacks = 0; - - //Go through existing flags and see if they can be moved to be on top of new groups - std::vector existing_explorer_attack_flags(explorer_attack_flags); - while(total_attacks && !existing_explorer_attack_flags.empty()) - { - int min_dist = INT_MAX; - int min_flag = 0; - int min_pos = 0; - int min_pos_x = 0; - int min_pos_y = 0; - ///Choose the flag <-> flag location combination that has the lowest distance, start from it - for(std::vector::iterator i = existing_explorer_attack_flags.begin(); i!=existing_explorer_attack_flags.end(); ++i) - { - if(echo.get_building_register().is_building_found(*i)) - { - Building* b = echo.get_building_register().get_building(*i); - for(std::vector >::iterator j = groups.begin(); j!=groups.end(); ++j) - { - int flag_x = j->get<1>(); - int flag_y = j->get<2>(); - int d = echo.player->map->warpDistSquare(flag_x, flag_y, b->posX, b->posY); - if(d < min_dist) - { - min_dist = d; - min_flag = i - existing_explorer_attack_flags.begin(); - min_pos = j - groups.begin(); - min_pos_x = flag_x; - min_pos_y = flag_y; - } - } - } - } - - if(min_dist != INT_MAX) - { - total_attacks-=1; - int id_flag = existing_explorer_attack_flags[min_flag]; - - existing_explorer_attack_flags.erase(existing_explorer_attack_flags.begin() + min_flag); - groups.erase(groups.begin() + min_pos); - - if(min_dist != 0) - { - ManagementOrder* mo_move=new ChangeFlagPosition(min_pos_x, min_pos_y, id_flag); - echo.add_management_order(mo_move); - } - } - else - { - break; - } - } - - //If there are remaining flags, its because these flags don't have a new position - //on the map to go to, so delete them - for(std::vector::iterator i = existing_explorer_attack_flags.begin(); i!=existing_explorer_attack_flags.end(); ++i) - { - if(echo.get_building_register().is_building_found(*i)) - { - ManagementOrder* mo_destroyed=new DestroyBuilding(*i); - echo.add_management_order(mo_destroyed); - } - } - - while(total_attacks && !groups.empty()) - { - boost::tuple groupInfo = *groups.begin(); - groups.erase(groups.begin()); - total_attacks -= 1; - - BuildingOrder* bo_flag = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, strategy.offense_explorer_flag_assigned); - bo_flag->add_constraint(new Construction::SinglePosition(groupInfo.get<1>(), groupInfo.get<2>())); - unsigned int id_flag=echo.add_building_order(bo_flag); - - ManagementOrder* mo_completion=new ChangeFlagSize(6, id_flag); - echo.add_management_order(mo_completion); - - ManagementOrder* mo_level=new ChangeFlagMinimumLevel(4, id_flag); - echo.add_management_order(mo_level); - - explorer_attack_flags.push_back(id_flag); - - ManagementOrder* mo_destroyed=new SendMessage("explorer attack flag deleted " + boost::lexical_cast(id_flag)); - mo_destroyed->add_condition(new BuildingDestroyed(id_flag)); - echo.add_management_order(mo_destroyed); - } -} - - - -void NewNicowar::update_farming(Echo& echo) -{ - //Farming wheat and wood in areas near water - AddArea* mo_farming=new AddArea(ForbiddenArea); - RemoveArea* mo_non_farming=new RemoveArea(ForbiddenArea); - AIEcho::Gradients::GradientInfo gi_water; - gi_water.add_source(new Entities::Water); - Gradient& water_gradient=echo.get_gradient_manager().get_gradient(gi_water); - - MapInfo mi(echo); - for(int x=0; xadd_location(x, y); - } - else if(!farm_spot && mi.is_forbidden_area(x, y)) - { - mo_non_farming->add_location(x, y); - } - } - } - } - echo.add_management_order(mo_farming); - echo.add_management_order(mo_non_farming); -} - - -void NewNicowar::update_fruit_flags(AIEcho::Echo& echo) -{ - if(fruit_phase && !exploration_on_fruit) - { - //Constraints arround nearby settlement - AIEcho::Gradients::GradientInfo gi_building; - gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); - - - //The main order for the exploration flag on cherry - BuildingOrder* bo_cherry = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 2); - //You want the closest fruit to your settlement possible - bo_cherry->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); - //Constraint arround the location of fruit - AIEcho::Gradients::GradientInfo gi_cherry; - gi_cherry.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); - //You want to be ontop of the cherry trees - bo_cherry->add_constraint(new AIEcho::Construction::MaximumDistance(gi_cherry, 0)); - //Add the building order to the list of orders - unsigned int id_cherry=echo.add_building_order(bo_cherry); - - ManagementOrder* mo_completion_cherry=new ChangeFlagSize(4, id_cherry); - echo.add_management_order(mo_completion_cherry); - - - - //The main order for the exploration flag in orange - BuildingOrder* bo_orange = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 2); - //You want the closest fruit to your settlement possible - bo_orange->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); - //Constraints arround the location of fruit - AIEcho::Gradients::GradientInfo gi_orange; - gi_orange.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); - //You want to be ontop of the orange trees - bo_orange->add_constraint(new AIEcho::Construction::MaximumDistance(gi_orange, 0)); - unsigned int id_orange=echo.add_building_order(bo_orange); - - ManagementOrder* mo_completion_orange=new ChangeFlagSize(4, id_orange); - echo.add_management_order(mo_completion_orange); - - //The main order for the exploration flag on prunes - BuildingOrder* bo_prune = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 2); - //You want the closest fruit to your settlement possible - bo_prune->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); - AIEcho::Gradients::GradientInfo gi_prune; - gi_prune.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); - //You want to be ontop of the prune trees - bo_prune->add_constraint(new AIEcho::Construction::MaximumDistance(gi_prune, 0)); - //Add the building order to the list of orders - unsigned int id_prune=echo.add_building_order(bo_prune); - - ManagementOrder* mo_completion_prune=new ChangeFlagSize(4, id_prune); - echo.add_management_order(mo_completion_prune); - - - - exploration_on_fruit=true; - } - update_fruit_alliances(echo); -} - - -void NewNicowar::update_fruit_alliances(AIEcho::Echo& echo) -{ - bool activated=fruit_phase; - - for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) - { - ManagementOrder* mo_alliance=new ChangeAlliances(*i, indeterminate, indeterminate, indeterminate, activated, indeterminate); - echo.add_management_order(mo_alliance); - } -} - diff --git a/src/AINull.cpp b/src/AINull.cpp deleted file mode 100644 index 5af5fc91b..000000000 --- a/src/AINull.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "AINull.h" -#include "Order.h" - -boost::shared_ptr AINull::getOrder(void) -{ - return boost::shared_ptr(new NullOrder()); -} diff --git a/src/AINull.h b/src/AINull.h deleted file mode 100644 index e4980838e..000000000 --- a/src/AINull.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __AI_NULL_H -#define __AI_NULL_H - -#include "AIImplementation.h" - -class AINull : public AIImplementation -{ -public: - AINull() { } - ~AINull() { } - - void init(Player *player) { } - - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) { return true; } - void save(GAGCore::OutputStream *stream) { } - - boost::shared_ptr getOrder(void); - -private: -}; - -#endif - - - diff --git a/src/AINumbi.h b/src/AINumbi.h deleted file mode 100644 index 84e59f1e8..000000000 --- a/src/AINumbi.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __AI_NUMBI_H -#define __AI_NUMBI_H - -#include "BuildingType.h" -#include "AIImplementation.h" - -class Game; -class Map; -class Order; -class Player; -class Team; -class Building; - -class AINumbi : public AIImplementation -{ -public: - AINumbi(Player *player); - AINumbi(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - ~AINumbi(); - - Player *player; - Team *team; - Game *game; - Map *map; - - bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - - boost::shared_ptrgetOrder(void); - -private: - int timer; - int phase; - int attackPhase; - int phaseTime; - int critticalWarriors; - int critticalTime; - int attackTimer; - int mainBuilding[15]; //BuildingType::NB_BUILDING=15 with lover versions - void init(Player *player); - int estimateFood(Building *building); - int countUnits(void); - int countUnits(const int medicalState); - boost::shared_ptrswarmsForWorkers(const int minSwarmNumbers, const int nbWorkersFator, const int workers, const int explorers, const int warriors); - void nextMainBuilding(const int buildingType); - int nbFreeAround(const int buildingType, int posX, int posY, int width, int height); - bool parseBuildingType(const int buildingType); - void squareCircleScann(int &dx, int &dy, int &sx, int &sy, int &x, int &y, int &mx, int &my); - bool findNewEmplacement(const int buildingType, int *posX, int *posY); - boost::shared_ptrmayAttack(int critticalMass, int critticalTimeout, Sint32 numberRequested); - boost::shared_ptradjustBuildings(const int numbers, const int numbersInc, const int workers, const int buildingType); - boost::shared_ptrcheckoutExpands(const int numbers, const int workers); - boost::shared_ptrmayUpgrade(const int ptrigger, const int ntrigger); -}; - -#endif - - - diff --git a/src/BasePlayer.cpp b/src/BasePlayer.cpp index 1272aeaea..df7469d95 100644 --- a/src/BasePlayer.cpp +++ b/src/BasePlayer.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "BasePlayer.h" @@ -81,6 +64,12 @@ void BasePlayer::setTeamNumber(Sint32 teamNumber) this->teamNumberMask=1<readEnterSection("BasePlayer"); @@ -92,6 +81,18 @@ bool BasePlayer::load(GAGCore::InputStream *stream, Sint32 versionMinor) teamNumber = stream->readSint32("teamNumber"); teamNumberMask = stream->readUint32("teamNumberMask"); stream->readLeaveSection(); + if (number < 0 || number >= Team::MAX_COUNT) + { + fprintf(stderr, "BasePlayer::load: out-of-range player number %d (must be in [0, %d))\n", + (int)number, Team::MAX_COUNT); + return false; + } + if (teamNumber < 0 || teamNumber >= Team::MAX_COUNT) + { + fprintf(stderr, "BasePlayer::load: out-of-range teamNumber %d (must be in [0, %d))\n", + (int)teamNumber, Team::MAX_COUNT); + return false; + } return true; } @@ -120,10 +121,10 @@ Uint32 BasePlayer::checkSum() //Uint32 netHost=SDL_SwapBE32(ip.host); //Uint32 netPort=(Uint32)SDL_SwapBE16(ip.port); //cs^=netHost; - // IP adress can't stay in checksum, because: - // We now support NAT or IP may simply be differents between computers - // And we uses checkSum in network. - // (we could uses two differents check sums, but the framework would be heavier) + // IP address can't stay in checksum, because: + // We now support NAT or IP may simply be different between computers + // And we use checkSum in network. + // (we could use two different check sums, but the framework would be heavier) //cs^=netPort; for (unsigned i=0; i or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef BasePlayer_h -#define BasePlayer_h +#pragma once #include #include "AI.h" @@ -34,6 +16,26 @@ class BasePlayer public: /** * Players can be AI or human players at the local machine or connected via a network. + * + * RUST PORT: don't replicate this layout. PlayerType conflates two + * orthogonal facts — "what kind of slot" (none / lost / network / + * local / AI) and "which AI implementation" — by reserving P_AI=5 + * as a base and treating P_AI+n as "AI implementation n". That's a + * sentinel-by-arithmetic encoding with no type safety: nothing + * prevents adding a real PlayerType in the trailing range, and + * round-tripping requires the helpers below. It survives in C++ + * only because the value is serialized into saves, replays, and + * the network protocol, so renumbering would break wire compat. + * + * The Rust version should split this into two fields, e.g.: + * enum PlayerKind { None, LostDropping, LostFinal, Network, Local, AI } + * struct BasePlayer { + * kind: PlayerKind, + * ai_type: Option, // Some iff kind == AI + * ... + * } + * Saves get re-versioned in the port anyway, so this is the right + * moment to fix it. */ enum PlayerType { @@ -67,13 +69,19 @@ class BasePlayer }; PlayerType type; - //TODO: Explain + /// Player slot index. Valid range: [0, Team::MAX_COUNT). Used to index + /// Game::players[] and as the bit position in numberMask. Sint32 number; - //TODO: Explain + /// Cached 1 << number. Kept in sync via setNumber(). Uint32 numberMask; std::string name; + /// Index of the Team this player controls. Valid range: + /// [0, mapHeader.getNumberOfTeams()) — must point at a live Team slot. + /// BasePlayer::load enforces the wider [0, Team::MAX_COUNT) bound; the + /// tighter map-aware bound is checked at the Game::setGameHeader call + /// site, where mapHeader is available. Sint32 teamNumber; - //TODO: Explain + /// Cached 1 << teamNumber. Kept in sync via setTeamNumber(). Uint32 teamNumberMask; ///true if this player is to quit but still has orders to process bool quitting; @@ -113,4 +121,3 @@ class BasePlayer bool disableRecursiveDestruction; }; -#endif diff --git a/src/BaseTeam.cpp b/src/BaseTeam.cpp index a237d33dd..df5416da1 100644 --- a/src/BaseTeam.cpp +++ b/src/BaseTeam.cpp @@ -1,28 +1,12 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "BaseTeam.h" #include "Marshaling.h" #include "Race.h" #include "Stream.h" +#include "Utilities.h" using namespace GAGCore; @@ -132,11 +116,11 @@ Uint32 BaseTeam::checkSum() Uint32 cs=0; cs^=teamNumber; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs^=numberOfPlayer; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs^=playersMask; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); return cs; } diff --git a/src/BaseTeam.h b/src/BaseTeam.h index ac3aad3e7..c478fc00e 100644 --- a/src/BaseTeam.h +++ b/src/BaseTeam.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef BaseTeam_h -#define BaseTeam_h +#pragma once #include "GraphicContext.h" @@ -65,4 +47,3 @@ class BaseTeam Uint32 checkSum(); }; -#endif diff --git a/src/BitArray.cpp b/src/BitArray.cpp index 3328776d8..9cf4bf700 100644 --- a/src/BitArray.cpp +++ b/src/BitArray.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "BitArray.h" #include diff --git a/src/BitArray.h b/src/BitArray.h index f795aee45..55d57419e 100644 --- a/src/BitArray.h +++ b/src/BitArray.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __BITARRAY_H -#define __BITARRAY_H +#pragma once #include @@ -42,8 +25,9 @@ namespace Utilities void set(size_t pos, bool value); bool get(size_t pos) const; void serialize(unsigned char *stream) const; + //! Copies ceil(size/8) bytes from `stream` into the internal buffer. + //! Performs no bound check on `stream` — the caller must guarantee + //! that at least ceil(size/8) bytes are readable. See BH-195. void deserialize(const unsigned char *stream, size_t size); }; } - -#endif diff --git a/src/Brush.cpp b/src/Brush.cpp index 03ded5e9f..13b9996d7 100644 --- a/src/Brush.cpp +++ b/src/Brush.cpp @@ -1,21 +1,5 @@ -/* -Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière -for any question or comment contact us at or - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "Brush.h" #include "BitArray.h" @@ -163,112 +147,80 @@ int BrushTool::getBrushHeight(unsigned figure) return dim[figure]; } -int BrushTool::getBrushDimXMinus(unsigned figure) -{ - if (getBrushWidth(figure) % 2) - return getBrushWidth(figure) / 2; - else - return getBrushWidth(figure) / 2; -} - -int BrushTool::getBrushDimXPlus(unsigned figure) -{ - if (getBrushWidth(figure) % 2) - return (getBrushWidth(figure) / 2) + 1; - else - return getBrushWidth(figure) / 2; -} - -int BrushTool::getBrushDimYMinus(unsigned figure) -{ - if (getBrushHeight(figure) % 2) - return getBrushHeight(figure) / 2; - else - return getBrushHeight(figure) / 2; -} - -int BrushTool::getBrushDimYPlus(unsigned figure) -{ - if (getBrushHeight(figure) % 2) - return (getBrushHeight(figure) / 2) + 1; - else - return getBrushHeight(figure) / 2; -} -/* -int BrushTool::getBrushDimX(unsigned figure) -{ - if (getBrushWidth(figure) % 2) - return (getBrushWidth(figure) - 1) >> 1; - else - return getBrushWidth(figure) >> 1; -} - -int BrushTool::getBrushDimY(unsigned figure) -{ - if (getBrushHeight(figure) % 2) - return (getBrushHeight(figure) - 1) >> 1; - else - return getBrushHeight(figure) >> 1; -}*/ +// For odd widths the center cell counts on the Plus side, so Plus = ceil(w/2) +// and Minus = floor(w/2). For even widths the brush is symmetric. +int BrushTool::getBrushDimXMinus(unsigned figure) { return getBrushWidth(figure) / 2; } +int BrushTool::getBrushDimXPlus(unsigned figure) { return (getBrushWidth(figure) + 1) / 2; } +int BrushTool::getBrushDimYMinus(unsigned figure) { return getBrushHeight(figure) / 2; } +int BrushTool::getBrushDimYPlus(unsigned figure) { return (getBrushHeight(figure) + 1) / 2; } bool BrushTool::getBrushValue(unsigned figure, int x, int y, int centerX, int centerY, int originalX, int originalY) { - int brush0[] = { 1 }; - int brush1[] = { 0, 1, 0, - 1, 1, 1, - 0, 1, 0 }; - int brush2[] = { 1, 0, 0, - 0, 1, 0, - 0, 0, 1, }; - int brush3[] = { 0, 0, 1, - 0, 1, 0, - 1, 0, 0, }; - /*int brush4[] = { 1, 0, 1, 0, 1, - 0, 1, 0, 1, 0, - 1, 0, 1, 0, 1, - 0, 1, 0, 1, 0, - 1, 0, 1, 0, 1 }; - int brush5[] = { 1, 0, 1, 0, 1, - 0, 0, 0, 0, 0, - 1, 0, 1, 0, 1, - 0, 0, 0, 0, 0, - 1, 0, 1, 0, 1 }; - */ - int brush4[] = { 1, 0, 1, 0, - 0, 1, 0, 1, - 1, 0, 1, 0, - 0, 1, 0, 1 }; - int brush5[] = { 1, 0, 1, 0, - 0, 0, 0, 0, - 1, 0, 1, 0, - 0, 0, 0, 0 }; - int brush6[] = { 1, 1, 1, - 1, 1, 1, - 1, 1, 1 }; - int brush7[] = { 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1 }; - int *brushes[BRUSH_COUNT] = { brush0, brush1, brush2, brush3, brush4, brush5, brush6, brush7 }; - + static constexpr int brush0[] = { 1 }; + static constexpr int brush1[] = { + 0, 1, 0, + 1, 1, 1, + 0, 1, 0, + }; + static constexpr int brush2[] = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + }; + static constexpr int brush3[] = { + 0, 0, 1, + 0, 1, 0, + 1, 0, 0, + }; + static constexpr int brush4[] = { + 1, 0, 1, 0, + 0, 1, 0, 1, + 1, 0, 1, 0, + 0, 1, 0, 1, + }; + static constexpr int brush5[] = { + 1, 0, 1, 0, + 0, 0, 0, 0, + 1, 0, 1, 0, + 0, 0, 0, 0, + }; + static constexpr int brush6[] = { + 1, 1, 1, + 1, 1, 1, + 1, 1, 1, + }; + static constexpr int brush7[] = { + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + }; + static constexpr const int* brushes[BRUSH_COUNT] = { + brush0, brush1, brush2, brush3, brush4, brush5, brush6, brush7, + }; + // Brushes 4 and 5 are checkerboard patterns: their cells must be aligned to + // the parity of the stroke origin so neighbouring strokes tile seamlessly. + static constexpr bool needsParityAlignment[BRUSH_COUNT] = { + false, false, false, false, true, true, false, false, + }; + assert(figure < BRUSH_COUNT); int w = getBrushWidth(figure); int h = getBrushHeight(figure); assert(x < w); assert(y < h); - - if ((figure == 4) || (figure == 5)) + + if (needsParityAlignment[figure]) { - // do alignment on specific brush (4 and 5) - if (centerX % 2 == originalX%2) + if (centerX % 2 == originalX % 2) x++; - if (centerY % 2 == originalY%2) + if (centerY % 2 == originalY % 2) y++; } - - return (brushes[figure][(y%h) * getBrushWidth(figure) + (x%w)] != 0); + + return (brushes[figure][(y % h) * getBrushWidth(figure) + (x % w)] != 0); } void BrushTool::setAddRemoveEnabledState(bool value) diff --git a/src/Brush.h b/src/Brush.h index 2dc9c7709..0873958b2 100644 --- a/src/Brush.h +++ b/src/Brush.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __BRUSH_H -#define __BRUSH_H +#pragma once #include #include @@ -144,4 +127,3 @@ class BrushAccumulator size_t getApplicationCount(void) { return applications.size(); } }; -#endif diff --git a/src/Building.cpp b/src/Building.cpp deleted file mode 100644 index 8e3919f60..000000000 --- a/src/Building.cpp +++ /dev/null @@ -1,2896 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include -#include -#include -#include - -#include "Building.h" -#include "BuildingType.h" -#include "Game.h" -#include "GlobalContainer.h" -#include "LogFileManager.h" -#include "Team.h" -#include "Unit.h" -#include "Utilities.h" -#include "Order.h" -#include "Bullet.h" -#include "Integrity.h" - -Building::Building(GAGCore::InputStream *stream, BuildingsTypes *types, Team *owner, Sint32 versionMinor) -{ - for (int i=0; i<2; i++) - { - globalGradient[i]=NULL; - localRessources[i]=NULL; - } - logFile = globalContainer->logFileManager->getFile("Building.log"); - load(stream, types, owner, versionMinor); -} - -Building::Building(int x, int y, Uint16 gid, Sint32 typeNum, Team *team, BuildingsTypes *types, Sint32 unitWorking, Sint32 unitWorkingFuture) -{ - logFile = globalContainer->logFileManager->getFile("Building.log"); - - // identity - this->gid=gid; - owner=team; - - // type - this->typeNum=typeNum; - type=types->get(typeNum); - owner->prestige+=type->prestige; - - // construction state - buildingState=ALIVE; - // We can only push on map level 0 building-sites ! - // If you want to add higher level building-sites, you have to change the "constructionResultState" to UPGRADE, - // and set the "buildingState" correctly. - if (type->isBuildingSite) - constructionResultState=NEW_BUILDING; - else - constructionResultState=NO_CONSTRUCTION; - - - // units - shortTypeNum = type->shortTypeNum; - maxUnitInside = type->maxUnitInside; - maxUnitWorking = unitWorking; - maxUnitWorkingLocal = maxUnitWorking; - maxUnitWorkingPreferred = maxUnitWorking; - maxUnitWorkingFuture = unitWorkingFuture; - maxUnitWorkingPrevious = 0; - desiredMaxUnitWorking = maxUnitWorking; - subscriptionWorkingTimer = 0; - priority = 0; - priorityLocal = 0; - oldPriority = 0; - - // position - posX=x; - posY=y; - posXLocal=posX; - posYLocal=posY; - - underAttackTimer=0; - canNotConvertUnitTimer=0; - - // flag usefull : - unitStayRange=type->defaultUnitStayRange; - unitStayRangeLocal=unitStayRange; - for(int i=0; ihpInit; // (Uint16) - - // prefered parameters - - productionTimeout=type->unitProductionTime; - - totalRatio=0; - ratioLocal[0]=ratio[0]=1; - totalRatio++; - percentUsed[0]=0; - for (int i=1; ireadEnterSection("Building"); - - // construction state - buildingState = (BuildingState)stream->readUint32("buildingState"); - constructionResultState = (ConstructionResultState)stream->readUint32("constructionResultState"); - - // identity - gid = stream->readUint16("gid"); - this->owner = owner; - - // position - posX = stream->readSint32("posX"); - posY = stream->readSint32("posY"); - posXLocal = posX; - posYLocal = posY; - - if(versionMinor>=61) - underAttackTimer = stream->readUint8("underAttackTimer"); - else - underAttackTimer = 0; - if(versionMinor>=81) - canNotConvertUnitTimer = stream->readUint8("canNotConvertUnitTimer"); - else - canNotConvertUnitTimer = 150; - - // priority - if(versionMinor>=79) - { - priority = stream->readSint32("priority"); - priorityLocal = stream->readSint32("priorityLocal"); - oldPriority = priority; - } - else - { - priority = 0; - priorityLocal = 0; - oldPriority = 0; - } - - // Flag specific - unitStayRange = stream->readUint32("unitStayRange"); - unitStayRangeLocal = unitStayRange; - - for (int i=0; ireadSint32(oss.str().c_str()); - } - assert(clearingRessources[STONE] == false); - - memcpy(clearingRessourcesLocal, clearingRessources, sizeof(bool)*BASIC_COUNT); - - minLevelToFlag = stream->readSint32("minLevelToFlag"); - minLevelToFlagLocal = minLevelToFlag; - - // Building Specific - for (int i=0; ireadSint32(oss.str().c_str()); - } - - // quality parameters - hp = stream->readSint32("hp"); - - // prefered parameters - productionTimeout = stream->readSint32("productionTimeout"); - totalRatio = stream->readSint32("totalRatio"); - for (int i=0; ireadSint32(oss.str().c_str()); - } - { - std::ostringstream oss; - oss << "percentUsed[" << i << "]"; - percentUsed[i] = stream->readSint32(oss.str().c_str()); - } - } - - receiveRessourceMask = stream->readUint32("receiveRessourceMask"); - sendRessourceMask = stream->readUint32("sendRessourceMask"); - receiveRessourceMaskLocal = receiveRessourceMask; - sendRessourceMaskLocal = sendRessourceMask; - - shootingStep = stream->readUint32("shootingStep"); - shootingCooldown = stream->readSint32("shootingCooldown"); - bullets = stream->readSint32("bullets"); - - // type - // FIXME : do not save typenum but name/isBuildingSite/level - typeNum = stream->readSint32("typeNum"); - type = types->get(typeNum); - assert(type); - updateRessourcesPointer(); - - // reload data from type - shortTypeNum = type->shortTypeNum; - maxUnitInside = type->maxUnitInside; - maxUnitWorking = type->maxUnitWorking; - - // init data not loaded - maxUnitWorkingLocal = maxUnitWorking; - maxUnitWorkingPreferred = 1; - maxUnitWorkingFuture = 1; - desiredMaxUnitWorking = maxUnitWorking; - subscriptionWorkingTimer = 0; - - owner->prestige += type->prestige; - - seenByMask = stream->readUint32("seenByMaskk"); - - inCanFeedUnit=LS_UNKNOWN; - inCanHealUnit=LS_UNKNOWN; - callListState = 0; - - for (int i=0; ireadLeaveSection(); - - lastShootStep = 0xFFFFFFFF; - lastShootSpeedX = 0; - lastShootSpeedY = 0; - - - for(int i=0; iwriteEnterSection("Building"); - - // construction state - stream->writeUint32((Uint32)buildingState, "buildingState"); - stream->writeUint32((Uint32)constructionResultState, "constructionResultState"); - - // identity - stream->writeUint16(gid, "gid"); - // we drop team - - // position - stream->writeSint32(posX, "posX"); - stream->writeSint32(posY, "posY"); - - stream->writeUint8(underAttackTimer, "underAttackTimer"); - stream->writeUint8(canNotConvertUnitTimer, "canNotConvertUnitTimer"); - - // priority - stream->writeSint32(priority, "priority"); - stream->writeSint32(priorityLocal, "priorityLocal"); - - // Flag specific - stream->writeUint32(unitStayRange, "unitStayRange"); - for(int i=0; iwriteSint32(clearingRessources[i], oss.str().c_str()); - } - stream->writeSint32(minLevelToFlag, "minLevelToFlag"); - - // Building Specific - for (int i=0; iwriteSint32(localRessource[i], oss.str().c_str()); - } - - // quality parameters - stream->writeSint32(hp, "hp"); - - // prefered parameters - stream->writeSint32(productionTimeout, "productionTimeout"); - stream->writeSint32(totalRatio, "totalRatio"); - for (int i=0; iwriteSint32(ratio[i], oss.str().c_str()); - } - { - std::ostringstream oss; - oss << "percentUsed[" << i << "]"; - stream->writeSint32(percentUsed[i], oss.str().c_str()); - } - } - - stream->writeUint32(receiveRessourceMask, "receiveRessourceMask"); - stream->writeUint32(sendRessourceMask, "sendRessourceMask"); - - stream->writeUint32(shootingStep, "shootingStep"); - stream->writeSint32(shootingCooldown, "shootingCooldown"); - stream->writeSint32(bullets, "bullets"); - - // type - stream->writeUint32(typeNum, "typeNum"); - // we drop type - - stream->writeUint32(seenByMask, "seenByMask"); - - stream->writeLeaveSection(); -} - -void Building::loadCrossRef(GAGCore::InputStream *stream, BuildingsTypes *types, Team *owner, Sint32 versionMinor) -{ - stream->readEnterSection("Building"); - fprintf(logFile, "loadCrossRef (%d)\n", gid); - - // units - maxUnitInside = stream->readSint32("maxUnitInside"); - assert(maxUnitInside < 65536); - - unsigned nbWorking = stream->readUint32("nbWorking"); - fprintf(logFile, " nbWorking=%d\n", nbWorking); - unitsWorking.clear(); - for (unsigned i=0; imyUnits[Unit::GIDtoID(stream->readUint16(oss.str().c_str()))]; - assert(unit); - unitsWorking.push_front(unit); - } - - subscriptionWorkingTimer = stream->readSint32("subscriptionWorkingTimer"); - maxUnitWorking = stream->readSint32("maxUnitWorking"); - maxUnitWorkingPreferred = stream->readSint32("maxUnitWorkingPreferred"); - if(versionMinor>=65) - maxUnitWorkingPrevious = stream->readSint32("maxUnitWorkingPrevious"); - else - maxUnitWorkingPrevious = maxUnitWorkingPreferred; - if(versionMinor>=70) - maxUnitWorkingFuture = stream->readSint32("maxUnitWorkingFuture"); - maxUnitWorkingLocal = maxUnitWorking; - desiredMaxUnitWorking = maxUnitWorking; - - if(versionMinor>=74 && versionMinor<77) - { - stream->readSint32("unitsFailingRequirements"); - } - else if(versionMinor>=77) - { - stream->readEnterSection("unitsFailingRequirements"); - for(int i=0; ireadEnterSection(i); - unitsFailingRequirements[i]=stream->readUint32("unitsFailingRequirements"); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - } - - unsigned nbInside = stream->readUint32("nbInside"); - fprintf(logFile, " nbInside=%d\n", nbInside); - unitsInside.clear(); - for (unsigned i=0; imyUnits[Unit::GIDtoID(stream->readUint16(oss.str().c_str()))]; - assert(unit); - unitsInside.push_front(unit); - } - - if (versionMinor>=80) - { - unsigned nbHarvesting = stream->readUint32("nbHarvesting"); - fprintf(logFile, " nbHarvesting=%d\n", nbHarvesting); - unitsHarvesting.clear(); - for (unsigned i=0; imyUnits[Unit::GIDtoID(stream->readUint16(oss.str().c_str()))]; - assert(unit); - unitsHarvesting.push_front(unit); - } - } - - stream->readLeaveSection(); -} - -void Building::saveCrossRef(GAGCore::OutputStream *stream) -{ - unsigned i; - - stream->writeEnterSection("Building"); - fprintf(logFile, "saveCrossRef (%d)\n", gid); - - // units - stream->writeSint32(maxUnitInside, "maxUnitInside"); - //TODO: std::list::size() is O(n). We should investigate - //if our intense use of this has an impact on overall performance. - //steph and nuage suggested to store and update size in a variable - //what is faster but also more error prone. - stream->writeUint32(unitsWorking.size(), "nbWorking"); - fprintf(logFile, " nbWorking=%zd\n", unitsWorking.size()); - i = 0; - for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) - { - assert(*it); - assert(owner->myUnits[Unit::GIDtoID((*it)->gid)]); - std::ostringstream oss; - oss << "unitsWorking[" << i++ << "]"; - stream->writeUint16((*it)->gid, oss.str().c_str()); - } - - stream->writeSint32(subscriptionWorkingTimer, "subscriptionWorkingTimer"); - stream->writeSint32(maxUnitWorking, "maxUnitWorking"); - stream->writeSint32(maxUnitWorkingPreferred, "maxUnitWorkingPreferred"); - stream->writeSint32(maxUnitWorkingPrevious, "maxUnitWorkingPrevious"); - stream->writeSint32(maxUnitWorkingFuture, "maxUnitWorkingFuture"); - - stream->writeEnterSection("unitsFailingRequirements"); - for(int i=0; iwriteEnterSection(i); - stream->writeUint32(unitsFailingRequirements[i], "unitsFailingRequirements"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - - stream->writeUint32(unitsInside.size(), "nbInside"); - fprintf(logFile, " nbInside=%zd\n", unitsInside.size()); - i = 0; - for (std::list::iterator it=unitsInside.begin(); it!=unitsInside.end(); ++it) - { - assert(*it); - assert(owner->myUnits[Unit::GIDtoID((*it)->gid)]); - std::ostringstream oss; - oss << "unitsInside[" << i++ << "]"; - stream->writeUint16((*it)->gid, oss.str().c_str()); - } - - stream->writeUint32(unitsHarvesting.size(), "nbHarvesting"); - fprintf(logFile, " nbHarvesting=%zd\n", unitsHarvesting.size()); - i = 0; - for (std::list::iterator it=unitsHarvesting.begin(); it!=unitsHarvesting.end(); ++it) - { - assert(*it); - std::ostringstream oss; - oss << "unitsHarvesting[" << i++ << "]"; - stream->writeUint16((*it)->gid, oss.str().c_str()); - } - - stream->writeLeaveSection(); -} - -bool Building::isRessourceFull(void) -{ - for (int i=0; imultiplierRessource[i]<=type->maxRessource[i]) - return false; - } - return true; -} - -int Building::neededRessource(void) -{ - Sint32 minProportion=0x7FFFFFFF; - int minType=-1; - int deci=syncRand()%MAX_RESSOURCES; - for (int ib=0; ibmaxRessource[i]; - if (maxr) - { - Sint32 proportion=(ressources[i]<<16)/maxr; - if (proportionmaxRessource[ri] - ressources[ri])) / (type->multiplierRessource[ri] * 3); - for (std::list::iterator ui = unitsWorking.begin(); ui != unitsWorking.end(); ++ui) - if ((*ui)->destinationPurpose >= 0) - { - assert((*ui)->destinationPurpose < MAX_NB_RESSOURCES); - needs[(*ui)->destinationPurpose]--; - } -} - -void Building::computeWishedRessources() -{ - // we balance the system with Units working on it: - for (int ri = 0; ri < MAX_NB_RESSOURCES; ri++) - wishedResources[ri] = (4 * (type->maxRessource[ri] - ressources[ri])) / (type->multiplierRessource[ri] * 3); - for (std::list::iterator ui = unitsWorking.begin(); ui != unitsWorking.end(); ++ui) - if ((*ui)->destinationPurpose >= 0) - { - assert((*ui)->destinationPurpose < MAX_NB_RESSOURCES); - wishedResources[(*ui)->destinationPurpose]--; - } -} - -int Building::neededRessource(int r) -{ - assert(r >= 0); - int need = type->maxRessource[r] - ressources[r] + 1 - type->multiplierRessource[r]; - return std::max(need,0); -} - - -int Building::totalWishedRessource() -{ - int sum=0; - for (int ri = 0; ri < MAX_NB_RESSOURCES; ri++) - sum += wishedResources[ri]; - return sum; -} - - - -void Building::launchConstruction(Sint32 unitWorking, Sint32 unitWorkingFuture) -{ - if ((buildingState==ALIVE) && (!type->isBuildingSite)) - { - if (hphpMax) - { - if ((type->prevLevel==-1) || !isHardSpaceForBuildingSite(REPAIR)) - return; - constructionResultState=REPAIR; - } - else - { - if ((type->nextLevel==-1) || !isHardSpaceForBuildingSite(UPGRADE)) - return; - constructionResultState=UPGRADE; - } - - owner->removeFromAbilitiesLists(this); - - // We remove all units who are going to the building: - // Notice that the algotithm is not fast but clean. - std::list unitsToRemove; - for (std::list::iterator it=unitsInside.begin(); it!=unitsInside.end(); ++it) - { - Unit *u=*it; - assert(u); - int d=u->displacement; - if ((d!=Unit::DIS_INSIDE)&&(d!=Unit::DIS_ENTERING_BUILDING)&&(d!=Unit::DIS_EXITING_BUILDING)) - { - u->standardRandomActivity(); - unitsToRemove.push_front(u); - } - } - - for (std::list::iterator it=unitsToRemove.begin(); it!=unitsToRemove.end(); ++it) - { - Unit *u=*it; - assert(u); - unitsInside.remove(u); - } - - maxUnitWorkingPrevious = maxUnitWorking; - buildingState=WAITING_FOR_CONSTRUCTION; - maxUnitWorkingLocal=0; - maxUnitWorking=0; - maxUnitInside=0; - updateCallLists(); - updateUnitsWorking(); // To remove all units working. - updateUnitsHarvesting(); // To remove all units working. - //following reassigns units to work on upgrade, certain buildings will - //glitch if units are not unassigned and then reassigned like this - maxUnitWorking = unitWorking; - maxUnitWorkingLocal = maxUnitWorking; - maxUnitWorkingPreferred = maxUnitWorking; - maxUnitWorkingFuture = unitWorkingFuture; - updateConstructionState(); // To switch to a real building site, if all units have been freed from building. - } -} - -void Building::cancelConstruction(Sint32 unitWorking) -{ - Sint32 recoverTypeNum=typeNum; - BuildingType *recoverType=type; - - if (type->isBuildingSite) - { - assert(buildingState==ALIVE); - int targetLevelTypeNum=-1; - - if (constructionResultState==UPGRADE) - targetLevelTypeNum=type->prevLevel; - else if (constructionResultState==REPAIR) - targetLevelTypeNum=type->nextLevel; - else - assert(false); - - if (targetLevelTypeNum!=-1) - { - recoverTypeNum=targetLevelTypeNum; - recoverType=globalContainer->buildingsTypes.get(targetLevelTypeNum); - } - else - assert(false); - } - else if (buildingState==WAITING_FOR_CONSTRUCTION_ROOM) - { - if(constructionResultState == UPGRADE) - removeForbiddenZoneFromUpgradeArea(); - - owner->buildingsTryToBuildingSiteRoom.remove(this); - buildingState=ALIVE; - } - else if (buildingState==WAITING_FOR_CONSTRUCTION) - { - buildingState=ALIVE; - } - else - { - // Congratulation, you have managed to click "cancel upgrade" - // when the building upgrade" was already canceled. - return; - } - - constructionResultState=NO_CONSTRUCTION; - - if (!type->isVirtual) - owner->map->setBuilding(posX, posY, type->width, type->height, NOGBID); - int midPosX=posX-type->decLeft; - int midPosY=posY-type->decTop; - owner->removeFromAbilitiesLists(this); - owner->prestige-=type->prestige; - typeNum=recoverTypeNum; - type=recoverType; - owner->prestige+=type->prestige; - owner->addToStaticAbilitiesLists(this); - - //Update the pointer ressources to the newly changed type - updateRessourcesPointer(); - - posX=midPosX+type->decLeft; - posY=midPosY+type->decTop; - posXLocal=posX; - posYLocal=posY; - - if (!type->isVirtual) - owner->map->setBuilding(posX, posY, type->width, type->height, gid); - - maxUnitWorking=maxUnitWorkingPrevious; - maxUnitWorkingLocal=maxUnitWorking; //maxUnitWorking; - maxUnitInside=type->maxUnitInside; - updateCallLists(); - updateUnitsWorking(); - // no unit harvesting at that point - - if (hp>=type->hpInit) - hp=type->hpInit; - - productionTimeout=type->unitProductionTime; - - if (type->unitProductionTime) - owner->swarms.push_back(this); - if (type->shootingRange) - owner->turrets.push_back(this); - if (type->canExchange) - owner->canExchange.push_back(this); - if (type->isVirtual) - owner->virtualBuildings.push_back(this); - if (type->zonable[WORKER]) - owner->clearingFlags.push_back(this); - - totalRatio=0; - - for (int i=0; ibuildingsWaitingForDestruction.push_front(this); - } -} - -void Building::cancelDelete(void) -{ - buildingState=ALIVE; - maxUnitWorking=maxUnitWorkingPrevious; - maxUnitWorkingLocal=maxUnitWorking; - maxUnitInside=type->maxUnitInside; - updateCallLists(); - updateUnitsWorking(); - // we do not update units harvesting because there is none at this point - // we do not update owner->buildingsWaitingForDestruction because Team::syncStep will remove this building from the list -} - - -void Building::updateCallLists(void) -{ - if (buildingState==DEAD) - return; - desiredMaxUnitWorking = desiredNumberOfWorkers(); - bool ressourceFull=isRessourceFull(); - if (ressourceFull && !(type->canExchange && owner->openMarket())) - { - // Then we don't need anyone more to fill me, if I'm still in the call list for units, - // remove me - if(callListState != 0) - { - owner->remove_building_needing_work(this, oldPriority); - callListState=0; - oldPriority = priority; - } - } - - if (unitsWorking.size()<(unsigned)desiredMaxUnitWorking) - { - if (buildingState==ALIVE) - { - // I need units, if I am not in the call lists, add me - if(callListState != 1) - { - owner->add_building_needing_work(this, priority); - callListState = 1; - oldPriority = priority; - } - // if i am in the call lists, update my then my position will need to be updated - else if(callListState == 1 && oldPriority == priority) - { - owner->remove_building_needing_work(this, oldPriority); - owner->add_building_needing_work(this, priority); - oldPriority = priority; - } - } - } - else - { - if(callListState != 0) - { - owner->remove_building_needing_work(this, oldPriority); - callListState=0; - oldPriority = priority; - } - } - - if ((signed)unitsInside.size()upgrade[i]) - { - owner->upgrade[i].push_front(this); - inUpgrade[i]=LS_IN; - } - - // this is for food handling - if (type->canFeedUnit) - { - if (ressources[CORN]>(int)unitsInside.size()) - { - if (inCanFeedUnit!=LS_IN) - { - owner->canFeedUnit.push_front(this); - //A Building newly getting available to feed is locked to conversion for 150 frames - canNotConvertUnitTimer=150; - inCanFeedUnit=LS_IN; - } - } - else - { - if (inCanFeedUnit!=LS_OUT) - { - owner->canFeedUnit.remove(this); - inCanFeedUnit=LS_OUT; - } - } - } - - // this is for Unit healing - if (type->canHealUnit && inCanHealUnit!=LS_IN) - { - owner->canHealUnit.push_front(this); - inCanHealUnit=LS_IN; - } - } - else - { - // delete itself from all Call lists - for (int i=0; iupgrade[i]) - { - owner->upgrade[i].remove(this); - inUpgrade[i]=LS_OUT; - } - - if (type->canFeedUnit && inCanFeedUnit!=LS_OUT) - { - owner->canFeedUnit.remove(this); - inCanFeedUnit=LS_OUT; - } - if (type->canHealUnit && inCanHealUnit!=LS_OUT) - { - owner->canHealUnit.remove(this); - inCanHealUnit=LS_OUT; - } - } -} - -void Building::updateConstructionState(void) -{ - if (buildingState==DEAD) - return; - - if ((buildingState==WAITING_FOR_CONSTRUCTION) || (buildingState==WAITING_FOR_CONSTRUCTION_ROOM)) - { - if (!isHardSpaceForBuildingSite()) - { - //this is semi-faulty code and needs to be fixed later - //anytime a building is upgraded but unable to do so it reverts to - //one worker working instead of previous value - cancelConstruction(1); - } - else if ((unitsWorking.size()==0) && (unitsInside.size()==0)) - { - if (buildingState!=WAITING_FOR_CONSTRUCTION_ROOM) - { - buildingState=WAITING_FOR_CONSTRUCTION_ROOM; - owner->buildingsTryToBuildingSiteRoom.push_front(this); - if(constructionResultState == UPGRADE) - addForbiddenZoneToUpgradeArea(); - if (verbose) - printf("bgid=%d, inserted in buildingsTryToBuildingSiteRoom\n", gid); - } - } - else if (verbose) - printf("bgid=%d, Building wait for upgrade, uws=%lu, uis=%lu.\n", gid, (unsigned long)unitsWorking.size(), (unsigned long)unitsInside.size()); - } -} - -void Building::updateBuildingSite(void) -{ - assert(type->isBuildingSite); - - if (isRessourceFull() && (buildingState!=WAITING_FOR_DESTRUCTION)) - { - // we really uses the resources of the buildingsite: - for(int i=0; imaxRessource[i]; - - owner->prestige-=type->prestige; - typeNum=type->nextLevel; - type=globalContainer->buildingsTypes.get(type->nextLevel); - assert(constructionResultState!=NO_CONSTRUCTION); - constructionResultState=NO_CONSTRUCTION; - owner->prestige+=type->prestige; - - //Update the pointer ressources to the newly changed type - updateRessourcesPointer(); - - - //now that building is complete clear the workers - for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); it++) - (*it)->standardRandomActivity(); - unitsWorking.clear(); - - if (type->maxUnitWorking) - { - maxUnitWorking = maxUnitWorkingFuture; - maxUnitWorkingFuture = 0; - } - else - maxUnitWorking=0; - maxUnitWorkingLocal=maxUnitWorking; - - // The working units still works for us, but - // we don't have any unit in buildings - assert(unitsInside.size()==0); - maxUnitInside=type->maxUnitInside; - - if (hp>=type->hpInit) - hp=type->hpInit; - - productionTimeout=type->unitProductionTime; - if (type->unitProductionTime) - owner->swarms.push_back(this); - if (type->shootingRange) - owner->turrets.push_back(this); - if (type->canExchange) - owner->canExchange.push_back(this); - if (type->isVirtual) - owner->virtualBuildings.push_back(this); - if (type->zonable[WORKER]) - owner->clearingFlags.push_back(this); - - setMapDiscovered(); - boost::shared_ptr event(new BuildingCompletedEvent(owner->game->stepCounter, getMidX(), getMidY(), shortTypeNum)); - owner->pushGameEvent(event); - - // we need to do an update again - updateCallLists(); - updateUnitsWorking(); - // no unit harvesting at that point - } -} - - - -void Building::updateUnitsWorking(void) -{ - if (maxUnitWorking==0) - { - // This is only a special optimisation case: - for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) - (*it)->standardRandomActivity(); - unitsWorking.clear(); - } - else - { - while (unitsWorking.size()>(unsigned)desiredMaxUnitWorking) - { - int maxDistSquare=0; - - Unit *fu=NULL; - std::list::iterator ittemp; - - // First choice: free an unit who has a not needed ressource.. - for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end();) - { - int r=(*it)->carriedRessource; - if (r>=0 && !neededRessource(r)) - { - fu=(*it); - fu->standardRandomActivity(); - it=unitsWorking.erase(it); - continue; - } else { - ++it; - } - } - if(fu!=NULL) continue; - // Second choice: free an unit who has no ressource.. - if (fu==NULL) - { - int minDistSquare=INT_MAX; - for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) - { - int r=(*it)->carriedRessource; - if (r<0) - { - int tx = posX; - int ty = posY; - if((*it)->targetX != -1) - { - tx = (*it)->targetX; - ty = (*it)->targetY; - } - int newDistSquare=distSquare((*it)->posX, (*it)->posY, tx, ty); - if (newDistSquare::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) - { - int newDistSquare=distSquare((*it)->posX, (*it)->posY, posX, posY); - if (newDistSquare>maxDistSquare) - { - maxDistSquare=newDistSquare; - fu=(*it); - ittemp=it; - } - } - - if (fu!=NULL) - { - if (verbose) - printf("bgid=%d, we free the unit gid=%d\n", gid, fu->gid); - // We free the unit. - fu->standardRandomActivity(); - unitsWorking.erase(ittemp); - } - else - break; - } - } -} - -void Building::updateUnitsHarvesting(void) -{ - // if we are not alive or has not vision, remove all units harvesting from this building - for (std::list::iterator it=unitsHarvesting.begin(); it!=unitsHarvesting.end();) - { - std::list::iterator tmpIt = it; - Unit* u = *tmpIt; - it++; - - // if the building is not available to fetch from (invisible or broken) - if ((buildingState != ALIVE) || ((owner->sharedVisionExchange & u->owner->me) == 0)) - { - // cancel the task u were just doing - u->attachedBuilding->removeUnitFromWorking(u); - // cancel fetching resources here - removeUnitFromHarvesting(u); - // behave randomly - u->standardRandomActivity(); - // TODO: replacing the remove by an erase should be a lot faster but - // it causes the game to crash when a market gets destroyed. No idea - // why. Actually there's no point bothering about this here as this - // method is not performance critical but still it's weired to me - // why it doesn't work the other way round. - // unitsHarvesting.erase(tmpIt); - } - } -} - -void Building::update(void) -{ - computeWishedRessources(); - if (buildingState==DEAD) - return; - desiredMaxUnitWorking = desiredNumberOfWorkers(); - updateCallLists(); - updateUnitsWorking(); - updateUnitsHarvesting(); - updateConstructionState(); - if (type->isBuildingSite) - updateBuildingSite(); -} - -void Building::setMapDiscovered(void) -{ - assert(type); - int vr=type->viewingRange; - if (type->canExchange) - owner->map->setMapDiscovered(posX-vr, posY-vr, type->width+vr*2, type->height+vr*2, owner->sharedVisionExchange); - else if (type->canFeedUnit) - owner->map->setMapDiscovered(posX-vr, posY-vr, type->width+vr*2, type->height+vr*2, owner->sharedVisionFood); - else - owner->map->setMapDiscovered(posX-vr, posY-vr, type->width+vr*2, type->height+vr*2, owner->sharedVisionOther); - owner->map->setMapExploredByBuilding(posX-vr, posY-vr, type->width+vr*2, type->height+vr*2, owner->teamNumber); -} - -void Building::getRessourceCountToRepair(int ressources[BASIC_COUNT]) -{ - assert(!type->isBuildingSite); - int repairLevelTypeNum=type->prevLevel; - BuildingType *repairBt=globalContainer->buildingsTypes.get(repairLevelTypeNum); - assert(repairBt); - Sint32 fDestructionRatio=(hp<<16)/type->hpMax; - Sint32 fTotErr=0; - for (int i=0; imaxRessource[i]; - int iVal=(fVal>>16); - fTotErr+=fVal&65535; - if (fTotErr>=65536) - { - fTotErr-=65536; - iVal++; - } - ressources[i]=repairBt->maxRessource[i]-iVal; - } -} - -bool Building::tryToBuildingSiteRoom(void) -{ - int midPosX=posX-type->decLeft; - int midPosY=posY-type->decTop; - - int targetLevelTypeNum=-1; - if (constructionResultState==UPGRADE) - targetLevelTypeNum=type->nextLevel; - else if (constructionResultState==REPAIR) - targetLevelTypeNum=type->prevLevel; - else - assert(false); - - if (targetLevelTypeNum==-1) - return false; - - BuildingType *targetBt=globalContainer->buildingsTypes.get(targetLevelTypeNum); - int newPosX=midPosX+targetBt->decLeft; - int newPosY=midPosY+targetBt->decTop; - - int newWidth=targetBt->width; - int newHeight=targetBt->height; - - bool isRoom=owner->map->isFreeForBuilding(newPosX, newPosY, newWidth, newHeight, gid); - if (isRoom) - { - if(constructionResultState == UPGRADE) - removeForbiddenZoneFromUpgradeArea(); - - // OK, we have found enough room to expand our building-site, then we set-up the building-site. - if (constructionResultState==REPAIR) - { - Sint32 fDestructionRatio=(hp<<16)/type->hpMax; - Sint32 fTotErr=0; - for (int i=0; imaxRessource[i]; - int iVal=(fVal>>16); - fTotErr+=fVal&65535; - if (fTotErr>=65536) - { - fTotErr-=65536; - iVal++; - } - ressources[i]=iVal; - } - } - - if (!type->isVirtual) - { - owner->map->setBuilding(posX, posY, type->decLeft, type->decLeft, NOGBID); - owner->map->setBuilding(newPosX, newPosY, newWidth, newHeight, gid); - } - - - owner->prestige-=type->prestige; - typeNum=targetLevelTypeNum; - type=targetBt; - owner->prestige+=type->prestige; - - //Update the pointer ressources to the newly changed type - updateRessourcesPointer(); - - buildingState=ALIVE; - owner->addToStaticAbilitiesLists(this); - - // towers may already have some stone! - if (constructionResultState==UPGRADE) - for (int i=0; imaxRessource[i]; - if (res>0 && resMax>0) - { - if (res>resMax) - res=resMax; - if (verbose) - printf("using %d ressources[%d] for fast constr (hp+=%d)\n", res, i, res*type->hpInc); - hp+=res*type->hpInc; - } - } - - // units - if (verbose) - printf("bgid=%d, uses maxUnitWorkingPreferred=%d\n", gid, maxUnitWorkingPreferred); - maxUnitWorking=maxUnitWorkingPreferred; - maxUnitWorkingLocal=maxUnitWorking; - maxUnitInside=type->maxUnitInside; - updateCallLists(); - updateUnitsWorking(); - // no unit harvesting at that point - - // position - posX=newPosX; - posY=newPosY; - posXLocal=posX; - posYLocal=posY; - - // flag usefull : - unitStayRange=type->defaultUnitStayRange; - unitStayRangeLocal=unitStayRange; - - // quality parameters - // hp=type->hpInit; // (Uint16) - - // prefered parameters - productionTimeout=type->unitProductionTime; - - totalRatio=0; - for (int i=0; idecLeft; - int midPosY=posY-type->decTop; - - int targetLevelTypeNum=-1; - targetLevelTypeNum=type->nextLevel; - - BuildingType *targetBt=globalContainer->buildingsTypes.get(targetLevelTypeNum); - int newPosX=midPosX+targetBt->decLeft; - int newPosY=midPosY+targetBt->decTop; - int newWidth=targetBt->width; - int newHeight=targetBt->height; - - for(int x=newPosX; x<(newPosX+newWidth); ++x) - { - for(int y=newPosY; y<(newPosY+newHeight); ++y) - { - owner->map->addForbidden(x, y, owner->teamNumber); - } - } - if(owner == owner->game->gui->getLocalTeam()) - owner->map->computeLocalForbidden(owner->teamNumber); - owner->map->updateForbiddenGradient(owner->teamNumber); -} - - - -void Building::removeForbiddenZoneFromUpgradeArea(void) -{ - int midPosX=posX-type->decLeft; - int midPosY=posY-type->decTop; - - int targetLevelTypeNum=-1; - targetLevelTypeNum=type->nextLevel; - - BuildingType *targetBt=globalContainer->buildingsTypes.get(targetLevelTypeNum); - int newPosX=midPosX+targetBt->decLeft; - int newPosY=midPosY+targetBt->decTop; - int newWidth=targetBt->width; - int newHeight=targetBt->height; - - for(int x=newPosX; x<(newPosX+newWidth); ++x) - { - for(int y=newPosY; y<(newPosY+newHeight); ++y) - { - owner->map->removeForbidden(x, y, owner->teamNumber); - } - } - if(owner == owner->game->gui->getLocalTeam()) - owner->map->computeLocalForbidden(owner->teamNumber); - owner->map->updateForbiddenGradient(owner->teamNumber); -} - - - -bool Building::isHardSpaceForBuildingSite(void) -{ - return isHardSpaceForBuildingSite(constructionResultState); -} - -bool Building::isHardSpaceForBuildingSite(ConstructionResultState constructionResultState) -{ - int tltn=-1; - if (constructionResultState==UPGRADE) - tltn=type->nextLevel; - else if (constructionResultState==REPAIR) - tltn=type->prevLevel; - else - assert(false); - - if (tltn==-1) - return true; - BuildingType *bt=globalContainer->buildingsTypes.get(tltn); - int x=posX+bt->decLeft-type->decLeft; - int y=posY+bt->decTop -type->decTop ; - int w=bt->width; - int h=bt->height; - - if (bt->isVirtual) - return true; - return owner->map->isHardSpaceForBuilding(x, y, w, h, gid); -} - -bool Building::fullInside(void) -{ - if ((type->canFeedUnit) && (ressources[CORN]<=(int)unitsInside.size())) - return true; - else - return ((signed)unitsInside.size()>=maxUnitInside); -} - - -int Building::desiredNumberOfWorkers(void) -{ - //If its virtual, than this building is a flag and always gets - //full ressources - if(type->isVirtual) - { - return maxUnitWorking; - } - //Otherwise, this building gets what the user desires, up to a limit of 2 units per 1 needed ressource, - //thus if no ressources are needed, then no units will be working here. - int neededRessourcesSum = 0; - for (size_t ri = 0; ri < MAX_RESSOURCES; ri++) - { - int neededRessources = (type->maxRessource[ri] - ressources[ri]) / type->multiplierRessource[ri]; - if (neededRessources > 0) - neededRessourcesSum += neededRessources; - } - int user_num = maxUnitWorking; - int max_considering_ressources = (4 * neededRessourcesSum) / 3; - return std::min(user_num, max_considering_ressources); -} - - -void Building::step(void) -{ - computeWishedRessources(); - - updateCallLists(); - if(underAttackTimer>0) - underAttackTimer--; - if(canNotConvertUnitTimer>0) - canNotConvertUnitTimer--; - // NOTE : Unit needs to update itself when it is in a building -} - - -bool Building::subscribeToBringRessourcesStep() -{ - for(int i=0; imap; - for(int i=0; i>2 - (d+dr))*500+100/harvest - */ - /* - int maxValue=-INT_MAX; - for(int n=0; nmyUnits[n]; - if(unit==NULL - || unit->activity != Unit::ACT_RANDOM - || unit->medical != Unit::MED_FREE - || !unit->performance[HARVEST]) - continue; - if(!canUnitWorkHere(unit)) - continue; - - int r=unit->carriedRessource; - int dist; - if(!map->buildingAvailable(this, unit->performance[SWIM], unit->posX, unit->posY, &dist)) - { - //std::cout << ":" << std::flush; - continue; //also to fill dist - } - int distUnitRessource; - int nr; - for (nr=0; nr0) - { - if(map->ressourceAvailable(owner->teamNumber, nr, unit->performance[SWIM], unit->posX, unit->posY, &distUnitRessource)) //to fill distUnitRessource - break; - else - continue; - } - } - if (neededRessource(nr)<=0) - { - //std::cout << "," << std::flush; - continue; - } - int rightRes=(((r>=0) && neededRessource(r))?1:0); - if(rightRes==1 && (unit->hungry-unit->trigHungry)/unit->race->hungryness/2hungry-unit->trigHungry)/unit->race->hungryness/2<(dist+distUnitRessource)) - continue; - int noRes=(r<0?1:0); - int wrongRes=(((r>=0) && !neededRessource(r))?1:0); - int value = ( - rightRes*10*(512-dist)+ - noRes*8*(512-dist-distUnitRessource)+ - wrongRes*2*(512-dist-distUnitRessource) - )*(unit->level[WALK]+1)+ - //enoughTimeLeft*5000+ - 50*(unit->level[HARVEST]+1)+ - (unit->level[SWIM]>0?-200:0);//swimmer's penalty to keep them free for swimmer tasks - //std::cout << "d" << dist << " dr" << distUnitRessource << " rr" << rightRes << " nr" << noRes << " wr" << wrongRes << " wa" << unit->level[WALK] << " ha" << unit->level[HARVEST] << " va" << value << std::endl << std::flush; - unit->destinationPurpose=(rightRes>0?r:nr); - fprintf(logFile, "[%d] bdp1 destinationPurpose=%d\n", unit->gid, unit->destinationPurpose); - if (value>maxValue) - { - maxValue=value; - choosen=unit; - } - } -*/ - // Compute the list of candidate units - Unit* possibleUnits[Unit::MAX_COUNT]; - int distances[Unit::MAX_COUNT]; - int resource[Unit::MAX_COUNT]; - int teamNumber=owner->teamNumber; - for(int n=0; nmyUnits[n]; - if(unit) - { - if(!unit->performance[HARVEST]) - { - continue; - } - else if(unit->attachedBuilding == this && unit->activity == Unit::ACT_FILLING) - { - continue; - } - else if(unit->activity != Unit::ACT_RANDOM || unit->medical != Unit::MED_FREE) - { - unitsFailingRequirements[UnitNotAvailable] += 1; - } - else if(!canUnitWorkHere(unit)) - { - unitsFailingRequirements[UnitTooLowLevel] += 1; - } - else - { - int distBuilding=0; - int timeLeft=(unit->hungry-unit->trigHungry)/unit->race->hungryness; - bool canSwim=unit->performance[SWIM]; - if(!map->buildingAvailable(this, canSwim, unit->posX, unit->posY, &distBuilding)) - { - unitsFailingRequirements[UnitCantAccessBuilding] += 1; - } - else if(distBuilding >= timeLeft) - { - unitsFailingRequirements[UnitTooFarFromBuilding] += 1; - } - else - { - int unitr = unit->carriedRessource; - if((unitr>=0) && neededRessource(unitr)) - { - possibleUnits[n] = unit; - distances[n] = distBuilding; - resource[n] = unitr; - } - else - { - int bestDist = 100000; - int bestResource = -1; - bool regularFound=false; - bool fruitFound=false; - bool regularFoundTooFar=false; - bool fruitFoundTooFar=false; - int x=unit->posX; - int y=unit->posY; - for(int r=0; r0) - { - if(rressourceAvailable(teamNumber, r, canSwim, x, y, &distResource)) - { - if(distResourcecarriedRessource; - int timeLeft=(unit->hungry-unit->trigHungry)/unit->race->hungryness; - if ((r>=0) && neededRessource(r)) - { - int dist = distances[n]; - int value=dist-(timeLeft>>1); - int level = unit->level[HARVEST]*10 + unit->level[WALK]; - unit->destinationPurpose=r; - if ((level>maxLevel) || (level==maxLevel && valuecarriedRessource<0) - { - int r = resource[n]; - int value=distances[n]; - int level = unit->level[HARVEST]*10 + unit->level[WALK]; - if ((level>maxLevel) || (level==maxLevel && valuedestinationPurpose=r; - } - } - } - } - - //Third: we look for an unit who is carrying an unwanted resource: - if (choosen==NULL) - { - for(int n=0; ncarriedRessource; - if ((r2>=0) && !neededRessource(r2)) - { - int r = resource[n]; - int value=distances[n]; - int level = unit->level[HARVEST]*10 + unit->level[WALK]; - if ((level>maxLevel) || (level==maxLevel && valuedestinationPurpose=r; - } - } - } - } - if (choosen) - { - unitsWorking.push_back(choosen); - choosen->subscriptionSuccess(this, false); - hired=true; - } - } - - updateCallLists(); - - if (verbose) - printf(" ...done\n"); - return hired; -} - -bool Building::subscribeForFlagingStep() -{ - if (buildingState==DEAD) - { - for(int i=0; i32) - { - for(int i=0; imyUnits[n]; - if(unit) - { - if(unit->attachedBuilding == this) - { - continue; - } - else if(type->zonable[EXPLORER] && unit->typeNum != EXPLORER) - { - continue; - } - else if(type->zonable[WORKER] && unit->typeNum != WORKER) - { - continue; - } - else if(type->zonable[WARRIOR] && unit->typeNum != WARRIOR) - { - continue; - } - else if(unit->activity != Unit::ACT_RANDOM || unit->medical != Unit::MED_FREE) - { - unitsFailingRequirements[UnitNotAvailable] += 1; - } - else if(!canUnitWorkHere(unit)) - { - unitsFailingRequirements[UnitTooLowLevel] += 1; - } - else if(type->zonable[WARRIOR] && unit->movement == Unit::MOV_ATTACKING_TARGET) - { - unitsFailingRequirements[UnitNotAvailable] += 1; - } - else - { - int distBuilding=0; - int timeLeft=(unit->hungry-unit->trigHungry)/unit->race->hungryness; - timeLeft*=timeLeft; - int directdist=owner->map->warpDistSquare(unit->posX, unit->posY, posX, posY); - bool canSwim=unit->performance[SWIM]; - if(type->zonable[EXPLORER] && timeLeft < directdist) - { - unitsFailingRequirements[UnitTooFarFromBuilding] += 1; - } - else if(!type->zonable[EXPLORER] && !owner->map->buildingAvailable(this, canSwim, unit->posX, unit->posY, &distBuilding)) - { - unitsFailingRequirements[UnitCantAccessBuilding] += 1; - } - else if(!type->zonable[EXPLORER] && distBuilding >= timeLeft) - { - unitsFailingRequirements[UnitTooFarFromBuilding] += 1; - } - else if(type->zonable[WORKER] && anyRessourceToClear[canSwim]==2) - { - unitsFailingRequirements[UnitCantAccessResource] += 1; - } - else - { - if(type->zonable[EXPLORER]) - distances[n]=directdist; - else - distances[n]=distBuilding; - possibleUnits[n]=unit; - } - } - } - } - - int minValue=INT_MAX; - int minLevel=INT_MAX; - int maxLevel=-INT_MAX; - Unit *choosen=NULL; - - /* To choose a good unit, we get a composition of things: - 1-the closer the unit is, the better it is. - 2-the less the unit is hungry, the better it is. - 3-the more hp the unit has, the better it is. - */ - if (type->zonable[EXPLORER]) - { - for(int n=0; nhungry/unit->race->hungryness; - int hp=(unit->hp<<4)/unit->race->unitTypes[0][0].performance[HP]; - timeLeft*=timeLeft; - hp*=hp; - int dist=distances[n]; - //Use explorers without ground attack first before ones with, so that ground attacking explorers - //are available for more important jobs - int value=dist-2*timeLeft-2*hp; - int level = unit->level[MAGIC_ATTACK_GROUND]; - if ((level < minLevel) || (level==minLevel && valuezonable[WARRIOR]) - { - for(int n=0; nhungry/unit->race->hungryness; - int hp=(unit->hp<<4)/unit->race->unitTypes[0][0].performance[HP]; - int dist = distances[n]; - int value=dist-2*timeLeft-2*hp; - //We want to maximize the attack level, use higher level soldeirs first - int level=unit->performance[ATTACK_SPEED]*unit->getRealAttackStrength(); - if ((level > maxLevel) || (level==maxLevel && valuezonable[WORKER]) - { - for(int n=0; nhungry-unit->trigHungry)/unit->race->hungryness; - int hp=(unit->hp<<4)/unit->race->unitTypes[0][0].performance[HP]; - int dist = distances[n]; - int value=dist-timeLeft-hp; - int level = unit->level[HARVEST]; - //We want to minimize the level of harvesting units, so that the higher level - //units are available for more important work. - if ((level < minLevel) || (level==minLevel && valuesubscriptionSuccess(this, false); - hired=true; - } - else - break; - } - - updateCallLists(); - - subscriptionWorkingTimer=0; - } - return hired; -} - - -void Building::subscribeUnitForInside(Unit* unit) -{ - unitsInside.push_back(unit); - unit->subscriptionSuccess(this, true); - updateCallLists(); -} - - -void Building::swarmStep(void) -{ - // increase HP - if (hphpMax) - hp++; - assert(NB_UNIT_TYPE==3); - if ((ressources[CORN]>=type->ressourceForOneUnit)&&(ratio[0]|ratio[1]|ratio[2])) - productionTimeout--; - - if (productionTimeout<0) - { - // We find the kind of unit we have to create: - Sint32 fProportion; - Sint32 fMinProportion=0x7FFFFFFF; - int minType=-1; - for (int i=0; i=0); - assert(minType=NB_UNIT_TYPE) - minType=0; - - // We get the unit UnitType: - int posX, posY, dx, dy; - UnitType *ut=owner->race.getUnitType(minType, 0); - - // Is there a place to exit ? - bool exitFound; - if (ut->performance[FLY]) - exitFound=findAirExit(&posX, &posY, &dx, &dy); - else - exitFound=findGroundExit(&posX, &posY, &dx, &dy, ut->performance[SWIM]); - if (exitFound) - { - Unit * u=owner->game->addUnit(posX, posY, owner->teamNumber, minType, 0, 0, dx, dy); - if (u) - { - ressources[CORN]-=type->ressourceForOneUnit; - updateCallLists(); - - u->activity=Unit::ACT_RANDOM; - u->displacement=Unit::DIS_RANDOM; - u->movement=Unit::MOV_EXITING_BUILDING; - u->speed=u->performance[u->action]; - - productionTimeout=type->unitProductionTime; - - // We update percentUsed[] - percentUsed[minType]++; - - bool allDone=true; - for (int i=0; iteamNumber); - } - } -} - - -void Building::turretStep(Uint32 stepCounter) -{ - // create bullet from stones in stock - if (ressources[STONE]>0 && (bullets<=(type->maxBullets-type->multiplierStoneToBullets))) - { - ressources[STONE]--; - bullets += type->multiplierStoneToBullets; - - // we need to be stone-feeded - updateCallLists(); - } - - // compute cooldown - if (shootingCooldown > 0) - { - shootingCooldown -= type->shootRythme; - return; - } - - // if we have no bullet, don't try to shoot - if (bullets <= 0) - return; - - //for some reason, any turret that is not 2x2 makes no sense at all to the game - assert(type->width ==2); - assert(type->height==2); - - int range = type->shootingRange; - shootingStep = (shootingStep+1)&0x7; - - Uint32 enemies = owner->enemies; - Map *map = owner->map; - assert(map); - - // the type of target we have found - enum TargetType - { - TARGETTYPE_NONE, - TARGETTYPE_BUILDING, - TARGETTYPE_WORKER, - TARGETTYPE_WARRIOR, - TARGETTYPE_EXPLORER, - }; - // The type of the best target we have found up to now - TargetType targetFound = TARGETTYPE_NONE; - // The score of the best target we have found up to now - int bestScore = INT_MIN; - // The number of ticks before the unit may move away - int bestTicks = 0; - // The position of the best target we have found up to now - int bestTargetX = 0, bestTargetY=0; - - for (int i=0; i<=range ; i++) - { - // The number of ticks before the bullet hits the target at range "i". - int ticksToHit = ((i << 5) + ((type->width) << 4)) / (type->shootSpeed>>8); - for (int j=0; j<=i ; j++) - { - for (int k=0; k<8; k++) - { - int targetX, targetY; - switch (k) - { - case 0: - targetX=posX-j; - targetY=posY-i; - break; - case 1: - targetX=posX+j+1; - targetY=posY-i; - break; - case 2: - targetX=posX-j; - targetY=posY+i+1; - break; - case 3: - targetX=posX+j+1; - targetY=posY+i+1; - break; - case 4: - targetX=posX-i; - targetY=posY-j; - break; - case 5: - targetX=posX+i+1; - targetY=posY-j; - break; - case 6: - targetX=posX-i; - targetY=posY+j+1; - break; - case 7: - targetX=posX+i+1; - targetY=posY+j+1; - break; - default: - assert(false); - targetX=0; - targetY=0; - break; - } - int targetGUID = map->getGroundUnit(targetX, targetY); - int airTargetGUID = map->getAirUnit(targetX, targetY); - if (targetGUID != NOGUID) - { - Sint32 otherTeam = Unit::GIDtoTeam(targetGUID); - Sint32 targetID = Unit::GIDtoID(targetGUID); - Uint32 otherTeamMask = 1<game->teams[otherTeam]->myUnits[targetID]; - if ((owner->sharedVisionExchange & otherTeamMask) == 0) - { - int targetTicks = (256 - testUnit->delta) / testUnit->speed; - // skip this unit if it will move away too soon. - if (targetTicks <= ticksToHit) - continue; - // shoot warrior first, then workers if no warrior - if (testUnit->typeNum == WARRIOR) - { - int targetOffense = (testUnit->getRealAttackStrength() * testUnit->performance[ATTACK_SPEED]); // 88 to 1024 - int targetWeakeness = 0; // 0 to 512 - if (testUnit->hp > 0) - { - if (testUnit->hp < type->shootDamage) // hahaha, how mean! - targetWeakeness = 512; - else - targetWeakeness = 256 / testUnit->hp; - } - int targetProximity = 0; // 0 to 512 - if (i <= 0) - targetProximity = 512; - else - targetProximity = (256 / i); - int targetScore = targetOffense + targetWeakeness + targetProximity; - // lower scores are overriden - if (targetScore > bestScore) - { - bestScore = targetScore; - bestTicks = targetTicks; - bestTargetX = targetX; - bestTargetY = targetY; - targetFound = TARGETTYPE_WARRIOR; - } - } - else if ((targetFound != TARGETTYPE_WARRIOR) && (testUnit->typeNum == WORKER)) - { - // adjust score for range - int targetScore = - testUnit->hp; - // lower scores are overriden - if (targetScore > bestScore) - { - bestScore = targetScore; - bestTicks = targetTicks; - bestTargetX = targetX; - bestTargetY = targetY; - targetFound = TARGETTYPE_WORKER; - } - } - } - } - } - //explorers are now priority targets as defined later - - if (airTargetGUID != NOGUID) - { - Sint32 otherTeam = Unit::GIDtoTeam(airTargetGUID); - Sint32 targetID = Unit::GIDtoID(airTargetGUID); - Uint32 otherTeamMask = 1<game->teams[otherTeam]->myUnits[targetID]; - if ((owner->sharedVisionExchange & otherTeamMask) == 0) - { - int targetTicks = (256 - testUnit->delta) / testUnit->speed; - // skip this unit if it will move away too soon. - if (targetTicks <= ticksToHit) - continue; - //Using simple calculation for now (should always shoot ground-attackers first, probably) - // adjust score for range - int targetScore = - testUnit->hp; - // lower scores are overriden - if (targetScore > bestScore) - { - bestScore = targetScore; - bestTicks = targetTicks; - bestTargetX = targetX; - bestTargetY = targetY; - targetFound = TARGETTYPE_EXPLORER; - } - } - } - } - - // shoot building only if no unit is found - if (targetFound == TARGETTYPE_NONE) - { - Uint16 targetGBID = map->getBuilding(targetX, targetY); - if (targetGBID != NOGBID) - { - Sint32 otherTeam = Building::GIDtoTeam(targetGBID); - //int otherID = Building::GIDtoID(targetGBID); - Uint32 otherTeamMask = 1< bestScore) - { - bestScore = targetScore; - bestTicks = 256; - bestTargetX = targetX; - bestTargetY = targetY; - targetFound = TARGETTYPE_BUILDING; - } - } - } - } - } - } - if (targetFound == TARGETTYPE_EXPLORER) - break;//specifying explorers as high priority - } - - if (targetFound != TARGETTYPE_NONE) - { - shootingStep = 0; - - //printf("%d found target found: (%d, %d) \n", gid, targetX, targetY); - Sector *s=owner->map->getSector(getMidX(), getMidY()); - - int px, py; - px=((posX)<<5)+((type->width)<<4); - py=((posY)<<5)+((type->height)<<4); - - int speedX, speedY, ticksLeft; - - // TODO : shall we really uses shootSpeed ? - // FIXME : is it correct this way ? Is there a function for this ? - int dpx=(bestTargetX*32)+16-4-px; // 4 is the half size of the bullet - int dpy=(bestTargetY*32)+16-4-py; - //printf("%d insert: dp=(%d, %d).\n", gid, dpx, dpy); - if (dpx>(map->getW()<<4)) - dpx=dpx-(map->getW()<<5); - if (dpx<-(map->getW()<<4)) - dpx=dpx+(map->getW()<<5); - if (dpy>(map->getH()<<4)) - dpy=dpy-(map->getH()<<5); - if (dpy<-(map->getH()<<4)) - dpy=dpy+(map->getH()<<5); - - int mdp; - - assert(dpx); - assert(dpy); - if (abs(dpx)>abs(dpy)) //we avoid a square root, since all ditances are squares lengthed. - { - mdp=abs(dpx); - speedX=((dpx*type->shootSpeed)/(mdp<<8)); - speedY=((dpy*type->shootSpeed)/(mdp<<8)); - assert(speedX!=0); - ticksLeft=abs(mdp/speedX); - } - else - { - mdp=abs(dpy); - speedX=((dpx*type->shootSpeed)/(mdp<<8)); - speedY=((dpy*type->shootSpeed)/(mdp<<8)); - assert(speedY!=0); - ticksLeft=abs(mdp/speedY); - } - - if (ticksLeft < bestTicks) - { - Bullet *b = new Bullet(px, py, speedX, speedY, ticksLeft, type->shootDamage, bestTargetX, bestTargetY, posX-1, posY-1, type->width+2, type->height+2); - s->bullets.push_front(b); - bullets--; - shootingCooldown = SHOOTING_COOLDOWN_MAX; - lastShootStep = stepCounter; - lastShootSpeedX = speedX; - lastShootSpeedY = speedY; - } - } - -} - - - -void Building::clearingFlagStep() -{ - if (unitsWorking.size()<(unsigned)maxUnitWorking) - for (int canSwim=0; canSwim<2; canSwim++) - if (localRessourcesCleanTime[canSwim]++>125) // Update every 5[s] - { - if (!owner->map->updateLocalRessources(this, canSwim)) - { - for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) - (*it)->standardRandomActivity(); - unitsWorking.clear(); - } - } -} - - - -void Building::kill(void) -{ - fprintf(logFile, "kill gid=%d buildingState=%d\n", gid, buildingState); - if (buildingState==DEAD) - return; - - - fprintf(logFile, " still %zd unitsInside\n", unitsInside.size()); - for (std::list::iterator it=unitsInside.begin(); it!=unitsInside.end(); ++it) - { - //TODO: We should somehow try to save their lives. In training buildings they should just drop out untrained etc. - Unit *u=*it; - fprintf(logFile, " guid=%d\n", u->gid); - if (u->displacement==Unit::DIS_INSIDE) - u->isDead=true; - - if (u->displacement==Unit::DIS_ENTERING_BUILDING) - { - if (u->performance[FLY]) - owner->map->setAirUnit(u->posX-u->dx, u->posY-u->dy, NOGUID); - else - owner->map->setGroundUnit(u->posX-u->dx, u->posY-u->dy, NOGUID); - //printf("(%x)Building:: Unit(uid%d)(id%d) killed while entering. dis=%d, mov=%d, ab=%x, ito=%d \n",this, u->gid, Unit::UIDtoID(u->gid), u->displacement, u->movement, (int)u->attachedBuilding, u->insideTimeout); - u->isDead=true; - } - u->standardRandomActivity(); - } - unitsInside.clear(); - - fprintf(logFile, " still %zd unitsWorking\n", unitsInside.size()); - for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) - { - assert(*it); - (*it)->standardRandomActivity(); - } - unitsWorking.clear(); - - maxUnitWorking=0; - maxUnitWorkingLocal=0; - maxUnitInside=0; - desiredMaxUnitWorking = 0; - updateCallLists(); - - - if (!type->isVirtual) - { - owner->map->setBuilding(posX, posY, type->width, type->height, NOGBID); - owner->dirtyGlobalGradient(); - owner->map->updateForbiddenGradient(owner->teamNumber); - owner->map->updateGuardAreasGradient(owner->teamNumber); - owner->map->updateClearAreasGradient(owner->teamNumber); - if (type->isBuildingSite && type->level==0) - { - bool good=false; - for (int r=0; r0) - { - good=true; - break; - } - if (!good) - owner->noMoreBuildingSitesCountdown=Team::noMoreBuildingSitesCountdownMax; - } - - } - - buildingState=DEAD; - - updateUnitsHarvesting(); - - owner->prestige-=type->prestige; - - owner->buildingsToBeDestroyed.push_front(this); -} - - -bool Building::canUnitWorkHere(Unit* unit) -{ - if(type->isVirtual) - { - if(type->zonable[unit->typeNum]) - { - if (unit->typeNum == WARRIOR) - { - int level=std::min(unit->level[ATTACK_SPEED], unit->level[ATTACK_STRENGTH]); - if(minLevelToFlag<=level) - return true; - } - else if (unit->typeNum == EXPLORER) - { - if(minLevelToFlag && !unit->level[MAGIC_ATTACK_GROUND]) - return false; - else - return true; - } - else if (unit->typeNum == WORKER) - { - return true; - } - - } - } - else if(unit->typeNum == WORKER) - { - int actLevel=unit->level[HARVEST]; - if(type->level <= actLevel) - return true; - } - return false; - -} - - - -void Building::removeUnitFromWorking(Unit* unit) -{ - unitsWorking.remove(unit); - updateCallLists(); -} - -void Building::insertUnitToHarvesting(Unit* unit) -{ - unitsHarvesting.push_front(unit); -} - - -void Building::removeUnitFromHarvesting(Unit* unit) -{ - unitsHarvesting.remove(unit); -} - - -void Building::removeUnitFromInside(Unit* unit) -{ - unitsInside.remove(unit); - updateCallLists(); -} - - - -void Building::updateRessourcesPointer() -{ - if(!type->useTeamRessources) - { - ressources=localRessource; - } - else - { - ressources=owner->teamRessources; - } -} - - - -void Building::addRessourceIntoBuilding(int ressourceType) -{ - ressources[ressourceType]+=type->multiplierRessource[ressourceType]; - //You can not exceed the maximum amount - ressources[ressourceType] = std::min(ressources[ressourceType], type->maxRessource[ressourceType]); - switch (constructionResultState) - { - case NO_CONSTRUCTION: - break; - case NEW_BUILDING: - case UPGRADE: - { - hp+=type->hpInc; - hp = std::min(hp, type->hpMax); - } - break; - - case REPAIR: - { - int totRessources=0; - for (unsigned i=0; imaxRessource[i]; - hp += type->hpMax/totRessources; - hp = std::min(hp, type->hpMax); - } - break; - - default: - assert(false); - } - update(); -} - - - -void Building::removeRessourceFromBuilding(int ressourceType) -{ - ressources[ressourceType]-=type->multiplierRessource[ressourceType]; - ressources[ressourceType]= std::max(ressources[ressourceType], 0); - updateCallLists(); -} - - - -int Building::getMidX(void) -{ - return ((posX-type->decLeft)&owner->map->getMaskW()); -} - -int Building::getMidY(void) -{ - return ((posY-type->decTop)&owner->map->getMaskH()); -} - -bool Building::findGroundExit(int *posX, int *posY, int *dx, int *dy, bool canSwim) -{ - int testX, testY; - int exitQuality=0; - int oldQuality; - int exitX=0, exitY=0; - - // TODO: Introduce a border iterator for rectangles - - // if (exitQuality<4) - { - testY=this->posY-1; - oldQuality=0; - for (testX=this->posX-1; testX<=this->posX+type->width ; testX++) - checkGroundExitQuality(testX,testY,testX,testY-1,exitX,exitY,exitQuality,oldQuality,canSwim); - } - if (exitQuality<4) - { - testY=this->posY+type->height; - oldQuality=0; - for (testX=this->posX-1; (testX<=this->posX+type->width) ; testX++) - checkGroundExitQuality(testX,testY,testX,testY+1,exitX,exitY,exitQuality,oldQuality,canSwim); - } - if (exitQuality<4) - { - oldQuality=0; - testX=this->posX-1; - for (testY=this->posY-1; (testY<=this->posY+type->height) ; testY++) - checkGroundExitQuality(testX,testY,testX-1,testY,exitX,exitY,exitQuality,oldQuality,canSwim); - } - if (exitQuality<4) - { - oldQuality=0; - testX=this->posX+type->width; - for (testY=this->posY-1; (testY<=this->posY+type->height) ; testY++) - checkGroundExitQuality(testX,testY,testX+1,testY,exitX,exitY,exitQuality,oldQuality,canSwim); - } - if (exitQuality>0) - { - bool shouldBeTrue=owner->map->doesPosTouchBuilding(exitX, exitY, gid, dx, dy); - assert(shouldBeTrue); - *dx=-*dx; - *dy=-*dy; - *posX=exitX & owner->map->getMaskW(); - *posY=exitY & owner->map->getMaskH(); - return true; - } - return false; -} - -void Building::checkGroundExitQuality( - const int testX, - const int testY, - const int extraTestX, - const int extraTestY, - int & exitX, - int & exitY, - int & exitQuality, - int & oldQuality, - bool canSwim) -{ - Uint32 me=owner->me; - if (owner->map->isFreeForGroundUnit(testX, testY, canSwim, me)) - { - if (owner->map->isFreeForGroundUnit(extraTestX, extraTestY, canSwim, me)) - oldQuality++; - if (owner->map->isRessource(testX, testY-1)) - { - if (exitQuality<1+oldQuality) - { - exitQuality=1+oldQuality; - exitX=testX; - exitY=testY; - } - oldQuality=0; - } - else - { - if (exitQuality<2+oldQuality) - { - exitQuality=2+oldQuality; - exitX=testX; - exitY=testY; - } - oldQuality=1; - } - } -} - -bool Building::findAirExit(int *posX, int *posY, int *dx, int *dy) -{ - for (int xi=this->posX; xiposX+type->width; xi++) - for (int yi=this->posY; yiposY+type->height; yi++) - if (owner->map->isFreeForAirUnit(xi, yi)) - { - *posX=xi; - *posY=yi; - int tdx=xi-getMidX(); - int tdy=yi-getMidY(); - if (tdx<0) - *dx=-1; - else if (tdx==0) - *dx=0; - else - *dx=1; - - if (tdy<0) - *dy=-1; - else if (tdy==0) - *dy=0; - else - *dy=1; - return true; - } - return false; -} - -int Building::getLongLevel(void) -{ - return ((type->level)<<1)+1-type->isBuildingSite; -} - -void Building::computeFlagStatLocal(int *goingTo, int *onSpot) -{ - *goingTo = 0; - *onSpot = 0; - - Sint32 unitStayRangeLocalSquare = (1+unitStayRangeLocal)*(1+unitStayRangeLocal); - for (std::list::iterator ui=unitsWorking.begin(); ui!=unitsWorking.end(); ++ui) - { - Sint32 distSquareLocal = owner->map->warpDistSquare(posXLocal, posYLocal, (*ui)->posX, (*ui)->posY); - if (distSquareLocal < unitStayRangeLocalSquare) - (*onSpot)++; - else - (*goingTo)++; - } -} - - -Uint32 Building::eatOnce(Uint32 *mask) -{ - ressources[CORN]--; - assert(ressources[CORN]>=0); - Uint32 fruitMask=0; - Uint32 fruitCount=0; - for (int i=0; iinside) - happyness++; - return happyness; -} - -bool Building::canConvertUnit(void) -{ - assert(type->canFeedUnit); - return - canNotConvertUnitTimer<=0 && - ((int)unitsInside.size()=0); - checkInvariant((int)unitsWorking.size()<=Unit::MAX_COUNT); - for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) - { - checkInvariant(*it); - checkInvariant(owner->myUnits[Unit::GIDtoID((*it)->gid)]); - checkInvariant((*it)->attachedBuilding==this); - } - - checkInvariant(unitsInside.size()>=0); - checkInvariant((int)unitsInside.size()<=Unit::MAX_COUNT); - for (std::list::iterator it=unitsInside.begin(); it!=unitsInside.end(); ++it) - { - checkInvariant(*it); - checkInvariant(owner->myUnits[Unit::GIDtoID((*it)->gid)]); - checkInvariant((*it)->attachedBuilding==this); - } - for (std::list::iterator it=unitsHarvesting.begin(); it!=unitsHarvesting.end(); ++it) - { - checkInvariant(*it); - checkInvariant((*it)->targetBuilding==this); - } - return true; -} - -Uint32 Building::checkSum(std::vector *checkSumsVector) -{ - int cs=0; - - cs^=typeNum; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [0] - - cs^=buildingState; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [1] - cs=(cs<<31)|(cs>>1); - - cs^=constructionResultState; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [2] - cs=(cs<<31)|(cs>>1); - - cs^=maxUnitWorking; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [3] - - cs^=maxUnitWorkingFuture; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [4] - - cs^=maxUnitWorkingPreferred; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [5] - - cs^=maxUnitWorkingPrevious; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [7] - - cs^=desiredMaxUnitWorking; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [8] - - cs^=unitsWorking.size(); - if (checkSumsVector) - checkSumsVector->push_back(cs);// [9] - - cs^=subscriptionWorkingTimer; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [10] - - cs^=unitsInside.size(); - if (checkSumsVector) - checkSumsVector->push_back(cs);// [11] - cs=(cs<<31)|(cs>>1); - - cs^=posX; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [12] - - cs^=posY; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [13] - cs=(cs<<31)|(cs>>1); - - cs^=unitStayRange; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [14] - - for (int i=0; ipush_back(cs);// [15] - cs=(cs<<31)|(cs>>1); - - cs^=hp; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [16] - - cs^=productionTimeout; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [17] - - - cs^=totalRatio; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [18] - - - for (int i=0; i>1); - } - if (checkSumsVector) - checkSumsVector->push_back(cs);// [19] - - cs^=shootingStep; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [20] - - - cs^=shootingCooldown; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [21] - - - cs^=bullets; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [22] - cs=(cs<<31)|(cs>>1); - - cs^=seenByMask; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [23] - - cs^=gid; - if (checkSumsVector) - checkSumsVector->push_back(cs);// [24] - - - cs^=unitsHarvesting.size(); - if (checkSumsVector) - checkSumsVector->push_back(cs);// [25] - - return cs; -} diff --git a/src/BuildingType.cpp b/src/BuildingType.cpp deleted file mode 100644 index 13c8b68a6..000000000 --- a/src/BuildingType.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "BuildingType.h" -#include "GlobalContainer.h" -#include -#include - -BuildingType::BuildingType() -{ - gameSpritePtr = NULL; - miniSpritePtr = NULL; -} - -void BuildingType::loadFromConfigFile(const ConfigBlock *configBlock) -{ - configBlock->load(type, "type"); - - configBlock->load(gameSprite, "gameSprite"); - configBlock->load(gameSpriteImage, "gameSpriteImage"); - configBlock->load(gameSpriteCount, "gameSpriteCount"); - configBlock->load(miniSprite, "miniSprite"); - configBlock->load(miniSpriteImage, "miniSpriteImage"); - - configBlock->load(hueImage,"hueImage"); - configBlock->load(flagImage,"flagImage"); - configBlock->load(crossConnectMultiImage,"crossConnectMultiImage"); - - assert(NB_ABILITY == 17); - configBlock->load(upgrade[0], "upgradeStopWalk"); - configBlock->load(upgrade[1], "upgradeStopSwim"); - configBlock->load(upgrade[2], "upgradeStopFly"); - configBlock->load(upgrade[3], "upgradeWalk"); - configBlock->load(upgrade[4], "upgradeSwim"); - configBlock->load(upgrade[5], "upgradeFly"); - configBlock->load(upgrade[6], "upgradeBuild"); - configBlock->load(upgrade[7], "upgradeHarvest"); - configBlock->load(upgrade[8], "upgradeAttackSpeed"); - configBlock->load(upgrade[9], "upgradeAttackStrength"); - configBlock->load(upgrade[10], "upgradeMagicAttackAir"); - configBlock->load(upgrade[11], "upgradeMagicAttackGround"); - configBlock->load(upgrade[12], "upgradeMagicCreateWood"); - configBlock->load(upgrade[13], "upgradeMagicCreateCorn"); - configBlock->load(upgrade[14], "upgradeMagicCreateAlga"); - configBlock->load(upgrade[15], "upgradeArmor"); - configBlock->load(upgrade[16], "upgradeHP"); - configBlock->load(upgradeTime[0], "upgradeTimeStopWalk"); - configBlock->load(upgradeTime[1], "upgradeTimeStopSwim"); - configBlock->load(upgradeTime[2], "upgradeTimeStopFly"); - configBlock->load(upgradeTime[3], "upgradeTimeWalk"); - configBlock->load(upgradeTime[4], "upgradeTimeSwim"); - configBlock->load(upgradeTime[5], "upgradeTimeFly"); - configBlock->load(upgradeTime[6], "upgradeTimeBuild"); - configBlock->load(upgradeTime[7], "upgradeTimeHarvest"); - configBlock->load(upgradeTime[8], "upgradeTimeAttackSpeed"); - configBlock->load(upgradeTime[9], "upgradeTimeAttackStrength"); - configBlock->load(upgradeTime[10], "upgradeTimeMagicAttackAir"); - configBlock->load(upgradeTime[11], "upgradeTimeMagicAttackGround"); - configBlock->load(upgradeTime[12], "upgradeTimeMagicCreateWood"); - configBlock->load(upgradeTime[13], "upgradeTimeMagicCreateCorn"); - configBlock->load(upgradeTime[14], "upgradeTimeMagicCreateAlga"); - configBlock->load(upgradeTime[15], "upgradeTimeArmor"); - configBlock->load(upgradeTime[16], "upgradeTimeHP"); - configBlock->load(upgradeInParallel, "upgradeInParallel"); - - configBlock->load(foodable, "foodable"); - configBlock->load(fillable, "fillable"); - - assert(NB_UNIT_TYPE == 3); - configBlock->load(zonable[0], "zonableWorker"); - configBlock->load(zonable[1], "zonableExplorer"); - configBlock->load(zonable[2], "zonableWarrior"); - configBlock->load(zonableForbidden, "zonableForbidden"); - - configBlock->load(canFeedUnit, "canFeedUnit"); - configBlock->load(timeToFeedUnit, "timeToFeedUnit"); - configBlock->load(canHealUnit, "canHealUnit"); - configBlock->load(timeToHealUnit, "timeToHealUnit"); - configBlock->load(insideSpeed, "insideSpeed"); - configBlock->load(canExchange, "canExchange"); - configBlock->load(useTeamRessources, "useTeamRessources"); - - configBlock->load(width, "width"); - configBlock->load(height, "height"); - configBlock->load(decLeft, "decLeft"); - configBlock->load(decTop, "decTop"); - configBlock->load(isVirtual, "isVirtual"); - configBlock->load(isCloacked, "isCloacked"); - configBlock->load(shootingRange, "shootingRange"); - configBlock->load(shootDamage, "shootDamage"); - configBlock->load(shootSpeed, "shootSpeed"); - configBlock->load(shootRythme, "shootRythme"); - configBlock->load(maxBullets, "maxBullets"); - configBlock->load(multiplierStoneToBullets, "multiplierStoneToBullets"); - - configBlock->load(unitProductionTime, "unitProductionTime"); - configBlock->load(ressourceForOneUnit, "ressourceForOneUnit"); - - assert(MAX_NB_RESSOURCES == 15); - configBlock->load(maxRessource[0], "maxWood"); - configBlock->load(maxRessource[1], "maxCorn"); - configBlock->load(maxRessource[2], "maxPapyrus"); - configBlock->load(maxRessource[3], "maxStone"); - configBlock->load(maxRessource[4], "maxAlgue"); - configBlock->load(maxRessource[5], "maxFruit0"); - configBlock->load(maxRessource[6], "maxFruit1"); - configBlock->load(maxRessource[7], "maxFruit2"); - configBlock->load(maxRessource[8], "maxFruit3"); - configBlock->load(maxRessource[9], "maxFruit4"); - configBlock->load(maxRessource[10], "maxFruit5"); - configBlock->load(maxRessource[11], "maxFruit6"); - configBlock->load(maxRessource[12], "maxFruit7"); - configBlock->load(maxRessource[13], "maxFruit8"); - configBlock->load(maxRessource[14], "maxFruit9"); - configBlock->load(multiplierRessource[0], "multiplierWood"); - configBlock->load(multiplierRessource[1], "multiplierCorn"); - configBlock->load(multiplierRessource[2], "multiplierPapyrus"); - configBlock->load(multiplierRessource[3], "multiplierStone"); - configBlock->load(multiplierRessource[4], "multiplierAlgue"); - configBlock->load(multiplierRessource[5], "multiplierFruit0"); - configBlock->load(multiplierRessource[6], "multiplierFruit1"); - configBlock->load(multiplierRessource[7], "multiplierFruit2"); - configBlock->load(multiplierRessource[8], "multiplierFruit3"); - configBlock->load(multiplierRessource[9], "multiplierFruit4"); - configBlock->load(multiplierRessource[10], "multiplierFruit5"); - configBlock->load(multiplierRessource[11], "multiplierFruit6"); - configBlock->load(multiplierRessource[12], "multiplierFruit7"); - configBlock->load(multiplierRessource[13], "multiplierFruit8"); - configBlock->load(multiplierRessource[14], "multiplierFruit9"); - - configBlock->load(maxUnitInside, "maxUnitInside"); - configBlock->load(maxUnitWorking, "maxUnitWorking"); - - configBlock->load(hpInit, "hpInit"); - configBlock->load(hpMax, "hpMax"); - configBlock->load(hpInc, "hpInc"); - configBlock->load(armor, "armor"); - configBlock->load(level, "level"); - configBlock->load(shortTypeNum, "shortTypeNum"); - configBlock->load(isBuildingSite, "isBuildingSite"); - - configBlock->load(defaultUnitStayRange, "defaultUnitStayRange"); - configBlock->load(maxUnitStayRange, "maxUnitStayRange"); - - configBlock->load(viewingRange, "viewingRange"); - configBlock->load(regenerationSpeed, "regenerationSpeed"); - - configBlock->load(prestige, "prestige"); - - // regenerate local parameters - if ((!globalContainer->runNoX) && (type != "null")) - { - gameSpritePtr = Toolkit::getSprite(gameSprite.c_str()); - if (miniSpriteImage >= 0) - miniSpritePtr = Toolkit::getSprite(miniSprite.c_str()); - } -} - -//! Return a chcksum of all parameter that could lead to a game desynchronization -Uint32 BuildingType::checkSum(void) -{ - Uint32 cs = 0; - - for (size_t i = 0; i<(size_t)NB_ABILITY; i++) - { - cs ^= upgrade[i]; - cs = (cs<<1) | (cs>>31); - } - for (size_t i = 0; i<(size_t)NB_ABILITY; i++) - { - cs ^= upgradeTime[i]; - cs = (cs<<1) | (cs>>31); - } - cs ^= foodable; - cs = (cs<<1) | (cs>>31); - cs ^= fillable; - cs = (cs<<1) | (cs>>31); - for (size_t i = 0; i<(size_t)NB_UNIT_TYPE; i++) - { - cs ^= zonable[i]; - cs = (cs<<1) | (cs>>31); - } - cs ^= zonableForbidden; - cs = (cs<<1) | (cs>>31); - cs ^= canFeedUnit; - cs = (cs<<1) | (cs>>31); - cs ^= timeToFeedUnit; - cs = (cs<<1) | (cs>>31); - cs ^= canHealUnit; - cs = (cs<<1) | (cs>>31); - cs ^= timeToHealUnit; - cs = (cs<<1) | (cs>>31); - cs ^= insideSpeed; - cs = (cs<<1) | (cs>>31); - cs ^= canExchange; - cs = (cs<<1) | (cs>>31); - cs ^= useTeamRessources; - cs = (cs<<1) | (cs>>31); - cs ^= width; - cs = (cs<<1) | (cs>>31); - cs ^= height; - cs = (cs<<1) | (cs>>31); - cs ^= decLeft; - cs = (cs<<1) | (cs>>31); - cs ^= decTop; - cs = (cs<<1) | (cs>>31); - cs ^= isVirtual; - cs = (cs<<1) | (cs>>31); - cs ^= isCloacked; - cs = (cs<<1) | (cs>>31); - cs ^= shootingRange; - cs = (cs<<1) | (cs>>31); - cs ^= shootDamage; - cs = (cs<<1) | (cs>>31); - cs ^= shootSpeed; - cs = (cs<<1) | (cs>>31); - cs ^= shootRythme; - cs = (cs<<1) | (cs>>31); - cs ^= maxBullets; - cs = (cs<<1) | (cs>>31); - cs ^= multiplierStoneToBullets; - cs = (cs<<1) | (cs>>31); - cs ^= unitProductionTime; - cs = (cs<<1) | (cs>>31); - cs ^= ressourceForOneUnit; - cs = (cs<<1) | (cs>>31); - for (size_t i = 0; i<(size_t)MAX_NB_RESSOURCES; i++) - { - cs ^= maxRessource[i]; - cs = (cs<<1) | (cs>>31); - } - for (size_t i = 0; i<(size_t)MAX_NB_RESSOURCES; i++) - { - cs ^= multiplierRessource[i]; - cs = (cs<<1) | (cs>>31); - } - cs ^= maxUnitInside; - cs = (cs<<1) | (cs>>31); - cs ^= maxUnitWorking; - cs = (cs<<1) | (cs>>31); - cs ^= hpInit; - cs = (cs<<1) | (cs>>31); - cs ^= hpMax; - cs = (cs<<1) | (cs>>31); - cs ^= hpInc; - cs = (cs<<1) | (cs>>31); - cs ^= armor; - cs = (cs<<1) | (cs>>31); - cs ^= level; - cs = (cs<<1) | (cs>>31); - cs ^= shortTypeNum; - cs = (cs<<1) | (cs>>31); - cs ^= isBuildingSite; - cs = (cs<<1) | (cs>>31); - cs ^= defaultUnitStayRange; - cs = (cs<<1) | (cs>>31); - cs ^= maxUnitStayRange; - cs = (cs<<1) | (cs>>31); - cs ^= viewingRange; - cs = (cs<<1) | (cs>>31); - cs ^= regenerationSpeed; - cs = (cs<<1) | (cs>>31); - cs ^= prestige; - - return cs; -} diff --git a/src/BuildingType.h b/src/BuildingType.h deleted file mode 100644 index 0c701584d..000000000 --- a/src/BuildingType.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __BULDING_TYPE_H -#define __BULDING_TYPE_H - -#include - -#include "ConfigFiles.h" -#include "UnitConsts.h" -#include "Ressource.h" - -class BuildingType: public LoadableFromConfigFile -{ -public: - // basic infos - std::string type; - - // visualisation - std::string gameSprite; - Sint32 gameSpriteImage; - Sint32 gameSpriteCount; - std::string miniSprite; - Sint32 miniSpriteImage; - - Sint32 hueImage; // bool. The way we show the building's team (false=we draw a flag, true=we hue all the sprite) - Sint32 flagImage; - Sint32 crossConnectMultiImage; // If true, mean we have a wall-like building - - // could be Uint8, if non 0 tell the number of maximum units locked by bulding for: - // by order of priority (top = max) - Sint32 upgrade[NB_ABILITY]; // What kind on units can be upgraded here - Sint32 upgradeTime[NB_ABILITY]; // Time to upgrade an unit, given the upgrade type needed. - Sint32 upgradeInParallel; // if true, can learn all upgardes with one learning time into the building - Sint32 foodable; - Sint32 fillable; - Sint32 zonable[NB_UNIT_TYPE]; // If an unit is required for a presence. - Sint32 zonableForbidden; - - Sint32 canFeedUnit; - Sint32 timeToFeedUnit; - Sint32 canHealUnit; - Sint32 timeToHealUnit; - Sint32 insideSpeed; - Sint32 canExchange; - Sint32 useTeamRessources; - - Sint32 width, height; // Uint8, size in square - Sint32 decLeft, decTop; - Sint32 isVirtual; // bool, doesn't occupy ground occupation map, used for war-flag and exploration-flag. - Sint32 isCloacked; // bool, graphicaly invisible for enemy. - //Sint32 *walkOverMap; // should be allocated and deleted in a cleany way - //Sint32 walkableOver; // bool, can walk over - Sint32 shootingRange; // Uint8, if 0 can't shoot - Sint32 shootDamage; // Uint8 - Sint32 shootSpeed; // Uint8, the actual speed at which the shots fly through the air. - Sint32 shootRythme; // Uint8, The frequency with which a tower fires. It fires once every - // SHOOTING_COOLDOWN_MAX/shootRythme ticks. - Sint32 maxBullets; - Sint32 multiplierStoneToBullets; //The tower gets this many bullets every time a worker delivers stone to it. - - Sint32 unitProductionTime; // Uint8, nb tick to produce one unit - Sint32 ressourceForOneUnit; // The amount of wheat consumed in the production of a unit. - - Sint32 maxRessource[MAX_NB_RESSOURCES]; - Sint32 multiplierRessource[MAX_NB_RESSOURCES]; - Sint32 maxUnitInside; - Sint32 maxUnitWorking; - - Sint32 hpInit; // (Uint16) Initial HP of the building. This is generally equal to hpMax for completed buildings, - // equal to 1 for newly created buildings, and equal to the hpMax of the original building for - // upgrading buildings. - Sint32 hpMax; - Sint32 hpInc; // The amount by which the building's hitpoints are incremented when a resource is added to it, - // for buildings under construction. - Sint32 armor; // (Uint8) Any damage the building takes is reduced by this much, although it has a minumum of 1 - // for most damage, 0 only for Explorers. - Sint32 level; // (Uint8) - Sint32 shortTypeNum; // BuildingTypeShortNumber, Should not be used by the main engine, but only to choose the next level building. - Sint32 isBuildingSite; - - // Flag usefull - Sint32 defaultUnitStayRange; - Sint32 maxUnitStayRange; - - Sint32 viewingRange; - Sint32 regenerationSpeed; - - Sint32 prestige; - - // Regenerated parameters - Sprite *gameSpritePtr; - Sprite *miniSpritePtr; - int prevLevel; - int nextLevel; - -public: - BuildingType(); - virtual ~BuildingType() { } - virtual void loadFromConfigFile(const ConfigBlock *configBlock); - Uint32 checkSum(void); -}; - -#endif - diff --git a/src/BuildingUtils.cpp b/src/BuildingUtils.cpp deleted file mode 100644 index c869bdcf5..000000000 --- a/src/BuildingUtils.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "BuildingUtils.h" -#include "Team.h" - - -Sint32 BuildingUtils::GIDtoID(Uint16 gid) -{ - assert(gid < BuildingUtils::MAX_COUNT * Team::MAX_COUNT); - return gid % BuildingUtils::MAX_COUNT; -} - -Sint32 BuildingUtils::GIDtoTeam(Uint16 gid) -{ - assert(gid < BuildingUtils::MAX_COUNT * Team::MAX_COUNT); - return gid / BuildingUtils::MAX_COUNT; -} - -Uint16 BuildingUtils::GIDfrom(Sint32 id, Sint32 team) -{ - assert(id < BuildingUtils::MAX_COUNT); - assert(team < Team::MAX_COUNT); - return id + team * BuildingUtils::MAX_COUNT; -} - diff --git a/src/BuildingUtils.h b/src/BuildingUtils.h deleted file mode 100644 index fa3fb3865..000000000 --- a/src/BuildingUtils.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __BUILDING_UTILS_H -#define __BUILDING_UTILS_H - -#include - -class BuildingUtils -{ - public: - static Sint32 GIDtoID(Uint16 gid); - static Sint32 GIDtoTeam(Uint16 gid); - static Uint16 GIDfrom(Sint32 id, Sint32 team); - - static const int MAX_COUNT = 1024; -}; - - -#endif // __BUILDING_UTILS_H - diff --git a/src/BuildingsTypes.cpp b/src/BuildingsTypes.cpp deleted file mode 100644 index 81726ee63..000000000 --- a/src/BuildingsTypes.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charri�e - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include - -#include "BuildingsTypes.h" -#include "GlobalContainer.h" - -void BuildingsTypes::load() -{ - ConfigVector::load("data/buildings.default.txt", true); - ConfigVector::load("data/buildings.txt"); - - resolveUpgradeReferences(); - - checkIntegrity(); -} - -void BuildingsTypes::checkIntegrity(void) -{ - for (size_t i=0; imaxRessource[j]) - { - needRessource=true; - break; - } - if (needRessource) - assert(bt->fillable || bt->foodable); - - //hpInc integrity: - if (bt->isBuildingSite) - assert(bt->hpInc > 0); - else - assert(bt->hpInc == 0); - - //mpMax/hpInit integrity: - if (bt->isBuildingSite) - { - if (bt->level) - { - assert(bt->prevLevel != -1); - BuildingType *bt2 = get(bt->prevLevel); - assert(bt2); - if (bt->hpInit != bt2->hpMax) - { - std::cerr << "BuildingsTypes::load() : warning : " << bt->type << " : Building site (" << entriesToName[i] << ") has hpInit=" << bt->hpInit << ", but final building (" << entriesToName[bt->prevLevel] << ") has hpMax=" << bt2->hpMax << std::endl; - } - } - } - - - //hpInit/hpInc integrity: - if (bt->isBuildingSite) - { - int resSum=0; - for (int i=0; imaxRessource[i]; - int hpSum = bt->hpInit+resSum*bt->hpInc; - if (hpSum < bt->hpMax) - { - std::cerr << "BuildingsTypes::load() : warning : " << bt->type << " : hpSum(" << hpSum <<") < hpMax(" << bt->hpMax << ") with hpInit=" << bt->hpInit << ", hpInc=" << bt->hpInc << ", resSum=" << resSum << ". Make hpInc>=" << (bt->hpMax-bt->hpInit+resSum-1)/resSum << std::endl; - } - } - - - //flag integrity: - if (bt->isVirtual) - { - assert(bt->isCloacked); - assert(bt->defaultUnitStayRange); - } - if (bt->isCloacked) - { - assert(bt->isVirtual); - assert(bt->defaultUnitStayRange); - } - if (bt->defaultUnitStayRange) - { - assert(bt->isCloacked); - assert(bt->isVirtual); - } - if (bt->zonableForbidden) - { - assert(bt->isCloacked); - assert(bt->isVirtual); - assert(bt->defaultUnitStayRange); - } - - } -} - -void BuildingsTypes::resolveUpgradeReferences(void) -{ - for (size_t i=0; inextLevel = entries[i]->prevLevel = -1; - } - - for (size_t i=0; iisBuildingSite) - { - if ((bt2->level == bt1->level) && (bt2->type == bt1->type) && !(bt2->isBuildingSite)) - { - bt1->nextLevel = j; - bt2->prevLevel = i; - break; - } - } - else - { - if ((bt2->level == bt1->level+1) && (bt2->type == bt1->type) && (bt2->isBuildingSite)) - { - bt1->nextLevel = j; - bt2->prevLevel = i; - break; - } - } - } - } - } -} - -Sint32 BuildingsTypes::getTypeNum(const char *type, int level, bool isBuildingSite) -{ - assert(type); - for (size_t i=0; itype == type) && (entries[i]->level == level) && ((entries[i]->isBuildingSite!=0) == isBuildingSite)) - return i; - } - - //std::cerr << "BuildingsTypes::getTypeNum(" << type << "," << level << "," << isBuildingSite << ") : error : type does not exists" << std::endl; - // we can reach this point if we request a flag - return -1; -} - -BuildingType *BuildingsTypes::getByType(const char *type, int level, bool isBuildingSite) -{ - assert(type); - for (size_t i=0; itype == type) && (entries[i]->level == level) && ((entries[i]->isBuildingSite!=0) == isBuildingSite)) - return entries[i]; - } - - //std::cerr << "BuildingsTypes::getByType(" << type << "," << level << "," << isBuildingSite << ") : error : type does not exists" << std::endl; - // we can reach this point if we request a flag - return NULL; -} - -Sint32 BuildingsTypes::getTypeNum(const std::string &s, int level, bool isBuildingSite) -{ - return getTypeNum(s.c_str(), level, isBuildingSite); -} - -BuildingType *BuildingsTypes::getByType(const std::string &s,int level, bool isBuildingSite) -{ - return getByType(s.c_str(), level, isBuildingSite); -} - -Uint32 BuildingsTypes::checkSum(void) -{ - Uint32 cs = 0; - - for (size_t i=0; icheckSum(); - cs = (cs<<1) | (cs>>31); - } - - return cs; -} diff --git a/src/BuildingsTypes.h b/src/BuildingsTypes.h deleted file mode 100644 index 0c38a09a9..000000000 --- a/src/BuildingsTypes.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charri�e - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __BULDING_TYPES_H -#define __BULDING_TYPES_H - -#include "BuildingType.h" - -class BuildingsTypes: public ConfigVector -{ -protected: - void resolveUpgradeReferences(void); - void checkIntegrity(void); - -public: - virtual void load(); - virtual ~BuildingsTypes() { } - - Sint32 getTypeNum(const char *type, int level, bool isBuildingSite); - Sint32 getTypeNum(const std::string &s, int level, bool isBuildingSite); - BuildingType *getByType(const char *type, int level, bool isBuildingSite); - BuildingType *getByType(const std::string &s, int level, bool isBuildingSite); - - Uint32 checkSum(void); -}; - -#endif - diff --git a/src/Bullet.cpp b/src/Bullet.cpp index e982a88b5..d55f41962 100644 --- a/src/Bullet.cpp +++ b/src/Bullet.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "Bullet.h" #include diff --git a/src/Bullet.h b/src/Bullet.h index e3735ddc9..3dbc39cdc 100644 --- a/src/Bullet.h +++ b/src/Bullet.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __BULLET_H -#define __BULLET_H +#pragma once #define SHOOTING_COOLDOWN_MAX 65536 //! Number of bit before any significant one, to avoid overflow while computing totalDefensePower in TeamStat.cpp @@ -51,10 +34,3 @@ class Bullet void step(void); }; -struct BulletExplosion -{ - int x, y, ticksLeft; -}; - -#endif - diff --git a/src/CPUStatisticsManager.cpp b/src/CPUStatisticsManager.cpp deleted file mode 100644 index 9724baff6..000000000 --- a/src/CPUStatisticsManager.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "CPUStatisticsManager.h" -#include "Stream.h" -#include "Toolkit.h" -#include "boost/lexical_cast.hpp" -#include "GlobalContainer.h" - -using namespace GAGCore; - -CPUStatisticsManager::CPUStatisticsManager() - : frame_number(0), time_per_frame(40) -{ -} - - - -void CPUStatisticsManager::reset(int atime_per_frame) -{ - frame_number = 0; - time_per_frame = atime_per_frame; - statistics.clear(); -} - - - -void CPUStatisticsManager::addFrameData(int cpu_time_used) -{ - if(frame_number % 10 == 0) - statistics.push_back(cpu_time_used); - frame_number+=1; -} - - - -void CPUStatisticsManager::format() -{ -/* - std::string logName = "logs/"; - logName += globalContainer->getUsername(); - logName += "CPU.log"; - OutputLineStream* stream = new OutputLineStream(Toolkit::getFileManager()->openOutputStreamBackend(logName)); - stream->writeLine("Time CPU usage"); - for(int i=0; i<20; ++i) - { - std::string line=""; - int total_time = frame_number * time_per_frame * (i+1) / 20; - int seconds = (total_time / 1000) % 60; - int minutes = (total_time / 1000) / 60; - line+=boost::lexical_cast(minutes) + ":"; - if(seconds < 10) - line+= "0" + boost::lexical_cast(seconds); - else - line+= boost::lexical_cast(seconds); - - while(line.size() < 10) - line += " "; - - int total_cpu_time_consumed=0; - int total_recorded=0; - for(int j = (statistics.size() * (i) / 20); j < (statistics.size() * (i+1) / 20); ++j) - { - total_cpu_time_consumed += statistics[j]; - total_recorded += 1; - } - - float cpu_usage = (float)(total_cpu_time_consumed) / (float)(total_recorded * time_per_frame); - line += boost::lexical_cast(cpu_usage); - - stream->writeLine(line); - } - - delete stream; -*/ -} - diff --git a/src/CPUStatisticsManager.h b/src/CPUStatisticsManager.h deleted file mode 100644 index 8bb483224..000000000 --- a/src/CPUStatisticsManager.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __CPUStatisticsManager_H -#define __CPUStatisticsManager_H - -#include - -///This class holds the responsibility of managing CPU statistics -class CPUStatisticsManager -{ -public: - ///Constructs a CPU statistics manager - CPUStatisticsManager(); - - ///Resets the statistics - void reset(int time_per_frame); - - ///Add the data for another frame to the manager - void addFrameData(int cpu_time_used); - - ///Writes out CPU statistics chart - void format(); -private: - int frame_number; - int time_per_frame; - std::vector statistics; -}; - -#endif diff --git a/src/Campaign.cpp b/src/Campaign.cpp index f58711afe..b5f94f3bb 100644 --- a/src/Campaign.cpp +++ b/src/Campaign.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault #include "Campaign.h" #include "TextStream.h" @@ -23,8 +7,14 @@ #include "Toolkit.h" #include "FileManager.h" #include -#include "Game.h" -#include "GlobalContainer.h" +#include + +// Defined in map/io/MapHeader.cpp. Forward-declared here to avoid pulling +// MapHeader.h, which transitively includes Team.h / WinningConditions.h / +// Map.h — none of which Campaign.cpp itself uses. +std::string glob2NameToFilename(const std::string& dir, + const std::string& name, + const std::string& extension=""); using namespace GAGCore; @@ -191,48 +181,75 @@ Campaign::Campaign() bool Campaign::load(const std::string& fileName) { StreamBackend* backend = Toolkit::getFileManager()->openInputStreamBackend(fileName); - if (backend->isEndOfStream()) + // openInputStreamBackend never returns nullptr; missing files surface as + // a backend wrapping a NULL FILE*, which fails isValid(). + if (!backend->isValid()) { - //std::cerr << "Campaign::load(\"" << fileName << "\") : error, can't open file." << std::endl; + std::cerr << "Campaign::load(\"" << fileName << "\") : error, can't open file." << std::endl; delete backend; return false; } - else + + TextInputStream* stream = new TextInputStream(backend); + Uint32 versionMinor = stream->readUint32("versionMinor"); + // Parser failure on empty/corrupt files leaves versionMinor at the + // uninitialized-istream-extract default (0). Reject anything outside the + // supported range so corrupt files don't silently produce blank campaigns. + if (versionMinor < MINIMUM_VERSION_MINOR || versionMinor > VERSION_MINOR) { - TextInputStream* stream = new TextInputStream(backend); - Uint32 versionMinor = stream->readUint32("versionMinor"); - name = stream->readText("campaignName"); - playerName = stream->readText("playerName"); - stream->readEnterSection("maps"); - Uint32 size=stream->readUint32("mapNum"); - maps.resize(size); - for(Uint32 n=0; nreadEnterSection(n); - maps[n].load(stream, versionMinor); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - if(versionMinor >= 83) - { - description = stream->readText("description"); - } + std::cerr << "Campaign::load(\"" << fileName << "\") : unsupported or corrupt versionMinor " + << versionMinor << std::endl; delete stream; delete backend; - return true; + return false; } + + name = stream->readText("campaignName"); + playerName = stream->readText("playerName"); + stream->readEnterSection("maps"); + Uint32 size = stream->readUint32("mapNum"); + maps.resize(size); + for (Uint32 n = 0; n < size; ++n) + { + stream->readEnterSection(n); + maps[n].load(stream, versionMinor); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + if (versionMinor >= 83) + description = stream->readText("description"); + + delete stream; + delete backend; + return true; } -void Campaign::save(bool isGameSave) +bool Campaign::save(bool isGameSave) { std::string filename; if(!isGameSave) filename = glob2NameToFilename("campaigns", name.c_str(), "txt"); else filename = glob2NameToFilename("games", name.c_str(), "txt"); - TextOutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(filename)); + + // openOutputStreamBackend never returns nullptr; on fopen failure it + // returns a backend wrapping NULL, which fails isValid() and would crash + // (assert(fp) in debug, raw fwrite(NULL) UB in release) on the first + // write. Mirrors the pattern in Campaign::load and MapEdit::save. + std::unique_ptr backend( + Toolkit::getFileManager()->openOutputStreamBackend(filename)); + if (!backend->isValid()) + { + std::cerr << "Campaign::save(\"" << filename << "\") : error, can't open file." << std::endl; + return false; + } + + // TextOutputStream takes ownership of the backend and frees it in its + // destructor, so release() at the point of handoff. unique_ptr on the + // stream itself protects against leak-on-throw from any future write. + auto stream = std::make_unique(backend.release()); stream->writeUint32(VERSION_MINOR, "versionMinor"); stream->writeText(name, "campaignName"); stream->writeText(playerName, "playerName"); @@ -241,12 +258,12 @@ void Campaign::save(bool isGameSave) for(unsigned n=0; nwriteEnterSection(n); - maps[n].save(stream); + maps[n].save(stream.get()); stream->writeLeaveSection(); } stream->writeLeaveSection(); stream->writeText(description, "description"); - delete stream; + return true; } @@ -265,6 +282,18 @@ CampaignMapEntry& Campaign::getMap(unsigned n) +CampaignMapEntry* Campaign::findUnlockedMap(const std::string& mapName) +{ + for (size_t n = 0; n < maps.size(); ++n) + { + if (maps[n].getMapName() == mapName && maps[n].isUnlocked()) + return &maps[n]; + } + return nullptr; +} + + + void Campaign::appendMap(CampaignMapEntry& map) { maps.push_back(map); diff --git a/src/Campaign.h b/src/Campaign.h index aa9635cc0..a2975cf37 100644 --- a/src/Campaign.h +++ b/src/Campaign.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2006 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef CAMPAIGN_H -#define CAMPAIGN_H +#pragma once #include #include @@ -84,12 +68,19 @@ class Campaign ///Loads the campaign with the provided name bool load(const std::string& fileName); - ///Save the campaign - void save(bool isGameSave=false); + ///Save the campaign. Returns false if the destination file cannot be opened + ///(read-only directory, full disk, missing path); callers should react instead + ///of silently dropping the user's progress / edits. + bool save(bool isGameSave=false); ///Gets the number of maps in this campaign size_t getMapCount() const; ///Returns the name of the map n CampaignMapEntry& getMap(unsigned n); + ///Looks up an unlocked map by its display name. + ///Returns nullptr if no map with that name exists or if the matching map is still locked. + ///Use this from UI selection paths so a list-index mismatch (the displayed list shows + ///only unlocked maps; campaign.maps holds locked entries too) cannot confuse the lookup. + CampaignMapEntry* findUnlockedMap(const std::string& mapName); ///Appends a map to the list of maps void appendMap(CampaignMapEntry& map); ///Removes map n @@ -121,4 +112,3 @@ class Campaign }; -#endif diff --git a/src/CampaignEditor.cpp b/src/CampaignEditor.cpp index 8d9106895..d53f79261 100644 --- a/src/CampaignEditor.cpp +++ b/src/CampaignEditor.cpp @@ -1,26 +1,13 @@ -/* - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault #include "CampaignEditor.h" #include "Toolkit.h" #include "StringTable.h" #include "ChooseMapScreen.h" #include "Game.h" +#include "GlobalContainer.h" +#include "GUIMessageBox.h" #include #include #include "GUICheckList.h" @@ -28,8 +15,8 @@ CampaignEditor::CampaignEditor(const std::string& name) { - if(name!="") - campaign.load(name); + if (name != "" && !campaign.load(name)) + campaign.setName(name); StringTable& table=*Toolkit::getStringTable(); title = new Text(0, 18, ALIGN_FILL, ALIGN_SCREEN_CENTERED, "menu", table.getString("[campaign editor]")); mapList = new List(10, 50, 300, 300, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); @@ -61,8 +48,12 @@ void CampaignEditor::onAction(Widget *source, Action action, int par1, int par2) { if (source == ok) { - campaign.save(); - endExecute(OK); + if (campaign.save()) + endExecute(OK); + else + GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_ONEBUTTON, + Toolkit::getStringTable()->getString("[ERROR_CANT_SAVE_CAMPAIGN]"), + Toolkit::getStringTable()->getString("[ok]")); } else if (source == cancel) { @@ -102,25 +93,30 @@ void CampaignEditor::onAction(Widget *source, Action action, int par1, int par2) } else if (source == editMap) { - for(unsigned i=0; iselection(); + if (sel) { - if(mapList->getSelectionIndex()!=-1 && campaign.getMap(i).getMapName()==mapList->get()) + for(unsigned i=0; isetText(mapList->getSelectionIndex(), campaign.getMap(i).getMapName()); - } - else if(rcmee==CampaignMapEntryEditor::CANCEL) + if(campaign.getMap(i).getMapName()==mapList->get()) { + CampaignMapEntryEditor cmee(campaign, campaign.getMap(i)); + int rcmee = cmee.execute(gfx, 40); + if(rcmee==CampaignMapEntryEditor::OK) + { + mapList->setText(*sel, campaign.getMap(i).getMapName()); + } + else if(rcmee==CampaignMapEntryEditor::CANCEL) + { + } } } } } else if (source == removeMap) { - if(mapList->getSelectionIndex()!=-1) + auto sel = mapList->selection(); + if (sel) { for(unsigned i=0; igetSelectionIndex()); - mapList->removeText(mapList->getSelectionIndex()); + campaign.removeMap(*sel); + mapList->removeText(*sel); } } } diff --git a/src/CampaignEditor.h b/src/CampaignEditor.h index 237248284..723bcd0bc 100644 --- a/src/CampaignEditor.h +++ b/src/CampaignEditor.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2006 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef CAMPAIGN_EDITOR_H -#define CAMPAIGN_EDITOR_H +#pragma once #include "Glob2Screen.h" #include "Campaign.h" @@ -105,4 +89,3 @@ class CampaignMapEntryEditor : public Glob2Screen Text *isUnlockedLabel; }; -#endif diff --git a/src/CampaignMainMenu.cpp b/src/CampaignMainMenu.cpp index f27cd8d14..044870de7 100644 --- a/src/CampaignMainMenu.cpp +++ b/src/CampaignMainMenu.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2006-2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006-2008 Bradley Arsenault #include "CampaignMainMenu.h" @@ -40,7 +25,7 @@ void CampaignMainMenu::onAction(Widget *source, Action action, int par1, int par { if ((action==BUTTON_RELEASED) || (action==BUTTON_SHORTCUT)) { - if ((par1==LOADCAMPAIGN)) + if (par1==LOADCAMPAIGN) { CampaignSelectorScreen css(true); int rc_css=css.execute(globalContainer->gfx, 40); @@ -64,7 +49,7 @@ void CampaignMainMenu::onAction(Widget *source, Action action, int par1, int par endExecute(-1); } } - else if((par1==NEWCAMPAIGN)) + else if(par1==NEWCAMPAIGN) { CampaignSelectorScreen css; int rc_css=css.execute(globalContainer->gfx, 40); @@ -89,7 +74,7 @@ void CampaignMainMenu::onAction(Widget *source, Action action, int par1, int par endExecute(-1); } } - else if((par1==CANCEL)) + else if(par1==CANCEL) { endExecute(CANCEL); } diff --git a/src/CampaignMainMenu.h b/src/CampaignMainMenu.h index a10a959b1..a503671dc 100644 --- a/src/CampaignMainMenu.h +++ b/src/CampaignMainMenu.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2006-2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006-2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef CampaignMainMenu_h -#define CampaignMainMenu_h +#pragma once #include "Glob2Screen.h" #include "GUIButton.h" @@ -43,4 +27,3 @@ class CampaignMainMenu : public Glob2Screen Button *cancel; }; -#endif diff --git a/src/CampaignMenuScreen.cpp b/src/CampaignMenuScreen.cpp index 2e7cb5e50..514139e3f 100644 --- a/src/CampaignMenuScreen.cpp +++ b/src/CampaignMenuScreen.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault #include "CampaignMenuScreen.h" #include "Toolkit.h" @@ -22,10 +7,12 @@ #include "Engine.h" #include "GlobalContainer.h" #include "GUIMapPreview.h" +#include "GUIMessageBox.h" CampaignMenuScreen::CampaignMenuScreen(const std::string& name) { - campaign.load(name); + if (!campaign.load(name)) + campaign.setName(name); title = new Text(0, 18, ALIGN_FILL, ALIGN_SCREEN_CENTERED, "menu", campaign.getName()); addWidget(title); startMission = new TextButton(10, 430, 300, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[start mission]"), START); @@ -35,11 +22,7 @@ CampaignMenuScreen::CampaignMenuScreen(const std::string& name) playerName = new TextInput(330, 225, 300, 25, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", campaign.getPlayerName()); addWidget(playerName); availableMissions = new CheckList(10, 50, 300, 200, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); - for(unsigned i=0; iaddItem(campaign.getMap(i).getMapName(), campaign.getMap(i).isCompleted()); - } + repopulateAvailableMissions(); addWidget(availableMissions); @@ -54,17 +37,21 @@ void CampaignMenuScreen::onAction(Widget *source, Action action, int par1, int p { if ((action==BUTTON_RELEASED) || (action==BUTTON_SHORTCUT)) { - if ((par1==EXIT)) + if (par1==EXIT) { + // Player is leaving the campaign menu; if the save fails (read-only + // dir, full disk) we still proceed with the exit but the stderr log + // in Campaign::save records why progress wasn't persisted. campaign.save(true); endExecute(par1); } - else if((par1==START)) + else if(par1==START) { - if (availableMissions->getSelectionIndex() >= 0) + CampaignMapEntry* selected = campaign.findUnlockedMap(availableMissions->get()); + if (selected) { Engine engine; - int rc_e = engine.initCampaign(getMissionName(), campaign, availableMissions->get()); + int rc_e = engine.initCampaign(selected->getMapFileName(), campaign, selected->getMapName()); if (rc_e == Engine::EE_NO_ERROR) { int rcr = engine.run(); @@ -75,17 +62,19 @@ void CampaignMenuScreen::onAction(Widget *source, Action action, int par1, int p { endExecute(-1); } - availableMissions->clear(); - for(unsigned i=0; iaddItem(campaign.getMap(i).getMapName(), campaign.getMap(i).isCompleted()); - } - campaign.save(true); + repopulateAvailableMissions(); + // Post-mission save persists completion / unlock state. If it + // silently dropped, the player would re-launch a "completed" + // mission or find the next one still locked, so surface the + // failure instead of swallowing it. + if (!campaign.save(true)) + GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_ONEBUTTON, + Toolkit::getStringTable()->getString("[ERROR_CANT_SAVE_CAMPAIGN]"), + Toolkit::getStringTable()->getString("[ok]")); } } } - else if((action==TEXT_MODIFIED)) + else if(action==TEXT_MODIFIED) { if(source==playerName) { @@ -94,26 +83,25 @@ void CampaignMenuScreen::onAction(Widget *source, Action action, int par1, int p } else if (action == LIST_ELEMENT_SELECTED) { - std::string mapFileName = campaign.getMap(availableMissions->getSelectionIndex()).getMapFileName(); - mapPreview->setMapThumbnail(mapFileName.c_str()); - description->setText(Toolkit::getStringTable()->getString(campaign.getMap(availableMissions->getSelectionIndex()).getDescription())); + CampaignMapEntry* selected = campaign.findUnlockedMap(availableMissions->get()); + if (selected) + { + mapPreview->setMapThumbnail(selected->getMapFileName().c_str()); + description->setText(Toolkit::getStringTable()->getString(selected->getDescription())); + } } } - -std::string CampaignMenuScreen::getMissionName() +void CampaignMenuScreen::repopulateAvailableMissions() { - for(unsigned n=0; nclear(); + for (unsigned i = 0; i < campaign.getMapCount(); ++i) { - if(campaign.getMap(n).getMapName() == availableMissions->get()) - { - return campaign.getMap(n).getMapFileName(); - } + if (campaign.getMap(i).isUnlocked()) + availableMissions->addItem(campaign.getMap(i).getMapName(), campaign.getMap(i).isCompleted()); } - assert(false); - return ""; } diff --git a/src/CampaignMenuScreen.h b/src/CampaignMenuScreen.h index 9ee04ad46..e58cbddb6 100644 --- a/src/CampaignMenuScreen.h +++ b/src/CampaignMenuScreen.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2006 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef CAMPAIGN_MENU_SCREEN_H -#define CAMPAIGN_MENU_SCREEN_H +#pragma once #include "Campaign.h" #include "Glob2Screen.h" @@ -35,7 +19,6 @@ class CampaignMenuScreen : public Glob2Screen public: CampaignMenuScreen(const std::string& name); void onAction(Widget *source, Action action, int par1, int par2); - std::string getMissionName(); void setNewCampaign(); enum { @@ -64,7 +47,8 @@ class CampaignMenuScreen : public Glob2Screen //! The widget that will show a preview of the selection map MapPreview *mapPreview; + //! Rebuild the displayed mission list from the current campaign state. + void repopulateAvailableMissions(); }; -#endif diff --git a/src/CampaignSelectorScreen.cpp b/src/CampaignSelectorScreen.cpp index 8c3e792c6..e79dd2e5b 100644 --- a/src/CampaignSelectorScreen.cpp +++ b/src/CampaignSelectorScreen.cpp @@ -1,26 +1,11 @@ -/* - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault #include "CampaignSelectorScreen.h" #include "StringTable.h" #include "Toolkit.h" #include "Campaign.h" +#include CampaignSelectorScreen::CampaignSelectorScreen(bool isSelectingSave) { @@ -51,7 +36,7 @@ void CampaignSelectorScreen::onAction(Widget *source, Action action, int par1, i if (source == ok) { // we accept only if a valid map is selected - if (fileList->getSelectionIndex()!=-1) + if (fileList->selection()) endExecute(OK); } else if (source == cancel) @@ -61,7 +46,7 @@ void CampaignSelectorScreen::onAction(Widget *source, Action action, int par1, i } if (action == LIST_ELEMENT_SELECTED) { - if (fileList->getSelectionIndex()!=-1) + if (fileList->selection()) { Campaign toload; toload.load(getCampaignName()); @@ -69,7 +54,7 @@ void CampaignSelectorScreen::onAction(Widget *source, Action action, int par1, i } else { - description->setText(""); + description->setText(""); } } } @@ -78,6 +63,8 @@ void CampaignSelectorScreen::onAction(Widget *source, Action action, int par1, i std::string CampaignSelectorScreen::getCampaignName() { - return fileList->fullName(fileList->getText(fileList->getSelectionIndex()).c_str())+".txt"; + auto sel = fileList->selection(); + assert(sel); + return fileList->fullName(fileList->getText(*sel).c_str())+".txt"; } diff --git a/src/CampaignSelectorScreen.h b/src/CampaignSelectorScreen.h index efaf04d94..b85e16a19 100644 --- a/src/CampaignSelectorScreen.h +++ b/src/CampaignSelectorScreen.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2006 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef CAMPAIGN_SELECTOR_SCREEN_H -#define CAMPAIGN_SELECTOR_SCREEN_H +#pragma once #include "Glob2Screen.h" #include "GUIText.h" @@ -55,5 +39,4 @@ class CampaignSelectorScreen : public Glob2Screen }; -#endif diff --git a/src/ChecksumSidecar.cpp b/src/ChecksumSidecar.cpp new file mode 100644 index 000000000..e0e76acf3 --- /dev/null +++ b/src/ChecksumSidecar.cpp @@ -0,0 +1,135 @@ +#include "ChecksumSidecar.h" +#include "FileFormatVersions.h" +#include "Game.h" +#include "Team.h" +#include "Unit.h" +#include "Building.h" +#include +#include + +ChecksumSidecarWriter::ChecksumSidecarWriter() + : file(NULL), numTeams(0), numPlayers(0), ticksWritten(0) +{ +} + +ChecksumSidecarWriter::~ChecksumSidecarWriter() +{ + close(); +} + +void ChecksumSidecarWriter::writeU16(Uint16 v) +{ + fwrite(&v, sizeof(v), 1, file); +} + +void ChecksumSidecarWriter::writeU32(Uint32 v) +{ + fwrite(&v, sizeof(v), 1, file); +} + +bool ChecksumSidecarWriter::open(const std::string& replayPath, int numTeams, int numPlayers) +{ + std::string path = replayPath + ".checksums"; + file = GAGCore::Toolkit::getFileManager()->openFP(path, "wb"); + if (!file) + return false; + + this->numTeams = numTeams; + this->numPlayers = numPlayers; + ticksWritten = 0; + + // Header: magic + counts + placeholder for total_ticks + flags + fwrite(FILE_SIG_CHECKSUM_SIDECAR, FILE_SIG_LEN, 1, file); + writeU32(numTeams); + writeU32(numPlayers); + writeU32(0); // total_ticks placeholder + writeU32(0); // flags + + return true; +} + +void ChecksumSidecarWriter::writeTick(Uint32 tick, Uint32 totalChecksum, Game& game) +{ + if (!file) + return; + + // Check optional max ticks limit + const char* maxTicksEnv = getenv("GLOB2_CHECKSUM_SIDECAR_MAX_TICKS"); + if (maxTicksEnv) + { + Uint32 maxTicks = atoi(maxTicksEnv); + if (maxTicks > 0 && ticksWritten >= maxTicks) + return; + } + + writeU32(tick); + writeU32(totalChecksum); + + std::vector vec; + + for (int t = 0; t < numTeams; t++) + { + Team* team = game.teams[t]; + + // Team-level checksum + Uint32 teamCs = team->checkSum(NULL, NULL, NULL); + writeU32(teamCs); + + // Units + Uint32 unitCount = 0; + for (int i = 0; i < Unit::MAX_COUNT; i++) + if (team->myUnits[i]) + unitCount++; + writeU32(unitCount); + + for (int i = 0; i < Unit::MAX_COUNT; i++) + { + if (!team->myUnits[i]) + continue; + Unit* u = team->myUnits[i]; + vec.clear(); + Uint32 uCs = u->checkSum(&vec); + writeU16((Uint16)u->gid); + writeU32(uCs); + writeU32((Uint32)vec.size()); + for (size_t j = 0; j < vec.size(); j++) + writeU32(vec[j]); + } + + // Buildings + Uint32 bldgCount = 0; + for (int i = 0; i < Building::MAX_COUNT; i++) + if (team->myBuildings[i]) + bldgCount++; + writeU32(bldgCount); + + for (int i = 0; i < Building::MAX_COUNT; i++) + { + if (!team->myBuildings[i]) + continue; + Building* b = team->myBuildings[i]; + vec.clear(); + Uint32 bCs = b->checkSum(&vec); + writeU16((Uint16)b->gid); + writeU32(bCs); + writeU32((Uint32)vec.size()); + for (size_t j = 0; j < vec.size(); j++) + writeU32(vec[j]); + } + } + + ticksWritten++; +} + +void ChecksumSidecarWriter::close() +{ + if (!file) + return; + + // Patch total_ticks in header + fseek(file, CHECKSUM_SIDECAR_TOTALTICKS_OFFSET, SEEK_SET); + writeU32(ticksWritten); + + fclose(file); + file = NULL; +} diff --git a/src/ChecksumSidecar.h b/src/ChecksumSidecar.h new file mode 100644 index 000000000..fc43d06f9 --- /dev/null +++ b/src/ChecksumSidecar.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include +#include "GAGSys.h" + +class Game; + +//! Byte offset of the `total_ticks` Uint32 inside the sidecar header. +//! Header layout: +//! [0..3] FILE_SIG_CHECKSUM_SIDECAR (4-byte ASCII magic) +//! [4..7] numTeams (Uint32) +//! [8..11] numPlayers (Uint32) +//! [12..15] total_ticks (Uint32) <-- this offset +//! [16..19] flags (Uint32, reserved) +//! Patched at close() once the final tick count is known. If the header +//! layout changes, this offset must change too. See ChecksumSidecar.cpp. +static constexpr long CHECKSUM_SIDECAR_TOTALTICKS_OFFSET = 12; + +class ChecksumSidecarWriter +{ +public: + ChecksumSidecarWriter(); + ~ChecksumSidecarWriter(); + + bool open(const std::string& replayPath, int numTeams, int numPlayers); + void writeTick(Uint32 tick, Uint32 totalChecksum, Game& game); + void close(); + +private: + FILE* file; + int numTeams; + int numPlayers; + Uint32 ticksWritten; + + void writeU16(Uint16 v); + void writeU32(Uint32 v); +}; + diff --git a/src/ChooseMapScreen.cpp b/src/ChooseMapScreen.cpp index e028d3c82..35ae1479f 100644 --- a/src/ChooseMapScreen.cpp +++ b/src/ChooseMapScreen.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "ChooseMapScreen.h" #include "GUIGlob2FileList.h" @@ -29,6 +13,7 @@ #include #include #include +#include #include "Game.h" @@ -89,14 +74,7 @@ ChooseMapScreen::ChooseMapScreen(const char *directory, const char *extension, b { assert(type2 != NONE); - std::string alternativeTypeName; - - if (type2 == GAME) alternativeTypeName = Toolkit::getStringTable()->getString("[the games]"); - else if (type2 == MAP) alternativeTypeName = Toolkit::getStringTable()->getString("[the maps]"); - else if (type2 == REPLAY) alternativeTypeName = Toolkit::getStringTable()->getString("[the replays]"); - else assert(false); - - switchType = new TextButton(250, 420, 180, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "menu", alternativeTypeName.c_str(), SWITCHTYPE, 27); + switchType = new TextButton(250, 420, 180, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "menu", loadableTypeName(type2).c_str(), SWITCHTYPE, 27); addWidget(switchType); alternateFileList = new Glob2FileList(20, 60, 180, 400, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", alternateDirectory, alternateExtension, alternateRecurse); @@ -116,24 +94,16 @@ void ChooseMapScreen::onAction(Widget *source, Action action, int par1, int par2 { if (action == LIST_ELEMENT_SELECTED) { - //LoadableType currentDirectoryType; - - //if (currentDirectoryMode == DisplayRegular) currentDirectoryType = type1; - //else currentDirectoryType = type2; - - if((currentDirectoryMode == DisplayRegular && fileList->getSelectionIndex() != -1) || (currentDirectoryMode == DisplayAlternate && alternateFileList->getSelectionIndex() != -1)) + Glob2FileList* active = activeFileList(); + if (active->selection()) { - std::string mapFileName; - if(currentDirectoryMode == DisplayRegular) - mapFileName = fileList->listToFile(fileList->getText(par1).c_str()); - else - mapFileName = alternateFileList->listToFile(alternateFileList->getText(par1).c_str()); + std::string mapFileName = active->listToFile(active->getText(par1).c_str()); try { mapPreview->setMapThumbnail(mapFileName.c_str()); - InputStream *stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapFileName)); + auto stream = std::unique_ptr(new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapFileName))); if (stream->isEndOfStream()) { std::cerr << "ChooseMapScreen::onAction() : error, can't open file " << mapFileName << std::endl; @@ -142,7 +112,7 @@ void ChooseMapScreen::onAction(Widget *source, Action action, int par1, int par2 { if (verbose) std::cout << "ChooseMapScreen::onAction : loading map " << mapFileName << std::endl; - validMapSelected = mapHeader.load(stream); + validMapSelected = mapHeader.load(stream.get()); if (!validMapSelected) selectedType = NONE; @@ -154,15 +124,11 @@ void ChooseMapScreen::onAction(Widget *source, Action action, int par1, int par2 time_t mtime = Toolkit::getFileManager()->mtime(mapFileName); mapDate->setText(ctime(&mtime)); - if (currentDirectoryMode == DisplayRegular) - selectedType = type1; - else - selectedType = type2; + selectedType = activeType(); } else std::cerr << "ChooseMapScreen::onAction : invalid map header for map " << mapFileName << std::endl; } - delete stream; } catch (std::exception &e) { @@ -198,72 +164,22 @@ void ChooseMapScreen::onAction(Widget *source, Action action, int par1, int par2 else if (source == deleteMap) { // if a valid file is selected, delete it - - if(currentDirectoryMode == DisplayRegular) - { - if (fileList->getSelectionIndex() >= 0) - { - size_t i = fileList->getSelectionIndex(); - std::string mapFileName = fileList->listToFile(fileList->get().c_str()); - - Toolkit::getFileManager()->remove(mapFileName); - fileList->generateList(); - - fileList->setSelectionIndex(std::min(i, fileList->getCount()-1)); - fileList->selectionChanged(); - } - } - else + Glob2FileList* active = activeFileList(); + if (auto sel = active->selection()) { - if (alternateFileList->getSelectionIndex() >= 0) - { - size_t i = alternateFileList->getSelectionIndex(); - std::string mapFileName = alternateFileList->listToFile(alternateFileList->get().c_str()); - - Toolkit::getFileManager()->remove(mapFileName); - alternateFileList->generateList(); - - alternateFileList->setSelectionIndex(std::min(i, fileList->getCount()-1)); - alternateFileList->selectionChanged(); - } + size_t i = *sel; + std::string mapFileName = active->listToFile(active->get().c_str()); + + Toolkit::getFileManager()->remove(mapFileName); + active->generateList(); + + active->setSelectionIndex(std::min(i, active->getCount() - 1)); + active->selectionChanged(); } } else if (source == switchType) { - if(currentDirectoryMode == DisplayRegular) - { - assert(type1 != NONE); - - std::string newTypeName; - - if (type1 == GAME) newTypeName = Toolkit::getStringTable()->getString("[the games]"); - else if (type1 == MAP) newTypeName = Toolkit::getStringTable()->getString("[the maps]"); - else if (type1 == REPLAY) newTypeName = Toolkit::getStringTable()->getString("[the replays]"); - else assert(false); - - currentDirectoryMode = DisplayAlternate; - fileList->visible=false; - alternateFileList->visible=true; - switchType->setText(newTypeName); - alternateFileList->selectionChanged(); - } - else - { - assert(type2 != NONE); - - std::string newTypeName; - - if (type2 == GAME) newTypeName = Toolkit::getStringTable()->getString("[the games]"); - else if (type2 == MAP) newTypeName = Toolkit::getStringTable()->getString("[the maps]"); - else if (type2 == REPLAY) newTypeName = Toolkit::getStringTable()->getString("[the replays]"); - else assert(false); - - currentDirectoryMode = DisplayRegular; - fileList->visible=true; - alternateFileList->visible=false; - switchType->setText(newTypeName); - fileList->selectionChanged(); - } + setDirectoryMode(currentDirectoryMode == DisplayRegular ? DisplayAlternate : DisplayRegular); } } } @@ -301,3 +217,37 @@ ChooseMapScreen::LoadableType ChooseMapScreen::getSelectedType() { return selectedType; } + +Glob2FileList* ChooseMapScreen::activeFileList() const +{ + return (currentDirectoryMode == DisplayRegular) ? fileList : alternateFileList; +} + +ChooseMapScreen::LoadableType ChooseMapScreen::activeType() const +{ + return (currentDirectoryMode == DisplayRegular) ? type1 : type2; +} + +std::string ChooseMapScreen::loadableTypeName(LoadableType type) +{ + switch (type) + { + case GAME: return Toolkit::getStringTable()->getString("[the games]"); + case MAP: return Toolkit::getStringTable()->getString("[the maps]"); + case REPLAY: return Toolkit::getStringTable()->getString("[the replays]"); + case NONE: break; + } + assert(false); + return {}; +} + +void ChooseMapScreen::setDirectoryMode(DirectoryMode newMode) +{ + currentDirectoryMode = newMode; + const bool regular = (newMode == DisplayRegular); + fileList->visible = regular; + alternateFileList->visible = !regular; + // After switching, the button label points back to the list we just left. + switchType->setText(loadableTypeName(regular ? type2 : type1)); + activeFileList()->selectionChanged(); +} diff --git a/src/ChooseMapScreen.h b/src/ChooseMapScreen.h index 129051b81..53b82a687 100644 --- a/src/ChooseMapScreen.h +++ b/src/ChooseMapScreen.h @@ -1,29 +1,13 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __CHOOSE_MAP_SCREEN_H -#define __CHOOSE_MAP_SCREEN_H +#pragma once #include "MapHeader.h" #include "GameHeader.h" #include "Glob2Screen.h" #include +#include namespace GAGGUI { @@ -115,7 +99,7 @@ class ChooseMapScreen : public Glob2Screen //! The widget that will show a preview of the selection map MapPreview *mapPreview; //! The textual informations about the selected map - Text *mapName, *mapInfo, *mapVersion, *mapSize, *mapDate, *varPrestigeText; + Text *mapName, *mapInfo, *mapVersion, *mapSize, *mapDate; //! True when the selected map is valid bool validMapSelected; //! Default type @@ -126,8 +110,19 @@ class ChooseMapScreen : public Glob2Screen /// Called after a new mapHeader and gameHeader have been loaded. void updateMapInformation(); + /// Returns the file list currently shown: fileList when DisplayRegular, alternateFileList when DisplayAlternate. + Glob2FileList* activeFileList() const; + + /// Returns the LoadableType paired with the active list: type1 when DisplayRegular, type2 when DisplayAlternate. + LoadableType activeType() const; + + /// Maps a LoadableType to its display string ([the games]/[the maps]/[the replays]). Asserts on NONE. + static std::string loadableTypeName(LoadableType type); + + /// Switches to newMode: flips list visibility, sets the switchType button label to the other list's type name, and fires selectionChanged() on the newly-active list. + void setDirectoryMode(DirectoryMode newMode); + /// Designates whether there will be verbose debugging output. static const bool verbose = false; }; -#endif diff --git a/src/ConfigFiles.h b/src/ConfigFiles.h index 34c3106f8..bf632cc61 100644 --- a/src/ConfigFiles.h +++ b/src/ConfigFiles.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __CONFIG_FILES_H -#define __CONFIG_FILES_H +#pragma once #include #include @@ -187,5 +170,3 @@ class ConfigVector const std::string getNameById(size_t id) { return entriesToName[id]; } }; - -#endif diff --git a/src/CreditScreen.cpp b/src/CreditScreen.cpp index b1ef3e428..9261f72b3 100644 --- a/src/CreditScreen.cpp +++ b/src/CreditScreen.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "CreditScreen.h" #include "GlobalContainer.h" @@ -28,6 +12,7 @@ using namespace GAGGUI; #include #include #include +#include using namespace GAGCore; // using namespace std; diff --git a/src/CreditScreen.h b/src/CreditScreen.h index ae04fed5c..d1b2bdd59 100644 --- a/src/CreditScreen.h +++ b/src/CreditScreen.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __CREDIT_SCREEN_H -#define __CREDIT_SCREEN_H +#pragma once #include "Glob2Screen.h" @@ -30,4 +13,3 @@ class CreditScreen : public Glob2Screen void onAction(Widget *source, Action action, int par1, int par2); }; -#endif diff --git a/src/CustomGameOtherOptions.cpp b/src/CustomGameOtherOptions.cpp index f8e804144..cd0d00f9d 100644 --- a/src/CustomGameOtherOptions.cpp +++ b/src/CustomGameOtherOptions.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "CustomGameOtherOptions.h" @@ -26,7 +11,7 @@ #include CustomGameOtherOptions::CustomGameOtherOptions(GameHeader& gameHeader, MapHeader& mapHeader, bool readOnly) - : gameHeader(gameHeader), oldGameHeader(gameHeader), mapHeader(mapHeader) + : gameHeader(gameHeader), oldGameHeader(gameHeader) { ok = new TextButton(440, (readOnly ? 420 : 360), 180, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[ok]"), OK, 13); addWidget(ok); @@ -40,15 +25,6 @@ CustomGameOtherOptions::CustomGameOtherOptions(GameHeader& gameHeader, MapHeader title = new Text(0, 18, ALIGN_FILL, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[Other Options]")); addWidget(title); - playerNames=new Text*[32]; - color=new ColorButton*[32]; - allyTeamNumbers=new MultiTextButton*[32]; - for(int i=0; i >& winningConditions = gameHeader.getWinningConditions(); + std::list >& winningConditions = gameHeader.getWinningConditions(); winningConditions = WinningCondition::getDefaultWinningConditions(); //Update the prestige condition - for(std::list >::iterator i = winningConditions.begin(); i!=winningConditions.end(); ++i) + for(std::list >::iterator i = winningConditions.begin(); i!=winningConditions.end(); ++i) { if((*i)->getType() == WCPrestige) { @@ -198,11 +174,11 @@ void CustomGameOtherOptions::updateGameHeaderWinningConditions() void CustomGameOtherOptions::updateScreenWinningConditions() { - std::list >& winningConditions = gameHeader.getWinningConditions(); + std::list >& winningConditions = gameHeader.getWinningConditions(); //Update the prestige condition prestigeWinEnabled->setState(false); - for(std::list >::iterator i = winningConditions.begin(); i!=winningConditions.end(); ++i) + for(std::list >::iterator i = winningConditions.begin(); i!=winningConditions.end(); ++i) { if((*i)->getType() == WCPrestige) { diff --git a/src/CustomGameOtherOptions.h b/src/CustomGameOtherOptions.h index 53b296392..ab536c69b 100644 --- a/src/CustomGameOtherOptions.h +++ b/src/CustomGameOtherOptions.h @@ -1,23 +1,9 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef CustomGameOtherOptions_h -#define CustomGameOtherOptions_h +#include #include "AI.h" #include "Glob2Screen.h" @@ -75,11 +61,11 @@ class CustomGameOtherOptions : public Glob2Screen TextButton* cancel; ///List of the player names - Text ** playerNames; + std::array playerNames{}; //! Player colors - ColorButton ** color; - //! Player ally temas - MultiTextButton ** allyTeamNumbers; + std::array color{}; + //! Player ally teams + std::array allyTeamNumbers{}; ///Button fixing teams during the match OnOffButton *teamsFixed; @@ -103,7 +89,5 @@ class CustomGameOtherOptions : public Glob2Screen GameHeader& gameHeader; GameHeader oldGameHeader; - MapHeader& mapHeader; }; -#endif diff --git a/src/CustomGameScreen.cpp b/src/CustomGameScreen.cpp index a1db372cc..f1836b429 100644 --- a/src/CustomGameScreen.cpp +++ b/src/CustomGameScreen.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "CustomGameScreen.h" #include "Utilities.h" @@ -37,7 +21,7 @@ CustomGameScreen::CustomGameScreen() : ChooseMapScreen("maps", "map", true) { - for (int i=0; iclearColors(); for (int j = 0; jshow(); } // Close the rest - for (; isetState(false); color[i]->hide(); @@ -196,36 +185,59 @@ int CustomGameScreen::getSelectedColor(int i) +namespace +{ + // 1-based ally-team IDs. GameHeader::reset() seeds allyTeamNumbers[i] = i+1, + // so team 1 and team 2 are the lowest two groups available. + constexpr Uint8 HUMAN_ALLY_TEAM = 1; + constexpr Uint8 ENEMY_ALLY_TEAM = 2; + + FormatableString aiSelectorName(AI::ImplementitionID iid, int selectorIndex) + { + // selectorIndex is the position in the visible selector list; selector 0 + // is the human, so AI selectors start at 1 and display as "AI Name N" + // with N = selectorIndex - 1. + FormatableString name("%0 %1"); + name.arg(AINames::getAIText(iid)).arg(selectorIndex - 1); + return name; + } +} + +// Rebuilds gameHeader.players[] from the current selector widget state. +// Active selectors are packed into slots [0..count); slots [count..MAX_COUNT) +// are cleared to default BasePlayer{} so stale entries from earlier edits +// (or earlier map selections) cannot leak into save files or network packets, +// which serialize all MAX_COUNT_ON_DISK slots regardless of numberOfPlayers. +// Ally teams: the human's color goes on HUMAN_ALLY_TEAM, every other color +// on ENEMY_ALLY_TEAM. void CustomGameScreen::updatePlayers() { + for (int i = 0; i < Team::MAX_COUNT; i++) + gameHeader.getBasePlayer(i) = BasePlayer(); + int count = 0; int humanColor = 0; - for (int i=0; isettings.getUsername().c_str(), teamColor, BasePlayer::P_LOCAL); - humanColor = teamColor; - gameHeader.setAllyTeamNumber(teamColor, 1); - } - else - { - AI::ImplementitionID iid=getAiImplementation(i); - FormatableString name("%0 %1"); - name.arg(AINames::getAIText(iid)).arg(i-1); - gameHeader.getBasePlayer(count) = BasePlayer(i, name.c_str(), teamColor, Player::playerTypeFromImplementitionID(iid)); - if(teamColor != humanColor) - gameHeader.setAllyTeamNumber(teamColor, 2); - } - count+=1; + gameHeader.getBasePlayer(count) = BasePlayer(0, globalContainer->settings.getUsername().c_str(), teamColor, BasePlayer::P_LOCAL); + humanColor = teamColor; + gameHeader.setAllyTeamNumber(teamColor, HUMAN_ALLY_TEAM); } else { - gameHeader.getBasePlayer(i) = BasePlayer(); + AI::ImplementitionID iid = getAiImplementation(i); + FormatableString name = aiSelectorName(iid, i); + gameHeader.getBasePlayer(count) = BasePlayer(i, name.c_str(), teamColor, Player::playerTypeFromImplementitionID(iid)); + if (teamColor != humanColor) + gameHeader.setAllyTeamNumber(teamColor, ENEMY_ALLY_TEAM); } + count += 1; } gameHeader.setNumberOfPlayers(count); } diff --git a/src/CustomGameScreen.h b/src/CustomGameScreen.h index b4ef8ea3b..3bbe183f6 100644 --- a/src/CustomGameScreen.h +++ b/src/CustomGameScreen.h @@ -1,27 +1,11 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __CUSTOM_GAME_SCREEN_H -#define __CUSTOM_GAME_SCREEN_H +#pragma once #include "AI.h" #include "ChooseMapScreen.h" +#include "Team.h" #include using namespace GAGGUI; @@ -37,8 +21,6 @@ namespace GAGGUI class Glob2FileList; class MapPreview; -const int NumberOfPlayerSelectors=12; - //! This screen is used to setup a custom game. AI can be set. Map choosing functionnalities are inherited from ChooseMapScreen class CustomGameScreen : public ChooseMapScreen { @@ -64,13 +46,13 @@ class CustomGameScreen : public ChooseMapScreen void updatePlayers(); //! Player enable/disable buttons - OnOffButton *isPlayerActive[NumberOfPlayerSelectors]; + OnOffButton *isPlayerActive[Team::MAX_COUNT]; //! Team color buttons - ColorButton *color[NumberOfPlayerSelectors]; + ColorButton *color[Team::MAX_COUNT]; //! Text shown when entry is disabled - Text *closedText[NumberOfPlayerSelectors]; + Text *closedText[Team::MAX_COUNT]; //! Multi-text button containing names of available Players - MultiTextButton *aiSelector[NumberOfPlayerSelectors]; + MultiTextButton *aiSelector[Team::MAX_COUNT]; //! Text button that links to the custom game other settings screen TextButton* otherOptions; //! Text button that links to the ai descriptions @@ -78,4 +60,3 @@ class CustomGameScreen : public ChooseMapScreen }; -#endif diff --git a/src/DatasetWriter.cpp b/src/DatasetWriter.cpp new file mode 100644 index 000000000..1f9eeb4cc --- /dev/null +++ b/src/DatasetWriter.cpp @@ -0,0 +1,279 @@ +#include "DatasetWriter.h" + +#include +#include + +#include "Building.h" +#include "BuildingUtils.h" +#include "FileManager.h" +#include "Game.h" +#include "IntBuildingType.h" +#include "Map.h" +#include "Order.h" +#include "Player.h" +#include "Ressource.h" +#include "Team.h" +#include "TeamStat.h" +#include "Toolkit.h" +#include "Unit.h" +#include "UnitConsts.h" +#include "UnitUtils.h" + +DatasetWriter::DatasetWriter() + : file(NULL), numRecords(0) +{ +} + +DatasetWriter::~DatasetWriter() +{ + close(); +} + +void DatasetWriter::writeU32(Uint32 v) +{ + fwrite(&v, 4, 1, file); +} + +void DatasetWriter::writeI32(Sint32 v) +{ + fwrite(&v, 4, 1, file); +} + +bool DatasetWriter::open(const std::string& path) +{ + // Match ReplayWriter's absolute-path bypass: FileManager::openFP + // prepends every dirList entry, which turns absolute paths into + // nonsense (~/.glob2//tmp/foo). The trainer pipeline relies on + // arbitrary absolute paths working as written. + if (!path.empty() && path[0] == '/') + file = fopen(path.c_str(), "wb"); + else + file = GAGCore::Toolkit::getFileManager()->openFP(path, "wb"); + + if (!file) + return false; + + numRecords = 0; + + // Header: magic + num_records placeholder. No version field — there's + // only one producer and one consumer (in this repo) and regenerating + // datasets is cheap, so we'd never need to support multiple versions + // in flight. If the format ever changes wire-incompatibly, bump the + // magic to "GDS2" and parsers reject by magic mismatch. + fwrite("GDS1", 4, 1, file); + writeU32(0); // num_records placeholder, patched in close() + + return true; +} + +void DatasetWriter::writeRecord(Uint32 tick, Order& order, Game& game) +{ + if (!file) + return; + + // Skip non-action orders, matching the criteria ReplayWriter::pushOrder + // uses to filter what reaches the replay stream. ORDER_NULL fires every + // tick for every player ("did nothing") — without this skip the dataset + // is dominated by non-actions and num_records balloons (4 players × + // 30k ticks ≈ 120k records, of which only a few thousand are real + // AI decisions). + Uint8 type = order.getOrderType(); + if (type == ORDER_NULL || type == ORDER_VOICE_DATA) + return; + + writeU32(tick); + Uint8 sender = (Uint8)order.sender; + fwrite(&sender, 1, 1, file); + fwrite(&type, 1, 1, file); + + // State blob (observation features). Length-prefixed so the parser + // can skip past it without knowing the schema. Rather than computing + // the size up front, write a placeholder, dump the blob, then patch + // the length back in. + long stateLenOffset = ftell(file); + writeU32(0); + long stateStart = ftell(file); + + int senderTeamNum = game.players[order.sender]->team->teamNumber; + writeStateBlob(senderTeamNum, game); + + long stateEnd = ftell(file); + Uint32 stateLen = (Uint32)(stateEnd - stateStart); + fseek(file, stateLenOffset, SEEK_SET); + writeU32(stateLen); + fseek(file, stateEnd, SEEK_SET); + + // Order payload, exactly as Order::getData() returns it. + int payloadLen = order.getDataLength(); + writeU32((Uint32)payloadLen); + if (payloadLen > 0) + fwrite(order.getData(), 1, payloadLen, file); + + numRecords++; +} + +void DatasetWriter::writeStateBlob(int senderTeamNum, Game& game) +{ + Team* senderTeam = game.teams[senderTeamNum]; + const TeamStat* stat = const_cast(senderTeam->stats).getLatestStat(); + + // num_teams = 1 — bot-team-only by design (kyle approved). Enemy + // internal state would leak omniscient info; the spatial grid encodes + // visible enemy presence already. The redundant length prefix keeps + // the layout extensible if we ever revisit. + writeU32(1); + + // Bot-team scalars. + writeI32(senderTeam->prestige); + + Uint32 flags = 0; + if (senderTeam->isAlive) flags |= 1u << 0; + if (senderTeam->hasWon) flags |= 1u << 1; + if (senderTeam->hasLost) flags |= 1u << 2; + writeU32(flags); + + for (int i = 0; i < MAX_NB_RESSOURCES; i++) + writeI32(senderTeam->teamRessources[i]); + + for (int i = 0; i < NB_UNIT_TYPE; i++) + writeI32(stat->numberUnitPerType[i]); + + for (int i = 0; i < IntBuildingType::NB_BUILDING; i++) + writeI32(stat->numberBuildingPerType[i]); + + // Spatial grid. Downsample from the actual map to a fixed-max + // GRID_W × GRID_H. For maps smaller than GRID_W/GRID_H we shrink the + // grid to match (avoids padding empty cells). + const Map& map = game.map; + int mapW = map.getW(); + int mapH = map.getH(); + int gridW = std::min(mapW, (int)GRID_W); + int gridH = std::min(mapH, (int)GRID_H); + int stepX = std::max(1, mapW / gridW); + int stepY = std::max(1, mapH / gridH); + + writeU32((Uint32)gridW); + writeU32((Uint32)gridH); + + // Vision mask: cells we directly see plus everything our allies share + // with us via the three sharedVision channels (matches AI omniscience + // boundary; rule-based AIs reason from the same masked view). + Uint32 visionMask = senderTeam->me + | senderTeam->sharedVisionExchange + | senderTeam->sharedVisionFood + | senderTeam->sharedVisionOther; + + for (int gy = 0; gy < gridH; gy++) + { + int y0 = gy * stepY; + for (int gx = 0; gx < gridW; gx++) + { + int x0 = gx * stepX; + + // Terrain: take the top-left source cell (categorical; we'd + // need a histogram to do better and the model can learn around + // downsample artifacts). + int tt = map.getTerrainType(x0, y0); + Uint8 terrain = (tt < 0) ? 255 : (Uint8)tt; + + Uint32 resourceSum = 0; + Uint32 myUnitCount = 0; + Uint32 enemyUnitCount = 0; + Uint8 myBuilding = 0; + Uint8 enemyBuilding = 0; + bool anyCurrentlyVisible = false; + bool anyEverSeen = false; + + for (int dy = 0; dy < stepY; dy++) + { + int sy = y0 + dy; + for (int dx = 0; dx < stepX; dx++) + { + int sx = x0 + dx; + bool currentlyVisible = map.isFOWDiscovered(sx, sy, visionMask); + bool everSeen = map.isMapDiscovered(sx, sy, visionMask); + if (currentlyVisible) anyCurrentlyVisible = true; + if (everSeen) anyEverSeen = true; + + if (currentlyVisible) + { + const Ressource& r = map.getRessource(sx, sy); + if (r.type != NO_RES_TYPE) + resourceSum += r.amount; + } + + // Ground + air units in the same channel — separation + // would burn channels for marginal signal (air is rare). + Uint16 guid = map.getGroundUnit(sx, sy); + Uint16 auid = map.getAirUnit(sx, sy); + for (int pass = 0; pass < 2; pass++) + { + Uint16 gid = (pass == 0) ? guid : auid; + if (gid == NOGUID) continue; + int unitTeam = Unit::GIDtoTeam(gid); + if (unitTeam == senderTeamNum) + myUnitCount++; + else if (currentlyVisible) + enemyUnitCount++; + } + + Uint16 bgid = map.getBuilding(sx, sy); + if (bgid != NOGBID) + { + int bTeam = Building::GIDtoTeam(bgid); + int bId = Building::GIDtoID(bgid); + Building* b = NULL; + if (bTeam >= 0 && bTeam < game.mapHeader.getNumberOfTeams() && bId >= 0 && bId < Building::MAX_COUNT) + b = game.teams[bTeam]->myBuildings[bId]; + if (b) + { + // shortTypeNum is 0..NB_BUILDING-1; shift by 1 so + // 0 == "no building" stays unambiguous. + Uint8 typeId = (Uint8)(b->shortTypeNum + 1); + if (bTeam == senderTeamNum) + { + // First-write-wins on collision — multiple + // of my buildings inside one downsampled + // cell is rare and any type is fine. + if (myBuilding == 0) myBuilding = typeId; + } + else if (currentlyVisible) + { + if (enemyBuilding == 0) enemyBuilding = typeId; + } + } + } + } + } + + Uint8 chTerrain = terrain; + Uint8 chResource = (Uint8)std::min(resourceSum, (Uint32)255); + Uint8 chMyUnits = (Uint8)std::min(myUnitCount, (Uint32)255); + Uint8 chEnemyUnits = (Uint8)std::min(enemyUnitCount, (Uint32)255); + Uint8 chMyBuilding = myBuilding; + Uint8 chEnemyBuilding = enemyBuilding; + Uint8 chDiscovery = anyCurrentlyVisible ? 2 : (anyEverSeen ? 1 : 0); + + fwrite(&chTerrain, 1, 1, file); + fwrite(&chResource, 1, 1, file); + fwrite(&chMyUnits, 1, 1, file); + fwrite(&chEnemyUnits, 1, 1, file); + fwrite(&chMyBuilding, 1, 1, file); + fwrite(&chEnemyBuilding, 1, 1, file); + fwrite(&chDiscovery, 1, 1, file); + } + } +} + +void DatasetWriter::close() +{ + if (!file) + return; + + // Patch num_records at offset 4 (right after the magic). + fseek(file, 4, SEEK_SET); + writeU32(numRecords); + + fclose(file); + file = NULL; +} diff --git a/src/DatasetWriter.h b/src/DatasetWriter.h new file mode 100644 index 000000000..d73b9888e --- /dev/null +++ b/src/DatasetWriter.h @@ -0,0 +1,101 @@ +/* + AI-trainer dataset writer. + + Writes one record per executed Order to a binary file. Each record is + a (state_blob, action) pair the trainer's BC pipeline consumes — the + state_blob is the bot-team-only scalars + a fog-of-war-filtered 32×32×7 + spatial grid, computed at order time from the live Game state. See + glob2-ai-trainer/docs/training-design.md §6 for the full rationale. + + Triggered by the GLOB2_DATASET_PATH env var, mirroring GLOB2_REPLAY_PATH + and GLOB2_CHECKSUM_SIDECAR. + + Binary format (little-endian, fixed-size where shown): + + HEADER (8 bytes) + [4B] magic "GDS1" + [4B] u32 num_records (patched on close()) + + PER-RECORD + [4B] u32 tick + [1B] u8 sender_player_index + [1B] u8 order_type + [4B] u32 state_blob_len + [state_blob_len bytes] observation features (see layout below) + [4B] u32 order_payload_len + [order_payload_len bytes] order payload from Order::getData() + + STATE BLOB (variable size; ~7.3 KB at GRID_W=GRID_H=32) + [4B] u32 num_teams (always 1 — bot-team-only by design) + per team: + [4B] i32 prestige + [4B] u32 flags (bit0=isAlive, bit1=hasWon, bit2=hasLost) + [4B × 15] i32 teamRessources (MAX_NB_RESSOURCES) + [4B × 3] i32 unit_count_by_type (WORKER, EXPLORER, WARRIOR) + [4B × 13] i32 building_count_by_type (NB_BUILDING) + [4B] u32 grid_w (≤32, == min(map_w, 32)) + [4B] u32 grid_h (≤32, == min(map_h, 32)) + per cell × 7 channels (HWC; row-major, gy outer, gx inner): + [1B] terrain (0=GRASS, 1=SAND, 2=WATER, 255=other) + [1B] resource_amount (sum across cell, capped at 255; FOW: visible only) + [1B] my_unit_count (capped at 255; always shown — units are mine) + [1B] enemy_unit_count (capped at 255; FOW: visible only) + [1B] my_building_type (0=none, 1..NB_BUILDING; always shown) + [1B] enemy_building_type (0=none, 1..NB_BUILDING; FOW: visible only) + [1B] discovery (0=unknown, 1=previously seen, 2=currently visible) + + No version field: there's a single producer (this writer) and a single + consumer (the trainer's `dataset.rs` parser), regenerating datasets is + cheap, and we'd never need to support multiple wire formats in flight. + If the schema ever changes wire-incompatibly, bump the magic to "GDS2" + and parsers reject by magic mismatch. +*/ + +#pragma once + +#include +#include +#include "GAGSys.h" + +class Order; +class Game; + +class DatasetWriter +{ +public: + DatasetWriter(); + ~DatasetWriter(); + + /// Open the dataset file at `path`. Absolute paths are opened + /// directly with fopen (matching ReplayWriter's bypass of the + /// FileManager dirList prepend); relative paths go through the + /// FileManager search dirs. Returns true on success. + bool open(const std::string& path); + + bool isValid() const { return file != NULL; } + + /// Append one record. Called from Game::executeOrder for each order + /// pushed through the engine. The state blob is computed from `game` + /// using the sender's vision mask for fog-of-war filtering. + void writeRecord(Uint32 tick, Order& order, Game& game); + + /// Patch num_records into the header and close the file. + void close(); + + /// Grid dimensions for the spatial channels of the state blob. The + /// actual grid_w/grid_h written per record is min(map_w, GRID_W) / + /// min(map_h, GRID_H), so smaller maps don't pad with empty cells. + /// constexpr (rather than static const int) so std::min taking const& + /// doesn't force an out-of-class definition. + static constexpr int GRID_W = 32; + static constexpr int GRID_H = 32; + +private: + FILE* file; + Uint32 numRecords; + + void writeU32(Uint32 v); + void writeI32(Sint32 v); + void writeStateBlob(int senderTeamNum, Game& game); +}; + diff --git a/src/DynamicClouds.cpp b/src/DynamicClouds.cpp index 4a81092bf..d6004c1ec 100644 --- a/src/DynamicClouds.cpp +++ b/src/DynamicClouds.cpp @@ -1,25 +1,5 @@ -/*************************************************************************** - * HeightMapGenerator.h - * - * Sun Feb 4 16:17:38 2007 - * Copyright 2007 Leo Wandersleb - * Email: Leo.Wandersleb@gmx.de -*/ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Leo Wandersleb #include "DynamicClouds.h" #include "GraphicContext.h" diff --git a/src/DynamicClouds.h b/src/DynamicClouds.h index a884198d5..a81bb4b89 100644 --- a/src/DynamicClouds.h +++ b/src/DynamicClouds.h @@ -1,28 +1,7 @@ -/*************************************************************************** - * HeightMapGenerator.h - * - * Sun Feb 4 16:17:38 2007 - * Copyright 2007 Leo Wandersleb - * Email: Leo.Wandersleb@gmx.de -*/ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Leo Wandersleb -#ifndef _DYNAMICCLOUDS_H -#define _DYNAMICCLOUDS_H +#pragma once #include "PerlinNoise.h" #include "Settings.h" @@ -111,4 +90,3 @@ class DynamicClouds void render(DrawableSurface *dest, const int viewPortWidth, const int viewPortHeight, Layer layer); }; -#endif /* _DYNAMICCLOUDS_H */ diff --git a/src/EditorMainMenu.cpp b/src/EditorMainMenu.cpp index d808f43e8..c7facccfb 100644 --- a/src/EditorMainMenu.cpp +++ b/src/EditorMainMenu.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "CampaignEditor.h" #include "CampaignSelectorScreen.h" @@ -90,7 +73,7 @@ void EditorMainMenu::onAction(Widget *source, Action action, int par1, int par2) } else if (par1==LOADMAP) { - ChooseMapScreen chooseMapScreen("maps", "map", false, "games", "game", NULL); + ChooseMapScreen chooseMapScreen("maps", "map", false, "games", "game", false); int rc=chooseMapScreen.execute(globalContainer->gfx, 40); if (rc==ChooseMapScreen::OK) { diff --git a/src/EditorMainMenu.h b/src/EditorMainMenu.h index 04a67d0e4..2d6e309ef 100644 --- a/src/EditorMainMenu.h +++ b/src/EditorMainMenu.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef EditorMainMenu_h -#define EditorMainMenu_h +#pragma once #include "Glob2Screen.h" @@ -56,4 +38,3 @@ class EditorMainMenu : public Glob2Screen -#endif diff --git a/src/EndGameScreen.cpp b/src/EndGameScreen.cpp index be825cdd8..f00a4a29f 100644 --- a/src/EndGameScreen.cpp +++ b/src/EndGameScreen.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault #include "EndGameScreen.h" #include @@ -32,6 +15,7 @@ #include #include "GlobalContainer.h" #include "Team.h" +#include "TeamDisplay.h" #include "GameGUILoadSave.h" #include "StreamBackend.h" #include "ReplayWriter.h" @@ -361,8 +345,7 @@ EndGameScreen::EndGameScreen(GameGUI *gui) else strText = Toolkit::getStringTable()->getString("[Lost : %0 has more prestige than you]"); - std::string playerText = t->getFirstPlayerName(); - strText.arg(playerText); + strText.arg(displayPlayerName(*t)); titleText = strText; } } diff --git a/src/EndGameScreen.h b/src/EndGameScreen.h index 505e29995..fca4a2bb7 100644 --- a/src/EndGameScreen.h +++ b/src/EndGameScreen.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __END_GAME_SCREEN_H -#define __END_GAME_SCREEN_H +#pragma once #include "GameGUI.h" #include "Glob2Screen.h" @@ -104,4 +86,3 @@ class EndGameScreen : public Glob2Screen void saveReplay(const char *dir, const char *ext); }; -#endif diff --git a/src/Engine.cpp b/src/Engine.cpp index 6e22bfdf3..24bee9ade 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -1,47 +1,16 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include -#include -#include -#include -#include -#include #include +#include -#include "AINames.h" -#include "CustomGameScreen.h" #include "EndGameScreen.h" #include "Engine.h" -#include "Game.h" +#include "EngineTiming.h" #include "GlobalContainer.h" #include "LogFileManager.h" -#include "Utilities.h" -#include "YOGClientLobbyScreen.h" #include "SoundMixer.h" -#include "Player.h" -#include "NetMessage.h" -#include "GameGUIDialog.h" -#include "GUIMessageBox.h" -#include "ReplayReader.h" -#include "ReplayWriter.h" -#include "SDLCompat.h" #include @@ -49,6 +18,7 @@ Engine::Engine() { net=NULL; + checksumSidecar=NULL; logFile = globalContainer->logFileManager->getFile("Engine.log"); } @@ -63,197 +33,6 @@ Engine::~Engine() } } - - -int Engine::initCampaign(const std::string &mapName, Campaign& campaign, const std::string& missionName) -{ - MapHeader mapHeader = loadMapHeader(mapName); - GameHeader gameHeader = loadGameHeader(mapName); - if(gameHeader.getNumberOfPlayers() == 0) - { - gameHeader = prepareCampaign(mapHeader, gui.localPlayer, gui.localTeamNo); - } - else - { - gui.localPlayer = 0; - gui.localTeamNo = gameHeader.getBasePlayer(0).teamNumber; - } - - gameHeader.getBasePlayer(0).name = campaign.getPlayerName(); - - int end=initGame(mapHeader, gameHeader); - gui.setCampaignGame(campaign, missionName); - return end; -} - - - -int Engine::initCampaign(const std::string &mapName) -{ - MapHeader mapHeader = loadMapHeader(mapName); - GameHeader gameHeader = loadGameHeader(mapName); - if(gameHeader.getNumberOfPlayers() == 0) - { - gameHeader = prepareCampaign(mapHeader, gui.localPlayer, gui.localTeamNo); - } - else - { - gui.localPlayer = 0; - gui.localTeamNo = gameHeader.getBasePlayer(0).teamNumber; - } - int end=initGame(mapHeader, gameHeader); - return end; -} - - - -int Engine::initCustom(void) -{ - CustomGameScreen customGameScreen; - - int cgs=customGameScreen.execute(globalContainer->gfx, 40); - - if (cgs==CustomGameScreen::CANCEL) - return EE_CANCEL; - if (cgs==-1) - return -1; - - int teamColor=customGameScreen.getSelectedColor(0); - gui.localPlayer=0; - gui.localTeamNo=teamColor; - - int ret = initGame(customGameScreen.getMapHeader(), customGameScreen.getGameHeader()); - if(ret != EE_NO_ERROR) - return EE_CANT_LOAD_MAP; - else if(ret == -1) - return -1; - - return EE_NO_ERROR; -} - -int Engine::initCustom(const std::string &gameName) -{ - MapHeader mapHeader = loadMapHeader(gameName); - GameHeader gameHeader = loadGameHeader(gameName); - - // If the game is a network saved game, we need to toogle net players to ai players: - for (int p=0; pgfx, 40); - if (lgs == ChooseMapScreen::CANCEL) - return EE_CANCEL; - else if(lgs == -1) - return -1; - - assert(loadGameScreen.getSelectedType() != ChooseMapScreen::NONE); - assert(loadGameScreen.getSelectedType() != ChooseMapScreen::MAP); - - if (loadGameScreen.getSelectedType() == ChooseMapScreen::GAME) - return initCustom(loadGameScreen.getMapHeader().getFileName()); - else if (loadGameScreen.getSelectedType() == ChooseMapScreen::REPLAY) - return loadReplay(loadGameScreen.getMapHeader().getFileName(false,true)); - else - assert(false); -} - -int Engine::initMultiplayer(boost::shared_ptr multiplayerGame, boost::shared_ptr client, int localPlayer) -{ - gui.localPlayer = localPlayer; - gui.localTeamNo = multiplayerGame->getGameHeader().getBasePlayer(localPlayer).teamNumber; - multiplayer = multiplayerGame; - initGame(multiplayerGame->getMapHeader(), multiplayerGame->getGameHeader(), true, true); - multiplayer->setNetEngine(net); - - for (int p=0; pgetGameHeader().getNumberOfPlayers(); p++) - { - if (multiplayerGame->getGameHeader().getBasePlayer(p).type==BasePlayer::P_IP) - { - net->prepareForLatency(p, multiplayerGame->getGameHeader().getGameLatency()); - } - } - - net->setNetworkInfo(multiplayerGame->getGameHeader().getOrderRate(), client->getGameConnection()); - - return Engine::EE_NO_ERROR; -} - - - -void Engine::createRandomGame() -{ - bool validMapChosen = false; - MapHeader map; - - while (!validMapChosen) - { - try - { - map = chooseRandomMap(); - validMapChosen = true; - } - catch (std::ios_base::failure &e) - { - validMapChosen = false; - } - } - - std::cout<<"Randomly Chosen Map: "<openInputStreamBackend(mapHeader.getFileName())); - if (stream->isEndOfStream()) - { - delete stream; - return false; - } - delete stream; - MapHeader mh = loadMapHeader(mapHeader.getFileName()); - if(mh != mapHeader) - return false; - return true; -} - - - int Engine::run(void) { bool doRunOnceAgain=true; @@ -277,7 +56,7 @@ int Engine::run(void) musicDirs.push_back(filename); } } - + // select a music randomly // FIXME: implement more intelligent music choosing policy if (!musicDirs.empty()) @@ -285,349 +64,28 @@ int Engine::run(void) size_t musicIndex(rand() % musicDirs.size()); const std::string& musicDir(musicDirs[musicIndex]); std::cerr << "selecting music dir " << musicDir << std::endl; - globalContainer->mix->loadTrack(FormatableString("data/zik/%0/a1.ogg").arg(musicDir), 2); - globalContainer->mix->loadTrack(FormatableString("data/zik/%0/a2.ogg").arg(musicDir), 3); - globalContainer->mix->loadTrack(FormatableString("data/zik/%0/a3.ogg").arg(musicDir), 4); + globalContainer->mix->loadTrack(FormatableString("data/zik/%0/a1.ogg").arg(musicDir), MusicTrack::InGameDefault); + globalContainer->mix->loadTrack(FormatableString("data/zik/%0/a2.ogg").arg(musicDir), MusicTrack::BuildingEvent); + globalContainer->mix->loadTrack(FormatableString("data/zik/%0/a3.ogg").arg(musicDir), MusicTrack::WarEvent); } else { std::cerr << "Warning, no music found!" << std::endl; } - + // Stop menu music, load game music - globalContainer->mix->setNextTrack(2, true); + globalContainer->mix->setNextTrack(MusicTrack::InGameDefault, true); globalContainer->gfx->cursorManager.setDrawColor(gui.getLocalTeam()->color); } - + while (doRunOnceAgain) { - int speed=40; - bool networkReadyToExecute = true; - - // If playing in fast-forward, we process the GUI and draw everything only once every 3 game-steps - // This way, the overall fps stays about the same - int nextGuiStep = 1; - - cpuStats.reset(speed); - - Sint64 needToBeTime = 0; - Uint64 startTime = SDL_GetTicks64(); - unsigned frameNumber = 0; - bool sendBumpUp=false; - - while (gui.isRunning) - { - nextGuiStep--; - - // Set the replay speed - if (globalContainer->replaying) - { - if (globalContainer->replayFastForward && !gui.gamePaused) - { - speed = 12; - if (nextGuiStep < 0) nextGuiStep = 2; - } - else - { - speed = 40; - if (nextGuiStep < 0) nextGuiStep = 0; - } - } - else - { - // Process the GUI as usual, every step - nextGuiStep = 0; - } - - // We always allow the user to use the gui: - if (globalContainer->automaticEndingGame) - { - if (!gui.getLocalTeam()->isAlive && !globalContainer->automaticGameGlobalEndConditions) - { - printf("nox::gui.localTeam is dead\n"); - gui.isRunning = false; - automaticGameEndTick = SDL_GetTicks64(); - } - else if (gui.getLocalTeam()->hasWon && !globalContainer->automaticGameGlobalEndConditions) - { - printf("nox::gui.localTeam has won\n"); - gui.isRunning = false; - automaticGameEndTick = SDL_GetTicks64(); - } - else if (gui.game.totalPrestigeReached) - { - printf("nox::gui.game.totalPrestigeReached\n"); - gui.isRunning = false; - automaticGameEndTick = SDL_GetTicks64(); - } - else if (gui.game.isGameEnded) - { - printf("nox::gui.game.isGameEnded\n"); - gui.isRunning = false; - automaticGameEndTick = SDL_GetTicks64(); - } - } - if(!globalContainer->runNoX && nextGuiStep == 0) - gui.step(); - - if (!gui.hardPause) - { - if(multiplayer && multiplayer->getMultiplayerMode() == MultiplayerGame::NoMode) - { - gui.isRunning = false; - } - - // But some jobs have to be executed synchronously: - if (networkReadyToExecute) - { - gui.syncStep(); - - // The gui.localPlayer may have been updated (in replays) - // Keep them synchronized here - net->setLocalPlayer(gui.localPlayer); - - // We get and push local orders - shared_ptr localOrder = gui.getOrder(); - net->addLocalOrder(localOrder); - } - - // we get and push ai orders, if they are needed for this frame - for (int i=0; iai && !net->orderRecieved(i)) - { - shared_ptr order=gui.game.players[i]->ai->getOrder(gui.gamePaused); - net->pushOrder(order, i, true); - } - } - - gui.game.setWaitingOnMask(net->getWaitingOnMask()); - - if(multiplayer) - multiplayer->update(); - - if(networkReadyToExecute) - { - Uint32 checksum = gui.game.checkSum(NULL, NULL, NULL); - net->advanceStep(checksum); - - // Enable this to do test if checksums in the replay match - //if (globalContainer->replayReader) globalContainer->replayReader->setCheckSum(checksum); - if (globalContainer->replayWriter) globalContainer->replayWriter->setCheckSum(checksum); - } - - // We proceed network: - networkReadyToExecute=net->allOrdersRecieved(); - - - if(networkReadyToExecute) - { - sendBumpUp=false; - if(!net->matchCheckSums()) - { - std::cout<<"Game desychronized."< order=net->retrieveOrder(i); - if (!globalContainer->replaying) - { - gui.executeOrder(order); - } - else if (order->getOrderType() == ORDER_PLAYER_QUIT_GAME || - order->getOrderType() == ORDER_PAUSE_GAME) - { - gui.executeOrder(order); - } - } - net->clearTopOrders(); - } - } - /* - //The network latency bump-up has been disabled for beta 4 release - else if(!sendBumpUp) - { - sendBumpUp=true; - net->increaseLatencyAdjustment(); - } - */ - - // Load the replay's orders - if (globalContainer->replaying) - { - assert(globalContainer->replayReader); - assert(globalContainer->replayReader->isValid()); - - while (globalContainer->replayReader->hasMoreOrdersThisStep()) - { - shared_ptr order = globalContainer->replayReader->retrieveOrder(); - - if (order->getOrderType() != ORDER_PLAYER_QUIT_GAME && - order->getOrderType() != ORDER_PAUSE_GAME && - order->getOrderType() != ORDER_NULL) - { - gui.executeOrder(order); - } - } - - if (globalContainer->replayReader->isFinished()) - { - gui.showEndOfReplayScreen(); - } - } - - // here we do the real work - if (networkReadyToExecute && !gui.gamePaused && !gui.hardPause) - { - if (globalContainer->replaying) - { - assert(globalContainer->replayReader); - globalContainer->replayReader->advanceStep(); - } - - gui.game.syncStep(gui.localTeamNo); - } - } - - if (globalContainer->automaticEndingGame) - { - if ((int)gui.game.stepCounter == globalContainer->automaticEndingSteps) - { - gui.isRunning = false; - automaticGameEndTick = SDL_GetTicks64(); - printf("nox::gui.game.checkSum() = %08x\n", gui.game.checkSum()); - } - } - if(!globalContainer->runNoX) - { - if (nextGuiStep == 0) - { - // we draw - gui.drawAll(gui.localTeamNo); - globalContainer->gfx->nextFrame(); - } - - // if required, save videoshot - if (!(globalContainer->videoshotName.empty()) && - !(globalContainer->gfx->getOptionFlags() & GraphicContext::USEGPU) - ) - { - FormatableString fileName = FormatableString("videoshots/%0.%1.bmp").arg(globalContainer->videoshotName).arg(frameNumber++, 10, 10, '0'); - printf("printing video shot %s\n", fileName.c_str()); - globalContainer->gfx->printScreen(fileName.c_str()); - } - - // we compute timing - needToBeTime += speed; - Sint64 currentTime = static_cast(SDL_GetTicks64()) - static_cast(startTime); - //if we are more than 500 milliseconds behind where we should be, - //then truncate it. This is to avoid playing "catchup" for long - //periods of time if Glob2 recieved allmost no cpu time - if((currentTime - needToBeTime) > 500) - needToBeTime = currentTime - 500; - - //Any inconsistancies in the delays will be smoothed throughout the following frames, - Uint64 delay = std::max(0, needToBeTime - currentTime); - SDL_Delay(delay); - - // we set CPU stats -// net->setLeftTicks(computationAvailableTicks);//We may have to tell others IP players to wait for our slow computer. - gui.setCpuLoad((4000-(delay*100)) / 40); - if (networkReadyToExecute && !gui.gamePaused) - { - cpuStats.addFrameData(delay); - } - } - - if(gui.flushOutgoingAndExit) - { - shared_ptr localOrder = gui.getOrder(); - while(localOrder->getOrderType() != ORDER_NULL) - { - net->addLocalOrder(localOrder); - localOrder = gui.getOrder(); - } - - gui.isRunning=false; - net->flushAllOrders(); - break; - } - } - - if(globalContainer->automaticEndingGame) - { - int time = gui.game.stepCounter; - int seconds = (time / 25) % 60; - int minutes = (time / 25) / 60; - std::cout<< "automaticEndingGame ended: "<setGameResult(YOGGameResultWonGame); - } - else - { - if ((t->allies) & (gui.getLocalTeam()->me)) - multiplayer->setGameResult(YOGGameResultWonGame); - else - multiplayer->setGameResult(YOGGameResultLostGame); - } - } - else if(gui.getLocalTeam()->hasWon) - { - multiplayer->setGameResult(YOGGameResultWonGame); - } - else if (!gui.getLocalTeam()->isAlive) - { - multiplayer->setGameResult(YOGGameResultLostGame); - } - else if (!gui.game.isGameEnded) - { - multiplayer->setGameResult(YOGGameResultQuitGame); - } - } + runOneGameSession(doRunOnceAgain); + } - delete net; - net=NULL; - multiplayer.reset(); - - if (gui.exitGlobCompletely) - return -1; // There is no bypass for the "close window button" + if (gui.exitGlobCompletely) + return -1; // There is no bypass for the "close window button" - - doRunOnceAgain=false; - - if (gui.toLoadGameFileName[0]) - { - int rv; - - if (globalContainer->replaying) rv = loadReplay(gui.toLoadGameFileName); - else rv = initCustom(gui.toLoadGameFileName); - - if (rv==EE_NO_ERROR) - doRunOnceAgain=true; - gui.toLoadGameFileName[0]=0; // Avoid the communication system between GameGUI and Engine to loop. - } - } - - if(!globalContainer->runNoX) - { - } - if (globalContainer->runNoX || globalContainer->automaticEndingGame) { if(!globalContainer->runNoX) @@ -638,318 +96,16 @@ int Engine::run(void) { // Restart menu music assert(globalContainer->mix); - globalContainer->mix->setNextTrack(1, true); - + globalContainer->mix->setNextTrack(MusicTrack::Menu, true); + // Display End Game Screen EndGameScreen endGameScreen(&gui); - int result = endGameScreen.execute(globalContainer->gfx, 40); - + int result = endGameScreen.execute(globalContainer->gfx, GAME_TICK_MS); + // Return to default color globalContainer->gfx->cursorManager.setDefaultColor(); - + // Return return (result == -1) ? -1 : EE_NO_ERROR; } } - -MapHeader Engine::loadMapHeader(const std::string &filename) -{ - MapHeader mapHeader; - InputStream *stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(filename)); - if (stream->isEndOfStream()) - { - std::cerr << "Engine::loadMapHeader : error, can't open file " << filename << std::endl; - } - else - { - if (verbose) - std::cout << "Engine::loadMapHeader : loading map " << filename << std::endl; - - bool validMapSelected; - - try - { - validMapSelected = mapHeader.load(stream); - } - catch (std::ios_base::failure &e) - { - // Notify what filename couldn't load, because if we're doing -test-games(-nox) and loading the map fails, - // the map name won't be saved inside mapHeader. - std::cerr << "Engine::loadMapHeader : can't load map \"" << filename << "\": bad format" << std::endl; - - // We didn't solve the problem though, so we re-throw - throw; - } - - if (!validMapSelected) - std::cerr << "Engine::loadMapHeader : invalid map header for map " << filename << std::endl; - } - delete stream; - - //Map name is the filename without underscores or .map, it has to be updated in case the map file itself was renamed - std::string mapName; - if(mapHeader.getIsSavedGame()) - mapName=filename.substr(filename.find("/")+1, filename.size()-6-filename.find("/")); - else - mapName=filename.substr(filename.find("/")+1, filename.size()-5-filename.find("/")); - size_t pos = mapName.find("_"); - while(pos != std::string::npos) - { - mapName.replace(pos, 1, " "); - pos = mapName.find("_"); - } - mapHeader.setMapName(glob2FilenameToName(filename)); - - return mapHeader; -} - - - -GameHeader Engine::loadGameHeader(const std::string &filename) -{ - MapHeader mapHeader; - GameHeader gameHeader; - std::unique_ptr stream = std::make_unique(Toolkit::getFileManager()->openInputStreamBackend(filename)); - if (stream->isEndOfStream()) - { - std::cerr << "Engine::loadGameHeader : error, can't open file " << filename << std::endl; - return GameHeader(); // an empty game header - } - else - { - if (verbose) - std::cout << "Engine::loadGameHeader : loading map " << filename << std::endl; - bool headerValid = mapHeader.load(stream.get()); - bool validMapSelected = gameHeader.load(stream.get(), mapHeader.getVersionMinor()); - if (!headerValid || !validMapSelected) - { - std::cerr << "Engine::loadGameHeader : invalid game header for map " << filename << std::endl; - return GameHeader(); - } - } - return gameHeader; - -} - - - -int Engine::initGame(MapHeader& mapHeader, GameHeader& gameHeader, bool setGameHeader, bool ignoreGUIData, bool saveAI) -{ - bool error = false; - try - { - error = !gui.loadFromHeaders(mapHeader, gameHeader, setGameHeader, ignoreGUIData, saveAI); - } - catch (std::exception &e) - { - std::cerr << "Failed to load the map: exception received." << std::endl; - error = true; - } - if (error) { - if (!globalContainer->runNoX) - { - // Display an error message - GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_ONEBUTTON, Toolkit::getStringTable()->getString("[ERROR_CANT_LOAD_MAP]"), Toolkit::getStringTable()->getString("[ok]")); - } - return EE_CANT_LOAD_MAP; - } - - // We remove uncontrolled stuff from map - gui.game.clearingUncontrolledTeams(); - - // We do some cosmetic fix - finalAdjustements(); - - // we create the net game - net=new NetEngine(gui.game.gameHeader.getNumberOfPlayers(), gui.localPlayer); - - // Initialise the replay writer, unless we're showing a replay - if (!globalContainer->replaying) - { - assert(globalContainer->replayWriter == NULL); - globalContainer->replayWriter = new ReplayWriter(); - globalContainer->replayWriter->init("replays/last_game.replay", gui); - } - - return EE_NO_ERROR; -} - - - -GameHeader Engine::prepareCampaign(MapHeader& mapHeader, int& localPlayer, int& localTeam) -{ - GameHeader gameHeader; - - // We make a player for each team in the mapHeader - int playerNumber=0; - // Incase there are multiple "humans" selected, only the first will actually become human - bool wasHuman=false; - // Each team has a variable, type, that designates whether it is a human or an AI in - // a campaign match. - for (int i=0; iopenInputStreamBackend(filename)); - if (stream->isEndOfStream()) - { - std::cerr << "Engine::loadGame(\"" << filename << "\") : error, can't open file." << std::endl; - delete stream; - return false; - } - else - { - bool res = gui.load(stream); - delete stream; - if (!res) - { - std::cerr << "Engine::loadGame(\"" << filename << "\") : error, can't load game." << std::endl; - return false; - } - } - - if (verbose) - std::cout << "Engine::loadGame(\"" << filename << "\") : game successfully loaded." << std::endl; - return true; -} - - - -MapHeader Engine::chooseRandomMap() -{ - std::vector maps; - - std::string fullDir = "maps"; - - // we add the other files - if (Toolkit::getFileManager()->initDirectoryListing(fullDir.c_str(), "map", false)) - { - std::string fileName; - while (!(fileName = (Toolkit::getFileManager()->getNextDirectoryEntry())).empty()) - { - std::string fullFileName = fullDir + DIR_SEPARATOR + fileName; - maps.push_back(fullFileName); - } - } - - int number = syncRand() % maps.size(); - - return loadMapHeader(maps[number]); -} - - - -GameHeader Engine::createRandomGame(int numberOfTeams) -{ - GameHeader gameHeader; - int count = 0; - for (int i=0; isettings.getUsername(), teamColor, BasePlayer::P_LOCAL); - } - else - { - AI::ImplementitionID iid=static_cast(syncRand() % 5 + 1); - FormatableString name("%0 %1"); - name.arg(AINames::getAIText(iid)).arg(i-1); - gameHeader.getBasePlayer(count) = BasePlayer(i, name.c_str(), teamColor, Player::playerTypeFromImplementitionID(iid)); - } - gameHeader.setAllyTeamNumber(teamColor, teamColor); - count+=1; - } - gameHeader.setNumberOfPlayers(count); - return gameHeader; -} - -int Engine::loadReplay(const std::string &fileName) -{ - // Let globalContainer know what we are doing - globalContainer->replaying = true; - globalContainer->replayFileName = fileName; - - // Reset the replay's options - gui.localPlayer = 0; - gui.localTeamNo = 0; - globalContainer->replayVisibleTeams = 0xFFFFFFFF; - globalContainer->replayFastForward = false; - - // Initialize the ReplayReader in GlobalContainer - globalContainer->replayReader = new ReplayReader(); - bool replayLoaded = globalContainer->replayReader->loadReplay(fileName); - - // If the reader found that the replay isn't valid, show an error message and return - if (!replayLoaded) - { - if (!globalContainer->runNoX) - { - // Display an error message - GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_ONEBUTTON, Toolkit::getStringTable()->getString("[ERROR_CANT_LOAD_MAP]"), Toolkit::getStringTable()->getString("[ok]")); - } - - delete globalContainer->replayReader; - globalContainer->replayReader = NULL; - return EE_CANT_LOAD_MAP; - } - - assert(globalContainer->replayReader->isValid()); - - // Load the map and settings. - MapHeader mapHeader = loadMapHeader(fileName); - GameHeader gameHeader = loadGameHeader(fileName); - - // Set all players to a AINone - for (int p=0; prunNoX) - { - gui.adjustInitialViewport(); - } - gui.game.setAlliances(); -} diff --git a/src/Engine.h b/src/Engine.h index b958d2cca..29b124397 100644 --- a/src/Engine.h +++ b/src/Engine.h @@ -1,42 +1,25 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __ENGINE_H -#define __ENGINE_H +#pragma once #include "Header.h" #include "GameGUI.h" +#include #include #include "Campaign.h" #include "MapHeader.h" #include "GameHeader.h" #include "NetEngine.h" #include "MultiplayerGame.h" -#include "CPUStatisticsManager.h" +#include "ChecksumSidecar.h" class MultiplayersJoin; class NetGame; -using boost::shared_ptr; +using std::shared_ptr; /// Engine is the backend of the game. It is responsible for loading and setting up games and players, /// and its run function is meant to run the game that has been loaded. @@ -67,7 +50,7 @@ class Engine int initLoadGame(); /// Initiate a game with the given MultiplayerGame - int initMultiplayer(boost::shared_ptr multiplayerGame, boost::shared_ptr client, int localPlayer); + int initMultiplayer(std::shared_ptr multiplayerGame, std::shared_ptr client, int localPlayer); //! This function creates a game with a random map and random AI for every team void createRandomGame(); @@ -120,21 +103,85 @@ class Engine //! Do the final adjustements, like setting local teams and viewport, rendering minimap void finalAdjustements(void); - ///This function will choose a random map from the available maps - MapHeader chooseRandomMap(); + /// Choose a random map from the available maps. Returns std::nullopt + /// if maps/ is empty or unreadable (caller must surface this as a + /// fatal config error). Throws std::ios_base::failure if a randomly + /// selected .map file is malformed (caller's retry loop picks again). + /// See definition in EngineLoaders.cpp for the full behavior contract. + std::optional chooseRandomMap(); ///This function prepares a random set of AI's in a GameHeader, first player is always human + ai team GameHeader createRandomGame(int numberOfTeams); + /// Body of the outer "play one game and possibly load another" loop in run(). + /// Sets doRunOnceAgain=true to loop again (e.g. user picked a new save), false to return. + void runOneGameSession(bool& doRunOnceAgain); + + // --- runOneGameSession phase helpers --- + // + // The single 380-line body was decomposed into the helpers below. Each + // helper is one phase of the main loop or the post-loop teardown. They + // must be called in the order they appear here; the comments at each + // definition site name preconditions and which caller state each one + // mutates. See EngineRun.cpp. + + /// Choose this tick's sim interval (GAME_TICK_MS / REPLAY_FAST_FORWARD_MS) + /// and the GUI-draw cadence. Caller has already decremented nextGuiStep. + void selectReplaySpeed(int& speed, int& nextGuiStep); + + /// Headless / scripted-test polling: under --nox automaticEndingGame, flip + /// gui.isRunning=false once a local end condition fires. Records + /// automaticGameEndTick. + void pollAutomaticEndingConditions(); + + /// Push this tick's local + AI orders into the net layer and (if the + /// previous tick committed) call advanceStep + write the checksum sidecar. + /// Called only from inside the !hardPause branch. + void gatherAndAdvanceOrders(bool wasReadyLastTick); + + /// Once allOrdersRecieved() is true for this tick, validate checksums, + /// execute the matched orders, pump the replay reader, and run + /// game.syncStep. Called only from inside the !hardPause branch. + void executeOrdersAndStep(bool readyNow); + + /// Draw the frame (subject to the fast-forward cadence), save a videoshot + /// if requested, then SDL_Delay to maintain wall-clock pacing. Updates + /// needToBeTime + frameNumber across iterations. + void frameTimingAndDraw(int speed, int nextGuiStep, Sint64& needToBeTime, + unsigned& frameNumber, Uint64 startTime); + + /// If the GUI requested a clean exit, drain remaining local orders and + /// flush the net layer. Returns true if the engine loop should break. + bool flushOutgoingAndExit(); + + /// Print the headless end-of-game summary plus the GLOB2_GAME_END + /// key=value line that the AI-trainer pipeline scrapes. Caller checks + /// automaticEndingGame. + void printAutomaticEndingSummary(); + + /// Tell the YOG multiplayer session how this match ended (won, lost, + /// quit). Caller checks `multiplayer` is non-null. + void reportMultiplayerResult(); + + /// Close cross-replay sinks (sidecar, dataset) and tear down the network + /// + multiplayer state. The Engine itself stays alive for a possible + /// reload (see armReloadOrExit). + void teardownSession(); + + /// Decide whether run() should loop back into runOneGameSession (a + /// load-game request was armed in the GUI) or return to the menu. Always + /// clears toLoadGameFileName so a follow-up pass doesn't re-trigger it. + void armReloadOrExit(bool& doRunOnceAgain); + //! The GUI, contains the whole game also GameGUI gui; //! The netGame, take care of order queuing and dispatching NetEngine *net; + //! Checksum sidecar writer for cross-replay debugging + ChecksumSidecarWriter *checksumSidecar; //! The MultiplayerGame, recieves orders from across a network shared_ptr multiplayer; - CPUStatisticsManager cpuStats; - Uint64 automaticGameStartTick, automaticGameEndTick; FILE *logFile; @@ -142,4 +189,3 @@ class Engine static const bool verbose = false; }; -#endif diff --git a/src/EngineInit.cpp b/src/EngineInit.cpp new file mode 100644 index 000000000..d57588560 --- /dev/null +++ b/src/EngineInit.cpp @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include +#include +#include + +#include "AINames.h" +#include "ChecksumSidecar.h" +#include "CustomGameScreen.h" +#include "DatasetWriter.h" +#include "Engine.h" +#include "EngineTiming.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "GUIMessageBox.h" +#include "Player.h" +#include "ReplayReader.h" +#include "ReplayWriter.h" + +#include + + +int Engine::initCampaign(const std::string &mapName, Campaign& campaign, const std::string& missionName) +{ + MapHeader mapHeader = loadMapHeader(mapName); + GameHeader gameHeader = loadGameHeader(mapName); + if(gameHeader.getNumberOfPlayers() == 0) + { + gameHeader = prepareCampaign(mapHeader, gui.localPlayer, gui.localTeamNo); + } + else + { + gui.localPlayer = 0; + gui.localTeamNo = gameHeader.getBasePlayer(0).teamNumber; + } + + gameHeader.getBasePlayer(0).name = campaign.getPlayerName(); + + int end=initGame(mapHeader, gameHeader); + gui.setCampaignGame(campaign, missionName); + return end; +} + + + +int Engine::initCampaign(const std::string &mapName) +{ + MapHeader mapHeader = loadMapHeader(mapName); + GameHeader gameHeader = loadGameHeader(mapName); + if(gameHeader.getNumberOfPlayers() == 0) + { + gameHeader = prepareCampaign(mapHeader, gui.localPlayer, gui.localTeamNo); + } + else + { + gui.localPlayer = 0; + gui.localTeamNo = gameHeader.getBasePlayer(0).teamNumber; + } + int end=initGame(mapHeader, gameHeader); + return end; +} + + + +int Engine::initCustom(void) +{ + CustomGameScreen customGameScreen; + + int cgs=customGameScreen.execute(globalContainer->gfx, GAME_TICK_MS); + + if (cgs==CustomGameScreen::CANCEL) + return EE_CANCEL; + if (cgs==-1) + return -1; + + int teamColor=customGameScreen.getSelectedColor(0); + gui.localPlayer=0; + gui.localTeamNo=teamColor; + + int ret = initGame(customGameScreen.getMapHeader(), customGameScreen.getGameHeader()); + if(ret != EE_NO_ERROR) + return EE_CANT_LOAD_MAP; + else if(ret == -1) + return -1; + + return EE_NO_ERROR; +} + +int Engine::initCustom(const std::string &gameName) +{ + MapHeader mapHeader = loadMapHeader(gameName); + GameHeader gameHeader = loadGameHeader(gameName); + + // If the game is a network saved game, we need to toogle net players to ai players: + for (int p=0; pgfx, GAME_TICK_MS); + if (lgs == ChooseMapScreen::CANCEL) + return EE_CANCEL; + else if(lgs == -1) + return -1; + + assert(loadGameScreen.getSelectedType() != ChooseMapScreen::NONE); + assert(loadGameScreen.getSelectedType() != ChooseMapScreen::MAP); + + if (loadGameScreen.getSelectedType() == ChooseMapScreen::GAME) + return initCustom(loadGameScreen.getMapHeader().getFileName()); + else if (loadGameScreen.getSelectedType() == ChooseMapScreen::REPLAY) + return loadReplay(loadGameScreen.getMapHeader().getFileName(false,true)); + else + assert(false); +} + +int Engine::initMultiplayer(std::shared_ptr multiplayerGame, std::shared_ptr client, int localPlayer) +{ + gui.localPlayer = localPlayer; + gui.localTeamNo = multiplayerGame->getGameHeader().getBasePlayer(localPlayer).teamNumber; + multiplayer = multiplayerGame; + initGame(multiplayerGame->getMapHeader(), multiplayerGame->getGameHeader(), true, true); + multiplayer->setNetEngine(net); + + for (int p=0; pgetGameHeader().getNumberOfPlayers(); p++) + { + if (multiplayerGame->getGameHeader().getBasePlayer(p).type==BasePlayer::P_IP) + { + net->prepareForLatency(p, multiplayerGame->getGameHeader().getGameLatency()); + } + } + + net->setNetworkInfo(multiplayerGame->getGameHeader().getOrderRate(), client->getGameConnection()); + + return Engine::EE_NO_ERROR; +} + + + +void Engine::createRandomGame() +{ + MapHeader map; + + if (!globalContainer->testGamesMap.empty()) + { + // --map: try once, fail loudly. The legacy retry loop below would + // spin forever on a typo'd map name. loadMapHeader does NOT throw + // on a missing file (it logs to stderr and returns a default- + // constructed MapHeader with numberOfTeams=0), so we detect failure + // by checking the team count rather than catching an exception. + std::optional chosen; + try + { + chosen = chooseRandomMap(); + } + catch (std::ios_base::failure &e) + { + std::cerr << "--map: cannot load maps/" + << globalContainer->testGamesMap << ".map: " + << e.what() << std::endl; + exit(1); + } + // With --map set, chooseRandomMap never returns nullopt (the + // override path either loads or throws), but defend against it + // anyway so a future refactor doesn't reintroduce undefined state. + if (!chosen || chosen->getNumberOfTeams() <= 0) + { + std::cerr << "--map: cannot load maps/" + << globalContainer->testGamesMap << ".map " + << "(missing or invalid; numberOfTeams=0)" << std::endl; + exit(1); + } + map = *chosen; + } + else + { + bool validMapChosen = false; + while (!validMapChosen) + { + try + { + std::optional chosen = chooseRandomMap(); + if (!chosen) + { + // Empty or unreadable maps/ directory. Previously this + // path produced syncRand() % 0 (UB / SIGFPE) inside + // chooseRandomMap; now we exit cleanly so the user + // gets an actionable message instead of a crash or a + // retry loop that can never succeed. + std::cerr << "createRandomGame: no maps available in " + << "maps/ directory (empty or unreadable). " + << "Cannot pick a random map." << std::endl; + exit(1); + } + map = *chosen; + validMapChosen = true; + } + catch (std::ios_base::failure &e) + { + validMapChosen = false; + } + } + } + + std::cout<<"Randomly Chosen Map: "<testGamesMatchup.empty() + && (int)globalContainer->testGamesMatchup.size() != map.getNumberOfTeams()) + { + std::cerr << "--matchup has " << globalContainer->testGamesMatchup.size() + << " entries but map " << map.getMapName() << " has " + << map.getNumberOfTeams() << " teams" << std::endl; + exit(1); + } + + GameHeader game = createRandomGame(map.getNumberOfTeams()); + // Mirror the syncRand seed (captured at runTestGames entry) into the + // GameHeader so a saved .game file reloads with the same syncRand + // state. GameHeader's ctor defaults seed to time(NULL) at header- + // construction time, which won't match GLOB2_TEST_SEED (and even + // without that env var, can drift seconds away from the time(NULL) + // runTestGames already used for setSyncRandSeed). Without this mirror, + // --save-game-as / GLOB2_DUMP_GAME produce .game files that diverge + // from the original run when reloaded via --nox. + if (globalContainer->testGamesSeedSet) + { + game.setRandomSeed(globalContainer->testGamesSeed); + } + std::cout<<"Random Seed gameheader: "<openOutputStreamBackend(dumpPath)); + if (dumpStream->isEndOfStream()) + { + std::cerr << "GLOB2_DUMP_GAME: cannot open " << dumpPath << " for writing" << std::endl; + delete dumpStream; + exit(1); + } + gui.save(dumpStream, map.getMapName()); + delete dumpStream; + std::cout << "GLOB2_DUMP_GAME: wrote " << dumpPath << std::endl; + } + if (!globalContainer->testGamesSaveGameAs.empty()) + { + const std::string& path = globalContainer->testGamesSaveGameAs; + OutputStream* dumpStream = new BinaryOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(path)); + if (dumpStream->isEndOfStream()) + { + std::cerr << "--save-game-as: cannot open " << path << " for writing" << std::endl; + delete dumpStream; + exit(1); + } + gui.save(dumpStream, map.getMapName()); + delete dumpStream; + std::cout << "--save-game-as: wrote " << path << std::endl; + } +} + + + +bool Engine::haveMap(const MapHeader& mapHeader) +{ + // FIXME: This is a fairly ugly way to test if the file exists + InputStream *stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapHeader.getFileName())); + if (stream->isEndOfStream()) + { + delete stream; + return false; + } + delete stream; + MapHeader mh = loadMapHeader(mapHeader.getFileName()); + if(mh != mapHeader) + return false; + return true; +} + + + +int Engine::initGame(MapHeader& mapHeader, GameHeader& gameHeader, bool setGameHeader, bool ignoreGUIData, bool saveAI) +{ + bool error = false; + try + { + error = !gui.loadFromHeaders(mapHeader, gameHeader, setGameHeader, ignoreGUIData, saveAI); + } + catch (std::exception &e) + { + std::cerr << "Failed to load the map: exception received." << std::endl; + error = true; + } + if (error) { + if (!globalContainer->runNoX) + { + // Display an error message + GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_ONEBUTTON, Toolkit::getStringTable()->getString("[ERROR_CANT_LOAD_MAP]"), Toolkit::getStringTable()->getString("[ok]")); + } + return EE_CANT_LOAD_MAP; + } + + // We remove uncontrolled stuff from map + gui.game.clearingUncontrolledTeams(); + + // We do some cosmetic fix + finalAdjustements(); + + // we create the net game + net=new NetEngine(gui.game.gameHeader.getNumberOfPlayers(), gui.localPlayer); + + // Initialise the replay writer, unless we're showing a replay. + // GLOB2_REPLAY_PATH overrides the default output path (used by the + // AI-trainer pipeline to keep per-game replays without overwriting, + // and to allow concurrent headless instances to write to distinct files). + const char* envReplayPath = getenv("GLOB2_REPLAY_PATH"); + std::string replayPath = envReplayPath ? envReplayPath : "replays/last_game.replay"; + if (!globalContainer->replaying) + { + assert(globalContainer->replayWriter == NULL); + globalContainer->replayWriter = new ReplayWriter(); + globalContainer->replayWriter->init(replayPath, gui); + } + + // Initialise checksum sidecar writer if requested + if (getenv("GLOB2_CHECKSUM_SIDECAR")) + { + std::string sidecarBase = globalContainer->replaying + ? globalContainer->replayFileName + : replayPath; + checksumSidecar = new ChecksumSidecarWriter(); + checksumSidecar->open(sidecarBase, + gui.game.teamsCount(), + gui.game.gameHeader.getNumberOfPlayers()); + } + + // Initialise dataset writer if GLOB2_DATASET_PATH is set. Writes + // one (state, action) record per executed order — see DatasetWriter.h. + // Skipped when replaying (no orders fire that the trainer cares about). + const char* envDatasetPath = getenv("GLOB2_DATASET_PATH"); + if (envDatasetPath && !globalContainer->replaying) + { + assert(globalContainer->datasetWriter == NULL); + globalContainer->datasetWriter = new DatasetWriter(); + if (!globalContainer->datasetWriter->open(envDatasetPath)) + { + std::cerr << "GLOB2_DATASET_PATH: failed to open dataset file " + << envDatasetPath << std::endl; + delete globalContainer->datasetWriter; + globalContainer->datasetWriter = NULL; + } + } + + return EE_NO_ERROR; +} + + + +GameHeader Engine::prepareCampaign(MapHeader& mapHeader, int& localPlayer, int& localTeam) +{ + GameHeader gameHeader; + + // We make a player for each team in the mapHeader + int playerNumber=0; + // Incase there are multiple "humans" selected, only the first will actually become human + bool wasHuman=false; + // Each team has a variable, type, that designates whether it is a human or an AI in + // a campaign match. + for (int i=0; iopenInputStreamBackend(filename)); + if (stream->isEndOfStream()) + { + std::cerr << "Engine::loadGame(\"" << filename << "\") : error, can't open file." << std::endl; + delete stream; + return false; + } + else + { + bool res = gui.load(stream); + delete stream; + if (!res) + { + std::cerr << "Engine::loadGame(\"" << filename << "\") : error, can't load game." << std::endl; + return false; + } + } + + if (verbose) + std::cout << "Engine::loadGame(\"" << filename << "\") : game successfully loaded." << std::endl; + return true; +} + + + +int Engine::loadReplay(const std::string &fileName) +{ + // Let globalContainer know what we are doing + globalContainer->replaying = true; + globalContainer->replayFileName = fileName; + + // Reset the replay's options + gui.localPlayer = 0; + gui.localTeamNo = 0; + globalContainer->replayVisibleTeams = REPLAY_VISIBLE_TEAMS_ALL; + globalContainer->replayFastForward = false; + + // Initialize the ReplayReader in GlobalContainer + globalContainer->replayReader = new ReplayReader(); + bool replayLoaded = globalContainer->replayReader->loadReplay(fileName); + + // If the reader found that the replay isn't valid, show an error message and return + if (!replayLoaded) + { + if (!globalContainer->runNoX) + { + // Display an error message + GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_ONEBUTTON, Toolkit::getStringTable()->getString("[ERROR_CANT_LOAD_MAP]"), Toolkit::getStringTable()->getString("[ok]")); + } + + delete globalContainer->replayReader; + globalContainer->replayReader = NULL; + return EE_CANT_LOAD_MAP; + } + + assert(globalContainer->replayReader->isValid()); + + // Load the map and settings. + MapHeader mapHeader = loadMapHeader(fileName); + GameHeader gameHeader = loadGameHeader(fileName); + + // Set all players to a AINone + for (int p=0; prunNoX) + { + gui.adjustInitialViewport(); + } + gui.game.setAlliances(); +} diff --git a/src/EngineLoaders.cpp b/src/EngineLoaders.cpp new file mode 100644 index 000000000..a5a7f001e --- /dev/null +++ b/src/EngineLoaders.cpp @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include + +#include "AINames.h" +#include "Engine.h" +#include "EngineTiming.h" +#include "GlobalContainer.h" +#include "Player.h" +#include "Utilities.h" + +#include +#include +#include + + +// Loads a map header from disk. The two failure modes are asymmetric: +// * Missing or unreadable file: logs to stderr and returns a default-constructed +// MapHeader (numberOfTeams == 0). Callers must check. +// * Malformed file contents: re-throws std::ios_base::failure after logging. +MapHeader Engine::loadMapHeader(const std::string &filename) +{ + MapHeader mapHeader; + std::unique_ptr stream = std::make_unique(Toolkit::getFileManager()->openInputStreamBackend(filename)); + if (stream->isEndOfStream()) + { + std::cerr << "Engine::loadMapHeader : error, can't open file " << filename << std::endl; + } + else + { + if (verbose) + std::cout << "Engine::loadMapHeader : loading map " << filename << std::endl; + + bool validMapSelected; + + try + { + validMapSelected = mapHeader.load(stream.get()); + } + catch (std::ios_base::failure &e) + { + // Notify what filename couldn't load, because if we're doing -test-games(-nox) and loading the map fails, + // the map name won't be saved inside mapHeader. + std::cerr << "Engine::loadMapHeader : can't load map \"" << filename << "\": bad format" << std::endl; + + // We didn't solve the problem though, so we re-throw + throw; + } + + if (!validMapSelected) + std::cerr << "Engine::loadMapHeader : invalid map header for map " << filename << std::endl; + } + + mapHeader.setMapName(glob2FilenameToName(filename)); + + return mapHeader; +} + + + +GameHeader Engine::loadGameHeader(const std::string &filename) +{ + MapHeader mapHeader; + GameHeader gameHeader; + std::unique_ptr stream = std::make_unique(Toolkit::getFileManager()->openInputStreamBackend(filename)); + if (stream->isEndOfStream()) + { + std::cerr << "Engine::loadGameHeader : error, can't open file " << filename << std::endl; + return GameHeader(); // an empty game header + } + else + { + if (verbose) + std::cout << "Engine::loadGameHeader : loading map " << filename << std::endl; + bool headerValid = mapHeader.load(stream.get()); + bool validMapSelected = gameHeader.load(stream.get(), mapHeader.getVersionMinor()); + if (!headerValid || !validMapSelected) + { + std::cerr << "Engine::loadGameHeader : invalid game header for map " << filename << std::endl; + return GameHeader(); + } + } + return gameHeader; + +} + + + +// Pick one map from the maps/ directory at random and load its header. +// Three outcomes for the caller to handle: +// * --map override set: returns the named map (still throws on missing +// file, since a typo'd name is a fatal user error). +// * No override + maps/ has at least one .map file: consumes one +// syncRand() call to index uniformly into the listing and returns +// the loaded MapHeader. +// * No override + maps/ is empty or unreadable: returns std::nullopt +// without consuming RNG state. Callers must surface this as a clear +// fatal-config error — previously this path was undefined behavior +// (syncRand() % 0 → SIGFPE on x86, bypassing the createRandomGame +// retry-on-malformed-file loop and terminating the process). +// Loaded maps that turn out to be malformed propagate via +// std::ios_base::failure (the existing retry loop in createRandomGame +// catches that and picks again). +std::optional Engine::chooseRandomMap() +{ + if (!globalContainer->testGamesMap.empty()) + { + std::string fullPath = std::string("maps") + DIR_SEPARATOR + + globalContainer->testGamesMap + ".map"; + return loadMapHeader(fullPath); + } + + std::vector maps; + + std::string fullDir = "maps"; + + // we add the other files + if (Toolkit::getFileManager()->initDirectoryListing(fullDir.c_str(), "map", false)) + { + std::string fileName; + while (!(fileName = (Toolkit::getFileManager()->getNextDirectoryEntry())).empty()) + { + std::string fullFileName = fullDir + DIR_SEPARATOR + fileName; + maps.push_back(fullFileName); + } + } + + if (maps.empty()) + return std::nullopt; + + int number = syncRand() % maps.size(); + + return loadMapHeader(maps[number]); +} + + + +GameHeader Engine::createRandomGame(int numberOfTeams) +{ + GameHeader gameHeader; + int count = 0; + for (int i=0; isettings.getUsername(), teamColor, BasePlayer::P_LOCAL); + } + else + { + AI::ImplementitionID iid; + if (!globalContainer->testGamesMatchup.empty()) + { + // --matchup: matchup[k] is the AI for team k. teamColor + // here equals the team this AI plays for (the wrap-around + // at i==numberOfTeams gives teamColor=0, which gets + // matchup[0]). Team-count consistency was verified by the + // caller (createRandomGame() parameterless) before we got + // here, so direct indexing is safe. + iid = static_cast( + globalContainer->testGamesMatchup[teamColor]); + } + else if (!globalContainer->testGamesAIPool.empty()) + { + int idx = syncRand() % globalContainer->testGamesAIPool.size(); + iid = static_cast(globalContainer->testGamesAIPool[idx]); + } + else + { + iid = static_cast(syncRand() % AI_RANDOM_PICK_COUNT + 1); + } + FormatableString name("%0 %1"); + name.arg(AINames::getAIText(iid)).arg(i-1); + gameHeader.getBasePlayer(count) = BasePlayer(i, name.c_str(), teamColor, Player::playerTypeFromImplementitionID(iid)); + } + gameHeader.setAllyTeamNumber(teamColor, teamColor); + count+=1; + } + gameHeader.setNumberOfPlayers(count); + return gameHeader; +} diff --git a/src/EngineRun.cpp b/src/EngineRun.cpp new file mode 100644 index 000000000..7a80a0800 --- /dev/null +++ b/src/EngineRun.cpp @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "AINames.h" +#include "ChecksumSidecar.h" +#include "DatasetWriter.h" +#include "Engine.h" +#include "EngineTiming.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Player.h" +#include "ReplayReader.h" +#include "ReplayWriter.h" +#include "SDLCompat.h" + +#include + +using std::shared_ptr; + + +// Choose this tick's sim interval and GUI-draw cadence. Caller has already +// decremented nextGuiStep for this iteration; this resets it back to the +// per-mode reload value once it has run down to (or below) zero. +void Engine::selectReplaySpeed(int& speed, int& nextGuiStep) +{ + if (globalContainer->replaying) + { + if (globalContainer->replayFastForward && !gui.gamePaused) + { + speed = REPLAY_FAST_FORWARD_MS; + if (nextGuiStep < 0) nextGuiStep = REPLAY_FAST_FORWARD_DRAW_RATIO - 1; + } + else + { + speed = GAME_TICK_MS; + if (nextGuiStep < 0) nextGuiStep = 0; + } + } + else + { + // Process the GUI as usual, every step + nextGuiStep = 0; + } +} + +// Headless / scripted-test polling: under --nox automaticEndingGame, flip +// gui.isRunning=false once a local end condition fires (local team dead, local +// team won, total-prestige reached, game ended). Records automaticGameEndTick. +void Engine::pollAutomaticEndingConditions() +{ + if (!globalContainer->automaticEndingGame) + return; + + if (!gui.getLocalTeam()->isAlive && !globalContainer->automaticGameGlobalEndConditions) + { + printf("nox::gui.localTeam is dead\n"); + gui.isRunning = false; + automaticGameEndTick = SDL_GetTicks64(); + } + else if (gui.getLocalTeam()->hasWon && !globalContainer->automaticGameGlobalEndConditions) + { + printf("nox::gui.localTeam has won\n"); + gui.isRunning = false; + automaticGameEndTick = SDL_GetTicks64(); + } + else if (gui.game.totalPrestigeReached) + { + printf("nox::gui.game.totalPrestigeReached\n"); + gui.isRunning = false; + automaticGameEndTick = SDL_GetTicks64(); + } + else if (gui.game.isGameEnded) + { + printf("nox::gui.game.isGameEnded\n"); + gui.isRunning = false; + automaticGameEndTick = SDL_GetTicks64(); + } +} + +// Push this tick's local + AI orders into the network layer. AI poll, +// setWaitingOnMask, and multiplayer->update() always run; the "previous tick +// committed" branches (syncStep, addLocalOrder, advanceStep, sidecar) only +// fire when wasReadyLastTick — otherwise we're still waiting on a remote peer +// and must not advance. +void Engine::gatherAndAdvanceOrders(bool wasReadyLastTick) +{ + // But some jobs have to be executed synchronously: + if (wasReadyLastTick) + { + gui.syncStep(); + + // The gui.localPlayer may have been updated (in replays) + // Keep them synchronized here + net->setLocalPlayer(gui.localPlayer); + + // We get and push local orders + shared_ptr localOrder = gui.getOrder(); + net->addLocalOrder(localOrder); + } + + // we get and push ai orders, if they are needed for this frame + for (int i = 0; i < gui.game.gameHeader.getNumberOfPlayers(); i++) + { + if (gui.game.players[i]->ai && !net->orderRecieved(i)) + { + shared_ptr order = gui.game.players[i]->ai->getOrder(gui.gamePaused); + net->pushOrder(order, i, true); + } + } + + gui.game.setWaitingOnMask(net->getWaitingOnMask()); + + if (multiplayer) + multiplayer->update(); + + if (wasReadyLastTick) + { + Uint32 checksum = gui.game.checkSum(NULL, NULL, NULL); + net->advanceStep(checksum); + + // Enable this to do test if checksums in the replay match + //if (globalContainer->replayReader) globalContainer->replayReader->setCheckSum(checksum); + if (globalContainer->replayWriter) globalContainer->replayWriter->setCheckSum(checksum); + + if (checksumSidecar) + checksumSidecar->writeTick(gui.game.stepCounter, checksum, gui.game); + } +} + +// Once allOrdersRecieved() is true for this tick, commit the tick: validate +// checksums (assert on desync), execute the matched orders, pump the replay +// reader if we're in playback, and run game.syncStep. Called only from inside +// the !hardPause branch, so the original !gui.hardPause guard on syncStep is +// implicit here. +void Engine::executeOrdersAndStep(bool readyNow) +{ + if (readyNow) + { + if (!net->matchCheckSums()) + { + std::cout << "Game desychronized." << std::endl; + gui.game.dumpAllData("glob2.world-desynchronization.dump.txt"); + assert(false); + } + else + { + // We get all currents orders from the network and execute them: + for (int i = 0; i < gui.game.gameHeader.getNumberOfPlayers(); i++) + { + shared_ptr order = net->retrieveOrder(i); + if (!globalContainer->replaying) + { + gui.executeOrder(order); + } + else if (order->getOrderType() == ORDER_PLAYER_QUIT_GAME || + order->getOrderType() == ORDER_PAUSE_GAME) + { + gui.executeOrder(order); + } + } + net->clearTopOrders(); + } + } + + // Load the replay's orders + if (globalContainer->replaying) + { + assert(globalContainer->replayReader); + assert(globalContainer->replayReader->isValid()); + + while (globalContainer->replayReader->hasMoreOrdersThisStep()) + { + shared_ptr order = globalContainer->replayReader->retrieveOrder(); + + if (order->getOrderType() != ORDER_PLAYER_QUIT_GAME && + order->getOrderType() != ORDER_PAUSE_GAME && + order->getOrderType() != ORDER_NULL) + { + gui.executeOrder(order); + } + } + + if (globalContainer->replayReader->isFinished()) + { + gui.showEndOfReplayScreen(); + } + } + + // here we do the real work + if (readyNow && !gui.gamePaused) + { + if (globalContainer->replaying) + { + assert(globalContainer->replayReader); + globalContainer->replayReader->advanceStep(); + } + + gui.game.syncStep(gui.localTeamNo); + } +} + +// Draw the frame (skipped during replay fast-forward when not at a cadence +// boundary), save a videoshot if requested, then sleep to maintain wall-clock +// pacing relative to startTime. Mutates needToBeTime (accumulated tick budget) +// and frameNumber (videoshot index) across iterations. +void Engine::frameTimingAndDraw(int speed, int nextGuiStep, Sint64& needToBeTime, + unsigned& frameNumber, Uint64 startTime) +{ + if (nextGuiStep == 0) + { + // we draw + gui.drawAll(gui.localTeamNo); + globalContainer->gfx->nextFrame(); + } + + // if required, save videoshot + if (!(globalContainer->videoshotName.empty()) && + !(globalContainer->gfx->getOptionFlags() & GraphicContext::USEGPU) + ) + { + FormatableString fileName = FormatableString("videoshots/%0.%1.bmp").arg(globalContainer->videoshotName).arg(frameNumber++, 10, 10, '0'); + printf("printing video shot %s\n", fileName.c_str()); + globalContainer->gfx->printScreen(fileName.c_str()); + } + + // we compute timing + needToBeTime += speed; + Sint64 currentTime = static_cast(SDL_GetTicks64()) - static_cast(startTime); + //if we are more than MAX_CATCHUP_MS milliseconds behind where we should be, + //then truncate it. This is to avoid playing "catchup" for long + //periods of time if Glob2 recieved allmost no cpu time + if ((currentTime - needToBeTime) > MAX_CATCHUP_MS) + needToBeTime = currentTime - MAX_CATCHUP_MS; + + //Any inconsistancies in the delays will be smoothed throughout the following frames, + Uint64 delay = std::max(0, needToBeTime - currentTime); + SDL_Delay(delay); + + // we set CPU stats + // net->setLeftTicks(computationAvailableTicks); //We may have to tell others IP players to wait for our slow computer. + // delay is the slept-ms; reported load is the complementary % of the + // GAME_TICK_MS budget that was spent doing work this tick. Algebraically + // identical to the prior literal form (4000 - delay*100)/40. + const int loadPercent = static_cast( + (GAME_TICK_MS * 100 - delay * 100) / GAME_TICK_MS); + gui.setCpuLoad(loadPercent); +} + +// If the GUI requested a clean exit, drain remaining local orders into the +// network layer and flush. Returns true if the engine loop should break. +bool Engine::flushOutgoingAndExit() +{ + if (!gui.flushOutgoingAndExit) + return false; + + shared_ptr localOrder = gui.getOrder(); + while (localOrder->getOrderType() != ORDER_NULL) + { + net->addLocalOrder(localOrder); + localOrder = gui.getOrder(); + } + + gui.isRunning = false; + net->flushAllOrders(); + return true; +} + +// Print the human-readable end-of-game summary plus a single key=value line +// ("GLOB2_GAME_END ...") that the AI-trainer pipeline and external test +// drivers scrape from stdout. Caller checks automaticEndingGame. +void Engine::printAutomaticEndingSummary() +{ + int time = gui.game.stepCounter; + int seconds = (time / GAME_TICKS_PER_SECOND) % 60; + int minutes = (time / GAME_TICKS_PER_SECOND) / 60; + std::cout << "automaticEndingGame ended: " << time << " ticks, " << minutes << " minutes, " << seconds << " seconds" << std::endl; + + // Machine-parseable summary line for the AI-trainer pipeline (and any + // external driver scraping headless output). One line, key=value pairs, + // space-separated. Winner is the first team with hasWon set, else + // WINNER_TEAM_NONE (timeout / no winner). + int winnerTeam = WINNER_TEAM_NONE; + for (int t = 0; t < gui.game.mapHeader.getNumberOfTeams(); t++) + { + if (gui.game.teams[t] && gui.game.teams[t]->hasWon) + { + winnerTeam = t; + break; + } + } + Uint32 orders = globalContainer->replayWriter + ? globalContainer->replayWriter->getOrderCount() : 0; + std::cout << "GLOB2_GAME_END ticks=" << time + << " winner_team=" << winnerTeam + << " seed=" << gui.game.gameHeader.getRandomSeed() + << " map=\"" << gui.game.mapHeader.getMapName() << "\"" + << " orders=" << orders + << " players="; + for (int p = 0; p < gui.game.gameHeader.getNumberOfPlayers(); p++) + { + const BasePlayer& bp = gui.game.gameHeader.getBasePlayer(p); + if (p > 0) std::cout << ","; + std::cout << "team" << bp.teamNumber << ":"; + if (bp.type == BasePlayer::P_LOCAL) + std::cout << "local"; + else if (bp.type == BasePlayer::P_IP) + std::cout << "ip"; + else if (bp.type >= BasePlayer::P_AI) + std::cout << AINames::getAIText(BasePlayer::implementitionIdFromPlayerType(bp.type)); + else + std::cout << "none"; + } + std::cout << std::endl; +} + +// Tell the YOG multiplayer session how this match ended (won, lost, quit) so +// it can update ratings. Caller must check `multiplayer` is non-null first. +void Engine::reportMultiplayerResult() +{ + if (gui.game.totalPrestigeReached) + { + Team *t = gui.game.getTeamWithMostPrestige(); + assert(t); + if (t == gui.getLocalTeam()) + { + multiplayer->setGameResult(YOGGameResultWonGame); + } + else + { + if ((t->allies) & (gui.getLocalTeam()->me)) + multiplayer->setGameResult(YOGGameResultWonGame); + else + multiplayer->setGameResult(YOGGameResultLostGame); + } + } + else if (gui.getLocalTeam()->hasWon) + { + multiplayer->setGameResult(YOGGameResultWonGame); + } + else if (!gui.getLocalTeam()->isAlive) + { + multiplayer->setGameResult(YOGGameResultLostGame); + } + else if (!gui.game.isGameEnded) + { + multiplayer->setGameResult(YOGGameResultQuitGame); + } +} + +// Close cross-replay debug sinks (sidecar, dataset) and tear down the network +// + multiplayer session. The Engine itself stays alive for a possible reload. +void Engine::teardownSession() +{ + if (checksumSidecar) + { + checksumSidecar->close(); + delete checksumSidecar; + checksumSidecar = NULL; + } + + if (globalContainer->datasetWriter) + { + globalContainer->datasetWriter->close(); + delete globalContainer->datasetWriter; + globalContainer->datasetWriter = NULL; + } + + delete net; + net = NULL; + multiplayer.reset(); +} + +// Decide whether run() should loop back into runOneGameSession (e.g. the GUI +// armed a load-game request) or return to the menu. Always clears +// toLoadGameFileName afterwards so the next pass doesn't re-trigger it. +void Engine::armReloadOrExit(bool& doRunOnceAgain) +{ + if (gui.exitGlobCompletely) + { + doRunOnceAgain = false; + return; // There is no bypass for the "close window button" + } + + doRunOnceAgain = false; + + if (!gui.toLoadGameFileName.empty()) + { + int rv; + + if (globalContainer->replaying) rv = loadReplay(gui.toLoadGameFileName); + else rv = initCustom(gui.toLoadGameFileName); + + if (rv == EE_NO_ERROR) + doRunOnceAgain = true; + gui.toLoadGameFileName.clear(); // Avoid the communication system between GameGUI and Engine to loop. + } +} + +// Body of the outer "play one game and possibly load another" loop in run(). +// On entry: the game has been initialised (initGame) and audio/cursor set up. +// On exit: doRunOnceAgain==true means run() should call this again +// (e.g. user picked a save during play); false means run() returns. +// +// Phases of one main-loop iteration: +// 1. selectReplaySpeed - choose this tick's interval + draw cadence +// 2. pollAutomaticEndingConditions - headless end-condition tripwire +// 3. gui.step - GUI input (skipped under --nox / off-cadence) +// 4. gatherAndAdvanceOrders - push local+AI orders, advance net (if prev tick committed) +// 5. (gate flip) readyNow = net->allOrdersRecieved() +// 6. executeOrdersAndStep - run matched orders, replay reader, sim syncStep +// 7. automatic-ending step-count check +// 8. frameTimingAndDraw - draw, videoshot, sleep +// 9. flushOutgoingAndExit - drain on exit request +// +// `wasReadyLastTick` and `readyNow` make the two semantic phases of network +// readiness explicit (was CS-131 — the same boolean used to mean both). +void Engine::runOneGameSession(bool& doRunOnceAgain) +{ + int speed = GAME_TICK_MS; + bool wasReadyLastTick = true; + + // If playing in fast-forward, we process the GUI and draw everything only + // once every 3 game-steps so the overall fps stays about the same. + int nextGuiStep = 1; + + Sint64 needToBeTime = 0; + Uint64 startTime = SDL_GetTicks64(); + unsigned frameNumber = 0; + + while (gui.isRunning) + { + nextGuiStep--; + selectReplaySpeed(speed, nextGuiStep); + + // We always allow the user to use the gui: + pollAutomaticEndingConditions(); + + if (!globalContainer->runNoX && nextGuiStep == 0) + gui.step(); + + // readyNow defaults to wasReadyLastTick so that a hardPause iteration + // (which skips the gate flip below) carries the previous tick's + // network-readiness through into the next iteration's wasReadyLastTick + // — matching the original semantics where networkReadyToExecute was + // simply left untouched under hardPause. + bool readyNow = wasReadyLastTick; + + if (!gui.hardPause) + { + if (multiplayer && multiplayer->getMultiplayerMode() == MultiplayerGame::NoMode) + gui.isRunning = false; + + gatherAndAdvanceOrders(wasReadyLastTick); + + // Gate flip: from "previous tick committed" to "all orders for + // this tick are now in." Downstream helpers take readyNow, not + // wasReadyLastTick. + readyNow = net->allOrdersRecieved(); + + executeOrdersAndStep(readyNow); + } + + if (globalContainer->automaticEndingGame) + { + if ((int)gui.game.stepCounter == globalContainer->automaticEndingSteps) + { + gui.isRunning = false; + automaticGameEndTick = SDL_GetTicks64(); + printf("nox::gui.game.checkSum() = %08x\n", gui.game.checkSum()); + } + } + + if (!globalContainer->runNoX) + frameTimingAndDraw(speed, nextGuiStep, needToBeTime, frameNumber, startTime); + + if (flushOutgoingAndExit()) + break; + + wasReadyLastTick = readyNow; + } + + if (globalContainer->automaticEndingGame) + printAutomaticEndingSummary(); + + if (multiplayer) + reportMultiplayerResult(); + + teardownSession(); + + armReloadOrExit(doRunOnceAgain); +} diff --git a/src/EngineTiming.h b/src/EngineTiming.h new file mode 100644 index 000000000..1dbfd0cf4 --- /dev/null +++ b/src/EngineTiming.h @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// EngineTiming.h +// +// Cross-slice engine cadence constants. The engine ticks at a fixed rate +// (GAME_TICKS_PER_SECOND) and every tick is GAME_TICK_MS milliseconds long. +// Anything in the simulation expressed in "ticks" (cooldowns, timers, +// refresh intervals, event ages) is gated by these values, so they live in +// one shared header to avoid drift between Engine, Map, Team, Building, +// Unit, and AI subsystems. + +#pragma once + +// === Engine cadence === + +//! Engine fixed-step rate. The simulation advances exactly this many ticks +//! per real-time second. EngineRun.cpp's main loop is built around this. +static constexpr int GAME_TICKS_PER_SECOND = 25; + +//! Length of a single engine tick in milliseconds. Equal to +//! 1000 / GAME_TICKS_PER_SECOND. Used by Engine.cpp / EngineInit.cpp / +//! EngineRun.cpp for sleep budgeting and frame pacing. +static constexpr int GAME_TICK_MS = 40; + +//! Maximum amount of accumulated lag (in milliseconds) the engine will try +//! to catch up by running ticks back-to-back without sleeping. Beyond this +//! the engine drops the excess instead of spiral-of-death-ing. See +//! EngineRun.cpp. +static constexpr int MAX_CATCHUP_MS = 500; + +//! Tick interval (ms) the engine targets while replaying with fast-forward +//! enabled. Pairs with REPLAY_FAST_FORWARD_DRAW_RATIO so the GUI is drawn +//! once per N game-steps. ~3.33x normal speed at GAME_TICK_MS=40. See +//! EngineRun.cpp. +static constexpr int REPLAY_FAST_FORWARD_MS = 12; + +//! During replay fast-forward, draw 1 frame per (RATIO+1) simulation steps. +//! Encoded in the loop as `nextGuiStep = REPLAY_FAST_FORWARD_DRAW_RATIO - 1` +//! after each draw, so the GUI updates every (RATIO+1)-th tick. See +//! EngineRun.cpp. +static constexpr int REPLAY_FAST_FORWARD_DRAW_RATIO = 3; + +// === Engine init-time constants === + +//! Number of selectable AI implementations picked from when generating a +//! random matchup. The pick is `syncRand() % AI_RANDOM_PICK_COUNT + 1`, +//! skipping AI::NONE=0. Tracks the count of AICastor / AIEcho / AINicowar +//! / AIToubib / AIWarrush. See EngineLoaders.cpp. +static constexpr int AI_RANDOM_PICK_COUNT = 5; + +//! Bitmask value meaning "every team is visible" for replay viewing. Used +//! as the initial value of GlobalContainer::replayVisibleTeams (a Uint32 +//! per-team bitmask). See EngineInit.cpp. +static constexpr unsigned int REPLAY_VISIBLE_TEAMS_ALL = 0xFFFFFFFFu; + +// === Per-team / per-unit gameplay timers (in ticks) === + +//! How long a unit / building stays flagged as "under attack" after taking +//! damage. ~9.6 s at 25 TPS. Drives the under-attack icon, defensive flag +//! retargeting, and event throttling. See Unit.cpp / Building.h. +static constexpr int UNDER_ATTACK_TIMER_TICKS = 240; + +//! Initial value for Building::canNotConvertUnitTimer when a building +//! cannot recruit a unit; ticked down each step. ~6 s. See +//! Construction.cpp / building/Lifecycle.cpp. +static constexpr int CANNOT_CONVERT_TIMER_INIT = 150; + +// === Map mark / event lifetimes (in ticks) === + +//! Default time-to-live for a player-placed map mark before it disappears. +//! ~2 s. See MarkManager.cpp. +static constexpr int MARK_DEFAULT_LIFETIME_TICKS = 50; + +//! Per-event-type cooldown applied to GameEvent emission. Drops repeated +//! events of the same type for ~2 s after the previous one fired. +//! NOTE: Team::wasRecentEvent uses == against this exact value, so this +//! literal is structurally coupled — see bug #8 in the glossary. +static constexpr int GAME_EVENT_COOLDOWN_TICKS = 50; + +//! Maximum age (~4 s) of a GameEvent kept in Team's event list. Older +//! events are discarded when the list is updated. See Team.cpp. +static constexpr int GAME_EVENT_MAX_AGE_TICKS = 100; + +// === Map / minimap refresh intervals (in ticks) === + +//! Minimap full-redraw cadence — exactly one redraw per second at 25 TPS. +//! See Minimap.cpp. +static constexpr int MINIMAP_REFRESH_TICKS = 25; + +//! Clearing-flag local-resources gradient refresh cadence. ~5 s. See +//! TypeSteps.cpp. +static constexpr int CLEARING_FLAG_REFRESH_TICKS = 125; + diff --git a/src/EntitiesTypes.h b/src/EntitiesTypes.h deleted file mode 100644 index 4e316c180..000000000 --- a/src/EntitiesTypes.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __ENTITIES_TYPES_H -#define __ENTITIES_TYPES_H - -#include -#include - -#include -#include -#include -#include - -#include "EntityType.h" - -template class EntitiesTypes -{ -public: - virtual ~EntitiesTypes() - { - for (typename std::vector ::iterator it=entitiesTypes.begin(); it!=entitiesTypes.end(); ++it) - { - delete (*it); - } - } - - virtual void load(const std::string filename) - { - GAGCore::InputStream *stream = new GAGCore::BinaryInputStream(GAGCore::Toolkit::getFileManager()->openInputStreamBackend(filename)); - if (stream->isEndOfStream()) - { - std::cerr << "EntitiesTypes::load(\"" << filename << "\") : error, can't open file." << std::endl; - delete stream; - return; - } - - bool result = true; - - T defaultEntityType; - defaultEntityType.init(); - result = defaultEntityType.loadText(stream); - - while (result) - { - T *entityType = new T(); - *entityType = defaultEntityType; - result = entityType->loadText(stream); - if (result) - { - entitiesTypes.push_back(entityType); - } - else - delete entityType; - } - - delete stream; - } - - T* get(unsigned int num) - { - if ((num)::iterator it=entitiesTypes.begin(); it!=entitiesTypes.end(); ++it) - (*it)->dump(); - } - -protected: - std::vector entitiesTypes; -}; - -#endif diff --git a/src/EntityType.cpp b/src/EntityType.cpp deleted file mode 100644 index e60f08857..000000000 --- a/src/EntityType.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include -#include -#include -#include - -#include "EntityType.h" -#include "Utilities.h" - -EntityType::EntityType() -{ -} - -EntityType::EntityType(GAGCore::InputStream *stream) -{ - load(stream); -} - -void EntityType::init(void) -{ - size_t varSize; - Uint32 *startData; - getVars(&varSize, &startData); - memset(startData,0,varSize*sizeof(Uint32)); -} - -void EntityType::load(GAGCore::InputStream *stream) -{ - size_t size; - Uint32 *startData; - getVars(&size, &startData); - for (size_t i=0;ireadUint32(oss.str()); - } -} - -bool EntityType::loadText(GAGCore::InputStream *stream) -{ - char temp[256]; - char *token; - char *varname; - int val; - - size_t varSize; - Uint32 *startData; - const char **tab=getVars(&varSize, &startData); - - assert(stream); - while (true) - { - if (!Utilities::gets(temp, 256, stream)) - return false; - if (temp[0]=='*') - return true; - token=strtok(temp," \t\n\r=;"); - if ((!token) || (strcmp(token,"//")==0)) - continue; - varname=token; - token=strtok(NULL," \t\n\r=;"); - if (token) - val=atoi(token); - else - val=0; - - for (size_t i=0; iwriteUint32(startData[i], oss.str().c_str()); - } -} - -void EntityType::dump(void) -{ - size_t varSize; - Uint32 *startData; - const char **tab=getVars(&varSize, &startData); - - printf("%d Elements :\n", static_cast(varSize)); - for (size_t i=0; i or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __ENTITY_TYPE_H -#define __ENTITY_TYPE_H - -#include - -namespace GAGCore -{ - class InputStream; - class OutputStream; -} - -class EntityType -{ -public: - EntityType(); - EntityType(GAGCore::InputStream *stream); - virtual ~EntityType() { } - virtual const char **getVars(size_t *size, Uint32 **data) = 0; - virtual void init(void); - virtual void load(GAGCore::InputStream *stream); - virtual bool loadText(GAGCore::InputStream *stream); - virtual void save(GAGCore::OutputStream *stream); - virtual void dump(void); -}; - -#endif - diff --git a/src/Fatal.cpp b/src/Fatal.cpp index f13483575..956b05aae 100644 --- a/src/Fatal.cpp +++ b/src/Fatal.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #if (!defined (__GNUC__)) && (!defined (WIN32)) diff --git a/src/FertilityCalculator.cpp b/src/FertilityCalculator.cpp new file mode 100644 index 000000000..b14b2b4e7 --- /dev/null +++ b/src/FertilityCalculator.cpp @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007-2008 Bradley Arsenault + +#include "FertilityCalculator.h" + +#include "Map.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + // 31x31 weighting kernel: water tiles within Chebyshev distance kFertilityRadius + // of a grass tile contribute weight = int(kSqrtScale * sqrt((R-|dx|)*(R-|dy|))). + constexpr int kFertilityRadius = 15; + constexpr int kKernelSide = 2 * kFertilityRadius + 1; + constexpr float kSqrtScale = 4.2f; + + constexpr std::array, 8> kBfsNeighbors{{ + {-1, -1}, { 0, -1}, { 1, -1}, + {-1, 0}, { 1, 0}, + {-1, 1}, { 0, 1}, { 1, 1}, + }}; + + using DistanceMap = std::vector>; + + const std::array& fertilityKernel() + { + static const auto kernel = []() { + std::array k{}; + for (int ny = -kFertilityRadius; ny <= kFertilityRadius; ++ny) + { + for (int nx = -kFertilityRadius; nx <= kFertilityRadius; ++nx) + { + const int value = (kFertilityRadius - std::abs(nx)) + * (kFertilityRadius - std::abs(ny)); + const int idx = (ny + kFertilityRadius) * kKernelSide + + (nx + kFertilityRadius); + k[idx] = static_cast( + int(kSqrtScale * std::sqrt(static_cast(value)))); + } + } + return k; + }(); + return kernel; + } + + /// 8-connected BFS from every takeable corn/wood tile, traversing only grass + /// cells. Cells that are unreachable (or non-grass and not seeded) stay nullopt. + DistanceMap computeResourceDistance(const Map& map) + { + DistanceMap distance(static_cast(map.getW()) * map.getH()); + std::queue> frontier; + + for (int x = 0; x < map.getW(); ++x) + { + for (int y = 0; y < map.getH(); ++y) + { + if (map.isRessourceTakeable(x, y, CORN) + || map.isRessourceTakeable(x, y, WOOD)) + { + distance[map.coordToIndex(x, y)] = 0; + frontier.emplace(x, y); + } + } + } + + while (!frontier.empty()) + { + const auto [px, py] = frontier.front(); + frontier.pop(); + const Uint16 nextDepth = + static_cast(*distance[map.coordToIndex(px, py)] + 1); + + for (const auto [dx, dy] : kBfsNeighbors) + { + const int nx = map.normalizeX(px + dx); + const int ny = map.normalizeY(py + dy); + auto& cell = distance[map.coordToIndex(nx, ny)]; + if (!cell.has_value() && map.isGrass(nx, ny)) + { + cell = nextDepth; + frontier.emplace(nx, ny); + } + } + } + return distance; + } +} + +namespace FertilityCalculator +{ + void compute(Map& map, const ProgressCallback& progress) + { + // BFS is fast relative to the kernel pass, so it isn't progress-reported. + const DistanceMap reachable = computeResourceDistance(map); + const auto& kernel = fertilityKernel(); + + std::vector fertility( + static_cast(map.getW()) * map.getH(), 0); + Uint16 fertilityMax = 0; + + for (int x = 0; x < map.getW(); ++x) + { + if (progress) + progress(static_cast(x) / static_cast(map.getW())); + + for (int y = 0; y < map.getH(); ++y) + { + if (!map.isGrass(x, y)) + continue; + if (!reachable[map.coordToIndex(x, y)].has_value()) + continue; + + Uint16 total = 0; + for (int ny = -kFertilityRadius; ny <= kFertilityRadius; ++ny) + { + for (int nx = -kFertilityRadius; nx <= kFertilityRadius; ++nx) + { + // Map::isWater wraps coords via coordToIndex; no normalize needed. + if (map.isWater(x + nx, y + ny)) + { + const int kIdx = (ny + kFertilityRadius) * kKernelSide + + (nx + kFertilityRadius); + total += kernel[kIdx]; + } + } + } + fertilityMax = std::max(fertilityMax, total); + fertility[map.coordToIndex(x, y)] = total; + } + } + + for (int x = 0; x < map.getW(); ++x) + for (int y = 0; y < map.getH(); ++y) + map.getCase(x, y).fertility = fertility[map.coordToIndex(x, y)]; + map.fertilityMaximum = fertilityMax; + } +} diff --git a/src/FertilityCalculator.h b/src/FertilityCalculator.h new file mode 100644 index 000000000..156f74e0a --- /dev/null +++ b/src/FertilityCalculator.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007-2008 Bradley Arsenault + +#pragma once + +#include + +class Map; + +namespace FertilityCalculator +{ + /// Reports compute progress in [0, 1]. Invoked from the worker thread. + using ProgressCallback = std::function; + + /// Computes per-tile fertility, writes it into map.getCase(x,y).fertility, + /// and updates map.fertilityMaximum. The optional progress callback is + /// invoked once per column. May be called from a worker thread. + void compute(Map& map, const ProgressCallback& progress); +} diff --git a/src/FertilityCalculatorDialog.cpp b/src/FertilityCalculatorDialog.cpp index 386c8ed32..5e33e0587 100644 --- a/src/FertilityCalculatorDialog.cpp +++ b/src/FertilityCalculatorDialog.cpp @@ -1,136 +1,108 @@ -/* - Copyright (C) 2007-2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007-2008 Bradley Arsenault #include "FertilityCalculatorDialog.h" -#include "FertilityCalculatorThreadMessage.h" + +#include "FertilityCalculator.h" #include "GUIProgressBar.h" #include "GUIText.h" -#include #include "Map.h" -#include +#include "SDLCompat.h" #include "StringTable.h" #include "Toolkit.h" -#include "SDLCompat.h" + +#include +#include +#include using namespace GAGCore; using namespace GAGGUI; -using boost::static_pointer_cast; -FertilityCalculatorDialog::FertilityCalculatorDialog(GraphicContext *parentCtx, Map& map) - : OverlayScreen(parentCtx, 200, 100), map(map), parentCtx(parentCtx), thread(map, incoming, incomingMutex) +namespace { - addWidget(new Text(0, 20, ALIGN_FILL, ALIGN_LEFT, "standard", Toolkit::getStringTable()->getString("[Computing Fertility]"))); - percentDone = new Text(0, 40, ALIGN_FILL, ALIGN_LEFT, "menu"); - progress = new ProgressBar(0, 70, 0, ALIGN_FILL, ALIGN_TOP, 1000); - addWidget(percentDone); - addWidget(progress); - dispatchInit(); + constexpr int kProgressResolution = 1000; + constexpr Sint64 kFramePeriodMs = 40; } - - -void FertilityCalculatorDialog::onAction(Widget *source, Action action, int par1, int par2) +FertilityCalculatorDialog::FertilityCalculatorDialog(GraphicContext* parentCtx, Map& map) + : OverlayScreen(parentCtx, 200, 100), map(map), parentCtx(parentCtx) { - + addWidget(new Text(0, 20, ALIGN_FILL, ALIGN_LEFT, "standard", + Toolkit::getStringTable()->getString("[Computing Fertility]"))); + percentDone = new Text(0, 40, ALIGN_FILL, ALIGN_LEFT, "menu"); + progress = new ProgressBar(0, 70, 0, ALIGN_FILL, ALIGN_TOP, kProgressResolution); + addWidget(percentDone); + addWidget(progress); + dispatchInit(); } +void FertilityCalculatorDialog::onAction(Widget*, Action, int, int) +{ +} -void FertilityCalculatorDialog::execute() +void FertilityCalculatorDialog::runModal() { - // save screen in a temporary surface + // Save the screen behind us into a backing surface. parentCtx->setClipRect(); - DrawableSurface *background = new DrawableSurface(parentCtx->getW(), parentCtx->getH()); + DrawableSurface* background = new DrawableSurface(parentCtx->getW(), parentCtx->getH()); background->drawSurface(0, 0, parentCtx); - // start computing - boost::thread new_thread(boost::ref(thread)); + computeThread = std::thread([this]() { + FertilityCalculator::compute(map, [this](float p) { + progressFraction.store(p, std::memory_order_relaxed); + }); + computeDone.store(true, std::memory_order_release); + }); dispatchPaint(); SDL_Event event; - while(endValue<0) + while (endValue < 0) { - Uint64 time = SDL_GetTicks64(); + const Uint64 frameStart = SDL_GetTicks64(); while (SDL_PollEvent(&event)) { - if (event.type==SDL_QUIT) + if (event.type == SDL_QUIT) break; - //Manual integration of cmd+q and alt f4 - if(event.type == SDL_KEYDOWN) + // Manual integration of cmd+Q and Alt+F4. + if (event.type == SDL_KEYDOWN) { #ifdef USE_OSX - if(event.key.keysym.sym == SDLK_q && SDL_GetModState() & KMOD_GUI) - { + if (event.key.keysym.sym == SDLK_q && SDL_GetModState() & KMOD_GUI) break; - } #endif #ifdef USE_WIN32 - if(event.key.keysym.sym == SDLK_F4 && SDL_GetModState() & KMOD_ALT) - { + if (event.key.keysym.sym == SDLK_F4 && SDL_GetModState() & KMOD_ALT) break; - } #endif } - translateAndProcessEvent(&event); } - proccessIncoming(background); + + refreshProgressDisplay(); + if (computeDone.load(std::memory_order_acquire)) + endValue = 1; + dispatchPaint(); - parentCtx->drawSurface((int)0, (int)0, background); + parentCtx->drawSurface(0, 0, background); parentCtx->drawSurface(decX, decY, getSurface()); parentCtx->nextFrame(); - Uint64 newTime = SDL_GetTicks64(); - SDL_Delay(std::max(40ll - static_cast(newTime) + static_cast(time), 0)); + + const Uint64 frameEnd = SDL_GetTicks64(); + const Sint64 elapsed = static_cast(frameEnd) - static_cast(frameStart); + SDL_Delay(static_cast(std::max(kFramePeriodMs - elapsed, 0))); } - + + if (computeThread.joinable()) + computeThread.join(); delete background; } - - -void FertilityCalculatorDialog::proccessIncoming(DrawableSurface *background) +void FertilityCalculatorDialog::refreshProgressDisplay() { - //First parse incoming thread messages - boost::recursive_mutex::scoped_lock lock(incomingMutex); - while(!incoming.empty()) - { - boost::shared_ptr message = incoming.front(); - incoming.pop(); - Uint8 type = message->getMessageType(); - switch(type) - { - case FCTMUpdateCompletionPercent: - { - boost::shared_ptr info = static_pointer_cast(message); - std::stringstream s; - s<getPercent() * 100.0)<<"%"<setText(s.str()); - progress->setValue((int)(info->getPercent()*1000.0)); - } - break; - case FCTMFertilityCompleted: - { - boost::shared_ptr info = static_pointer_cast(message); - endValue = 1; - } - break; - } - } + const float p = progressFraction.load(std::memory_order_relaxed); + std::stringstream s; + s << std::setprecision(3) << (p * 100.0) << "%" << std::endl; + percentDone->setText(s.str()); + progress->setValue(static_cast(p * 1000.0)); } - diff --git a/src/FertilityCalculatorDialog.h b/src/FertilityCalculatorDialog.h index 41831b6b8..d3d0df99f 100644 --- a/src/FertilityCalculatorDialog.h +++ b/src/FertilityCalculatorDialog.h @@ -1,26 +1,11 @@ -/* - Copyright (C) 2007-2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007-2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef FertilityCalculatorDialog_h -#define FertilityCalculatorDialog_h +#pragma once #include "GUIBase.h" -#include "FertilityCalculatorThread.h" +#include +#include class Map; namespace GAGGUI @@ -33,30 +18,28 @@ namespace GAGCore class DrawableSurface; } -///This dialog shows progress of the fertility computation -class FertilityCalculatorDialog:public GAGGUI::OverlayScreen +/// Modal dialog that shows fertility-computation progress while the work runs +/// on a background thread. +class FertilityCalculatorDialog : public GAGGUI::OverlayScreen { public: - FertilityCalculatorDialog(GAGCore::GraphicContext *parentCtx, Map& map); - virtual ~FertilityCalculatorDialog() { } - virtual void onAction(GAGGUI::Widget *source, GAGGUI::Action action, int par1, int par2); - - ///This screen is modal, this executes it - void execute(); + FertilityCalculatorDialog(GAGCore::GraphicContext* parentCtx, Map& map); + ~FertilityCalculatorDialog() override = default; + void onAction(GAGGUI::Widget* source, GAGGUI::Action action, int par1, int par2) override; + + /// Modal: blocks until the background computation finishes. + void runModal(); + private: - ///This proccesses an incoming event from the fertility calculator thread - void proccessIncoming(GAGCore::DrawableSurface *background); - + void refreshProgressDisplay(); + Map& map; - GAGCore::GraphicContext *parentCtx; - + GAGCore::GraphicContext* parentCtx; + GAGGUI::Text* percentDone; GAGGUI::ProgressBar* progress; - - FertilityCalculatorThread thread; - std::queue > incoming; - boost::recursive_mutex incomingMutex; -}; - -#endif + std::thread computeThread; + std::atomic progressFraction{0.f}; + std::atomic computeDone{false}; +}; diff --git a/src/FertilityCalculatorThread.cpp b/src/FertilityCalculatorThread.cpp deleted file mode 100644 index 5e62cc4d0..000000000 --- a/src/FertilityCalculatorThread.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - Copyright (C) 2007-2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "FertilityCalculatorThread.h" -#include "Map.h" - -#include "FertilityCalculatorThreadMessage.h" - -FertilityCalculatorThread::FertilityCalculatorThread(Map& map, std::queue >& outgoing, boost::recursive_mutex& outgoingMutex) - : outgoing(outgoing), outgoingMutex(outgoingMutex), map(map) -{ -} - - - -void FertilityCalculatorThread::operator()() -{ - ///This function goes so quick relative to the following, it isn't even considered for percent complete - computeRessourcesGradient(); - fertilitymax = 0; - fertility.resize(map.getW() * map.getH()); - std::fill(fertility.begin(), fertility.end(), 0); - for(int x=0; x 1) - { - Uint16 total=0; - for(int nx = -15; nx <= 15; ++nx) - { - for(int ny = -15; ny <= 15; ++ny) - { - int value = (15 - std::abs(nx)) * (15 - std::abs(ny)); - //Square root fall-off, to make things more even - if(map.isWater(x+nx, y+ny)) - total += int(4.2f * std::sqrt((float)value)); - } - } - fertilitymax = std::max(fertilitymax, total); - fertility[x * map.getH() + y] = total; - } - } - } - } - - for(int x=0; x message(new FCTFertilityCompleted); - sendToMainThread(message); -} - - - -void FertilityCalculatorThread::sendMessage(boost::shared_ptr message) -{ - boost::recursive_mutex::scoped_lock lock(incomingMutex); - incoming.push(message); -} - - - -bool FertilityCalculatorThread::hasThreadExited() -{ - return hasExited; -} - - - -void FertilityCalculatorThread::sendToMainThread(boost::shared_ptr message) -{ - boost::recursive_mutex::scoped_lock lock(outgoingMutex); - outgoing.push(message); -} - - - - -void FertilityCalculatorThread::computeRessourcesGradient() -{ - gradient.resize(map.getW()*map.getH()); - std::fill(gradient.begin(), gradient.end(),0); - - std::queue positions; - for(int x=0; x message(new FCTUpdateCompletionPercent(percent)); - sendToMainThread(message); -} - - - -int FertilityCalculatorThread::get_pos(int x, int y) -{ - return x*map.getH()+y; -} - diff --git a/src/FertilityCalculatorThread.h b/src/FertilityCalculatorThread.h deleted file mode 100644 index 49fdb08d6..000000000 --- a/src/FertilityCalculatorThread.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - Copyright (C) 2007-2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef FertilityCalculatorThread_h -#define FertilityCalculatorThread_h - -#include -#include -#include -#include -#include "SDL_net.h" - -class FertilityCalculatorThreadMessage; -class Map; - -///This functor, meant to be executed in another thread, calculates the fertility of the map -class FertilityCalculatorThread -{ -public: - ///Constructs the functor - FertilityCalculatorThread(Map& map, std::queue >& outgoing, boost::recursive_mutex& outgoingMutex); - - ///Launches the thread that computes fertility - void operator()(); - - ///Sends this thread a message - void sendMessage(boost::shared_ptr message); - - ///This returns whether the thread has exited - bool hasThreadExited(); - -private: - ///Sends this IRC message back to the main thread - void sendToMainThread(boost::shared_ptr message); - - ///Computes the ressources gradient - void computeRessourcesGradient(); - - ///Updates the percent complete - void updatePercentComplete(float percent); - - class position - { - public: - position(int x, int y) : x(x), y(y) {} - int x; - int y; - }; - - int get_pos(int x, int y); - - std::queue > incoming; - std::queue >& outgoing; - boost::recursive_mutex incomingMutex; - boost::recursive_mutex& outgoingMutex; - bool hasExited; - - std::vector fertility; - std::vector gradient; - Uint16 fertilitymax; - Map& map; -}; - - - - -#endif diff --git a/src/FertilityCalculatorThreadMessage.cpp b/src/FertilityCalculatorThreadMessage.cpp deleted file mode 100644 index e842644d5..000000000 --- a/src/FertilityCalculatorThreadMessage.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "FertilityCalculatorThreadMessage.h" - -#include -#include - -FCTUpdateCompletionPercent::FCTUpdateCompletionPercent(float percent) - : percent(percent) -{ -} - - - -Uint8 FCTUpdateCompletionPercent::getMessageType() const -{ - return FCTMUpdateCompletionPercent; -} - - - -std::string FCTUpdateCompletionPercent::format() const -{ - std::ostringstream s; - s<<"FCTUpdateCompletionPercent("<<"percent="<(rhs); - if(r.percent == percent) - return true; - } - return false; -} - - -float FCTUpdateCompletionPercent::getPercent() const -{ - return percent; -} - - - -FCTFertilityCompleted::FCTFertilityCompleted() -{ -} - - - -Uint8 FCTFertilityCompleted::getMessageType() const -{ - return FCTMFertilityCompleted; -} - - - -std::string FCTFertilityCompleted::format() const -{ - std::ostringstream s; - s<<"FCTFertilityCompleted()"; - return s.str(); -} - - - -bool FCTFertilityCompleted::operator==(const FertilityCalculatorThreadMessage& rhs) const -{ - if(typeid(rhs)==typeid(FCTFertilityCompleted)) - { - //const FCTFertilityCompleted& r = dynamic_cast(rhs); - return true; - } - return false; -} - - -//code_append_marker - - diff --git a/src/FertilityCalculatorThreadMessage.h b/src/FertilityCalculatorThreadMessage.h deleted file mode 100644 index 1ee300197..000000000 --- a/src/FertilityCalculatorThreadMessage.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef FertilityCalculatorThreadMessage_h -#define FertilityCalculatorThreadMessage_h - -#include -#include "SDL_net.h" - -enum FertilityCalculatorThreadMessageType -{ - FCTMUpdateCompletionPercent, - FCTMFertilityCompleted, - //type_append_marker -}; - - -///This class represents a message sent between the main thread and the thread that manages fertility calculations -class FertilityCalculatorThreadMessage -{ -public: - virtual ~FertilityCalculatorThreadMessage() {} - - ///Returns the event type - virtual Uint8 getMessageType() const = 0; - - ///Returns a formatted version of the event - virtual std::string format() const = 0; - - ///Compares two IRCThreadMessageType - virtual bool operator==(const FertilityCalculatorThreadMessage& rhs) const = 0; -}; - - -///FCTUpdateCompletionPercent -class FCTUpdateCompletionPercent : public FertilityCalculatorThreadMessage -{ -public: - ///Creates a FCTUpdateCompletionPercent event - FCTUpdateCompletionPercent(float percent); - - ///Returns FCTMUpdateCompletionPercent - Uint8 getMessageType() const; - - ///Returns a formatted version of the event - std::string format() const; - - ///Compares two FertilityCalculatorThreadMessage - bool operator==(const FertilityCalculatorThreadMessage& rhs) const; - - ///Retrieves percent - float getPercent() const; -private: - float percent; -}; - - - - -///FCTFertilityCompleted -class FCTFertilityCompleted : public FertilityCalculatorThreadMessage -{ -public: - ///Creates a FCTFertilityCompleted event - FCTFertilityCompleted(); - - ///Returns FCTMFertilityCompleted - Uint8 getMessageType() const; - - ///Returns a formatted version of the event - std::string format() const; - - ///Compares two FertilityCalculatorThreadMessage - bool operator==(const FertilityCalculatorThreadMessage& rhs) const; -}; - - - -//event_append_marker - -#endif - diff --git a/src/FileFormatVersions.h b/src/FileFormatVersions.h new file mode 100644 index 000000000..08260b79a --- /dev/null +++ b/src/FileFormatVersions.h @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// FileFormatVersions.h +// +// Save-format minor-version gates and 4-byte ASCII section signatures used +// throughout the save / replay loaders. Every `if (versionMinor >= N)` / +// `< N` comparison in Game_io / GameHeader / Player / Lifecycle / etc. has +// a corresponding named constant here, so that adding a new save-format +// feature only requires touching this one file plus the consumer. +// +// Per-AI minor-version gates (Nicowar's 59 / 60 / 66, etc.) live in their +// own AI tuning headers so that AI internals do not bleed into the +// engine-wide loader. + +#pragma once + +// === Save-format minor-version feature gates === +// Each constant is the FIRST versionMinor at which the named feature +// appears in the save stream. Loaders write/read the new field if +// versionMinor >= FILE_FORMAT_VERSION_*; otherwise they fall back to the +// pre-feature default. VALUES MUST NEVER MOVE — they are wire-locked. + +//! Building::underAttackTimer added (Lifecycle.cpp:201; UnitSerialization.cpp:62). +static constexpr int FILE_FORMAT_VERSION_UNDER_ATTACK_TIMER = 61; + +//! Pre-fertility marker — saves before this lacked the fertility map. +static constexpr int FILE_FORMAT_VERSION_PRE_FERTILITY = 63; + +//! Unified seed introduced; "GaBt" sig appeared (Game_io.cpp:134, 146, 417; +//! GameHeader.cpp:54, 75, 77, 129, 150, 152). +static constexpr int FILE_FORMAT_VERSION_UNIFIED_SEED = 64; + +//! Building::maxUnitWorkingPrevious field added (Lifecycle.cpp:423). +static constexpr int FILE_FORMAT_VERSION_MAX_UNIT_WORKING_PREVIOUS = 65; + +//! Building::maxUnitWorkingFuture field added (Lifecycle.cpp:427). +static constexpr int FILE_FORMAT_VERSION_MAX_UNIT_WORKING_FUTURE = 70; + +//! Allies + winning conditions added (GameHeader.cpp:54, 129). +static constexpr int FILE_FORMAT_VERSION_ALLIES_AND_WIN_CONDITIONS = 71; + +//! mapDiscovered flag added (GameHeader.cpp:77, 152). +static constexpr int FILE_FORMAT_VERSION_MAP_DISCOVERED_FLAG = 72; + +//! Team::race field added (TeamSerialization.cpp:135). +static constexpr int FILE_FORMAT_VERSION_RACE_FIELD = 73; + +//! Building::unitsFailingRequirements stored as int (Lifecycle.cpp:432). +//! Predates the array form (see _ARRAY below). +static constexpr int FILE_FORMAT_VERSION_UNITS_FAILING_REQUIREMENTS_INT = 74; + +//! Campaign text / objectives section added (Game_io.cpp:204). +static constexpr int FILE_FORMAT_VERSION_CAMPAIGN_TEXT_OBJECTIVES = 75; + +//! Briefing + hints + objective `failed` flag (Game_io.cpp:215; +//! GameObjectives.cpp:225). +static constexpr int FILE_FORMAT_VERSION_BRIEFING_HINTS_OBJ_FAILED = 76; + +//! Building::unitsFailingRequirements promoted from int to array +//! (Lifecycle.cpp:432). +static constexpr int FILE_FORMAT_VERSION_UNITS_FAILING_REQUIREMENTS_ARRAY = 77; + +//! OrderCreate payload grew 20 -> 28 bytes to carry flagRadius +//! (OrderBuilding.cpp:47-49). +static constexpr int FILE_FORMAT_VERSION_ORDER_CREATE_FLAG_RADIUS = 78; + +//! Building::priority field added (Lifecycle.cpp:211). +static constexpr int FILE_FORMAT_VERSION_BUILDING_PRIORITY_FIELD = 79; + +//! Building::unitsHarvesting list added (Lifecycle.cpp:459). +static constexpr int FILE_FORMAT_VERSION_UNITS_HARVESTING_LIST = 80; + +//! Building::canNotConvertUnitTimer added (Lifecycle.cpp:205, 208). +static constexpr int FILE_FORMAT_VERSION_CANNOT_CONVERT_TIMER = 81; + +//! USL mapscript serialization (Game_io.cpp:197). +static constexpr int FILE_FORMAT_VERSION_USL_MAPSCRIPT = 82; + +//! Drop unit-skin name into the save stream (UnitSerialization.cpp:25). +static constexpr int FILE_FORMAT_VERSION_DROP_UNIT_SKIN_NAME = 84; + +// === Save-file section signatures (4-byte ASCII tags) === +// Embedded as four chars at the start of each save section so a corrupted +// stream fails fast. NEVER change these values — old saves on disk depend +// on them. + +//! Length in bytes of every section signature above. +static constexpr int FILE_SIG_LEN = 4; + +//! Top-level "Game Begin" sig (Game_io.cpp:128, 415). +inline constexpr const char FILE_SIG_GAME_BEGIN[5] = "GaBe"; +//! "Game Sync" — pre-step state (Game_io.cpp:141, 428). +inline constexpr const char FILE_SIG_GAME_SYNC[5] = "GaSy"; +//! "Game Built" — post-construction marker for unified-seed saves +//! (Game_io.cpp:146). +inline constexpr const char FILE_SIG_GAME_BUILT[5] = "GaBt"; +//! "Game Team" sig (Game_io.cpp:160, 435). +inline constexpr const char FILE_SIG_GAME_TEAM[5] = "GaTe"; +//! "Game Map" sig (Game_io.cpp:167, 446). +inline constexpr const char FILE_SIG_GAME_MAP[5] = "GaMa"; +//! "Game Player" sig (Game_io.cpp:180). +inline constexpr const char FILE_SIG_GAME_PLAYER[5] = "GaPl"; +//! Player section "begin" sig (Player.cpp:107). +inline constexpr const char FILE_SIG_PLAYER_BEGIN[5] = "PLYb"; +//! Player section "end" sig (Player.cpp:152). +inline constexpr const char FILE_SIG_PLAYER_END[5] = "PLYe"; +//! Game checksum sidecar v1 magic (ChecksumSidecar.cpp:41). +inline constexpr const char FILE_SIG_CHECKSUM_SIDECAR[5] = "GCS1"; + +//! SHA-1 hash byte length, used by the checksum sidecar (Game_io.cpp:459-461). +static constexpr int SHA1_BYTE_LEN = 20; + diff --git a/src/FixedPoint.h b/src/FixedPoint.h new file mode 100644 index 000000000..c705c9d05 --- /dev/null +++ b/src/FixedPoint.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// FixedPoint.h +// +// Fixed-point arithmetic shifts shared across the simulation. The C++ +// engine carries deterministic numeric state in plain integer types and +// uses Q16.16 (and occasionally Q24.8) shifts to interpret them as +// fractional. These constants name the shift / mask / "one" so that +// expressions like `(value << 16)` or `value & 65535` become +// `value << FIXED_POINT_SHIFT_16` etc. Sites: Construction.cpp, +// TypeSteps.cpp, Update.cpp, Minimap.cpp, Step.cpp, TeamRouting.cpp, +// UnitMovement.cpp. +// +// NOTE FOR PORTERS: The Rust port replaces this with the `I16F16` type +// from the `fixed` crate (see docs/rust/determinism.md); raw shifts stay +// only on the C++ side. + +#pragma once + +// === Q16.16 fixed-point === + +//! Bit-shift used to encode a Q16.16 fixed-point value: the integer part +//! occupies the high 16 bits, the fractional part the low 16 bits. +static constexpr int FIXED_POINT_SHIFT_16 = 16; + +//! Mask for the fractional 16 bits of a Q16.16 value, equal to +//! (1u << FIXED_POINT_SHIFT_16) - 1. +static constexpr unsigned int FIXED_POINT_FRAC_MASK = 65535u; + +//! The Q16.16 representation of 1.0, equal to 1u << FIXED_POINT_SHIFT_16. +static constexpr unsigned int FIXED_POINT_ONE = 65536u; + +// === Q24.8 fixed-point === + +//! Bit-shift used by per-tick scaling that does not need 16-bit fractional +//! precision (building shootSpeed, attack quality, upgrade-score scaling). +//! Sites: Step.cpp:200, TypeSteps.cpp:357, 358, 365, 366, TeamRouting.cpp:227, +//! UnitMovement.cpp:250, 270. +static constexpr int Q8_FIXED_POINT_SHIFT = 8; + diff --git a/src/GUIGlob2FileList.cpp b/src/GUIGlob2FileList.cpp index dd357590a..8d648d7c0 100644 --- a/src/GUIGlob2FileList.cpp +++ b/src/GUIGlob2FileList.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "GUIGlob2FileList.h" #include "Game.h" diff --git a/src/GUIGlob2FileList.h b/src/GUIGlob2FileList.h index f2e252199..7aecfef76 100644 --- a/src/GUIGlob2FileList.h +++ b/src/GUIGlob2FileList.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIGLOB2FILELIST_H -#define __GUIGLOB2FILELIST_H +#pragma once #include using namespace GAGGUI; @@ -41,6 +24,3 @@ class Glob2FileList: public FileList virtual std::string listToFile(const std::string listName) const; }; - - -#endif diff --git a/src/GUIMapPreview.cpp b/src/GUIMapPreview.cpp index 0faa158b0..15d97a121 100644 --- a/src/GUIMapPreview.cpp +++ b/src/GUIMapPreview.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include @@ -37,8 +21,8 @@ MapPreview::MapPreview(int x, int y, Uint32 hAlign, Uint32 vAlign) this->y = y; this->hAlignFlag = hAlign; this->vAlignFlag = vAlign; - this->w = 128; - this->h = 128; + this->w = PreviewSize; + this->h = PreviewSize; surface = NULL; } @@ -49,8 +33,8 @@ MapPreview::MapPreview(int x, int y, Uint32 hAlign, Uint32 vAlign, const std::st this->y = y; this->hAlignFlag = hAlign; this->vAlignFlag = vAlign; - this->w = 128; - this->h = 128; + this->w = PreviewSize; + this->h = PreviewSize; surface = NULL; } @@ -109,35 +93,36 @@ void MapPreview::setMapThumbnail(const MapThumbnail &nthumbnail) void MapPreview::paint(void) { - int x, y, w, h; - getScreenPos(&x, &y, &w, &h); - + int sx, sy, sw, sh; + getScreenPos(&sx, &sy, &sw, &sh); + assert(parent); assert(parent->getSurface()); - + DrawableSurface *target = parent->getSurface(); + + // getScreenPos returns the layout area; ALIGN_FILL means "centre the + // fixed PreviewSize tile within the available area on that axis". if (hAlignFlag == ALIGN_FILL) - x += (w-128)>>1; + sx += (sw - PreviewSize) / 2; if (vAlignFlag == ALIGN_FILL) - y += (h-128)>>1; - + sy += (sh - PreviewSize) / 2; + if (surface) { - parent->getSurface()->drawSurface(x, y, surface); + target->drawSurface(sx, sy, surface); } else { - /*parent->getSurface()->drawLine(x, y, x+127, y+127, 255, 0, 0); - parent->getSurface()->drawLine(x+127, y, x, y+127, 255, 0, 0);*/ - /*parent->getSurface()->drawRect(x, y, 128, 128, ColorTheme::frontColor);*/ Font *standardFont = Toolkit::getFont("standard"); assert(standardFont); - std::string line0 = Toolkit::getStringTable()->getString("[GUIMapPreview text 0]"); - std::string line1 = Toolkit::getStringTable()->getString("[GUIMapPreview text 1]"); - int sw0 = standardFont->getStringWidth(line0); - int sw1 = standardFont->getStringWidth(line1); - int sh = standardFont->getStringHeight(line0); - parent->getSurface()->drawString(x+((128-sw0)>>1), y+64-sh, standardFont, line0); - parent->getSurface()->drawString(x+((128-sw1)>>1), y+64, standardFont, line1); + const std::string line0 = Toolkit::getStringTable()->getString("[GUIMapPreview text 0]"); + const std::string line1 = Toolkit::getStringTable()->getString("[GUIMapPreview text 1]"); + const int line0Width = standardFont->getStringWidth(line0); + const int line1Width = standardFont->getStringWidth(line1); + const int lineHeight = standardFont->getStringHeight(line0); + const int centerY = sy + PreviewSize / 2; + target->drawString(sx + (PreviewSize - line0Width) / 2, centerY - lineHeight, standardFont, line0); + target->drawString(sx + (PreviewSize - line1Width) / 2, centerY, standardFont, line1); } - Style::style->drawFrame(parent->getSurface(), x, y, 128, 128, Color::ALPHA_TRANSPARENT); + Style::style->drawFrame(target, sx, sy, PreviewSize, PreviewSize, Color::ALPHA_TRANSPARENT); } diff --git a/src/GUIMapPreview.h b/src/GUIMapPreview.h index 48caf9278..81d4a6c2f 100644 --- a/src/GUIMapPreview.h +++ b/src/GUIMapPreview.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GUIMAPPREVIEW_H -#define __GUIMAPPREVIEW_H +#pragma once #include #include "MapGenerationDescriptor.h" @@ -42,6 +25,9 @@ using namespace GAGCore; class MapPreview: public RectangularWidget { public: + //! Fixed pixel size of the preview tile (square). + static constexpr int PreviewSize = 128; + //! Constructor, takes position, alignement and initial map name MapPreview(int x, int y, Uint32 hAlign, Uint32 vAlign); //! Constructor, takes position, alignement, initial map name and a tooltip @@ -66,4 +52,3 @@ class MapPreview: public RectangularWidget DrawableSurface* surface; }; -#endif diff --git a/src/Game.cpp b/src/Game.cpp index 6cf725b2d..27f7f65d0 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include @@ -37,13 +21,13 @@ #include #include "BuildingType.h" +#include "DatasetWriter.h" #include "Game.h" #include "GameUtilities.h" #include "GlobalContainer.h" -#include "LogFileManager.h" #include "Order.h" #include "Unit.h" -#include "UnitSkin.h" +#include "render/UnitSkin.h" #include "Integrity.h" #include "Utilities.h" #include "GameGUI.h" @@ -57,43 +41,22 @@ #include "TextStream.h" #include "FertilityCalculatorDialog.h" -#include "NetMessage.h" - #include "ReplayWriter.h" -#define BULLET_IMGID 0 +#ifndef YOG_SERVER_ONLY +#include "render/GameAnimations.h" +#endif // !YOG_SERVER_ONLY -#define MIN_MAX_PRESIGE 500 -#define TEAM_MAX_PRESTIGE 150 +#define BULLET_IMGID 0 Game::Game(GameGUI *gui, MapEdit* edit): mapscript(gui) { - logFile = globalContainer->logFileManager->getFile("Game.log"); - init(gui, edit); } Game::~Game() { - int sum=0; - for (int i=0; iedit=edit; buildProjects.clear(); +#ifndef YOG_SERVER_ONLY + animations = std::make_unique(!globalContainer->runNoX, 0); +#endif // !YOG_SERVER_ONLY + mapHeader.reset(); gameHeader.reset(); @@ -125,7 +92,7 @@ void Game::init(GameGUI *gui, MapEdit* edit) stepCounter=0; prestigeToReach=0; - for (int i=0; iteamNumber must satisfy +// 0 <= teamNumber < mapHeader.getNumberOfTeams() AND teams[teamNumber] must +// be non-null. The two in-memory callers (MapEditClicks, MapEditDialog) +// build their GameHeader from a getNumberOfTeams() loop, so they're +// well-formed by construction. The loader path (GameGUIPersistence -> +// GameHeader::load -> BasePlayer::load) bounds-checks teamNumber against +// Team::MAX_COUNT before reaching here; the assert catches the residual +// case where teamNumber is in [getNumberOfTeams(), MAX_COUNT) — a stale +// header paired with a smaller-team map. void Game::setGameHeader(const GameHeader& newGameHeader, bool saveAI) { for (int i=0; isetBasePlayer(&newGameHeader.getBasePlayer(i), teams); } - teams[players[i]->teamNumber]->numberOfPlayer+=1; - teams[players[i]->teamNumber]->playersMask|=(1<teamNumber; + assert(tn >= 0 && tn < mapHeader.getNumberOfTeams()); + assert(teams[tn] != NULL); + teams[tn]->numberOfPlayer+=1; + teams[tn]->playersMask|=(1< order, int localPlayer) -{ - assert(order->sender>=0); - assert(order->sendersender < gameHeader.getNumberOfPlayers()); - - if (globalContainer->replayWriter && globalContainer->replayWriter->isValid()) - { - globalContainer->replayWriter->pushOrder(order); - } - - anyPlayerWaited=false; - Team *team=players[order->sender]->team; - assert(team); - bool isPlayerAlive=team->isAlive; - Uint8 orderType=order->getOrderType(); - switch (orderType) - { - case ORDER_CREATE: - { - boost::shared_ptr oc=boost::static_pointer_cast(order); - if (!isPlayerAlive) - break; - - int posX=(oc->posX)&map.getMaskW(); - int posY=(oc->posY)&map.getMaskH(); - assert(oc->teamNumber==team->teamNumber); - BuildingType *bt=globalContainer->buildingsTypes.get(oc->typeNum); - bool isVirtual=bt->isVirtual; - int w=bt->width; - int h=bt->height; - if (!isVirtual && (team->noMoreBuildingSitesCountdown>0)) - break; - bool isRoom=checkRoomForBuilding(posX, posY, bt, oc->teamNumber); - if (isVirtual || isRoom) - { - Building *b=addBuilding(posX, posY, oc->typeNum, oc->teamNumber, oc->unitWorking, oc->unitWorkingFuture); - if (b) - { - if(isVirtual && oc->flagRadius>=0) - { - b->unitStayRange = oc->flagRadius; - b->unitStayRangeLocal = oc->flagRadius; - } - fprintf(logFile, "ORDER_CREATE (%d, %d, %d)", posX, posY, bt->shortTypeNum); - b->owner->addToStaticAbilitiesLists(b); - b->update(); - } - } - else if (!isVirtual && !isRoom && map.isHardSpaceForBuilding(posX, posY, w, h)) - { - BuildProject buildProject; - buildProject.posX = posX; - buildProject.posY = posY; - fprintf(logFile, "new BuildProject (%d, %d)", posX, posY); - buildProject.teamNumber = oc->teamNumber; - buildProject.typeNum = oc->typeNum; - buildProject.unitWorking = oc->unitWorking; - buildProject.unitWorkingFuture = oc->unitWorkingFuture; - buildProjects.push_back(buildProject); - Uint32 teamMask=Team::teamNumberToMask(oc->teamNumber); - for (int y=posY; yteamNumber == players[localPlayer]->teamNumber) - map.localForbiddenMap.set(index, true); - } - map.updateForbiddenGradient(oc->teamNumber); - } - } - break; - case ORDER_MODIFY_BUILDING: - { - if (!isPlayerAlive) - break; - boost::shared_ptr omb=boost::static_pointer_cast(order); - Uint16 gid=omb->gid; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Building *b=teams[team]->myBuildings[id]; - if ((b) && (b->buildingState==Building::ALIVE)) - { - fprintf(logFile, "ORDER_MODIFY_BUILDING"); - assert(omb->numberRequested <= 20); - b->maxUnitWorking=omb->numberRequested; - b->maxUnitWorkingPreferred=b->maxUnitWorking; - if (order->sender!=localPlayer) - b->maxUnitWorkingLocal=b->maxUnitWorking; - b->update(); - } - } - break; - case ORDER_MODIFY_EXCHANGE: - { - if (!isPlayerAlive) - break; - boost::shared_ptr ome=boost::static_pointer_cast(order); - Uint16 gid=ome->gid; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Building *b=teams[team]->myBuildings[id]; - if ((b) && (b->buildingState==Building::ALIVE)) - { - fprintf(logFile, "ORDER_MODIFY_EXCHANGE"); - b->receiveRessourceMask=ome->receiveRessourceMask; - b->sendRessourceMask=ome->sendRessourceMask; - if (order->sender!=localPlayer) - { - b->receiveRessourceMaskLocal=b->receiveRessourceMask; - b->sendRessourceMaskLocal=b->sendRessourceMask; - } - b->update(); - } - } - break; - case ORDER_MODIFY_FLAG: - { - if (!isPlayerAlive) - break; - boost::shared_ptr omf=boost::static_pointer_cast(order); - Uint16 gid=omf->gid; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Building *b=teams[team]->myBuildings[id]; - if ((b) && (b->buildingState==Building::ALIVE) && (b->type->defaultUnitStayRange)) - { - fprintf(logFile, "ORDER_MODIFY_FLAG"); - int oldRange=b->unitStayRange; - int newRange=omf->range; - b->unitStayRange=newRange; - if (order->sender!=localPlayer) - b->unitStayRangeLocal=newRange; - - if (b->type->zonableForbidden) - { - if (newRangedirtyGlobalGradient(); - map.dirtyLocalGradient(b->posX-oldRange-16, b->posY-oldRange-16, 32+oldRange*2, 32+oldRange*2, team); - } - } - else - { - for (int i=0; i<2; i++) - { - b->dirtyLocalGradient[i]=true; - b->locked[i]=false; - if (b->globalGradient[i]) - { - delete[] b->globalGradient[i]; - b->globalGradient[i]=NULL; - } - if (b->localRessources[i]) - { - delete[] b->localRessources[i]; - b->localRessources[i]=NULL; - } - } - } - } - } - break; - case ORDER_MODIFY_CLEARING_FLAG: - { - if (!isPlayerAlive) - break; - boost::shared_ptr omcf=boost::static_pointer_cast(order); - Uint16 gid=omcf->gid; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Building *b=teams[team]->myBuildings[id]; - if (b - && b->buildingState==Building::ALIVE - && b->type->defaultUnitStayRange - && b->type->zonable[WORKER]) - { - fprintf(logFile, "ORDER_MODIFY_CLEARING_FLAG"); - memcpy(b->clearingRessources, omcf->clearingRessources, sizeof(bool)*BASIC_COUNT); - if (order->sender!=localPlayer) - memcpy(b->clearingRessourcesLocal, omcf->clearingRessources, sizeof(bool)*BASIC_COUNT); - } - } - break; - case ORDER_MODIFY_MIN_LEVEL_TO_FLAG: - { - if (!isPlayerAlive) - break; - boost::shared_ptr omwf=boost::static_pointer_cast(order); - int team=Building::GIDtoTeam(omwf->gid); - int id=Building::GIDtoID(omwf->gid); - Building *b=teams[team]->myBuildings[id]; - if (b - && b->buildingState==Building::ALIVE - && b->type->defaultUnitStayRange - && (b->type->zonable[WARRIOR] || b->type->zonable[EXPLORER])) - { - fprintf(logFile, "ORDER_MODIFY_MIN_LEVEL_TO_FLAG"); - b->minLevelToFlag = omwf->minLevelToFlag; - // if it was another player, update local - if (order->sender != localPlayer) - b->minLevelToFlagLocal = b->minLevelToFlag; - - // flush all the actual units - int maxUnitWorkingSaved = b->maxUnitWorking; - b->maxUnitWorking = 0; - b->update(); - b->maxUnitWorking = maxUnitWorkingSaved; - b->update(); - } - } - break; - case ORDER_MOVE_FLAG: - { - if (!isPlayerAlive) - break; - boost::shared_ptr omf=boost::static_pointer_cast (order); - Uint16 gid=omf->gid; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - bool drop=omf->drop; - Building *b=teams[team]->myBuildings[id]; - if ((b) && (b->buildingState==Building::ALIVE) && (b->type->isVirtual)) - { - fprintf(logFile, "ORDER_MOVE_FLAG"); - if (drop && b->type->zonableForbidden) - { - int range=b->unitStayRange; - map.dirtyLocalGradient(b->posX-range-16, b->posY-range-16, 32+range*2, 32+range*2, team); - } - - b->posX=omf->x; - b->posY=omf->y; - - if (b->type->zonableForbidden) - { - if (drop) - teams[team]->dirtyGlobalGradient(); - } - else - { - for (int i=0; i<2; i++) - { - b->dirtyLocalGradient[i]=true; - b->locked[i]=false; - if (b->globalGradient[i]) - { - delete[] b->globalGradient[i]; - b->globalGradient[i]=NULL; - } - if (b->localRessources[i]) - { - delete b->localRessources[i]; - b->localRessources[i]=NULL; - } - } - } - - if (order->sender!=localPlayer || globalContainer->replaying) - { - b->posXLocal=b->posX; - b->posYLocal=b->posY; - } - } - } - break; - case ORDER_ALTERATE_FORBIDDEN: - { - fprintf(logFile, "ORDER_ALTERATE_FORBIDDEN"); - boost::shared_ptr oaa = boost::static_pointer_cast(order); - if (oaa->type == BrushTool::MODE_ADD) - { - Uint32 teamMask = Team::teamNumberToMask(oaa->teamNumber); - size_t orderMaskIndex = 0; - for (int y=oaa->centerY+oaa->minY; ycenterY+oaa->maxY; y++) - for (int x=oaa->centerX+oaa->minX; xcenterX+oaa->maxX; x++) - { - if (oaa->mask.get(orderMaskIndex)) - { - size_t index = (x&map.wMask)+(((y&map.hMask)<teamNumber == players[localPlayer]->teamNumber) - map.localForbiddenMap.set(index, true); - } - orderMaskIndex++; - } - } - else if (oaa->type == BrushTool::MODE_DEL) - { - Uint32 notTeamMask = ~Team::teamNumberToMask(oaa->teamNumber); - size_t orderMaskIndex = 0; - for (int y=oaa->centerY+oaa->minY; ycenterY+oaa->maxY; y++) - for (int x=oaa->centerX+oaa->minX; xcenterX+oaa->maxX; x++) - { - if (oaa->mask.get(orderMaskIndex)) - { - size_t index = (x&map.wMask)+(((y&map.hMask)<teamNumber == players[localPlayer]->teamNumber) - map.localForbiddenMap.set(index, false); - } - orderMaskIndex++; - } - - // We remove, so we need to refresh the gradients, unfortunatly - teams[oaa->teamNumber]->dirtyGlobalGradient(); - map.dirtyLocalGradient(oaa->centerX+oaa->minX-16, oaa->centerY+oaa->minY-16, oaa->maxX-oaa->minX+32, oaa->maxY-oaa->minY+32, oaa->teamNumber); - } - else - assert(false); - map.updateForbiddenGradient(oaa->teamNumber); - map.updateGuardAreasGradient(oaa->teamNumber); - map.updateClearAreasGradient(oaa->teamNumber); - } - break; - case ORDER_ALTERATE_GUARD_AREA: - { - fprintf(logFile, "ORDER_ALTERATE_GUARD_AREA"); - boost::shared_ptr oaa = boost::static_pointer_cast(order); - if (oaa->type == BrushTool::MODE_ADD) - { - Uint32 teamMask = Team::teamNumberToMask(oaa->teamNumber); - size_t orderMaskIndex = 0; - for (int y=oaa->centerY+oaa->minY; ycenterY+oaa->maxY; y++) - for (int x=oaa->centerX+oaa->minX; xcenterX+oaa->maxX; x++) - { - if (oaa->mask.get(orderMaskIndex)) - { - size_t index = (x&map.wMask)+(((y&map.hMask)<teamNumber == players[localPlayer]->teamNumber) - map.localGuardAreaMap.set(index, true); - } - orderMaskIndex++; - } - } - else if (oaa->type == BrushTool::MODE_DEL) - { - Uint32 notTeamMask = ~Team::teamNumberToMask(oaa->teamNumber); - size_t orderMaskIndex = 0; - for (int y=oaa->centerY+oaa->minY; ycenterY+oaa->maxY; y++) - for (int x=oaa->centerX+oaa->minX; xcenterX+oaa->maxX; x++) - { - if (oaa->mask.get(orderMaskIndex)) - { - size_t index = (x&map.wMask)+(((y&map.hMask)<teamNumber == players[localPlayer]->teamNumber) - map.localGuardAreaMap.set(index, false); - } - orderMaskIndex++; - } - } - else - assert(false); - map.updateGuardAreasGradient(oaa->teamNumber); - } - break; - case ORDER_ALTERATE_CLEAR_AREA: - { - fprintf(logFile, "ORDER_ALTERATE_CLEAR_AREA"); - boost::shared_ptr oaa = boost::static_pointer_cast(order); - if (oaa->type == BrushTool::MODE_ADD) - { - Uint32 teamMask = Team::teamNumberToMask(oaa->teamNumber); - size_t orderMaskIndex = 0; - for (int y=oaa->centerY+oaa->minY; ycenterY+oaa->maxY; y++) - for (int x=oaa->centerX+oaa->minX; xcenterX+oaa->maxX; x++) - { - if (oaa->mask.get(orderMaskIndex)) - { - size_t index = (x&map.wMask)+(((y&map.hMask)<teamNumber == players[localPlayer]->teamNumber) - map.localClearAreaMap.set(index, true); - } - orderMaskIndex++; - } - } - else if (oaa->type == BrushTool::MODE_DEL) - { - Uint32 notTeamMask = ~Team::teamNumberToMask(oaa->teamNumber); - size_t orderMaskIndex = 0; - for (int y=oaa->centerY+oaa->minY; ycenterY+oaa->maxY; y++) - for (int x=oaa->centerX+oaa->minX; xcenterX+oaa->maxX; x++) - { - if (oaa->mask.get(orderMaskIndex)) - { - size_t index = (x&map.wMask)+(((y&map.hMask)<teamNumber == players[localPlayer]->teamNumber) - map.localClearAreaMap.set(index, false); - } - orderMaskIndex++; - } - } - else - assert(false); - map.updateClearAreasGradient(oaa->teamNumber); - } - break; - case ORDER_MODIFY_SWARM: - { - if (!isPlayerAlive) - break; - boost::shared_ptr oms=boost::static_pointer_cast(order); - Uint16 gid=oms->gid; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Building *b=teams[team]->myBuildings[id]; - if ((b) && (b->buildingState==Building::ALIVE) && (b->type->unitProductionTime)) - { - fprintf(logFile, "ORDER_MODIFY_SWARM"); - for (int j=0; jratio[j]=oms->ratio[j]; - if (order->sender!=localPlayer) - b->ratioLocal[j]=b->ratio[j]; - } - b->update(); - } - } - break; - case ORDER_DELETE: - { - Uint16 gid=boost::static_pointer_cast(order)->gid; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Building *b=teams[team]->myBuildings[id]; - if (b) - { - fprintf(logFile, "ORDER_DELETE"); - b->launchDelete(); - assert(b->type); - if (b->type->zonableForbidden) - { - teams[team]->dirtyGlobalGradient(); - int range=b->unitStayRange; - map.dirtyLocalGradient(b->posX-range-16, b->posY-range-16, 32+range*2, 32+range*2, team); - } - } - } - break; - case ORDER_CHANGE_PRIORITY: - { - Uint16 gid=boost::static_pointer_cast(order)->gid; - Sint32 priority=boost::static_pointer_cast(order)->priority; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Building *b=teams[team]->myBuildings[id]; - if (b) - { - fprintf(logFile, "ORDER_CHANGE_PRIORITY"); - b->priority = priority; - b->updateCallLists(); - } - } - break; - case ORDER_CANCEL_DELETE: - { - Uint16 gid=boost::static_pointer_cast(order)->gid; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Building *b=teams[team]->myBuildings[id]; - if (b) - { - fprintf(logFile, "ORDER_CANCEL_DELETE"); - b->cancelDelete(); - } - } - break; - case ORDER_CONSTRUCTION: - { - if (!isPlayerAlive) - break; - boost::shared_ptr oc = boost::static_pointer_cast(order); - Uint16 gid = oc->gid; - - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Team *t=teams[team]; - Building *b=t->myBuildings[id]; - if (b) - { - fprintf(logFile, "ORDER_CONSTRUCTION"); - b->launchConstruction(oc->unitWorking, oc->unitWorkingFuture); - } - } - break; - case ORDER_CANCEL_CONSTRUCTION: - { - if (!isPlayerAlive) - break; - boost::shared_ptr oc = boost::static_pointer_cast(order); - Uint16 gid=oc->gid; - int team=Building::GIDtoTeam(gid); - int id=Building::GIDtoID(gid); - Team *t=teams[team]; - Building *b=t->myBuildings[id]; - if (b) - { - fprintf(logFile, "ORDER_CANCEL_CONSTRUCTION"); - b->cancelConstruction(oc->unitWorking); - } - } - break; - case ORDER_SET_ALLIANCE: - { - boost::shared_ptr sao=boost::static_pointer_cast(order); - Uint32 team=sao->teamNumber; - teams[team]->allies=sao->alliedMask; - teams[team]->enemies=sao->enemyMask; - teams[team]->sharedVisionExchange=sao->visionExchangeMask; - teams[team]->sharedVisionFood=sao->visionFoodMask; - teams[team]->sharedVisionOther=sao->visionOtherMask; - fprintf(logFile, "ORDER_SET_ALLIANCE"); - } - break; - case ORDER_PLAYER_QUIT_GAME: - { - boost::shared_ptr pqgo=boost::static_pointer_cast(order); - - bool found = false; - for(int i=0; iplayer && players[i]) - { - if(players[i]->teamNumber == players[pqgo->player]->teamNumber) - { - found = true; - } - } - } - if(! found) - { - teams[players[pqgo->player]->teamNumber]->isAlive = false; - } - - players[pqgo->player]->makeItAI(AI::NONE); - gameHeader.getBasePlayer(pqgo->player).makeItAI(AI::NONE); - fprintf(logFile, "ORDER_PLAYER_QUIT_GAME"); - } - break; - } -} - void Game::setAlliances(void) @@ -798,2366 +218,40 @@ void Game::setAlliances(void) } } -bool Game::load(GAGCore::InputStream *stream) +void Game::setWaitingOnMask(Uint32 mask) { - assert(stream); - - stream->readEnterSection("Game"); - - ///Clears any previous game - clearGame(); - mapHeader.reset(); - gameHeader.reset(); - - // We load the map header - MapHeader tempMapHeader; - if (verbose) - printf("Loading map header\n"); - if (!tempMapHeader.load(stream)) - { - fprintf(logFile, "Game::load::tempMapHeader.load\n"); - stream->readLeaveSection(); - return false; - } - mapHeader=tempMapHeader; - Sint32 versionMinor=mapHeader.getVersionMinor(); - - - // We load the game header - GameHeader tempGameHeader; - if (verbose) - printf("Loading game header\n"); - if (!tempGameHeader.load(stream, versionMinor)) - { - fprintf(logFile, "Game::load::tempMapHeader.load\n"); - stream->readLeaveSection(); - return false; - } - gameHeader=tempGameHeader; - - // Test the beginning signature. Signatures are basic corruption tests. - // Since Game loads many other structures, it has many of them. - char signature[4]; - stream->read(signature, 4, "signatureStart"); - if (memcmp(signature,"GaBe", 4)!=0) - { - fprintf(logFile, "Signature missmatch at Game::load begin\n"); - stream->readLeaveSection(); - return false; - } - - ///Load the step counter - stepCounter = stream->readUint32("stepCounter"); + Uint32 oldMask = maskAwayPlayer; + maskAwayPlayer = mask; - if(versionMinor < 64) + if(mask != 0) { - ///Load random seeds, these are no longer used - stream->readUint32("SyncRandSeedA"); - stream->readUint32("SyncRandSeedB"); - stream->readUint32("SyncRandSeedC"); - - stream->read(signature, 4, "signatureAfterSyncRand"); - if (memcmp(signature,"GaSy", 4)!=0) - { - fprintf(logFile, "Signature missmatch after Game::load sync rand\n"); - stream->readLeaveSection(); - return false; - } + if(oldMask == 0) + anyPlayerWaitedTimeFor = 0; + anyPlayerWaited = true; } else { - stream->read(signature, 4, "signatureBeforeTeams"); - if (memcmp(signature,"GaBt", 4)!=0) - { - fprintf(logFile, "Signature missmatch before Game::load teams \n"); - stream->readLeaveSection(); - return false; - } - } - - ///Load teams - stream->readEnterSection("teams"); - for (int i=0; ireadEnterSection(i); - teams[i]=new Team(stream, this, versionMinor); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->read(signature, 4, "signatureAfterTeams"); - if (memcmp(signature,"GaTe", 4)!=0) - { - fprintf(logFile, "Signature missmatch after Game::load teams\n"); - stream->readLeaveSection(); - return false; - } - - // Load the map. Team has to be saved and loaded first. - if(!map.load(stream, mapHeader, this)) - { - fprintf(logFile, "Signature missmatch in map\n"); - stream->readLeaveSection(); - return false; - } - - stream->read(signature, 4, "signatureAfterMap"); - if (memcmp(signature,"GaMa", 4)!=0) - { - fprintf(logFile, "Signature missmatch after map\n"); - stream->readLeaveSection(); - return false; - } - - // Load the players. Both Map and Team must be loaded first. - stream->readEnterSection("players"); - for (int i=0; ireadEnterSection(i); - players[i]=new Player(stream, teams, versionMinor); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->read(signature, 4, "signatureAfterPlayers"); - if (memcmp(signature,"GaPl", 4)!=0) - { - fprintf(logFile, "Signature missmatch after players\n"); - stream->readLeaveSection(); - return false; - } - - // We have to finish Team's loading - for (int i=0; iupdate(); - } - - // Check integrity of loaded game - if (!integrity()) - return false; - - // Now load the old map script - if (!sgslScript.load(stream, this)) - { - stream->readLeaveSection(); - return false; - } - - if(versionMinor >= 82) - { - // This is the new map script system - mapscript.decodeData(stream, mapHeader.getVersionMinor()); - } - - ///Load the campaign text for the game. - if(versionMinor < 75) - stream->readText("campaignText"); - - // default prestige calculation - prestigeToReach = std::max(MIN_MAX_PRESIGE, mapHeader.getNumberOfTeams()*TEAM_MAX_PRESTIGE); - - if(mapHeader.getVersionMinor() >= 75) - { - objectives.decodeData(stream, mapHeader.getVersionMinor()); - } - - if(mapHeader.getVersionMinor() >= 76) - { - missionBriefing = stream->readText("briefing"); - gameHints.decodeData(stream, mapHeader.getVersionMinor()); - } - - stream->readLeaveSection(); - - ///versions less than 63 did not have fertility computed with the map, but computed it live. - ///compute it now - if(mapHeader.getVersionMinor() < 63) - { - if(globalContainer->runNoX) - { - std::queue > incoming; - boost::recursive_mutex incomingMutex; - FertilityCalculatorThread calculator(map, incoming, incomingMutex); - calculator(); - } - else - { - FertilityCalculatorDialog dialog(globalContainer->gfx, map); - dialog.execute(); - } - } - - return true; -} - -bool Game::checkBuildingsDoNotOverlapAndHealMissing() { - std::vector buildings(map.getW()*map.getH(), NOGBID); - for (int ti=0; timyBuildings[bi]; - if (!building) - continue; - const auto x = building->posX; - const auto y = building->posY; - const auto type = building->type; - const auto w = type->width; - const auto h = type->height; - const auto gid = building->gid; - for (int yi=y; yiisVirtual) - continue; - // check for overlap - const auto index = map.coordToIndex(xi, yi); - checkInvariant(buildings[index]==NOGBID); - buildings[index] = gid; - // heal missing cells - if (map.getCase(xi, yi).building != gid) - { - std::cerr << "Missing map cell GBID at " << xi << "," << yi - << " for team " << ti - << " building " << bi - << " (" << building->type->type << "), healing!" - << std::endl; - map.getCase(xi, yi).building = gid; - } - } - } + anyPlayerWaited = false; } - return true; } -bool Game::integrity(void) -{ - ///Check teams integrity - for (int i=0; iintegrity()); - - ///Check that buildings do not overlap, as a pre-condition for healing - checkInvariant(checkBuildingsDoNotOverlapAndHealMissing()); - - ///Check that all ID do point to existing objects - for (int y=0; ymyBuildings[bid]; - checkInvariant(building); - #define healBuildingOutsideCoord(expr, coordL, coordH) \ - if (!(expr)) { \ - std::cerr << "Invalid coordinate " << #coordH << "=" << coordL \ - << " for team " << tid \ - << " building " << bid \ - << " (" << building->type->type << ")" \ - << " with " << #coordH \ - << " span [" << building->pos ## coordH << ":" << buildingEnd ## coordH << "[, healing!" \ - << std::endl; \ - map.getCase(x, y).building = NOGBID; \ - } - const auto buildingEndX = building->posX + building->type->width; - healBuildingOutsideCoord(x >= building->posX || x < (buildingEndX & map.wMask), x, X); - healBuildingOutsideCoord(x < buildingEndX, x, X); - const auto buildingEndY = building->posY + building->type->height; - healBuildingOutsideCoord(y >= building->posY || y < (buildingEndY & map.hMask), y, Y); - healBuildingOutsideCoord(y < buildingEndY, y, Y); - } - if (c.groundUnit != NOGUID) - { - int tid = Unit::GIDtoTeam(c.groundUnit); - checkInvariant(teams[tid]); - const auto unit = teams[tid]->myUnits[Unit::GIDtoID(c.groundUnit)]; - checkInvariant(unit); - // checkInvariantText(unit->posX == x, ", unit " << unit->typeNum << " at " << x << "," << y << " has instead posX=" << unit->posX); - // checkInvariantText(unit->posY == y, ", unit " << unit->typeNum << " at " << x << "," << y << " has instead posY=" << unit->posY); - } - if (c.airUnit != NOGUID) - { - int tid = Unit::GIDtoTeam(c.airUnit); - checkInvariant(teams[tid]); - const auto unit = teams[tid]->myUnits[Unit::GIDtoID(c.airUnit)]; - checkInvariant(unit); - checkInvariant(unit->posX == x); - checkInvariant(unit->posY == y); - } - } - return true; -} -void Game::save(GAGCore::OutputStream *stream, bool fileIsAMap, const std::string& name) +void Game::dumpAllData(const std::string& file) { - assert(stream); - stream->writeEnterSection("Game"); - if(dynamic_cast(stream)) - { - dynamic_cast(stream)->enableSHA1(); - } - - ///Save the two headers, record the position in the file because mapHeader will - ///will need to be overwritten with the mapOffset known - Uint32 mapHeaderOffset = stream->getPosition(); - mapHeader.setMapName(name); - mapHeader.setIsSavedGame(!fileIsAMap); - mapHeader.resetGameSHA1(); - - for (int i=0; iwrite("GaBe", 4, "signatureStart"); - stream->writeUint32(stepCounter, "stepCounter"); - stream->write("GaBt", 4, "signatureBeforeTeams"); - - ///Save teams - stream->writeEnterSection("teams"); - for (int i=0; iwriteEnterSection(i); - teams[i]->save(stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - stream->write("GaTe", 4, "signatureAfterTeams"); - - - ///Save the map offset to the header, before we save the map - ///Then, save the map - mapHeader.setMapOffset(stream->getPosition()); - map.save(stream); - stream->write("GaMa", 4, "signatureAfterMap"); - - ///Save the players - stream->writeEnterSection("players"); - for (int i=0; iwriteEnterSection(i); - players[i]->save(stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - stream->write("GaPl", 4, "signatureAfterPlayers"); - - // Save the old map script state - sgslScript.save(stream, this); - - // This is the new map script system - mapscript.encodeData(stream); - - ///Save game objectives - objectives.encodeData(stream); - stream->writeText(missionBriefing, "missionBriefing"); - gameHints.encodeData(stream); - - Uint8 sha1[20]; - for(int i=0; i<20; ++i) - sha1[i]=0; - if(dynamic_cast(stream)) - { - dynamic_cast(stream)->finishSHA1(sha1); - } - mapHeader.setGameSHA1(sha1); - - ///Overwrite the MapHeader. This is done after the map - ///offset has been set. - if (stream->canSeek()) + OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(file)); + if (stream->isEndOfStream()) { - Uint32 position = stream->getPosition(); - stream->seekFromStart(mapHeaderOffset); - mapHeader.save(stream); - stream->seekFromStart(position); + std::cerr << "Can't dump full game memory to file "<< file << std::endl; } - - stream->writeLeaveSection(); -} - -void Game::buildProjectSyncStep(Sint32 localTeam) -{ - for (std::list::iterator bpi=buildProjects.begin(); bpi!=buildProjects.end();) + else { - int posX=bpi->posX&map.getMaskW(); - int posY=bpi->posY&map.getMaskH(); - int teamNumber=bpi->teamNumber; - assert(teamNumber <= teamsCount()); - Sint32 typeNum=(bpi->typeNum); - BuildingType *bt=globalContainer->buildingsTypes.get(typeNum); - int w=bt->width; - int h=bt->height; - if (!map.isHardSpaceForBuilding(posX, posY, w, h)) - { - fprintf(logFile, "BuildProject failure (%d, %d)\n", posX, posY); - Uint32 notTeamMask=~Team::teamNumberToMask(teamNumber); - for (int y=posY; y::iterator to_erase=bpi; - bpi++; - buildProjects.erase(to_erase); - continue; - } - else if (checkRoomForBuilding(posX, posY, bt, teamNumber)) - { - Building *b=addBuilding(posX, posY, typeNum, teamNumber, bpi->unitWorking, bpi->unitWorkingFuture); - if (b) - { - Uint32 notTeamMask=~Team::teamNumberToMask(teamNumber); - for (int y=posY; yowner->addToStaticAbilitiesLists(b); - b->update(); - fprintf(logFile, "BuildProject success (%d, %d)\n", posX, posY); - std::list::iterator to_erase=bpi; - bpi++; - buildProjects.erase(to_erase); - continue; - } - } - bpi++; - } -} - -void Game::wonSyncStep(void) -{ - //TODO: sideeffects? - //std::list >& conditions = - gameHeader.getWinningConditions(); - - bool areAllDecided=true; - //We do this twice, because some win conditions depend on other win conditions - for(int i=0; icheckWinConditions(); - } - for(int i=0; icheckWinConditions(); - if(teams[i]->winCondition == WCUnknown) - areAllDecided=false; - } - isGameEnded = areAllDecided; - -} - -void Game::scriptSyncStep() -{ - // do a script step - sgslScript.syncStep(gui); - mapscript.syncStep(gui); -} - - - -void Game::prestigeSyncStep() -{ - totalPrestige=0; - totalPrestigeReached=false; - for (int i=0; iprestige; - } - if(totalPrestige >= prestigeToReach) - { - totalPrestigeReached=true; - } -} - - - -void Game::syncStep(Sint32 localTeam) -{ - if (!anyPlayerWaited) - { - if (globalContainer->replayWriter && globalContainer->replayWriter->isValid()) - { - globalContainer->replayWriter->advanceStep(); - } - - Uint64 startTick=SDL_GetTicks64(); - - for (int i=0; isyncStep(); - - map.syncStep(stepCounter); - - syncRand(); - - if ((stepCounter&31)==16) - { - map.switchFogOfWar(); - for (int t=0; tmyBuildings[i]; - if (b) - { - assert(b->owner==teams[t]); - assert(b->type); - } - if ((b)&&(!b->type->isBuildingSite || (b->type->level>0))&&(!b->type->isVirtual)) - { - b->setMapDiscovered(); - } - } - } - - if ((stepCounter&15)==1) - buildProjectSyncStep(localTeam); - - if ((stepCounter&31)==0) - { - prestigeSyncStep(); - scriptSyncStep(); - wonSyncStep(); - } - - Uint64 endTick=SDL_GetTicks64(); - ticksGameSum[stepCounter&31]+=static_cast(endTick) - static_cast(startTick); - stepCounter++; - anyPlayerWaitedTimeFor+=1; - } -} - -void Game::dirtyWarFlagGradient(void) -{ - for (int i=0; idirtyWarFlagGradient(); -} - -// Script interface - -int Game::isTeamAlive(int team) -{ - if ( - (team >= 0) && (team < mapHeader.getNumberOfTeams()) - ) - return teams[team]->isAlive; - else - return false; -} - -int Game::unitsCount(int team, int type) -{ - if ( - (team >= 0) && (team < mapHeader.getNumberOfTeams()) && - (type >= 0) && (type < NB_UNIT_TYPE) - ) - return teams[team]->stats.getLatestStat()->numberUnitPerType[type]; - else - return 0; -} - -int Game::unitsUpgradesCount(int team, int type, int ability, int level) -{ - if ( - (team >= 0) && (team < mapHeader.getNumberOfTeams()) && - (type >= 0) && (type < NB_UNIT_TYPE) && - (ability >= 0) && (ability < NB_ABILITY) && - (level >= 0) && (level < NB_UNIT_LEVELS) - ) - return teams[team]->stats.getLatestStat()->upgradeStatePerType[type][ability][level]; - else - return 0; -} - -int Game::buildingsCount(int team, int type, int level) -{ - if ( - (team >= 0) && (team < mapHeader.getNumberOfTeams()) && - (type >= 0) && (type < IntBuildingType::NB_BUILDING) && - (level >= 0) && (level < 6) - ) - return teams[team]->stats.getLatestStat()->numberBuildingPerTypePerLevel[type][level]; - else - return 0; -} - - -void Game::addTeam(int pos) -{ - assert(mapHeader.getNumberOfTeams()teamNumber=mapHeader.getNumberOfTeams(); - teams[pos]->race.load(); - teams[pos]->setCorrectMasks(); - - pos=mapHeader.getNumberOfTeams(); - pos+=1; - mapHeader.setNumberOfTeams(pos); - for (int i=0; isetCorrectColor( ((float)i*360.0f) /(float)pos ); - - prestigeToReach = std::max(MIN_MAX_PRESIGE, pos*TEAM_MAX_PRESTIGE); - - map.addTeam(); - - sgslScript.addTeam(); -} - -void Game::removeTeam(int pos) -{ - if(pos==-1) - { - pos=mapHeader.getNumberOfTeams(); - pos-=1; - mapHeader.setNumberOfTeams(pos); - } - if (mapHeader.getNumberOfTeams()>0) - { - Team *team=teams[pos]; - - team->clearMap(); - - delete team; - assert (mapHeader.getNumberOfTeams()!=0); - for (int i=0; isetCorrectColor(((float)i*360.0f)/(float)mapHeader.getNumberOfTeams()); - - map.removeTeam(); - sgslScript.removeTeam(pos); - teams[pos]=NULL; - } -} - -void Game::clearingUncontrolledTeams(void) -{ - for (int ti=0; tiplayersMask==0) - { - fprintf(logFile, "clearing team %d\n", ti); - team->clearMap(); - team->clearLists(); - team->clearMem(); - } - } -} - -void Game::regenerateDiscoveryMap(void) -{ - map.unsetMapDiscovered(); - for (int t=0; tmyUnits[i]; - if (u) - { - map.setMapDiscovered(u->posX-1, u->posY-1, 3, 3, teams[t]->sharedVisionOther); - } - } - for (int i=0; imyBuildings[i]; - if (b) - { - b->setMapDiscovered(); - } - } - } -} - -Unit *Game::addUnit(int x, int y, int team, Sint32 typeNum, int level, int delta, int dx, int dy) -{ - assert(teamrace.getUnitType(typeNum, level); - - x = (x + map.getW()) % map.getW(); - y = (y + map.getH()) % map.getH(); - - bool fly=ut->performance[FLY]; - bool free; - if (fly) - free=map.isFreeForAirUnit(x, y); - else - free=map.isFreeForGroundUnit(x, y, ut->performance[SWIM], Team::teamNumberToMask(team)); - if (!free) - return NULL; - - int id=-1; - for (int i=0; imyUnits[i]==NULL) - { - id=i; - break; - } - if (id==-1) - return NULL; - - //ok, now we can safely deposite an unit. - int gid=Unit::GIDfrom(id, team); - if (fly) - map.setAirUnit(x, y, gid); - else - map.setGroundUnit(x, y, gid); - - teams[team]->myUnits[id]= new Unit(x, y, gid, typeNum, teams[team], level); - teams[team]->myUnits[id]->dx=dx; - teams[team]->myUnits[id]->dy=dy; - teams[team]->myUnits[id]->directionFromDxDy(); - teams[team]->myUnits[id]->delta=delta; - teams[team]->myUnits[id]->selectPreferredMovement(); - return teams[team]->myUnits[id]; -} - -Building *Game::addBuilding(int x, int y, int typeNum, int teamNumber, Sint32 unitWorking, Sint32 unitWorkingFuture) -{ - Team *team=teams[teamNumber]; - assert(team); - - int id=-1; - for (int i=0; imyBuildings[i]==NULL) - { - id=i; - break; - } - if (id==-1) - { - //TODO:Building limit reached! - return NULL; - } - - //ok, now we can safely deposite an building. - int gid=Building::GIDfrom(id, teamNumber); - - int w=globalContainer->buildingsTypes.get(typeNum)->width; - int h=globalContainer->buildingsTypes.get(typeNum)->height; - - Building *b=new Building(x&map.getMaskW(), y&map.getMaskH(), gid, typeNum, team, &globalContainer->buildingsTypes, unitWorking, unitWorkingFuture); - - if (b->type->canExchange) - team->canExchange.push_front(b); - if (b->type->isVirtual) - team->virtualBuildings.push_front(b); - else - map.setBuilding(x, y, w, h, gid); - team->myBuildings[id]=b; - return b; -} - -bool Game::removeUnitAndBuildingAndFlags(int x, int y, unsigned flags) -{ - bool found=false; - if (flags & DEL_GROUND_UNIT) - { - Uint16 gauid=map.getAirUnit(x, y); - if (gauid!=NOGUID) - { - int id=Unit::GIDtoID(gauid); - int team=Unit::GIDtoTeam(gauid); - map.setAirUnit(x, y, NOGUID); - delete (teams[team]->myUnits[id]); - teams[team]->myUnits[id]=NULL; - found=true; - } - } - if (flags & DEL_AIR_UNIT) - { - Uint16 gguid=map.getGroundUnit(x, y); - if (gguid!=NOGUID) - { - int id=Unit::GIDtoID(gguid); - int team=Unit::GIDtoTeam(gguid); - map.setGroundUnit(x, y, NOGUID); - delete (teams[team]->myUnits[id]); - teams[team]->myUnits[id]=NULL; - found=true; - } - } - if (flags & DEL_BUILDING) - { - Uint16 gbid=map.getBuilding(x, y); - if (gbid!=NOGBID) - { - int id=Building::GIDtoID(gbid); - int team=Building::GIDtoTeam(gbid); - Building *b=teams[team]->myBuildings[id]; - if (!b->type->isVirtual) - map.setBuilding(b->posX, b->posY, b->type->width, b->type->height, NOGBID); - delete b; - teams[team]->myBuildings[id]=NULL; - found=true; - } - } - if (flags & DEL_FLAG) - { - for (int ti=0; ti::iterator bi=teams[ti]->virtualBuildings.begin(); bi!=teams[ti]->virtualBuildings.end(); ++bi) - if ((*bi)->posX==x && (*bi)->posY==y) - { - teams[ti]->myBuildings[Building::GIDtoID((*bi)->gid)]=NULL; - delete *bi; - teams[ti]->virtualBuildings.erase(bi); - found=true; - break; - } - } - return found; -} - -bool Game::removeUnitAndBuildingAndFlags(int x, int y, int size, unsigned flags) -{ - int sts = size>>1; - int stp = (~size)&1; - bool somethingInRect = false; - - for (int scx=(x-sts); scx<=(x+sts-stp); scx++) - for (int scy=(y-sts); scy<=(y+sts-stp); scy++) - if (removeUnitAndBuildingAndFlags((scx&(map.getMaskW())), (scy&(map.getMaskH())), flags)) - somethingInRect = true; - - return somethingInRect; -} - -bool Game::checkRoomForBuilding(int mousePosX, int mousePosY, const BuildingType *bt, int *buildingPosX, int *buildingPosY, int teamNumber, bool checkFow) -{ - int x=mousePosX+bt->decLeft; - int y=mousePosY+bt->decTop; - - *buildingPosX=x; - *buildingPosY=y; - - return checkRoomForBuilding(x, y, bt, teamNumber, checkFow); -} - -bool Game::checkRoomForBuilding(int x, int y, const BuildingType *bt, int teamNumber, bool checkFow) -{ - Team *team=teams[teamNumber]; - assert(team); - - int w=bt->width; - int h=bt->height; - - bool isRoom=true; - if (bt->isVirtual) - { - if (teamNumber<0) - return true; - - for (std::list::iterator vb=team->virtualBuildings.begin(); vb!=team->virtualBuildings.end(); ++vb) - { - Building *b=*vb; - if ((b->posX==(x&map.getMaskW())) && (b->posY==(y&map.getMaskH()))) - return false; - } - return true; - } - else - isRoom=map.isFreeForBuilding(x, y, w, h); - - if (!checkFow) - return isRoom; - - if (isRoom) - { - for (int dy=y; dyme)) - return true; - return false; - } - else - return false; -} - -bool Game::checkHardRoomForBuilding(int coordX, int coordY, const BuildingType *bt, int *mapX, int *mapY) -{ - int x=coordX+bt->decLeft; - int y=coordY+bt->decTop; - - *mapX=x; - *mapY=y; - - return checkHardRoomForBuilding(x, y, bt); -} - -bool Game::checkHardRoomForBuilding(int x, int y, const BuildingType *bt) -{ - int w=bt->width; - int h=bt->height; - assert(!bt->isVirtual); // This method is not for flags! - return map.isHardSpaceForBuilding(x, y, w, h); -} - - - -Unit* Game::getUnit(int guid) -{ - if(guid == NOGUID) - return NULL; - return teams[Unit::GIDtoTeam(guid)]->myUnits[Unit::GIDtoID(guid)]; -} - - - -void Game::drawPointBar(int x, int y, BarOrientation orientation, int maxLength, int actLength, int secondActLength, Uint8 r, Uint8 g, Uint8 b, Uint8 r2, Uint8 g2, Uint8 b2, int barWidth) -{ - assert(maxLength>=0); - assert(maxLength<65536); - assert(actLength<=maxLength); - - if ((orientation==LEFT_TO_RIGHT) || (orientation==RIGHT_TO_LEFT)) - { - /*globalContainer->gfx->drawHorzLine(x, y, maxLength*3+1, 32, 32, 32); - globalContainer->gfx->drawHorzLine(x, y+barWidth+1, maxLength*3+1, 32, 32, 32); - for (int i=0; igfx->drawVertLine(x+i*3, y+1, barWidth, 32, 32, 32); - */ - globalContainer->gfx->drawFilledRect(x, y, maxLength*3+1, barWidth+2, 0, 0, 0); - - if (orientation==LEFT_TO_RIGHT) - { - int i; - for (i=0; igfx->drawFilledRect(x+i*3+1, y+1, 2, barWidth, r, g, b); - for (; igfx->drawFilledRect(x+i*3+1, y+1, 2, barWidth, r2, g2, b2); - for (; igfx->drawRect(x+i*3, y, 4, barWidth+2, r/3, g/3, b/3); - } - else - { - int i; - for (i=0; igfx->drawRect(x+i*3, y, 4, barWidth+2, r/3, g/3, b/3); - for (; igfx->drawFilledRect(x+i*3+1, y+1, 2, barWidth, r2, g2, b2); - for (; igfx->drawFilledRect(x+i*3+1, y+1, 2, barWidth, r, g, b); - } - } - else if ((orientation==BOTTOM_TO_TOP) || (orientation==TOP_TO_BOTTOM)) - { - /*globalContainer->gfx->drawVertLine(x, y, maxLength*3+1, 32, 32, 32); - globalContainer->gfx->drawVertLine(x+barWidth+1, y, maxLength*3+1, 32, 32, 32); - for (int i=0; igfx->drawHorzLine(x+1, y+i*3, barWidth, 32, 32, 32); - */ - globalContainer->gfx->drawFilledRect(x, y, barWidth+2, maxLength*3+1, 0, 0, 0); - - if (orientation==TOP_TO_BOTTOM) - { - int i; - for (i=0; igfx->drawFilledRect(x+1, y+i*3+1, barWidth, 2, r, g, b); - for (; igfx->drawFilledRect(x+1, y+i*3+1, barWidth, 2, r2, g2, b2); - for (; igfx->drawRect(x, y+i*3, 4, barWidth+2, r/3, g/3, b/3); - } - else - { - int i; - for (i=0; igfx->drawRect(x, y+i*3, 4, barWidth+2, r/3, g/3, b/3); - for (; igfx->drawFilledRect(x+1, y+i*3+1, barWidth, 2, r2, g2, b2); - for (; igfx->drawFilledRect(x+1, y+i*3+1, barWidth, 2, r, g, b); - } - } - else - assert(false); -} - -void Game::drawUnit(int x, int y, Uint16 gid, int viewportX, int viewportY, int screenW, int screenH, int localTeam, Uint32 drawOptions) -{ - int id=Unit::GIDtoID(gid); - int team=Unit::GIDtoTeam(gid); - Unit *unit=teams[team]->myUnits[id]; - assert(unit); - if (!unit) - { - globalContainer->gfx->drawRect((x<<5)+1, (y<<5)+1, 30, 30, 255, 255, 0); - return; - } - int dx=unit->dx; - int dy=unit->dy; - - Uint32 visibleTeams = teams[localTeam]->me; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - if ((drawOptions & DRAW_WHOLE_MAP) == 0) - if ((!map.isFOWDiscovered(x+viewportX, y+viewportY, visibleTeams))&&(!map.isFOWDiscovered(x+viewportX-dx, y+viewportY-dy, visibleTeams))) - return; - - int imgid; - assert(unit->action>=0); - assert(unit->actionskin->startImage[unit->action]; - int px, py; - map.mapCaseToDisplayable(unit->posX, unit->posY, &px, &py, viewportX, viewportY); - int deltaLeft=255-unit->delta; - if (unit->actiondx*deltaLeft)>>3; - py-=(unit->dy*deltaLeft)>>3; - } - else - { - // TODO : if looks ugly, do something intelligent here - } - - int dir=unit->direction; - int delta=unit->delta; - assert(dir>=0); - assert(dir<9); - assert(delta>=0); - assert(delta<256); - if (dir==8) - { - imgid+=8*(delta>>5); - } - else - { - imgid+=8*dir; - imgid+=(delta>>5); - } - - // draw unit - Sprite *unitSprite = unit->skin->sprite; - unitSprite->setBaseColor(teams[team]->color); - int decX = (unitSprite->getW(imgid)-32)>>1; - int decY = (unitSprite->getH(imgid)-32)>>1; - globalContainer->gfx->drawSprite(px-decX, py-decY, unitSprite, imgid); - - // draw selection - if (unit==selectedUnit) - { - globalContainer->gfx->drawCircle(px+16, py+16, 16, 0, 0, 255); - if (unit->owner->teamNumber == localTeam) - globalContainer->gfx->drawCircle(px+16, py+16, 16, 0, 0, 190); - else if ((teams[localTeam]->allies) & (unit->owner->me)) - globalContainer->gfx->drawCircle(px+16, py+16, 16, 255, 196, 0); - else - globalContainer->gfx->drawCircle(px+16, py+16, 16, 190, 0, 0); - } - - // draw xp animation - if (unit->levelUpAnimation) - { - std::ostringstream oss; - oss << unit->experienceLevel; - globalContainer->standardFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 242, 131, 14)); - globalContainer->gfx->drawString(px + 16 - (globalContainer->standardFont->getStringWidth(oss.str().c_str()) >> 1), py - 16 - 2 *( LEVEL_UP_ANIMATION_FRAME_COUNT - unit->levelUpAnimation), globalContainer->standardFont, oss.str(), 0, (255*unit->levelUpAnimation) / LEVEL_UP_ANIMATION_FRAME_COUNT); - globalContainer->standardFont->popStyle(); - } - - // draw magic animation - if (unit->magicActionAnimation) - { - if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) - { - globalContainer->gfx->drawSprite(px+16-(globalContainer->magiceffect->getW(0)>>1), py+16-(globalContainer->magiceffect->getH(0)>>1), globalContainer->magiceffect, 0); - } - else - { - unsigned alpha = (unit->magicActionAnimation * 255) / MAGIC_ACTION_ANIMATION_FRAME_COUNT; - if (globalContainer->gfx->canDrawStretchedSprite()) - { - int stretchW = ((MAGIC_ACTION_ANIMATION_FRAME_COUNT - unit->magicActionAnimation) * globalContainer->magiceffect->getW(0)) / (MAGIC_ACTION_ANIMATION_FRAME_COUNT * 2); - int stretchH = ((MAGIC_ACTION_ANIMATION_FRAME_COUNT - unit->magicActionAnimation) * globalContainer->magiceffect->getH(0)) / (MAGIC_ACTION_ANIMATION_FRAME_COUNT * 2); - globalContainer->gfx->drawSprite(px+16-stretchW, py+16-stretchH, stretchW*2, stretchH*2, globalContainer->magiceffect, 0, alpha); - } - else - { - globalContainer->gfx->drawSprite(px+16-(globalContainer->magiceffect->getW(0)>>1), py+16-(globalContainer->magiceffect->getH(0)>>1), globalContainer->magiceffect, 0, alpha); - } - } - } - - if ((pxmouseX)&&(pymouseY)&&(((drawOptions & DRAW_WHOLE_MAP) != 0) ||(map.isFOWDiscovered(x+viewportX, y+viewportY, visibleTeams))||(Unit::GIDtoTeam(gid)==localTeam))) - mouseUnit=unit; - - if ((drawOptions & DRAW_HEALTH_FOOD_BAR) != 0 ) - { - drawPointBar(px+1, py+25, LEFT_TO_RIGHT, 10, (unit->hungry*10)/Unit::HUNGRY_MAX, 80, 179, 223); - - float hpRatio=(float)unit->hp/(float)unit->performance[HP]; - if (hpRatio>0.6) - drawPointBar(px+1, py+25+3, LEFT_TO_RIGHT, 10, 1+(int)(9*hpRatio), 78, 187, 78); - else if (hpRatio>0.3) - drawPointBar(px+1, py+25+3, LEFT_TO_RIGHT, 10, 1+(int)(9*hpRatio), 255, 255, 0); - else - drawPointBar(px+1, py+25+3, LEFT_TO_RIGHT, 10, 1+(int)(9*hpRatio), 255, 0, 0); - - if ((unit->performance[HARVEST]) && (unit->carriedRessource>=0)) - globalContainer->gfx->drawSprite(px+24, py, globalContainer->ressourceMini, unit->carriedRessource); - globalContainer->gfx->finishDrawingSprite(globalContainer->ressourceMini, 255); - } - - if (drawOptions & DRAW_ACCESSIBILITY) - { - std::ostringstream oss; - oss << unit->owner->teamNumber; - int accessW = globalContainer->littleFont->getStringWidth(oss.str().c_str()); - int accessH = globalContainer->littleFont->getStringHeight(oss.str().c_str()); - int accessX = px+((32-accessW)>>1); - int accessY = py+((32-accessH)>>1); - globalContainer->gfx->drawFilledRect(accessX-4, accessY, accessW+8, accessH, Color(0, 0, 0, 127)); - globalContainer->gfx->drawRect(accessX-4, accessY, accessW+8, accessH, Color(255, 255, 255, 127)); - globalContainer->gfx->drawString(accessX, accessY, globalContainer->littleFont, oss.str()); - } - if(highlightUnitType & (1<typeNum)) - { - globalContainer->gfx->drawSprite(px, py-decY-32, globalContainer->gamegui, 36); - } -} - -struct BuildingPosComp -{ - bool operator () (Building * const & a, Building * const & b) - { - if (a->posY != b->posY) - return a->posY < b->posY; - else - return a->posX < b->posX; - } -}; - -inline void Game::drawMapWater(int sw, int sh, int viewportX, int viewportY, int time) -{ - int waterStartX = -(((viewportX<<5)+time/2) % 512); - int waterStartY = -((viewportY<<5) % 512); - for (int y=waterStartY; ygfx->drawSprite(x, y, globalContainer->terrainWater, 0); - globalContainer->gfx->finishDrawingSprite(globalContainer->terrainWater, 255); -} - -inline void Game::drawMapTerrain(int left, int top, int right, int bot, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - Uint32 visibleTeams = teams[localTeam]->me; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - // we draw the terrains, eventually with debug rects: - for (int y=top; y<=bot; y++) - for (int x=left; x<=right; x++) - if ( - map.isMapPartiallyDiscovered( - x+viewportX-1, - y+viewportY-1, - x+viewportX+1, - y+viewportY+1, - visibleTeams) || - ((drawOptions & DRAW_WHOLE_MAP) != 0)) - { - // draw terrain - int id=map.getTerrain(x+viewportX, y+viewportY); - Sprite *sprite; - if (id<272) - { - sprite=globalContainer->terrain; - } - else - { - assert(false); // Now there shouldn't be any more ressources on "terrain". - sprite=globalContainer->ressources; - id-=272; - } - if ((id < 256) || (id >= 256+16)) - globalContainer->gfx->drawSprite(x<<5, y<<5, sprite, id); - } - globalContainer->gfx->finishDrawingSprite(globalContainer->terrain, 255); -} - -inline void Game::drawMapRessources(int left, int top, int right, int bot, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - Uint32 visibleTeams = teams[localTeam]->me; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - for (int y=top; y<=bot; y++) - for (int x=left; x<=right; x++) - if ( - map.isMapPartiallyDiscovered( - x+viewportX-1, - y+viewportY-1, - x+viewportX+1, - y+viewportY+1, - visibleTeams) || - ((drawOptions & DRAW_WHOLE_MAP) != 0)) - { - const auto& r = map.getRessource(x+viewportX, y+viewportY); - if (r.type!=NO_RES_TYPE) - { - Sprite *sprite=globalContainer->ressources; - int type=r.type; - int amount=r.amount; - int variety=r.variety; - const RessourceType *rt=globalContainer->ressourcesTypes.get(type); - int imgid=rt->gfxId+(variety*rt->sizesCount)+amount; - if (!rt->eternal) - imgid--; - int dx=(sprite->getW(imgid)-32)>>1; - int dy=(sprite->getH(imgid)-32)>>1; - assert(type>=0); - assert(type<(int)globalContainer->ressourcesTypes.size()); - assert(amount>=0); - assert(amount<=rt->sizesCount); - assert(variety>=0); - assert(varietyvarietiesCount); - globalContainer->gfx->drawSprite((x<<5)-dx, (y<<5)-dy, sprite, imgid); - } - } - globalContainer->gfx->finishDrawingSprite(globalContainer->ressources, 255); -} - -inline void Game::drawMapGroundUnits(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - //Reset the mouse unit to NULL, as this time arround there may not be a unit - //under the mouse pointer - mouseUnit=NULL; - for (int y=top-1; y<=bot; y++) - for (int x=left-1; x<=right; x++) - { - Uint16 gid=map.getGroundUnit(x+viewportX, y+viewportY); - if (gid!=NOGUID) - drawUnit(x, y, gid, viewportX, viewportY, (sw>>5), (sh>>5), localTeam, drawOptions); - } -} - -inline void Game::drawMapDebugAreas(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - if (false) - for (int y=top-1; y<=bot; y++) - for (int x=left-1; x<=right; x++) - { - //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, ((AICastor *)players[1]->ai->aiImplementation)->wheatCareMap[0][(x+viewportX)+(y+viewportY)*map.w]); - //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, ((AICastor *)players[1]->ai->aiImplementation)->notGrassMap[(x+viewportX)+(y+viewportY)*map.w]); -// globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, map.guardAreasGradient[0][1][(x+viewportX)+(y+viewportY)*map.w]); -// globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, ((Nicowar::AINicowar*)players[3]->ai->aiImplementation)->getGradientManager().getGradient(Nicowar::Gradient::VillageCenter, Nicowar::Gradient::Resource).getHeight(x+viewportX, y+viewportY)); - //((AICastor *)players[0].ai->aiImplementation)->wheatCareMap - } - //if (map.getForbidden(x+viewportX, y+viewportY)) - //{ - //if (!map.isFreeForGroundUnit(x+viewportX, y+viewportY, 1, 1)) - // globalContainer->gfx->drawRect(x<<5, y<<5, 32, 32, 255, 16, 32); - //globalContainer->gfx->drawRect(2+(x<<5), 2+(y<<5), 28, 28, 255, 16, 32); - //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, map.getGradient(1, 5, 0, x+viewportX, y+viewportY)); - //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, map.getGradient(0, STONE, 1, x+viewportX, y+viewportY)); - //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, map.forbiddenGradient[0][0][(x+viewportX)+(y+viewportY)*map.w]); - //globalContainer->gfx->drawString((x<<5), (y<<5)+16, globalContainer->littleFont, ((x+viewportX)&(map.getMaskW()))); - //globalContainer->gfx->drawString((x<<5)+16, (y<<5)+8, globalContainer->littleFont, ((y+viewportY)&(map.getMaskH()))); - //} - - // We draw debug area: - if (false) - { - assert(teams[0]); - Building *b=selectedBuilding; - if (b) - for (int y=top-1; y<=bot; y++) - for (int x=left-1; x<=right; x++) - { - //if (map.warpDistMax(b->posX, b->posY, x+viewportX, y+viewportY)<16) - { - //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, "%d", map.getGradient(0, 6, 1, x+viewportX, y+viewportY)); - //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, "%d", map.warpDistMax(b->posX, b->posY, x+viewportX, y+viewportY)); - //int lx=(x+viewportX-b->posX+15+32)&31; - //int ly=(y+viewportY-b->posY+15+32)&31; - //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, b->localGradient[1][lx+ly*32]); - if(b->globalGradient[1]) - globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, b->globalGradient[1][(x+viewportX) + (y+viewportY)*map.w]); - //globalContainer->gfx->drawString((x<<5), (y<<5)+10, globalContainer->littleFont, lx); - //globalContainer->gfx->drawString((x<<5)+16, (y<<5)+10, globalContainer->littleFont, ly); - //globalContainer->gfx->drawString((x<<5), (y<<5)+16, globalContainer->littleFont, "%d", x+viewportX); - //globalContainer->gfx->drawString((x<<5)+16, (y<<5)+16, globalContainer->littleFont, "%d", y+viewportY); - //globalContainer->gfx->drawString((x<<5), (y<<5)+16, globalContainer->littleFont, "%d", x+viewportX-b->posX+16); - //globalContainer->gfx->drawString((x<<5)+16, (y<<5)+16, globalContainer->littleFont, "%d", y+viewportY-b->posY+16); - } - } - } - - // We draw debug area: - if (false) - if (selectedUnit && selectedUnit->verbose) - { - //assert(teams[0]); - Building *b=selectedUnit->attachedBuilding; - //b=teams[0]->myBuildings[21]; - //if (teams[0]->virtualBuildings.size()) - // b=*teams[0]->virtualBuildings.begin(); - if (b && b->localRessources[1]) - for (int y=top-1; y<=bot; y++) - for (int x=left-1; x<=right; x++) - if (map.warpDistMax(b->posX, b->posY, x+viewportX, y+viewportY)<16) - { - int lx=(x+viewportX-b->posX+15)&31; - int ly=(y+viewportY-b->posY+15)&31; - globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, b->localRessources[1][lx+ly*32]); - } - } - - // We draw debug area: - //if (selectedUnit && selectedUnit->verbose) - if (selectedBuilding && selectedBuilding->verbose) - { - //Building *b=NULL; - Building *b=selectedBuilding; - //Building *b=selectedUnit->attachedBuilding; - - //assert(teams[0]); - //Building *b=teams[0]->myBuildings[0]; - //if (teams[0]->virtualBuildings.size()) - // b=*teams[0]->virtualBuildings.begin(); - - int w=map.getW(); - if (b) - for (int y=top-1; y<=bot; y++) - for (int x=left-1; x<=right; x++) - { - if (b->verbose==1 || b->verbose==2) - { - if (b->globalGradient[b->verbose&1]) - globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, - b->globalGradient[b->verbose&1][((x+viewportX)&(map.getMaskW()))+((y+viewportY)&(map.getMaskH()))*w]); - } - else if ((b->verbose==3 || b->verbose==4) && map.isInLocalGradient(x+viewportX, y+viewportY, b->posX, b->posY)) - { - int lx=(x+viewportX-b->posX+15)&31; - int ly=(y+viewportY-b->posY+15)&31; - if (!b->dirtyLocalGradient[b->verbose&1]) - globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, b->localGradient[b->verbose&1][lx+ly*32]); - } - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 192, 192, 192)); - globalContainer->gfx->drawString((x<<5), (y<<5)+16, globalContainer->littleFont, (x+viewportX+map.getW())&(map.getMaskW())); - globalContainer->gfx->drawString((x<<5)+16, (y<<5)+8, globalContainer->littleFont, (y+viewportY+map.getH())&(map.getMaskH())); - globalContainer->littleFont->popStyle(); - } - - } -} - -inline void Game::drawMapBuilding(int x, int y, int gid, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - Building *building = teams[Building::GIDtoTeam(gid)]->myBuildings[Building::GIDtoID(gid)]; - assert(building); - BuildingType *type=building->type; - Team *team=building->owner; - - int imgid; - if (type->crossConnectMultiImage) - { - int add = 0; - Uint16 b; - // Up - b = map.getBuilding(building->posXLocal, building->posYLocal-1); - if ((b != NOGBID) && - (Building::GIDtoTeam(b) == team->teamNumber) && (teams[Building::GIDtoTeam(b)]->myBuildings[Building::GIDtoID(b)]->type == type)) - add |= (1<<3); - // Bottom - b = map.getBuilding(building->posXLocal, building->posYLocal+building->type->height); - if ((b != NOGBID) && - (Building::GIDtoTeam(b) == team->teamNumber) && (teams[Building::GIDtoTeam(b)]->myBuildings[Building::GIDtoID(b)]->type == type)) - add |= (1<<2); - // Left - b = map.getBuilding(building->posXLocal-1, building->posYLocal); - if ((b != NOGBID) && - (Building::GIDtoTeam(b) == team->teamNumber) && (teams[Building::GIDtoTeam(b)]->myBuildings[Building::GIDtoID(b)]->type == type)) - add |= (1<<1); - // Right - b = map.getBuilding(building->posXLocal+building->type->width, building->posYLocal); - if ((b != NOGBID) && - (Building::GIDtoTeam(b) == team->teamNumber) && (teams[Building::GIDtoTeam(b)]->myBuildings[Building::GIDtoID(b)]->type == type)) - add |= (1<<0); - imgid = type->gameSpriteImage + add; - } - else - { - // FIXME : why building->hp is > type->hpMax ? - int hp = std::min(building->hp, type->hpMax); - int damageImgShift = type->gameSpriteCount - ((hp * type->gameSpriteCount) / (type->hpMax+1)) - 1; - assert(damageImgShift >= 0); - imgid = type->gameSpriteImage + damageImgShift; - } -// int x, y; - int dx, dy; - -// map.mapCaseToDisplayable(building->posXLocal, building->posYLocal, &x, &y, viewportX, viewportY); - - // select buildings and set the team colors - Sprite *buildingSprite = type->gameSpritePtr; - dx = (type->width<<5)-buildingSprite->getW(imgid); - dy = (type->height<<5)-buildingSprite->getH(imgid); - buildingSprite->setBaseColor(team->color); - - // draw building - globalContainer->gfx->drawSprite(x+dx, y+dy, buildingSprite, imgid); - globalContainer->gfx->finishDrawingSprite(buildingSprite, 255); - - if ((drawOptions & DRAW_BUILDING_RECT) != 0) - { - int batW=(type->width )<<5; - int batH=(type->height)<<5; - int typeNum=building->typeNum; - globalContainer->gfx->drawRect(x, y, batW, batH, 255, 255, 255, 127); - - BuildingType *lastbt=globalContainer->buildingsTypes.get(typeNum); - int lastTypeNum=typeNum; - int max=0; - while(lastbt->nextLevel>=0) - { - lastTypeNum=lastbt->nextLevel; - lastbt=globalContainer->buildingsTypes.get(lastTypeNum); - if (max++>200) - { - printf("GameGUI: Error: nextLevelTypeNum architecture is broken.\n"); - assert(false); - break; - } - } - int exBatX=x+((lastbt->decLeft-type->decLeft)<<5); - int exBatY=y+((lastbt->decTop-type->decTop)<<5); - int exBatW=(lastbt->width)<<5; - int exBatH=(lastbt->height)<<5; - - globalContainer->gfx->drawRect(exBatX, exBatY, exBatW, exBatH, 255, 255, 255, 127); - } - - Uint32 visibleTeams = teams[localTeam]->me; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - if (((drawOptions & DRAW_HEALTH_FOOD_BAR) != 0) && (building->owner->sharedVisionOther & visibleTeams)) - { - //int unitDecx=(building->type->width*16)-((3*building->maxUnitInside)>>1); - // TODO : find better color for this - // health - if (type->hpMax) - { - int maxWidth, actWidth, addDec; - float hpRatio=(float)building->hp/(float)type->hpMax; - if (type->width==1) - { - maxWidth=8; - actWidth=1+(int)(7.0f*hpRatio); - addDec=2; - } - else - { - maxWidth=16; - actWidth=1+(int)(15.0f*hpRatio); - addDec=7; - } - int decy=(type->height*32); - int healDecx=(type->width-(maxWidth>>3))*16+addDec; - - if (building->hp!=type->hpMax || !building->type->crossConnectMultiImage) - { - if (hpRatio>0.6) - drawPointBar(x+healDecx, y+decy-4, LEFT_TO_RIGHT, maxWidth, actWidth, 78, 187, 78); - else if (hpRatio>0.3) - drawPointBar(x+healDecx, y+decy-4, LEFT_TO_RIGHT, maxWidth, actWidth, 255, 255, 0); - else - drawPointBar(x+healDecx, y+decy-4, LEFT_TO_RIGHT, maxWidth, actWidth, 255, 0, 0); - } - } - - // units - if (building->maxUnitInside>0) - drawPointBar(x+type->width*32-4, y+1, BOTTOM_TO_TOP, building->maxUnitInside, (signed)building->unitsInside.size(), 255, 255, 255); - if (building->maxUnitWorking>0) - drawPointBar(x+type->width*16-((3*building->maxUnitWorking)>>1), y+1,LEFT_TO_RIGHT , building->maxUnitWorking, (signed)building->unitsWorking.size(), 0, 255, 255, 255, 255, 64, 0); - - // food (for inns) - if ((type->canFeedUnit) || (type->unitProductionTime)) - { - // compute bar size, prevent oversize - int bDiv=1; - assert(type->height!=0); - while ( ((type->maxRessource[CORN]*3+1)/bDiv)>((type->height*32)-10)) - bDiv++; - drawPointBar(x+1, y+1, BOTTOM_TO_TOP, type->maxRessource[CORN]/bDiv, building->ressources[CORN]/bDiv, 255, 255, 120, 1+bDiv); - } - - // bullets (for defence towers) - if (type->maxBullets) - { - // compute bar size, prevent oversize - int bDiv=1; - assert(type->height!=0); - while ( ((type->maxBullets*3+1)/bDiv)>((type->height*32)-10)) - bDiv++; - drawPointBar(x+1, y+1, BOTTOM_TO_TOP, type->maxBullets/bDiv, building->bullets/bDiv, 200, 200, 200, 1+bDiv); - } - } - - if (drawOptions & DRAW_ACCESSIBILITY) - { - std::ostringstream oss; - oss << building->owner->teamNumber; - int accessW = globalContainer->littleFont->getStringWidth(oss.str().c_str()); - int accessH = globalContainer->littleFont->getStringHeight(oss.str().c_str()); - int accessX = x+(((type->width<<5)-accessW)>>1); - int accessY = y+(((type->height<<5)-accessH)>>1); - globalContainer->gfx->drawFilledRect(accessX-4, accessY, accessW+8, accessH, Color(0, 0, 0, 127)); - globalContainer->gfx->drawRect(accessX-4, accessY, accessW+8, accessH, Color(255, 255, 255, 127)); - globalContainer->gfx->drawString(accessX, accessY, globalContainer->littleFont, oss.str()); - } - - if(highlightBuildingType & (1<shortTypeNum)) - { - globalContainer->gfx->drawSprite(x + buildingSprite->getW(imgid)/2 - 16, y-36, globalContainer->gamegui, 36); - } -} - - -inline void Game::drawMapGroundBuildings(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, std::set *visibleBuildings) -{ - Uint32 visibleTeams = teams[localTeam]->me; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - std::set drawnBuildings; - for (int y=top-1; y<=bot; y++) - for (int x=left-1; x<=right; x++) - { - Uint16 gid=map.getBuilding(x+viewportX, y+viewportY); - if (gid!=NOGBID) // Then this is a building - { - //globalContainer->gfx->drawRect(x<<5, y<<5, 32, 32, 255, 128, 0); - //globalContainer->gfx->drawRect(2+(x<<5), 2+(y<<5), 28, 28, 255, 128, 0); - - int id = Building::GIDtoID(gid); - int team = Building::GIDtoTeam(gid); - - Building *building=teams[team]->myBuildings[id]; - if(drawnBuildings.find(building)==drawnBuildings.end()) - { - assert(building); // if this fails, and unwanted garbage-UID is on the ground. - if (((drawOptions & DRAW_WHOLE_MAP) != 0) - || Building::GIDtoTeam(gid)==localTeam - || (building->seenByMask & visibleTeams) - || map.isFOWDiscovered(x+viewportX, y+viewportY, visibleTeams)) - { - int px,py; - map.mapCaseToDisplayable(building->posXLocal, building->posYLocal, &px, &py, viewportX, viewportY); - drawMapBuilding(px, py, gid, viewportX, viewportY, localTeam, drawOptions); - drawnBuildings.insert(building); - } - } - } - } - if(visibleBuildings) - *visibleBuildings = drawnBuildings; -} -/** - * Draws the visible (viewport) part of the given map - */ -inline void Game::drawMapAreas(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - static int areaAnimationTick = 0; - - if ((drawOptions & DRAW_AREA) != 0 && (!globalContainer->replaying || globalContainer->replayShowAreas)) - { - drawMapArea(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, &map, &Map::isForbiddenLocal, areaAnimationTick, ForbiddenArea); - drawMapArea(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, &map, &Map::isGuardAreaLocal, areaAnimationTick, GuardArea); - drawMapArea(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, &map, &Map::isClearAreaLocal, areaAnimationTick, ClearingArea); - for (int y=top; ygfx->drawLine((x<<5), 8+(y<<5), 32+(x<<5), 8+(y<<5), 128, 64, 0); - globalContainer->gfx->drawLine((x<<5), 16+(y<<5), 32+(x<<5), 16+(y<<5), 128, 64, 0); - globalContainer->gfx->drawLine((x<<5), 24+(y<<5), 32+(x<<5), 24+(y<<5), 128, 64, 0); -// globalContainer->gfx->drawLine((x<<5), 32+(y<<5), 32+(x<<5), 32+(y<<5), 128, 64, 0); - - if (map.canRessourcesGrow(x+viewportX, y+viewportY-1)) - globalContainer->gfx->drawHorzLine((x<<5), (y<<5), 32, 255, 128, 0); - if (map.canRessourcesGrow(x+viewportX, y+viewportY+1)) - globalContainer->gfx->drawHorzLine((x<<5), 32+(y<<5), 32, 255, 128, 0); - - if (map.canRessourcesGrow(x+viewportX-1, y+viewportY)) - globalContainer->gfx->drawVertLine((x<<5), (y<<5), 32, 255, 128, 0); - if (map.canRessourcesGrow(x+viewportX+1, y+viewportY)) - globalContainer->gfx->drawVertLine(32+(x<<5), (y<<5), 32, 255, 128, 0); - } - } - } - areaAnimationTick++; - } -} -/** - * Draws the visible (viewport) part of the given map - */ -inline void Game::drawMapArea(int left, int top, int right, int bot, int sw, - int sh, int viewportX, int viewportY, int localTeam, - Uint32 drawOptions, Map * map, bool (Map::*mapIs)(int, int) const, int areaAnimationTick, - AreaType areaType) -{ - Sprite* sprite; - GAGCore::Color c; - switch (areaType) - { - case ClearingArea: sprite = globalContainer->areaClearing; c = GAGCore::Color(255,255,0); break; - case ForbiddenArea: sprite = globalContainer->areaForbidden; c = GAGCore::Color(255,0,0); break; - case GuardArea: sprite = globalContainer->areaGuard; c = GAGCore::Color(0,0,255); break; - default: assert(false); - } - for (int y=top; y*mapIs)(x+viewportX, y+viewportY)) - { - int randId = (x+viewportX) * 7919 + (y+viewportY) * 17; - int frame = ((randId + areaAnimationTick) % (sprite->getFrameCount() * 2)) / 2; - globalContainer->gfx->drawSprite((x<<5), (y<<5), sprite, frame); - - if (!(map->*mapIs)(x+viewportX, y+viewportY-1)) - globalContainer->gfx->drawHorzLine((x<<5), (y<<5), 32, c); - if (!(map->*mapIs)(x+viewportX, y+viewportY+1)) - globalContainer->gfx->drawHorzLine((x<<5), 32+(y<<5), 32, c); - - if (!(map->*mapIs)(x+viewportX-1, y+viewportY)) - globalContainer->gfx->drawVertLine((x<<5), (y<<5), 32, c); - if (!(map->*mapIs)(x+viewportX+1, y+viewportY)) - globalContainer->gfx->drawVertLine(32+(x<<5), (y<<5), 32, c); - } - } - } - globalContainer->gfx->finishDrawingSprite(sprite, 255); -} - -inline void Game::drawMapAirUnits(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - for (int y=top-1; y<=bot; y++) - for (int x=left-1; x<=right; x++) - { - Uint16 gid=map.getAirUnit(x+viewportX, y+viewportY); - if (gid!=NOGUID) - drawUnit(x, y, gid, viewportX, viewportY, (sw>>5), (sh>>5), localTeam, drawOptions); - } -} - -inline void Game::drawMapScriptAreas(int left, int top, int right, int bot, int viewportX, int viewportY) -{ - for (int y=top; ygfx->drawString((x<<5)+(n%3)*10, (y<<5)+(n/3)*10, globalContainer->littleFont, str.str()); - - globalContainer->gfx->drawHorzLine((x<<5), (y<<5), 32, 64, 255, 255); - globalContainer->gfx->drawHorzLine((x<<5), 32+(y<<5), 32, 64, 255, 255); - - globalContainer->gfx->drawVertLine((x<<5), (y<<5), 32, 64, 255, 255); - globalContainer->gfx->drawVertLine(32+(x<<5), (y<<5), 32, 64, 255, 255); - } - } - } -} - -inline void Game::drawMapBulletsExplosionsDeathAnimations(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - // Let's paint the bullets and explosions - // TODO : optimise : test only possible sectors to show bullets. - - Sprite *bulletSprite = globalContainer->bullet; - // FIXME : have team in bullets to have the correct color - - Uint32 visibleTeams = teams[localTeam]->me; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - int mapPixW=(map.getW())<<5; - int mapPixH=(map.getH())<<5; - - for (int i=0; i<(map.getSectorW()*map.getSectorH()); i++) - { - Sector *s=map.getSector(i); - // bullets - for (std::list::iterator it=s->bullets.begin();it!=s->bullets.end();it++) - { - int x=(*it)->px-(viewportX<<5); - int y=(*it)->py-(viewportY<<5); - int balisticShift = 0; - - if (x<0) - x+=mapPixW; - if (y<0) - y+=mapPixH; - if ((*it)->ticksInitial) - { - float x = static_cast((*it)->ticksLeft); - float T = static_cast((*it)->ticksInitial); - float speedX = static_cast((*it)->speedX); - float speedY = static_cast((*it)->speedX); - float K = static_cast(sqrt(speedX * speedX + speedY * speedY)); - balisticShift = static_cast(K * ((-1.0f * x * x) / T + x)); - } - - //printf("px=(%d, %d) vp=(%d, %d)\n", (*it)->px, (*it)->py, viewportX, viewportY); - if ( (x<=sw) && (y<=sh) ) - { - globalContainer->gfx->drawSprite(x, y-balisticShift, bulletSprite, BULLET_IMGID); - globalContainer->gfx->drawSprite(x+(balisticShift>>1), y, bulletSprite, BULLET_IMGID+1); - } - } - globalContainer->gfx->finishDrawingSprite(bulletSprite, 255); - // explosions - for (std::list::iterator it=s->explosions.begin();it!=s->explosions.end();it++) - { - if (map.isFOWDiscovered((*it)->x, (*it)->y, visibleTeams)) - { - int x, y; - map.mapCaseToDisplayable((*it)->x, (*it)->y, &x, &y, viewportX, viewportY); - int frame = globalContainer->bulletExplosion->getFrameCount() - (*it)->ticksLeft - 1; - int decX = globalContainer->bulletExplosion->getW(frame)>>1; - int decY = globalContainer->bulletExplosion->getH(frame)>>1; - globalContainer->gfx->drawSprite(x+16-decX, y+16-decY, globalContainer->bulletExplosion, frame); - } - } - globalContainer->gfx->finishDrawingSprite(globalContainer->bulletExplosion, 255); - // death animations - for (std::list::iterator it=s->deathAnimations.begin();it!=s->deathAnimations.end();++it) - { - if (map.isFOWDiscovered((*it)->x, (*it)->y, visibleTeams)) - { - int x, y; - map.mapCaseToDisplayable((*it)->x, (*it)->y, &x, &y, viewportX, viewportY); - int frame = globalContainer->deathAnimation->getFrameCount() - (*it)->ticksLeft - 1; - int decX = globalContainer->deathAnimation->getW(frame)>>1; - int decY = globalContainer->deathAnimation->getH(frame)>>1; - Team *team = (*it)->team; - - globalContainer->deathAnimation->setBaseColor(team->color); - globalContainer->gfx->drawSprite(x+16-decX, y+16-decY-frame, globalContainer->deathAnimation, frame); - } - } - globalContainer->gfx->finishDrawingSprite(globalContainer->deathAnimation, 255); - } -} - -inline void Game::drawMapFogOfWar(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - if ((drawOptions & DRAW_WHOLE_MAP) == 0) - { - // we have decrease on because we do unalign lookup - for (int y=top-1; y<=bot; y++) - for (int x=left-1; x<=right; x++) - { - unsigned i0, i1, i2, i3; - - /*if ( (!map.isMapDiscovered(x+viewportX, y+viewportY, teams[localTeam]->me))) - { - globalContainer->gfx->drawFilledRect(x<<5, y<<5, 32, 32, 10, 10, 10); - } - else if ( (!map.isFOW(x+viewportX, y+viewportY, teams[localTeam]->me))) - { - globalContainer->gfx->drawSprite(x<<5, y<<5, globalContainer->terrainShader, 0); - }*/ - - Uint32 visibleTeams = teams[localTeam]->me; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - // first draw black - i0=!map.isMapDiscovered(x+viewportX+1, y+viewportY+1, visibleTeams) ? 1 : 0; - i1=!map.isMapDiscovered(x+viewportX, y+viewportY+1, visibleTeams) ? 1 : 0; - i2=!map.isMapDiscovered(x+viewportX+1, y+viewportY, visibleTeams) ? 1 : 0; - i3=!map.isMapDiscovered(x+viewportX, y+viewportY, visibleTeams) ? 1 : 0; - unsigned blackValue = i0 + (i1<<1) + (i2<<2) + (i3<<3); - if (blackValue==15) - globalContainer->gfx->drawFilledRect((x<<5)+16, (y<<5)+16, 32, 32, 0, 0, 0); - else if (blackValue) - globalContainer->gfx->drawSprite((x<<5)+16, (y<<5)+16, globalContainer->terrainBlack, blackValue); - - // then if it isn't full black, draw shade - if (blackValue!=15) - { - i0=!map.isFOWDiscovered(x+viewportX+1, y+viewportY+1, visibleTeams) ? 1 : 0; - i1=!map.isFOWDiscovered(x+viewportX, y+viewportY+1, visibleTeams) ? 1 : 0; - i2=!map.isFOWDiscovered(x+viewportX+1, y+viewportY, visibleTeams) ? 1 : 0; - i3=!map.isFOWDiscovered(x+viewportX, y+viewportY, visibleTeams) ? 1 : 0; - unsigned shadeValue = i0 + (i1<<1) + (i2<<2) + (i3<<3); - - if (shadeValue==15) - globalContainer->gfx->drawFilledRect((x<<5)+16, (y<<5)+16, 32, 32, 0, 0, 0, 127); - else if (shadeValue) - globalContainer->gfx->drawSprite((x<<5)+16, (y<<5)+16, globalContainer->terrainShader, shadeValue); - } - } - globalContainer->gfx->finishDrawingSprite(globalContainer->terrainBlack, 255); - globalContainer->gfx->finishDrawingSprite(globalContainer->terrainShader, 255); - } -} - -inline void Game::drawMapOverlayMaps(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - if(drawOptions & DRAW_OVERLAY) - { - OverlayArea* overlays; - if(gui) - overlays=&gui->overlay; - else if(edit) - overlays=&edit->overlay; - else assert(false); - int overlayMax=overlays->getMaximum(); - Color overlayColor; - if(overlays->getOverlayType() == OverlayArea::Starving) - overlayColor=Color(192, 0, 0); - if(overlays->getOverlayType() == OverlayArea::Damage) - overlayColor=Color(192, 0, 0); - if(overlays->getOverlayType() == OverlayArea::Defence) - overlayColor=Color(0, 0, 192); - if(overlays->getOverlayType() == OverlayArea::Fertility) - overlayColor=Color(0, 192, 128); - ///Both width and height have +2 to cover half-squares arround the edge of the viewport - int width = (right - left) + 2; - int height = (bot - top) + 2; - - overlayAlphas.resize(width * height); - for (int y=0; yme; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - int rx=(x+viewportX-1+map.getW())%map.getW(); - int ry=(y+viewportY-1+map.getH())%map.getH(); - if(!edit && !map.isMapDiscovered(rx, ry, visibleTeams)) - continue; - if(overlays->getValue(rx, ry)) - { - const int value_c=overlays->getValue(rx, ry); - const int alpha_c=int(float(200)/float(overlayMax) * float(value_c)); - overlayAlphas[width * y + x] = alpha_c; - } - } - } - - ///This is to correct OpenGL's blending not beeing offset correctly to line up with the map tiles - if(globalContainer->gfx->getOptionFlags() & GraphicContext::USEGPU) - globalContainer->gfx->drawAlphaMap(overlayAlphas, width, height, -16, -16, 32, 32, overlayColor); - else - globalContainer->gfx->drawAlphaMap(overlayAlphas, width, height, -32, -32, 32, 32, overlayColor); - } -} - - - -inline void Game::drawUnitPathLines(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) -{ - if ((drawOptions & DRAW_PATH_LINE) != 0) - { - for(int i=0; imyUnits[i]; - if (unit) - { - drawUnitPathLine(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, unit); - } - } - } - if(selectedUnit != NULL) - { - drawUnitPathLine(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, selectedUnit); - } -} - - - -inline void Game::drawUnitPathLine(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, Unit* unit) -{ - Uint32 visibleTeams = teams[localTeam]->me; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - if(unit->owner->sharedVisionOther & visibleTeams) - { - if (unit->validTarget) - { - if(isOnScreen(left,top,right,bot,viewportX,viewportY,unit->posX,unit->posY) || isOnScreen(left,top,right,bot,viewportX,viewportY,unit->targetX,unit->targetY)) - { - int px, py; - map.mapCaseToDisplayableVector(unit->posX, unit->posY, &px, &py, viewportX, viewportY, sw, sh); - int deltaLeft=255-unit->delta; - if (unit->actiondx*deltaLeft)>>3; - py-=(unit->dy*deltaLeft)>>3; - } - - - int lsx, lsy, ldx, ldy; - map.mapCaseToDisplayableVector(unit->targetX, unit->targetY, &ldx, &ldy, viewportX, viewportY, sw, sh); - lsx=px+16; - lsy=py+16; - if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) - globalContainer->gfx->drawLine(lsx, lsy, ldx+16, ldy+16, 250, 250, 250); - else - globalContainer->gfx->drawLine(lsx, lsy, ldx+16, ldy+16, 250, 250, 250, 128); - } - } - } -} - - - -inline void Game::drawUnitOffScreen(int sx, int sy, int sw, int sh, int viewportX, int viewportY, Unit* unit, Uint32 drawOptions) -{ - // Get the direction to the unit - int px, py; - map.mapCaseToDisplayableVector(unit->posX, unit->posY, &px, &py, viewportX, viewportY, sw, sh); - int deltaLeft=255-unit->delta; - if (unit->actiondx*deltaLeft)>>3; - py-=(unit->dy*deltaLeft)>>3; - } - - // To get the center of the unit - px+=16; - py+=16; - - // Place the internal box dimensions - int i_sx = sx + 20; - int i_sy = sy + 20; - int i_sw = sw - 40; - int i_sh = sh - 40; - - // The units draw position releative to the center of the internal square - int rel_cx = px - i_sx - i_sw/2; - int rel_cy = py - i_sy - i_sh/2; - if(rel_cx == 0) - rel_cx = 1; - if(rel_cy == 0) - rel_cy = 1; - - //globalContainer->gfx->drawLine(sx + sw/2, sy + sh/2, px, py, Color::white); - - // Decide which edge of the screen the box is on, and compute its center cordinates - int bx = 0; - int by = 0; - float slope = float(rel_cy) / float(rel_cx); - float angle = atan2f(float(rel_cy), float(rel_cx)); - float screen=float(i_sh) / float(i_sw); - if(rel_cx > 0 && std::abs(slope) <= std::abs(screen)) - { - bx = i_sx + i_sw; - by = i_sy + (i_sh/2) + int(slope * float(i_sw/2)); - } - else if(rel_cx < 0 && std::abs(slope) <= std::abs(screen)) - { - bx = i_sx; - by = i_sy + (i_sh/2) - int(slope * float(i_sw/2)); - } - else if(rel_cy > 0 && std::abs(slope) >= std::abs(screen)) - { - bx = i_sx + (i_sw/2) + int(float(i_sh/2) / slope); - by = i_sy + i_sh; - } - else if(rel_cy < 0 && std::abs(slope) >= std::abs(screen)) - { - bx = i_sx + (i_sw/2) - int(float(i_sh/2) / slope); - by = i_sy; - } - - bx -= 20; - by -= 20; - - // draw unit's image - int imgid; - UnitType *ut=unit->race->getUnitType(unit->typeNum, 0); - assert(unit->action>=0); - - assert(unit->actionstartImage[unit->action]; - - int dir=unit->direction; - int delta=unit->delta; - assert(dir>=0); - assert(dir<9); - assert(delta>=0); - assert(delta<256); - if (dir==8) - { - imgid+=8*(delta>>5); - } - else - { - imgid+=8*dir; - imgid+=(delta>>5); - } - - Sprite *unitSprite=globalContainer->units; - unitSprite->setBaseColor(unit->owner->color); - int decX = (32-unitSprite->getW(imgid))>>1; - int decY = (32-unitSprite->getH(imgid))>>1; - - // Draw the code - //globalContainer->gfx->drawFilledRect(bx, by, 40, 40, 0,0,0,128); - //globalContainer->gfx->drawCircle(bx+20, by+20, 20, Color::white); - Color transpWhite = Color(255, 255, 255, 192); - globalContainer->gfx->drawLine( - bx+20+cosf(angle)*5, - by+20+sinf(angle)*5, - bx+20+cosf(angle)*17, - by+20+sinf(angle)*17, - Color::white); - globalContainer->gfx->drawLine( - bx+20+cosf(angle)*17, - by+20+sinf(angle)*17, - bx+20+cosf(angle-M_PI/6)*10, - by+20+sinf(angle-M_PI/6)*10, - Color::white); - globalContainer->gfx->drawLine( - bx+20+cosf(angle)*17, - by+20+sinf(angle)*17, - bx+20+cosf(angle+M_PI/6)*10, - by+20+sinf(angle+M_PI/6)*10, - Color::white); - globalContainer->gfx->drawSprite(bx+decX+4, by+decY+4, unitSprite, imgid, 160); -} - - -float Game::interpolateValues(float a, float b, float x) -{ - float ft = 3.141592653f * x; - float f = (1.0f - std::cos(ft)) * 0.5f; - return a*(1.0-f) + b*f; -} - - - -inline bool Game::isOnScreen(int left, int top, int right, int bot, int viewportX, int viewportY, int x, int y) -{ - - left += viewportX; - right += viewportX; - top += viewportY; - bot += viewportY; - - if((x >= left-1 && x <= right) || (x+map.getW() >= left-1 && x+map.getW() <= right)) - { - if((y >= top-1 && y <= bot) || (y+map.getH() >= top-1 && y+map.getH() <= bot)) - { - return true; - } - } - return false; -} - - - -void Game::drawMap(int sx, int sy, int sw, int sh, int rightMargin, int topMargin, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, std::set *visibleBuildings) -{ - static int time = 0; - static DynamicClouds ds(&globalContainer->settings); - int left=(sx>>5); - int top=(sy>>5); - int right=((sx+sw+31)>>5); - int bot=((sy+sh+31)>>5); - - time++; - drawMapWater(sw, sh, viewportX, viewportY, time); - drawMapTerrain(left, top, right, bot, viewportX, viewportY, localTeam, drawOptions); - drawMapRessources(left, top, right, bot, viewportX, viewportY, localTeam, drawOptions); - drawMapGroundUnits(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); - drawMapDebugAreas(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); - drawMapGroundBuildings(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, visibleBuildings); - drawMapAirUnits(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); - if((drawOptions & DRAW_SCRIPT_AREAS) != 0) - drawMapScriptAreas(left, top, right, bot, viewportX, viewportY); - drawMapBulletsExplosionsDeathAnimations(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); - - // compute and draw cloud shadow if we are in high quality - if ((globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) == 0) - { - ds.compute(viewportX, viewportY, sw, sh, time); - ds.render(globalContainer->gfx, sw, sh, DynamicClouds::SHADOW); - } - - drawMapFogOfWar(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); - drawMapAreas(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); - drawMapOverlayMaps(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); - drawUnitPathLines(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); - - // draw cloud overlay if we are in high quality - if ((globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) == 0) - ds.render(globalContainer->gfx, sw, sh, DynamicClouds::CLOUD); - - // Draw units that are off the screen for the selected building - - Uint32 visibleTeams = teams[localTeam]->me; - if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; - - if(selectedBuilding != NULL && (selectedBuilding->owner->sharedVisionOther & visibleTeams)) - { - for(std::list::iterator i = selectedBuilding->unitsWorking.begin(); i!=selectedBuilding->unitsWorking.end(); ++i) - { - Unit* unit = *i; - if(!isOnScreen(left, top, right, bot, viewportX, viewportY, unit->posX, unit->posY)) - { - drawUnitOffScreen(0, topMargin, sw - rightMargin, sh-topMargin, viewportX, viewportY, unit, drawOptions); - } - } - } - - // we look on the whole map for buildings - // TODO : increase speed, do not count on graphic clipping - if (!globalContainer->replaying || globalContainer->replayShowFlags) - { - // In replays we want to show the flags of all players, so we build a list of whose buildings to show - std::list teamsToShow; - - if (!globalContainer->replaying) - { - // Only add the local team - teamsToShow.push_back(teams[localTeam]); - } - else - { - // Add all teams - for (int i=0; i::iterator teamsIt=teamsToShow.begin(); teamsIt!=teamsToShow.end(); ++teamsIt) - { - for (std::list::iterator virtualIt=(*teamsIt)->virtualBuildings.begin(); - virtualIt!=(*teamsIt)->virtualBuildings.end(); ++virtualIt) - { - Building *building=*virtualIt; - BuildingType *type=building->type; - - int team = building->owner->teamNumber; - - int imgid = type->gameSpriteImage; - - int x, y; - map.mapCaseToDisplayable(building->posXLocal, building->posYLocal, &x, &y, viewportX, viewportY); - - // all flags are hued: - Sprite *buildingSprite = type->gameSpritePtr; - buildingSprite->setBaseColor(teams[team]->color); - globalContainer->gfx->drawSprite(x, y, buildingSprite, imgid); - - // flag circle: - if (((drawOptions & DRAW_HEALTH_FOOD_BAR) != 0) || (building==selectedBuilding)) - globalContainer->gfx->drawCircle(x+16, y+16, 16+(32*building->unitStayRange), 0, 0, 255); - - // FIXME : ugly copy past - if ((drawOptions & DRAW_HEALTH_FOOD_BAR) != 0) - { - int decy=(type->height*32); - int healDecx=(type->width-2)*16+1; - //int unitDecx=(building->type->width*16)-((3*building->maxUnitInside)>>1); - - // TODO : find better color for this - // health - if (type->hpMax) - { - float hpRatio=(float)building->hp/(float)type->hpMax; - if (hpRatio>0.6) - drawPointBar(x+healDecx+6, y+decy-4, LEFT_TO_RIGHT, 16, 1+(int)(15.0f*hpRatio), 78, 187, 78); - else if (hpRatio>0.3) - drawPointBar(x+healDecx+6, y+decy-4, LEFT_TO_RIGHT, 16, 1+(int)(15.0f*hpRatio), 255, 255, 0); - else - drawPointBar(x+healDecx+6, y+decy-4, LEFT_TO_RIGHT, 16, 1+(int)(15.0f*hpRatio), 255, 0, 0); - } - - // units - - if (building->maxUnitInside>0) - drawPointBar(x+type->width*32-4, y+1, BOTTOM_TO_TOP, building->maxUnitInside, (signed)building->unitsInside.size(), 255, 255, 255); - if (building->maxUnitWorking>0) - drawPointBar(x+type->width*16-((3*building->maxUnitWorking)>>1), y+1,LEFT_TO_RIGHT , building->maxUnitWorking, (signed)building->unitsWorking.size(), 255, 255, 255); - - // food - if ((type->canFeedUnit) || (type->unitProductionTime)) - { - // compute bar size, prevent oversize - int bDiv=1; - assert(type->height!=0); - while ( ((type->maxRessource[CORN]*3+1)/bDiv)>((type->height*32)-10)) - bDiv++; - drawPointBar(x+1, y+1, BOTTOM_TO_TOP, type->maxRessource[CORN]/bDiv, building->ressources[CORN]/bDiv, 255, 255, 120, 1+bDiv); - } - } - } - } - } - - if (false) - for (int y=top-1; y<=bot; y++) - for (int x=left-1; x<=right; x++) - for (int pi=0; piai && players[pi]->ai->implementitionID==AI::CASTOR) - { - AICastor *ai=(AICastor *)players[pi]->ai->aiImplementation; - //Uint8 *gradient=ai->wheatCareMap[1]; - Uint8 *gradient=ai->hydratationMap; - //Uint8 *gradient=ai->enemyWarriorsMap; - //Uint8 *gradient=map.forbiddenGradient[1][0]; - //Uint8 *gradient=map.ressourcesGradient[0][CORN][0]; - - assert(gradient); - size_t addr=((x+viewportX)&map.wMask)+map.w*((y+viewportY)&map.hMask); - Uint8 value=gradient[addr]; - if (value) - globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, value); - - /*Uint8 *gradient2=ai->wheatCareMap[1]; - assert(gradient2); - Uint8 value2=gradient2[addr]; - if (value2) - globalContainer->gfx->drawString((x<<5), (y<<5)+10, globalContainer->littleFont, value2);*/ - break; - } -} - - - -void Game::setWaitingOnMask(Uint32 mask) -{ - Uint32 oldMask = maskAwayPlayer; - maskAwayPlayer = mask; - - if(mask != 0) - { - if(oldMask == 0) - anyPlayerWaitedTimeFor = 0; - anyPlayerWaited = true; - } - else - { - anyPlayerWaited = false; - } -} - - - -void Game::dumpAllData(const std::string& file) -{ - OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(file)); - if (stream->isEndOfStream()) - { - std::cerr << "Can't dump full game memory to file "<< file << std::endl; - } - else - { - std::cerr << "Dumped full game memory to file "<< file << std::endl; - save(stream, false, file); + std::cerr << "Dumped full game memory to file "<< file << std::endl; + save(stream, false, file); } delete stream; } - - -Uint32 Game::checkSum(std::vector *checkSumsVector, std::vector *checkSumsVectorForBuildings, std::vector *checkSumsVectorForUnits, bool heavy) -{ - Uint32 cs=0; - - Uint32 headerCs=mapHeader.checkSum(); - cs^=headerCs; - if (checkSumsVector) - checkSumsVector->push_back(headerCs);// [0] - - cs=(cs<<31)|(cs>>1); - - Uint32 teamsCs=0; - for (int i=0; icheckSum(checkSumsVector, checkSumsVectorForBuildings, checkSumsVectorForUnits); - teamsCs=(teamsCs<<31)|(teamsCs>>1); - cs=(cs<<31)|(cs>>1); - } - cs^=teamsCs; - if (checkSumsVector) - checkSumsVector->push_back(teamsCs);// [1+t*20] - - cs=(cs<<31)|(cs>>1); - - Uint32 playersCs=0; - for (int i=0; icheckSum(checkSumsVector); - playersCs=(playersCs<<31)|(playersCs>>1); - cs=(cs<<31)|(cs>>1); - } - cs^=playersCs; - if (checkSumsVector) - checkSumsVector->push_back(playersCs);// [2+t*20+p*2] - - cs=(cs<<31)|(cs>>1); - - for (int i=0; itype==BasePlayer::P_IP) - { - heavy=true; - break; - } - } - Uint32 mapCs=map.checkSum(heavy); - cs^=mapCs; - if (checkSumsVector) - checkSumsVector->push_back(mapCs);// [3+t*20+p*2] - - cs=(cs<<31)|(cs>>1); - - Uint32 scriptCs=sgslScript.checkSum(); - cs^=scriptCs; - if (checkSumsVector) - checkSumsVector->push_back(scriptCs);// [4+t*20+p*2] - - return cs; -} - Team *Game::getTeamWithMostPrestige(void) { int maxPrestige=0; @@ -3177,8 +271,8 @@ Team *Game::getTeamWithMostPrestige(void) bool Game::isPrestigeWinCondition(void) { - std::list >& conditions = gameHeader.getWinningConditions(); - for(std::list >::iterator i = conditions.begin(); i!=conditions.end(); ++i) + std::list >& conditions = gameHeader.getWinningConditions(); + for(std::list >::iterator i = conditions.begin(); i!=conditions.end(); ++i) { if((*i)->getType() == WCPrestige) return true; diff --git a/src/Game.h b/src/Game.h index a0c423ebf..c8f814a06 100644 --- a/src/Game.h +++ b/src/Game.h @@ -1,28 +1,11 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2007 Bradley Arsenault - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GAME_H -#define __GAME_H +#pragma once #include +#include #include "Map.h" #include "SGSL.h" @@ -33,6 +16,7 @@ #include "GameObjectives.h" #include "GameHints.h" #include "MapScript.h" +#include "BuildingGuiState.h" namespace GAGCore { @@ -45,6 +29,94 @@ class MapGenerationDescriptor; class GameGUI; class MapEdit; +class OrderCreate; +class OrderModifyBuilding; +class OrderModifyExchange; +class OrderModifyFlag; +class OrderModifyClearingFlag; +class OrderModifyMinLevelToFlag; +class OrderMoveFlag; +class OrderAlterateForbidden; +class OrderAlterateGuardArea; +class OrderAlterateClearArea; +class OrderModifySwarm; +class OrderDelete; +class OrderChangePriority; +class OrderCancelDelete; +class OrderConstruction; +class SetAllianceOrder; +class PlayerQuitsGameOrder; +#ifndef YOG_SERVER_ONLY +class GameAnimations; +#endif // !YOG_SERVER_ONLY + +// Minimum value of the prestige-victory threshold. +#define MIN_MAX_PRESTIGE 500 +// Each team contributes this much to the prestige-victory threshold. +#define TEAM_MAX_PRESTIGE 150 + +// === Game-loop sentinels (cross-slice) === + +//! Returned by winner-tracking code to mean "no team has won yet". Used by +//! EngineRun.cpp:301 against winnerTeam. +static constexpr int WINNER_TEAM_NONE = -1; + +//! Default `pos` argument to Game::addTeam / removeTeam meaning "append +//! at the end of the team list" (Game.cpp:171; Game_editor.cpp:102, 124). +//! Distinct from any other -1 sentinel — see glossary §2. +static constexpr int TEAM_POS_END = -1; + +//! Length of the rolling tick-time profile buffer in Game::ticksGameSum. +//! Indexed by `stepCounter & 31`, not by team id (bug #11). Naming this +//! separately documents the actual meaning — it is unrelated to team count. +static constexpr int TICK_PROFILE_BUF_LEN = 32; + +//! Fog-of-war switch cadence: every (mask + 1) ticks, perform the FOW +//! switch when (stepCounter & FOW_SWITCH_TICK_MASK) == FOW_SWITCH_TICK_PHASE. +//! See Game_sync.cpp:175. +static constexpr int FOW_SWITCH_TICK_MASK = 31; +static constexpr int FOW_SWITCH_TICK_PHASE = 16; + +//! Build-project step cadence: every 16 ticks, run buildProjectSyncStep +//! when (stepCounter & MASK) == PHASE. See Game_sync.cpp:194. +static constexpr int BUILD_PROJECT_TICK_MASK = 15; +static constexpr int BUILD_PROJECT_TICK_PHASE = 1; + +//! World-logic step cadence: every 32 ticks, run the world-logic pass when +//! (stepCounter & MASK) == PHASE. See Game_sync.cpp:197. +static constexpr int WORLD_LOGIC_TICK_MASK = 31; +static constexpr int WORLD_LOGIC_TICK_PHASE = 0; + +//! Upper bound of the (team * Building::MAX_COUNT + buildingId) global id +//! space, equal to Team::MAX_COUNT * Building::MAX_COUNT. +//! Asserted in OrderBuilding.cpp:78, 109, 141, 179, 214; OrderModify.cpp:29; +//! UnitSerialization.cpp:235. +static constexpr int BUILDING_GID_MAX = Team::MAX_COUNT * Building::MAX_COUNT; + +//! Number of distinct levels a building can reach (0..MAX_BUILDING_LEVELS-1). +//! Used by Game_editor.cpp:91. Distinct from NB_UNIT_LEVELS. +static constexpr int MAX_BUILDING_LEVELS = 6; + +//! Cap on how many workers an OrderModifyBuilding may request for one +//! building. See Game_orders.cpp:140. +static constexpr int MAX_BUILDING_WORKER_REQUEST = 20; + +//! Sentinel returned by the linear search for a free unit/building slot +//! in Game::addUnit / Game::addBuilding when every slot is occupied. +//! See Game_editor.cpp:203, 234. Distinct from any other -1 sentinel. +static constexpr int SLOT_INDEX_NONE = -1; + +//! Full HSV hue range in degrees, used to spread team colours evenly +//! around the colour wheel: hue = (i * TEAM_COLOR_HUE_DEGREES) / numTeams. +//! See Game_editor.cpp:113, 139. +static constexpr float TEAM_COLOR_HUE_DEGREES = 360.0f; + +//! Padding (in tiles) added on each side of the rectangle passed to +//! Map::dirtyLocalGradient when a building/flag changes. The width/height +//! of the dirty rect therefore grows by 2 * GRADIENT_DIRTY_BORDER_TILES. +//! See Game_orders.cpp:193, 279, 360, 496. +static constexpr int GRADIENT_DIRTY_BORDER_TILES = 16; + class Game { static const bool verbose = false; @@ -94,7 +166,7 @@ class Game void setGameHeader(const GameHeader& gameHeader, bool saveAI=false); /// Executes an Order with respect to the localPlayer of the GUI. All Orders get processed here. - void executeOrder(boost::shared_ptr order, int localPlayer); + void executeOrder(std::shared_ptr order, int localPlayer); /// Makes a step for building projects that are waiting for the areas to clear of units. void buildProjectSyncStep(Sint32 localTeam); @@ -124,8 +196,8 @@ class Game // Editor stuff // add & remove teams, used by the map editor and the random map generator - void addTeam(int pos=-1); - void removeTeam(int pos=-1); + void addTeam(int pos=TEAM_POS_END); + void removeTeam(int pos=TEAM_POS_END); //! If a team is uncontrolled (playerMask == 0), remove units and buildings from map void clearingUncontrolledTeams(void); void regenerateDiscoveryMap(void); @@ -145,7 +217,11 @@ class Game bool checkHardRoomForBuilding(int x, int y, const BuildingType *bt); void drawUnit(int x, int y, Uint16 gid, int viewportX, int viewportY, int screenW, int screenH, int localTeam, Uint32 drawOptions); - void drawMap(int sx, int sy, int sw, int sh, int rightMargin, int topMargin, int viewportX, int viewportY, int teamSelected, Uint32 drawOptions = 0, std::set *visibleBuildings = 0); + /// `buildingGuiState` (optional) provides per-flag pending positions so + /// drag-targets render before the move-flag order has executed. The map + /// editor passes nullptr — it mutates buildings directly without an + /// orderQueue, so there is no pending shadow to consult. + void drawMap(int sx, int sy, int sw, int sh, int rightMargin, int topMargin, int viewportX, int viewportY, int teamSelected, Uint32 drawOptions = 0, std::set *visibleBuildings = 0, const BuildingGuiStateMap* buildingGuiState = nullptr); ///Sets the mask respresenting which players the game is waiting on void setWaitingOnMask(Uint32 mask); @@ -179,6 +255,39 @@ class Game ///Clears existing game information, deleting the teams and players, in preperation of a new game. void clearGame(); + /// Look up a Building by its global ID. Returns nullptr if the slot is empty. + /// Collapses the gid → team-index → building-index → pointer decode that + /// would otherwise appear inline at every executeOrder caller. + Building* lookupBuilding(Uint16 gid) const; + + /// Per-order-type executors. The dispatcher executeOrder() downcasts the + /// shared_ptr to its concrete type and calls the matching helper. + /// Helpers do NOT re-check team aliveness — the dispatcher gates that. + void executeCreate(const OrderCreate& order, int localPlayer); + void executeModifyBuilding(const OrderModifyBuilding& order, int localPlayer); + void executeModifyExchange(const OrderModifyExchange& order, int localPlayer); + void executeModifyFlag(const OrderModifyFlag& order, int localPlayer); + void executeModifyClearingFlag(const OrderModifyClearingFlag& order, int localPlayer); + /// Sets minLevelToFlag and flushes currently-assigned units by toggling + /// maxUnitWorking through zero so the building releases them on update(). + void executeModifyMinLevelToFlag(const OrderModifyMinLevelToFlag& order, int localPlayer); + void executeMoveFlag(const OrderMoveFlag& order, int localPlayer); + void executeAlterateForbidden(const OrderAlterateForbidden& order, int localPlayer); + void executeAlterateGuardArea(const OrderAlterateGuardArea& order, int localPlayer); + void executeAlterateClearArea(const OrderAlterateClearArea& order, int localPlayer); + void executeModifySwarm(const OrderModifySwarm& order, int localPlayer); + /// Delete-building. Bypasses the team-alive gate: dead-team buildings + /// can still be torn down. + void executeDelete(const OrderDelete& order); + void executeChangePriority(const OrderChangePriority& order); + void executeCancelDelete(const OrderCancelDelete& order); + void executeConstruction(const OrderConstruction& order); + void executeCancelConstruction(const OrderConstruction& order); + void executeSetAlliance(const SetAllianceOrder& order); + /// Marks the leaving player's team dead only if no other player still + /// controls that team; either way, the leaving player slot becomes AI::NONE. + void executePlayerQuitGame(const PlayerQuitsGameOrder& order); + public: bool anyPlayerWaited; int anyPlayerWaitedTimeFor; @@ -196,30 +305,33 @@ class Game ///draws a point bar. This can be health, hunger, fill level, etc. Point bars can have 2 sections of actLength and secondActLength, followed by black until maxLength. r/g/b is for the first section, r2/g2/b2 for the second void drawPointBar(int x, int y, BarOrientation orientation, int maxLength, int actLength, int secondActLength, Uint8 r, Uint8 g, Uint8 b, Uint8 r2, Uint8 g2, Uint8 b2, int barWidth=2); + ///draws an HP bar coloured green/yellow/red against the 0.6 / 0.3 hpRatio thresholds + void drawHealthBar(int x, int y, int maxLength, int actLength, float hpRatio); + ///draws a building resource bar (food, bullets, ...) auto-shrinking to fit within (height*32)-10 pixels + void drawBuildingResourceBar(int x, int y, BuildingType* type, int maxValue, int currentValue, Uint8 r, Uint8 g, Uint8 b); ///draws the overlay representing water - inline void drawMapWater(int sw, int sh, int viewportX, int viewportY, int time); + void drawMapWater(int sw, int sh, int viewportX, int viewportY, int time); ///draws the terrain tiles of sand and gras - inline void drawMapTerrain(int left, int top, int right, int bot, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawMapTerrain(int left, int top, int right, int bot, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); ///draws the resources like algues, wheat or fruit trees - inline void drawMapRessources(int left, int top, int right, int bot, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawMapRessources(int left, int top, int right, int bot, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); ///draws the ground units. up till now those are workers and warriors - inline void drawMapGroundUnits(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawMapGroundUnits(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); ///draws debug information. switched in the code. - inline void drawMapDebugAreas(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); - inline void drawMapGroundBuildings(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, std::set *visibleBuildings); - inline void drawMapBuilding(int x, int y, int gid, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); - inline void drawMapAreas(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); - inline void drawMapArea(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, Map * map, bool (Map::*mapIs)(int, int) const, int areaAnimationTick, AreaType areaType); - inline void drawMapAirUnits(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); - inline void drawMapScriptAreas(int left, int top, int right, int bot, int viewportX, int viewportY); - inline void drawMapBulletsExplosionsDeathAnimations(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); - inline void drawMapFogOfWar(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); - inline void drawMapOverlayMaps(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); - inline void drawUnitPathLines(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); - inline void drawUnitPathLine(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, Unit* unit); - inline void drawUnitOffScreen(int sx, int sy, int sw, int sh, int viewportX, int viewportY, Unit* unit, Uint32 drawOptions); - static float interpolateValues(float a, float b, float x); - inline bool isOnScreen(int left, int top, int right, int bot, int viewportX, int viewportY, int x, int y); + void drawMapDebugAreas(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawMapGroundBuildings(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, std::set *visibleBuildings, const BuildingGuiStateMap* buildingGuiState); + void drawMapBuilding(int x, int y, int gid, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawMapAreas(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawMapArea(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, Map * map, bool (Map::*mapIs)(int, int) const, int areaAnimationTick, AreaType areaType); + void drawMapAirUnits(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawMapScriptAreas(int left, int top, int right, int bot, int viewportX, int viewportY); + void drawMapBulletsExplosionsDeathAnimations(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawMapFogOfWar(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawMapOverlayMaps(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawUnitPathLines(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions); + void drawUnitPathLine(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, Unit* unit); + void drawUnitOffScreen(int sx, int sy, int sw, int sh, int viewportX, int viewportY, Unit* unit, Uint32 drawOptions); + bool isOnScreen(int left, int top, int right, int bot, int viewportX, int viewportY, int x, int y); public: Uint32 checkSum(std::vector *checkSumsVector=NULL, std::vector *checkSumsVectorForBuildings=NULL, std::vector *checkSumsVectorForUnits=NULL, bool heavy=false); @@ -242,6 +354,13 @@ class Game std::string missionBriefing; GameGUI *gui; MapEdit *edit; +#ifndef YOG_SERVER_ONLY + //! Render-side container for bullet explosions and unit death + //! animations. Always non-null in non-server builds; the runNoX + //! gate is internal to GameAnimations. See + //! src/render/GameAnimations.h. + std::unique_ptr animations; +#endif // !YOG_SERVER_ONLY std::list buildProjects; ///Stores alpha values to be passed to the drawing system. kept here so it isn't re-allocated ///every frame @@ -275,8 +394,6 @@ class Game bool generateMap(MapGenerationDescriptor &descriptor); protected: - FILE *logFile; - int ticksGameSum[Team::MAX_COUNT]; + int ticksGameSum[TICK_PROFILE_BUF_LEN]; }; -#endif diff --git a/src/GameEvent.cpp b/src/GameEvent.cpp index 752d001f6..5454e27df 100644 --- a/src/GameEvent.cpp +++ b/src/GameEvent.cpp @@ -1,234 +1,94 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2007 Bradley Arsenault #include "GameEvent.h" -#include "Toolkit.h" -#include "StringTable.h" + #include "FormatableString.h" -#include "UnitConsts.h" +#include "Game.h" #include "IntBuildingType.h" +#include "StringTable.h" +#include "Team.h" +#include "TeamDisplay.h" +#include "Toolkit.h" +#include "UnitDisplayNames.h" using namespace GAGCore; -GameEvent::GameEvent(Uint32 step, Sint16 x, Sint16 y) - : step(step), x(x), y(y) -{ - -} - - - -GameEvent::~GameEvent() -{ - -} - - - -Uint32 GameEvent::getStep() -{ - return step; -} - - - -Sint16 GameEvent::getX() -{ - return x; -} - - - -Sint16 GameEvent::getY() -{ - return y; -} - - - -UnitUnderAttackEvent::UnitUnderAttackEvent(Uint32 step, Sint16 x, Sint16 y, Uint32 type) - : GameEvent(step, x, y), type(type) -{ - -} - - - -std::string UnitUnderAttackEvent::formatMessage() -{ - std::string message; - message+=FormatableString(Toolkit::getStringTable()->getString("[Your %0 are under attack]")) - .arg(getUnitName(type)); - return message; -} - - - -GAGCore::Color UnitUnderAttackEvent::formatColor() -{ - return GAGCore::Color(200, 30, 30); -} - - - -Uint8 UnitUnderAttackEvent::getEventType() -{ - return GEUnitUnderAttack; -} - - - - -UnitLostConversionEvent::UnitLostConversionEvent(Uint32 step, Sint16 x, Sint16 y, const std::string& teamName) - : GameEvent(step, x, y), teamName(teamName) -{ - -} - - - -std::string UnitLostConversionEvent::formatMessage() +GameEvent::GameEvent(GameEventType type, Uint32 step, Sint16 x, Sint16 y, Uint32 typeNum, Uint8 otherTeamNumber) + : type(type), step(step), x(x), y(y), typeNum(typeNum), otherTeamNumber(otherTeamNumber) { - std::string message; - message += FormatableString(Toolkit::getStringTable()->getString("[Your unit got converted to %0's team]")).arg(teamName); - return message; } - - -GAGCore::Color UnitLostConversionEvent::formatColor() +GameEvent GameEvent::unitUnderAttack(Uint32 step, Sint16 x, Sint16 y, Uint32 unitType) { - return GAGCore::Color(140, 0, 0); + return GameEvent(GEUnitUnderAttack, step, x, y, unitType, 0); } - - -Uint8 UnitLostConversionEvent::getEventType() +GameEvent GameEvent::unitLostConversion(Uint32 step, Sint16 x, Sint16 y, Uint8 otherTeamNumber) { - return GEUnitLostConversion; + return GameEvent(GEUnitLostConversion, step, x, y, 0, otherTeamNumber); } - - - -UnitGainedConversionEvent::UnitGainedConversionEvent(Uint32 step, Sint16 x, Sint16 y, const std::string& teamName) - : GameEvent(step, x, y), teamName(teamName) +GameEvent GameEvent::unitGainedConversion(Uint32 step, Sint16 x, Sint16 y, Uint8 otherTeamNumber) { - + return GameEvent(GEUnitGainedConversion, step, x, y, 0, otherTeamNumber); } - - -std::string UnitGainedConversionEvent::formatMessage() +GameEvent GameEvent::buildingUnderAttack(Uint32 step, Sint16 x, Sint16 y, Uint8 buildingType) { - std::string message; - message += FormatableString(Toolkit::getStringTable()->getString("[%0's team unit got converted to your team]")).arg(teamName); - return message; + return GameEvent(GEBuildingUnderAttack, step, x, y, buildingType, 0); } - - -GAGCore::Color UnitGainedConversionEvent::formatColor() +GameEvent GameEvent::buildingCompleted(Uint32 step, Sint16 x, Sint16 y, Uint8 buildingType) { - return GAGCore::Color(100, 255, 100); + return GameEvent(GEBuildingCompleted, step, x, y, buildingType, 0); } - - -Uint8 UnitGainedConversionEvent::getEventType() +std::string GameEvent::formatMessage(const Game& game) const { - return GEUnitGainedConversion; + StringTable* table = Toolkit::getStringTable(); + switch (type) + { + case GEUnitUnderAttack: + return FormatableString(table->getString("[Your %0 are under attack]")) + .arg(getUnitName(typeNum)); + case GEUnitLostConversion: + return FormatableString(table->getString("[Your unit got converted to %0's team]")) + .arg(displayPlayerName(*game.teams[otherTeamNumber])); + case GEUnitGainedConversion: + return FormatableString(table->getString("[%0's team unit got converted to your team]")) + .arg(displayPlayerName(*game.teams[otherTeamNumber])); + case GEBuildingUnderAttack: + { + std::string key = "[the "; + key += IntBuildingType::typeFromShortNumber(typeNum); + key += " is under attack]"; + return table->getString(key.c_str()); + } + case GEBuildingCompleted: + { + std::string key = "[the "; + key += IntBuildingType::typeFromShortNumber(typeNum); + key += " is finished]"; + return table->getString(key.c_str()); + } + case GESize: + break; + } + return std::string(); } - - - -BuildingUnderAttackEvent::BuildingUnderAttackEvent(Uint32 step, Sint16 x, Sint16 y, Uint8 type) - : GameEvent(step, x, y), type(type) +GAGCore::Color GameEvent::formatColor() const { - + switch (type) + { + case GEUnitUnderAttack: return GAGCore::Color(200, 30, 30); + case GEUnitLostConversion: return GAGCore::Color(140, 0, 0); + case GEUnitGainedConversion: return GAGCore::Color(100, 255, 100); + case GEBuildingUnderAttack: return GAGCore::Color(255, 0, 0); + case GEBuildingCompleted: return GAGCore::Color(30, 255, 30); + case GESize: break; + } + return GAGCore::Color(); } - - - -std::string BuildingUnderAttackEvent::formatMessage() -{ - std::string message; - std::string key = "[the "; - key += IntBuildingType::typeFromShortNumber(type); - key += " is under attack]"; - message += Toolkit::getStringTable()->getString(key.c_str()); - return message; -} - - - -GAGCore::Color BuildingUnderAttackEvent::formatColor() -{ - return GAGCore::Color(255, 0, 0); -} - - - -Uint8 BuildingUnderAttackEvent::getEventType() -{ - return GEBuildingUnderAttack; -} - - - - -BuildingCompletedEvent::BuildingCompletedEvent(Uint32 step, Sint16 x, Sint16 y, Uint8 type) - : GameEvent(step, x, y), type(type) -{ - -} - - - -std::string BuildingCompletedEvent::formatMessage() -{ - std::string message; - std::string key = "[the "; - key += IntBuildingType::typeFromShortNumber(type); - key += " is finished]"; - message += Toolkit::getStringTable()->getString(key.c_str()); - return message; -} - - - -GAGCore::Color BuildingCompletedEvent::formatColor() -{ - return GAGCore::Color(30, 255, 30); -} - - - -Uint8 BuildingCompletedEvent::getEventType() -{ - return GEBuildingCompleted; -} - - - -///code_append_marker - diff --git a/src/GameEvent.h b/src/GameEvent.h index d98f08d37..82b2c0fd0 100644 --- a/src/GameEvent.h +++ b/src/GameEvent.h @@ -1,183 +1,59 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2007 Bradley Arsenault - Copyright (C) 2007 Bradley Arsenault +#pragma once - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GameEvent_h -#define GameEvent_h +#include #include "GraphicContext.h" +class Game; + enum GameEventType { - GEUnitUnderAttack=0, + GEUnitUnderAttack = 0, GEUnitLostConversion, GEUnitGainedConversion, GEBuildingUnderAttack, GEBuildingCompleted, - //type_append_marker GESize, }; - -///This represents an event in the game. This includes events such as building completion, -///units being attacked, etc... + +/// An in-game notification (unit/building under attack, conversion, building +/// completed). Pushed onto Team::events by the simulation; consumed by the GUI +/// to show a colored chat message and let the player jump to the location. +/// +/// The event payload is locale-agnostic: conversion events store the other +/// team's number, not its localized display name. The display string is +/// resolved at format time via formatMessage(game). class GameEvent { public: - ///Constructs a GameEvent with the step and the (x,y) cordinates of the event on screen - GameEvent(Uint32 step, Sint16 x, Sint16 y); - - virtual ~GameEvent(); + static GameEvent unitUnderAttack(Uint32 step, Sint16 x, Sint16 y, Uint32 unitType); + static GameEvent unitLostConversion(Uint32 step, Sint16 x, Sint16 y, Uint8 otherTeamNumber); + static GameEvent unitGainedConversion(Uint32 step, Sint16 x, Sint16 y, Uint8 otherTeamNumber); + static GameEvent buildingUnderAttack(Uint32 step, Sint16 x, Sint16 y, Uint8 buildingType); + static GameEvent buildingCompleted(Uint32 step, Sint16 x, Sint16 y, Uint8 buildingType); - ///This formats a user-readable message, including translating the message - virtual std::string formatMessage()=0; + std::string formatMessage(const Game& game) const; + GAGCore::Color formatColor() const; - ///Returns the color of the message after its formatted - virtual GAGCore::Color formatColor()=0; - - ///Returns the step of the event - Uint32 getStep(); - - ///Returns the x-cordinate - Sint16 getX(); - - ///Returns the y-cordinate - Sint16 getY(); - - ///Returns the event type - virtual Uint8 getEventType()=0; + GameEventType getEventType() const { return type; } + Uint32 getStep() const { return step; } + Sint16 getX() const { return x; } + Sint16 getY() const { return y; } private: + GameEvent(GameEventType type, Uint32 step, Sint16 x, Sint16 y, Uint32 typeNum, Uint8 otherTeamNumber); + + GameEventType type; Uint32 step; Sint16 x; Sint16 y; + // Unit type for GEUnitUnderAttack; building shortTypeNum for GEBuildingUnderAttack + // and GEBuildingCompleted; unused for conversion events. + Uint32 typeNum; + // Other team's index in Game::teams for conversion events; unused otherwise. + Uint8 otherTeamNumber; }; - - - - -class UnitUnderAttackEvent : public GameEvent -{ -public: - ///Constructs a UnitUnderAttack event - UnitUnderAttackEvent(Uint32 step, Sint16 x, Sint16 y, Uint32 type); - - ///This formats a user-readable message, including translating the message - std::string formatMessage(); - - ///Returns the color of the message after its formatted - GAGCore::Color formatColor(); - - ///Returns the event type - Uint8 getEventType(); -private: - Uint32 type; -}; - - - - -class UnitLostConversionEvent : public GameEvent -{ -public: - ///Constructs a UnitLostConversion event - UnitLostConversionEvent(Uint32 step, Sint16 x, Sint16 y, const std::string& teamName); - - ///This formats a user-readable message, including translating the message - std::string formatMessage(); - - ///Returns the color of the message after its formatted - GAGCore::Color formatColor(); - - ///Returns the event type - Uint8 getEventType(); -private: - std::string teamName; -}; - - - - -class UnitGainedConversionEvent : public GameEvent -{ -public: - ///Constructs a UnitGainedConversion event - UnitGainedConversionEvent(Uint32 step, Sint16 x, Sint16 y, const std::string& teamName); - - ///This formats a user-readable message, including translating the message - std::string formatMessage(); - - ///Returns the color of the message after its formatted - GAGCore::Color formatColor(); - - ///Returns the event type - Uint8 getEventType(); -private: - std::string teamName; -}; - - - - -class BuildingUnderAttackEvent : public GameEvent -{ -public: - ///Constructs a BuildingUnderAttack event - BuildingUnderAttackEvent(Uint32 step, Sint16 x, Sint16 y, Uint8 type); - - ///This formats a user-readable message, including translating the message - std::string formatMessage(); - - ///Returns the color of the message after its formatted - GAGCore::Color formatColor(); - - ///Returns the event type - Uint8 getEventType(); -private: - Uint8 type; -}; - - - - -class BuildingCompletedEvent : public GameEvent -{ -public: - ///Constructs a BuildingCompleted event - BuildingCompletedEvent(Uint32 step, Sint16 x, Sint16 y, Uint8 type); - - ///This formats a user-readable message, including translating the message - std::string formatMessage(); - - ///Returns the color of the message after its formatted - GAGCore::Color formatColor(); - - ///Returns the event type - Uint8 getEventType(); -private: - Uint8 type; -}; - - - -//event_append_marker - - - -#endif diff --git a/src/GameGUI.cpp b/src/GameGUI.cpp deleted file mode 100644 index 897fb6152..000000000 --- a/src/GameGUI.cpp +++ /dev/null @@ -1,5366 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Game.h" -#include "GameGUI.h" -#include "GameGUIDialog.h" -#include "GameGUILoadSave.h" -#include "GameUtilities.h" -#include "GlobalContainer.h" -#include "Unit.h" -#include "Utilities.h" -#include "IRC.h" -#include "SoundMixer.h" -#include "VoiceRecorder.h" -#include "GameGUIKeyActions.h" -#include "Player.h" -#include "ReplayReader.h" -#include "ReplayWriter.h" -#include "config.h" -#include "Order.h" - -#include - -#define TYPING_INPUT_BASE_INC 7 -#define TYPING_INPUT_MAX_POS 46 - -// these values are manually layouted for cuteste perception -#define YPOS_BASE_DEFAULT 180 -#define YPOS_BASE_CONSTRUCTION (YPOS_BASE_DEFAULT + 5) -#define YPOS_BASE_FLAG (YPOS_BASE_DEFAULT + 5) -#define YPOS_BASE_STAT YPOS_BASE_DEFAULT -#define YPOS_BASE_BUILDING (YPOS_BASE_DEFAULT + 10) -#define YPOS_BASE_UNIT (YPOS_BASE_DEFAULT + 10) -#define YPOS_BASE_RESSOURCE YPOS_BASE_DEFAULT - -#define YOFFSET_NAME 28 -#define YOFFSET_ICON 52 -#define YOFFSET_CARYING 34 -#define YOFFSET_BAR 32 -#define YOFFSET_INFOS 12 -#define YOFFSET_TOWER 22 - -#define YOFFSET_B_SEP 6 - -#define YOFFSET_TEXT_BAR 16 -#define YOFFSET_TEXT_PARA 14 -#define YOFFSET_TEXT_LINE 12 - -#define YOFFSET_PROGRESS_BAR 10 - -#define YOFFSET_BRUSH 56 - -// The sidebar on the right -#define RIGHT_MENU_WIDTH 160 -#define RIGHT_MENU_HALF_WIDTH (RIGHT_MENU_WIDTH / 2) -#define RIGHT_MENU_OFFSET ((RIGHT_MENU_WIDTH -128)/2) -#define RIGHT_MENU_RIGHT_OFFSET (RIGHT_MENU_WIDTH - RIGHT_MENU_OFFSET) - -// Icons for main menu, alliance and objectives buttons. -#define IGM_ICON_HEIGHT 36 -#define IGM_MAIN_MENU_ICON_Y 0 -#define IGM_ALLIANCE_ICON_Y IGM_ICON_HEIGHT -#define IGM_OBJECTIVES_ICON_Y (IGM_ICON_HEIGHT * 2) - -// Settings for the right sidebar in replays -#define REPLAY_PANEL_XOFFSET 25 -#define REPLAY_PANEL_YOFFSET (YPOS_BASE_STAT+10) -#define REPLAY_PANEL_SPACE_BETWEEN_OPTIONS 22 -#define REPLAY_PANEL_PLAYERLIST_YOFFSET (5*REPLAY_PANEL_SPACE_BETWEEN_OPTIONS+5) - -// The actual progress bar (including buttons) -#define REPLAY_PROGRESS_BAR_X_OFFSET 4 -#define REPLAY_PROGRESS_BAR_Y_OFFSET 3 -#define REPLAY_PROGRESS_BAR_BUTTON_WIDTH 15 -#define REPLAY_PROGRESS_BAR_CAP_WIDTH 10 -#define REPLAY_PROGRESS_BAR_NUM_BUTTONS 3 - -// The panel around the actual progress bar -#define REPLAY_BAR_WIDTH (globalContainer->settings.screenWidth - RIGHT_MENU_WIDTH - 4) -#define REPLAY_BAR_HEIGHT (2*REPLAY_PROGRESS_BAR_Y_OFFSET + 20) -#define REPLAY_BAR_Y (globalContainer->settings.screenHeight - REPLAY_BAR_HEIGHT) -#define REPLAY_BAR_TIMER_X (REPLAY_PROGRESS_BAR_X_OFFSET + REPLAY_PROGRESS_BAR_CAP_WIDTH + 5) - -// Sprites for the replay bar -#define REPLAY_BAR_LEFT_CAP_SPRITE 56 -#define REPLAY_BAR_RIGHT_CAP_SPRITE 57 -#define REPLAY_BAR_PLAY_BUTTON_SPRITE 51 -#define REPLAY_BAR_PLAY_BUTTON_ACTIVE_SPRITE 50 -#define REPLAY_BAR_PAUSE_BUTTON_SPRITE 53 -#define REPLAY_BAR_PAUSE_BUTTON_ACTIVE_SPRITE 52 -#define REPLAY_BAR_FAST_FORWARD_BUTTON_SPRITE 55 -#define REPLAY_BAR_FAST_FORWARD_BUTTON_ACTIVE_SPRITE 54 - -using boost::shared_ptr; -using boost::static_pointer_cast; - -enum GameGUIGfxId -{ - EXCHANGE_BUILDING_ICONS = 21 -}; - -//! The screen that contains the text input while typing message in game -class InGameTextInput:public OverlayScreen -{ -protected: - //! the text input widget - TextInput *textInput; - -public: - //! InGameTextInput constructor - InGameTextInput(GraphicContext *parentCtx); - //! InGameTextInput destructor - virtual ~InGameTextInput() { } - //! React on action from any widget (but there is only one anyway) - virtual void onAction(Widget *source, Action action, int par1, int par2); - //! Return the text typed - std::string getText(void) const { return textInput->getText(); } - //! Set the text - void setText(const std::string text) const { textInput->setText(text); } -}; - -InGameTextInput::InGameTextInput(GraphicContext *parentCtx) -:OverlayScreen(parentCtx, 492, 34) -{ - textInput=new TextInput(5, 5, 482, 24, ALIGN_LEFT, ALIGN_LEFT, "standard", "", true, 256); - addWidget(textInput); - dispatchInit(); -} - -void InGameTextInput::onAction(Widget *source, Action action, int par1, int par2) -{ - if (action==TEXT_VALIDATED) - { - endValue=0; - } -} - -GameGUI::GameGUI() - : keyboardManager(GameGUIShortcuts), game(this), toolManager(game, brush, defaultAssign, ghostManager), - minimap(globalContainer->runNoX, - RIGHT_MENU_WIDTH, // width of the menu - (globalContainer->runNoX ? 0 : globalContainer->gfx->getW()), // width of the screen - 20, // x offset - 10, // y offset - 128, // width - 128, //height - Minimap::ShowFOW), // minimap mode - - ghostManager(game) -{ -} - -GameGUI::~GameGUI() -{ - for (ParticleSet::iterator it = particles.begin(); it != particles.end(); ++it) - delete *it; -} - -void GameGUI::init() -{ - notmenu = false; - isRunning=true; - gamePaused=false; - hardPause=false; - exitGlobCompletely=false; - flushOutgoingAndExit=false; - drawHealthFoodBar=true; - drawPathLines=false; - drawAccessibilityAids=false; - viewportX=0; - viewportY=0; - mouseX=0; - mouseY=0; - displayMode=CONSTRUCTION_VIEW; - replayDisplayMode=RDM_REPLAY_VIEW; - selectionMode=NO_SELECTION; - selectionPushed=false; - selection.building = NULL; - selection.unit = NULL; - miniMapPushed=false; - putMark=false; - showUnitWorkingToBuilding=true; - chatMask=0xFFFFFFFF; - hasSpaceBeenClicked=false; - swallowSpaceKey=false; - scriptTextUpdated = false; - - viewportSpeedX=0; - viewportSpeedY=0; - - showStarvingMap=false; - showDamagedMap=false; - showDefenseMap=false; - showFertilityMap=false; - - inGameMenu=IGM_NONE; - gameMenuScreen=NULL; - typingInputScreen=NULL; - scrollableText=NULL; - typingInputScreenPos=0; - - eventGoTypeIterator = 0; - localTeam=NULL; - teamStats=NULL; - - hasEndOfGameDialogBeenShown=false; - panPushed=false; - - buildingsChoiceName.clear(); - buildingsChoiceName.push_back("swarm"); - buildingsChoiceName.push_back("inn"); - buildingsChoiceName.push_back("hospital"); - buildingsChoiceName.push_back("racetrack"); - buildingsChoiceName.push_back("swimmingpool"); - buildingsChoiceName.push_back("barracks"); - buildingsChoiceName.push_back("school"); - buildingsChoiceName.push_back("defencetower"); - buildingsChoiceName.push_back("stonewall"); - buildingsChoiceName.push_back("market"); - - buildingsChoiceState.resize(buildingsChoiceName.size(), true); - - flagsChoiceName.clear(); - flagsChoiceName.push_back("explorationflag"); - flagsChoiceName.push_back("warflag"); - flagsChoiceName.push_back("clearingflag"); - flagsChoiceState.resize(flagsChoiceName.size(), true); - - hiddenGUIElements=0; - - for (size_t i=0; i=0); - assert(localTeamNo0); - assert(game.gameHeader.getNumberOfPlayers()stats; - - // recompute local forbidden and guard areas - game.map.computeLocalForbidden(localTeamNo); - game.map.computeLocalGuardArea(localTeamNo); - game.map.computeLocalClearArea(localTeamNo); - - // set default event position - eventGoPosX = localTeam->startPosX; - eventGoPosY = localTeam->startPosY; - eventGoType = 0; -} - -void GameGUI::adjustInitialViewport() -{ - assert(localTeam); - viewportX=localTeam->startPosX-((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); - viewportY=localTeam->startPosY-(globalContainer->gfx->getH()>>6); - viewportX&=game.map.getMaskW(); - viewportY&=game.map.getMaskH(); -} - -void GameGUI::moveFlag(int mx, int my, bool drop) -{ - if (globalContainer->replaying) return; - - int posX, posY; - Building* selBuild=selection.building; - game.map.cursorToBuildingPos(mx, my, selBuild->type->width, selBuild->type->height, &posX, &posY, viewportX, viewportY); - if ((selBuild->posXLocal!=posX) - ||(selBuild->posYLocal!=posY) - ||(drop && (selectionPushedPosX!=posX || selectionPushedPosY!=posY))) - { - Uint16 gid=selBuild->gid; - shared_ptr oms(new OrderMoveFlag(gid, posX, posY, drop)); - // First, we check if anoter move of the same flag is already in the "orderQueue". - bool found=false; - for (std::list >::iterator it=orderQueue.begin(); it!=orderQueue.end(); ++it) - { - if ( ((*it)->getOrderType()==ORDER_MOVE_FLAG)) - { - if(static_pointer_cast(*it)->gid==gid) - { - (*it) = oms; - found=true; - break; - } - } - } - if (!found) - orderQueue.push_back(oms); - selBuild->posXLocal=posX; - selBuild->posYLocal=posY; - } -} - -void GameGUI::dragStep(int mx, int my, int button) -{ - /* We used to use SDL_GetMouseState, like the following - commented-out code, but that was buggy and prevented - dragging from correctly going through intermediate cells. - It is vital to use the mouse position and button status as - it was at the time in the middle of the event stream, not - as it is now. So instead we make sure the correct data is - passed to us as a parameter. */ - // int mx, my; - // Uint8 button = SDL_GetMouseState(&mx, &my); - // fprintf (stderr, "enter dragStep: button: %d, mx: %d, selectionMode: %d\n", button, mx, selectionMode); - if ((button&SDL_BUTTON(1)) && (mxgfx->getW()-RIGHT_MENU_WIDTH)) - { - // Update flag - if (selectionMode == BUILDING_SELECTION) - { - Building* selBuild=selection.building; - if (selBuild && selectionPushed && (selBuild->type->isVirtual)) - moveFlag(mx, my, false); - } - // Update tool - else if (selectionMode==BRUSH_SELECTION || selectionMode==TOOL_SELECTION) - { - toolManager.handleMouseDrag(mx, my, localTeamNo, viewportX, viewportY); - } - } - // fprintf (stderr, "exit dragStep\n"); -} - -/* We need to keep track of the last recorded mouse position for use - in drag steps. We can't simply use SDL_GetMouseState to get this - information, because we need the information as it was in the - middle of the event stream. (There may be many later events we - have not yet processed.) */ -int lastMouseX = 0, lastMouseY = 0; // can't make these Uint16 because of SDL_GetMouseState -Uint16 lastMouseButtonState = 0; - -void GameGUI::step(void) -{ - SDL_Event event, mouseMotionEvent, windowEvent; - bool wasMouseMotion=false; - bool wasWindowEvent=false; - int oldMouseMapX = -1, oldMouseMapY = -1; // hopefully the values here will never matter - // we get all pending events but for mousemotion we only keep the last one - while (SDL_PollEvent(&event)) - { - if (event.type==SDL_MOUSEMOTION) - { - lastMouseX = event.motion.x; - lastMouseY = event.motion.y; - lastMouseButtonState = event.motion.state; - int mouseMapX, mouseMapY; - bool onViewport = (lastMouseX < globalContainer->gfx->getW()-RIGHT_MENU_WIDTH); - /* We keep track for each mouse motion event - of which map cell it corresponds to. When - dragging, we will use this to make sure we - process at least one event per map cell, - and only discard multiple events when they - are for the same map cell. This is - necessary to make dragging work correctly - when drawing areas with the brush. */ - if (onViewport) - { - game.map.cursorToBuildingPos (lastMouseX, lastMouseY, 1, 1, &mouseMapX, &mouseMapY, viewportX, viewportY); - } - else - { - /* We interpret all locations outside the - viewport as being equivalent, and - distinct from any map location. */ - mouseMapX = -1; - mouseMapY = -1; - } - // fprintf (stderr, "mouse motion: (lastMouseX,lastMouseY): (%d,%d), (mouseMapX,mouseMapY): (%d,%d), (oldMouseMapX,oldMouseMapY): (%d,%d)\n", lastMouseX, lastMouseY, mouseMapX, mouseMapY, oldMouseMapX, oldMouseMapY); - /* Make sure dragging does not skip over map cells by - processing the old stored event rather than throwing - it away. */ - if (wasMouseMotion - && (lastMouseButtonState & SDL_BUTTON(1)) // are we dragging? (should not be hard-coding this condition but should be abstract somehow) - && ((mouseMapX != oldMouseMapX) - || (mouseMapY != oldMouseMapY)) - ) - { - // fprintf (stderr, "processing old event instead of discarding it\n"); - processEvent(&mouseMotionEvent); - } - oldMouseMapX = mouseMapX; - oldMouseMapY = mouseMapY; - mouseMotionEvent=event; - wasMouseMotion=true; - } -# ifdef USE_OSX - else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_q && SDL_GetModState() & KMOD_GUI) - { - isRunning=false; - exitGlobCompletely=true; - } -# endif -# ifdef USE_WIN32 - else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_F4 && SDL_GetModState() & KMOD_ALT) - { - isRunning=false; - exitGlobCompletely=true; - } -# endif - else if ((event.type == SDL_MOUSEBUTTONDOWN) || (event.type == SDL_MOUSEBUTTONUP)) - { - lastMouseButtonState = SDL_GetMouseState (&lastMouseX, &lastMouseY); - /* We ignore what SDL_GetMouseState does to - lastMouseX and lastMouseY, because that may - reflect many subsequent events that we have not - yet processed. Technically, we shouldn't use - SDL_GetMouseState at all but should calculate the - button state by keeping track of what has - happened. However, I haven't had the programming - energy to do this, so I am cheating in the line - above. */ - lastMouseX = event.button.x; - lastMouseY = event.button.y; - processEvent (&event); - } - else if (event.type==SDL_WINDOWEVENT) - { - windowEvent=event; - wasWindowEvent=true; - } - else - { - processEvent(&event); - } - } - if (wasMouseMotion) - processEvent(&mouseMotionEvent); - if (wasWindowEvent) - processEvent(&windowEvent); - - flushScrollWheelOrders(); - - int oldViewportX = viewportX; - int oldViewportY = viewportY; - - viewportX += game.map.getW(); - viewportY += game.map.getH(); - handleKeyAlways(); - viewportX += viewportSpeedX; - viewportY += viewportSpeedY; - viewportX &= game.map.getMaskW(); - viewportY &= game.map.getMaskH(); - - if ((viewportX!=oldViewportX) || (viewportY!=oldViewportY)) - { - dragStep(lastMouseX, lastMouseY, lastMouseButtonState); - moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); - } - - assert(localTeam); - boost::shared_ptr gevent = localTeam->getEvent(); - while(gevent) - { - Color c = gevent->formatColor(); - addMessage(c, gevent->formatMessage(), false); - eventGoPosX = gevent->getX(); - eventGoPosY = gevent->getY(); - eventGoType = gevent->getEventType(); - gevent = localTeam->getEvent(); - } - - // voice step - boost::shared_ptr orderVoiceData; - while ((orderVoiceData = globalContainer->voiceRecorder->getNextOrder()) != NULL) - { - orderVoiceData->recepientsMask = chatMask ^ (chatMask & (1< messages; - setMultiLine(game.sgslScript.textShown, &messages, " "); - - ///Add each line as a seperate message to the message manager. - ///Must be done backwards to appear in the right order - for (int i=messages.size()-1; i>=0; i--) - { - messageManager.addChatMessage(InGameMessage(messages[i], Color(255, 255, 255), 0)); - } - - previousSGSLText = game.sgslScript.textShown; - } - - // Check if the text being displayed has changed, and if it has, add it to the history box - if (scriptTextUpdated) - { - // Split into one per line - std::vector messages; - setMultiLine(scriptText, &messages, " "); - - // Add each line as a seperate message to the message manager. - // Must be done backwards to appear in the right order - for (int i=messages.size()-1; i>=0; i--) - { - messageManager.addChatMessage(InGameMessage(messages[i], Color(255, 255, 255), 0)); - } - - scriptTextUpdated = false; - } - - // music step - musicStep(); - - boost::shared_ptr order = toolManager.getOrder(); - while(order) - { - orderQueue.push_back(order); - order = toolManager.getOrder(); - } - - ///This shows the mission briefing at the begginning of the mission - if(game.stepCounter == 12) - { - if(game.missionBriefing != "") - { - if(gameMenuScreen) - { - delete gameMenuScreen; - gameMenuScreen=NULL; - } - inGameMenu=IGM_OBJECTIVES; - gameMenuScreen = new InGameObjectivesScreen(this, true); - } - } - - if(game.stepCounter % 25 == 1) - { - if(showStarvingMap) - overlay.compute(game, OverlayArea::Starving, localTeamNo); - else if(showDamagedMap) - overlay.compute(game, OverlayArea::Damage, localTeamNo); - else if(showDefenseMap) - overlay.compute(game, OverlayArea::Defence, localTeamNo); - else if(showFertilityMap) - overlay.compute(game, OverlayArea::Fertility, localTeamNo); - } - - // do we have won or lost conditions - checkWonConditions(); - - if (game.anyPlayerWaited) // TODO: warning valgrind - game.anyPlayerWaitedTimeFor++; -} - -void GameGUI::musicStep(void) -{ - static unsigned warTimeout = 0; - static unsigned buildingTimeout = 0; - - // something bad happened - if (localTeam->wasRecentEvent(GEUnitUnderAttack) || - localTeam->wasRecentEvent(GEUnitLostConversion) || - localTeam->wasRecentEvent(GEBuildingUnderAttack)) - { - warTimeout = 220; - globalContainer->mix->setNextTrack(4, true); - } - - // something good happened - if (localTeam->wasRecentEvent(GEUnitGainedConversion) || - localTeam->wasRecentEvent(GEBuildingCompleted)) - { - buildingTimeout = 220; - globalContainer->mix->setNextTrack(3, true); - } - - // if end of special thing - if ((buildingTimeout == 1) || (warTimeout == 1)) - globalContainer->mix->setNextTrack(2, true); - - // decay variables - if (warTimeout > 0) - warTimeout--; - if (buildingTimeout > 0) - buildingTimeout--; -} - -void GameGUI::syncStep(void) -{ - assert(localTeam); - assert(teamStats); - - if ((game.stepCounter&255) == 79) - { - const std::string name = Toolkit::getStringTable()->getString("[auto save]"); - std::string fileName = glob2NameToFilename("games", name, "game"); - OutputStream *stream = new BinaryOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(fileName)); - if (stream->isEndOfStream()) - { - std::cerr << "GameGUI::syncStep : can't open autosave file " << name << " for writing" << std::endl; - } - else - { - save(stream, name); - } - delete stream; - } -} - -bool GameGUI::processScrollableWidget(SDL_Event *event) -{ - scrollableText->translateAndProcessEvent(event); - return true; -} - -bool GameGUI::processGameMenu(SDL_Event *event) -{ - gameMenuScreen->translateAndProcessEvent(event); - switch (inGameMenu) - { - case IGM_MAIN: - { - switch (gameMenuScreen->endValue) - { - case InGameMainScreen::LOAD_GAME: - { - delete gameMenuScreen; - inGameMenu=IGM_LOAD; - if (globalContainer->replaying) - gameMenuScreen = new LoadSaveScreen("replays", "replay", true, std::string(Toolkit::getStringTable()->getString("[load replay]")), defualtGameSaveName.c_str(), glob2FilenameToName, glob2NameToFilename); - else - gameMenuScreen = new LoadSaveScreen("games", "game", true, false, defualtGameSaveName.c_str(), glob2FilenameToName, glob2NameToFilename); - return true; - } - break; - case InGameMainScreen::SAVE_GAME: - { - delete gameMenuScreen; - inGameMenu=IGM_SAVE; - gameMenuScreen = new LoadSaveScreen("games", "game", false, false, defualtGameSaveName.c_str(), glob2FilenameToName, glob2NameToFilename); - return true; - } - break; - case InGameMainScreen::OPTIONS: - { - delete gameMenuScreen; - inGameMenu=IGM_OPTION; - gameMenuScreen = new InGameOptionScreen(this); - return true; - } - break; - case InGameMainScreen::RETURN_GAME: - { - delete gameMenuScreen; - inGameMenu=IGM_NONE; - gameMenuScreen=NULL; - return true; - } - break; - case InGameMainScreen::QUIT_GAME: - { - delete gameMenuScreen; - inGameMenu=IGM_NONE; - gameMenuScreen=NULL; - orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); - flushOutgoingAndExit=true; - return true; - } - break; - default: - return false; - } - } - - case IGM_ALLIANCE: - { - switch (gameMenuScreen->endValue) - { - case InGameAllianceScreen::OK : - { - Uint32 playerMask[5]; - Uint32 teamMask[5]; - playerMask[0]=((InGameAllianceScreen *)gameMenuScreen)->getAlliedMask(); - playerMask[1]=((InGameAllianceScreen *)gameMenuScreen)->getEnemyMask(); - playerMask[2]=((InGameAllianceScreen *)gameMenuScreen)->getExchangeVisionMask(); - playerMask[3]=((InGameAllianceScreen *)gameMenuScreen)->getFoodVisionMask(); - playerMask[4]=((InGameAllianceScreen *)gameMenuScreen)->getOtherVisionMask(); - teamMask[0]=teamMask[1]=teamMask[2]=teamMask[3]=teamMask[4]=0; - - // mask are for players, we need to convert them to team. - for (int pi=0; piteamNumber; - for (int mi=0; mi<5; mi++) - { - if (playerMask[mi]&(1<playersMask==0) - teamMask[1]|=(1<(new SetAllianceOrder(localTeamNo, - teamMask[0], teamMask[1], teamMask[2], teamMask[3], teamMask[4]))); - chatMask=((InGameAllianceScreen *)gameMenuScreen)->getChatMask(); - inGameMenu=IGM_NONE; - delete gameMenuScreen; - gameMenuScreen=NULL; - } - return true; - - default: - return false; - } - } - - case IGM_OPTION: - { - if (gameMenuScreen->endValue == InGameOptionScreen::OK) - { - inGameMenu=IGM_NONE; - delete gameMenuScreen; - gameMenuScreen=NULL; - return true; - } - else - { - return false; - } - } - - case IGM_OBJECTIVES: - { - if (gameMenuScreen->endValue == InGameObjectivesScreen::OK) - { - inGameMenu=IGM_NONE; - delete gameMenuScreen; - gameMenuScreen=NULL; - return true; - } - else - { - return false; - } - } - - case IGM_LOAD: - case IGM_SAVE: - { - switch (gameMenuScreen->endValue) - { - case LoadSaveScreen::OK: - { - std::string locationName=((LoadSaveScreen *)gameMenuScreen)->getFileName(); - if (inGameMenu==IGM_LOAD) - { - toLoadGameFileName = locationName; - orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); - flushOutgoingAndExit=true; - } - else - { - defualtGameSaveName=((LoadSaveScreen *)gameMenuScreen)->getName(); - OutputStream *stream = new BinaryOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(locationName)); - if (stream->isEndOfStream()) - { - std::cerr << "GGU : Can't save map " << locationName << std::endl; - } - else - { - const std::string name = ((LoadSaveScreen *)gameMenuScreen)->getName(); - assert(name.size()); - save(stream, name); - } - delete stream; - } - } - - case LoadSaveScreen::CANCEL: - inGameMenu=IGM_NONE; - delete gameMenuScreen; - gameMenuScreen=NULL; - return true; - - default: - return false; - } - } - - case IGM_END_OF_GAME: - { - switch (gameMenuScreen->endValue) - { - case InGameEndOfGameScreen::QUIT: - orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); - flushOutgoingAndExit=true; - - case InGameEndOfGameScreen::CONTINUE: - inGameMenu=IGM_NONE; - delete gameMenuScreen; - gameMenuScreen=NULL; - return true; - - case InGameEndOfGameScreen::WATCH_AGAIN: - assert(globalContainer->replaying); - inGameMenu=IGM_NONE; - delete gameMenuScreen; - gameMenuScreen=NULL; - toLoadGameFileName = globalContainer->replayFileName; - orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); - flushOutgoingAndExit=true; - return true; - - default: - return false; - } - } - - default: - return false; - } -} - -void GameGUI::processEvent(SDL_Event *event) -{ - // handle typing - if (typingInputScreen) - { - if ((event->type==SDL_KEYDOWN) && (event->key.keysym.sym == SDLK_ESCAPE)) - { - typingInputScreenInc=-TYPING_INPUT_BASE_INC; - typingInputScreen->endValue=1; - } - - typingInputScreen->translateAndProcessEvent(event); - - if (typingInputScreen->endValue==0) - { - //Interpret message - std::string message = typingInputScreen->getText(); - Uint32 nchatMask = chatMask; - if(message[0] == '/') - { - std::string name; - for(int i=1; message[i]!=' '; ++i) - name += message[i]; - message = message.substr(message.find(' ')+1); - if(name=="a") - { - nchatMask = localTeam->allies; - } - else - { - for(int i=0; ime; - break; - } - } - } - } - - if (!message.empty()) - { - orderQueue.push_back(shared_ptr(new MessageOrder(nchatMask, MessageOrder::NORMAL_MESSAGE_TYPE, message.c_str()))); - typingInputScreen->setText(""); - } - typingInputScreenInc=-TYPING_INPUT_BASE_INC; - typingInputScreen->endValue=1; - return; - } - } - - // the dump (debug) keys are always handled - if (event->type == SDL_KEYDOWN) - handleKeyDump(event->key); - - - if (event->type==SDL_MOUSEBUTTONUP) - { - int button=event->button.button; - if (button==SDL_BUTTON_MIDDLE) - { - panPushed=false; - } - } - - - if (event->type == SDL_MOUSEBUTTONDOWN) - { - int butx = event->button.x; - int buty = event->button.y; - - int leftEdge = globalContainer->gfx->getW() - RIGHT_MENU_WIDTH - IGM_ICON_HEIGHT/2; - int rightEdge = globalContainer->gfx->getW() - RIGHT_MENU_WIDTH + IGM_ICON_HEIGHT/2; - int menu = -1; - - if (event->button.button == SDL_BUTTON_LEFT - && (butx > leftEdge) - && (butx < rightEdge)) - { - if (buty < IGM_MAIN_MENU_ICON_Y + IGM_ICON_HEIGHT) - { - menu = IGM_MAIN; - } - if (!(hiddenGUIElements & HIDABLE_ALLIANCE) - && (buty > IGM_ALLIANCE_ICON_Y) - && (buty < IGM_ALLIANCE_ICON_Y + IGM_ICON_HEIGHT)) - { - menu = IGM_ALLIANCE; - } - if ((buty > IGM_OBJECTIVES_ICON_Y) - && (buty < IGM_OBJECTIVES_ICON_Y + IGM_ICON_HEIGHT)) - { - menu = IGM_OBJECTIVES; - } - - if (menu != -1) - { - if (inGameMenu != IGM_NONE) - { - delete gameMenuScreen; - gameMenuScreen = NULL; - } - if (inGameMenu == menu) - inGameMenu = IGM_NONE; - else - inGameMenu = static_cast(menu); - - switch (menu) - { - case IGM_MAIN: - gameMenuScreen = new InGameMainScreen(globalContainer->replaying); - break; - case IGM_ALLIANCE: - gameMenuScreen = new InGameAllianceScreen(this); - break; - case IGM_OBJECTIVES: - gameMenuScreen = new InGameObjectivesScreen(this, false); - break; - default: - assert(false); - } - } - } - } - - - // if there is a menu he get events first - if (inGameMenu) - { - notmenu=true; - processGameMenu(event); - } - else - { - notmenu=false; - if (scrollableText) - { - processScrollableWidget(event); - } - if (event->type==SDL_KEYDOWN) - { - handleKey(event->key.keysym, true); - } - else if (event->type==SDL_KEYUP) - { - handleKey(event->key.keysym, false); - } - else if (event->type==SDL_MOUSEBUTTONDOWN) - { - int button=event->button.button; - //int state=event->button.state; - - if (button==SDL_BUTTON_RIGHT) - { - handleRightClick(); - } - else if (button==SDL_BUTTON_LEFT) - { - if (event->button.x>globalContainer->gfx->getW()-RIGHT_MENU_WIDTH) - handleMenuClick(event->button.x-globalContainer->gfx->getW()+RIGHT_MENU_WIDTH, event->button.y, event->button.button); - else if (globalContainer->replaying && event->button.y >= REPLAY_BAR_Y) - handleReplayProgressBarClick(event->button.x, event->button.y, event->button.button); - else - handleMapClick(event->button.x, event->button.y, event->button.button); - } - else if (button==SDL_BUTTON_MIDDLE) - { - if ((selectionMode==BUILDING_SELECTION) && (globalContainer->gfx->getW()-event->button.xverbose=(selBuild->verbose+1)%5; -// printf("building gid=(%d)\n", selBuild->gid); -// if (selBuild->verbose==0) -// printf(" verbose off\n"); -// else if (selBuild->verbose==1 || selBuild->verbose==2) -// printf(" verbose global [%d]\n", selBuild->verbose&1); -// else if (selBuild->verbose==3 || selBuild->verbose==4) -// printf(" verbose local [%d]\n", selBuild->verbose&1); -// else -// assert(false); -// printf(" pos=(%d, %d)\n", selBuild->posX, selBuild->posY); -// printf(" dirtyLocalGradient=[%d, %d]\n", selBuild->dirtyLocalGradient[0], selBuild->dirtyLocalGradient[1]); -// printf(" globalGradient=[%p, %p]\n", selBuild->globalGradient[0], selBuild->globalGradient[1]); -// printf(" locked=[%d, %d]\n", selBuild->locked[0], selBuild->locked[1]); - - } - else - { - // Enable panning - panPushed=true; - panMouseX=event->button.x; - panMouseY=event->button.y; - panViewX=viewportX; - panViewY=viewportY; - } - } - else if (button==4) - { - scrollWheelChanges += 1; - - } - else if (button==5) - { - scrollWheelChanges -= 1; - } - } - else if (event->type==SDL_MOUSEBUTTONUP) - { - int button=event->button.button; - if ((button==SDL_BUTTON_LEFT) && (event->button.x < globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)) - { - if ((selectionMode==BUILDING_SELECTION) && selectionPushed && selection.building->type->isVirtual) - { - // update flag - moveFlag(event->button.x, event->button.y, true); - } - // We send the order - else if (selectionMode==BRUSH_SELECTION || selectionMode==TOOL_SELECTION) - { - toolManager.handleMouseUp(event->button.x, event->button.y, localTeamNo, viewportX, viewportY); - } - } - miniMapPushed=false; - selectionPushed=false; - panPushed=false; - // showUnitWorkingToBuilding=false; - } - else if (event->type==SDL_MOUSEWHEEL) - { - int factor = event->wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1; - scrollWheelChanges += event->wheel.y * factor; - } - } - - if (event->type==SDL_MOUSEMOTION) - { - handleMouseMotion(event->motion.x, event->motion.y, event->motion.state); - } - else if (event->type==SDL_WINDOWEVENT) - { - handleActivation(event->window.data1, event->window.data2); - } - else if (event->type==SDL_QUIT) - { - exitGlobCompletely=true; - orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); - flushOutgoingAndExit=true; - } - else if (event->type==SDL_WINDOWEVENT_RESIZED) - { - // FIXME: window resize is broken - /*int newW=event->window.data1; - int newH=event->window.data2; - newW&=(~(0x1F)); - newH&=(~(0x1F)); - if (newW<640) - newW=640; - if (newH<480) - newH=480; - printf("New size : %dx%d\n", newW, newH); - globalContainer->gfx->setRes(newW, newH);*/ - } -} - -void GameGUI::handleActivation(Uint8 state, Uint8 gain) -{ - if (gain==0) - { - viewportSpeedX=viewportSpeedY=0; - } -} - -void GameGUI::handleRightClick(void) -{ - // We cycle between views: - if (selectionMode==NO_SELECTION) - { - nextDisplayMode(); - } - // We deselect all, we want no tools activated: - else - { - clearSelection(); - } -} - -void GameGUI::nextDisplayMode(void) -{ - if (globalContainer->replaying) - { - replayDisplayMode=ReplayDisplayMode((replayDisplayMode + 1) % RDM_NB_VIEWS); - return; - } - - int t=0; - do - { - displayMode=DisplayMode((displayMode + 1) % NB_VIEWS); - if ((t++)==4) - { - displayMode=NB_VIEWS; - break; - } - } while ((1<<((int)displayMode)) & hiddenGUIElements); -} - -void GameGUI::repairAndUpgradeBuilding(Building *building, bool repair, bool upgrade) -{ - BuildingType *buildingType = building->type; - - // building site can't be repaired nor upgraded - if (buildingType->isBuildingSite) - return; - // we can upgrade or repair only building from our team - if (building->owner->teamNumber != localTeamNo) - return; - int typeNum = building->typeNum + 1; //determines type of updated building - int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum); - int repairUnitWorking = defaultAssign.getDefaultAssignedUnits(building->typeNum - 1); - int unitWorkingFuture = defaultAssign.getDefaultAssignedUnits(typeNum+1); - if ((building->hp < buildingType->hpMax) && repair) - { - // repair - if ((building->type->regenerationSpeed == 0) && - (building->isHardSpaceForBuildingSite(Building::REPAIR)) && - (localTeam->maxBuildLevel() >= buildingType->level)) - orderQueue.push_back(shared_ptr(new OrderConstruction(building->gid, repairUnitWorking, building->maxUnitWorkingLocal))); - } - else if (upgrade) - { - // upgrade - if ((buildingType->nextLevel != -1) && - (building->isHardSpaceForBuildingSite(Building::UPGRADE)) && - (localTeam->maxBuildLevel() > buildingType->level)) - orderQueue.push_back(shared_ptr(new OrderConstruction(building->gid, unitWorking, unitWorkingFuture))); - } -} - -void GameGUI::handleKey(SDL_Keysym key, bool pressed) -{ - - int modifier; - - if (pressed) - modifier=1; - else - modifier=-1; - - if (typingInputScreen == NULL) - { - if(key.sym == SDLK_SPACE && pressed && swallowSpaceKey) - { - setIsSpaceSet(true); - } - else - { - Uint32 action_t = keyboardManager.getAction(KeyPress(key, pressed)); - switch(action_t) - { - case GameGUIKeyActions::DoNothing: - { - } - break; - case GameGUIKeyActions::ShowMainMenu: - { - if (inGameMenu==IGM_NONE) - { - gameMenuScreen=new InGameMainScreen(globalContainer->replaying); - inGameMenu=IGM_MAIN; - } - } - break; - case GameGUIKeyActions::UpgradeBuilding: - { - if (selectionMode==BUILDING_SELECTION) - { - Building* selBuild = selection.building; - int typeNum = selBuild->typeNum; //determines type of updated building - int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum - 1); - if (selBuild->constructionResultState == Building::UPGRADE) - orderQueue.push_back(shared_ptr(new OrderCancelConstruction(selBuild->gid, unitWorking))); - else if ((selBuild->constructionResultState==Building::NO_CONSTRUCTION) && (selBuild->buildingState==Building::ALIVE)) - repairAndUpgradeBuilding(selBuild, false, true); - } - } - break; - case GameGUIKeyActions::IncreaseUnitsWorking: - { - if (selectionMode==BUILDING_SELECTION) - { - Building* selBuild=selection.building; - if ((selBuild->owner->teamNumber==localTeamNo) && (selBuild->type->maxUnitWorking) && (selBuild->maxUnitWorkingLocalmaxUnitWorkingLocal+1); - selBuild->maxUnitWorkingLocal = nbReq; - orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, nbReq))); - defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, nbReq); - } - } - } - break; - case GameGUIKeyActions::DecreaseUnitsWorking: - { - if (selectionMode==BUILDING_SELECTION) - { - Building* selBuild=selection.building; - if ((selBuild->owner->teamNumber==localTeamNo) && (selBuild->type->maxUnitWorking) && (selBuild->maxUnitWorkingLocal>0)) - { - int nbReq=std::max(0, selBuild->maxUnitWorkingLocal-1); - selBuild->maxUnitWorkingLocal = nbReq; - orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, nbReq))); - defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, nbReq); - } - } - } - break; - case GameGUIKeyActions::OpenChatBox: - { - typingInputScreen=new InGameTextInput(globalContainer->gfx); - typingInputScreenInc=TYPING_INPUT_BASE_INC; - typingInputScreenPos=0; - } - break; - case GameGUIKeyActions::IterateSelection: - { - iterateSelection(); - } - break; - case GameGUIKeyActions::GoToEvent: - { - eventGoTypeIterator = eventGoType; - int evX = eventGoPosX; - int evY = eventGoPosY; - - int oldViewportX = viewportX; - int oldViewportY = viewportY; - - int sw = globalContainer->gfx->getW(); - int sh = globalContainer->gfx->getH(); - viewportX = evX-((sw-RIGHT_MENU_WIDTH)>>6); - viewportY = evY-(sh>>6); - - moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); - } - break; - case GameGUIKeyActions::GoToHome: - { - int evX = localTeam->startPosX; - int evY = localTeam->startPosY; - - int oldViewportX = viewportX; - int oldViewportY = viewportY; - - int sw = globalContainer->gfx->getW(); - int sh = globalContainer->gfx->getH(); - viewportX = evX-((sw-RIGHT_MENU_WIDTH)>>6); - viewportY = evY-(sh>>6); - - moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); - } - break; - case GameGUIKeyActions::PauseGame: - { - orderQueue.push_back(shared_ptr(new PauseGameOrder(!gamePaused))); - } - break; - case GameGUIKeyActions::HardPause: - { - hardPause=!hardPause; - } - break; - case GameGUIKeyActions::ToggleDrawUnitPaths: - { - drawPathLines=!drawPathLines; - } - break; - case GameGUIKeyActions::DestroyBuilding: - { - if (selectionMode==BUILDING_SELECTION) - { - Building* selBuild=selection.building; - if (selBuild->owner->teamNumber==localTeamNo) - { - if (selBuild->buildingState==Building::WAITING_FOR_DESTRUCTION) - { - orderQueue.push_back(shared_ptr(new OrderCancelDelete(selBuild->gid))); - } - else if (selBuild->buildingState==Building::ALIVE) - { - orderQueue.push_back(shared_ptr(new OrderDelete(selBuild->gid))); - } - } - } - } - break; - case GameGUIKeyActions::RepairBuilding: - { - if (selectionMode==BUILDING_SELECTION) - { - Building* selBuild = selection.building; - int typeNum = selBuild->typeNum; //determines type of updated building - int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum); - if (selBuild->constructionResultState == Building::REPAIR) - orderQueue.push_back(shared_ptr(new OrderCancelConstruction(selBuild->gid, unitWorking))); - else if ((selBuild->constructionResultState==Building::NO_CONSTRUCTION) && (selBuild->buildingState==Building::ALIVE)) - repairAndUpgradeBuilding(selBuild, true, false); - } - } - break; - case GameGUIKeyActions::ToggleDrawInformation: - { - drawHealthFoodBar=!drawHealthFoodBar; - } - break; - case GameGUIKeyActions::ToggleDrawAccessibilityAids: - { - drawAccessibilityAids = !drawAccessibilityAids; - } - break; - case GameGUIKeyActions::MarkMap: - { - putMark=true; - globalContainer->gfx->cursorManager.setNextType(CursorManager::CURSOR_MARK); - } - break; - case GameGUIKeyActions::ToggleRecordingVoice: - { - if (globalContainer->voiceRecorder->recordingNow) - globalContainer->voiceRecorder->stopRecording(); - else - globalContainer->voiceRecorder->startRecording(); - } - break; - case GameGUIKeyActions::ViewHistory: - { - if ( ! scrollableText) - scrollableText = messageManager.createScrollableHistoryScreen(); - else - { - delete scrollableText; - scrollableText=NULL; - } - } - break; - case GameGUIKeyActions::SelectConstructInn: - { - clearSelection(); - if (isBuildingEnabled(std::string("inn"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("inn")); - } - } - break; - case GameGUIKeyActions::SelectConstructSwarm: - { - clearSelection(); - if (isBuildingEnabled(std::string("swarm"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("swarm")); - } - } - break; - case GameGUIKeyActions::SelectConstructHospital: - { - clearSelection(); - if (isBuildingEnabled(std::string("hospital"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("hospital")); - } - } - break; - case GameGUIKeyActions::SelectConstructRacetrack: - { - clearSelection(); - if (isBuildingEnabled(std::string("racetrack"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("racetrack")); - } - } - break; - case GameGUIKeyActions::SelectConstructSwimmingPool: - { - clearSelection(); - if (isBuildingEnabled(std::string("swimmingpool"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("swimmingpool")); - } - } - break; - case GameGUIKeyActions::SelectConstructBarracks: - { - clearSelection(); - if (isBuildingEnabled(std::string("barracks"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("barracks")); - } - } - break; - case GameGUIKeyActions::SelectConstructSchool: - { - clearSelection(); - if (isBuildingEnabled(std::string("school"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("school")); - } - } - break; - case GameGUIKeyActions::SelectConstructDefenceTower: - { - clearSelection(); - if (isBuildingEnabled(std::string("defencetower"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("defencetower")); - } - } - break; - case GameGUIKeyActions::SelectConstructStoneWall: - { - clearSelection(); - if (isBuildingEnabled(std::string("stonewall"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("stonewall")); - } - } - break; - case GameGUIKeyActions::SelectConstructMarket: - { - clearSelection(); - if (isBuildingEnabled(std::string("market"))) - { - displayMode = CONSTRUCTION_VIEW; - setSelection(TOOL_SELECTION, (void *)("market")); - } - } - break; - case GameGUIKeyActions::SelectPlaceExplorationFlag: - { - clearSelection(); - if (isFlagEnabled(std::string("explorationflag"))) - { - displayMode = FLAG_VIEW; - setSelection(TOOL_SELECTION, (void*)("explorationflag")); - } - } - break; - case GameGUIKeyActions::SelectPlaceWarFlag: - { - clearSelection(); - if (isFlagEnabled(std::string("warflag"))) - { - displayMode = FLAG_VIEW; - setSelection(TOOL_SELECTION, (void*)("warflag")); - } - } - break; - case GameGUIKeyActions::SelectPlaceClearingFlag: - { - clearSelection(); - if (isFlagEnabled(std::string("clearingflag"))) - { - displayMode = FLAG_VIEW; - setSelection(TOOL_SELECTION, (void*)("clearingflag")); - } - } - break; - case GameGUIKeyActions::SelectPlaceForbiddenArea: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(GameGUIToolManager::Forbidden); - } - break; - case GameGUIKeyActions::SelectPlaceGuardArea: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(GameGUIToolManager::Guard); - } - break; - case GameGUIKeyActions::SelectPlaceClearingArea: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(GameGUIToolManager::Clearing); - } - break; - case GameGUIKeyActions::SwitchToAddingAreas: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setType(BrushTool::MODE_ADD); - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - case GameGUIKeyActions::SwitchToRemovingAreas: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setType(BrushTool::MODE_DEL); - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - case GameGUIKeyActions::SwitchToAreaBrush1: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setFigure(0); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - case GameGUIKeyActions::SwitchToAreaBrush2: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setFigure(1); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - case GameGUIKeyActions::SwitchToAreaBrush3: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setFigure(2); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - case GameGUIKeyActions::SwitchToAreaBrush4: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setFigure(3); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - case GameGUIKeyActions::SwitchToAreaBrush5: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setFigure(4); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - case GameGUIKeyActions::SwitchToAreaBrush6: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setFigure(5); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - case GameGUIKeyActions::SwitchToAreaBrush7: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setFigure(6); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - case GameGUIKeyActions::SwitchToAreaBrush8: - { - if(selectionMode != BRUSH_SELECTION) - clearSelection(); - brush.setFigure(7); - if (brush.getType() == BrushTool::MODE_NONE) - { - brush.setType(BrushTool::MODE_ADD); - } - displayMode = FLAG_VIEW; - setSelection(BRUSH_SELECTION); - toolManager.activateZoneTool(); - } - break; - } - } - } -} - -void GameGUI::handleKeyDump(SDL_KeyboardEvent key) -{ - if (key.keysym.sym == SDLK_PRINTSCREEN) - { - if ((key.keysym.mod & KMOD_SHIFT) != 0) - { - OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend("glob2.dump.txt")); - if (stream->isEndOfStream()) - { - std::cerr << "Can't dump full game memory to file glob2.dump.txt" << std::endl; - } - else - { - std::cerr << "Dump full game memory" << std::endl; - save(stream, "glob2.dump.txt"); - } - delete stream; - } - else - { - globalContainer->gfx->printScreen("screenshot.bmp"); - } - } -} - -void GameGUI::handleKeyAlways(void) -{ - SDL_PumpEvents(); - const Uint8 *keystate = SDL_GetKeyboardState(NULL); - if (notmenu == false) - { - SDL_Keymod modState = SDL_GetModState(); - int xMotion = 1; - int yMotion = 1; - /* We check that only Control is held to avoid accidentally - matching window manager bindings for switching windows - and/or desktops. */ - if (!(modState & (KMOD_ALT|KMOD_SHIFT))) - { - /* It violates good abstraction principles that I - have to do the calculations in the next two - lines. There should be methods that abstract - these computations. */ - if ((modState & KMOD_CTRL)) - { - /* We move by half screens if Control is held while - the arrow keys are held. So we shift by 6 - instead of 5. (If we shifted by 5, it would be - good to subtract 1 so that there would be a small - overlap between what is viewable both before and - after the motion.) */ - xMotion = ((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); - yMotion = ((globalContainer->gfx->getH())>>6); - } - else - { - /* We move the screen by one square at a time if CTRL key - is not being help */ - xMotion = 1; - yMotion = 1; - } - } - else if (modState) - { - /* Probably some keys held down as part of window - manager operations. */ - xMotion = 0; - yMotion = 0; - } - - if (keystate[SDL_SCANCODE_UP]) - viewportY -= yMotion; - if (keystate[SDL_SCANCODE_KP_8]) - viewportY -= yMotion; - if (keystate[SDL_SCANCODE_DOWN]) - viewportY += yMotion; - if (keystate[SDL_SCANCODE_KP_2]) - viewportY += yMotion; - if ((keystate[SDL_SCANCODE_LEFT]) && (typingInputScreen == NULL)) // we haave a test in handleKeyAlways, that's not very clean, but as every key check based on key states and not key events are here, it is much simpler and thus easier to understand and thus cleaner ;-) - viewportX -= xMotion; - if (keystate[SDL_SCANCODE_KP_4]) - viewportX -= xMotion; - if ((keystate[SDL_SCANCODE_RIGHT]) && (typingInputScreen == NULL)) // we haave a test in handleKeyAlways, that's not very clean, but as every key check based on key states and not key events are here, it is much simpler and thus easier to understand and thus cleaner ;-) - viewportX += xMotion; - if (keystate[SDL_SCANCODE_KP_6]) - viewportX += xMotion; - if (keystate[SDL_SCANCODE_KP_7]) - { - viewportX -= xMotion; - viewportY -= yMotion; - } - if (keystate[SDL_SCANCODE_KP_9]) - { - viewportX += xMotion; - viewportY -= yMotion; - } - if (keystate[SDL_SCANCODE_KP_1]) - { - viewportX -= xMotion; - viewportY += yMotion; - } - if (keystate[SDL_SCANCODE_KP_3]) - { - viewportX += xMotion; - viewportY += yMotion; - } - } -} - -void GameGUI::minimapMouseToPos(int mx, int my, int *cx, int *cy, bool forScreenViewport) -{ - minimap.convertToMap(mx, my, *cx, *cy); - - ///when for the screen viewport, center - if (forScreenViewport) - { - *cx-=((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); - *cy-=((globalContainer->gfx->getH())>>6); - } - -} - -void GameGUI::handleMouseMotion(int mx, int my, int button) -{ - const int scrollZoneWidth = 10; - game.mouseX=mouseX=mx; - game.mouseY=mouseY=my; - - int oldViewportX = viewportX; - int oldViewportY = viewportY; - - if (miniMapPushed) - { - minimapMouseToPos(mx, my, &viewportX, &viewportY, true); - } - else - { - if (mxglobalContainer->gfx->getW()-scrollZoneWidth) ) - viewportSpeedX=1; - else - viewportSpeedX=0; - - if (myglobalContainer->gfx->getH()-scrollZoneWidth) - viewportSpeedY=1; - else - viewportSpeedY=0; - } - - if (panPushed) - { - // handle paning - int dx = (mx-panMouseX)>>1; - int dy = (my-panMouseY)>>1; - viewportX = (panViewX+dx)&game.map.getMaskW(); - viewportY = (panViewY+dy)&game.map.getMaskH(); - } - - moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); - - dragStep(mx, my, button); -} - -void GameGUI::handleMapClick(int mx, int my, int button) -{ - if (selectionMode==TOOL_SELECTION) - { - toolManager.handleMouseDown(mx, my, localTeamNo, viewportX, viewportY); - - } - else if (selectionMode==BRUSH_SELECTION) - { - toolManager.handleMouseDown(mx, my, localTeamNo, viewportX, viewportY); - } - else if (putMark) - { - int markx, marky; - game.map.displayToMapCaseAligned(mx, my, &markx, &marky, viewportX, viewportY); - orderQueue.push_back(shared_ptr(new MapMarkOrder(localTeamNo, markx, marky))); - globalContainer->gfx->cursorManager.setNextType(CursorManager::CURSOR_NORMAL); - putMark = false; - } - else - { - int mapX, mapY; - game.map.displayToMapCaseAligned(mx, my, &mapX, &mapY, viewportX, viewportY); - selectionPushedPosX=mapX; - selectionPushedPosY=mapY; - // check for flag first - for (std::list::iterator virtualIt=localTeam->virtualBuildings.begin(); - virtualIt!=localTeam->virtualBuildings.end(); ++virtualIt) - { - Building *b=*virtualIt; - if ((b->posXLocal==mapX) && (b->posYLocal==mapY)) - { - setSelection(BUILDING_SELECTION, b); - selectionPushed=true; - return; - } - } - // then for unit - if (game.mouseUnit) - { - // a unit is selected: - setSelection(UNIT_SELECTION, game.mouseUnit); - selectionPushed = true; - // handle dump of unit characteristics - if ((SDL_GetModState() & KMOD_SHIFT) != 0) - { - OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend("unit.dump.txt")); - if (stream->isEndOfStream()) - { - std::cerr << "Can't dump unit to file unit.dump.txt" << std::endl; - } - else - { - std::cerr << "Dump unit " << game.mouseUnit->gid << " memory" << std::endl; - game.mouseUnit->save(stream); - game.mouseUnit->saveCrossRef(stream); - if (game.mouseUnit->attachedBuilding) - { - game.mouseUnit->attachedBuilding->save(stream); - game.mouseUnit->attachedBuilding->saveCrossRef(stream); - } - } - delete stream; - } - } - else - { - // then for building - Uint16 gbid=game.map.getBuilding(mapX, mapY); - if (gbid != NOGBID) - { - int buildingTeam=Building::GIDtoTeam(gbid); - // we can select for view buildings that are in shared vision, or any building in replay mode - if ((buildingTeam==localTeamNo) - || game.map.isFOWDiscovered(mapX, mapY, localTeam->me) - || (game.map.isMapDiscovered(mapX, mapY, localTeam->me) && (game.teams[buildingTeam]->allies&(1<replaying ) - { - setSelection(BUILDING_SELECTION, gbid); - selectionPushed=true; - // showUnitWorkingToBuilding=true; - // handle dump of building characteristics - if ((SDL_GetModState() & KMOD_SHIFT) != 0) - { - OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend("building.dump.txt")); - if (stream->isEndOfStream()) - { - std::cerr << "Can't dump unit to file building.dump.txt" << std::endl; - } - else - { - std::cerr << "Dump building " << selection.building->gid << " memory" << std::endl; - selection.building->save(stream); - selection.building->saveCrossRef(stream); - } - delete stream; - } - } - } - else - { - // and ressource - if (game.map.isRessource(mapX, mapY) && game.map.isMapDiscovered(mapX, mapY, localTeam->me)) - { - setSelection(RESSOURCE_SELECTION, mapY*game.map.getW()+mapX); - selectionPushed=true; - } - else - { - if (selectionMode == RESSOURCE_SELECTION) - clearSelection(); - } - } - } - } -} - -void GameGUI::handleMenuClick(int mx, int my, int button) -{ - // handle minimap - if (my<128 && mx > (RIGHT_MENU_OFFSET) && mx < RIGHT_MENU_WIDTH - RIGHT_MENU_OFFSET) - { - if (putMark) - { - int markx, marky; - minimapMouseToPos(globalContainer->gfx->getW() - RIGHT_MENU_WIDTH + mx, my, &markx, &marky, false); - orderQueue.push_back(shared_ptr(new MapMarkOrder(localTeamNo, markx, marky))); - globalContainer->gfx->cursorManager.setNextType(CursorManager::CURSOR_NORMAL); - putMark = false; - } - else - { - miniMapPushed=true; - int oldViewportX = viewportX; - int oldViewportY = viewportY; - minimapMouseToPos(globalContainer->gfx->getW() - RIGHT_MENU_WIDTH + mx, my, &viewportX, &viewportY, true); - moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); - } - } - // Check if one of the panel buttons has been clicked - else if (myreplaying) - { - int dec = (RIGHT_MENU_WIDTH-128)/2; - int dm=(mx-dec)/32; - if (!((1<owner->teamNumber!=localTeamNo) - return; - int ypos = YPOS_BASE_BUILDING + YOFFSET_NAME + YOFFSET_ICON + YOFFSET_B_SEP; - BuildingType *buildingType = selBuild->type; - int lmx = mx - RIGHT_MENU_OFFSET; // local mx - - // working bar - if (selBuild->type->maxUnitWorking) - { - if (((selBuild->owner->allies)&(1<ypos+YOFFSET_TEXT_BAR - && mybuildingState==Building::ALIVE - && lmx < 128) - { - int nbReq; - if (lmx<18) - { - if(selBuild->maxUnitWorkingLocal>0) - { - nbReq=(selBuild->maxUnitWorkingLocal-=1); - orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, nbReq))); - defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, nbReq); - } - } - else if (lmx<(128-18)) - { - nbReq=selBuild->maxUnitWorkingLocal=((lmx-18)*MAX_UNIT_WORKING)/(128-36); - orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, nbReq))); - defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, nbReq); - } - else - { - if(selBuild->maxUnitWorkingLocalmaxUnitWorkingLocal+=1); - orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, nbReq))); - defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, nbReq); - } - } - } - ypos += YOFFSET_BAR + YOFFSET_B_SEP; - } - - // priorities - if(selBuild->type->maxUnitWorking) - { - ypos += YOFFSET_B_SEP; - if (((selBuild->owner->allies)&(1<ypos+16 - && mybuildingState==Building::ALIVE) - { - int width = (128 - 8)/3; - - if(lmx>=0 && lmx<=12) - { - orderQueue.push_back(shared_ptr(new OrderChangePriority(selBuild->gid, -1))); - selBuild->priorityLocal = -1; - } - else if(lmx>=(width) && lmx<(width+12)) - { - orderQueue.push_back(shared_ptr(new OrderChangePriority(selBuild->gid, 0))); - selBuild->priorityLocal = 0; - } - else if(lmx>=(width*2) && lmx<=(width*2+12)) - { - orderQueue.push_back(shared_ptr(new OrderChangePriority(selBuild->gid, 1))); - selBuild->priorityLocal = 1; - } - } - ypos += YOFFSET_BAR+YOFFSET_B_SEP; - } - - // flag range bar - if (buildingType->defaultUnitStayRange) - { - if (((selBuild->owner->allies)&(1<ypos+YOFFSET_TEXT_BAR) - && (myunitStayRangeLocal>0) - { - nbReq=(selBuild->unitStayRangeLocal-=1); - orderQueue.push_back(shared_ptr(new OrderModifyFlag(selBuild->gid, nbReq))); - } - } - else if (lmxunitStayRangeLocal=((lmx-18)*(unsigned)selBuild->type->maxUnitStayRange)/(128-36); - orderQueue.push_back(shared_ptr(new OrderModifyFlag(selBuild->gid, nbReq))); - } - else - { - // TODO : check in orderQueue to avoid useless orders. - if (selBuild->unitStayRangeLocal < selBuild->type->maxUnitStayRange) - { - nbReq=(selBuild->unitStayRangeLocal+=1); - orderQueue.push_back(shared_ptr(new OrderModifyFlag(selBuild->gid, nbReq))); - } - } - } - ypos += YOFFSET_BAR+YOFFSET_B_SEP; - } - - // flags specific options: - if (((selBuild->owner->allies)&(1<10 - && lmx<22) - { - - // cleared ressources for clearing flags: - if (buildingType->type == "clearingflag") - { - ypos+=YOFFSET_B_SEP+YOFFSET_TEXT_PARA; - int j=0; - for (int i=0; iypos && myclearingRessourcesLocal[i]=!selBuild->clearingRessourcesLocal[i]; - orderQueue.push_back(shared_ptr(new OrderModifyClearingFlag(selBuild->gid, selBuild->clearingRessourcesLocal))); - } - - ypos+=YOFFSET_TEXT_PARA; - j++; - } - } - - if (buildingType->type == "warflag") - { - ypos+=YOFFSET_B_SEP+YOFFSET_TEXT_PARA; - for (int i=0; i<4; i++) - { - if (my>ypos && myminLevelToFlagLocal=i; - orderQueue.push_back(shared_ptr(new OrderModifyMinLevelToFlag(selBuild->gid, selBuild->minLevelToFlagLocal))); - } - - ypos+=YOFFSET_TEXT_PARA; - } - - } - - if (buildingType->type == "explorationflag") - { - // we use minLevelToFlag as an int which says what magic effect at minimum an explorer - // must be able to do to be accepted at this flag - // 0 == any explorer - // 1 == must be able to attack ground - ypos+=YOFFSET_B_SEP+YOFFSET_TEXT_PARA; - for (int i=0; i<2; i++) - { - if (my>ypos && myminLevelToFlagLocal=i; - orderQueue.push_back(shared_ptr(new OrderModifyMinLevelToFlag(selBuild->gid, selBuild->minLevelToFlagLocal))); - } - - ypos+=YOFFSET_TEXT_PARA; - } - } - } - - if (buildingType->armor) - ypos+=YOFFSET_TEXT_LINE; - if (buildingType->maxUnitInside) - ypos += YOFFSET_INFOS; - if (buildingType->shootDamage) - ypos += YOFFSET_TOWER; - ypos += YOFFSET_B_SEP; - - //Exchannge building - //Exchanging as a feature is broken - /* - if (selBuild->type->canExchange && ((selBuild->owner->allies)&(1<startY) && (my92) && (lmx<104)) - { - if (selBuild->receiveRessourceMask & (1<receiveRessourceMaskLocal &= ~(1<receiveRessourceMaskLocal |= (1<sendRessourceMaskLocal &= ~(1<(new OrderModifyExchange(selBuild->gid, selBuild->receiveRessourceMaskLocal, selBuild->sendRessourceMaskLocal))); - } - - if ((lmx>110) && (lmx<122)) - { - if (selBuild->sendRessourceMask & (1<sendRessourceMaskLocal &= ~(1<receiveRessourceMaskLocal &= ~(1<sendRessourceMaskLocal |= (1<(new OrderModifyExchange(selBuild->gid, selBuild->receiveRessourceMaskLocal, selBuild->sendRessourceMaskLocal))); - } - } - } - */ - // ressources in - unsigned j = 0; - for (unsigned i=0; iressourcesTypes.size(); i++) - { - if (buildingType->maxRessource[i]) - { - j++; - ypos += 11; - } - } - if (buildingType->maxBullets) - { - j++; - ypos += 11; - } - ypos+=5; - - if (selBuild->type->unitProductionTime) - { - ypos+=15; - for (int i=0; iypos+(i*20))&&(myratioLocal[i]>0) - { - selBuild->ratioLocal[i]--; - orderQueue.push_back(shared_ptr(new OrderModifySwarm(selBuild->gid, selBuild->ratioLocal))); - } - } - else if (lmx<(128-18)) - { - selBuild->ratioLocal[i]=((lmx-18)*MAX_RATIO_RANGE)/(128-36); - orderQueue.push_back(shared_ptr(new OrderModifySwarm(selBuild->gid, selBuild->ratioLocal))); - } - else - { - if (selBuild->ratioLocal[i]ratioLocal[i]++; - orderQueue.push_back(shared_ptr(new OrderModifySwarm(selBuild->gid, selBuild->ratioLocal))); - } - } - //printf("ratioLocal[%d]=%d\n", i, selBuild->ratioLocal[i]); - } - } - } - - if ((my>globalContainer->gfx->getH()-48) && (mygfx->getH()-32)) - { - if (selBuild->constructionResultState==Building::REPAIR) - { - int typeNum = selBuild->typeNum; //determines type of updated building - int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum); - orderQueue.push_back(shared_ptr(new OrderCancelConstruction(selBuild->gid, unitWorking))); - } - else if (selBuild->constructionResultState==Building::UPGRADE) - { - int typeNum = selBuild->typeNum; //determines type of updated building - int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum - 1); - orderQueue.push_back(shared_ptr(new OrderCancelConstruction(selBuild->gid, unitWorking))); - } - else if ((selBuild->constructionResultState==Building::NO_CONSTRUCTION) && (selBuild->buildingState==Building::ALIVE)) - { - repairAndUpgradeBuilding(selBuild, true, true); - } - } - - if ((my>globalContainer->gfx->getH()-24) && (mygfx->getH()-8)) - { - if (selBuild->buildingState==Building::WAITING_FOR_DESTRUCTION) - { - orderQueue.push_back(shared_ptr(new OrderCancelDelete(selBuild->gid))); - } - else if (selBuild->buildingState==Building::ALIVE) - { - orderQueue.push_back(shared_ptr(new OrderDelete(selBuild->gid))); - } - } - } - else if (selectionMode==UNIT_SELECTION) - { - Unit* selUnit=selection.unit; - assert(selUnit); - selUnit->verbose=!selUnit->verbose; - printf("unit gid=(%d) verbose %d\n", selUnit->gid, selUnit->verbose); - printf(" pos=(%d, %d)\n", selUnit->posX, selUnit->posY); - printf(" needToRecheckMedical=%d\n", selUnit->needToRecheckMedical); - printf(" medical=%d\n", selUnit->medical); - printf(" activity=%d\n", selUnit->activity); - printf(" displacement=%d\n", selUnit->displacement); - printf(" movement=%d\n", selUnit->movement); - printf(" action=%d\n", selUnit->action); - - if (selUnit->attachedBuilding) - printf(" attachedBuilding bgid=%d\n", selUnit->attachedBuilding->gid); - else - printf(" attachedBuilding NULL\n"); - printf(" destinationPurpose=%d\n", selUnit->destinationPurpose); - printf(" carriedRessource=%d\n", selUnit->carriedRessource); - } - else if ((displayMode==CONSTRUCTION_VIEW && !globalContainer->replaying)) - { - int xNum=mx/(RIGHT_MENU_WIDTH/2); - int yNum=(my-YPOS_BASE_CONSTRUCTION)/46; - int id=yNum*2+xNum; - if (id<(int)buildingsChoiceName.size()) - if (buildingsChoiceState[id]) - setSelection(TOOL_SELECTION, (void *)buildingsChoiceName[id].c_str()); - } - else if ((displayMode==FLAG_VIEW && !globalContainer->replaying)) - { - int dec = (RIGHT_MENU_WIDTH - 128)/2; - my -= YPOS_BASE_FLAG; - int nmx = mx - dec; - if (my > YOFFSET_BRUSH) - { - // set the selection - setSelection(BRUSH_SELECTION); - // change the brush type (forbidden, guard, clear) if necessary - if (my < YOFFSET_BRUSH+40) - { - if (nmx < 44) - toolManager.activateZoneTool(GameGUIToolManager::Forbidden); - else if (nmx < 84) - toolManager.activateZoneTool(GameGUIToolManager::Guard); - else if(nmx < 124) - toolManager.activateZoneTool(GameGUIToolManager::Clearing); - } - // anyway, update the tool - brush.handleClick(mx-dec, my-YOFFSET_BRUSH-40); - toolManager.activateZoneTool(); - } - else - { - int xNum=mx / (RIGHT_MENU_WIDTH/3); - int yNum=my / 46; - int id=yNum*3+xNum; - if (id<(int)flagsChoiceName.size()) - if (flagsChoiceState[id]) - setSelection(TOOL_SELECTION, (void*)flagsChoiceName[id].c_str()); - } - } - else if ((displayMode==STAT_GRAPH_VIEW && !globalContainer->replaying) || (replayDisplayMode==RDM_STAT_GRAPH_VIEW && globalContainer->replaying)) - { - if(mx > 8 && mx < 24) - { - // In replays, this menu bar is 15 pixels lower than usual to show "Watching: player-name" - int inc; - - if (globalContainer->replaying) inc = 15; - else inc = 0; - - if(my > YPOS_BASE_STAT+140+inc+64 && my < YPOS_BASE_STAT+140+inc+80) - { - showDamagedMap=false; - showDefenseMap=false; - showFertilityMap=false; - showStarvingMap=!showStarvingMap; - overlay.compute(game, OverlayArea::Starving, localTeamNo); - } - - if(my > YPOS_BASE_STAT+140+inc+88 && my < YPOS_BASE_STAT+140+inc+104) - { - showDamagedMap=!showDamagedMap; - showStarvingMap=false; - showDefenseMap=false; - showFertilityMap=false; - overlay.compute(game, OverlayArea::Damage, localTeamNo); - } - - if(my > YPOS_BASE_STAT+140+inc+112 && my < YPOS_BASE_STAT+140+inc+128) - { - showDefenseMap=!showDefenseMap; - showStarvingMap=false; - showDamagedMap=false; - showFertilityMap=false; - overlay.compute(game, OverlayArea::Defence, localTeamNo); - } - - if(my > YPOS_BASE_STAT+140+inc+136 && my < YPOS_BASE_STAT+140+inc+152) - { - showFertilityMap=!showFertilityMap; - showDefenseMap=false; - showStarvingMap=false; - showDamagedMap=false; - overlay.compute(game, OverlayArea::Fertility, localTeamNo); - } - } - } - else if (replayDisplayMode==RDM_REPLAY_VIEW && globalContainer->replaying) - { - int x = REPLAY_PANEL_XOFFSET; - int y = REPLAY_PANEL_YOFFSET; - int inc = REPLAY_PANEL_SPACE_BETWEEN_OPTIONS; - - if (mx > x && mx < x+20 && my > y+1*inc && my < y+1*inc + 20) - { - // Disable/show fog of war - globalContainer->replayShowFog = !globalContainer->replayShowFog; - - if (globalContainer->replayShowFog) minimap.setMinimapMode( Minimap::ShowFOW ); - else minimap.setMinimapMode( Minimap::HideFOW ); - } - if (mx > x && mx < x+20 && my > y+2*inc && my < y+2*inc + 20) - { - // Disable/enable combined vision - if (globalContainer->replayVisibleTeams == 0xFFFFFFFF) - { - globalContainer->replayVisibleTeams = localTeam->me; - } - else - { - globalContainer->replayVisibleTeams = 0xFFFFFFFF; - } - } - if (mx > x && mx < x+20 && my > y+3*inc && my < y+3*inc + 20) - { - // Show/hide player's areas - globalContainer->replayShowAreas = !globalContainer->replayShowAreas; - } - if (mx > x && mx < x+20 && my > y+4*inc && my < y+4*inc + 20) - { - // Show/hide flags - globalContainer->replayShowFlags = !globalContainer->replayShowFlags; - } - - for (int i = 0; i < game.teamsCount(); i++) - { - if (mx > x && mx < x+20 && my > y+REPLAY_PANEL_PLAYERLIST_YOFFSET+(i+1)*inc && my < y+REPLAY_PANEL_PLAYERLIST_YOFFSET+(i+1)*inc + 20) - { - localTeamNo = i; - - // Update everything to match this team number - adjustLocalTeam(); - - // Update localPlayer to the first player of this team - for (int j=0; jteamNumber == localTeamNo) - { - localPlayer = j; - break; - } - } - - // Update the visible players unless all players are visible - if (globalContainer->replayVisibleTeams != 0xFFFFFFFF) - { - globalContainer->replayVisibleTeams = localTeam->me; - } - } - } - } -} - -void GameGUI::handleReplayProgressBarClick(int mx, int my, int button) -{ - // Check the play, pause and fast-forward buttons - if (globalContainer->replaying) - { - int x = REPLAY_BAR_WIDTH - REPLAY_PROGRESS_BAR_X_OFFSET - REPLAY_PROGRESS_BAR_CAP_WIDTH; - int y = REPLAY_BAR_Y + REPLAY_PROGRESS_BAR_Y_OFFSET; - int inc = REPLAY_PROGRESS_BAR_BUTTON_WIDTH; - - if (my >= y && my <= y+20) - { - if (mx >= x-3*inc && mx <= x-2*inc) - { - // Play - gamePaused = false; - globalContainer->replayFastForward = false; - } - if (mx > x-2*inc && mx <= x-inc) - { - // Pause - gamePaused = true; - } - if (mx > x-inc && mx <= x) - { - // Fast-forward - gamePaused = false; - globalContainer->replayFastForward = true; - } - } - } -} - -boost::shared_ptr GameGUI::getOrder(void) -{ - boost::shared_ptr order; - if (orderQueue.size()==0) - order=shared_ptr(new NullOrder()); - else - { - order=orderQueue.front(); - orderQueue.pop_front(); - } - return order; -} - -void GameGUI::drawParticles(void) -{ - for (ParticleSet::iterator it = particles.begin(); it != particles.end(); ) - { - Particle* p = *it; - - // delete old particles - if (p->age >= p->lifeSpan) - { - ParticleSet::iterator oldIt = it; - ++it; - - delete *oldIt; - particles.erase(oldIt); - - continue; - } - else - p->age++; - - // do stupid physics - p->x += p->vx; - p->y += p->vy; - p->vx += p->ax; - p->vy += p->ay; - - // get image - float img = (float)p->startImg + (float)((p->endImg - p->startImg) * p->age) / ((float)p->lifeSpan + 1); - Uint8 alpha = (Uint8)(255.f * (img - truncf(img))); - int imgA = (int)img; - - globalContainer->particles->setBaseColor(p->color); - - // first image - int w = globalContainer->particles->getW(imgA); - int h = globalContainer->particles->getH(imgA); - globalContainer->gfx->drawSprite(p->x - 0.5f * w, p->y - 0.5f * h, globalContainer->particles, imgA, 255-alpha); - - // second image - int imgB = imgA + 1; - if (imgB < p->endImg) - { - w = globalContainer->particles->getW(imgA); - h = globalContainer->particles->getH(imgA); - globalContainer->gfx->drawSprite(p->x - 0.5f * w, p->y - 0.5f * h, globalContainer->particles, imgB, alpha); - } - - ++it; - } -} - -void GameGUI::drawPanelButtons(int y) -{ - if (!globalContainer->replaying) - { - if (!(hiddenGUIElements & HIDABLE_BUILDINGS_LIST)) - { - if (((selectionMode==NO_SELECTION) || (selectionMode==TOOL_SELECTION)) && (displayMode==CONSTRUCTION_VIEW)) - drawPanelButton(y, 0, NB_VIEWS, 1); - else - drawPanelButton(y, 0, NB_VIEWS, 0); - } - - if (!(hiddenGUIElements & HIDABLE_FLAGS_LIST)) - { - if (((selectionMode==NO_SELECTION) || (selectionMode==TOOL_SELECTION) || (selectionMode==BRUSH_SELECTION)) && (displayMode==FLAG_VIEW)) - drawPanelButton(y, 1, NB_VIEWS, 29); - else - drawPanelButton(y, 1, NB_VIEWS, 28); - } - - if (!(hiddenGUIElements & HIDABLE_TEXT_STAT)) - { - if ((selectionMode==NO_SELECTION) && (displayMode==STAT_TEXT_VIEW)) - drawPanelButton(y, 2, NB_VIEWS, 3); - else - drawPanelButton(y, 2, NB_VIEWS, 2); - } - - if (!(hiddenGUIElements & HIDABLE_GFX_STAT)) - { - if ((selectionMode==NO_SELECTION) && (displayMode==STAT_GRAPH_VIEW)) - drawPanelButton(y, 3, NB_VIEWS, 5); - else - drawPanelButton(y, 3, NB_VIEWS, 4); - } - } - else - { - if (replayDisplayMode==RDM_REPLAY_VIEW) - drawPanelButton(y, 0, RDM_NB_VIEWS, 48); - else - drawPanelButton(y, 0, RDM_NB_VIEWS, 49); - - if (replayDisplayMode==RDM_STAT_TEXT_VIEW) - drawPanelButton(y, 1, RDM_NB_VIEWS, 3); - else - drawPanelButton(y, 1, RDM_NB_VIEWS, 2); - - if (replayDisplayMode==RDM_STAT_GRAPH_VIEW) - drawPanelButton(y, 2, RDM_NB_VIEWS, 5); - else - drawPanelButton(y, 2, RDM_NB_VIEWS, 4); - } - - if(hilights.find(HilightUnderMinimapIcon) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36, y, 38)); - } -} - -void GameGUI::drawPanelButton(int y, int pos, int numButtons, int sprite) -{ - int dec = (RIGHT_MENU_WIDTH - numButtons*32)/2; - - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + dec + pos*32, y, globalContainer->gamegui, sprite); -} - -void GameGUI::drawChoice(int pos, std::vector &types, std::vector &states, unsigned numberPerLine) -{ - assert(numberPerLine >= 2); - assert(numberPerLine <= 3); - int sel=-1; - int width = (RIGHT_MENU_WIDTH/numberPerLine); - size_t i; - - for (i=0; ibuildingsTypes.getByType(type.c_str(), 0, false); - assert(bt); - int imgid = bt->miniSpriteImage; - int x, y; - - x=((i % numberPerLine)*width)+globalContainer->gfx->getW()-RIGHT_MENU_WIDTH; - y=((i / numberPerLine)*46)+YPOS_BASE_BUILDING; - globalContainer->gfx->setClipRect(x, y, 64, 46); - - Sprite *buildingSprite; - if (imgid >= 0) - { - buildingSprite = bt->miniSpritePtr; - } - else - { - buildingSprite = bt->gameSpritePtr; - imgid = bt->gameSpriteImage; - } - - int decX = (width-buildingSprite->getW(imgid))>>1; - int decY = (46-buildingSprite->getW(imgid))>>1; - - buildingSprite->setBaseColor(localTeam->color); - globalContainer->gfx->drawSprite(x+decX, y+decY, buildingSprite, imgid); - globalContainer->gfx->finishDrawingSprite(buildingSprite, 255); - - globalContainer->gfx->setClipRect(); - if(hilights.find(HilightBuildingOnPanel+IntBuildingType::shortNumberFromType(type)) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(x+decX-36, y-6+decX, 38)); - } - } - } - int count = i; - - globalContainer->gfx->setClipRect(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 128, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128); - - // draw building selection if needed - if (selectionMode == TOOL_SELECTION) - { - int sw; - if (numberPerLine == 2) - sw = globalContainer->gamegui->getW(8); - else - sw = globalContainer->gamegui->getW(23); - - - assert(sel>=0); - int x=((sel % numberPerLine)*width)+globalContainer->gfx->getW()-RIGHT_MENU_WIDTH; - int y=((sel / numberPerLine)*46)+YPOS_BASE_BUILDING; - - int decX = (width - sw) / 2; - - if (numberPerLine == 2) - globalContainer->gfx->drawSprite(x+decX, y+1, globalContainer->gamegui, 8); - else - globalContainer->gfx->drawSprite(x+decX, y+4, globalContainer->gamegui, 23); - } - - int toDrawInfoFor = -1; - if (mouseX>globalContainer->gfx->getW()-RIGHT_MENU_WIDTH) - { - if (mouseY>pos) - { - int xNum=(mouseX-globalContainer->gfx->getW()+RIGHT_MENU_WIDTH) / width; - int yNum=(mouseY-pos)/46; - int id=yNum*numberPerLine+xNum; - if (id::iterator i = std::find(types.begin(), types.end(), toolManager.getBuildingName()); - if(i != types.end()) - { - toDrawInfoFor = i - types.begin(); - } - } - } - - // draw infos - if (toDrawInfoFor != -1) - { - int id = toDrawInfoFor; - - std::string &type = types[id]; - if (states[id]) - { - int buildingInfoStart=globalContainer->gfx->getH()-50; - - std::string key = "[" + type + "]"; - drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, key.c_str()); - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 128, 128, 128)); - key = "[" + type + " explanation]"; - drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-20, key.c_str()); - key = "[" + type + " explanation 2]"; - drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-8, key.c_str()); - globalContainer->littleFont->popStyle(); - BuildingType *bt = globalContainer->buildingsTypes.getByType(type, 0, true); - if (bt) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+4+(RIGHT_MENU_WIDTH-128)/2, buildingInfoStart+6, globalContainer->littleFont, - FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Wood]")).arg(bt->maxRessource[0]).c_str()); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+4+(RIGHT_MENU_WIDTH-128)/2, buildingInfoStart+17, globalContainer->littleFont, - FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Stone]")).arg(bt->maxRessource[3]).c_str()); - - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+4+64+(RIGHT_MENU_WIDTH-128)/2, buildingInfoStart+6, globalContainer->littleFont, - FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Alga]")).arg(bt->maxRessource[4]).c_str()); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+4+64+(RIGHT_MENU_WIDTH-128)/2, buildingInfoStart+17, globalContainer->littleFont, - FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Corn]")).arg(bt->maxRessource[1]).c_str()); - - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+4+(RIGHT_MENU_WIDTH-128)/2, buildingInfoStart+28, globalContainer->littleFont, - FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Papyrus]")).arg(bt->maxRessource[2]).c_str()); - } - } - } -} - - -void GameGUI::drawUnitInfos(void) -{ - Unit* selUnit=selection.unit; - assert(selUnit); - int ypos = YPOS_BASE_UNIT; - Uint8 r, g, b; - - // draw "unit" of "player" - std::string title; - title += getUnitName(selUnit->typeNum); - title += " ("; - - std::string textT=selUnit->owner->getFirstPlayerName(); - if (textT.empty()) - textT=Toolkit::getStringTable()->getString("[Uncontrolled]"); - title += textT; - title += ")"; - - if (localTeam->teamNumber == selUnit->owner->teamNumber) - { r=160; g=160; b=255; } - else if (localTeam->allies & selUnit->owner->me) - { r=255; g=210; b=20; } - else - { r=255; g=50; b=50; } - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); - int titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); - int titlePos = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+((RIGHT_MENU_WIDTH-titleLen)>>1); - globalContainer->gfx->drawString(titlePos, ypos+5, globalContainer->littleFont, title.c_str()); - globalContainer->littleFont->popStyle(); - - ypos += YOFFSET_NAME; - - // draw unit's image - Unit* unit=selUnit; - int imgid; - UnitType *ut=unit->race->getUnitType(unit->typeNum, 0); - assert(unit->action>=0); - assert(unit->actionstartImage[unit->action]; - - int dir=unit->direction; - int delta=unit->delta; - assert(dir>=0); - assert(dir<9); - assert(delta>=0); - assert(delta<256); - if (dir==8) - { - imgid+=8*(delta>>5); - } - else - { - imgid+=8*dir; - imgid+=(delta>>5); - } - - Sprite *unitSprite=globalContainer->units; - unitSprite->setBaseColor(unit->owner->color); - int decX = (32-unitSprite->getW(imgid))>>1; - int decY = (32-unitSprite->getH(imgid))>>1; - int ddx = (RIGHT_MENU_HALF_WIDTH - 56) / 2 + 2; - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+ddx+12+decX, ypos+7+4+decY, unitSprite, imgid); - - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+ddx, ypos+4, globalContainer->gamegui, 18); - - // draw HP - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos, globalContainer->littleFont, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[hp]")).c_str()); - - if (selUnit->hp<=selUnit->trigHP) - { r=255; g=0; b=0; } - else - { r=0; g=255; b=0; } - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0/%1").arg(selUnit->hp).arg(selUnit->performance[HP]).c_str()); - globalContainer->littleFont->popStyle(); - - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_LINE+YOFFSET_TEXT_PARA, globalContainer->littleFont, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[food]")).c_str()); - - // draw food - if (selUnit->isUnitHungry()) - { r=255; g=0; b=0; } - else - { r=0; g=255; b=0; } - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+2*YOFFSET_TEXT_LINE+YOFFSET_TEXT_PARA, globalContainer->littleFont, FormatableString("%0 % (%1)").arg(((float)selUnit->hungry*100.0f)/(float)Unit::HUNGRY_MAX, 0, 0).arg(selUnit->fruitCount).c_str()); - globalContainer->littleFont->popStyle(); - - ypos += YOFFSET_ICON+10; - - int rdec = (RIGHT_MENU_WIDTH-128)/2; - - if (selUnit->performance[HARVEST]) - { - if (selUnit->carriedRessource>=0) - { - const RessourceType* r = globalContainer->ressourcesTypes.get(selUnit->carriedRessource); - unsigned resImg = r->gfxId + r->sizesCount - 1; - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos+8, globalContainer->littleFont, Toolkit::getStringTable()->getString("[carry]")); - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-32-8-rdec, ypos, globalContainer->ressources, resImg); - } - else - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos+8, globalContainer->littleFont, Toolkit::getStringTable()->getString("[don't carry anything]")); - } - } - ypos += YOFFSET_CARYING+10; - - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 : %1").arg(Toolkit::getStringTable()->getString("[current speed]")).arg(selUnit->speed).c_str()); - ypos += YOFFSET_TEXT_PARA+10; - - if (selUnit->performance[ARMOR]) - { - int armorReductionPerHappyness = selUnit->race->getUnitType(selUnit->typeNum, selUnit->level[ARMOR])->armorReductionPerHappyness; - int realArmor = selUnit->performance[ARMOR] - selUnit->fruitCount * armorReductionPerHappyness; - if (realArmor < 0) - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 255, 0, 0)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 : %1 = %2 - %3 * %4").arg(Toolkit::getStringTable()->getString("[armor]")).arg(realArmor).arg(selUnit->performance[ARMOR]).arg(selUnit->fruitCount).arg(armorReductionPerHappyness).c_str()); - if (realArmor < 0) - globalContainer->littleFont->popStyle(); - } - ypos += YOFFSET_TEXT_PARA; - - if (selUnit->typeNum!=EXPLORER) - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[levels]")).c_str()); - ypos += YOFFSET_TEXT_PARA; - - if (selUnit->performance[WALK]) - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1) : %2").arg(Toolkit::getStringTable()->getString("[Walk]")).arg((1+selUnit->level[WALK])).arg(selUnit->performance[WALK]).c_str()); - ypos += YOFFSET_TEXT_LINE; - - if (selUnit->performance[SWIM]) - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1) : %2").arg(Toolkit::getStringTable()->getString("[Swim]")).arg(selUnit->level[SWIM]).arg(selUnit->performance[SWIM]).c_str()); - ypos += YOFFSET_TEXT_LINE; - - if (selUnit->performance[BUILD]) - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1) : %2").arg(Toolkit::getStringTable()->getString("[Build]")).arg(1+selUnit->level[BUILD]).arg(selUnit->performance[BUILD]).c_str()); - ypos += YOFFSET_TEXT_LINE; - - if (selUnit->performance[HARVEST]) - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1) : %2").arg(Toolkit::getStringTable()->getString("[Harvest]")).arg(1+selUnit->level[HARVEST]).arg(selUnit->performance[HARVEST]).c_str()); - ypos += YOFFSET_TEXT_LINE; - - if (selUnit->performance[ATTACK_SPEED]) - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1) : %2").arg(Toolkit::getStringTable()->getString("[At. speed]")).arg(1+selUnit->level[ATTACK_SPEED]).arg(selUnit->performance[ATTACK_SPEED]).c_str()); - ypos += YOFFSET_TEXT_LINE; - - if (selUnit->performance[ATTACK_STRENGTH]) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1+%2) : %3+%4").arg(Toolkit::getStringTable()->getString("[At. strength]")).arg(1+selUnit->level[ATTACK_STRENGTH]).arg(selUnit->experienceLevel).arg(selUnit->performance[ATTACK_STRENGTH]).arg(selUnit->experienceLevel).c_str()); - - ypos += YOFFSET_TEXT_PARA + 2; - } - - if (selUnit->performance[MAGIC_ATTACK_AIR]) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1+%2) : %3+%4").arg(Toolkit::getStringTable()->getString("[Magic At. Air]")).arg(1+selUnit->level[MAGIC_ATTACK_AIR]).arg(selUnit->experienceLevel).arg(selUnit->performance[MAGIC_ATTACK_AIR]).arg(selUnit->experienceLevel).c_str()); - - ypos += YOFFSET_TEXT_PARA + 2; - } - - if (selUnit->performance[MAGIC_ATTACK_GROUND]) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1+%2) : %3+%4").arg(Toolkit::getStringTable()->getString("[Magic At. Ground]")).arg(1+selUnit->level[MAGIC_ATTACK_GROUND]).arg(selUnit->experienceLevel).arg(selUnit->performance[MAGIC_ATTACK_GROUND]).arg(selUnit->experienceLevel).c_str()); - - ypos += YOFFSET_TEXT_PARA + 2; - } - - if (selUnit->performance[ATTACK_STRENGTH] || selUnit->performance[MAGIC_ATTACK_AIR] || selUnit->performance[MAGIC_ATTACK_GROUND]) - drawXPProgressBar(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos, selUnit->experience, selUnit->getNextLevelThreshold()); -} - -void GameGUI::drawValueAlignedRight(int y, int v) -{ - FormatableString s("%0"); - s.arg(v); - int len = globalContainer->littleFont->getStringWidth(s.c_str()); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-len-2, y, globalContainer->littleFont, s.c_str()); -} - -void GameGUI::drawCosts(int ressources[BASIC_COUNT], Font *font) -{ - for (int i=0; i>1; - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+4+(i&0x1)*64, 256+172-42+y*12, - font, - FormatableString("%0: %1").arg(getRessourceName(i)).arg(ressources[i]).c_str()); - } -} - -void GameGUI::drawCheckButton(int x, int y, std::string caption, bool isSet) -{ - globalContainer->gfx->drawRect(x, y, 16, 16, Color::white); - if(isSet) - { - globalContainer->gfx->drawLine(x+4, y+4, x+12, y+12, Color::white); - globalContainer->gfx->drawLine(x+12, y+4, x+4, y+12, Color::white); - } - globalContainer->gfx->drawString(x+20, y, globalContainer->littleFont, caption); -} - - -void GameGUI::drawRadioButton(int x, int y, bool isSet) -{ - if(isSet) - { - globalContainer->gfx->drawSprite(x, y, globalContainer->gamegui, 20); - } - else - { - globalContainer->gfx->drawSprite(x, y, globalContainer->gamegui, 19); - } -} - -void GameGUI::drawBuildingInfos(void) -{ - Building* selBuild = selection.building; - assert(selBuild); - BuildingType *buildingType = selBuild->type; - int ypos = YPOS_BASE_BUILDING; - Uint8 r, g, b; - unsigned unitInsideBarYDec = 0; - - // draw "building" of "player" - std::string title; - std::string key ="[" + buildingType->type + "]"; - title += Toolkit::getStringTable()->getString(key.c_str()); - { - title += " ("; - std::string textT=selBuild->owner->getFirstPlayerName(); - if (textT.empty()) - textT=Toolkit::getStringTable()->getString("[Uncontrolled]"); - title += textT; - title += ")"; - } - - if (localTeam->teamNumber == selBuild->owner->teamNumber) - { r=160; g=160; b=255; } - else if (localTeam->allies & selBuild->owner->me) - { r=255; g=210; b=20; } - else - { r=255; g=50; b=50; } - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); - int titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); - int titlePos = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+((RIGHT_MENU_WIDTH-titleLen)>>1); - globalContainer->gfx->drawString(titlePos, ypos, globalContainer->littleFont, title.c_str()); - globalContainer->littleFont->popStyle(); - - // building text - title = ""; - if ((buildingType->nextLevel>=0) || (buildingType->prevLevel>=0)) - { - const std::string textT = Toolkit::getStringTable()->getString("[level]"); - title += FormatableString("%0 %1").arg(textT).arg(buildingType->level+1); - } - if (buildingType->isBuildingSite) - { - title += " ("; - title += Toolkit::getStringTable()->getString("[building site]"); - title += ")"; - } - if (buildingType->prestige) - { - title += " - "; - title += Toolkit::getStringTable()->getString("[Prestige]"); - } - titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); - titlePos = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+((RIGHT_MENU_WIDTH-titleLen)>>1); - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 200, 200, 200)); - globalContainer->gfx->drawString(titlePos, ypos+YOFFSET_TEXT_PARA-1, globalContainer->littleFont, title.c_str()); - globalContainer->littleFont->popStyle(); - - ypos += YOFFSET_NAME; - - - // building icon - Sprite *miniSprite; - int imgid; - if (buildingType->miniSpriteImage >= 0) - { - miniSprite = buildingType->miniSpritePtr; - imgid = buildingType->miniSpriteImage; - } - else - { - miniSprite = buildingType->gameSpritePtr; - imgid = buildingType->gameSpriteImage; - } - int dx = (56-miniSprite->getW(imgid))>>1; - int dy = (46-miniSprite->getH(imgid))>>1; - int ddx = (RIGHT_MENU_HALF_WIDTH - 56) / 2 + 2; - miniSprite->setBaseColor(selBuild->owner->color); - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+ddx+dx, ypos+4+dy, miniSprite, imgid); - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+ddx, ypos+4, globalContainer->gamegui, 18); - globalContainer->gfx->finishDrawingSprite(miniSprite, 255); - - // draw HP - if (buildingType->hpMax) - { - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos, globalContainer->littleFont, Toolkit::getStringTable()->getString("[hp]")); - globalContainer->littleFont->popStyle(); - - if (selBuild->hp <= buildingType->hpMax/5) - { r=255; g=0; b=0; } - else - { r=0; g=255; b=0; } - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0/%1").arg(selBuild->hp).arg(buildingType->hpMax).c_str()); - globalContainer->littleFont->popStyle(); - } - - // inside - if (buildingType->maxUnitInside && ((selBuild->owner->allies)&(1<littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+YOFFSET_TEXT_LINE, globalContainer->littleFont, Toolkit::getStringTable()->getString("[inside]")); - globalContainer->littleFont->popStyle(); - if (selBuild->buildingState==Building::ALIVE) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0/%1").arg(selBuild->unitsInside.size()).arg(selBuild->maxUnitInside).c_str()); - } - else - { - if (selBuild->unitsInside.size()>1) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0%1").arg(Toolkit::getStringTable()->getString("[Still (i)]")).arg(selBuild->unitsInside.size()).c_str()); - } - else if (selBuild->unitsInside.size()==1) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, globalContainer->littleFont, - Toolkit::getStringTable()->getString("[Still one]") ); - } - } - } - - // it is a unit ranged attractor (aka flag) - if (buildingType->defaultUnitStayRange && ((selBuild->owner->allies)&(1<computeFlagStatLocal(&goingTo, &onSpot); - // display flag stat - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos, globalContainer->littleFont, FormatableString("%0").arg(Toolkit::getStringTable()->getString("[In way]")).c_str()); - globalContainer->littleFont->popStyle(); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0").arg(goingTo).c_str()); - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+YOFFSET_TEXT_LINE, - globalContainer->littleFont, FormatableString(Toolkit::getStringTable()->getString("[On the spot]")).c_str()); - globalContainer->littleFont->popStyle(); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-+RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0").arg(onSpot).c_str()); - } - - ypos += YOFFSET_ICON+YOFFSET_B_SEP; - - // working bar - if (buildingType->maxUnitWorking) - { - if ((selBuild->owner->allies)&(1<buildingState==Building::ALIVE) - { - // If we're replaying, display the actual number, not the locally cached one (changable by the gui user) - const int maxUnitsWorking = (globalContainer->replaying?selBuild->maxUnitWorking:selBuild->maxUnitWorkingLocal); - - std::string working = Toolkit::getStringTable()->getString("[working]"); - const int len = globalContainer->littleFont->getStringWidth(working)+4; - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, working); - globalContainer->littleFont->popStyle(); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4+len, ypos, globalContainer->littleFont, FormatableString("%0/%1").arg((int)selBuild->unitsWorking.size()).arg(maxUnitsWorking).c_str()); - drawScrollBox(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos+YOFFSET_TEXT_BAR, maxUnitsWorking, maxUnitsWorking, selBuild->unitsWorking.size(), MAX_UNIT_WORKING); - } - else - { - if (selBuild->unitsWorking.size()>1) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0%1%2").arg(Toolkit::getStringTable()->getString("[still (w)]")).arg(selBuild->unitsWorking.size()).arg(Toolkit::getStringTable()->getString("[units working]")).c_str()); - } - else if (selBuild->unitsWorking.size()==1) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, - Toolkit::getStringTable()->getString("[still one unit working]") ); - } - } - } - if(hilights.find(HilightUnitsAssignedBar) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36, ypos+6, 38)); - } - ypos += YOFFSET_BAR+YOFFSET_B_SEP; - } - - // priority buttons - if(buildingType->maxUnitWorking) - { - if((selBuild->owner->allies)&(1<buildingState==Building::ALIVE) - { - // If we're replaying, display the actual number, not the locally cached one (changable by the gui user) - const int priority = (globalContainer->replaying?selBuild->priority:selBuild->priorityLocal); - - ypos += YOFFSET_B_SEP; - - int width = 128/3; - std::string prioritystr = Toolkit::getStringTable()->getString("[priority]"); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, prioritystr); - - std::string lowstr = Toolkit::getStringTable()->getString("[low priority]"); - std::string medstr = Toolkit::getStringTable()->getString("[medium priority]"); - std::string highstr = Toolkit::getStringTable()->getString("[high priority]"); - - drawRadioButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos+12+4, (priority==-1)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+14, ypos+12+2, globalContainer->littleFont, lowstr); - - drawRadioButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+width, ypos+12+4, (priority==0)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+14+width, ypos+12+2, globalContainer->littleFont, medstr); - - drawRadioButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+width*2, ypos+12+4, (priority==1)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+14+width*2, ypos+12+2, globalContainer->littleFont, highstr); - - ypos += YOFFSET_BAR+YOFFSET_B_SEP; - } - } - } - - // flag range bar - if (buildingType->defaultUnitStayRange) - { - if ((selBuild->owner->allies)&(1<replaying?selBuild->unitStayRange:selBuild->unitStayRangeLocal); - - std::string range = Toolkit::getStringTable()->getString("[range]"); - const int len = globalContainer->littleFont->getStringWidth(range)+4; - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, range); - globalContainer->littleFont->popStyle(); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4+len, ypos, globalContainer->littleFont, FormatableString("%0").arg(selBuild->unitStayRange).c_str()); - drawScrollBox(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos+YOFFSET_TEXT_BAR, selBuild->unitStayRange, unitStayRange, 0, selBuild->type->maxUnitStayRange); - } - ypos += YOFFSET_BAR+YOFFSET_B_SEP; - } - - // flag control of team and allies - if ((selBuild->owner->allies) & (1<type == "clearingflag") - { - ypos += YOFFSET_B_SEP; - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, - Toolkit::getStringTable()->getString("[Clearing:]")); - ypos += YOFFSET_TEXT_PARA; - int j=0; - for (int i=0; igfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+28, ypos, globalContainer->littleFont, - getRessourceName(i)); - int spriteId; - if (globalContainer->replaying?selBuild->clearingRessources[i]:selBuild->clearingRessourcesLocal[i]) - spriteId=20; - else - spriteId=19; - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+10, ypos+2, globalContainer->gamegui, spriteId); - - ypos+=YOFFSET_TEXT_PARA; - j++; - } - } - // min war level for war flags: - else if (buildingType->type == "warflag") - { - ypos += YOFFSET_B_SEP; - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, - Toolkit::getStringTable()->getString("[Min required level:]")); - ypos += YOFFSET_TEXT_PARA; - for (int i=0; i<4; i++) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+28, ypos, globalContainer->littleFont, 1+i); - int spriteId; - if (i==(globalContainer->replaying?selBuild->minLevelToFlag:selBuild->minLevelToFlagLocal)) - spriteId=20; - else - spriteId=19; - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+10, ypos+2, globalContainer->gamegui, spriteId); - - ypos+=YOFFSET_TEXT_PARA; - } - } - else if (buildingType->type == "explorationflag") - { - int spriteId; - - ypos += YOFFSET_B_SEP; - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, - Toolkit::getStringTable()->getString("[Min required level:]")); - ypos += YOFFSET_TEXT_PARA; - - // we use minLevelToFlag as an int which says what magic effect at minimum an explorer - // must be able to do to be accepted at this flag - // 0 == any explorer - // 1 == must be able to attack ground - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+28, ypos, globalContainer->littleFont,Toolkit::getStringTable()->getString("[any explorer]")); - if ((globalContainer->replaying?selBuild->minLevelToFlag:selBuild->minLevelToFlagLocal) == 0) - spriteId = 20; - else - spriteId = 19; - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+10, ypos+2, globalContainer->gamegui, spriteId); - - ypos += YOFFSET_TEXT_PARA; - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+28, ypos, globalContainer->littleFont,Toolkit::getStringTable()->getString("[ground attack]")); - if ((globalContainer->replaying?selBuild->minLevelToFlag:selBuild->minLevelToFlagLocal) == 1) - spriteId = 20; - else - spriteId = 19; - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+10, ypos+2, globalContainer->gamegui, spriteId); - ypos += YOFFSET_TEXT_PARA; - } - } - - globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); - - // other infos - if (buildingType->armor) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[armor]")).arg(buildingType->armor).c_str()); - ypos+=YOFFSET_TEXT_LINE; - } - if (buildingType->maxUnitInside) - ypos += YOFFSET_INFOS; - if (buildingType->shootDamage) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos+1, globalContainer->littleFont, FormatableString("%0 : %1").arg(Toolkit::getStringTable()->getString("[damage]")).arg(buildingType->shootDamage).c_str()); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos+12, globalContainer->littleFont, FormatableString("%0 : %1").arg(Toolkit::getStringTable()->getString("[range]")).arg(buildingType->shootingRange).c_str()); - ypos += YOFFSET_TOWER; - } - - // There is unit inside, show time to leave - if ((selBuild->owner->allies) & (1<timeToFeedUnit) - maxTimeTo=buildingType->timeToFeedUnit; - else if (buildingType->timeToHealUnit) - maxTimeTo=buildingType->timeToHealUnit; - else - for (int i=0; iupgradeTime[i]) - maxTimeTo=std::max(maxTimeTo, buildingType->upgradeTime[i]); - int dec = (RIGHT_MENU_RIGHT_OFFSET-128); - if (maxTimeTo) - { - globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos, 128, 7, 168, 150, 90); - for (std::list::iterator it=selBuild->unitsInside.begin(); it!=selBuild->unitsInside.end(); ++it) - { - Unit *u=*it; - assert(u); - if (u->displacement==Unit::DIS_INSIDE) - { - int dividend=-u->insideTimeout*128+128-u->delta/2; - int divisor=1+maxTimeTo; - int left=dividend/divisor; - int alpha=((dividend%divisor)*255)/divisor; - - if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) - { - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-1-dec, ypos, 7, 17, 30, 64); - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-dec, ypos, 7, 63, 111, 149); - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left+1-dec, ypos, 7, 17, 30, 64); - } - else - { - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-2-dec, ypos, 7, 17, 30, 64, alpha); - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-1-dec, ypos, 7, 17, 30, 64); - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-dec, ypos, 7, 17, 30, 64); - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left+1-dec, ypos, 7, 17, 30, 64); - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left+2-dec, ypos, 7, 17, 30, 64, 255-alpha); - - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-1-dec, ypos, 7, 63, 111, 149, alpha); - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-dec, ypos, 7, 63, 111, 149); - globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left+1-dec, ypos, 7, 63, 111, 149, 255-alpha); - } - } - } - - ypos += YOFFSET_PROGRESS_BAR; - unitInsideBarYDec = YOFFSET_PROGRESS_BAR; - } - } - - ypos += YOFFSET_B_SEP; - - // exchange building - if (buildingType->canExchange && ((selBuild->owner->sharedVisionExchange)&(1<littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, Toolkit::getStringTable()->getString("[market]")); - globalContainer->littleFont->popStyle(); - //globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-36-3, ypos+1, globalContainer->gamegui, EXCHANGE_BUILDING_ICONS); - ypos += YOFFSET_TEXT_PARA; - for (unsigned i=0; igfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1/%2)").arg(getRessourceName(i+HAPPYNESS_BASE)).arg(selBuild->ressources[i+HAPPYNESS_BASE]).arg(buildingType->maxRessource[i+HAPPYNESS_BASE]).c_str()); - - /* - int inId, outId; - if (selBuild->receiveRessourceMaskLocal & (1<sendRessourceMaskLocal & (1<gfx->drawSprite(globalContainer->gfx->getW()-36, ypos+2, globalContainer->gamegui, inId); - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-18, ypos+2, globalContainer->gamegui, outId); - */ - - ypos += YOFFSET_TEXT_PARA; - } - } - - if ((selBuild->owner->allies) & (1<canExchange) - { - - // ressources in - unsigned j = 0; - for (unsigned i=0; iressourcesTypes.size(); i++) - { - if (buildingType->maxRessource[i]) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 : %1/%2").arg(getRessourceName(i)).arg(selBuild->ressources[i]).arg(buildingType->maxRessource[i]).c_str()); - j++; - ypos += 11; - } - } - if (buildingType->maxBullets) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 : %1/%2").arg(Toolkit::getStringTable()->getString("[Bullets]")).arg(selBuild->bullets).arg(buildingType->maxBullets).c_str()); - j++; - ypos += 11; - } - ypos+=5; - } - //Unit production ratios and unit production - if (buildingType->unitProductionTime) // swarm - { - int left=(selBuild->productionTimeout*128)/buildingType->unitProductionTime; - int elapsed=128-left; - globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos, elapsed, 7, 100, 100, 255); - globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+elapsed, ypos, left, 7, 128, 128, 128); - - ypos+=15; - for (int i=0; ireplaying?selBuild->ratio[i]:selBuild->ratioLocal[i]); - - drawScrollBox(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos, selBuild->ratio[i], ratio, 0, MAX_RATIO_RANGE); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+24, ypos, globalContainer->littleFont, getUnitName(i)); - - if(i==1 && hilights.find(HilightRatioBar) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET-36, ypos-8, 38)); - } - - ypos+=20; - } - - } - - // data on whether or not the building is recieving units - bool otherFailure=true; - for(unsigned j=0; junitsFailingRequirements[j]; - if(j!=0 && n>0) - otherFailure=true; - } - if(otherFailure) - { - for(unsigned j=0; junitsFailingRequirements[j]; - if(n>0 && (int)selBuild->unitsWorking.size() < selBuild->desiredMaxUnitWorking) - { - std::string s; - if(j == Building::UnitNotAvailable) - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units not available]")).arg(n); - if(j == Building::UnitTooLowLevel) - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units too low level]")).arg(n); - else if(j == Building::UnitCantAccessBuilding) - { - if (buildingType->isVirtual) - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units can't access flag]")).arg(n); - else - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units can't access building]")).arg(n); - } - else if(j == Building::UnitTooFarFromBuilding) - { - if (buildingType->isVirtual) - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units too far from flag]")).arg(n); - else - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units too far from building]")).arg(n); - } - else if(j == Building::UnitCantAccessResource) - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units can't access resource]")).arg(n); - else if(j == Building::UnitCantAccessFruit) - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units too far from resource]")).arg(n); - else if(j == Building::UnitTooFarFromResource) - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units can't access fruit]")).arg(n); - else if(j == Building::UnitTooFarFromFruit) - s = FormatableString(Toolkit::getStringTable()->getString("[%0 units too far from fruit]")).arg(n); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+10, ypos, globalContainer->littleFont, s.c_str()); - ypos+=11; - } - } - } - - // repair and upgrade - if(selBuild->owner == localTeam) - { - if (selBuild->constructionResultState==Building::REPAIR) - { - if (buildingType->isBuildingSite) - assert(buildingType->nextLevel!=-1); - drawBlueButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, globalContainer->gfx->getH()-48, "[cancel repair]"); - } - else if (selBuild->constructionResultState==Building::UPGRADE) - { - assert(buildingType->nextLevel!=-1); - if (buildingType->isBuildingSite) - assert(buildingType->prevLevel!=-1); - drawBlueButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, globalContainer->gfx->getH()-48, "[cancel upgrade]"); - } - else if ((selBuild->constructionResultState==Building::NO_CONSTRUCTION) && (selBuild->buildingState==Building::ALIVE) && !buildingType->isBuildingSite) - { - if (selBuild->hphpMax) - { - // repair - if (selBuild->type->regenerationSpeed==0 && selBuild->isHardSpaceForBuildingSite(Building::REPAIR) && localTeam->maxBuildLevel()>=buildingType->level) - { - drawBlueButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, globalContainer->gfx->getH()-48, "[repair]"); - if ( mouseX>globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+12 && mouseXgfx->getW()-12 - && mouseY>globalContainer->gfx->getH()-48 && mouseYgfx->getH()-48+16 ) - { - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 200, 200, 255)); - int ressources[BASIC_COUNT]; - selBuild->getRessourceCountToRepair(ressources); - drawCosts(ressources, globalContainer->littleFont); - globalContainer->littleFont->popStyle(); - } - } - } - else if (buildingType->nextLevel!=-1) - { - // upgrade - if (selBuild->isHardSpaceForBuildingSite(Building::UPGRADE) && (localTeam->maxBuildLevel()>buildingType->level)) - { - drawBlueButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, globalContainer->gfx->getH()-48, "[upgrade]"); - if ( mouseX>globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+12 && mouseXgfx->getW()-12 - && mouseY>globalContainer->gfx->getH()-48 && mouseYgfx->getH()-48+16 ) - { - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 200, 200, 255)); - - // We draw the ressources cost. - int typeNum=buildingType->nextLevel; - BuildingType *bt=globalContainer->buildingsTypes.get(typeNum); - drawCosts(bt->maxRessource, globalContainer->littleFont); - - // We draw the new abilities: - int blueYpos = YPOS_BASE_BUILDING + YOFFSET_NAME; - - bt=globalContainer->buildingsTypes.get(bt->nextLevel); - - if (bt->hpMax) - drawValueAlignedRight(blueYpos+YOFFSET_TEXT_LINE, bt->hpMax); - if (bt->maxUnitInside) - drawValueAlignedRight(blueYpos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, bt->maxUnitInside); - blueYpos += YOFFSET_ICON+YOFFSET_B_SEP; - - if (buildingType->maxUnitWorking) - blueYpos += YOFFSET_BAR+YOFFSET_B_SEP; - - if (bt->armor) - { - if (!buildingType->armor) - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, blueYpos-1, globalContainer->littleFont, Toolkit::getStringTable()->getString("[armor]")); - drawValueAlignedRight(blueYpos-1, bt->armor); - blueYpos+=YOFFSET_TEXT_LINE; - } - if (buildingType->maxUnitInside) - blueYpos += YOFFSET_INFOS; - if (bt->shootDamage) - { - drawValueAlignedRight(blueYpos+1, bt->shootDamage); - drawValueAlignedRight(blueYpos+12, bt->shootingRange); - blueYpos += YOFFSET_TOWER; - } - blueYpos += unitInsideBarYDec; - blueYpos += YOFFSET_B_SEP; - - unsigned j = 0; - for (unsigned i=0; iressourcesTypes.size(); i++) - { - if (buildingType->maxRessource[i]) - { - drawValueAlignedRight(blueYpos+(j*11), bt->maxRessource[i]); - j++; - } - } - - if (bt->maxBullets) - { - drawValueAlignedRight(blueYpos+(j*11), bt->maxBullets); - j++; - } - - globalContainer->littleFont->popStyle(); - } - } - } - } - - // building destruction - if (selBuild->buildingState==Building::WAITING_FOR_DESTRUCTION) - { - drawRedButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, globalContainer->gfx->getH()-24, "[cancel destroy]"); - } - else if (selBuild->buildingState==Building::ALIVE) - { - drawRedButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, globalContainer->gfx->getH()-24, "[destroy]"); - } - } - } -} - -void GameGUI::drawRessourceInfos(void) -{ - const Ressource &r = game.map.getRessource(selection.ressource); - int ypos = YPOS_BASE_RESSOURCE; - if (r.type!=NO_RES_TYPE) - { - // Draw ressource name - const std::string &ressourceName = getRessourceName(r.type); - int titleLen = globalContainer->littleFont->getStringWidth(ressourceName.c_str()); - int titlePos = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+((RIGHT_MENU_WIDTH-titleLen)>>1); - globalContainer->gfx->drawString(titlePos, ypos+(YOFFSET_TEXT_PARA>>1), globalContainer->littleFont, ressourceName.c_str()); - ypos += 2*YOFFSET_TEXT_PARA; - - // Draw ressource image - const RessourceType* rt = globalContainer->ressourcesTypes.get(r.type); - unsigned resImg = rt->gfxId + r.variety*rt->sizesCount + r.amount; - if (!rt->eternal) - resImg--; - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+16, ypos, globalContainer->ressources, resImg); - - // Draw ressource count - if (rt->granular) - { - int sizesCount=rt->sizesCount; - int amount=r.amount; - const std::string amountS = FormatableString("%0/%1").arg(amount).arg(sizesCount); - int amountSH = globalContainer->littleFont->getStringHeight(amountS.c_str()); - globalContainer->gfx->drawString(globalContainer->gfx->getW()-64, ypos+((32-amountSH)>>1), globalContainer->littleFont, amountS.c_str()); - } - } - else - { - clearSelection(); - } -} - -void GameGUI::drawReplayPanel(void) -{ - Font *font=globalContainer->littleFont; - - int x = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + REPLAY_PANEL_XOFFSET; - int y = REPLAY_PANEL_YOFFSET; - int inc = REPLAY_PANEL_SPACE_BETWEEN_OPTIONS; - - globalContainer->gfx->drawString(x, y, font, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[Options]"))); - - drawCheckButton(x, y + 1*inc, Toolkit::getStringTable()->getString("[fog of war]"), globalContainer->replayShowFog); - drawCheckButton(x, y + 2*inc, Toolkit::getStringTable()->getString("[combined vision]"), (globalContainer->replayVisibleTeams == 0xFFFFFFFF)); - drawCheckButton(x, y + 3*inc, Toolkit::getStringTable()->getString("[show areas]"), (globalContainer->replayShowAreas)); - drawCheckButton(x, y + 4*inc, Toolkit::getStringTable()->getString("[show flags]"), (globalContainer->replayShowFlags)); - - globalContainer->gfx->drawString(x, y + REPLAY_PANEL_PLAYERLIST_YOFFSET, font, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[players]"))); - - for (int i = 0; i < game.teamsCount(); i++) - { - // I know this is a matter of taste, but I prefer checkboxes here. Radio buttons are a totally different style - //drawRadioButton(x, y + REPLAY_PANEL_PLAYERLIST_YOFFSET + (i+1)*inc, game.teams[i]->getFirstPlayerName().c_str(), localTeamNo == i); - drawRadioButton(x + 1, y + REPLAY_PANEL_PLAYERLIST_YOFFSET + (i+1)*inc + 1, localTeamNo == i); - globalContainer->gfx->drawString(x + 20, y + REPLAY_PANEL_PLAYERLIST_YOFFSET + (i+1)*inc, font, game.teams[i]->getFirstPlayerName().c_str()); - } -} - -void GameGUI::drawReplayProgressBar(bool drawBackground) -{ - assert(globalContainer->replaying); - assert(globalContainer->replayReader); - assert(globalContainer->replayReader->isValid()); - - // set the clipping rectangle - globalContainer->gfx->setClipRect( 0, REPLAY_BAR_Y - 4, REPLAY_BAR_WIDTH, REPLAY_BAR_HEIGHT + 4); - - // draw menu background, black if low speed graphics, transparent otherwise - if (drawBackground) - { - if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) - globalContainer->gfx->drawFilledRect( 0, REPLAY_BAR_Y, REPLAY_BAR_WIDTH, REPLAY_BAR_HEIGHT, 0, 0, 0); - else - globalContainer->gfx->drawFilledRect( 0, REPLAY_BAR_Y, REPLAY_BAR_WIDTH, REPLAY_BAR_HEIGHT, 0, 0, 40, 180); - } - - // Progress bar y - int y = REPLAY_BAR_Y + REPLAY_PROGRESS_BAR_Y_OFFSET; - - // Draw the actual progress bar - Style::style->drawProgressBar(globalContainer->gfx, - REPLAY_PROGRESS_BAR_X_OFFSET + REPLAY_PROGRESS_BAR_CAP_WIDTH - 1, y, - REPLAY_BAR_WIDTH - 2*REPLAY_PROGRESS_BAR_X_OFFSET - REPLAY_PROGRESS_BAR_NUM_BUTTONS * REPLAY_PROGRESS_BAR_BUTTON_WIDTH - 2*REPLAY_PROGRESS_BAR_CAP_WIDTH + 2, - globalContainer->replayReader->getCurrentStep(), - globalContainer->replayReader->getNumStepsTotal()); - - // Draw the round caps - globalContainer->gfx->drawSprite( - REPLAY_PROGRESS_BAR_X_OFFSET, y, - globalContainer->gamegui, - REPLAY_BAR_LEFT_CAP_SPRITE); - globalContainer->gfx->drawSprite( - REPLAY_BAR_WIDTH - REPLAY_PROGRESS_BAR_X_OFFSET - REPLAY_PROGRESS_BAR_CAP_WIDTH, y, - globalContainer->gamegui, - REPLAY_BAR_RIGHT_CAP_SPRITE); - - // Draw the buttons for play, pause and fast-forward - int x = REPLAY_BAR_WIDTH - REPLAY_PROGRESS_BAR_X_OFFSET - REPLAY_PROGRESS_BAR_CAP_WIDTH; - int inc = REPLAY_PROGRESS_BAR_BUTTON_WIDTH; - - globalContainer->gfx->drawSprite( x - inc*3, y, globalContainer->gamegui, (!gamePaused && !globalContainer->replayFastForward ? REPLAY_BAR_PLAY_BUTTON_ACTIVE_SPRITE : REPLAY_BAR_PLAY_BUTTON_SPRITE)); - globalContainer->gfx->drawSprite( x - inc*2, y, globalContainer->gamegui, (gamePaused ? REPLAY_BAR_PAUSE_BUTTON_ACTIVE_SPRITE : REPLAY_BAR_PAUSE_BUTTON_SPRITE)); - globalContainer->gfx->drawSprite( x - inc*1, y, globalContainer->gamegui, (!gamePaused && globalContainer->replayFastForward ? REPLAY_BAR_FAST_FORWARD_BUTTON_ACTIVE_SPRITE : REPLAY_BAR_FAST_FORWARD_BUTTON_SPRITE)); - - // Calculate the time - // This is based on default speed 25 fps, not the actual Engine's speed - // because if we fast-forward we still want to see the old time - unsigned int time1_sec = (globalContainer->replayReader->getCurrentStep()/25)%60; - unsigned int time1_min = (globalContainer->replayReader->getCurrentStep()/(25*60))%60; - unsigned int time1_hour = (globalContainer->replayReader->getCurrentStep()/(25*3600)); - - unsigned int time2_sec = (globalContainer->replayReader->getNumStepsTotal()/25)%60; - unsigned int time2_min = (globalContainer->replayReader->getNumStepsTotal()/(25*60))%60; - unsigned int time2_hour = (globalContainer->replayReader->getNumStepsTotal()/(25*3600)); - - // Draw the time - if (time2_hour <= 99) - { - globalContainer->gfx->drawString(REPLAY_BAR_TIMER_X, y+3, globalContainer->littleFont, - FormatableString("%0:%1:%2 / %3:%4:%5") - .arg(time1_hour) - .arg(time1_min,2,10,'0') - .arg(time1_sec,2,10,'0') - .arg(time2_hour) - .arg(time2_min,2,10,'0') - .arg(time2_sec,2,10,'0') - .c_str()); - } - else - { - // Time did not get saved properly, don't show it - globalContainer->gfx->drawString(REPLAY_BAR_TIMER_X, y+3, globalContainer->littleFont, - FormatableString("%0:%1:%2") - .arg(time1_hour) - .arg(time1_min,2,10,'0') - .arg(time1_sec,2,10,'0') - .c_str()); - } - - // Draw the filename of the replay - std::string replayName = glob2FilenameToName(globalContainer->replayFileName); - int stringWidth = globalContainer->littleFont->getStringWidth(replayName.c_str()); - int pos = (globalContainer->settings.screenWidth-RIGHT_MENU_WIDTH)/2 - stringWidth/2; - globalContainer->gfx->drawString(pos, y+3, globalContainer->littleFont, replayName.c_str()); - - // Draw the border - if (drawBackground) - { - for (int i = 0; i < REPLAY_BAR_WIDTH; i += 32) - { - globalContainer->gfx->drawSprite(i, REPLAY_BAR_Y-4, globalContainer->gamegui, 16); - } - } -} - -void GameGUI::drawPanel(void) -{ - // ensure we have a valid selection and associate pointers - checkSelection(); - - // set the clipping rectangle - globalContainer->gfx->setClipRect(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 128, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128); - - // draw menu background, black if low speed graphics, transparent otherwise - if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) - globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 133, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128, 0, 0, 0); - else - globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 133, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128, 0, 0, 40, 180); - - if(hilights.find(HilightRightSidePanel) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36, globalContainer->gfx->getH()/2, 38)); - } - - // draw the panel selection buttons - drawPanelButtons(YPOS_BASE_DEFAULT-32); - - switch(selectionMode) - { - case BUILDING_SELECTION: - drawBuildingInfos(); - break; - case UNIT_SELECTION: - drawUnitInfos(); - break; - case RESSOURCE_SELECTION: - drawRessourceInfos(); - break; - default: - if (!globalContainer->replaying) - { - switch(displayMode) - { - case CONSTRUCTION_VIEW: - drawChoice(YPOS_BASE_CONSTRUCTION, buildingsChoiceName, buildingsChoiceState); - break; - case FLAG_VIEW: - drawFlagView(); - break; - case STAT_TEXT_VIEW: - teamStats->drawText(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET, YPOS_BASE_STAT); - break; - case STAT_GRAPH_VIEW: - teamStats->drawStat(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET, YPOS_BASE_STAT); - drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+140+64, Toolkit::getStringTable()->getString("[Starving Map]"), showStarvingMap); - drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+140+88, Toolkit::getStringTable()->getString("[Damaged Map]"), showDamagedMap); - drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+140+112, Toolkit::getStringTable()->getString("[Defense Map]"), showDefenseMap); - drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+140+136, Toolkit::getStringTable()->getString("[Fertility Map]"), showFertilityMap); - break; - default: - std::cout << "Was not expecting displayMode" << displayMode; - assert(false); - } - } - else - { - switch(replayDisplayMode) - { - case RDM_REPLAY_VIEW: - drawReplayPanel(); - break; - case RDM_STAT_TEXT_VIEW: - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+15, YPOS_BASE_STAT+5, globalContainer->littleFont, FormatableString("%0 %1").arg(Toolkit::getStringTable()->getString("[watching:]")).arg(localTeam->getFirstPlayerName()).c_str()); - teamStats->drawText(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET, YPOS_BASE_STAT+15); - break; - case RDM_STAT_GRAPH_VIEW: - globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+15, YPOS_BASE_STAT+5, globalContainer->littleFont, FormatableString("%0 %1").arg(Toolkit::getStringTable()->getString("[watching:]")).arg(localTeam->getFirstPlayerName()).c_str()); - teamStats->drawStat(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET, YPOS_BASE_STAT+15); - drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+155+64, Toolkit::getStringTable()->getString("[Starving Map]"), showStarvingMap); - drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+155+88, Toolkit::getStringTable()->getString("[Damaged Map]"), showDamagedMap); - drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+155+112, Toolkit::getStringTable()->getString("[Defense Map]"), showDefenseMap); - drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+155+136, Toolkit::getStringTable()->getString("[Fertility Map]"), showFertilityMap); - break; - default: - std::cout << "Was not expecting replayDisplayMode" << replayDisplayMode; - assert(false); - } - } - } -} - -void GameGUI::drawFlagView(void) -{ - int dec = (RIGHT_MENU_WIDTH - 128)/2; - // draw flags - drawChoice(YPOS_BASE_FLAG, flagsChoiceName, flagsChoiceState, 3); - - // draw choice of area - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, globalContainer->gamegui, 13); - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+48+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, globalContainer->gamegui, 14); - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+88+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, globalContainer->gamegui, 25); - if (brush.getType() != BrushTool::MODE_NONE) - { - int decX = 8 + ((int)toolManager.getZoneType()) * 40 + dec; - globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, YPOS_BASE_FLAG+YOFFSET_BRUSH, globalContainer->gamegui, 22); - } - globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); - if(hilights.find(HilightForbiddenZoneOnPanel) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36+8+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, 38)); - } - if(hilights.find(HilightGuardZoneOnPanel) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36+48+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, 38)); - } - if(hilights.find(HilightClearingZoneOnPanel) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36+88+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, 38)); - } - - // draw brush - brush.draw(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH+40); - - if(hilights.find(HilightBrushSelector) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH+40+30, 38)); - } - - // draw brush help text - if ((mouseX>globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+dec) && (mouseY>YPOS_BASE_FLAG+YOFFSET_BRUSH)) - { - int buildingInfoStart = globalContainer->gfx->getH()-50; - if (mouseYgfx->getW() + RIGHT_MENU_WIDTH; - if (panelMouseX < 44) - drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[forbidden area]"); - else if (panelMouseX < 84) - drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[guard area]"); - else - drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[clear area]"); - } - else - { - if (toolManager.getZoneType() == GameGUIToolManager::Forbidden) - drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[forbidden area]"); - else if (toolManager.getZoneType() == GameGUIToolManager::Guard) - drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[guard area]"); - else if (toolManager.getZoneType() == GameGUIToolManager::Clearing) - drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[clear area]"); - else - assert(false); - } - } -} - -void GameGUI::drawTopScreenBar(void) -{ - // bar background - if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) - globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 16, 0, 0, 0); - else - globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 16, 0, 0, 40, 180); - - // draw unit stats - Uint8 redC[]={200, 0, 0}; - Uint8 greenC[]={0, 200, 0}; - Uint8 whiteC[]={200, 200, 200}; - Uint8 yellowC[]={200, 200, 0}; - Uint8 actC[3]; - int free, tot; - - int dec = (globalContainer->gfx->getW()-640)>>2; - dec += 10; - - globalContainer->unitmini->setBaseColor(localTeam->color); - for (int i=0; i<3; i++) - { - free = teamStats->getFreeUnits(i); - // worker is a special case - if (i==0) - free -= teamStats->getWorkersNeeded(); - tot = teamStats->getTotalUnits(i); - if (free<0) - memcpy(actC, redC, sizeof(redC)); - else if (free>0) - memcpy(actC, greenC, sizeof(greenC)); - else - memcpy(actC, whiteC, sizeof(whiteC)); - - globalContainer->gfx->drawSprite(dec+2, -1, globalContainer->unitmini, i); - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, actC[0], actC[1], actC[2])); - globalContainer->gfx->drawString(dec+22, 0, globalContainer->littleFont, FormatableString("%0 / %1").arg(free).arg(tot).c_str()); - globalContainer->littleFont->popStyle(); - - if(i==WORKER && hilights.find(HilightWorkersWorkingFreeStat) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(dec+22, 32, 39)); - } - - else if(i==WARRIOR && hilights.find(HilightExplorersWorkingFreeStat) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(dec+22, 32, 39)); - } - - else if(i==EXPLORER && hilights.find(HilightWarriorsWorkingFreeStat) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(dec+22, 32, 39)); - } - - dec += 70; - } - - // draw prestige stats - globalContainer->gfx->drawString(dec+0, 0, globalContainer->littleFont, FormatableString("%0 / %1 / %2").arg(localTeam->prestige).arg(game.totalPrestige).arg(game.prestigeToReach).c_str()); - - dec += 90; - - // draw unit conversion stats - globalContainer->gfx->drawString(dec, 0, globalContainer->littleFont, FormatableString("+%0 / -%1").arg(localTeam->unitConversionGained).arg(localTeam->unitConversionLost).c_str()); - - // draw CPU load - dec += 70; - int cpuLoad=0; - for (unsigned i=0; igfx->drawFilledRect(dec, 4, cpuLength, 8, actC[0], actC[1], actC[2]); - globalContainer->gfx->drawVertLine(dec, 2, 12, 200, 200, 200); - globalContainer->gfx->drawVertLine(dec+40, 2, 12, 200, 200, 200); - - // draw window bar - int pos=globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-16; - for (int i=0; igfx->drawSprite(i, 16, globalContainer->gamegui, 16); - } - for (int i=16; igfx->getH(); i+=32) - { - globalContainer->gfx->drawSprite(pos+12, i, globalContainer->gamegui, 17); - } - - - int index; - // draw main menu button - if (inGameMenu == IGM_MAIN) - index = 7; - else - index = 6; - globalContainer->gfx->drawSprite(pos, IGM_MAIN_MENU_ICON_Y, globalContainer->gamegui, index); - - // draw alliance button - if ( !(hiddenGUIElements & HIDABLE_ALLIANCE) ) - { - if (inGameMenu == IGM_ALLIANCE) - index = 44; - else - index = 45; - globalContainer->gfx->drawSprite(pos, IGM_ALLIANCE_ICON_Y, globalContainer->gamegui, index); - } - - // draw objectives button - if (inGameMenu == IGM_OBJECTIVES) - index = 46; - else - index = 47; - globalContainer->gfx->drawSprite(pos, IGM_OBJECTIVES_ICON_Y, globalContainer->gamegui, index); - - if(hilights.find(HilightMainMenuIcon) != hilights.end()) - { - arrowPositions.push_back(HilightArrowPosition(pos-32, 32, 43)); - } -} - -void GameGUI::drawOverlayInfos(void) -{ - if (selectionMode==TOOL_SELECTION) - { - globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); - toolManager.drawTool(mouseX, mouseY, localTeamNo, viewportX, viewportY); - } - else if (selectionMode==BRUSH_SELECTION) - { - globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); - toolManager.drawTool(mouseX, mouseY, localTeamNo, viewportX, viewportY); - } - else if (selectionMode==BUILDING_SELECTION) - { - Building* selBuild=selection.building; - globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); - int centerX, centerY; - game.map.buildingPosToCursor(selBuild->posXLocal, selBuild->posYLocal, selBuild->type->width, selBuild->type->height, ¢erX, ¢erY, viewportX, viewportY); - if (selBuild->owner->teamNumber==localTeamNo) - globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 0, 0, 190); - else if ((localTeam->allies) & (selBuild->owner->me)) - globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 255, 196, 0); - else if (!selBuild->type->isVirtual) - globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 190, 0, 0); - - // draw a white circle around units that are working at building - if ((showUnitWorkingToBuilding) - && ((selBuild->owner->allies) &(1<::iterator unitsWorkingIt=selBuild->unitsWorking.begin(); unitsWorkingIt!=selBuild->unitsWorking.end(); ++unitsWorkingIt) - { - Unit *unit=*unitsWorkingIt; - int px, py; - game.map.mapCaseToDisplayable(unit->posX, unit->posY, &px, &py, viewportX, viewportY); - int deltaLeft=255-unit->delta; - if (unit->actiondx*deltaLeft)>>3; - py-=(unit->dy*deltaLeft)>>3; - } - globalContainer->gfx->drawCircle(px+16, py+16, 16, 255, 255, 255, 180); - } - } - } - else if (selectionMode==RESSOURCE_SELECTION) - { - int rx = selection.ressource & game.map.getMaskW(); - int ry = selection.ressource >> game.map.getShiftW(); - int px, py; - game.map.mapCaseToDisplayable(rx, ry, &px, &py, viewportX, viewportY); - globalContainer->gfx->drawCircle(px+16, py+16, 16, 0, 0, 190); - } - - // draw message List - if (game.anyPlayerWaited && game.maskAwayPlayer && game.anyPlayerWaitedTimeFor>2) - { - int nbap=0; // Number of away players - Uint32 pm=1; - Uint32 apm=game.maskAwayPlayer; - for(int pi=0; pigfx->drawFilledRect(32, 32, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64, 22+nbap*20, 0, 0, 140, 127); - globalContainer->gfx->drawRect(32, 32, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64, 22+nbap*20, 255, 255, 255); - pm=1; - int pnb=0; - for(int pi2=0; pi2gfx->drawString(44, 44+pnb*20, globalContainer->standardFont, FormatableString(Toolkit::getStringTable()->getString("[waiting for %0]")).arg(game.players[pi2]->name).c_str()); - pnb++; - } - pm=pm<<1; - } - } - else - { - int ymesg = 32; - int yinc = 0; - - // TODO: die with SGSL - // show script text - if (game.sgslScript.isTextShown) - { - std::vector lines; - setMultiLine(game.sgslScript.textShown, &lines); - globalContainer->gfx->drawFilledRect(24, ymesg-8, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64+16, lines.size()*20+16, 0,0,0,128); - for (unsigned i=0; igfx->drawString(32, ymesg+yinc, globalContainer->standardFont, lines[i].c_str()); - yinc += 20; - } - - if (swallowSpaceKey) - { - globalContainer->gfx->drawFilledRect(24, ymesg+yinc+8, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64+16, 20, 0,0,0,128); - globalContainer->gfx->drawString(32, ymesg+yinc, globalContainer->standardFont, Toolkit::getStringTable()->getString("[press space]")); - yinc += 20; - } - yinc += 8; - } - - // show script text - if (!scriptText.empty()) - { - std::vector lines; - setMultiLine(scriptText, &lines); - globalContainer->gfx->drawFilledRect(24, ymesg-8, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64+16, lines.size()*20+16, 0,0,0,128); - for (unsigned i=0; igfx->drawString(32, ymesg+yinc, globalContainer->standardFont, lines[i].c_str()); - yinc += 20; - } - } - - // show script counter - if (game.sgslScript.getMainTimer()) - { - globalContainer->gfx->drawString(globalContainer->gfx->getW()-165, ymesg, globalContainer->standardFont, FormatableString("%0").arg(game.sgslScript.getMainTimer()).c_str()); - yinc = std::max(yinc, 32); - } - - ymesg += yinc+2; - - messageManager.drawAllGameMessages(32, ymesg); - } - - // display map mark - globalContainer->gfx->setClipRect(); - markManager.drawAll(localTeamNo, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+20, 10, 128, viewportX, viewportY, game); - - // display text if placing a building - if(selectionMode == TOOL_SELECTION && toolManager.getBuildingName() != "") - { - globalContainer->standardFont->pushStyle(Font::Style(Font::STYLE_NORMAL, Color(255,255,255))); - globalContainer->gfx->drawString(10, globalContainer->gfx->getH()-100, globalContainer->standardFont, Toolkit::getStringTable()->getString("[Building Tool Line Explanation]"), 0, 75); - globalContainer->gfx->drawString(10, globalContainer->gfx->getH()-100+12, globalContainer->standardFont, Toolkit::getStringTable()->getString("[Building Tool Box Explanation]"), 0, 75); - globalContainer->standardFont->popStyle(); - } - - // Draw icon if trasmitting - if (globalContainer->voiceRecorder->recordingNow) - globalContainer->gfx->drawSprite(5, globalContainer->gfx->getH()-50, globalContainer->gamegui, 24); - - // Draw which players are transmitting voice - int xinc = 42; - for(int p=0; pmix->isPlayerTransmittingVoice(p)) - { - if(xinc==42) - { - globalContainer->gamegui->setBaseColor(game.teams[game.players[p]->teamNumber]->color); - globalContainer->gfx->drawSprite(42, globalContainer->gfx->getH()-55, globalContainer->gamegui, 30); - xinc += 47; - } - int height = globalContainer->standardFont->getStringHeight(game.players[p]->name.c_str()); - - globalContainer->standardFont->pushStyle(Font::Style(Font::STYLE_NORMAL, game.teams[game.players[p]->teamNumber]->color)); - globalContainer->gfx->drawString(xinc, globalContainer->gfx->getH()-35-height/2, globalContainer->standardFont, game.players[p]->name); - xinc += globalContainer->standardFont->getStringWidth(game.players[p]->name.c_str()) + 5; - globalContainer->standardFont->popStyle(); - } - } - - if(!scrollableText) - messageManager.drawAllChatMessages(32, globalContainer->gfx->getH() - 165); - - // Draw the bar contining number of units, CPU load, etc... - drawTopScreenBar(); -} - -void GameGUI::drawInGameMenu(void) -{ - gameMenuScreen->dispatchPaint(); - globalContainer->gfx->drawSurface((int)gameMenuScreen->decX, (int)gameMenuScreen->decY, gameMenuScreen->getSurface()); - - // Draw a-la-aqua drop shadows - if ((globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) == 0) - { - int x = gameMenuScreen->decX; - int y = gameMenuScreen->decY; - int w = gameMenuScreen->getSurface()->getW(); - int h = gameMenuScreen->getSurface()->getH(); - - globalContainer->gfx->drawSprite(x-8, y+h, globalContainer->terrainShader, 17); - globalContainer->gfx->drawSprite(x+w, y+h, globalContainer->terrainShader, 18); - globalContainer->gfx->setClipRect(x, y+h, w, 16); - for (int i=0; igfx->drawSprite(x+i, y+h, globalContainer->terrainShader, 16); - } - globalContainer->gfx->setClipRect(x-8, y, w+16, h); - for (int i=0; igfx->drawSprite(x-8, y+i, globalContainer->terrainShader, 19); - globalContainer->gfx->drawSprite(x+w, y+i, globalContainer->terrainShader, 20); - } - } -} - -void GameGUI::drawInGameTextInput(void) -{ - typingInputScreen->decX=(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-492)/2; - typingInputScreen->decY=globalContainer->gfx->getH()-typingInputScreenPos; - typingInputScreen->dispatchPaint(); - globalContainer->gfx->drawSurface((int)typingInputScreen->decX, (int)typingInputScreen->decY, typingInputScreen->getSurface()); - if (typingInputScreenInc>0) - { - if (typingInputScreenPosTYPING_INPUT_BASE_INC) - typingInputScreenPos+=typingInputScreenInc; - else - { - typingInputScreenInc=0; - delete typingInputScreen; - typingInputScreen=NULL; - } - } -} - -void GameGUI::drawInGameScrollableText(void) -{ - scrollableText->decX=28; - scrollableText->decY=globalContainer->gfx->getH() - 165; - scrollableText->dispatchPaint(); - globalContainer->gfx->drawSurface(scrollableText->decX, scrollableText->decY, scrollableText->getSurface()); -} - -void GameGUI::drawAll(int team) -{ - // draw the map - Uint32 drawOptions = (drawHealthFoodBar ? Game::DRAW_HEALTH_FOOD_BAR : 0) | - (drawPathLines ? Game::DRAW_PATH_LINE : 0) | - (drawAccessibilityAids ? Game::DRAW_ACCESSIBILITY : 0 ) | - ((selectionMode==TOOL_SELECTION) ? Game::DRAW_BUILDING_RECT : 0) | - ((showStarvingMap) ? Game::DRAW_OVERLAY : 0) | - ((showDamagedMap) ? Game::DRAW_OVERLAY : 0) | - ((showDefenseMap) ? Game::DRAW_OVERLAY : 0) | - ((showFertilityMap) ? Game::DRAW_OVERLAY : 0) | - ((globalContainer->replaying && !globalContainer->replayShowFog) ? Game::DRAW_WHOLE_MAP : 0) | - Game::DRAW_AREA; - - updateHilightInGame(); - arrowPositions.clear(); - if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) - { - globalContainer->gfx->setClipRect(0, 16, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-16); - game.drawMap(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH(), 0, 16, viewportX, viewportY, localTeamNo, drawOptions); - } - else - { - std::set visibleBuildings; - - globalContainer->gfx->setClipRect(); - - game.drawMap(0, 0, globalContainer->gfx->getW(), globalContainer->gfx->getH(), RIGHT_MENU_WIDTH, 16, viewportX, viewportY, localTeamNo, drawOptions, &visibleBuildings); - - // generate and draw particles - generateNewParticles(&visibleBuildings); - drawParticles(); - } - - ///Draw ghost buildings - if (!globalContainer->replaying) ghostManager.drawAll(viewportX, viewportY, localTeamNo); - - // if paused, tint the game area - if (gamePaused) - { - std::string s; - - if (globalContainer->replaying && globalContainer->replayReader->isFinished()) - { - s = Toolkit::getStringTable()->getString("[replay ended]"); - } - else - { - globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH(), 0, 0, 0, 20); - s = Toolkit::getStringTable()->getString("[Paused]"); - } - - int x = (globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-globalContainer->menuFont->getStringWidth(s))/2; - globalContainer->gfx->drawString(x, globalContainer->gfx->getH()-80, globalContainer->menuFont, s); - } - - // draw the panel - globalContainer->gfx->setClipRect(); - drawPanel(); - - // draw the minimap - drawOptions = 0; - //globalContainer->gfx->setClipRect(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 0, 128, 128); - //game.drawMiniMap(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 0, 128, 128, viewportX, viewportY, team, drawOptions); - - globalContainer->gfx->setClipRect(); - minimap.draw(localTeamNo, viewportX, viewportY, (globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)/32, globalContainer->gfx->getH()/32 ); - - // draw the progress bar if this is a replay - if (globalContainer->replaying) drawReplayProgressBar(); - - // draw the top bar and other infos - globalContainer->gfx->setClipRect(); - drawOverlayInfos(); - - // draw menu if any - if (inGameMenu) - { - globalContainer->gfx->setClipRect(); - drawInGameMenu(); - } - - // draw input box if any - if (typingInputScreen) - { - globalContainer->gfx->setClipRect(); - drawInGameTextInput(); - } - if (scrollableText) - drawInGameScrollableText(); - - // draw the hilight arrows - for(int i=0; i<(int)arrowPositions.size(); ++i) - { - globalContainer->gfx->drawSprite(arrowPositions[i].x, arrowPositions[i].y, globalContainer->gamegui, arrowPositions[i].sprite); - - } -} - -void GameGUI::checkWonConditions(void) -{ - if (hasEndOfGameDialogBeenShown || globalContainer->replaying) - return; - - if (game.totalPrestigeReached && game.isPrestigeWinCondition()) - { - if (inGameMenu==IGM_NONE) - { - inGameMenu=IGM_END_OF_GAME; - gameMenuScreen=new InGameEndOfGameScreen(Toolkit::getStringTable()->getString("[Total prestige reached]"), true); - hasEndOfGameDialogBeenShown=true; - miniMapPushed=false; - } - } - else if (localTeam->hasLost==true) - { - if (inGameMenu==IGM_NONE) - { - inGameMenu=IGM_END_OF_GAME; - gameMenuScreen=new InGameEndOfGameScreen(Toolkit::getStringTable()->getString("[you have lost]"), true); - hasEndOfGameDialogBeenShown=true; - miniMapPushed=false; - } - } - else if (localTeam->hasWon==true) - { - if (inGameMenu==IGM_NONE) - { - if(campaign!=NULL) - { - campaign->setCompleted(missionName); - } - inGameMenu=IGM_END_OF_GAME; - gameMenuScreen=new InGameEndOfGameScreen(Toolkit::getStringTable()->getString("[you have won]"), true); - hasEndOfGameDialogBeenShown=true; - miniMapPushed=false; - } - } -} - -void GameGUI::showEndOfReplayScreen() -{ - gamePaused = true; - - if (!hasEndOfGameDialogBeenShown) - { - hasEndOfGameDialogBeenShown = true; - - inGameMenu=IGM_END_OF_GAME; - gameMenuScreen=new InGameEndOfGameScreen(Toolkit::getStringTable()->getString("[replay ended]"), true); - miniMapPushed=false; - } -} - -void GameGUI::executeOrder(boost::shared_ptr order) -{ - switch (order->getOrderType()) - { - case ORDER_TEXT_MESSAGE : - { - boost::shared_ptr mo=static_pointer_cast(order); - int sp=mo->sender; - Uint32 messageOrderType=mo->messageOrderType; - - if (messageOrderType==MessageOrder::NORMAL_MESSAGE_TYPE) - { - if (mo->recepientsMask &(1<name).arg(mo->getText()), true); - } - else if (messageOrderType==MessageOrder::PRIVATE_MESSAGE_TYPE) - { - if (mo->recepientsMask &(1< %2").arg(Toolkit::getStringTable()->getString("[from:]")).arg(game.players[sp]->name).arg(mo->getText()), true); - else if (sp==localPlayer) - { - Uint32 rm=mo->recepientsMask; - int k; - for (k=0; k %2").arg(Toolkit::getStringTable()->getString("[to:]")).arg(game.players[k]->name).arg(mo->getText()), true); - break; - } - else - rm=rm>>1; - assert(k ov = static_pointer_cast(order); - if (ov->recepientsMask & (1<mix->addVoiceData(ov); - game.executeOrder(order, localPlayer); - } - break; - case ORDER_PLAYER_QUIT_GAME : - { - int qp=order->sender; - if (qp==localPlayer) - isRunning=false; - addMessage(Color(200, 200, 200), FormatableString(Toolkit::getStringTable()->getString("[%0 has left the game]")).arg(game.players[qp]->name), true); - game.executeOrder(order, localPlayer); - } - break; - - case ORDER_MAP_MARK: - { - boost::shared_ptr mmo=static_pointer_cast(order); - - assert(game.teams[mmo->teamNumber]->teamNumberteamNumber]->allies & (game.teams[localTeamNo]->me)) - addMark(mmo); - } - break; - case ORDER_PAUSE_GAME: - { - boost::shared_ptr pgo=static_pointer_cast(order); - gamePaused=pgo->pause; - } - break; - case ORDER_CREATE: - { - boost::shared_ptr pgo=static_pointer_cast(order); - if(pgo->teamNumber == localTeamNo) - ghostManager.removeBuilding(pgo->posX, pgo->posY); - game.executeOrder(order, localPlayer); - } - break; - default: - { - game.executeOrder(order, localPlayer); - } - } -} - -bool GameGUI::loadFromHeaders(MapHeader& mapHeader, GameHeader& gameHeader, bool setGameHeader, bool ignoreGUIData, bool saveAI) -{ - init(); - InputStream *stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapHeader.getFileName())); - if (stream->isEndOfStream()) - { - delete stream; - stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapHeader.getFileName(true))); - if(stream->isEndOfStream()) - { - delete stream; - stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapHeader.getFileName(false,true))); - if(stream->isEndOfStream()) - { - std::cerr << "GameGUI::loadFromHeaders() : error, can't open file " << mapHeader.getFileName() << ", " << mapHeader.getFileName(true) << " or " << mapHeader.getFileName(false,true) << std::endl; - delete stream; - return false; - } - } - } - - bool res = load(stream, ignoreGUIData); - delete stream; - if (!res) - return false; - - //Use the map header from the file, the one sent across the network is in the latest format version, where as the actual map - //may be an older file version. - //game.setMapHeader(mapHeader); - if(setGameHeader) - game.setGameHeader(gameHeader, saveAI); - - return true; -} - -bool GameGUI::load(GAGCore::InputStream *stream, bool ignoreGUIData) -{ - init(); - - bool result = game.load(stream); - - if (result == false) - { - std::cerr << "GameGUI::load : can't load game" << std::endl; - return false; - } - defualtGameSaveName = game.mapHeader.getMapName(); - if (game.mapHeader.getIsSavedGame()) - { - // load gui's specific infos - stream->readEnterSection("GameGUI"); - - ///Load the data, but don't store it in local variables - if(ignoreGUIData) - { - stream->readUint32("chatMask"); - stream->readSint32("localPlayer"); - stream->readSint32("localTeamNo"); - stream->readSint32("viewportX"); - stream->readSint32("viewportY"); - stream->readUint32("hiddenGUIElements"); - stream->readUint32("buildingsChoiceMask"); - stream->readUint32("flagsChoiceMask"); - } - else - { - chatMask = stream->readUint32("chatMask"); - - localPlayer = stream->readSint32("localPlayer"); - localTeamNo = stream->readSint32("localTeamNo"); - - viewportX = stream->readSint32("viewportX"); - viewportY = stream->readSint32("viewportY"); - - hiddenGUIElements = stream->readUint32("hiddenGUIElements"); - Uint32 buildingsChoiceMask = stream->readUint32("buildingsChoiceMask"); - Uint32 flagsChoiceMask = stream->readUint32("flagsChoiceMask"); - - // invert value if hidden - for (unsigned i=0; i= 69) - defaultAssign.load(stream, game.mapHeader.getVersionMinor()); - stream->readLeaveSection(); - } - - minimap.setGame(game); - - return true; -} - -void GameGUI::save(GAGCore::OutputStream *stream, const std::string name) -{ - // Game is can't be no more automatically generated - game.save(stream, false, name); - - stream->writeEnterSection("GameGUI"); - stream->writeUint32(chatMask, "chatMask"); - stream->writeSint32(localPlayer, "localPlayer"); - stream->writeSint32(localTeamNo, "localTeamNo"); - stream->writeSint32(viewportX, "viewportX"); - stream->writeSint32(viewportY, "viewportY"); - stream->writeUint32(hiddenGUIElements, "hiddenGUIElements"); - Uint32 buildingsChoiceMask = 0; - Uint32 flagsChoiceMask = 0; - // save one if visible - for (unsigned i=0; iwriteUint32(buildingsChoiceMask, "buildingsChoiceMask"); - stream->writeUint32(flagsChoiceMask, "flagsChoiceMask"); - defaultAssign.save(stream); - stream->writeLeaveSection(); -} - -void GameGUI::drawButton(int x, int y, std::string caption, int r, int g, int b, bool doLanguageLookup) -{ - globalContainer->gfx->drawSprite(x+8, y, globalContainer->gamegui, 12); - globalContainer->gfx->drawFilledRect(x+17, y+3, 94, 10, r, g, b); - - std::string textToDraw; - if (doLanguageLookup) - textToDraw=Toolkit::getStringTable()->getString(caption); - else - textToDraw=caption; - int len=globalContainer->littleFont->getStringWidth(textToDraw); - int h=globalContainer->littleFont->getStringHeight(textToDraw); - globalContainer->gfx->drawString(x+17+((94-len)>>1), y+((16-h)>>1), globalContainer->littleFont, textToDraw); -} - -void GameGUI::drawBlueButton(int x, int y, std::string caption, bool doLanguageLookup) -{ - drawButton(x,y,caption,128,128,192,doLanguageLookup); -} - -void GameGUI::drawRedButton(int x, int y, std::string caption, bool doLanguageLookup) -{ - drawButton(x,y,caption,192,128,128,doLanguageLookup); -} - -void GameGUI::drawTextCenter(int x, int y, std::string caption) -{ - std::string text; - - text=Toolkit::getStringTable()->getString(caption); - int dec=(RIGHT_MENU_WIDTH-globalContainer->littleFont->getStringWidth(text))>>1; - globalContainer->gfx->drawString(x+dec, y, globalContainer->littleFont, text); -} - -void GameGUI::drawScrollBox(int x, int y, int value, int valueLocal, int act, int max) -{ - //scrollbar borders - globalContainer->gfx->setClipRect(x+8, y, 112, 16); - globalContainer->gfx->drawSprite(x+8, y, globalContainer->gamegui, 9); - - //localBar - int size=(valueLocal*92)/max; - globalContainer->gfx->setClipRect(x+18, y, size, 16); - globalContainer->gfx->drawSprite(x+18, y+3, globalContainer->gamegui, 10); - - //actualBar - size=(act*92)/max; - globalContainer->gfx->setClipRect(x+18, y, size, 16); - globalContainer->gfx->drawSprite(x+18, y+4, globalContainer->gamegui, 11); - - globalContainer->gfx->setClipRect(); -} - -void GameGUI::drawXPProgressBar(int x, int y, int act, int max) -{ - globalContainer->gfx->setClipRect(x+8, y, 112, 16); - - globalContainer->gfx->setClipRect(x+18, y, 92, 16); - globalContainer->gfx->drawSprite(x+18, y+3, globalContainer->gamegui, 10); - - globalContainer->gfx->setClipRect(x+18, y, (act*92)/max, 16); - globalContainer->gfx->drawSprite(x+18, y+4, globalContainer->gamegui, 11); - - globalContainer->gfx->setClipRect(); -} - -void GameGUI::cleanOldSelection(void) -{ - if (selectionMode==BUILDING_SELECTION) - { - game.selectedBuilding=NULL; - } - else if (selectionMode==UNIT_SELECTION) - { - game.selectedUnit=NULL; - } - else if (selectionMode==BRUSH_SELECTION) - { - toolManager.deactivateTool(); - } - else if (selectionMode==TOOL_SELECTION) - { - toolManager.deactivateTool(); - } -} - -void GameGUI::setSelection(SelectionMode newSelMode, unsigned newSelection) -{ - if (selectionMode!=newSelMode) - { - cleanOldSelection(); - selectionMode=newSelMode; - } - - if (selectionMode==BUILDING_SELECTION) - { - int id=Building::GIDtoID(newSelection); - int team=Building::GIDtoTeam(newSelection); - selection.building=game.teams[team]->myBuildings[id]; - game.selectedBuilding=selection.building; - } - else if (selectionMode==UNIT_SELECTION) - { - int id=Unit::GIDtoID(newSelection); - int team=Unit::GIDtoTeam(newSelection); - selection.unit=game.teams[team]->myUnits[id]; - game.selectedUnit=selection.unit; - } - else if (selectionMode==RESSOURCE_SELECTION) - { - selection.ressource=newSelection; - } -} - -void GameGUI::setSelection(SelectionMode newSelMode, void* newSelection) -{ - if (selectionMode!=newSelMode) - { - cleanOldSelection(); - selectionMode=newSelMode; - } - - if (selectionMode==BUILDING_SELECTION) - { - selection.building=(Building*)newSelection; - game.selectedBuilding=selection.building; - } - else if (selectionMode==UNIT_SELECTION) - { - selection.unit=(Unit*)newSelection; - game.selectedUnit=selection.unit; - } - else if (selectionMode==TOOL_SELECTION) - { - toolManager.activateBuildingTool((char*)(newSelection)); - } -} - -void GameGUI::checkSelection(void) -{ - if ((selectionMode==BUILDING_SELECTION) && (game.selectedBuilding==NULL)) - { - clearSelection(); - } - else if ((selectionMode==UNIT_SELECTION) && (game.selectedUnit==NULL)) - { - clearSelection(); - } -} - - -void GameGUI::iterateSelection(void) -{ - if (selectionMode==BUILDING_SELECTION) - { - Building* selBuild=selection.building; - Uint16 selectionGBID=selBuild->gid; - assert(selBuild); - assert(selectionGBID!=NOGBID); - int pos=Building::GIDtoID(selectionGBID); - int team=Building::GIDtoTeam(selectionGBID); - int i=pos; - if (team==localTeamNo) - { - while (imyBuildings[i % Building::MAX_COUNT]; - if (b && b->typeNum==selBuild->typeNum) - { - setSelection(BUILDING_SELECTION, b); - centerViewportOnSelection(); - break; - } - } - } - } - else if (selectionMode==TOOL_SELECTION) - { - Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum(toolManager.getBuildingName(), 0, false); - for (int i=0; imyBuildings[i]; - if (b && b->typeNum==typeNum) - { - setSelection(BUILDING_SELECTION, b); - centerViewportOnSelection(); - break; - } - } - } - else if (selectionMode == UNIT_SELECTION) - { - Unit * selUnit = selection.unit; - assert(selUnit); - Uint16 gid = selUnit->gid; - /* to be safe should check if gid is valid here? */ - /* if looking at one of our pieces, continue with the next - one of our pieces of same type, otherwise start at the - beginning of our pieces of that type. */ - Sint32 id = ((Unit::GIDtoTeam(gid) == localTeamNo) ? Unit::GIDtoID(gid) : 0); - id %= Unit::MAX_COUNT; /* just in case! */ - // std::cerr << "starting id: " << id << std::endl; - Sint32 i = id; - while (1) - { - i = ((i + 1) % Unit::MAX_COUNT); - if (i == id) break; - // std::cerr << "trying id: " << i << std::endl; - Unit * u = game.teams[localTeamNo]->myUnits[i]; - if (u && (u->typeNum == selUnit->typeNum)) - { - // std::cerr << "found id: " << i << std::endl; - setSelection(UNIT_SELECTION, u); - centerViewportOnSelection(); - break; - } - } - } -} - -void GameGUI::centerViewportOnSelection(void) -{ - if ((selectionMode==BUILDING_SELECTION) || (selectionMode==UNIT_SELECTION)) - { - Sint32 posX, posY; - if (selectionMode==BUILDING_SELECTION) - { - Building* b=selection.building; - //assert (selBuild); - //Building *b=game.teams[Building::GIDtoTeam(selectionGBID)]->myBuildings[Building::GIDtoID(selectionGBID)]; - assert(b); - posX = b->getMidX(); - posY = b->getMidY(); - } - else if (selectionMode==UNIT_SELECTION) - { - Unit * u = selection.unit; - assert (u); - posX = u->posX; - posY = u->posY; - } - - /* It violates good abstraction principles that we know here - that the size of the right panel is RIGHT_MENU_WIDTH pixels, and that each - map cell is 32 pixels. This information should be - abstracted. */ - - int oldViewportX = viewportX; - int oldViewportY = viewportY; - - viewportX = posX - ((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); - viewportY = posY - ((globalContainer->gfx->getH())>>6); - viewportX = viewportX & game.map.getMaskW(); - viewportY = viewportY & game.map.getMaskH(); - - moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); - } -} - - -void GameGUI::dumpUnitInformation(void) -{ - if(game.selectedUnit != NULL) - { - Unit* unit = game.selectedUnit; - std::cout<<"unit->posx = "<posX<posy = "<posY<gid = "<gid<medical = "<medical<activity = "<activity<displacement = "<displacement<movement = "<movement<action = "<action<targetBuilding) - std::cout<<"unit->targetBuilding->gid = "<targetBuilding->gid<replaying) return; - - hiddenGUIElements |= (1<settings.language) - showScriptText(text); -} - -void GameGUI::hideScriptText() -{ - scriptText.clear(); -} - -void GameGUI::setCpuLoad(int s) -{ - smoothedCPULoad[smoothedCPUPos]=s; - smoothedCPUPos=(smoothedCPUPos+1) % SMOOTHED_CPU_SIZE; -} - - - -void GameGUI::setCampaignGame(Campaign& campaign, const std::string& missionName) -{ - this->campaign=&campaign; - this->missionName=missionName; -} - - - -void GameGUI::updateHilightInGame() -{ - game.highlightUnitType = 0; - if(hilights.find(HilightWorkers) != hilights.end()) - { - game.highlightUnitType |= 1< *output, std::string indent) -{ - unsigned pos = 0; - int length = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64; - - std::string lastWord; - std::string lastLine; - std::string ninput=input; - if(ninput[ninput.length()-1] != ' ') - ninput += " "; - - while (posstandardFont->getStringWidth(lastLine.c_str()); - int actWordLength = globalContainer->standardFont->getStringWidth(lastWord.c_str()); - int spaceLength = globalContainer->standardFont->getStringWidth(" "); - if (actWordLength+actLineLength+spaceLength < length) - { - if (lastLine.length()) - lastLine += " "; - lastLine += lastWord; - lastWord.clear(); - } - else - { - output->push_back(lastLine); - lastLine = indent+lastWord; - lastWord.clear(); - } - } - else - { - lastWord += ninput[pos]; - } - pos++; - } - if (lastLine.length()) - lastLine += " "; - lastLine += lastWord; - if (lastLine.length()) - output->push_back(lastLine); -} - -void GameGUI::addMessage(const GAGCore::Color& color, const std::string &msgText, bool chat) -{ - //Split into one per line - std::vector messages; - globalContainer->standardFont->pushStyle(Font::Style(Font::STYLE_BOLD, 255, 255, 255)); - setMultiLine(msgText, &messages); - globalContainer->standardFont->popStyle(); - - ///Add each line as a seperate message to the message manager. - ///Must be done backwards to appear in the right order - for (int i=messages.size()-1; i>=0; i--) - { - if(!chat) - messageManager.addGameMessage(InGameMessage(messages[i], color)); - else - messageManager.addChatMessage(InGameMessage(messages[i], color, 16000)); - } -} - -void GameGUI::addMark(shared_ptrmmo) -{ - markManager.addMark(Mark(mmo->x, mmo->y, game.teams[mmo->teamNumber]->color)); -} - - -void GameGUI::flushScrollWheelOrders() -{ - SDL_Keymod modState = SDL_GetModState(); - if (scrollWheelChanges!=0 && selectionMode==BUILDING_SELECTION) - { - Building* selBuild=selection.building; - if ((selBuild->owner->teamNumber==localTeamNo) && - (selBuild->buildingState==Building::ALIVE)) - { - if ((selBuild->type->maxUnitWorking) && - (!globalContainer->settings.scrollWheelEnabled ? (modState & KMOD_CTRL) : !(SDL_GetModState()&KMOD_SHIFT))) - { - selBuild->maxUnitWorkingLocal+=scrollWheelChanges; - int nbReq=selBuild->maxUnitWorkingLocal=std::min(20, std::max(0, (selBuild->maxUnitWorkingLocal))); - orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, nbReq))); - defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, nbReq); - } - else if ((selBuild->type->defaultUnitStayRange) && - (SDL_GetModState()&KMOD_SHIFT)) - { - selBuild->unitStayRangeLocal+=scrollWheelChanges; - int nbReq=selBuild->unitStayRangeLocal=std::min(selBuild->type->maxUnitStayRange, std::max(0, (selBuild->unitStayRangeLocal))); - orderQueue.push_back(shared_ptr(new OrderModifyFlag(selBuild->gid, nbReq))); - } - } - } - scrollWheelChanges=0; -} - -void GameGUI::generateNewParticles(std::set *visibleBuildings) -{ - for (std::set::iterator it = visibleBuildings->begin(); it != visibleBuildings->end(); ++it) - { - Building* building = *it; - BuildingType* type = building->type; - int x, y; - game.map.mapCaseToDisplayable(building->posXLocal, building->posYLocal, &x, &y, viewportX, viewportY); - - if (!type->isBuildingSite) - { - // damaged building smoke - float hpRatio = (float)building->hp / (float)type->hpMax; - if ( - (hpRatio < 0.2 && ((game.stepCounter & 0x1) == 0)) || - (hpRatio < 0.5 && ((game.stepCounter & 0x3) == 0)) - ) - { - Particle* p = new Particle; - p->x = x + type->width * 16; - p->y = y + type->height * 16; - if (hpRatio < 0.2) - { - p->vx = 0.5f - (float)rand() / (float)RAND_MAX; - p->vy = - 3.f * (float)rand() / (float)RAND_MAX; - } - else - { - p->vx = 0.3f - (float)rand() / (float)RAND_MAX; - p->vy = - 1.8f * (float)rand() / (float)RAND_MAX; - } - p->ax = 0.f; - p->ay = -0.01f; - p->age = 0; - p->lifeSpan = 50; - p->startImg = 0; - p->endImg = 2; - p->color = building->owner->color; - particles.insert(p); - } - - // turret firing - if (building->lastShootStep != 0xFFFFFFFF) - { - if ((game.stepCounter - building->lastShootStep < 6) && (game.stepCounter % 2 == 0)) - { - float norm = building->lastShootSpeedX * building->lastShootSpeedX + building->lastShootSpeedY * building->lastShootSpeedY; - float w2 = type->width * 16; - float h2 = type->height * 16; - float dx = (building->lastShootSpeedX * w2) / sqrt(norm); - float dy = (building->lastShootSpeedY * h2) / sqrt(norm); - Particle* p = new Particle; - p->x = x + w2 + dx; - p->y = y + h2 + dy; - p->vx = 0.3f - (float)rand() / (float)RAND_MAX; - p->vy = - 1.2f * (float)rand() / (float)RAND_MAX; - p->ax = 0.f; - p->ay = -0.02f; - p->age = 0; - p->lifeSpan = 30; - p->startImg = 0; - p->endImg = 2; - p->color = building->owner->color; - particles.insert(p); - } - } - } - } -} - -void GameGUI::moveParticles(int oldViewportX, int viewportX, int oldViewportY, int viewportY) -{ - if ((viewportX==oldViewportX) && (viewportY==oldViewportY)) - return; - - int dx = viewportX - oldViewportX; - if (dx > game.map.getW() / 2) - dx -= game.map.getW(); - else if (dx < -game.map.getW() / 2) - dx += game.map.getW(); - - int dy = viewportY - oldViewportY; - if (dy > game.map.getH() / 2) - dy -= game.map.getH(); - else if (dy < -game.map.getH() / 2) - dy += game.map.getH(); - - for (ParticleSet::iterator it = particles.begin(); it != particles.end(); ++it) - { - Particle* p = *it; - p->x -= dx * 32; - p->y -= dy * 32; - } -} diff --git a/src/GameGUIDefaultAssignManager.h b/src/GameGUIDefaultAssignManager.h deleted file mode 100644 index 57bcd3cea..000000000 --- a/src/GameGUIDefaultAssignManager.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GameGUIDefaultAssignManager_h -#define GameGUIDefaultAssignManager_h - -#include -#include "Types.h" - -namespace GAGCore -{ - class OutputStream; - class InputStream; -}; - - -///This class manages the default number of units to be assigned when constructing a new buildings -class GameGUIDefaultAssignManager -{ -public: - ///Constructs a GameGUIDefaultAssignManager - GameGUIDefaultAssignManager(); - - ///Retrive the default assigned units for a given building typenum (note, not the - ///ntBuildingType typenum, the BuildingTypes typenum) - int getDefaultAssignedUnits(int typenum); - - ///Sets the default assigned units for a given building typenum - void setDefaultAssignedUnits(int typenum, int value); - - ////Saves the default assign information - void save(GAGCore::OutputStream* stream) const; - - ///Loads the default assign information - void load(GAGCore::InputStream* stream, Sint32 versionMinor); - -private: - std::map unitCount; -}; - - -#endif diff --git a/src/GameHeader.cpp b/src/GameHeader.cpp index c97a4c2d3..f64db69d0 100644 --- a/src/GameHeader.cpp +++ b/src/GameHeader.cpp @@ -1,23 +1,10 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "GameHeader.h" +#include "FileFormatVersions.h" + #include GameHeader::GameHeader() @@ -59,23 +46,42 @@ bool GameHeader::load(GAGCore::InputStream *stream, Sint32 versionMinor) return false; } stream->readEnterSection("players"); - for(int i=0; ireadEnterSection(i); - players[i].load(stream, versionMinor); + if (i < Team::MAX_COUNT) + { + if (!players[i].load(stream, versionMinor)) + { + stream->readLeaveSection(i); + stream->readLeaveSection(); + stream->readLeaveSection(); + return false; + } + } + else + { + BasePlayer scratch; + // Trailing on-disk slots beyond Team::MAX_COUNT are padding; their + // teamNumber field is never consumed, so a bad value here is not a + // crash hazard. Discard validation failures. + scratch.load(stream, versionMinor); + } stream->readLeaveSection(i); } stream->readLeaveSection(); - if(versionMinor >= 71) + if(versionMinor >= FILE_FORMAT_VERSION_ALLIES_AND_WIN_CONDITIONS) { stream->readEnterSection("allyTeamNumbers"); - for(int i=0; ireadUint8("allyTeamNumber"); + Uint8 v = stream->readUint8("allyTeamNumber"); + if (i < Team::MAX_COUNT) + allyTeamNumbers[i] = v; } stream->readLeaveSection(); allyTeamsFixed = stream->readUint8("allyTeamsFixed"); - + stream->readEnterSection("winningConditions"); winningConditions.clear(); Uint32 size = stream->readUint32("size"); @@ -87,9 +93,9 @@ bool GameHeader::load(GAGCore::InputStream *stream, Sint32 versionMinor) } stream->readLeaveSection(); } - if(versionMinor >= 64) + if(versionMinor >= FILE_FORMAT_VERSION_UNIFIED_SEED) seed = stream->readUint32("seed"); - if(versionMinor >= 72) + if(versionMinor >= FILE_FORMAT_VERSION_MAP_DISCOVERED_FLAG) mapDiscovered = stream->readUint8("mapDiscovered"); stream->readLeaveSection(); return true; @@ -104,24 +110,28 @@ void GameHeader::save(GAGCore::OutputStream *stream) const stream->writeUint8(orderRate, "orderRate"); stream->writeSint32(numberOfPlayers, "numberOfPlayers"); stream->writeEnterSection("players"); - for(int i=0; iwriteEnterSection(i); - players[i].save(stream); + if (i < Team::MAX_COUNT) + players[i].save(stream); + else + BasePlayer().save(stream); stream->writeLeaveSection(); } stream->writeLeaveSection(); stream->writeEnterSection("allyTeamNumbers"); - for(int i=0; iwriteUint8(allyTeamNumbers[i], "allyTeamNumber"); + const Uint8 v = (i < Team::MAX_COUNT) ? allyTeamNumbers[i] : static_cast(i + 1); + stream->writeUint8(v, "allyTeamNumber"); } stream->writeLeaveSection(); stream->writeUint8(allyTeamsFixed, "allyTeamsFixed"); stream->writeEnterSection("winningConditions"); stream->writeUint32(winningConditions.size(), "size"); int n=0; - for(std::list >::const_iterator i=winningConditions.begin(); i!=winningConditions.end(); ++i) + for(std::list >::const_iterator i=winningConditions.begin(); i!=winningConditions.end(); ++i) { stream->writeEnterSection(n); (*i)->encodeData(stream); @@ -141,16 +151,18 @@ bool GameHeader::loadWithoutPlayerInfo(GAGCore::InputStream *stream, Sint32 vers stream->readEnterSection("GameHeader"); gameLatency = stream->readSint32("gameLatency"); orderRate = stream->readUint8("orderRate"); - if(versionMinor >= 71) + if(versionMinor >= FILE_FORMAT_VERSION_ALLIES_AND_WIN_CONDITIONS) { stream->readEnterSection("allyTeamNumbers"); - for(int i=0; ireadUint8("allyTeamNumber"); + Uint8 v = stream->readUint8("allyTeamNumber"); + if (i < Team::MAX_COUNT) + allyTeamNumbers[i] = v; } stream->readLeaveSection(); allyTeamsFixed = stream->readUint8("allyTeamsFixed"); - + stream->readEnterSection("winningConditions"); winningConditions.clear(); Uint32 size = stream->readUint32("size"); @@ -162,9 +174,9 @@ bool GameHeader::loadWithoutPlayerInfo(GAGCore::InputStream *stream, Sint32 vers } stream->readLeaveSection(); } - if(versionMinor >= 64) + if(versionMinor >= FILE_FORMAT_VERSION_UNIFIED_SEED) seed = stream->readUint32("seed"); - if(versionMinor >= 72) + if(versionMinor >= FILE_FORMAT_VERSION_MAP_DISCOVERED_FLAG) mapDiscovered = stream->readUint8("mapDiscovered"); stream->readLeaveSection(); return true; @@ -178,16 +190,17 @@ void GameHeader::saveWithoutPlayerInfo(GAGCore::OutputStream *stream) const stream->writeSint32(gameLatency, "gameLatency"); stream->writeUint8(orderRate, "orderRate"); stream->writeEnterSection("allyTeamNumbers"); - for(int i=0; iwriteUint8(allyTeamNumbers[i], "allyTeamNumber"); + const Uint8 v = (i < Team::MAX_COUNT) ? allyTeamNumbers[i] : static_cast(i + 1); + stream->writeUint8(v, "allyTeamNumber"); } stream->writeLeaveSection(); stream->writeUint8(allyTeamsFixed, "allyTeamsFixed"); stream->writeEnterSection("winningConditions"); stream->writeUint32(winningConditions.size(), "size"); int n=0; - for(std::list >::const_iterator i=winningConditions.begin(); i!=winningConditions.end(); ++i) + for(std::list >::const_iterator i=winningConditions.begin(); i!=winningConditions.end(); ++i) { stream->writeEnterSection(n); (*i)->encodeData(stream); @@ -207,10 +220,18 @@ bool GameHeader::loadPlayerInfo(GAGCore::InputStream *stream, Sint32 versionMino stream->readEnterSection("GameHeader"); numberOfPlayers = stream->readSint32("numberOfPlayers"); stream->readEnterSection("players"); - for(int i=0; ireadEnterSection(i); - players[i].load(stream, versionMinor); + if (i < Team::MAX_COUNT) + { + players[i].load(stream, versionMinor); + } + else + { + BasePlayer scratch; + scratch.load(stream, versionMinor); + } stream->readLeaveSection(i); } stream->readLeaveSection(); @@ -225,10 +246,13 @@ void GameHeader::savePlayerInfo(GAGCore::OutputStream *stream) const stream->writeEnterSection("GameHeader"); stream->writeSint32(numberOfPlayers, "numberOfPlayers"); stream->writeEnterSection("players"); - for(int i=0; iwriteEnterSection(i); - players[i].save(stream); + if (i < Team::MAX_COUNT) + players[i].save(stream); + else + BasePlayer().save(stream); stream->writeLeaveSection(); } stream->writeLeaveSection(); diff --git a/src/GameHeader.h b/src/GameHeader.h index 28c5c970d..a4b4f9e2a 100644 --- a/src/GameHeader.h +++ b/src/GameHeader.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GAMEHEADER_H -#define __GAMEHEADER_H +#pragma once #include "BasePlayer.h" #include "Stream.h" @@ -102,7 +86,7 @@ class GameHeader ///Returns the list of winning conditions. This list can be modified. Mind, though, the pecking order of winning conditions. ///Ones first on the list are considered first. - inline std::list >& getWinningConditions() { return winningConditions; } + inline std::list >& getWinningConditions() { return winningConditions; } ///Returns the random generator seed thats being used inline Uint32 getRandomSeed() const { return seed; } @@ -136,7 +120,7 @@ class GameHeader bool allyTeamsFixed; ///Represents the winning conditions of the game. - std::list > winningConditions; + std::list > winningConditions; ///Represents the random seed used for the game Uint32 seed; @@ -146,4 +130,3 @@ class GameHeader }; -#endif diff --git a/src/GameHints.cpp b/src/GameHints.cpp index 84874753b..671d4469c 100644 --- a/src/GameHints.cpp +++ b/src/GameHints.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "GameHints.h" #include "Stream.h" diff --git a/src/GameHints.h b/src/GameHints.h index c85d97766..7c5c25933 100644 --- a/src/GameHints.h +++ b/src/GameHints.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GameHints_h -#define GameHints_h +#pragma once #include #include @@ -69,4 +53,3 @@ class GameHints std::vector scriptNumbers; }; -#endif diff --git a/src/GameObjectives.cpp b/src/GameObjectives.cpp index 710c003bc..202b07e4b 100644 --- a/src/GameObjectives.cpp +++ b/src/GameObjectives.cpp @@ -1,22 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "GameObjectives.h" +#include "FileFormatVersions.h" #include "Stream.h" #include #include @@ -237,7 +223,7 @@ void GameObjectives::decodeData(GAGCore::InputStream* stream, Uint32 versionMino texts.push_back(stream->readText("text")); hidden.push_back(stream->readUint8("hidden")); completed.push_back(stream->readUint8("completed")); - if(versionMinor>=76) + if(versionMinor>=FILE_FORMAT_VERSION_BRIEFING_HINTS_OBJ_FAILED) failed.push_back(stream->readUint8("failed")); else failed.push_back(false); diff --git a/src/GameObjectives.h b/src/GameObjectives.h index b415d102b..10529c340 100644 --- a/src/GameObjectives.h +++ b/src/GameObjectives.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#ifndef GameObjectives_h -#define GameObjectives_h +#pragma once #include #include @@ -100,4 +83,3 @@ class GameObjectives std::string invalidText; }; -#endif diff --git a/src/GameUtilities.cpp b/src/GameUtilities.cpp index 480b6c3b4..7c4524699 100644 --- a/src/GameUtilities.cpp +++ b/src/GameUtilities.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "GameUtilities.h" #include "Game.h" diff --git a/src/GameUtilities.h b/src/GameUtilities.h index 81058ac13..602190e0d 100644 --- a/src/GameUtilities.h +++ b/src/GameUtilities.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GAME_UTILITIES_H -#define __GAME_UTILITIES_H +#pragma once class Game; @@ -28,4 +11,3 @@ namespace GameUtilities void globalCoordToLocalView(const Game *game, int localTeam, int globalX, int globalY, int *localX, int *localY); }; -#endif diff --git a/src/Game_editor.cpp b/src/Game_editor.cpp new file mode 100644 index 000000000..4e42d5ae4 --- /dev/null +++ b/src/Game_editor.cpp @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "AICastor.h" +#include "AINicowar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "BuildingType.h" +#include "DatasetWriter.h" +#include "Game.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Unit.h" +#include "render/UnitSkin.h" +#include "Integrity.h" +#include "Utilities.h" +#include "GameGUI.h" +#include "SDLCompat.h" + +#include "MapEdit.h" + +#include "Brush.h" +#include "DynamicClouds.h" +#include "Bullet.h" +#include "TextStream.h" +#include "FertilityCalculatorDialog.h" + +#include "ReplayWriter.h" + +#define BULLET_IMGID 0 + +// Editor utilities and script-interface queries. Split out of Game.cpp. + +// Script interface + +int Game::isTeamAlive(int team) +{ + if ( + (team >= 0) && (team < mapHeader.getNumberOfTeams()) + ) + return teams[team]->isAlive; + else + return false; +} + +int Game::unitsCount(int team, int type) +{ + if ( + (team >= 0) && (team < mapHeader.getNumberOfTeams()) && + (type >= 0) && (type < NB_UNIT_TYPE) + ) + return teams[team]->stats.getLatestStat()->numberUnitPerType[type]; + else + return 0; +} + +int Game::unitsUpgradesCount(int team, int type, int ability, int level) +{ + if ( + (team >= 0) && (team < mapHeader.getNumberOfTeams()) && + (type >= 0) && (type < NB_UNIT_TYPE) && + (ability >= 0) && (ability < NB_ABILITY) && + (level >= 0) && (level < NB_UNIT_LEVELS) + ) + return teams[team]->stats.getLatestStat()->upgradeStatePerType[type][ability][level]; + else + return 0; +} + +int Game::buildingsCount(int team, int type, int level) +{ + if ( + (team >= 0) && (team < mapHeader.getNumberOfTeams()) && + (type >= 0) && (type < IntBuildingType::NB_BUILDING) && + (level >= 0) && (level < MAX_BUILDING_LEVELS) + ) + return teams[team]->stats.getLatestStat()->numberBuildingPerTypePerLevel[type][level]; + else + return 0; +} + + +void Game::addTeam(int pos) +{ + assert(mapHeader.getNumberOfTeams()teamNumber=mapHeader.getNumberOfTeams(); + teams[pos]->race.load(); + teams[pos]->setCorrectMasks(); + + pos=mapHeader.getNumberOfTeams(); + pos+=1; + mapHeader.setNumberOfTeams(pos); + for (int i=0; isetCorrectColor( ((float)i*TEAM_COLOR_HUE_DEGREES) /(float)pos ); + + prestigeToReach = std::max(MIN_MAX_PRESTIGE, pos*TEAM_MAX_PRESTIGE); + + map.addTeam(); + + sgslScript.addTeam(); +} + +void Game::removeTeam(int pos) +{ + if(pos==TEAM_POS_END) + { + pos=mapHeader.getNumberOfTeams(); + pos-=1; + mapHeader.setNumberOfTeams(pos); + } + if (mapHeader.getNumberOfTeams()>0) + { + Team *team=teams[pos]; + + team->clearMap(); + + delete team; + assert (mapHeader.getNumberOfTeams()!=0); + for (int i=0; isetCorrectColor(((float)i*TEAM_COLOR_HUE_DEGREES)/(float)mapHeader.getNumberOfTeams()); + + map.removeTeam(); + sgslScript.removeTeam(pos); + teams[pos]=NULL; + } +} + +void Game::clearingUncontrolledTeams(void) +{ + for (int ti=0; tiplayersMask==0) + { + team->clearMap(); + team->clearLists(); + team->clearMem(); + } + } +} + +void Game::regenerateDiscoveryMap(void) +{ + map.unsetMapDiscovered(); + for (int t=0; tmyUnits[i]; + if (u) + { + map.setMapDiscovered(u->posX-1, u->posY-1, 3, 3, teams[t]->sharedVisionOther); + } + } + for (int i=0; imyBuildings[i]; + if (b) + { + b->setMapDiscovered(); + } + } + } +} + +Unit *Game::addUnit(int x, int y, int team, Sint32 typeNum, int level, int delta, int dx, int dy) +{ + assert(teamrace.getUnitType(typeNum, level); + + x = (x + map.getW()) % map.getW(); + y = (y + map.getH()) % map.getH(); + + bool fly=ut->performance[FLY]; + bool free; + if (fly) + free=map.isFreeForAirUnit(x, y); + else + free=map.isFreeForGroundUnit(x, y, ut->performance[SWIM], Team::teamNumberToMask(team)); + if (!free) + return NULL; + + int id=SLOT_INDEX_NONE; + for (int i=0; imyUnits[i]==NULL) + { + id=i; + break; + } + if (id==SLOT_INDEX_NONE) + return NULL; + + //ok, now we can safely deposite an unit. + int gid=Unit::GIDfrom(id, team); + if (fly) + map.setAirUnit(x, y, gid); + else + map.setGroundUnit(x, y, gid); + + teams[team]->myUnits[id]= new Unit(x, y, gid, typeNum, teams[team], level); + teams[team]->myUnits[id]->dx=dx; + teams[team]->myUnits[id]->dy=dy; + teams[team]->myUnits[id]->directionFromDxDy(); + teams[team]->myUnits[id]->delta=delta; + teams[team]->myUnits[id]->selectPreferredMovement(); + return teams[team]->myUnits[id]; +} + +Building *Game::addBuilding(int x, int y, int typeNum, int teamNumber, Sint32 unitWorking, Sint32 unitWorkingFuture) +{ + Team *team=teams[teamNumber]; + assert(team); + + int id=SLOT_INDEX_NONE; + for (int i=0; imyBuildings[i]==NULL) + { + id=i; + break; + } + if (id==SLOT_INDEX_NONE) + { + //TODO:Building limit reached! + return NULL; + } + + //ok, now we can safely deposite an building. + int gid=Building::GIDfrom(id, teamNumber); + + int w=globalContainer->buildingsTypes.get(typeNum)->width; + int h=globalContainer->buildingsTypes.get(typeNum)->height; + + Building *b=new Building(x&map.getMaskW(), y&map.getMaskH(), gid, typeNum, team, &globalContainer->buildingsTypes, unitWorking, unitWorkingFuture); + + if (b->type->canExchange) + team->canExchange.push_front(b); + if (b->type->isVirtual) + team->virtualBuildings.push_front(b); + else + map.setBuilding(x, y, w, h, gid); + team->myBuildings[id]=b; + return b; +} + +bool Game::removeUnitAndBuildingAndFlags(int x, int y, unsigned flags) +{ + bool found=false; + if (flags & DEL_GROUND_UNIT) + { + Uint16 gauid=map.getAirUnit(x, y); + if (gauid!=NOGUID) + { + int id=Unit::GIDtoID(gauid); + int team=Unit::GIDtoTeam(gauid); + map.setAirUnit(x, y, NOGUID); + delete (teams[team]->myUnits[id]); + teams[team]->myUnits[id]=NULL; + found=true; + } + } + if (flags & DEL_AIR_UNIT) + { + Uint16 gguid=map.getGroundUnit(x, y); + if (gguid!=NOGUID) + { + int id=Unit::GIDtoID(gguid); + int team=Unit::GIDtoTeam(gguid); + map.setGroundUnit(x, y, NOGUID); + delete (teams[team]->myUnits[id]); + teams[team]->myUnits[id]=NULL; + found=true; + } + } + if (flags & DEL_BUILDING) + { + Uint16 gbid=map.getBuilding(x, y); + if (gbid!=NOGBID) + { + int id=Building::GIDtoID(gbid); + int team=Building::GIDtoTeam(gbid); + Building *b=teams[team]->myBuildings[id]; + if (!b->type->isVirtual) + map.setBuilding(b->posX, b->posY, b->type->width, b->type->height, NOGBID); + delete b; + teams[team]->myBuildings[id]=NULL; + found=true; + } + } + if (flags & DEL_FLAG) + { + for (int ti=0; ti::iterator bi=teams[ti]->virtualBuildings.begin(); bi!=teams[ti]->virtualBuildings.end(); ++bi) + if ((*bi)->posX==x && (*bi)->posY==y) + { + teams[ti]->myBuildings[Building::GIDtoID((*bi)->gid)]=NULL; + delete *bi; + teams[ti]->virtualBuildings.erase(bi); + found=true; + break; + } + } + return found; +} + +bool Game::removeUnitAndBuildingAndFlags(int x, int y, int size, unsigned flags) +{ + int sts = size>>1; + int stp = (~size)&1; + bool somethingInRect = false; + + for (int scx=(x-sts); scx<=(x+sts-stp); scx++) + for (int scy=(y-sts); scy<=(y+sts-stp); scy++) + if (removeUnitAndBuildingAndFlags((scx&(map.getMaskW())), (scy&(map.getMaskH())), flags)) + somethingInRect = true; + + return somethingInRect; +} + +bool Game::checkRoomForBuilding(int mousePosX, int mousePosY, const BuildingType *bt, int *buildingPosX, int *buildingPosY, int teamNumber, bool checkFow) +{ + int x=mousePosX+bt->decLeft; + int y=mousePosY+bt->decTop; + + *buildingPosX=x; + *buildingPosY=y; + + return checkRoomForBuilding(x, y, bt, teamNumber, checkFow); +} + +bool Game::checkRoomForBuilding(int x, int y, const BuildingType *bt, int teamNumber, bool checkFow) +{ + Team *team=teams[teamNumber]; + assert(team); + + int w=bt->width; + int h=bt->height; + + bool isRoom=true; + if (bt->isVirtual) + { + if (teamNumber<0) + return true; + + for (std::list::iterator vb=team->virtualBuildings.begin(); vb!=team->virtualBuildings.end(); ++vb) + { + Building *b=*vb; + if ((b->posX==(x&map.getMaskW())) && (b->posY==(y&map.getMaskH()))) + return false; + } + return true; + } + else + isRoom=map.isFreeForBuilding(x, y, w, h); + + if (!checkFow) + return isRoom; + + if (isRoom) + { + for (int dy=y; dyme)) + return true; + return false; + } + else + return false; +} + +bool Game::checkHardRoomForBuilding(int coordX, int coordY, const BuildingType *bt, int *mapX, int *mapY) +{ + int x=coordX+bt->decLeft; + int y=coordY+bt->decTop; + + *mapX=x; + *mapY=y; + + return checkHardRoomForBuilding(x, y, bt); +} + +bool Game::checkHardRoomForBuilding(int x, int y, const BuildingType *bt) +{ + int w=bt->width; + int h=bt->height; + assert(!bt->isVirtual); // This method is not for flags! + return map.isHardSpaceForBuilding(x, y, w, h); +} + + + +Unit* Game::getUnit(int guid) +{ + if(guid == NOGUID) + return NULL; + return teams[Unit::GIDtoTeam(guid)]->myUnits[Unit::GIDtoID(guid)]; +} + diff --git a/src/Game_io.cpp b/src/Game_io.cpp new file mode 100644 index 000000000..7f49e4b46 --- /dev/null +++ b/src/Game_io.cpp @@ -0,0 +1,538 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "AICastor.h" +#include "AINicowar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "BuildingType.h" +#include "DatasetWriter.h" +#include "FileFormatVersions.h" +#include "Game.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Unit.h" +#include "render/UnitSkin.h" +#include "Integrity.h" +#include "Utilities.h" +#include "GameGUI.h" +#include "SDLCompat.h" + +#include "MapEdit.h" + +#include "Brush.h" +#include "DynamicClouds.h" +#include "Bullet.h" +#include "TextStream.h" +#include "FertilityCalculator.h" +#include "FertilityCalculatorDialog.h" + +#include "ReplayWriter.h" + +#define BULLET_IMGID 0 + +// Save/load, integrity, checksum. Split out of Game.cpp. + +namespace +{ + // RAII guard: enters a stream section on construction, leaves it on + // destruction unless commit() was called. Lets failure paths just + // `return false;` without remembering to call readLeaveSection(). + // Note: readLeaveSection is a no-op for BinaryStream (the format used + // for save files); it only affects TextStream nesting. + class ReadSectionGuard + { + GAGCore::InputStream *stream; + bool committed = false; + public: + ReadSectionGuard(GAGCore::InputStream *s, const char *name) : stream(s) + { + stream->readEnterSection(name); + } + ReadSectionGuard(const ReadSectionGuard &) = delete; + ReadSectionGuard &operator=(const ReadSectionGuard &) = delete; + void commit() + { + stream->readLeaveSection(); + committed = true; + } + ~ReadSectionGuard() + { + if (!committed) + stream->readLeaveSection(); + } + }; + + // Read a 4-byte signature and check it equals `expected`. Signatures are + // basic corruption tests scattered through the save format. + bool readMatchingSignature(GAGCore::InputStream *stream, + const char *expected, + const char *fieldName) + { + char signature[FILE_SIG_LEN]; + stream->read(signature, FILE_SIG_LEN, fieldName); + return memcmp(signature, expected, FILE_SIG_LEN) == 0; + } + + // Note: the rotr1 helper used below now lives in Utilities.h so all + // checksum mixers in the codebase share one definition. +} + +bool Game::load(GAGCore::InputStream *stream) +{ + assert(stream); + + ReadSectionGuard gameSection(stream, "Game"); + + ///Clears any previous game + clearGame(); + mapHeader.reset(); + gameHeader.reset(); + + // We load the map header + MapHeader tempMapHeader; + if (verbose) + printf("Loading map header\n"); + if (!tempMapHeader.load(stream)) + return false; + mapHeader=tempMapHeader; + Sint32 versionMinor=mapHeader.getVersionMinor(); + + + // We load the game header + GameHeader tempGameHeader; + if (verbose) + printf("Loading game header\n"); + if (!tempGameHeader.load(stream, versionMinor)) + return false; + gameHeader=tempGameHeader; + + if (!readMatchingSignature(stream, FILE_SIG_GAME_BEGIN, "signatureStart")) + return false; + + ///Load the step counter + stepCounter = stream->readUint32("stepCounter"); + + if(versionMinor < FILE_FORMAT_VERSION_UNIFIED_SEED) + { + ///Load random seeds, these are no longer used + stream->readUint32("SyncRandSeedA"); + stream->readUint32("SyncRandSeedB"); + stream->readUint32("SyncRandSeedC"); + + if (!readMatchingSignature(stream, FILE_SIG_GAME_SYNC, "signatureAfterSyncRand")) + return false; + } + else + { + if (!readMatchingSignature(stream, FILE_SIG_GAME_BUILT, "signatureBeforeTeams")) + return false; + } + + ///Load teams + stream->readEnterSection("teams"); + for (int i=0; ireadEnterSection(i); + teams[i]=new Team(stream, this, versionMinor); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + if (!readMatchingSignature(stream, FILE_SIG_GAME_TEAM, "signatureAfterTeams")) + return false; + + // Load the map. Team has to be saved and loaded first. + if(!map.load(stream, mapHeader, this)) + return false; + + if (!readMatchingSignature(stream, FILE_SIG_GAME_MAP, "signatureAfterMap")) + return false; + + // Load the players. Both Map and Team must be loaded first. + stream->readEnterSection("players"); + for (int i=0; ireadEnterSection(i); + players[i]=new Player(stream, teams, versionMinor); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + if (!readMatchingSignature(stream, FILE_SIG_GAME_PLAYER, "signatureAfterPlayers")) + return false; + + // We have to finish Team's loading + for (int i=0; iupdate(); + } + + // Check integrity of loaded game + if (!integrity()) + return false; + + // Now load the old map script + if (!sgslScript.load(stream, this)) + return false; + + if(versionMinor >= FILE_FORMAT_VERSION_USL_MAPSCRIPT) + { + // This is the new map script system + mapscript.decodeData(stream, mapHeader.getVersionMinor()); + } + + ///Load the campaign text for the game. + if(versionMinor < FILE_FORMAT_VERSION_CAMPAIGN_TEXT_OBJECTIVES) + stream->readText("campaignText"); + + // default prestige calculation + prestigeToReach = std::max(MIN_MAX_PRESTIGE, mapHeader.getNumberOfTeams()*TEAM_MAX_PRESTIGE); + + if(mapHeader.getVersionMinor() >= FILE_FORMAT_VERSION_CAMPAIGN_TEXT_OBJECTIVES) + { + objectives.decodeData(stream, mapHeader.getVersionMinor()); + } + + if(mapHeader.getVersionMinor() >= FILE_FORMAT_VERSION_BRIEFING_HINTS_OBJ_FAILED) + { + missionBriefing = stream->readText("briefing"); + gameHints.decodeData(stream, mapHeader.getVersionMinor()); + } + + gameSection.commit(); + + ///versions less than 63 did not have fertility computed with the map, but computed it live. + ///compute it now + if(mapHeader.getVersionMinor() < FILE_FORMAT_VERSION_PRE_FERTILITY) + { + if(globalContainer->runNoX) + { + FertilityCalculator::compute(map, {}); + } + else + { + FertilityCalculatorDialog dialog(globalContainer->gfx, map); + dialog.runModal(); + } + } + + return true; +} + +bool Game::checkBuildingsDoNotOverlapAndHealMissing() { + std::vector buildings(map.getW()*map.getH(), NOGBID); + for (int ti=0; timyBuildings[bi]; + if (!building) + continue; + const auto x = building->posX; + const auto y = building->posY; + const auto type = building->type; + const auto w = type->width; + const auto h = type->height; + const auto gid = building->gid; + for (int yi=y; yiisVirtual) + continue; + // check for overlap + const auto index = map.coordToIndex(xi, yi); + checkInvariant(buildings[index]==NOGBID); + buildings[index] = gid; + // heal missing cells + if (map.getCase(xi, yi).building != gid) + { + std::cerr << "Missing map cell GBID at " << xi << "," << yi + << " for team " << ti + << " building " << bi + << " (" << building->type->type << "), healing!" + << std::endl; + map.getCase(xi, yi).building = gid; + } + } + } + } + return true; +} + +bool Game::integrity(void) +{ + ///Check teams integrity + for (int i=0; iintegrity()); + + ///Check that buildings do not overlap, as a pre-condition for healing + checkInvariant(checkBuildingsDoNotOverlapAndHealMissing()); + + ///Check that all ID do point to existing objects + for (int y=0; ymyBuildings[bid]; + checkInvariant(building); + + // If a cell points at a building whose footprint doesn't + // actually cover this cell, log it and clear the bad GBID. + auto healOutsideCoord = [&](bool predicate, const char *coordName, + int coordValue, int posValue, int endValue) + { + if (!predicate) + { + std::cerr << "Invalid coordinate " << coordName << "=" << coordValue + << " for team " << tid + << " building " << bid + << " (" << building->type->type << ")" + << " with " << coordName + << " span [" << posValue << ":" << endValue << "[, healing!" + << std::endl; + map.getCase(x, y).building = NOGBID; + } + }; + + const auto buildingEndX = building->posX + building->type->width; + healOutsideCoord(x >= building->posX || x < (buildingEndX & map.wMask), + "X", x, building->posX, buildingEndX); + healOutsideCoord(x < buildingEndX, "X", x, building->posX, buildingEndX); + const auto buildingEndY = building->posY + building->type->height; + healOutsideCoord(y >= building->posY || y < (buildingEndY & map.hMask), + "Y", y, building->posY, buildingEndY); + healOutsideCoord(y < buildingEndY, "Y", y, building->posY, buildingEndY); + } + if (c.groundUnit != NOGUID) + { + int tid = Unit::GIDtoTeam(c.groundUnit); + checkInvariant(teams[tid]); + const auto unit = teams[tid]->myUnits[Unit::GIDtoID(c.groundUnit)]; + checkInvariant(unit); + // checkInvariantText(unit->posX == x, ", unit " << unit->typeNum << " at " << x << "," << y << " has instead posX=" << unit->posX); + // checkInvariantText(unit->posY == y, ", unit " << unit->typeNum << " at " << x << "," << y << " has instead posY=" << unit->posY); + } + if (c.airUnit != NOGUID) + { + int tid = Unit::GIDtoTeam(c.airUnit); + checkInvariant(teams[tid]); + const auto unit = teams[tid]->myUnits[Unit::GIDtoID(c.airUnit)]; + checkInvariant(unit); + checkInvariant(unit->posX == x); + checkInvariant(unit->posY == y); + } + } + return true; +} + +void Game::save(GAGCore::OutputStream *stream, bool fileIsAMap, const std::string& name) +{ + assert(stream); + stream->writeEnterSection("Game"); + if(dynamic_cast(stream)) + { + dynamic_cast(stream)->enableSHA1(); + } + + ///Save the two headers, record the position in the file because mapHeader will + ///will need to be overwritten with the mapOffset known. + /// + /// We mutate mapHeader briefly to shape the on-disk record (mapName, + /// isSavedGame), then restore it on scope exit via the RAII guard + /// below. Without the restore, every in-game save (the ReplayWriter's + /// initial state dump with name="replayHeader" and the GameGUI auto-save + /// every 256 ticks with name="Auto save") would permanently overwrite + /// the live mapHeader.mapName — observable later in things like the + /// GLOB2_GAME_END "map=" field, which would read "Auto save" instead + /// of the actual map. Map-editor "Save As" still wants the new name + /// to persist; MapEdit::save() explicitly re-sets it after the call. + struct MapHeaderRestoreGuard + { + MapHeader &header; + std::string savedMapName; + bool savedIsSavedGame; + MapHeaderRestoreGuard(MapHeader &h) + : header(h), savedMapName(h.getMapName()), savedIsSavedGame(h.getIsSavedGame()) {} + MapHeaderRestoreGuard(const MapHeaderRestoreGuard &) = delete; + MapHeaderRestoreGuard &operator=(const MapHeaderRestoreGuard &) = delete; + ~MapHeaderRestoreGuard() + { + header.setMapName(savedMapName); + header.setIsSavedGame(savedIsSavedGame); + } + } mapHeaderRestore(mapHeader); + + Uint32 mapHeaderOffset = stream->getPosition(); + mapHeader.setMapName(name); + mapHeader.setIsSavedGame(!fileIsAMap); + mapHeader.resetGameSHA1(); + + for (int i=0; iwrite(FILE_SIG_GAME_BEGIN, FILE_SIG_LEN, "signatureStart"); + stream->writeUint32(stepCounter, "stepCounter"); + stream->write(FILE_SIG_GAME_BUILT, FILE_SIG_LEN, "signatureBeforeTeams"); + + ///Save teams + stream->writeEnterSection("teams"); + for (int i=0; iwriteEnterSection(i); + teams[i]->save(stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + stream->write(FILE_SIG_GAME_TEAM, FILE_SIG_LEN, "signatureAfterTeams"); + + + ///Save the map offset to the header, before we save the map + ///Then, save the map + mapHeader.setMapOffset(stream->getPosition()); + map.save(stream); + stream->write(FILE_SIG_GAME_MAP, FILE_SIG_LEN, "signatureAfterMap"); + + ///Save the players + stream->writeEnterSection("players"); + for (int i=0; iwriteEnterSection(i); + players[i]->save(stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + stream->write(FILE_SIG_GAME_PLAYER, FILE_SIG_LEN, "signatureAfterPlayers"); + + // Save the old map script state + sgslScript.save(stream, this); + + // This is the new map script system + mapscript.encodeData(stream); + + ///Save game objectives + objectives.encodeData(stream); + stream->writeText(missionBriefing, "missionBriefing"); + gameHints.encodeData(stream); + + Uint8 sha1[SHA1_BYTE_LEN]; + for(int i=0; i(stream)) + { + dynamic_cast(stream)->finishSHA1(sha1); + } + mapHeader.setGameSHA1(sha1); + + ///Overwrite the MapHeader. This is done after the map + ///offset has been set. + if (stream->canSeek()) + { + Uint32 position = stream->getPosition(); + stream->seekFromStart(mapHeaderOffset); + mapHeader.save(stream); + stream->seekFromStart(position); + } + + stream->writeLeaveSection(); + + // mapHeaderRestore's destructor restores the pre-save mapName and + // isSavedGame on scope exit. +} + +Uint32 Game::checkSum(std::vector *checkSumsVector, std::vector *checkSumsVectorForBuildings, std::vector *checkSumsVectorForUnits, bool heavy) +{ + Uint32 cs=0; + + Uint32 headerCs=mapHeader.checkSum(); + cs^=headerCs; + if (checkSumsVector) + checkSumsVector->push_back(headerCs);// [0] + + cs=rotr1(cs); + + Uint32 teamsCs=0; + for (int i=0; icheckSum(checkSumsVector, checkSumsVectorForBuildings, checkSumsVectorForUnits); + teamsCs=rotr1(teamsCs); + cs=rotr1(cs); + } + cs^=teamsCs; + if (checkSumsVector) + checkSumsVector->push_back(teamsCs);// [1+t*20] + + cs=rotr1(cs); + + Uint32 playersCs=0; + for (int i=0; icheckSum(checkSumsVector); + playersCs=rotr1(playersCs); + cs=rotr1(cs); + } + cs^=playersCs; + if (checkSumsVector) + checkSumsVector->push_back(playersCs);// [2+t*20+p*2] + + cs=rotr1(cs); + + for (int i=0; itype==BasePlayer::P_IP) + { + heavy=true; + break; + } + } + Uint32 mapCs=map.checkSum(heavy); + cs^=mapCs; + if (checkSumsVector) + checkSumsVector->push_back(mapCs);// [3+t*20+p*2] + + cs=rotr1(cs); + + Uint32 scriptCs=sgslScript.checkSum(); + cs^=scriptCs; + if (checkSumsVector) + checkSumsVector->push_back(scriptCs);// [4+t*20+p*2] + + return cs; +} diff --git a/src/Game_orders.cpp b/src/Game_orders.cpp new file mode 100644 index 000000000..8ee88f09c --- /dev/null +++ b/src/Game_orders.cpp @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// Order execution. Split out of Game.cpp; see Game.cpp for the rest of the +// Game class implementation. + +#include +#include + +#include "AICastor.h" +#include "AINicowar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "BuildingType.h" +#include "DatasetWriter.h" +#include "Game.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Unit.h" +#include "render/UnitSkin.h" +#include "Integrity.h" +#include "Utilities.h" +#include "GameGUI.h" +#include "SDLCompat.h" + +#include "MapEdit.h" + +#include "Brush.h" +#include "DynamicClouds.h" +#include "Bullet.h" +#include "TextStream.h" +#include "FertilityCalculatorDialog.h" + +#include "ReplayWriter.h" + +Building* Game::lookupBuilding(Uint16 gid) const +{ + int team=Building::GIDtoTeam(gid); + int id=Building::GIDtoID(gid); + return teams[team]->myBuildings[id]; +} + +void Game::executeOrder(std::shared_ptr order, int localPlayer) +{ + assert(order->sender>=0); + assert(order->sendersender < gameHeader.getNumberOfPlayers()); + + if (globalContainer->replayWriter && globalContainer->replayWriter->isValid()) + { + globalContainer->replayWriter->pushOrder(order); + } + + // Mirror the order into the AI-trainer dataset if requested via + // GLOB2_DATASET_PATH. One record per executed order, tagged with + // the firing tick (the live stepCounter is correct here because + // executeOrder runs after Game::syncStep advances it). + if (globalContainer->datasetWriter && globalContainer->datasetWriter->isValid()) + { + globalContainer->datasetWriter->writeRecord((Uint32)stepCounter, *order, *this); + } + + anyPlayerWaited=false; + Team *team=players[order->sender]->team; + assert(team); + bool isPlayerAlive=team->isAlive; + Uint8 orderType=order->getOrderType(); + switch (orderType) + { + case ORDER_CREATE: + if (!isPlayerAlive) break; + executeCreate(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_MODIFY_BUILDING: + if (!isPlayerAlive) break; + executeModifyBuilding(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_MODIFY_EXCHANGE: + if (!isPlayerAlive) break; + executeModifyExchange(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_MODIFY_FLAG: + if (!isPlayerAlive) break; + executeModifyFlag(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_MODIFY_CLEARING_FLAG: + if (!isPlayerAlive) break; + executeModifyClearingFlag(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_MODIFY_MIN_LEVEL_TO_FLAG: + if (!isPlayerAlive) break; + executeModifyMinLevelToFlag(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_MOVE_FLAG: + if (!isPlayerAlive) break; + executeMoveFlag(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_ALTERATE_FORBIDDEN: + executeAlterateForbidden(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_ALTERATE_GUARD_AREA: + executeAlterateGuardArea(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_ALTERATE_CLEAR_AREA: + executeAlterateClearArea(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_MODIFY_SWARM: + if (!isPlayerAlive) break; + executeModifySwarm(*std::static_pointer_cast(order), localPlayer); + break; + case ORDER_DELETE: + executeDelete(*std::static_pointer_cast(order)); + break; + case ORDER_CHANGE_PRIORITY: + executeChangePriority(*std::static_pointer_cast(order)); + break; + case ORDER_CANCEL_DELETE: + executeCancelDelete(*std::static_pointer_cast(order)); + break; + case ORDER_CONSTRUCTION: + if (!isPlayerAlive) break; + executeConstruction(*std::static_pointer_cast(order)); + break; + case ORDER_CANCEL_CONSTRUCTION: + if (!isPlayerAlive) break; + // Historical: the cancel-construction case downcasts to OrderConstruction, + // not OrderCancelConstruction. Preserve that — the two layouts overlap on + // the fields read here, and changing it is a behavior change. + executeCancelConstruction(*std::static_pointer_cast(order)); + break; + case ORDER_SET_ALLIANCE: + executeSetAlliance(*std::static_pointer_cast(order)); + break; + case ORDER_PLAYER_QUIT_GAME: + executePlayerQuitGame(*std::static_pointer_cast(order)); + break; + } +} + +void Game::executeCreate(const OrderCreate& oc, int localPlayer) +{ + int posX=(oc.posX)&map.getMaskW(); + int posY=(oc.posY)&map.getMaskH(); + assert(oc.teamNumber==players[oc.sender]->team->teamNumber); + BuildingType *bt=globalContainer->buildingsTypes.get(oc.typeNum); + bool isVirtual=bt->isVirtual; + int w=bt->width; + int h=bt->height; + if (!isVirtual && (teams[oc.teamNumber]->noMoreBuildingSitesCountdown>0)) + return; + bool isRoom=checkRoomForBuilding(posX, posY, bt, oc.teamNumber); + if (isVirtual || isRoom) + { + Building *b=addBuilding(posX, posY, oc.typeNum, oc.teamNumber, oc.unitWorking, oc.unitWorkingFuture); + if (b) + { + if(isVirtual && oc.flagRadius>=0) + { + b->unitStayRange = oc.flagRadius; + } + b->owner->addToStaticAbilitiesLists(b); + b->update(); + } + } + else if (!isVirtual && !isRoom && map.isHardSpaceForBuilding(posX, posY, w, h)) + { + BuildProject buildProject; + buildProject.posX = posX; + buildProject.posY = posY; + buildProject.teamNumber = oc.teamNumber; + buildProject.typeNum = oc.typeNum; + buildProject.unitWorking = oc.unitWorking; + buildProject.unitWorkingFuture = oc.unitWorkingFuture; + buildProjects.push_back(buildProject); + Uint32 teamMask=Team::teamNumberToMask(oc.teamNumber); + for (int y=posY; yteamNumber) + map.localForbiddenMap.set(index, true); + } + map.updateForbiddenGradient(oc.teamNumber); + } +} + +void Game::executeModifyBuilding(const OrderModifyBuilding& omb, int localPlayer) +{ + Building *b=lookupBuilding(omb.gid); + if ((b) && (b->buildingState==Building::ALIVE)) + { + assert(omb.numberRequested <= MAX_BUILDING_WORKER_REQUEST); + b->maxUnitWorking=omb.numberRequested; + b->maxUnitWorkingPreferred=b->maxUnitWorking; + b->update(); + } +} + +void Game::executeModifyExchange(const OrderModifyExchange& ome, int localPlayer) +{ + Building *b=lookupBuilding(ome.gid); + if ((b) && (b->buildingState==Building::ALIVE)) + { + b->receiveRessourceMask=ome.receiveRessourceMask; + b->sendRessourceMask=ome.sendRessourceMask; + if (ome.sender!=localPlayer) + { + b->receiveRessourceMaskLocal=b->receiveRessourceMask; + b->sendRessourceMaskLocal=b->sendRessourceMask; + } + b->update(); + } +} + +void Game::executeModifyFlag(const OrderModifyFlag& omf, int localPlayer) +{ + Building *b=lookupBuilding(omf.gid); + if ((b) && (b->buildingState==Building::ALIVE) && (b->type->defaultUnitStayRange)) + { + int oldRange=b->unitStayRange; + int newRange=omf.range; + b->unitStayRange=newRange; + + if (b->type->zonableForbidden) + { + if (newRangeowner->dirtyGlobalGradient(); + map.dirtyLocalGradient(b->posX-oldRange-GRADIENT_DIRTY_BORDER_TILES, b->posY-oldRange-GRADIENT_DIRTY_BORDER_TILES, 2*GRADIENT_DIRTY_BORDER_TILES+oldRange*2, 2*GRADIENT_DIRTY_BORDER_TILES+oldRange*2, b->owner->teamNumber); + } + } + else + { + b->resetPathfindGradients(); + } + } +} + +void Game::executeModifyClearingFlag(const OrderModifyClearingFlag& omcf, int localPlayer) +{ + Building *b=lookupBuilding(omcf.gid); + if (b + && b->buildingState==Building::ALIVE + && b->type->defaultUnitStayRange + && b->type->zonable[WORKER]) + { + memcpy(b->clearingRessources, omcf.clearingRessources, sizeof(bool)*BASIC_COUNT); + if (omcf.sender!=localPlayer) + memcpy(b->clearingRessourcesLocal, omcf.clearingRessources, sizeof(bool)*BASIC_COUNT); + } +} + +void Game::executeModifyMinLevelToFlag(const OrderModifyMinLevelToFlag& omwf, int localPlayer) +{ + Building *b=lookupBuilding(omwf.gid); + if (b + && b->buildingState==Building::ALIVE + && b->type->defaultUnitStayRange + && (b->type->zonable[WARRIOR] || b->type->zonable[EXPLORER])) + { + b->minLevelToFlag = omwf.minLevelToFlag; + // if it was another player, update local + if (omwf.sender != localPlayer) + b->minLevelToFlagLocal = b->minLevelToFlag; + + // flush all the actual units + int maxUnitWorkingSaved = b->maxUnitWorking; + b->maxUnitWorking = 0; + b->update(); + b->maxUnitWorking = maxUnitWorkingSaved; + b->update(); + } +} + +void Game::executeMoveFlag(const OrderMoveFlag& omf, int localPlayer) +{ + bool drop=omf.drop; + Building *b=lookupBuilding(omf.gid); + if ((b) && (b->buildingState==Building::ALIVE) && (b->type->isVirtual)) + { + if (drop && b->type->zonableForbidden) + { + int range=b->unitStayRange; + map.dirtyLocalGradient(b->posX-range-GRADIENT_DIRTY_BORDER_TILES, b->posY-range-GRADIENT_DIRTY_BORDER_TILES, 2*GRADIENT_DIRTY_BORDER_TILES+range*2, 2*GRADIENT_DIRTY_BORDER_TILES+range*2, b->owner->teamNumber); + } + + b->posX=omf.x; + b->posY=omf.y; + + if (b->type->zonableForbidden) + { + if (drop) + b->owner->dirtyGlobalGradient(); + } + else + { + b->resetPathfindGradients(); + } + } +} + +void Game::executeAlterateForbidden(const OrderAlterateForbidden& oaa, int localPlayer) +{ + if (oaa.type == BrushTool::MODE_ADD) + { + Uint32 teamMask = Team::teamNumberToMask(oaa.teamNumber); + size_t orderMaskIndex = 0; + for (int y=oaa.centerY+oaa.minY; yteamNumber) + map.localForbiddenMap.set(index, true); + } + orderMaskIndex++; + } + } + else if (oaa.type == BrushTool::MODE_DEL) + { + Uint32 notTeamMask = ~Team::teamNumberToMask(oaa.teamNumber); + size_t orderMaskIndex = 0; + for (int y=oaa.centerY+oaa.minY; yteamNumber) + map.localForbiddenMap.set(index, false); + } + orderMaskIndex++; + } + + // We remove, so we need to refresh the gradients, unfortunatly + teams[oaa.teamNumber]->dirtyGlobalGradient(); + map.dirtyLocalGradient(oaa.centerX+oaa.minX-GRADIENT_DIRTY_BORDER_TILES, oaa.centerY+oaa.minY-GRADIENT_DIRTY_BORDER_TILES, oaa.maxX-oaa.minX+2*GRADIENT_DIRTY_BORDER_TILES, oaa.maxY-oaa.minY+2*GRADIENT_DIRTY_BORDER_TILES, oaa.teamNumber); + } + else + assert(false); + map.updateForbiddenGradient(oaa.teamNumber); + map.updateGuardAreasGradient(oaa.teamNumber); + map.updateClearAreasGradient(oaa.teamNumber); +} + +void Game::executeAlterateGuardArea(const OrderAlterateGuardArea& oaa, int localPlayer) +{ + if (oaa.type == BrushTool::MODE_ADD) + { + Uint32 teamMask = Team::teamNumberToMask(oaa.teamNumber); + size_t orderMaskIndex = 0; + for (int y=oaa.centerY+oaa.minY; yteamNumber) + map.localGuardAreaMap.set(index, true); + } + orderMaskIndex++; + } + } + else if (oaa.type == BrushTool::MODE_DEL) + { + Uint32 notTeamMask = ~Team::teamNumberToMask(oaa.teamNumber); + size_t orderMaskIndex = 0; + for (int y=oaa.centerY+oaa.minY; yteamNumber) + map.localGuardAreaMap.set(index, false); + } + orderMaskIndex++; + } + } + else + assert(false); + map.updateGuardAreasGradient(oaa.teamNumber); +} + +void Game::executeAlterateClearArea(const OrderAlterateClearArea& oaa, int localPlayer) +{ + if (oaa.type == BrushTool::MODE_ADD) + { + Uint32 teamMask = Team::teamNumberToMask(oaa.teamNumber); + size_t orderMaskIndex = 0; + for (int y=oaa.centerY+oaa.minY; yteamNumber) + map.localClearAreaMap.set(index, true); + } + orderMaskIndex++; + } + } + else if (oaa.type == BrushTool::MODE_DEL) + { + Uint32 notTeamMask = ~Team::teamNumberToMask(oaa.teamNumber); + size_t orderMaskIndex = 0; + for (int y=oaa.centerY+oaa.minY; yteamNumber) + map.localClearAreaMap.set(index, false); + } + orderMaskIndex++; + } + } + else + assert(false); + map.updateClearAreasGradient(oaa.teamNumber); +} + +void Game::executeModifySwarm(const OrderModifySwarm& oms, int localPlayer) +{ + Building *b=lookupBuilding(oms.gid); + if ((b) && (b->buildingState==Building::ALIVE) && (b->type->unitProductionTime)) + { + for (int j=0; jratio[j]=oms.ratio[j]; + if (oms.sender!=localPlayer) + b->ratioLocal[j]=b->ratio[j]; + } + b->update(); + } +} + +void Game::executeDelete(const OrderDelete& od) +{ + Building *b=lookupBuilding(od.gid); + if (b) + { + b->launchDelete(); + assert(b->type); + if (b->type->zonableForbidden) + { + b->owner->dirtyGlobalGradient(); + int range=b->unitStayRange; + map.dirtyLocalGradient(b->posX-range-GRADIENT_DIRTY_BORDER_TILES, b->posY-range-GRADIENT_DIRTY_BORDER_TILES, 2*GRADIENT_DIRTY_BORDER_TILES+range*2, 2*GRADIENT_DIRTY_BORDER_TILES+range*2, b->owner->teamNumber); + } + } +} + +void Game::executeChangePriority(const OrderChangePriority& ocp) +{ + Building *b=lookupBuilding(ocp.gid); + if (b) + { + b->priority = ocp.priority; + b->updateCallLists(); + } +} + +void Game::executeCancelDelete(const OrderCancelDelete& ocd) +{ + Building *b=lookupBuilding(ocd.gid); + if (b) + { + b->cancelDelete(); + } +} + +void Game::executeConstruction(const OrderConstruction& oc) +{ + Building *b=lookupBuilding(oc.gid); + if (b) + { + b->launchConstruction(oc.unitWorking, oc.unitWorkingFuture); + } +} + +void Game::executeCancelConstruction(const OrderConstruction& oc) +{ + Building *b=lookupBuilding(oc.gid); + if (b) + { + b->cancelConstruction(oc.unitWorking); + } +} + +void Game::executeSetAlliance(const SetAllianceOrder& sao) +{ + Uint32 team=sao.teamNumber; + teams[team]->allies=sao.alliedMask; + teams[team]->enemies=sao.enemyMask; + teams[team]->sharedVisionExchange=sao.visionExchangeMask; + teams[team]->sharedVisionFood=sao.visionFoodMask; + teams[team]->sharedVisionOther=sao.visionOtherMask; +} + +void Game::executePlayerQuitGame(const PlayerQuitsGameOrder& pqgo) +{ + bool found = false; + for(int i=0; iteamNumber == players[pqgo.player]->teamNumber) + { + found = true; + } + } + } + if(! found) + { + teams[players[pqgo.player]->teamNumber]->isAlive = false; + } + + players[pqgo.player]->makeItAI(AI::NONE); + gameHeader.getBasePlayer(pqgo.player).makeItAI(AI::NONE); +} diff --git a/src/Game_sync.cpp b/src/Game_sync.cpp new file mode 100644 index 000000000..92129da75 --- /dev/null +++ b/src/Game_sync.cpp @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "AICastor.h" +#include "AINicowar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "BuildingType.h" +#include "DatasetWriter.h" +#include "Game.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Unit.h" +#include "render/UnitSkin.h" +#include "Integrity.h" +#include "Utilities.h" +#include "GameGUI.h" +#include "SDLCompat.h" + +#include "MapEdit.h" + +#include "Brush.h" +#include "DynamicClouds.h" +#include "Bullet.h" +#include "TextStream.h" +#include "FertilityCalculatorDialog.h" + +#include "ReplayWriter.h" + +#define BULLET_IMGID 0 + +// Per-tick sync. Split out of Game.cpp. + +void Game::buildProjectSyncStep(Sint32 localTeam) +{ + for (std::list::iterator bpi=buildProjects.begin(); bpi!=buildProjects.end();) + { + int posX=bpi->posX&map.getMaskW(); + int posY=bpi->posY&map.getMaskH(); + int teamNumber=bpi->teamNumber; + assert(teamNumber <= teamsCount()); + Sint32 typeNum=(bpi->typeNum); + BuildingType *bt=globalContainer->buildingsTypes.get(typeNum); + int w=bt->width; + int h=bt->height; + if (!map.isHardSpaceForBuilding(posX, posY, w, h)) + { + Uint32 notTeamMask=~Team::teamNumberToMask(teamNumber); + for (int y=posY; y::iterator to_erase=bpi; + bpi++; + buildProjects.erase(to_erase); + continue; + } + else if (checkRoomForBuilding(posX, posY, bt, teamNumber)) + { + Building *b=addBuilding(posX, posY, typeNum, teamNumber, bpi->unitWorking, bpi->unitWorkingFuture); + if (b) + { + Uint32 notTeamMask=~Team::teamNumberToMask(teamNumber); + for (int y=posY; yowner->addToStaticAbilitiesLists(b); + b->update(); + std::list::iterator to_erase=bpi; + bpi++; + buildProjects.erase(to_erase); + continue; + } + } + bpi++; + } +} + +void Game::wonSyncStep(void) +{ + //TODO: sideeffects? + //std::list >& conditions = + gameHeader.getWinningConditions(); + + bool areAllDecided=true; + //We do this twice, because some win conditions depend on other win conditions + for(int i=0; icheckWinConditions(); + } + for(int i=0; icheckWinConditions(); + if(teams[i]->winCondition == WCUnknown) + areAllDecided=false; + } + isGameEnded = areAllDecided; + +} + +void Game::scriptSyncStep() +{ + // do a script step + sgslScript.syncStep(gui); + mapscript.syncStep(gui); +} + + + +void Game::prestigeSyncStep() +{ + totalPrestige=0; + totalPrestigeReached=false; + for (int i=0; iprestige; + } + if(totalPrestige >= prestigeToReach) + { + totalPrestigeReached=true; + } +} + + + +void Game::syncStep(Sint32 localTeam) +{ + if (!anyPlayerWaited) + { + if (globalContainer->replayWriter && globalContainer->replayWriter->isValid()) + { + globalContainer->replayWriter->advanceStep(); + } + + Uint64 startTick=SDL_GetTicks64(); + + for (int i=0; isyncStep(); + + map.syncStep(stepCounter); + + syncRand(); + + if ((stepCounter&FOW_SWITCH_TICK_MASK)==FOW_SWITCH_TICK_PHASE) + { + map.switchFogOfWar(); + for (int t=0; tmyBuildings[i]; + if (b) + { + assert(b->owner==teams[t]); + assert(b->type); + } + if ((b)&&(!b->type->isBuildingSite || (b->type->level>0))&&(!b->type->isVirtual)) + { + b->setMapDiscovered(); + } + } + } + + if ((stepCounter&BUILD_PROJECT_TICK_MASK)==BUILD_PROJECT_TICK_PHASE) + buildProjectSyncStep(localTeam); + + if ((stepCounter&WORLD_LOGIC_TICK_MASK)==WORLD_LOGIC_TICK_PHASE) + { + prestigeSyncStep(); + scriptSyncStep(); + wonSyncStep(); + } + + Uint64 endTick=SDL_GetTicks64(); + ticksGameSum[stepCounter&(TICK_PROFILE_BUF_LEN-1)]+=static_cast(endTick) - static_cast(startTick); + stepCounter++; + anyPlayerWaitedTimeFor+=1; + } +} + +void Game::dirtyWarFlagGradient(void) +{ + for (int i=0; idirtyWarFlagGradient(); +} diff --git a/src/Glob2.cpp b/src/Glob2.cpp index 3d9822145..ab2958920 100644 --- a/src/Glob2.cpp +++ b/src/Glob2.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "Glob2.h" #include "GlobalContainer.h" @@ -70,7 +54,7 @@ # include #endif -using boost::shared_ptr; +using std::shared_ptr; /*! \mainpage Globulation 2 Reference documentation @@ -145,14 +129,30 @@ int Glob2::runNoX() int Glob2::runTestGames() { globalContainer->automaticEndingSteps=90000; - while(true) + int maxRuns = globalContainer->runTestGamesCount; + int run = 0; + while(maxRuns == 0 || run < maxRuns) { - long t = time(NULL); + // GLOB2_TEST_SEED overrides the wall-clock seed for deterministic + // regression testing. With a fixed seed (and unchanged maps/), two + // runs produce byte-identical replays — the basis for the + // behavior-preservation harness used by C++ cleanup work. + const char* envSeed = getenv("GLOB2_TEST_SEED"); + long t = envSeed ? atol(envSeed) : time(NULL); setSyncRandSeed(t); + // Capture the seed so createRandomGame can mirror it into + // GameHeader::seed — otherwise a saved .game file (from + // --save-game-as or GLOB2_DUMP_GAME) would carry the wall-clock + // time(NULL) that GameHeader's default ctor wrote, not the seed + // that actually drove this run, and reloading via --nox would + // diverge from the original. + globalContainer->testGamesSeed = (Uint32)t; + globalContainer->testGamesSeedSet = true; std::cout<<"Random Seed initial: "< or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GLOB2_H -#define __GLOB2_H +#pragma once //! This class is used to handle the whole game class Glob2 @@ -39,4 +22,3 @@ class Glob2 int run(int argc, char *argv[]); }; -#endif diff --git a/src/Glob2Screen.cpp b/src/Glob2Screen.cpp index 7be7afc70..99dd866f5 100644 --- a/src/Glob2Screen.cpp +++ b/src/Glob2Screen.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière #include "Glob2Screen.h" #include "GlobalContainer.h" diff --git a/src/Glob2Screen.h b/src/Glob2Screen.h index d0e60771f..452d84e81 100644 --- a/src/Glob2Screen.h +++ b/src/Glob2Screen.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GLOB2_SCREEN_H -#define __GLOB2_SCREEN_H +#pragma once #include #include @@ -50,5 +33,4 @@ class Glob2TabScreen : public TabScreen int randomSeed; }; -#endif diff --git a/src/Glob2Style.cpp b/src/Glob2Style.cpp index 7af9219a0..48f9dcd67 100644 --- a/src/Glob2Style.cpp +++ b/src/Glob2Style.cpp @@ -1,24 +1,10 @@ -/* - Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charrière #include "Glob2Style.h" #include "GlobalContainer.h" +#include +using namespace GAGCore; Glob2Style::Glob2Style() { diff --git a/src/Glob2Style.h b/src/Glob2Style.h index c1a31f545..3538c46eb 100644 --- a/src/Glob2Style.h +++ b/src/Glob2Style.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2006 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2006 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GLOB2_STYLE_H -#define __GLOB2_STYLE_H +#pragma once #include @@ -43,4 +26,3 @@ class Glob2Style : public Style Sprite *sprite; }; -#endif diff --git a/src/GlobalContainer.cpp b/src/GlobalContainer.cpp index 7ae9d1337..5de195ab7 100644 --- a/src/GlobalContainer.cpp +++ b/src/GlobalContainer.cpp @@ -1,21 +1,7 @@ -/* - Copyright (C) 2001-2007 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2007 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +#include #include #include @@ -26,32 +12,22 @@ #include "Glob2Screen.h" #include "Glob2Style.h" #include "GlobalContainer.h" -#include "Header.h" #include "IntBuildingType.h" #include "KeyboardManager.h" #include "LogFileManager.h" #include "MapEditKeyActions.h" -#include "NonANSICStdWrapper.h" #include "Player.h" #include "Race.h" #include "SoundMixer.h" -#include "UnitsSkins.h" +#include "render/UnitSkin.h" #include "VoiceRecorder.h" #ifndef YOG_SERVER_ONLY +#include "DatasetWriter.h" #include "ReplayReader.h" #include "ReplayWriter.h" #endif // !YOG_SERVER_ONLY -// version related stuff -#ifdef HAVE_CONFIG_H - #include -#endif -#ifndef PACKAGE_VERSION - #define PACKAGE_VERSION "System Specific - not using autoconf" -#endif -#include "Version.h" #include "YOGConsts.h" -#include "NetConsts.h" #include "GraphicContext.h" @@ -98,6 +74,13 @@ GlobalContainer::GlobalContainer(void) adminRouter = false; runTestGames=false; + runTestGamesCount=0; + testGamesAIPool.clear(); + testGamesMap.clear(); + testGamesMatchup.clear(); + testGamesSaveGameAs.clear(); + testGamesSeed=0; + testGamesSeedSet=false; runTestMapGeneration=false; automaticEndingGame=false; automaticEndingSteps=-1; @@ -112,7 +95,6 @@ GlobalContainer::GlobalContainer(void) terrainBlack = NULL; ressources = NULL; units = NULL; - unitsSkins = NULL; menuFont = NULL; standardFont = NULL; @@ -132,6 +114,7 @@ GlobalContainer::GlobalContainer(void) #ifndef YOG_SERVER_ONLY replayReader = NULL; replayWriter = NULL; + datasetWriter = NULL; #endif // !YOG_SERVER_ONLY assert((int)USERNAME_MAX_LENGTH==(int)BasePlayer::MAX_NAME_LENGTH); @@ -145,10 +128,6 @@ GlobalContainer::~GlobalContainer(void) delete Style::style; Style::style = &defaultStyle; - // release unit skins - if (unitsSkins) - delete unitsSkins; - // close sound if (mix) delete mix; @@ -170,316 +149,11 @@ GlobalContainer::~GlobalContainer(void) // delete replay handlers delete replayReader; replayReader = NULL; delete replayWriter; replayWriter = NULL; + delete datasetWriter; datasetWriter = NULL; #endif // !YOG_SERVER_ONLY } -/** - * changes the username to the one provided, assuming it's under the max - * username length - * TODO: modify settings.userName to be a private variable and use - * a mutator function instead - * @param name pointer to the name - */ -/*void GlobalContainer::setUsername(const std::string &name) -{ - settings.setUsername(name); -}*/ - -/** - * parses all command line arguments - * @param argc number of arguments - * @param argv the arguments themselves - * @see Glob2::main() - */ -void GlobalContainer::parseArgs(int argc, char *argv[]) -{ - for (int i=1; i \n"); - printf("zero steps will make the game run until the end.\n"); - printf("\n"); - exit(0); - } - } - else if (strcmp(argv[i], "-daemon")==0) - { - runNoX=true; - hostServer=true; - } - else if (strcmp(argv[i], "-router")==0) - { - runNoX=true; - hostRouter=true; - } - else if (strcmp(argv[i], "-admin-router")==0) - { - runNoX=true; - adminRouter=true; - } - else if (strcmp(argv[i], "-test-games")==0) - { - runTestGames=true; - automaticEndingGame = true; - automaticGameGlobalEndConditions=true; - } - else if (strcmp(argv[i], "-test-games-nox")==0) - { - runTestGames=true; - automaticEndingGame = true; - runNoX=true; - automaticGameGlobalEndConditions=true; - } - else if (strcmp(argv[i], "-test-map-gen")==0) - { - runTestMapGeneration = true; - runNoX=true; - } - else if (strcmp(argv[i], "-vs")==0) - { - if (i+1 < argc) - { - videoshotName = argv[i+1]; - i++; - } - else - { - printf("usage:\n"); - printf("-vs "); - exit(0); - } - } - else if (strcmp(argv[i], "-textshot")==0) - { - if(i+1 < argc) - { - GAGCore::DrawableSurface::translationPicturesDirectory = argv[i+1]; - i++; - } - else - { - GAGCore::DrawableSurface::translationPicturesDirectory = "."; - i++; - } - } - else if (strcmp(argv[i], "-f")==0) - { - settings.screenFlags |= GraphicContext::FULLSCREEN; - } - else if (strcmp(argv[i], "-F")==0) - { - settings.screenFlags &= ~GraphicContext::FULLSCREEN; - } - - else if (strcmp(argv[i], "-c")==0) - { - settings.screenFlags |= GraphicContext::CUSTOMCURSOR; - } - else if (strcmp(argv[i], "-C")==0) - { - settings.screenFlags &= ~GraphicContext::CUSTOMCURSOR; - } - - else if (strcmp(argv[i], "-r")==0) - { - settings.screenFlags |= GraphicContext::RESIZABLE; - } - else if (strcmp(argv[i], "-R")==0) - { - settings.screenFlags &= ~GraphicContext::RESIZABLE; - } - - else if (strcmp(argv[i], "-sgsl")==0) - { - settings.optionFlags &= ~OPTION_MAP_EDIT_USE_USL; - } - else if (strcmp(argv[i], "-usl")==0) - { - settings.optionFlags |= OPTION_MAP_EDIT_USE_USL; - } - - else if (strcmp(argv[i], "-g")==0) - { - settings.screenFlags |= GraphicContext::USEGPU; - } - else if (strcmp(argv[i], "-G")==0) - { - settings.screenFlags &= ~GraphicContext::USEGPU; - } - - else if (strcmp(argv[i], "-l")==0) - { - settings.optionFlags |= OPTION_LOW_SPEED_GFX; - } - else if (strcmp(argv[i], "-h")==0) - { - settings.optionFlags &= ~OPTION_LOW_SPEED_GFX; - } - else if (strcmp(argv[i], "-m")==0) - { - settings.mute = 1; - } - else if (strcmp(argv[i], "-M")==0) - { - settings.mute = 0; - } - else if (strcmp(argv[i], "-replay")==0) - { - replaying=true; - replayFileName=argv[i+1]; - } - else if (strcmp(argv[i], "-y")==0) - { - if(i+1 < argc) - { - // TODO: Let this option really change hostname. - yogHostName = argv[i+1]; - i++; - } - else - { - printf("usage:\n"); - printf("-y "); - exit(0); - } - } - else if (strcmp(argv[i],"-s")==0) - { - if (i+1 < argc) - { - i++; - const char *resStr=&(argv[i][0]); - int ix, iy; - int nscaned = sscanf(resStr, "%dx%dx", &ix, &iy); - if (nscaned == 2) - { - if (ix!=0 && iy!=0) - { - if (ix<640) - ix=640; - settings.screenWidth = ix; - if (iy<480) - iy=480; - settings.screenHeight = iy; - } - } - } - } - else if (strcmp(argv[i], "-d")==0) - { - if(i+1 < argc) - { - fileManager->addDir(argv[i+1]); - i++; - } - else - { - printf("usage:\n"); - printf("-d "); - exit(0); - } - } - else if (strcmp(argv[i], "-dl")==0) - { - std::cout << "Glob2 will fuse the following directories into its virtual filesystem:\n"; - const unsigned dirCount(fileManager->getDirCount()); - for (unsigned i = 0; i < dirCount; ++i) - { - std::cout << i << "\t" << fileManager->getDir(i) << std::endl; - } - exit(0); - } - else if (strcmp(argv[i], "-u")==0) - { - if(i+1 < argc) - { - settings.setUsername(argv[i+1]); - i++; - } - else - { - printf("usage:\n"); - printf("-u "); - exit(0); - } - } - else -#endif // !YOG_SERVER_ONLY - if (strcmp(argv[i], "-version")==0 || strcmp(argv[i], "--version")==0) - { - printf("\nGlobulation 2 - %s\n\n", PACKAGE_VERSION); - printf("Compiled on %s at %s\n\n", __DATE__, __TIME__); - SDL_version v; - SDL_VERSION(&v); - printf("Compiled with SDL version %d.%d.%d\n", v.major, v.minor, v.patch); - SDL_GetVersion(&v); - printf("Linked with SDL version %d.%d.%d\n\n", v.major, v.minor, v.patch); - printf("Featuring :\n"); - printf("* Map version %d\n", VERSION_MINOR); - printf("* Maps up to version %d can still be loaded\n", MINIMUM_VERSION_MINOR); - printf("* Network Protocol version %d\n", NET_PROTOCOL_VERSION); - printf("This program and all related materials are GPL, see COPYING for details.\n"); - printf("(C) 2001-2007 Stephane Magnenat, Luc-Olivier de Charriere and other contributors.\n"); - printf("See data/authors.txt for a full list.\n\n"); - printf("Type %s --help for a list of command line options.\n\n", argv[0]); - exit(0); - } - else if (strcmp(argv[i], "/?")==0 || strcmp(argv[i], "--help")==0) - { - printf("\nGlobulation 2\n"); - printf("Command line arguments:\n"); - printf("switches:\n"); -#ifndef YOG_SERVER_ONLY - printf("-c/-C\tenable/disable custom cursor\n"); - printf("-f/-F\tset/clear full screen\n"); - printf("-g/-G\tenable/disable OpenGL acceleration (GPU use)\n"); - printf("-h\thigh speed graphics: max of transparency effects\n"); - printf("-l\tlow speed graphics: disable some transparency effects\n"); - printf("-m/-M\tmute/unmute the sound (both music and speech)\n"); - printf("-r/-R\tset/clear resizable window\n"); - printf("-sgsl\tedit SGSL script in the map editor (default)\n"); - printf("-usl\tedit USL script in the map editor\n"); - printf("\n"); - printf("-d \tadd a directory to the directory search list\n"); - printf("-dl\tprint the directory search list\n"); - printf("-s \tset resolution and depth (for instance : -s 640x480\n"); - printf("-u \tspecify a user name\n"); - printf("-y \tspecify an alternative hostname for YOG server\n"); - printf("-daemon\t runs the YOG server\n"); - printf("-router\t runs the YOG game router\n"); - printf("-nox \t runs the game without using the X server\n"); - printf("-textshot \t takes pictures of various translation texts as they are drawn on the screen, requires the convert command\n"); - printf("-test-games\tCreates random games with AI and tests them\n"); - printf("-test-games-nox\tCreates random games with AI and tests them, without gui\n"); - printf("-test-map-gen\tGenerates random maps endlessly, without gui\n"); - printf("-admin-router Allows you to connect to a YOG router to do administration\n"); - printf("-vs \tsave a videoshot as name\n"); - printf("-replay \t replay the game stored in the specified file.\n"); -#endif // !YOG_SERVER_ONLY - printf("-version\tprint the version and exit\n"); - exit(0); - } - } -} +// parseArgs is defined in GlobalContainerArgs.cpp. #ifndef YOG_SERVER_ONLY void GlobalContainer::updateLoadProgressScreen(int value) @@ -524,13 +198,15 @@ void GlobalContainer::loadClient(void) // create mixer mix = new SoundMixer(settings.musicVolume, settings.voiceVolume, settings.mute); - mix->loadTrack("data/zik/intro.ogg"); - mix->loadTrack("data/zik/menu.ogg"); - mix->loadTrack("data/zik/original/a1.ogg"); - mix->loadTrack("data/zik/original/a2.ogg"); - mix->loadTrack("data/zik/original/a3.ogg"); - mix->setNextTrack(0); - mix->setNextTrack(1); + // Track slots must match the MusicTrack enum order. Engine::run may + // later overwrite the InGame* slots with a randomly chosen music dir. + mix->loadTrack("data/zik/intro.ogg", MusicTrack::Intro); + mix->loadTrack("data/zik/menu.ogg", MusicTrack::Menu); + mix->loadTrack("data/zik/original/a1.ogg", MusicTrack::InGameDefault); + mix->loadTrack("data/zik/original/a2.ogg", MusicTrack::BuildingEvent); + mix->loadTrack("data/zik/original/a3.ogg", MusicTrack::WarEvent); + mix->setNextTrack(MusicTrack::Intro); + mix->setNextTrack(MusicTrack::Menu); // create voice recorder voiceRecorder = new VoiceRecorder(); @@ -538,8 +214,9 @@ void GlobalContainer::loadClient(void) updateLoadProgressScreen(15); } - // load buildings types - buildingsTypes.load(); + // initialize building types: resolve sprite pointers and prev/next-level + // links for the static table baked into game/entities/buildings*.cpp. + buildingsTypes.init(); IntBuildingType::init(); if (!runNoX) @@ -605,7 +282,7 @@ void GlobalContainer::loadClient(void) updateLoadProgressScreen(70); // load units units = Toolkit::getSprite("data/gfx/unit"); - unitsSkins = new UnitsSkins(); + initUnitSkins(); updateLoadProgressScreen(90); // load graphics for gui @@ -643,30 +320,14 @@ void GlobalContainer::load(void) Toolkit::getStringTable()->setLang(Toolkit::getStringTable()->getLangCode(settings.language)); // load default unit types Race::loadDefault(); - // load resources types - ressourcesTypes.load("data/ressources.txt"); ///TODO: coding in english or french? english is resources, french is ressources + // Resource types are now a compile-time const table (see + // src/game/entities/resources.cpp); nothing to load here. #ifndef YOG_SERVER_ONLY loadClient(); #endif // !YOG_SERVER_ONLY } - -#ifndef YOG_SERVER_ONLY -/** - * supposed to return the checksum of all config files, but nobody - * got around to adding the unit configs to the program. Feel free to do - * that, thanks in advanced. - * - * @return the checksum of all config files - */ -Uint32 GlobalContainer::getConfigCheckSum() -{ - // TODO: add the units config - return buildingsTypes.checkSum() + ressourcesTypes.checkSum() + Race::checkSumDefault(); -} -#endif // !YOG_SERVER_ONLY - /** * returns the hostname of the computer * @return local computer's name diff --git a/src/GlobalContainer.h b/src/GlobalContainer.h index cd0ed9de4..ff0703673 100644 --- a/src/GlobalContainer.h +++ b/src/GlobalContainer.h @@ -1,27 +1,12 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GLOBALCONTAINER_H -#define __GLOBALCONTAINER_H - -#include "BuildingsTypes.h" -#include "RessourcesTypes.h" +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include + +#include "BuildingType.h" +#include "RessourceType.h" #include "Settings.h" namespace GAGCore @@ -37,9 +22,9 @@ using namespace GAGCore; class SoundMixer; class VoiceRecorder; class LogFileManager; -class UnitsSkins; class ReplayReader; class ReplayWriter; +class DatasetWriter; class GlobalContainer { @@ -97,9 +82,7 @@ class GlobalContainer Sprite *brush; Sprite *magiceffect; Sprite *particles; - - UnitsSkins *unitsSkins; - + Font *menuFont; Font *standardFont; Font *littleFont; @@ -120,7 +103,42 @@ class GlobalContainer bool automaticGameGlobalEndConditions; //! Set false if the automatic game will end if the local team wins/loses, true to wait for the entire game to finish bool runTestGames; //! runs test games - + int runTestGamesCount; //! number of test games to run (0 = infinite) + //! AI implementation IDs (AI::ImplementitionID values) eligible for random + //! AI assignment in createRandomGame. Empty means "all AIs allowed" (legacy + //! behavior: NUMBI..NICOWAR uniformly). Set via --ai-types. + //! Mutually exclusive with testGamesMatchup. + std::vector testGamesAIPool; + + //! Bare map name (no .map extension, no path) to pin createRandomGame to. + //! Empty means "pick a random map from maps/" (legacy). Set via --map. + std::string testGamesMap; + + //! Per-team AI implementation IDs for createRandomGame. testGamesMatchup[k] + //! is the AI assigned to team k. Empty means "use testGamesAIPool or random + //! default" (legacy). Set via --matchup. Validated against the loaded map's + //! getNumberOfTeams() at game creation time. Requires testGamesMap to be + //! set (else we'd have no team count to validate against). + std::vector testGamesMatchup; + + //! Path for --save-game-as: write the fully-initialised tick-0 game state + //! to this .game file before running, so the same scenario can later be + //! replayed deterministically via --nox. Empty means "do not save". Only + //! the -test-games / -test-games-nox flow honors this (the save happens + //! inside createRandomGame). Pair with GLOB2_TEST_SEED for full + //! reproducibility — the seed mirrored into GameHeader::seed is the + //! one captured in testGamesSeed below. + std::string testGamesSaveGameAs; + + //! Seed actually passed to setSyncRandSeed() at the top of runTestGames(). + //! createRandomGame() mirrors this into GameHeader::seed so the saved + //! .game file (via --save-game-as or GLOB2_DUMP_GAME) loads with the same + //! syncRand state. Without this mirror, GameHeader's constructor default + //! (time(NULL) at header-construction time) wins and the loaded game + //! diverges from the original -test-games-nox run. + Uint32 testGamesSeed; + bool testGamesSeedSet; + bool runTestMapGeneration; //! runs test map generation bool hostServer; @@ -141,15 +159,10 @@ class GlobalContainer #ifndef YOG_SERVER_ONLY ReplayReader *replayReader; //!< Reads and processes replay files, and outputs orders ReplayWriter *replayWriter; //!< Writes orders into replay files + DatasetWriter *datasetWriter; //!< Writes (state, action) records for AI training (GLOB2_DATASET_PATH) #endif // !YOG_SERVER_ONLY -public: -#ifndef YOG_SERVER_ONLY - Uint32 getConfigCheckSum(); -#endif // !YOG_SERVER_ONLY }; extern GlobalContainer *globalContainer; -#endif - diff --git a/src/GlobalContainerArgs.cpp b/src/GlobalContainerArgs.cpp new file mode 100644 index 000000000..bdb731e33 --- /dev/null +++ b/src/GlobalContainerArgs.cpp @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2007 Stephane Magnenat & Luc-Olivier de Charrière + +// Command-line argument parsing for GlobalContainer. Split out of +// GlobalContainer.cpp because parseArgs and its helpers are nearly +// 400 lines on their own and have no overlap with the asset/init code. + +#include + +#include +#include + +#include "AINames.h" +#include "FileManager.h" +#include "GlobalContainer.h" + +// version related stuff +#ifdef HAVE_CONFIG_H + #include +#endif +#ifndef PACKAGE_VERSION + #define PACKAGE_VERSION "System Specific - not using autoconf" +#endif +#include "Version.h" +#include "NetConsts.h" + +#include "GraphicContext.h" + +namespace +{ + // Parse a comma-separated AI name list into AI::ImplementitionID values. + // Used by --ai-types (which warns and skips on unknown) and --matchup + // (which warns and exits(1) on unknown). The flag name is included in the + // stderr message; valid AI names are kept canonical here so both flags + // stay in sync. + void parseAIList(const char* flagName, const std::string& list, + std::vector& out, bool exitOnUnknown) + { + std::stringstream ss(list); + std::string item; + while (std::getline(ss, item, ',')) + { + int matched = AINames::parseAIName(item); + if (matched > 0) + { + out.push_back(matched); + } + else + { + std::cerr << flagName << ": unknown AI '" << item + << "' (valid: numbi, castor, warrush, reachtoinfinity, nicowar, toubib)" << std::endl; + if (exitOnUnknown) + exit(1); + } + } + } +} + +/** + * parses all command line arguments + * @param argc number of arguments + * @param argv the arguments themselves + * @see Glob2::main() + */ +void GlobalContainer::parseArgs(int argc, char *argv[]) +{ + for (int i=1; i \n"); + printf("zero steps will make the game run until the end.\n"); + printf("\n"); + exit(0); + } + } + else if (strcmp(argv[i], "-daemon")==0) + { + runNoX=true; + hostServer=true; + } + else if (strcmp(argv[i], "-router")==0) + { + runNoX=true; + hostRouter=true; + } + else if (strcmp(argv[i], "-admin-router")==0) + { + runNoX=true; + adminRouter=true; + } + else if (strcmp(argv[i], "-test-games")==0 || strcmp(argv[i], "-test-games-nox")==0) + { + runTestGames=true; + automaticEndingGame = true; + automaticGameGlobalEndConditions=true; + if (strcmp(argv[i], "-test-games-nox")==0) + runNoX=true; + if (i + 1 < argc && argv[i + 1][0] != '-') + { + runTestGamesCount = atoi(argv[i + 1]); + i++; + } + } + else if (strcmp(argv[i], "-test-map-gen")==0) + { + runTestMapGeneration = true; + runNoX=true; + } + else if (strcmp(argv[i], "--ai-types")==0) + { + // Constrain the random AI pool used by createRandomGame for + // -test-games / -test-games-nox. Comma-separated AI names, + // case-insensitive (see AINames::parseAIName). Unknown names + // are reported on stderr and skipped. Empty pool (default) + // means "use all main AIs uniformly". + if (i + 1 < argc) + { + parseAIList("--ai-types", argv[i + 1], testGamesAIPool, false); + i++; + } + else + { + printf("--ai-types requires an argument\n"); + exit(0); + } + } + else if (strcmp(argv[i], "--map")==0) + { + // Pin the random-game map. Bare name, no .map extension — + // resolved as maps/.map by Engine::chooseRandomMap. + if (i + 1 < argc) + { + testGamesMap = argv[i + 1]; + i++; + } + else + { + printf("--map requires an argument\n"); + exit(0); + } + } + else if (strcmp(argv[i], "--matchup")==0) + { + // Per-team AI assignment for the random-game flow. Comma- + // separated AI names; matchup[k] is the AI for team k. + // Validated against the loaded map's getNumberOfTeams() in + // Engine::createRandomGame() before the game starts. + if (i + 1 < argc) + { + parseAIList("--matchup", argv[i + 1], testGamesMatchup, true); + i++; + } + else + { + printf("--matchup requires an argument\n"); + exit(0); + } + } + else if (strcmp(argv[i], "--save-game-as")==0) + { + // Write the fully-initialised tick-0 game state to a .game file + // before running. Lets a -test-games-nox scenario be replayed + // deterministically via --nox . Pair with GLOB2_TEST_SEED + // for a fully reproducible scenario: the same env var seeds + // syncRand AND gets mirrored into GameHeader::seed at save time + // (see Engine::createRandomGame in engine_init.cpp). + if (i + 1 < argc) + { + testGamesSaveGameAs = argv[i + 1]; + i++; + } + else + { + printf("--save-game-as requires an argument\n"); + exit(0); + } + } + else if (strcmp(argv[i], "-vs")==0) + { + if (i+1 < argc) + { + videoshotName = argv[i+1]; + i++; + } + else + { + printf("usage:\n"); + printf("-vs "); + exit(0); + } + } + else if (strcmp(argv[i], "-textshot")==0) + { + if(i+1 < argc) + { + GAGCore::DrawableSurface::translationPicturesDirectory = argv[i+1]; + i++; + } + else + { + GAGCore::DrawableSurface::translationPicturesDirectory = "."; + } + } + else if (strcmp(argv[i], "-f")==0) + { + settings.screenFlags |= GraphicContext::FULLSCREEN; + } + else if (strcmp(argv[i], "-F")==0) + { + settings.screenFlags &= ~GraphicContext::FULLSCREEN; + } + + else if (strcmp(argv[i], "-c")==0) + { + settings.screenFlags |= GraphicContext::CUSTOMCURSOR; + } + else if (strcmp(argv[i], "-C")==0) + { + settings.screenFlags &= ~GraphicContext::CUSTOMCURSOR; + } + + else if (strcmp(argv[i], "-r")==0) + { + settings.screenFlags |= GraphicContext::RESIZABLE; + } + else if (strcmp(argv[i], "-R")==0) + { + settings.screenFlags &= ~GraphicContext::RESIZABLE; + } + + else if (strcmp(argv[i], "-sgsl")==0) + { + settings.optionFlags &= ~OPTION_MAP_EDIT_USE_USL; + } + else if (strcmp(argv[i], "-usl")==0) + { + settings.optionFlags |= OPTION_MAP_EDIT_USE_USL; + } + + else if (strcmp(argv[i], "-g")==0) + { + settings.screenFlags |= GraphicContext::USEGPU; + } + else if (strcmp(argv[i], "-G")==0) + { + settings.screenFlags &= ~GraphicContext::USEGPU; + } + + else if (strcmp(argv[i], "-l")==0) + { + settings.optionFlags |= OPTION_LOW_SPEED_GFX; + } + else if (strcmp(argv[i], "-h")==0) + { + settings.optionFlags &= ~OPTION_LOW_SPEED_GFX; + } + else if (strcmp(argv[i], "-m")==0) + { + settings.mute = 1; + } + else if (strcmp(argv[i], "-M")==0) + { + settings.mute = 0; + } + else if (strcmp(argv[i], "-replay")==0) + { + if (i+1 < argc) + { + replaying=true; + replayFileName=argv[i+1]; + i++; + } + else + { + printf("usage:\n"); + printf("-replay \n"); + exit(0); + } + } + else if (strcmp(argv[i], "-y")==0) + { + if(i+1 < argc) + { + // TODO: Let this option really change hostname. + yogHostName = argv[i+1]; + i++; + } + else + { + printf("usage:\n"); + printf("-y "); + exit(0); + } + } + else if (strcmp(argv[i],"-s")==0) + { + if (i+1 < argc) + { + i++; + const char *resStr=&(argv[i][0]); + int ix, iy; + int nscaned = sscanf(resStr, "%dx%dx", &ix, &iy); + if (nscaned == 2) + { + if (ix!=0 && iy!=0) + { + if (ix<640) + ix=640; + settings.screenWidth = ix; + if (iy<480) + iy=480; + settings.screenHeight = iy; + } + } + } + } + else if (strcmp(argv[i], "-d")==0) + { + if(i+1 < argc) + { + fileManager->addDir(argv[i+1]); + i++; + } + else + { + printf("usage:\n"); + printf("-d "); + exit(0); + } + } + else if (strcmp(argv[i], "-dl")==0) + { + std::cout << "Glob2 will fuse the following directories into its virtual filesystem:\n"; + const unsigned dirCount(fileManager->getDirCount()); + for (unsigned i = 0; i < dirCount; ++i) + { + std::cout << i << "\t" << fileManager->getDir(i) << std::endl; + } + exit(0); + } + else if (strcmp(argv[i], "-u")==0) + { + if(i+1 < argc) + { + settings.setUsername(argv[i+1]); + i++; + } + else + { + printf("usage:\n"); + printf("-u "); + exit(0); + } + } + else +#endif // !YOG_SERVER_ONLY + if (strcmp(argv[i], "-version")==0 || strcmp(argv[i], "--version")==0) + { + printf("\nGlobulation 2 - %s\n\n", PACKAGE_VERSION); + printf("Compiled on %s at %s\n\n", __DATE__, __TIME__); + SDL_version v; + SDL_VERSION(&v); + printf("Compiled with SDL version %d.%d.%d\n", v.major, v.minor, v.patch); + SDL_GetVersion(&v); + printf("Linked with SDL version %d.%d.%d\n\n", v.major, v.minor, v.patch); + printf("Featuring :\n"); + printf("* Map version %d\n", VERSION_MINOR); + printf("* Maps up to version %d can still be loaded\n", MINIMUM_VERSION_MINOR); + printf("* Network Protocol version %d\n", NET_PROTOCOL_VERSION); + printf("This program and all related materials are GPL, see COPYING for details.\n"); + printf("(C) 2001-2007 Stephane Magnenat, Luc-Olivier de Charriere and other contributors.\n"); + printf("See data/authors.txt for a full list.\n\n"); + printf("Type %s --help for a list of command line options.\n\n", argv[0]); + exit(0); + } + else if (strcmp(argv[i], "/?")==0 || strcmp(argv[i], "--help")==0) + { + printf("\nGlobulation 2\n"); + printf("Command line arguments:\n"); + printf("switches:\n"); +#ifndef YOG_SERVER_ONLY + printf("-c/-C\tenable/disable custom cursor\n"); + printf("-f/-F\tset/clear full screen\n"); + printf("-g/-G\tenable/disable OpenGL acceleration (GPU use)\n"); + printf("-h\thigh speed graphics: max of transparency effects\n"); + printf("-l\tlow speed graphics: disable some transparency effects\n"); + printf("-m/-M\tmute/unmute the sound (both music and speech)\n"); + printf("-r/-R\tset/clear resizable window\n"); + printf("-sgsl\tedit SGSL script in the map editor (default)\n"); + printf("-usl\tedit USL script in the map editor\n"); + printf("\n"); + printf("-d \tadd a directory to the directory search list\n"); + printf("-dl\tprint the directory search list\n"); + printf("-s \tset resolution and depth (for instance : -s 640x480\n"); + printf("-u \tspecify a user name\n"); + printf("-y \tspecify an alternative hostname for YOG server\n"); + printf("-daemon\t runs the YOG server\n"); + printf("-router\t runs the YOG game router\n"); + printf("-nox \t runs the game without using the X server\n"); + printf("-textshot \t takes pictures of various translation texts as they are drawn on the screen, requires the convert command\n"); + printf("-test-games\tCreates random games with AI and tests them\n"); + printf("-test-games-nox\tCreates random games with AI and tests them, without gui\n"); + printf("--ai-types \tcomma-separated AI names to draw from in -test-games* (default: all)\n"); + printf("\t\tvalid: numbi, castor, warrush, reachtoinfinity, nicowar, toubib\n"); + printf("--map \tpin the map for -test-games* (resolved as maps/.map)\n"); + printf("--matchup \tcomma-separated per-team AI names; matchup[k] plays team k\n"); + printf("\t\trequires --map; mutually exclusive with --ai-types\n"); + printf("--save-game-as \twrite the tick-0 .game file before running -test-games*\n"); + printf("\t\t(pair with GLOB2_TEST_SEED for a reproducible scenario)\n"); + printf("-test-map-gen\tGenerates random maps endlessly, without gui\n"); + printf("-admin-router Allows you to connect to a YOG router to do administration\n"); + printf("-vs \tsave a videoshot as name\n"); + printf("-replay \t replay the game stored in the specified file.\n"); +#endif // !YOG_SERVER_ONLY + printf("-version\tprint the version and exit\n"); + exit(0); + } + } + + // Cross-flag validation for the random-game family. Fail-fast here + // before any expensive setup (map listing, etc.) runs. + if (!testGamesMatchup.empty() && testGamesMap.empty()) + { + std::cerr << "--matchup requires --map; we need to know the map's " + << "team count to validate the matchup before starting a game" + << std::endl; + exit(1); + } + if (!testGamesMatchup.empty() && !testGamesAIPool.empty()) + { + std::cerr << "--matchup and --ai-types are mutually exclusive: " + << "--matchup pins each team's AI; --ai-types randomizes within " + << "a pool. Use one or the other." << std::endl; + exit(1); + } + if (!testGamesSaveGameAs.empty() && !runTestGames) + { + std::cerr << "--save-game-as requires -test-games or -test-games-nox; " + << "the save happens at random-game creation time, which only " + << "runs in those modes" << std::endl; + exit(1); + } +} diff --git a/src/Gradient.cpp b/src/Gradient.cpp index c1a3bea0d..da868869e 100644 --- a/src/Gradient.cpp +++ b/src/Gradient.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "Gradient.h" diff --git a/src/Gradient.h b/src/Gradient.h index 493bd8ee3..45743e142 100644 --- a/src/Gradient.h +++ b/src/Gradient.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef Gradient_h -#define Gradient_h +#pragma once #include "Building.h" #include "Map.h" @@ -92,19 +74,9 @@ template Gradient: template void Gradient::computeFullGradient(Map* map) { - Tint *listedAddr = new Tint[width*height]; - size_t listCountWrite = 0; - - // We set the obstacle and free places for (size_t i=0; i<(width*height); i++) - { - int n = method.getValue(map, i); - gradient[i] = n; - if(n == 255) - listedAddr[listCountWrite++] = i; - } - - map->updateGlobalGradientVersionSimple(gradient, listedAddr, listCountWrite, Map::GT_UNDEFINED); + gradient[i] = method.getValue(map, i); + map->updateGlobalGradient(gradient); } template BuildingGradientMethod::BuildingGradientMethod(Building* building) @@ -118,15 +90,13 @@ template Uint8 BuildingGradientMethodposX; int posY=building->posY; - int posW=building->type->width; Uint32 teamMask=building->owner->me; Uint16 bgid=building->gid; - + Case& c=map->cases[square]; - + bool isWarFlag=false; bool isWarFlagSquare=false; - bool isClearingFlag=false; bool isClearingFlagSquare=false; if (building->type->isVirtual) { @@ -143,7 +113,6 @@ template Uint8 BuildingGradientMethodclearingRessources[c.ressource.type]) @@ -182,4 +151,3 @@ template Uint8 BuildingGradientMethod or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __HEADER_H -#define __HEADER_H +#pragma once #include "GAGSys.h" -#endif diff --git a/src/IRCThread.h b/src/IRCThread.h deleted file mode 100644 index 7e04bef1c..000000000 --- a/src/IRCThread.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef IRCThread_h -#define IRCThread_h - -#include "IRC.h" -#include -#include -#include -#include - -class IRCThreadMessage; - -///IRC thread manages IRC -class IRCThread -{ -public: - IRCThread(std::queue >& outgoing, boost::recursive_mutex& outgoingMutex); - - ///Runs the IRC thread - void operator()(); - - ///Sends this IRC thread a message - void sendMessage(boost::shared_ptr message); - - ///This returns whether the thread has exited - bool hasThreadExited(); -private: - ///Sends this IRC message back to the main thread - void sendToMainThread(boost::shared_ptr message); - - IRC irc; - std::string channel; - - std::queue > incoming; - std::queue >& outgoing; - boost::recursive_mutex incomingMutex; - boost::recursive_mutex& outgoingMutex; - bool hasExited; -}; - -#endif diff --git a/src/IntBuildingType.h b/src/IntBuildingType.h deleted file mode 100644 index 539e2a59a..000000000 --- a/src/IntBuildingType.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __INT_BUILDING_TYPE_H -#define __INT_BUILDING_TYPE_H - -#include -#include -#include - -struct IntBuildingType -{ - enum Number - { - SWARM_BUILDING=0, - FOOD_BUILDING=1, - HEAL_BUILDING=2, - - WALKSPEED_BUILDING=3, - SWIMSPEED_BUILDING=4, - ATTACK_BUILDING=5, - SCIENCE_BUILDING=6, - - DEFENSE_BUILDING=7, - - EXPLORATION_FLAG=8, - WAR_FLAG=9, - CLEARING_FLAG=10, - - STONE_WALL=11, - - MARKET_BUILDING=12, - - NB_BUILDING - }; - - static std::map conversionMap; - static std::vector reverseConversionMap; - static std::string null; - - static int shortNumberFromType(const std::string &type); - static const std::string & typeFromShortNumber(int number); - - static void init(void); -}; - -#endif diff --git a/src/Integrity.h b/src/Integrity.h index 7a937ac83..e7f240a31 100644 --- a/src/Integrity.h +++ b/src/Integrity.h @@ -1,24 +1,9 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __INTEGRITY_H -#define __INTEGRITY_H +#include #define checkInvariant(x) \ if (!(x)) \ @@ -34,4 +19,3 @@ return false;\ } \ -#endif \ No newline at end of file diff --git a/src/KeyboardManager.cpp b/src/KeyboardManager.cpp index d7fa6f882..0e842e7bc 100644 --- a/src/KeyboardManager.cpp +++ b/src/KeyboardManager.cpp @@ -1,22 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include +#include #include "FileManager.h" #include "FormatableString.h" #include "GameGUIKeyActions.h" diff --git a/src/KeyboardManager.h b/src/KeyboardManager.h index 4916fb9d2..2076ccb0e 100644 --- a/src/KeyboardManager.h +++ b/src/KeyboardManager.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __KEYBOARD_MANAGER_H -#define __KEYBOARD_MANAGER_H +#pragma once #include #include @@ -108,4 +92,3 @@ class KeyboardManager ShortcutMode mode; }; -#endif diff --git a/src/LANFindScreen.cpp b/src/LANFindScreen.cpp index ad0150d3c..72ce1e8dd 100644 --- a/src/LANFindScreen.cpp +++ b/src/LANFindScreen.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "LANFindScreen.h" #include "Utilities.h" @@ -34,7 +17,7 @@ #include "YOGClientGameListManager.h" using namespace GAGGUI; -using boost::shared_ptr; +using std::shared_ptr; LANFindScreen::LANFindScreen() { @@ -119,7 +102,7 @@ void LANFindScreen::onAction(Widget *source, Action action, int par1, int par2) while(client->getConnectionState() != YOGClient::ClientOnStandby) client->update(); - boost::shared_ptr game(new MultiplayerGame(client)); + std::shared_ptr game(new MultiplayerGame(client)); client->setMultiplayerGame(game); while (client->getGameListManager()->getGameList().size() == 0) @@ -138,7 +121,7 @@ void LANFindScreen::onAction(Widget *source, Action action, int par1, int par2) listener.disableListening(); int rc = screen.execute(globalContainer->gfx, 40); listener.enableListening(); - client->setMultiplayerGame(boost::shared_ptr()); + client->setMultiplayerGame(std::shared_ptr()); if(rc == -1) endExecute(-1); } diff --git a/src/LANFindScreen.h b/src/LANFindScreen.h index 63771b226..69d9a52fa 100644 --- a/src/LANFindScreen.h +++ b/src/LANFindScreen.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __LAN_FIND_SCREEN_H -#define __LAN_FIND_SCREEN_H +#pragma once #include "Glob2Screen.h" #include "NetBroadcastListener.h" @@ -68,4 +50,3 @@ class LANFindScreen : public Glob2Screen NetBroadcastListener listener; }; -#endif diff --git a/src/LANGameInformation.cpp b/src/LANGameInformation.cpp index 0fb69edc3..f3b1a7cc1 100644 --- a/src/LANGameInformation.cpp +++ b/src/LANGameInformation.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "LANGameInformation.h" #include "Stream.h" diff --git a/src/LANGameInformation.h b/src/LANGameInformation.h index 9d1a61fa7..7138ce0ae 100644 --- a/src/LANGameInformation.h +++ b/src/LANGameInformation.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __LANGameInformation_h -#define __LANGameInformation_h +#pragma once #include "YOGGameInfo.h" #include "SDL_net.h" @@ -55,4 +39,3 @@ class LANGameInformation -#endif diff --git a/src/LANMenuScreen.cpp b/src/LANMenuScreen.cpp index 598e49354..92a81c2d7 100644 --- a/src/LANMenuScreen.cpp +++ b/src/LANMenuScreen.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "ChooseMapScreen.h" #include "FormatableString.h" @@ -33,7 +16,7 @@ #include #include "YOGServer.h" -using boost::shared_ptr; +using std::shared_ptr; LANMenuScreen::LANMenuScreen() { @@ -64,7 +47,7 @@ void LANMenuScreen::onAction(Widget *source, Action action, int par1, int par2) } else if(par1 == HOST) { - ChooseMapScreen cms("maps", "map", false, "games", "game", NULL); + ChooseMapScreen cms("maps", "map", false, "games", "game", false); int rc = cms.execute(globalContainer->gfx, 40); if(rc == ChooseMapScreen::OK) { @@ -86,7 +69,7 @@ void LANMenuScreen::onAction(Widget *source, Action action, int par1, int par2) while(client->getConnectionState() != YOGClient::ClientOnStandby) client->update(); - boost::shared_ptr game(new MultiplayerGame(client)); + std::shared_ptr game(new MultiplayerGame(client)); client->setMultiplayerGame(game); std::string name = FormatableString(Toolkit::getStringTable()->getString("[%0's game]")).arg(globalContainer->settings.getUsername()); game->createNewGame(name); @@ -96,7 +79,7 @@ void LANMenuScreen::onAction(Widget *source, Action action, int par1, int par2) Glob2TabScreen screen(true); MultiplayerGameScreen* mgs = new MultiplayerGameScreen(&screen, game, client); int rc = screen.execute(globalContainer->gfx, 40); - client->setMultiplayerGame(boost::shared_ptr()); + client->setMultiplayerGame(std::shared_ptr()); if(rc == -1) endExecute(-1); else @@ -116,12 +99,6 @@ void LANMenuScreen::onAction(Widget *source, Action action, int par1, int par2) } } -void LANMenuScreen::paint(int x, int y, int w, int h) -{ - gfx->drawFilledRect(x, y, w, h, 0, 0, 0); - //gfxCtx->drawSprite(0, 0, arch, 0); -} - int LANMenuScreen::menu(void) { return LANMenuScreen().execute(globalContainer->gfx, 30); diff --git a/src/LANMenuScreen.h b/src/LANMenuScreen.h index 2c6897463..feab2e8a8 100644 --- a/src/LANMenuScreen.h +++ b/src/LANMenuScreen.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __LAN_MENU_SCREEN_H -#define __LAN_MENU_SCREEN_H +#pragma once #include "Glob2Screen.h" @@ -32,7 +14,6 @@ class LANMenuScreen : public Glob2Screen LANMenuScreen(); virtual ~LANMenuScreen(); void onAction(Widget *source, Action action, int par1, int par2); - void paint(int x, int y, int w, int h); static int menu(void); enum @@ -53,4 +34,3 @@ class LANMenuScreen : public Glob2Screen }; }; -#endif diff --git a/src/LogFileManager.cpp b/src/LogFileManager.cpp index eabde9cbf..fb273376a 100644 --- a/src/LogFileManager.cpp +++ b/src/LogFileManager.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "LogFileManager.h" #include "FileManager.h" diff --git a/src/LogFileManager.h b/src/LogFileManager.h index d0697fd12..65369e3fc 100644 --- a/src/LogFileManager.h +++ b/src/LogFileManager.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GLOB_LOG_FILE_MANAGER_H -#define __GLOB_LOG_FILE_MANAGER_H +#pragma once #include "Header.h" #include @@ -68,4 +51,3 @@ class LogFileManager FileManager *fileManager; }; -#endif diff --git a/src/MainMenuScreen.cpp b/src/MainMenuScreen.cpp index 75ebde618..b8993fa1e 100644 --- a/src/MainMenuScreen.cpp +++ b/src/MainMenuScreen.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charri�e - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "MainMenuScreen.h" #include "GlobalContainer.h" diff --git a/src/MainMenuScreen.h b/src/MainMenuScreen.h index 281d7757a..43877fca2 100644 --- a/src/MainMenuScreen.h +++ b/src/MainMenuScreen.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __MAIN_MENU_SCREEN_H -#define __MAIN_MENU_SCREEN_H +#pragma once #include "Glob2Screen.h" @@ -46,4 +29,3 @@ class MainMenuScreen:public Glob2Screen static int menu(void); }; -#endif diff --git a/src/Map.cpp b/src/Map.cpp deleted file mode 100644 index 0a1630d40..000000000 --- a/src/Map.cpp +++ /dev/null @@ -1,5271 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#include "Map.h" -#include "Game.h" -#include "Utilities.h" -#include "GlobalContainer.h" -#include "LogFileManager.h" -#include "Unit.h" - -#include -#include -#include -#include - - -#if defined( LOG_GRADIENT_LINE_GRADIENT ) -#include -#endif - -#define UPDATE_MAX(max,value) { if (value>(max)) (max)=value; } - -// use deltaOne for first perpendicular direction -static const int deltaOne[8][2]={ - { 0, -1}, - { 1, 0}, - { 0, 1}, - {-1, 0}, - {-1, -1}, - { 1, -1}, - { 1, 1}, - {-1, 1}}; - -// use tabClose for original circular direction -static const int tabClose[8][2]={ - {-1, -1}, - { 0, -1}, - { 1, -1}, - { 1, 0}, - { 1, 1}, - { 0, 1}, - {-1, 1}, - {-1, 0}}; - -// use tabMiniFar for all miniGrad far points -static const int tabFar[16][2]={ - {-2, -2}, - {-1, -2}, - { 0, -2}, - { 1, -2}, - { 2, -2}, - { 2, -1}, - { 2, 0}, - { 2, 1}, - { 2, 2}, - { 1, 2}, - { 0, 2}, - {-1, 2}, - {-2, 2}, - {-2, 1}, - {-2, 0}, - {-2, -1}}; - -// helper to fill vectors -template -void fill(std::vector& vec, const T& value) { - std::fill(vec.begin(), vec.end(), value); -} - -Map::Map() -{ - game=NULL; - - arraysBuilt=false; - - aStarPoints = NULL; - for (int t=0; tlogFileManager->getFile("Map.log"); - std::fill(incRessourceLog, incRessourceLog + 16, 0); - - areaNames.resize(9); - - fertilityMaximum = 0; -} - -Map::~Map(void) -{ - FILE *resLogFile = globalContainer->logFileManager->getFile("IncRessourceLog.log"); - for (int i=0; i<=11; i++) - fprintf(resLogFile, "incRessourceLog[%2d] =%8d\n", i, incRessourceLog[i]); - fprintf(resLogFile, "\n"); - fflush(resLogFile); - clear(); -} - -void Map::clear() -{ - logAtClear(); - if (arraysBuilt) - { - for (int t=0; t%5d]:%5d\n", vi * (size / 64), (vi + 1) * (size / 64) - 1, sum); - } - /*fprintf(logFile, "listCountSizeStats:\n"); - for (size_t i = 0; i< size; i++) - if (listCountSizeStats[i][i]) - fprintf(logFile, "[%5d]:%5d\n", i, listCountSizeStats[i][i]);*/ - } - } - #endif -} - -void Map::setSize(int wDec, int hDec, TerrainType terrainType) -{ - clear(); - - assert(wDec<16); - assert(hDec<16); - this->wDec=wDec; - this->hDec=hDec; - w=1<>4; - hSector=h>>4; - sizeSector=wSector*hSector; - - if(sectors) - delete[] sectors; - sectors=new Sector[sizeSector]; - - aStarPoints=new AStarAlgorithmPoint[w*h]; - - - immobileUnits = new Uint8[w*h]; - for (int i=0; igame=game; - assert(arraysBuilt); - assert(sectors); - for (int i=0; i=16); - - Sint32 versionMinor = header.getVersionMinor(); - - clear(); - - stream->readEnterSection("Map"); - - char signature[4]; - stream->read(signature, 4, "signatureStart"); - if (memcmp(signature, "MapB", 4)!=0) - { - fprintf(stderr, "Map:: Failed to find signature at the beginning of Map.\n"); - return false; - } - - // We load and compute size: - wDec = stream->readSint32("wDec"); - hDec = stream->readSint32("hDec"); - w = 1<read(undermap, size, "undermap"); - stream->readEnterSection("cases"); - for (size_t i=0; ireadEnterSection(i); - mapDiscovered[i] = stream->readUint32("mapDiscovered"); - - cases[i].terrain = stream->readUint16("terrain"); - cases[i].building = stream->readUint16("building"); - - stream->read(&(cases[i].ressource), 4, "ressource"); - cases[i].groundUnit = stream->readUint16("groundUnit"); - cases[i].airUnit = stream->readUint16("airUnit"); - cases[i].forbidden = stream->readUint32("forbidden"); - if(versionMinor < 62) - stream->readUint32("hiddenForbidden"); - cases[i].guardArea = stream->readUint32("guardArea"); - cases[i].clearArea = stream->readUint32("clearArea"); - cases[i].scriptAreas = stream->readUint16("scriptAreas"); - cases[i].canRessourcesGrow = stream->readUint8("canRessourcesGrow"); - if(versionMinor >= 63) - cases[i].fertility = stream->readUint16("fertility"); - fertilityMaximum = std::max(fertilityMaximum, cases[i].fertility); - - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - for(int n=0; n<9; ++n) - { - stream->readEnterSection(n); - setAreaName(n, stream->readText("areaname")); - stream->readLeaveSection(); - } - - if (game) - { - /* Must set game field before following action as they - may need it (in particular - makeDiscoveredAreasExplored uses it). */ - this->game=game; - - // This is a game, so we do compute gradients - for (int t=0; treadSint32("wSector"); - hSector = stream->readSint32("hSector"); - sizeSector = wSector*hSector; - assert(sectors == NULL); - sectors = new Sector[sizeSector]; - - arraysBuilt = true; - - stream->readEnterSection("sectors"); - for (int i=0; ireadEnterSection(i); - if (!sectors[i].load(stream, this->game, versionMinor)) - { - stream->readLeaveSection(3); - return false; - } - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->read(signature, 4, "signatureEnd"); - stream->readLeaveSection(); - - if (memcmp(signature, "MapE", 4)!=0) - { - fprintf(stderr, "Map:: Failed to find signature at the end of Map.\n"); - return false; - } - - return true; -} - -void Map::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Map"); - stream->write("MapB", 4, "signatureStart"); - - // We save size: - stream->writeSint32(wDec, "wDec"); - stream->writeSint32(hDec, "hDec"); - - // We write what's inside the map: - stream->write(undermap, size, "undermap"); - stream->writeEnterSection("cases"); - for (size_t i=0; iwriteEnterSection(i); - stream->writeUint32(mapDiscovered[i], "mapDiscovered"); - - stream->writeUint16(cases[i].terrain, "terrain"); - stream->writeUint16(cases[i].building, "building"); - - stream->write(&(cases[i].ressource), 4, "ressource"); - - stream->writeUint16(cases[i].groundUnit, "groundUnit"); - stream->writeUint16(cases[i].airUnit, "airUnit"); - stream->writeUint32(cases[i].forbidden, "forbidden"); - stream->writeUint32(cases[i].guardArea, "guardArea"); - stream->writeUint32(cases[i].clearArea, "clearArea"); - stream->writeUint16(cases[i].scriptAreas, "scriptAreas"); - stream->writeUint8(cases[i].canRessourcesGrow, "canRessourcesGrow"); - stream->writeUint16(cases[i].fertility, "fertility"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - //Save area names - for(int n=0; n<9; ++n) - { - stream->writeEnterSection(n); - stream->writeText(getAreaName(n), "areaname"); - stream->writeLeaveSection(); - } - - // We save sectors: - stream->writeSint32(wSector, "wSector"); - stream->writeSint32(hSector, "hSector"); - stream->writeEnterSection("sectors"); - for (int i=0; iwriteEnterSection(i); - sectors[i].save(stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->write("MapE", 4, "signatureEnd"); - stream->writeLeaveSection(); -} - -void Map::addTeam(void) -{ - int numberOfTeam=game->mapHeader.getNumberOfTeams(); - int oldNumberOfTeam=numberOfTeam-1; - assert(numberOfTeam>0); - - for (int t=0; tmapHeader.getNumberOfTeams(); -// int oldNumberOfTeam=numberOfTeam+1; - assert(numberOfTeamressourcesTypes.get(r.type)->expendable) - { - // we extand ressource: - int dx, dy; - Unit::dxDyFromDirection((syncRand()&7), &dx, &dy); - int nx=x+dx; - int ny=y+dy; - if(canRessourcesGrow(nx, ny)) - incRessource(nx, ny, r.type, r.variety); - } - } - } - } - } -} - -#ifndef YOG_SERVER_ONLY -void Map::syncStep(Uint32 stepCounter) -{ - growRessources(); - for (int i=0; i> 1) & 31; - if (team < game->mapHeader.getNumberOfTeams()) - updateExploredArea(team); - } - - // We only update one gradient per step: - bool updated=false; - while (!updated) - { - int numberOfTeam=game->mapHeader.getNumberOfTeams(); - for (int t=0; t=0); - assert(id=0); - assert(teammyBuildings[id]->seenByMask|=sharedVision; - } -} - -void Map::setMapBuildingsDiscovered(int x, int y, int w, int h, Uint32 sharedVision, Team *teams[Team::MAX_COUNT]) -{ - for (int dx=x; dxressourcesTypes.get(r.type); - - if (!fulltype->shrinkable) - return; - if (fulltype->eternal) - { - if (r.amount > 0) - r.amount--; - } - else - { - if (!fulltype->granular || r.amount<=1) - r.clear(); - else - r.amount--; - } -} - -void Map::decRessource(int x, int y, int ressourceType) -{ - if (isRessourceTakeable(x, y, ressourceType)) - decRessource(x, y); -} - -bool Map::incRessource(int x, int y, int ressourceType, int variety) -{ - Ressource &r = getCase(x, y).ressource; - const RessourceType *fulltype; - incRessourceLog[0]++; - if (r.type == NO_RES_TYPE) - { - incRessourceLog[1]++; - if (getBuilding(x, y) != NOGBID) - return false; - incRessourceLog[2]++; - if (getGroundUnit(x, y) != NOGUID) - return false; - incRessourceLog[3]++; - - fulltype = globalContainer->ressourcesTypes.get(ressourceType); - if (getTerrainType(x, y) == fulltype->terrain) - { - r.type = ressourceType; - r.variety = variety; - r.amount = 1; - r.animation = 0; - incRessourceLog[4]++; - return true; - } - else - { - incRessourceLog[5]++; - return false; - } - } - else - { - fulltype = globalContainer->ressourcesTypes.get(r.type); - incRessourceLog[6]++; - } - - incRessourceLog[7]++; - if (r.type != ressourceType) - return false; - incRessourceLog[8]++; - if (!fulltype->shrinkable) - return false; - incRessourceLog[9]++; - if (r.amount < fulltype->sizesCount) - { - incRessourceLog[10]++; - r.amount++; - return true; - } - else - { - incRessourceLog[11]++; - r.amount--; - } - return false; -} - -bool Map::isFreeForGroundUnit(int x, int y, bool canSwim, Uint32 teamMask) const -{ - if (isRessource(x, y)) - return false; - if (getBuilding(x, y)!=NOGBID) - return false; - if (getGroundUnit(x, y)!=NOGUID) - return false; - if (!canSwim && isWater(x, y)) - return false; - if (getForbidden(x, y)&teamMask) - return false; - return true; -} - -bool Map::isFreeForGroundUnitNoForbidden(int x, int y, bool canSwim) const -{ - if (isRessource(x, y)) - return false; - if (getBuilding(x, y)!=NOGBID) - return false; - if (getGroundUnit(x, y)!=NOGUID) - return false; - if (!canSwim && isWater(x, y)) - return false; - return true; -} - -bool Map::isFreeForBuilding(int x, int y) const -{ - if (isRessource(x, y)) - return false; - if (getBuilding(x, y)!=NOGBID) - return false; - if (getGroundUnit(x, y)!=NOGUID) - return false; - if (isGrass(x, y)) - return true; - else - return false; -} - -bool Map::isFreeForBuilding(int x, int y, int w, int h) const -{ - for (int yi=y; yiposX; - int y=unit->posY; - - for (int tdx=-1; tdx<=1; tdx++) - for (int tdy=-1; tdy<=1; tdy++) - if (getBuilding(x+tdx, y+tdy)==gbid) - { - *dx=tdx; - *dy=tdy; - return true; - } - return false; -} - -bool Map::doesPosTouchBuilding(int x, int y, Uint16 gbid) const -{ - for (int tdx=-1; tdx<=1; tdx++) - for (int tdy=-1; tdy<=1; tdy++) - if (getBuilding(x+tdx, y+tdy)==gbid) - return true; - return false; -} - -bool Map::doesPosTouchBuilding(int x, int y, Uint16 gbid, int *dx, int *dy) const -{ - for (int tdx=-1; tdx<=1; tdx++) - for (int tdy=-1; tdy<=1; tdy++) - if (getBuilding(x+tdx, y+tdy)==gbid) - { - *dx=tdx; - *dy=tdy; - return true; - } - return false; -} - -bool Map::doesUnitTouchRessource(Unit *unit, int *dx, int *dy) const -{ - int x=unit->posX; - int y=unit->posY; - Uint32 me=unit->owner->me; - for (int tdx=-1; tdx<=1; tdx++) - for (int tdy=-1; tdy<=1; tdy++) - if (isRessource(x+tdx, y+tdy) && ((getForbidden(x+tdx, y+tdy)&me)==0)) - { - *dx=tdx; - *dy=tdy; - return true; - } - return false; -} - -bool Map::doesUnitTouchRessource(Unit *unit, int ressourceType, int *dx, int *dy) const -{ - int x=unit->posX; - int y=unit->posY; - Uint32 me=unit->owner->me; - for (int tdx=-1; tdx<=1; tdx++) - for (int tdy=-1; tdy<=1; tdy++) - if (isRessourceTakeable(x+tdx, y+tdy, ressourceType) && ((getForbidden(x+tdx, y+tdy)&me)==0)) - { - *dx=tdx; - *dy=tdy; - return true; - } - return false; -} - -bool Map::doesPosTouchRessource(int x, int y, int ressourceType, int *dx, int *dy) const -{ - for (int tdx=-1; tdx<=1; tdx++) - for (int tdy=-1; tdy<=1; tdy++) - if (isRessourceTakeable(x+tdx, y+tdy,ressourceType)) - { - *dx=tdx; - *dy=tdy; - return true; - } - return false; -} - -//! This method gives a good direction to hit for a warrior, and return false if nothing was found. -//! Currently, it chooses to hit any turret if available, then units, then other buildings. -bool Map::doesUnitTouchEnemy(Unit *unit, int *dx, int *dy) const -{ - int x=unit->posX; - int y=unit->posY; - int bestTime=256;//Shorter is better - int bdx=0, bdy=0; - - Uint32 enemies=unit->owner->enemies; - for (int tdx=-1; tdx<=1; tdx++) - for (int tdy=-1; tdy<=1; tdy++) - { - Sint32 gbid=getBuilding(x+tdx, y+tdy); - if (gbid!=NOGBID) - { - int otherTeam=Building::GIDtoTeam(gbid); - Uint32 otherTeamMask=1<teams[otherTeam]); - int otherID=Building::GIDtoID(gbid); - Building *b=game->teams[otherTeam]->myBuildings[otherID]; - if (!b->type->defaultUnitStayRange) - { - if (b->type->shootingRange) - { - bdx=tdx; - bdy=tdy; - bestTime=0; - } - else if (bestTime>255) - { - bdx=tdx; - bdy=tdy; - bestTime=255; - } - } - } - } - Sint32 guid=getGroundUnit(x+tdx, y+tdy); - if (guid!=NOGUID) - { - int otherTeam=Unit::GIDtoTeam(guid); - Uint32 otherTeamMask=1<teams[otherTeam]); - int otherID=Unit::GIDtoID(guid); - Unit *otherUnit=game->teams[otherTeam]->myUnits[otherID]; - if ((unit->owner->sharedVisionExchange & otherTeamMask)==0) - { - int time=(256-otherUnit->delta)/otherUnit->speed; - if (time>1); dx>1)+1; dx++) - for (int dy=y-(l>>1); dy>1)+1; dy++) - { - if (t==GRASS) - { - if (getUMTerrain(dx,dy-1)==WATER) - { -// setNoRessource(dx, dy-1, 1); - setUMTerrain(dx,dy-1,SAND); - } - if (getUMTerrain(dx,dy+1)==WATER) - { -// setNoRessource(dx, dy+1, 1); - setUMTerrain(dx,dy+1,SAND); - } - - if (getUMTerrain(dx-1,dy)==WATER) - { -// setNoRessource(dx-1, dy, 1); - setUMTerrain(dx-1,dy,SAND); - } - if (getUMTerrain(dx+1,dy)==WATER) - { -// setNoRessource(dx+1, dy, 1); - setUMTerrain(dx+1,dy,SAND); - } - - if (getUMTerrain(dx-1,dy-1)==WATER) - { -// setNoRessource(dx-1, dy-1, 1); - setUMTerrain(dx-1,dy-1,SAND); - } - if (getUMTerrain(dx+1,dy-1)==WATER) - { -// setNoRessource(dx+1, dy-1, 1); - setUMTerrain(dx+1,dy-1,SAND); - } - - if (getUMTerrain(dx+1,dy+1)==WATER) - { -// setNoRessource(dx+1, dy+1, 1); - setUMTerrain(dx+1,dy+1,SAND); - } - if (getUMTerrain(dx-1,dy+1)==WATER) - { -// setNoRessource(dx-1, dy+1, 1); - setUMTerrain(dx-1,dy+1,SAND); - } - } - else if (t==WATER) - { - if (getUMTerrain(dx,dy-1)==GRASS) - { -// setNoRessource(dx, dy-1, 1); - setUMTerrain(dx,dy-1,SAND); - } - if (getUMTerrain(dx,dy+1)==GRASS) - { -// setNoRessource(dx, dy+1, 1); - setUMTerrain(dx,dy+1,SAND); - } - - if (getUMTerrain(dx-1,dy)==GRASS) - { -// setNoRessource(dx-1, dy, 1); - setUMTerrain(dx-1,dy,SAND); - } - if (getUMTerrain(dx+1,dy)==GRASS) - { -// setNoRessource(dx+1, dy, 1); - setUMTerrain(dx+1,dy,SAND); - } - - if (getUMTerrain(dx-1,dy-1)==GRASS) - { -// setNoRessource(dx-1, dy-1, 1); - setUMTerrain(dx-1,dy-1,SAND); - } - if (getUMTerrain(dx+1,dy-1)==GRASS) - { -// setNoRessource(dx+1, dy-1, 1); - setUMTerrain(dx+1,dy-1,SAND); - } - - if (getUMTerrain(dx+1,dy+1)==GRASS) - { -// setNoRessource(dx+1, dy+1, 1); - setUMTerrain(dx+1,dy+1,SAND); - } - if (getUMTerrain(dx-1,dy+1)==GRASS) - { -// setNoRessource(dx-1, dy+1, 1); - setUMTerrain(dx-1,dy+1,SAND); - } - } - setUMTerrain(dx,dy,t); - } - if (t==SAND) - regenerateMap(x-(l>>1)-1,y-(l>>1)-1,l+1,l+1); - else - regenerateMap(x-(l>>1)-2,y-(l>>1)-2,l+3,l+3); -} - -void Map::setNoRessource(int x, int y, int l) -{ - assert(l>=0); - assert(l>1); dx>1)+1; dx++) - for (int dy=y-(l>>1); dy>1)+1; dy++) - cases[coordToIndex(dx, dy)].ressource.clear(); -} - -void Map::setRessource(int x, int y, int type, int l) -{ - assert(l>=0); - assert(l>1); dx>1)+1; dx++) - for (int dy=y-(l>>1); dy>1)+1; dy++) - if (isRessourceAllowed(dx, dy, type)) - { - Ressource& rp=cases[coordToIndex(dx, dy)].ressource; - rp.type=type; - const RessourceType *rt=globalContainer->ressourcesTypes.get(type); - rp.variety=syncRand()%rt->varietiesCount; - assert(rt->sizesCount>1); - rp.amount=1+syncRand()%(rt->sizesCount-1); - rp.animation=0; - } -} - -bool Map::isRessourceAllowed(int x, int y, int type) -{ - return (getBuilding(x, y) == NOGBID) && (getGroundUnit(x, y) == NOGUID) && (getTerrainType(x, y)==globalContainer->ressourcesTypes.get(type)->terrain); -} - -bool Map::isPointSet(int n, int x, int y) const -{ - return getCase(x, y).scriptAreas & 1< (w - 16)) - x-=w; - if (y > (h - 16)) - y-=h; - *px=x<<5; - *py=y<<5; -} - -void Map::mapCaseToDisplayableVector(int mx, int my, int *px, int *py, int viewportX, int viewportY, int screenW, int screenH) const -{ - int x = (mx - viewportX + w) & wMask; - int y = (my - viewportY + h) & hMask; - if (x > (w/2 + (screenW/64))) - x-=w; - if (y > (h/2 + (screenH/64))) - y-=h; - *px=x<<5; - *py=y<<5; -} - -void Map::displayToMapCaseAligned(int mx, int my, int *px, int *py, int viewportX, int viewportY) const -{ - *px=((mx>>5)+viewportX)&getMaskW(); - *py=((my>>5)+viewportY)&getMaskH(); -} - -void Map::displayToMapCaseUnaligned(int mx, int my, int *px, int *py, int viewportX, int viewportY) const -{ - *px=(((mx+16)>>5)+viewportX)&getMaskW(); - *py=(((my+16)>>5)+viewportY)&getMaskH(); -} - -void Map::cursorToBuildingPos(int mx, int my, int buildingWidth, int buildingHeight, int *px, int *py, int viewportX, int viewportY) const -{ - int tempX, tempY; - if (buildingWidth&0x1) - tempX=((mx)>>5)+viewportX; - else - tempX=((mx+16)>>5)+viewportX; - - if (buildingHeight&0x1) - tempY=((my)>>5)+viewportY; - else - tempY=((my+16)>>5)+viewportY; - - *px=tempX&getMaskW(); - *py=tempY&getMaskH(); -} - -void Map::buildingPosToCursor(int px, int py, int buildingWidth, int buildingHeight, int *mx, int *my, int viewportX, int viewportY) const -{ - mapCaseToDisplayable(px, py, mx, my, viewportX, viewportY); - *mx+=buildingWidth*16; - *my+=buildingHeight*16; -} - -bool Map::ressourceAvailable(int teamNumber, int ressourceType, bool canSwim, int x, int y) const -{ - Uint8 g = getGradient(teamNumber, ressourceType, canSwim, x, y); - return g>1; //Because 0==obstacle, 1==no obstacle, but you don't know if there is anything around. -} - -bool Map::ressourceAvailable(int teamNumber, int ressourceType, bool canSwim, int x, int y, int *dist) const -{ - Uint8 g = getGradient(teamNumber, ressourceType, canSwim, x, y); - if (g>1) - { - *dist = 255-g; - return true; - } - else - return false; -} - -bool Map::ressourceAvailableUpdate(int teamNumber, int ressourceType, bool canSwim, int x, int y, Sint32 *targetX, Sint32 *targetY, int *dist) -{ - // distance and availability - bool result; - if (dist) - result = ressourceAvailable(teamNumber, ressourceType, canSwim, x, y, dist); - else - result = ressourceAvailable(teamNumber, ressourceType, canSwim, x, y); - - // target position - Uint8 *gradient = ressourcesGradient[teamNumber][ressourceType][canSwim]; - ressourceAvailableCount[teamNumber][ressourceType]++; - if (getGlobalGradientDestination(gradient, x, y, targetX, targetY)) - ressourceAvailableCountSuccess[teamNumber][ressourceType]++; - else - ressourceAvailableCountFailure[teamNumber][ressourceType]++; - - return result; -} - -bool Map::getGlobalGradientDestination(Uint8 *gradient, int x, int y, Sint32 *targetX, Sint32 *targetY) const -{ - // we start from our current position - int vx = x & wMask; - int vy = y & hMask; - // max is initialized to gradient value of current position - Uint8 max = gradient[coordToIndex(vx, vy)]; - - bool result = false; - // for up to 255 steps, we follow gradient - for (int count=0; count<255; count++) - { - bool found = false; - int vddx = 0; - int vddy = 0; - - // search all directions - for (int d=0; d<8; d++) - { - int ddx = deltaOne[d][0]; - int ddy = deltaOne[d][1]; - Uint8 g = gradient[coordToIndex(vx + ddx, vy + ddy)]; - if (g>max) - { - max = g; - vddx = ddx; - vddy = ddy; - found = true; - } - } - - // change position - vx = (vx+vddx) & wMask; - vy = (vy+vddy) & hMask; - - // if we have reached destination break - if (max == 255) - { - result = true; - break; - } - // if we haven't found a suitable direction, we break, but we do not have exact destination - else if (!found) - break; - } - - // return best destination and wether it is exact or not - *targetX = vx; - *targetY = vy; - return result; -} - - -/* -This was the old way. I was much more complex but reliable with partially broken gradients. Let's keep it for now in case of such type of gradient reappears -bool Map::ressourceAvailable(int teamNumber, int ressourceType, bool canSwim, int x, int y, Sint32 *targetX, Sint32 *targetY, int *dist) - -commented out version last seen in revision 0ea2652945a0 - -*/ - -void Map::updateGlobalGradientSlow(Uint8 *gradient) -{ - if (size <= 65536) - updateGlobalGradientSlow(gradient); - else - updateGlobalGradientSlow(gradient); -} - -template void Map::updateGlobalGradientSlow(Uint8 *gradient) -{ - Tint *listedAddr = new Tint[size]; - size_t listCountWrite = 0; - // make the first list: - for (size_t i = 0; i < size; i++) - if (gradient[i] >= 3) - listedAddr[listCountWrite++] = i; - updateGlobalGradient(gradient, listedAddr, listCountWrite, GT_UNDEFINED, true); - delete[] listedAddr; -} - -/*! Note that you can't provide any listedAddr[], or the gradient may technically end up - wrong. Given the results of the tests, this will never happen. The easiest way to provide - a listedAddr[] which guarantee a correct result, is to put only references to gradient - heights that are all the same. Currently this is the case of all gradient computation but - the AI ones (GT_UNDEFINED). For further undestanding you have to dig into the code and - try #define check_disorderable_gradient_error_probability */ -template void Map::updateGlobalGradientVersionSimple( - Uint8 *gradient, Tint *listedAddr, size_t listCountWrite, GradientType gradientType) -{ - size_t listCountRead = 0; - #ifdef check_disorderable_gradient_error_probability - size_t listCountSizeMax = 0; - #endif - while (listCountRead < listCountWrite) - { - Tint deltaAddrG = listedAddr[(listCountRead++)&(size-1)]; - - size_t y = deltaAddrG >> wDec; // Calculate the coordinates of - size_t x = deltaAddrG & wMask; // the current field and of the - - size_t yu = ((y - 1) & hMask); // fields next to it. - size_t yd = ((y + 1) & hMask); // We live on a torus! If we are on - size_t xl = ((x - 1) & wMask); // the "last line" of the map, the - size_t xr = ((x + 1) & wMask); // next line is the line 0 again. - - Uint8 g = gradient[(y << wDec) | x] - 1; - if (g <= 1) // All free non-source-fields start with gradient=1 - continue; // There is no need to propagate gradient when g==1 - - size_t deltaAddrC[8]; - Uint8 *addr; - Uint8 side; - - deltaAddrC[0] = (yu << wDec) | xl; // Calculate the positions of the - deltaAddrC[1] = (yu << wDec) | x ; // 8 fields next to us from their - deltaAddrC[2] = (yu << wDec) | xr; // coordinates. - deltaAddrC[3] = (y << wDec) | xr; - deltaAddrC[4] = (yd << wDec) | xr; - deltaAddrC[5] = (yd << wDec) | x ; - deltaAddrC[6] = (yd << wDec) | xl; - deltaAddrC[7] = (y << wDec) | xl; - for (int ci=0; ci<8; ci++) // Check for each of this fields if we - { // can improve its gradient value - addr = &gradient[deltaAddrC[ci]]; - side = *addr; - if (side > 0 && side < g) // side==0 means: you cannot walk on - { // this field. - // If we can improve this field - *addr = g; // we must add it as a new source - #ifdef check_disorderable_gradient_error_probability - size_t listCountSize = 1 + listCountWrite - listCountRead; - if (listCountSizeMax < listCountSize) - listCountSizeMax = listCountSize; - #endif - // Here we check if the queue is large enough to - // contain this field as a new gradient source. - if (listCountWrite + 1 + size!= listCountRead) - listedAddr[(listCountWrite++)&(size-1)] = deltaAddrC[ci]; - else - fprintf(stderr, "Map::updateGlobalGradientVersionSimple(): listedAddr[] overflow error"); - } - } - } - #ifdef check_disorderable_gradient_error_probability - if (listCountSizeMax < size) - listCountSizeStats[gradientType][listCountSizeMax]++; - else - listCountSizeStatsOver[gradientType]++; - #endif - //assert(listCountWrite<=size); -} - -template void Map::updateGlobalGradientVersionSimon(Uint8 *gradient, Tint *listedAddr, size_t listCountWrite) -{ -/* This algorithm uses the fact that all fields which are adjacent to the field - directly below the current one, are also adjacent to either the field to its left - or right. Thus this field only needs to become a source if its left or right - is not accessable. The same with the other 3 directions. - | | - | | -------+-------+------ - |current| - |field | -------+-------+------ - L | below | R - | | -------+-------+------ - next |next to| next - to L | both | to R -*/ - -#if defined(LOG_SIMON_GRADIENT) - size_t spared=0; - size_t listCountWriteStart=listCountWrite; -#endif - - - size_t listCountRead = 0; - while (listCountRead < listCountWrite) - { - Tint deltaAddrG = listedAddr[(listCountRead++)&(size-1)]; - - size_t y = deltaAddrG >> wDec; - size_t x = deltaAddrG & wMask; - - size_t yu = ((y - 1) & hMask); - size_t yd = ((y + 1) & hMask); - size_t xl = ((x - 1) & wMask); - size_t xr = ((x + 1) & wMask); - - Uint8 g = gradient[(y << wDec) | x] - 1; - if (g <= 1) - continue; - - Uint32 flag = 0; - Uint8 *addr; - Uint8 side; - { // In this scope we care only about the diagonal neighbours. - /* We will use flags to mark if at least one of the 2 fields - next to a adjacent nondiagonal field is not accessable. - Binary representation: - 9 = 1001 - 3 = 0011 - 6 = 0110 - 12 = 1100 - 1 is the upper right - 1 is the lower right - 1 is the lower left - 1 is the upper left - */ - const Uint32 diagFlags[4] = {9, 3, 6, 12}; - size_t deltaAddrC[4]; - - deltaAddrC[0] = (yu << wDec) | xl; // Calculate the position - deltaAddrC[1] = (yu << wDec) | xr; // of the 4 diagonal fields - deltaAddrC[2] = (yd << wDec) | xr; - deltaAddrC[3] = (yd << wDec) | xl; - // 0|_|1 - // _|*|_ * represents the current field - // 3| |2 - for (size_t ci = 0; ci < 4; ci++) // Check them - { - addr = &gradient[deltaAddrC[ci]]; - side = *addr; - if (side > 0 && side < g) - { - *addr = g; - listedAddr[(listCountWrite++)&(size-1)] = deltaAddrC[ci]; - } - else if (side == 0) // If field is inaccessable, - flag |= diagFlags[ci]; // mark the corresponding bit - } - } - { // Now we take a look at our nondiagonal neighbours - size_t deltaAddrC[4]; - - deltaAddrC[0] = (yu << wDec) | x ; // _|0|_ - deltaAddrC[1] = (y << wDec) | xr; // 3|*|1 - deltaAddrC[2] = (yd << wDec) | x ; // |2| - deltaAddrC[3] = (y << wDec) | xl; - - - for (size_t ci = 0; ci < 4; ci++) - { - addr = &gradient[deltaAddrC[ci]]; - side = *addr; - if (side > 0 && side < g) - { - *addr = g; - // Only mark this as a new source, - // if its left or right was inaccessable. - if (flag & 1) // Information is in the first bit - listedAddr[(listCountWrite++)&(size-1)] = deltaAddrC[ci]; -#if defined(LOG_SIMON_GRADIENT) - else - spared++; -#endif - } - flag >>= 1; // Shift the next bit into position - } - } - } -#if defined(LOG_SIMON_GRADIENT) - FILE *logSimon = globalContainer->logFileManager->getFile("Simon.log"); - fprintf(logSimon,"listed: %4d inserted: %4d spared: %3d\n",listCountWrite, listCountWrite-listCountWriteStart,spared); -#endif - //assert(listCountWrite<=size); -} - -template void Map::updateGlobalGradientVersionKai(Uint8 *gradient, Tint *listedAddr, size_t listCountWrite) -{ - // This version tries to go through the memory in consecutive order - // in the hope that the cache usage will be improved. - // Instead of picking one individual field and test its neighbours, - // we test if the field to its right is the next field we must process. - // If it is, we test the field to right of this field and so on. - // Otherwise we stop. We also stop if the gradient value of the field to - // the right differs from that of the current field or if we have reached - // the end of the line. (We don't have to, but we do.) - // After that we have a horizontal line segment. - // Now we check if we can improve the line segment above it, and below it. - // And the fields on the left and right. - - size_t sizeMask = size-1; // Mask needed to use listedAddr as queue. - size_t listCountRead = 0; // Index of first untreated field in listedAddr. - -#if defined(LOG_GRADIENT_LINE_GRADIENT) - std::map dcount; -#endif -#if defined(LOG_SIMON_GRADIENT) - size_t spared=0; - size_t listCountWriteStart=listCountWrite; -#endif - - while (listCountRead < listCountWrite) // While listedAddr not empty. - { - Tint deltaAddrG = listedAddr[listCountRead&sizeMask]; - - size_t y = deltaAddrG >> wDec; - size_t x = deltaAddrG & wMask; - - size_t yu = ((y - 1) & hMask); - size_t yd = ((y + 1) & hMask); - - - Uint8 myg = gradient[deltaAddrG]; // Get the gradient of the current field - Uint8 g = myg-1; // g will be the gradient of the children. - if (g <= 1) // All free non-source-fields start with gradient=1 - { - listCountRead++; - continue; // There is no need to propagate gradient when g==1 - } - - - Uint8 *addr; // Pointer to a field. - Uint8 side; // Gradient value of a field. - size_t pos; // pos stores the combined (x,y) coordinate. - - - // Get the length of the segment. - size_t d; // Length of the line segment. - size_t ylineDec = y << wDec; // Line the field is in. - // remember: && and || only compute second argument if they have to. - for (d=1; (++listCountRead < listCountWrite); d++) // While not empty. - { - pos = listedAddr[listCountRead&sizeMask]; // Next untreated field. - // We can tollerate gaps of length 1. - // Break if this field has not the same g as I, or is not the one - // to my right or the one behind this. - - if (gradient[pos] != myg) // Need same g for all fields in line. - break; - if (pos == (ylineDec | ( (d + x) & wMask ) ) ) - continue; // If the next field is beside to the right. -#define ALLOW_SMALL_GAPS -#if defined( ALLOW_SMALL_GAPS ) - if (pos == (ylineDec | ( (d + 1 + x) & wMask ) ) ) - { // If it is behind it. We overleap one field. - addr = &gradient[(ylineDec | ( (x+d++) & wMask ) )]; - side = *addr; - if ( side>0 && side0 && side0 && side0 && side0 && side0 && sidelogFileManager->getFile("Simon.log"); - fprintf(logSimon,"listed: %4d inserted: %4d spared: %3d\n",listCountWrite, listCountWrite-listCountWriteStart,spared); -#endif - -#if defined( LOG_GRADIENT_LINE_GRADIENT ) - FILE *dlog = globalContainer->logFileManager->getFile("GradientLineLength.log"); - for (std::map::iterator it=dcount.begin();it!=dcount.end();it++) - fprintf(dlog,"line length: %3d count: %4d\n",it->first,it->second); -#endif -} - -template void Map::updateGlobalGradient( - Uint8 *gradient, Tint *listedAddr, size_t listCountWrite, GradientType gradientType, bool canSwim) -{ - #define USE_DYNAMICAL_GRADIENT_VERSION_SR - -#if defined(LOG_GRADIENT_LINE_GRADIENT) - FILE *dlog = globalContainer->logFileManager->getFile("GradientLineLength.log"); - fprintf(dlog, "gradientType: %d\n", gradientType); - fprintf(dlog, "canSwim: %d\n", canSwim); -#endif -#if defined(LOG_SIMON_GRADIENT) - FILE *logSimon = globalContainer->logFileManager->getFile("Simon.log"); - fprintf(logSimon, "gradientType: %d\n", gradientType); - fprintf(logSimon, "canSwim: %d\n", canSwim); -#endif - - #if defined( USE_GRADIENT_VERSION_TEST_KAI) - if (gradientType == GT_UNDEFINED) - updateGlobalGradientVersionSimple(gradient, listedAddr, listCountWrite, gradientType); - else - { - Tint *testListedAddr = new Tint[size]; - Uint8 *testGradient = new Uint8[size]; - memcpy (testListedAddr, listedAddr, size); - memcpy (testGradient, gradient, size); - updateGlobalGradientVersionKai(testGradient, testListedAddr, listCountWrite); - updateGlobalGradientVersionSimple(gradient, listedAddr, listCountWrite, gradientType); - assert (memcmp (testGradient, gradient, size) == 0); - } - - #elif defined(USE_GRADIENT_VERSION_KAI) - updateGlobalGradientVersionKai(gradient, listedAddr, listCountWrite); - - #elif defined(USE_GRADIENT_VERSION_SIMON) - updateGlobalGradientVersionSimon(gradient, listedAddr, listCountWrite); - - #elif defined(USE_GRADIENT_VERSION_SIMPLE) - updateGlobalGradientVersionSimple(gradient, listedAddr, listCountWrite, gradientType); - - #elif defined(USE_DYNAMICAL_GRADIENT_VERSION_SR) - if (gradientType == GT_RESOURCE) - updateGlobalGradientVersionSimon(gradient, listedAddr, listCountWrite); - else - updateGlobalGradientVersionSimple(gradient, listedAddr, listCountWrite, gradientType); - - #elif defined(USE_DYNAMICAL_GRADIENT_VERSION_KR) - if (gradientType == GT_RESOURCE) - updateGlobalGradientVersionKai(gradient, listedAddr, listCountWrite); - else - updateGlobalGradientVersionSimple(gradient, listedAddr, listCountWrite, gradientType); - - #elif defined(USE_DYNAMICAL_GRADIENT_VERSION) - // use the fastest gradient computation for each GradientType: - switch (gradientType) - { - case GT_UNDEFINED: - updateGlobalGradientVersionSimon(gradient, listedAddr, listCountWrite); - // speed 105.09% compare to simple on test - break; - - case GT_RESOURCE: - updateGlobalGradientVersionSimon(gradient, listedAddr, listCountWrite); - //speed 104.76% compare to simple on test - break; - - case GT_BUILDING: - updateGlobalGradientVersionKai(gradient, listedAddr, listCountWrite); - // speed 100.29% compare to simple on test - break; - - case GT_FORBIDDEN: - updateGlobalGradientVersionKai(gradient, listedAddr, listCountWrite); - // speed 100.18% compare to simple on test - break; - - case GT_GUARD_AREA: - updateGlobalGradientVersionSimple(gradient, listedAddr, listCountWrite, gradientType); - // fastest one here - break; - - case GT_CLEAR_AREA: - updateGlobalGradientVersionSimple(gradient, listedAddr, listCountWrite, gradientType); - // fastest one here - break; - - default: - assert(false); - abort(); - break; - } - - #else - #error Please select a gradient version - #endif -} - -void Map::updateRessourcesGradient(int teamNumber, Uint8 ressourceType, bool canSwim) -{ - if (size <= 65536) - updateRessourcesGradient(teamNumber, ressourceType, canSwim); - else - updateRessourcesGradient(teamNumber, ressourceType, canSwim); -} - -template void Map::updateRessourcesGradient(int teamNumber, Uint8 ressourceType, bool canSwim) -{ - Uint8 *gradient=ressourcesGradient[teamNumber][ressourceType][canSwim]; - assert(gradient); - Tint *listedAddr = new Tint[size]; - size_t listCountWrite = 0; - - Uint32 teamMask=Team::teamNumberToMask(teamNumber); - assert(globalContainer); - for (size_t i=0; i=256 && c.terrain<16+256)) //!canSwim && isWater - gradient[i]=0; - else - gradient[i]=1; - } - else if (c.ressource.type==ressourceType) - { - if (globalContainer->ressourcesTypes.get(ressourceType)->visibleToBeCollected && !(fogOfWar[i]&teamMask)) - gradient[i]=0; - else - { - gradient[i]=255; - listedAddr[listCountWrite++] = i; - } - } - else - gradient[i]=0; - } - - updateGlobalGradient(gradient, (Tint *)listedAddr, listCountWrite, GT_RESOURCE, canSwim); - delete[] listedAddr; -} - -bool Map::directionFromMinigrad(Uint8 miniGrad[25], int *dx, int *dy, const bool strict, bool verbose) const -{ - Uint8 max; - Uint8 mxd; // max in direction - Uint32 maxs[8]; - - max=mxd=miniGrad[1+1*5]; - if (max && max!=255) - { - max=1; - UPDATE_MAX(max,miniGrad[0+2*5]); - UPDATE_MAX(max,miniGrad[0+1*5]); - UPDATE_MAX(max,miniGrad[0+0*5]); - UPDATE_MAX(max,miniGrad[1+0*5]); - UPDATE_MAX(max,miniGrad[2+0*5]); - } - maxs[0]=(max<<8)|mxd; - max=mxd=miniGrad[3+1*5]; - if (max && max!=255) - { - max=1; - UPDATE_MAX(max,miniGrad[2+0*5]); - UPDATE_MAX(max,miniGrad[3+0*5]); - UPDATE_MAX(max,miniGrad[4+0*5]); - UPDATE_MAX(max,miniGrad[4+1*5]); - UPDATE_MAX(max,miniGrad[4+2*5]); - } - maxs[1]=(max<<8)|mxd; - max=mxd=miniGrad[3+3*5]; - if (max && max!=255) - { - max=1; - UPDATE_MAX(max,miniGrad[4+2*5]); - UPDATE_MAX(max,miniGrad[4+3*5]); - UPDATE_MAX(max,miniGrad[4+4*5]); - UPDATE_MAX(max,miniGrad[3+4*5]); - UPDATE_MAX(max,miniGrad[2+4*5]); - } - maxs[2]=(max<<8)|mxd; - max=mxd=miniGrad[1+3*5]; - if (max && max!=255) - { - max=1; - UPDATE_MAX(max,miniGrad[2+4*5]); - UPDATE_MAX(max,miniGrad[1+4*5]); - UPDATE_MAX(max,miniGrad[0+4*5]); - UPDATE_MAX(max,miniGrad[0+3*5]); - UPDATE_MAX(max,miniGrad[0+2*5]); - } - maxs[3]=(max<<8)|mxd; - - - max=mxd=miniGrad[2+1*5]; - if (max && max!=255) - { - max=1; - UPDATE_MAX(max,miniGrad[1+0*5]); - UPDATE_MAX(max,miniGrad[2+0*5]); - UPDATE_MAX(max,miniGrad[3+0*5]); - } - maxs[4]=(max<<8)|mxd; - max=mxd=miniGrad[3+2*5]; - if (max && max!=255) - { - max=1; - UPDATE_MAX(max,miniGrad[4+1*5]); - UPDATE_MAX(max,miniGrad[4+2*5]); - UPDATE_MAX(max,miniGrad[4+3*5]); - } - maxs[5]=(max<<8)|mxd; - max=mxd=miniGrad[2+3*5]; - if (max && max!=255) - { - max=1; - UPDATE_MAX(max,miniGrad[1+4*5]); - UPDATE_MAX(max,miniGrad[2+4*5]); - UPDATE_MAX(max,miniGrad[3+4*5]); - } - maxs[6]=(max<<8)|mxd; - max=mxd=miniGrad[1+2*5]; - if (max && max!=255) - { - max=1; - UPDATE_MAX(max,miniGrad[0+1*5]); - UPDATE_MAX(max,miniGrad[0+2*5]); - UPDATE_MAX(max,miniGrad[0+3*5]); - } - maxs[7]=(max<<8)|mxd; - - int centerg=miniGrad[2+2*5]; - centerg=(centerg<<8)|centerg; - int maxg=0; - int maxd=8; - bool good=false; - if (strict) - { - for (int d=0; d<8; d++) - { - int g=maxs[d]; - if (g>centerg) - good=true; - if (maxg<=g) - { - maxg=g; - maxd=d; - } - } - } - else - { - for (int d=0; d<8; d++) - { - int g=maxs[d]; - if (g && g!=centerg) - good=true; - if (maxg<=g) - { - maxg=g; - maxd=d; - } - } - } - - if (verbose) - { - if (verbose) - printf("miniGrad (%d):\n", strict); - for (int ry=0; ry<5; ry++) - { - for (int rx=0; rx<5; rx++) - if (verbose) - printf("%4d", miniGrad[rx+ry*5]); - if (verbose) - printf("\n"); - } - if (verbose) - { - printf("maxs:\n"); - for (int d=0; d<8; d++) - printf("%4d.%4d (%d)\n", maxs[d]>>8, maxs[d]&0xFF, maxs[d]); - printf("max=%4d.%4d (%d), d=%d, good=%d\n", maxs[maxd]>>8, maxs[maxd]&0xFF, maxs[maxd], maxd, good); - }; - } - - if (!good) - return false; - - int stdd; - if (maxd<4) - stdd=(maxd<<1); - else if (maxd!=8) - stdd=1+((maxd-4)<<1); - else - stdd=8; - - //printf("stdd=%4d\n", maxd); - - Unit::dxDyFromDirection(stdd, dx, dy); - return true; -} - -bool Map::directionByMinigrad(Uint32 teamMask, bool canSwim, int x, int y, int *dx, int *dy, const Uint8 *gradient, bool strict, bool verbose) const -{ - Uint8 miniGrad[25]; - miniGrad[2+2*5]=gradient[x+y*w]; - for (int di=0; di<16; di++) - { - int rx=tabFar[di][0]; - int ry=tabFar[di][1]; - int xg = x + rx; - int yg = y + ry; - int g=gradient[coordToIndex(xg, yg)]; - if (g==0 || g==255 || isFreeForGroundUnit(xg, yg, canSwim, teamMask)) - miniGrad[rx+ry*5+12]=g; - else - miniGrad[rx+ry*5+12]=0; - } - for (int di=0; di<8; di++) - { - int rx=tabClose[di][0]; - int ry=tabClose[di][1]; - int xg = x + rx; - int yg = y + ry; - int g=gradient[coordToIndex(xg, yg)]; - if (g==0 || isFreeForGroundUnit(xg, yg, canSwim, teamMask)) - miniGrad[rx+ry*5+12]=g; - else - miniGrad[rx+ry*5+12]=0; - } - if (verbose) - printf("directionByMinigrad global %d\n", canSwim); - return directionFromMinigrad(miniGrad, dx, dy, strict, verbose); -} - -bool Map::directionByMinigrad(Uint32 teamMask, bool canSwim, int x, int y, int bx, int by, int *dx, int *dy, Uint8 localGradient[1024], bool strict, bool verbose) const -{ - Uint8 miniGrad[25]; - for (int ry=0; ry<5; ry++) - for (int rx=0; rx<5; rx++) - { - int gx=(x+rx-2)&wMask; - int gy=(y+ry-2)&hMask; - int lx=(x-bx+15+rx-2)&wMask; - int ly=(y-by+15+ry-2)&hMask; - //printf("+r=(%d, %d), b=(%d, %d), g=(%d, %d), l=(%d, %d)\n", rx, ry, bx, by, gx, gy, lx, ly); - if (lx==wMask) - { - gx=(gx+1)&wMask; - lx=0; - } - else if (lx==32) - { - gx=(gx-1)&wMask; - lx=31; - } - if (ly==hMask) - { - gy=(gy+1)&hMask; - ly=0; - } - else if (ly==32) - { - gy=(gy-1)&hMask; - ly=31; - } - assert(lx>=0); - assert(ly>=0); - assert(lx<32); - assert(ly<32); - int g=localGradient[lx+ly*32]; - //printf("|r=(%d, %d), b=(%d, %d), g=(%d, %d), l=(%d, %d), g=%d\n", rx, ry, bx, by, gx, gy, lx, ly, g); - if (g==0 || g==255 || (rx==2 && ry==2) || isFreeForGroundUnit(gx, gy, canSwim, teamMask)) - miniGrad[rx+ry*5]=g; - else - miniGrad[rx+ry*5]=0; - } - for (int ry=1; ry<=3; ry++) - for (int rx=1; rx<=3; rx++) - if (miniGrad[rx+ry*5]==255) - { - int gx=(x+rx-2)&wMask; - int gy=(y+ry-2)&hMask; - if (!isFreeForGroundUnit(gx, gy, canSwim, teamMask)) - miniGrad[rx+ry*5]=0; - } - if (verbose) - printf("directionByMinigrad local %d\n", canSwim); - return directionFromMinigrad(miniGrad, dx, dy, strict, verbose); -} - -bool Map::pathfindRessource(int teamNumber, Uint8 ressourceType, bool canSwim, int x, int y, int *dx, int *dy, bool *stopWork, bool verbose) -{ - pathToRessourceCountTot++; - if (verbose) - printf("pathfindingRessource...\n"); - assert(ressourceTypeposX; - int y=unit->posY; - if ((cases[x+(y<owner->me) - { - if (verbose) - printf(" forbidden\n"); - if (pathfindForbidden(NULL, unit->owner->teamNumber, (unit->performance[SWIM]>0), x, y, &unit->dx, &unit->dy, verbose)) - { - if (verbose) - printf(" success\n"); - unit->directionFromDxDy(); - } - else - { - if (verbose) - printf(" failed\n"); - unit->dx=0; - unit->dy=0; - unit->direction=8; - } - } - else - { - bool da[8]; - int count=0; - for (int di=0; di<8; di++) - { - int tx=(x+tabClose[di][0])&wMask; - int ty=(y+tabClose[di][1])&hMask; - if (isFreeForGroundUnit(tx, ty, (unit->performance[SWIM]>0), unit->owner->me)) - { - da[di]=true; - count++; - } - else - da[di]=false; - } - if (verbose) - { - printf("count=%d\n", count); - for (int di=0; di<8; di++) - printf("da[%d]=%d\n", di, da[di]); - } - if (count==0) - { - unit->dx=0; - unit->dy=0; - unit->direction=8; - return; - } - int dir=syncRand()%count; - if (verbose) - printf(" dir=%d\n", dir); - for (int di=0; di<8; di++) - if (da[di] && dir--==0) - { - unit->dx=tabClose[di][0]; - unit->dy=tabClose[di][1]; - unit->direction=di; - if (verbose) - printf("d=(%d, %d), d=%d\n", unit->dx, unit->dy, unit->direction); - return; - } - assert(false); - } -} -#endif // !YOG_SERVER_ONLY - -/** Helper for updateLocalGradient, and others */ -int clip_0_31(int x) {return (x<0)? 0 : (x>31)? 31 : x;} - -/** Helper for updateLocalGradient */ -void fillGradientCircle(Uint8* gradient, int r) { - int r2=r*r; - for (int yi=-r; yi<=r; yi++) - { - int yi2=yi*yi; - int yyi=clip_0_31(15+yi); - for (int xi=-r; xi<=r; xi++) - if (yi2+(xi*xi)gid); - //printf("updatingLocalGradient (gbid=%d)...\n", building->gid); - assert(building); - assert(building->type); - building->dirtyLocalGradient[canSwim]=false; - int posX=building->posX; - int posY=building->posY; - int posW=building->type->width; - int posH=building->type->height; - Uint32 teamMask=building->owner->me; - Uint16 bgid=building->gid; - - Uint8 *tgtGradient=building->localGradient[canSwim]; - - Uint8 gradient[1024]; - - // 1. INITIALIZATION of gradient[]: - // 1a. Set all values to 1 (meaning 'far away, but not inaccessable'). - memset(gradient, 1, 1024); - - bool isWarFlag=false; - bool isClearingFlag=false; - if(building->type->isVirtual && building->type->zonable[WARRIOR]) - isWarFlag=true; - if(building->type->isVirtual && building->type->zonable[WORKER]) - isClearingFlag=true; - - // 1b. Set values at target building to 255 (meaning 'very close'/'at destination'). - if (building->type->isVirtual && !building->type->zonable[WORKER]) - { - assert(!building->type->zonableForbidden); - int r=building->unitStayRange; - int r2=r*r; - for (int yi=-r; yi<=r; yi++) - { - int yi2=(yi*yi); - int yyi=clip_0_31(15+yi); - for (int xi=-r; xi<=r; xi++) - { - if (yi2+(xi*xi)<=r2) - { - int xxi=clip_0_31(15+xi); - gradient[xxi+(yyi<<5)]=255; - } - } - } - } - else if (building->type->isVirtual && building->type->zonable[WORKER]) - { - assert(!building->type->zonableForbidden); - int r=building->unitStayRange; - int r2=r*r; - for (int yi=-r; yi<=r; yi++) - { - int yi2=(yi*yi); - int yyi=clip_0_31(15+yi); - for (int xi=-r; xi<=r; xi++) - { - if (yi2+(xi*xi)<=r2) - { - size_t addr = coordToIndex(posX+w+xi, posY+h+yi); - if(cases[addr].ressource.type != NO_RES_TYPE && building->clearingRessources[cases[addr].ressource.type]) - { - int xxi=clip_0_31(15+xi); - gradient[xxi+(yyi<<5)]=255; - } - } - } - } - } - else - fillGradientRectangle(gradient, posW, posH); - - // 1c. Set values at inaccessible areas to 0 (meaning, well, 'inaccessible'). - // Here g=Global(map axis), l=Local(map axis) - - for (int yl=0; yl<32; yl++) - { - int wyl=(yl<<5); - int yg=(yl+posY-15)&hMask; - int wyg=w*yg; - for (int xl=0; xl<32; xl++) - { - int xg=(xl+posX-15)&wMask; - const Case& c=cases[wyg+xg]; - int wyx=wyl+xl; - - if (c.building==NOGBID) - { - if (c.forbidden&teamMask) - gradient[wyx] = 0; - else if (c.ressource.type!=NO_RES_TYPE && !(isClearingFlag && gradient[wyx]==255)) - gradient[wyx] = 0; - else if(immobileUnits[wyx] != 255) - gradient[wyx] = 0; - else if (!canSwim && isWater(xg, yg)) - gradient[wyx] = 0; - } - else - { - if (c.building==bgid) - { - gradient[wyx] = 255; - } - //Warflags don't consider enemy buildings an obstacle - else if(!isWarFlag || (1<owner->allies)) - gradient[wyx] = 0; - else if(gradient[wyx]!=255) - gradient[wyx] = 1; - } - } - } - - // 2. NEED TO UPDATE? Check boundary conditions to see if they have changed. - // I commented this out, because the tgtGradient is not initialized - // in the first runs: leading to an unconditional jump - // todo: write a real fix - -/* - bool change = false; - - for (int i=0; i<1024; i++) { - // The boundary conditions - do they match? - if (gradient[i]==0 || gradient[i]==255 || tgtGradient[i]==0 || tgtGradient[i]==255) { - if (gradient[i] != tgtGradient[i]) { - if (((gradient[i]+1)&0xFE)==0 || // Is either gradient or tgtGradient 0 or 255? - ((tgtGradient[i]+1)&0xFE)==0) - { - change = true; break; - } - } - } - if (!change) return; // No need to update; boundary conditions are unchanged. - } - if (!change) return; // No need to update; boundary conditions are unchanged. -*/ - // 3. Check that the building is REACHABLE. - if (!building->type->isVirtual) - { - building->locked[canSwim]=true; - int x=14; - int y=14; - int d=posW+1; - for (int ai=0; ai<4; ai++) //angle-iterator - for (int mi=0; mi=0); - assert(y>=0); - assert(x<32); - assert(y<32); - - Uint8 g=gradient[(y<<5)+x]; - //printf("ai=%d, mi=%d, (%d, %d), g=%d\n", ai, mi, x, y, g); - if (g) - { - building->locked[canSwim]=false; - goto doubleBreak; - } - switch (ai) - { - case 0: - x++; - break; - case 1: - y++; - break; - case 2: - x--; - break; - case 3: - y--; - break; - } - } - - assert(building->locked[canSwim]); - localBuildingGradientUpdateLocked++; - //fprintf(logFile, "...not updatedLocalGradient! building bgid=%d is locked!\n", building->gid); - //printf("...not updatedLocalGradient! building bgid=%d is locked!\n", building->gid); - memcpy(tgtGradient, gradient, 1024); // Don't leave gradient as-is (it might be dirty) - return; - doubleBreak:; - } - else - building->locked[canSwim]=false; - - // 4. PROPAGATION of gradient values. - propagateLocalGradients(gradient); - - // 5. WRITEBACK (because of the 'any change'-computation). - memcpy(tgtGradient, gradient, 1024); -} - -void propagateLocalGradients(Uint8* gradient) { - //In this algorithm, "l" stands for one case at Left, "r" for one case at Right, "u" for Up, and "d" for Down. - for (int depth=0; depth<2; depth++) // With a higher depth, we can have more complex obstacles. - { - for (int down=0; down<2; down++) - { - int x, y, dis, die, ddi; - if (down) - { - x=0; - y=0; - dis=31; - die=1; - ddi=-2; - } - else - { - x=15; - y=15; - dis=1; - die=31; - ddi=+2; - } - - for (int di=dis; di!=die; di+=ddi) //distance-iterator - { - for (int bi=0; bi<2; bi++) //back-iterator - { - for (int ai=0; ai<4; ai++) //angle-iterator - { - for (int mi=0; mi=0); - assert(y>=0); - assert(x<32); - assert(y<32); - - int wy=(y<<5); - Uint8 max=gradient[wy+x]; - if (max && max!=255) - { - for (int dy=-32; dy<=32; dy+=32) { - int ypart = wy+dy; - if (ypart & (32*32)) continue; // Over- or underflow - for (int dx=-1; dx<=1; dx++) { - int xpart = x+dx; - if (xpart & 32) continue; // Over- or underflow - UPDATE_MAX(max,gradient[ypart+xpart]); - } - } - // TODO: checkstyle found very long code duplicaitons here - // src/Map.cpp:3463: warning: Found duplicate of 59 lines in src/Map.cpp, starting from line 3,858 - assert(max); - if (max==1) - gradient[wy+x]=1; - else - gradient[wy+x]=max-1; - } - - if (bi==0) - { - switch (ai) - { - case 0: - x++; - break; - case 1: - y++; - break; - case 2: - x--; - break; - case 3: - y--; - break; - } - } - else - { - switch (ai) - { - case 0: - y++; - break; - case 1: - x++; - break; - case 2: - y--; - break; - case 3: - x--; - break; - } - } - } - } - } - if (down) - { - x++; - y++; - } - else - { - x--; - y--; - } - } - } - } - //printf("...updatedLocalGradient\n"); - //fprintf(logFile, "...updatedLocalGradient\n"); -} - - -void Map::updateGlobalGradient(Building *building, bool canSwim) -{ - if (size <= 65536) - updateGlobalGradient(building, canSwim); - else - updateGlobalGradient(building, canSwim); -} - -template void Map::updateGlobalGradient(Building *building, bool canSwim) -{ - globalBuildingGradientUpdate++; - assert(building); - assert(building->type); - //printf("updatingGlobalGradient (gbid=%d)\n", building->gid); - //fprintf(logFile, "updatingGlobalGradient (gbid=%d)...", building->gid); - int posX=building->posX; - int posY=building->posY; - int posW=building->type->width; - //int posH=building->type->height; - Uint32 teamMask=building->owner->me; - Uint16 bgid=building->gid; - - Uint8 *gradient=building->globalGradient[canSwim]; - assert(gradient); - - Tint *listedAddr = new Tint[size]; - size_t listCountWrite = 0; - - bool isClearingFlag=false; - bool isWarFlag=false; - if (building->type->isVirtual && building->type->zonable[WARRIOR]) - isWarFlag=true; - - memset(gradient, 1, size); - if (building->type->isVirtual && !building->type->zonable[WORKER]) - { - assert(!building->type->zonableForbidden); - int r=building->unitStayRange; - int r2=r*r; - for (int yi=-r; yi<=r; yi++) - { - int yi2=(yi*yi); - for (int xi=-r; xi<=r; xi++) - if (yi2+(xi*xi)<=r2) - { - size_t addr = coordToIndex(posX+w+xi, posY+h+yi); - if(gradient[addr] == 1) - { - gradient[addr] = 255; - listedAddr[listCountWrite++] = addr; - } - } - } - } - else if (building->type->isVirtual && building->type->zonable[WORKER]) - { - assert(!building->type->zonableForbidden); - isClearingFlag=true; - int r=building->unitStayRange; - int r2=r*r; - for (int yi=-r; yi<=r; yi++) - { - int yi2=(yi*yi); - for (int xi=-r; xi<=r; xi++) - if (yi2+(xi*xi)<=r2) - { - size_t addr = coordToIndex(posX+w+xi, posY+h+yi); - if(cases[addr].ressource.type!=NO_RES_TYPE && building->clearingRessources[cases[addr].ressource.type]) - { - if(gradient[addr] == 1) - { - gradient[addr] = 255; - listedAddr[listCountWrite++] = addr; - } - } - } - } - } - - for (int y=0; yowner->allies)) - gradient[wyx] = 0; - else if(gradient[wyx]!=255) - gradient[wyx] = 1; - } - } - } - - if (!building->type->isVirtual) - { - building->locked[canSwim]=true; - int x=(posX-1)&wMask; - int y=(posY-1)&hMask; - int d=posW+1; - for (int ai=0; ai<4; ai++) //angle-iterator - for (int mi=0; mi=0); - assert(y>=0); - assert(xlocked[canSwim]=false; - goto doubleBreak; - } - switch (ai) - { - case 0: - x++; - break; - case 1: - y++; - break; - case 2: - x--; - break; - case 3: - y--; - break; - } - x=(x+w)&wMask; - y=(y+h)&hMask; - } - - assert(building->locked[canSwim]); - globalBuildingGradientUpdateLocked++; - //printf("...not updatedGlobalGradient! building bgid=%d is locked!\n", building->gid); - //fprintf(logFile, "...not updatedGlobalGradient! building bgid=%d is locked!\n", building->gid); - delete[] listedAddr; - return; - doubleBreak:; - } - else - building->locked[canSwim]=false; - - updateGlobalGradient(gradient, listedAddr, listCountWrite, GT_BUILDING, canSwim); - delete[] listedAddr; -} - -bool Map::updateLocalRessources(Building *building, bool canSwim) -{ - localRessourcesUpdateCount++; - assert(building); - assert(building->type); - assert(building->type->isVirtual); - fprintf(logFile, "updatingLocalRessources[%d] (gbid=%d)...\n", canSwim, building->gid); - - int posX=building->posX; - int posY=building->posY; - Uint32 teamMask=building->owner->me; - - Uint8 *gradient=building->localRessources[canSwim]; - if (gradient==NULL) - { - gradient=new Uint8[1024]; - building->localRessources[canSwim]=gradient; - } - assert(gradient); - - bool *clearingRessources=building->clearingRessources; - bool anyRessourceToClear=false; - - memset(gradient, 1, 1024); - int range=building->unitStayRange; - if (range>15) - range=15; - int range2=range*range; - for (int yl=0; yl<32; yl++) - { - int wyl=(yl<<5); - int yg=(yl+posY-15)&hMask; - int wyg=w*yg; - int dyl2=(yl-15)*(yl-15); - for (int xl=0; xl<32; xl++) - { - int xg=(xl+posX-15)&wMask; - const Case& c=cases[wyg+xg]; - int addrl=wyl+xl; - int dist2=(xl-15)*(xl-15)+dyl2; - if (dist2<=range2) - { - if (c.forbidden&teamMask) - gradient[addrl]=0; - else if (c.ressource.type!=NO_RES_TYPE) - { - Sint8 t=c.ressource.type; - if (tlocalRessourcesCleanTime[canSwim]=0; - if (anyRessourceToClear) - building->anyRessourceToClear[canSwim]=1; - else - { - building->anyRessourceToClear[canSwim]=2; - return false; - } - expandLocalGradient(gradient); - return true; -} - - -void Map::expandLocalGradient(Uint8 *gradient) -{ - for (int depth=0; depth<2; depth++) // With a higher depth, we can have more complex obstacles. - { - for (int down=0; down<2; down++) - { - int x, y, dis, die, ddi; - if (down) - { - x=0; - y=0; - dis=31; - die=1; - ddi=-2; - } - else - { - x=15; - y=15; - dis=1; - die=31; - ddi=+2; - } - - for (int di=dis; di!=die; di+=ddi) //distance-iterator - { - for (int bi=0; bi<2; bi++) //back-iterator - { - for (int ai=0; ai<4; ai++) //angle-iterator - { - for (int mi=0; mi=0); - assert(y>=0); - assert(x<32); - assert(y<32); - - int wy=(y<<5); - int wyu, wyd; - if (y==0) - wyu=0; - else - wyu=((y-1)<<5); - if (y==31) - wyd=32*31; - else - wyd=((y+1)<<5); - Uint8 max=gradient[wy+x]; - if (max && max!=255) - { - int xl, xr; - if (x==0) - xl=0; - else - xl=x-1; - if (x==31) - xr=31; - else - xr=x+1; - - Uint8 side; - - side=gradient[wyu+xl]; - if (side > max) max=side; - side=gradient[wyu+x ]; - if (side > max) max=side; - side=gradient[wyu+xr]; - if (side > max) max=side; - - side=gradient[wy +xr]; - if (side > max) max=side; - - side=gradient[wyd+xr]; - if (side > max) max=side; - side=gradient[wyd+x ]; - if (side > max) max=side; - side=gradient[wyd+xl]; - if (side > max) max=side; - - side=gradient[wy +xl]; - if (side > max) max=side; - - assert(max); - if (max==1) - gradient[wy+x]=1; - else - gradient[wy+x]=max-1; - } - - if (bi==0) - { - switch (ai) - { - case 0: - x++; - break; - case 1: - y++; - break; - case 2: - x--; - break; - case 3: - y--; - break; - } - } - else - { - switch (ai) - { - case 0: - y++; - break; - case 1: - x++; - break; - case 2: - y--; - break; - case 3: - x--; - break; - } - } - } - } - } - if (down) - { - x++; - y++; - } - else - { - x--; - y--; - } - } - } - } -} - -bool Map::buildingAvailable(Building *building, bool canSwim, int x, int y, int *dist) -{ - buildingAvailableCountTot++; - assert(building); - int bx=building->posX; - int by=building->posY; - x&=wMask; - y&=hMask; - assert(x>=0); - assert(y>=0); - - Uint8 *gradient=building->localGradient[canSwim]; - - if (isInLocalGradient(x, y, bx, by)) - { - buildingAvailableCountClose++; - int lx=(x-bx+15+32)&31; - int ly=(y-by+15+32)&31; - if (!building->dirtyLocalGradient[canSwim]) - { - Uint8 currentg=gradient[lx+(ly<<5)]; - if (currentg>1) - { - buildingAvailableCountCloseSuccessFast++; - *dist=255-currentg; - return true; - } - else - { - for (int d=0; d<8; d++) - { - int ddx, ddy; - Unit::dxDyFromDirection(d, &ddx, &ddy); - int lxddx=clip_0_31(lx+ddx); - int lyddy=clip_0_31(ly+ddy); - Uint8 g=gradient[lxddx+(lyddy<<5)]; - if (g>1) - { - buildingAvailableCountCloseSuccessAround++; - *dist=255-g; - return true; - } - } - } - } - - updateLocalGradient(building, canSwim); - if (building->locked[canSwim]) - { - buildingAvailableCountCloseFailureLocked++; - //printf("ba-a- local gradient to building bgid=%d@(%d, %d) failed, locked. p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - //fprintf(logFile, "ba-a- local gradient to building bgid=%d@(%d, %d) failed, locked. p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - - return false; - } - - Uint8 currentg=gradient[lx+ly*32]; - - if (currentg>1) - { - buildingAvailableCountCloseSuccessUpdate++; - *dist=255-currentg; - return true; - } - else - { - for (int d=0; d<8; d++) - { - int ddx, ddy; - Unit::dxDyFromDirection(d, &ddx, &ddy); - int lxddx=clip_0_31(lx+ddx); - int lyddy=clip_0_31(ly+ddy); - Uint8 g=gradient[lxddx+(lyddy<<5)]; - if (g>1) - { - buildingAvailableCountCloseSuccessUpdateAround++; - *dist=255-g; - return true; - } - } - } - buildingAvailableCountCloseFailureEnd++; - return false; - } - else - buildingAvailableCountIsFar++; - buildingAvailableCountFar++; - - - gradient=building->globalGradient[canSwim]; - if (gradient==NULL) - { - buildingAvailableCountFarNew++; - gradient=new Uint8[size]; - fprintf(logFile, "ba- allocating globalGradient for gbid=%d (%p)\n", building->gid, gradient); - building->globalGradient[canSwim]=gradient; - } - else - { - buildingAvailableCountFarOld++; - if (building->locked[canSwim]) - { - buildingAvailableCountFarOldFailureLocked++; - //printf("ba-b- global gradient to building bgid=%d@(%d, %d) failed, locked. p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - //fprintf(logFile, "ba-b- global gradient to building bgid=%d@(%d, %d) failed, locked. p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - return false; - } - Uint8 currentg=gradient[coordToIndex(x, y)]; - if (currentg>1) - { - buildingAvailableCountFarOldSuccessFast++; - *dist=255-currentg; - return true; - } - else - { - for (int d=0; d<8; d++) - { - int ddx, ddy; - Unit::dxDyFromDirection(d, &ddx, &ddy); - Uint8 g=gradient[coordToIndex(x + ddx, y + ddy)]; - if (g>1) - { - buildingAvailableCountFarOldSuccessAround++; - *dist=255-g; - return true; - } - } - buildingAvailableCountFarOldFailureEnd++; - //printf("ba-c- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - //fprintf(logFile, "ba-c- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - return false; - } - } - - updateGlobalGradient(building, canSwim); - if (building->locked[canSwim]) - { - buildingAvailableCountFarNewFailureLocked++; - //printf("ba-d- global gradient to building bgid=%d@(%d, %d) failed, locked.\n", building->gid, building->posX, building->posY); - fprintf(logFile, "ba-d- global gradient to building bgid=%d@(%d, %d) failed, locked.\n", building->gid, building->posX, building->posY); - return false; - } - - Uint8 currentg=gradient[coordToIndex(x, y)]; - if (currentg>1) - { - buildingAvailableCountFarNewSuccessFast++; - *dist=255-currentg; - return true; - } - else - { - for (int d=0; d<8; d++) - { - int ddx, ddy; - Unit::dxDyFromDirection(d, &ddx, &ddy); - Uint8 g=gradient[coordToIndex(x + ddx, y + ddy)]; - if (g>1) - { - buildingAvailableCountFarNewSuccessClosely++; - *dist=255-g; - return true; - } - } - if (building->type->isVirtual) - { - //printf("ba-e- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - //fprintf(logFile, "ba-e- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - buildingAvailableCountFarNewFailureVirtual++; - } - else - { - if (building->verbose) - printf("ba-f- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - fprintf(logFile, "ba-f- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - buildingAvailableCountFarNewFailureEnd++; - } - return false; - } -} - -bool Map::pathfindBuilding(Building *building, bool canSwim, int x, int y, int *dx, int *dy, bool verbose) -{ - pathToBuildingCountTot++; - assert(building); - if (verbose) - printf("pathfindingBuilding (gbid=%d)...\n", building->gid); - int bx=building->posX; - int by=building->posY; - assert(x>=0); - assert(y>=0); - Uint32 teamMask=building->owner->me; - if (((cases[x+y*w].forbidden) & teamMask)!=0) - { - int teamNumber=building->owner->teamNumber; - if (verbose) - printf(" ...pathfindForbidden(%d, %d, %d, %d)\n", teamNumber, canSwim, x, y); - return pathfindForbidden(building->globalGradient[canSwim], teamNumber, canSwim, x, y, dx, dy, verbose); - } - Uint8 *gradient=building->localGradient[canSwim]; - if (isInLocalGradient(x, y, bx, by)) - { - pathToBuildingCountClose++; - int lx=(x-bx+15+32)&31; - int ly=(y-by+15+32)&31; - Uint8 currentg=gradient[lx+(ly<<5)]; - - if (!building->dirtyLocalGradient[canSwim] && currentg==255) - { - *dx=0; - *dy=0; - pathToBuildingCountCloseSuccessStand++; - if (verbose) - printf("...pathfindedBuilding v1\n"); - return true; - } - - if (!building->dirtyLocalGradient[canSwim] && currentg>1) - { - if (directionByMinigrad(teamMask, canSwim, x, y, bx, by, dx, dy, gradient, true, verbose)) - { - pathToBuildingCountCloseSuccessBase++; - if (verbose) - printf("...pathfindedBuilding v2\n"); - return true; - } - } - - updateLocalGradient(building, canSwim); - if (building->locked[canSwim]) - { - pathToBuildingCountCloseFailureLocked++; - if (verbose) - printf("a- local gradient to building bgid=%d@(%d, %d) failed, locked. p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - fprintf(logFile, "a- local gradient to building bgid=%d@(%d, %d) failed, locked. p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - return false; - } - - currentg=gradient[lx+ly*32]; - if (currentg>1) - { - if (directionByMinigrad(teamMask, canSwim, x, y, bx, by, dx, dy, gradient, true, verbose)) - { - pathToBuildingCountCloseSuccessUpdated++; - if (verbose) - printf("...pathfindedBuilding v4\n"); - return true; - } - } - pathToBuildingCountCloseFailureEnd++; - } - else - pathToBuildingCountIsFar++; - pathToBuildingCountFar++; - //Here the "local-32*32-cases-gradient-pathfinding-system" has failed, then we look for a full size gradient. - - gradient=building->globalGradient[canSwim]; - if (gradient==NULL) - { - pathToBuildingCountFarIsNew++; - gradient=new Uint8[size]; - if (verbose) - printf("allocating globalGradient for gbid=%d (%p)\n", building->gid, gradient); - fprintf(logFile, "allocating globalGradient for gbid=%d (%p)\n", building->gid, gradient); - building->globalGradient[canSwim]=gradient; - } - else - { - bool found=false; - Uint8 currentg=gradient[coordToIndex(x, y)]; - if (building->locked[canSwim]) - { - pathToBuildingCountFarOldFailureLocked++; - if (verbose) - printf("b- global gradient to building bgid=%d@(%d, %d) failed, locked. p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - fprintf(logFile, "b- global gradient to building bgid=%d@(%d, %d) failed, locked. p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - return false; - } - else if (currentg==1) - { - pathToBuildingCountFarOldFailureBad++; - if (verbose) - printf("c- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - fprintf(logFile, "c- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - return false; - } - else - found=directionByMinigrad(teamMask, canSwim, x, y, dx, dy, gradient, true, verbose); - - //printf("found=%d, d=(%d, %d)\n", found, *dx, *dy); - if (found) - { - pathToBuildingCountFarOldSuccess++; - if (verbose) - printf("...pathfindedBuilding v6\n"); - return true; - } - else if (building->lastGlobalGradientUpdateStepCounter[canSwim]+128>game->stepCounter) // not faster than 5.12s - { - pathToBuildingCountFarOldFailureRepeat++; - if (verbose) - printf("d- global gradient to building bgid=%d@(%d, %d) failed, repeat.\n", building->gid, building->posX, building->posY); - return directionByMinigrad(teamMask, canSwim, x, y, dx, dy, gradient, false, verbose); - } - else - { - pathToBuildingCountFarOldFailureUnusable++; - } - } - - updateGlobalGradient(building, canSwim); - building->lastGlobalGradientUpdateStepCounter[canSwim]=game->stepCounter; - - if (building->locked[canSwim]) - { - pathToBuildingCountFarUpdateFailureLocked++; - if (verbose) - printf("e- global gradient to building bgid=%d@(%d, %d) failed, locked.\n", building->gid, building->posX, building->posY); - fprintf(logFile, "e- global gradient to building bgid=%d@(%d, %d) failed, locked.\n", building->gid, building->posX, building->posY); - return false; - } - - Uint8 currentg=gradient[coordToIndex(x, y)]; - if (currentg>1) - { - if (directionByMinigrad(teamMask, canSwim, x, y, dx, dy, gradient, true, verbose)) - { - pathToBuildingCountFarUpdateSuccess++; - if (verbose) - printf("...pathfindedBuilding v7\n"); - return true; - } - } - - if (building->type->isVirtual) - { - pathToBuildingCountFarUpdateFailureVirtual++; - if (verbose) - printf("f- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - //fprintf(logFile, "f- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - } - else - { - pathToBuildingCountFarUpdateFailureBad++; - // TODO: find why this happend so often - if (verbose) - printf("g- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d), canSwim=%d\n", building->gid, building->posX, building->posY, x, y, canSwim); - fprintf(logFile, "g- global gradient to building bgid=%d@(%d, %d) failed! p=(%d, %d), canSwim=%d\n", building->gid, building->posX, building->posY, x, y, canSwim); - } - return false; -} - -bool Map::pathfindLocalRessource(Building *building, bool canSwim, int x, int y, int *dx, int *dy) -{ - pathfindLocalRessourceCount++; - assert(building); - assert(building->type); - assert(building->type->isVirtual); - //printf("pathfindingLocalRessource[%d] (gbid=%d)...\n", canSwim, building->gid); - - int bx=building->posX; - int by=building->posY; - Uint32 teamMask=building->owner->me; - - Uint8 *gradient=building->localRessources[canSwim]; - if (gradient==NULL) - { - if (!updateLocalRessources(building, canSwim)) - return false; - gradient=building->localRessources[canSwim]; - } - assert(gradient); - //HACK: I have no idea what is going on or why isInLocalGradient(x, y, bx, by) was asserted and why isInLocalGradient(x, y, bx, by) checks for the rectangle it is checking for, but this fixes a rare crash. - if(!isInLocalGradient(x, y, bx, by)) - return false; -// assert(isInLocalGradient(x, y, bx, by)); - - int lx=(x-bx+15+32)&31; - int ly=(y-by+15+32)&31; - int max=0; - Uint8 currentg=gradient[lx+(ly<<5)]; - bool found=false; - bool gradientUsable=false; - - if (currentg==1 && (building->localRessourcesCleanTime[canSwim]+=16)<128) - { - // This mean there are still ressources, but they are unreachable. - // We wait 5[s] before recomputing anything. - if (verbose) - printf("...pathfindedLocalRessource v0 failure waiting\n"); - pathfindLocalRessourceCountWait++; - return false; - } - - if (currentg>1 && currentg!=255) - { - for (int sd=0; sd<=1; sd++) - for (int d=sd; d<8; d+=2) - { - int ddx, ddy; - Unit::dxDyFromDirection(d, &ddx, &ddy); - int lxddx=clip_0_31(lx+ddx); - int lyddy=clip_0_31(ly+ddy); - Uint8 g=gradient[lxddx+(lyddy<<5)]; - if (!gradientUsable && g>currentg && isHardSpaceForGroundUnit(x+ddx, y+ddy, canSwim, teamMask)) - gradientUsable=true; - if (g>=max && isFreeForGroundUnit(x+ddx, y+ddy, canSwim, teamMask)) - { - max=g; - *dx=ddx; - *dy=ddy; - found=true; - } - } - - if (gradientUsable) - { - if (found) - { - pathfindLocalRessourceCountSuccessBase++; - //printf("...pathfindedLocalRessource v1\n"); - return true; - } - else - { - *dx=0; - *dy=0; - pathfindLocalRessourceCountSuccessLocked++; - if (verbose) - printf("...pathfindedLocalRessource v2 locked\n"); - return true; - } - } - } - - updateLocalRessources(building, canSwim); - - max=0; - currentg=gradient[lx+(ly<<5)]; - found=false; - gradientUsable=false; - - if (currentg==1) - { - pathfindLocalRessourceCountFailureNone++; - //printf("...pathfindedLocalRessource v3 No ressource\n"); - return false; - } - else if ((currentg!=0) && (currentg!=255)) - { - for (int sd=0; sd<=1; sd++) - for (int d=sd; d<8; d+=2) - { - int ddx, ddy; - Unit::dxDyFromDirection(d, &ddx, &ddy); - int lxddx=clip_0_31(lx+ddx); - int lyddy=clip_0_31(ly+ddy); - Uint8 g=gradient[lxddx+(lyddy<<5)]; - if (!gradientUsable && g>currentg && isHardSpaceForGroundUnit(x+ddx, y+ddy, canSwim, teamMask)) - gradientUsable=true; - if (g>=max && isFreeForGroundUnit(x+ddx, y+ddy, canSwim, teamMask)) - { - max=g; - *dx=ddx; - *dy=ddy; - found=true; - } - } - - if (gradientUsable) - { - if (found) - { - pathfindLocalRessourceCountSuccessUpdate++; - //printf("...pathfindedLocalRessource v3\n"); - return true; - } - else - { - *dx=0; - *dy=0; - pathfindLocalRessourceCountSuccessUpdateLocked++; - if (verbose) - printf("...pathfindedLocalRessource v4 locked\n"); - return true; - } - } - else - { - pathfindLocalRessourceCountFailureUnusable++; - fprintf(logFile, "lr-a- failed to pathfind localRessource bgid=%d@(%d, %d) p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - if (verbose) - printf("lr-a- failed to pathfind localRessource bgid=%d@(%d, %d) p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - return false; - } - } - else - { - pathfindLocalRessourceCountFailureBad++; - fprintf(logFile, "lr-b- failed to pathfind localRessource bgid=%d@(%d, %d) p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - if (verbose) - printf("lr-b- failed to pathfind localRessource bgid=%d@(%d, %d) p=(%d, %d)\n", building->gid, building->posX, building->posY, x, y); - return false; - } -} - -void Map::dirtyLocalGradient(int x, int y, int wl, int hl, int teamNumber) -{ - y &= hMask; - x &= wMask; - fprintf(logFile, "Map::dirtyLocalGradient(%d, %d, %d, %d, %d)\n", x, y, wl, hl, teamNumber); - for (int hi=0; hiteams[teamNumber]->myBuildings[Building::GIDtoID(bgid)]; - for (int canSwim=0; canSwim<2; canSwim++) - { - b->dirtyLocalGradient[canSwim]=true; - b->locked[canSwim]=false; - if (b->localRessources[canSwim]) - { - delete b->localRessources[canSwim]; - b->localRessources[canSwim]=NULL; - } - } - } - } - } -} - -bool Map::pathfindForbidden(const Uint8 *optionGradient, int teamNumber, bool canSwim, int x, int y, int *dx, int *dy, bool verbose) -{ - if (verbose) - printf("pathfindForbidden(%d, %d, (%d, %d))\n", teamNumber, canSwim, x, y); - pathfindForbiddenCount++; - Uint8 *gradient=forbiddenGradient[teamNumber][canSwim]; - if (verbose && !gradient) - printf("error, Map::pathfindForbidden(), forbiddenGradient[teamNumber=%d][canSwim=%d] is NULL\n", teamNumber, canSwim); - assert(gradient); - - Uint32 maxValue=0; - int maxd=0; - for (int di=0; di<8; di++) - { - int rx=tabClose[di][0]; - int ry=tabClose[di][1]; - int xg=(x+rx)&wMask; - int yg=(y+ry)&hMask; - if (verbose) - printf("[di=%d], r=(%d, %d), g=(%d, %d)\n", di, rx, ry, xg, yg); - if (!isFreeForGroundUnitNoForbidden(xg, yg, canSwim)) - continue; - size_t addr=xg+(yg<(addr), gradient[addr]); - Uint8 option; - if (optionGradient!=NULL) - option=optionGradient[addr]; - else - option=0; - if (verbose) - printf("option=%d @ %p\n", option, optionGradient); - Uint32 value=(base<<8)|option; - if (verbose) - printf("value=%d \n", value); - if (maxValue=(2<<8)) - { - *dx=tabClose[maxd][0]; - *dy=tabClose[maxd][1]; - if (verbose) - printf(" Success (%d:%d) (%d, %d)\n", (maxValue>>8), (maxValue&0xFF), *dx, *dy); - pathfindForbiddenCountSuccess++; - return true; - } - else - { - if (verbose) - printf(" Failure (%d)\n", maxValue); - pathfindForbiddenCountFailure++; - return false; - } -} - -bool Map::pathfindGuardArea(int teamNumber, bool canSwim, int x, int y, int *dx, int *dy) -{ - Uint8 *gradient = guardAreasGradient[teamNumber][canSwim]; - Uint8 max = gradient[x + (y<(teamNumber, canSwim); - else - updateForbiddenGradient(teamNumber, canSwim); -} - -template void Map::updateForbiddenGradient(int teamNumber, bool canSwim) -{ -#define SIMONS_FORBIDDEN_GRADIENT_INIT - -#if defined(TEST_FORBIDDEN_GRADIENT_INIT) - #define SIMONS_FORBIDDEN_GRADIENT_INIT - #define SIMPLE_FORBIDDEN_GRADIENT_INIT -#endif - - Tint *listedAddr = new Tint[size]; - size_t listCountWrite=0; - Uint32 teamMask = Team::teamNumberToMask(teamNumber); - -#ifdef SIMON2_FORBIDDEN_GRADIENT_INIT - Uint8 *gradient = forbiddenGradient[teamNumber][canSwim]; - assert(gradient); - for (size_t i = 0; i < size; i++) - { - const Case& c = cases[i]; - if ((c.ressource.type != NO_RES_TYPE) || (c.building!=NOGBID) || (!canSwim && isWater(i))) - { - gradient[i] = 0; - } - else if ((c.forbidden) & teamMask) - { - // we compute the 8 addresses around i: - // (a stands for address, u for up, d for down, l for left, r for right, m for middle) - size_t aul = (i - 1 - w) & (size - 1); - size_t aum = (i - w) & (size - 1); - size_t aur = (i + 1 - w) & (size - 1); - size_t amr = (i + 1 ) & (size - 1); - size_t adr = (i + 1 + w) & (size - 1); - size_t adm = (i + w) & (size - 1); - size_t adl = (i - 1 + w) & (size - 1); - size_t aml = (i - 1 ) & (size - 1); - - if( ((cases[aul].ressource.type != NO_RES_TYPE) || ((cases[aul].forbidden) &teamMask) - || (cases[aul].building!=NOGBID) || (!canSwim && isWater(aul))) && - ((cases[aul].ressource.type != NO_RES_TYPE) || ((cases[aum].forbidden) &teamMask) - || (cases[aum].building!=NOGBID) || (!canSwim && isWater(aum))) && - ((cases[aul].ressource.type != NO_RES_TYPE) || ((cases[aur].forbidden) &teamMask) - || (cases[aur].building!=NOGBID) || (!canSwim && isWater(aur))) && - ((cases[aul].ressource.type != NO_RES_TYPE) || ((cases[amr].forbidden) &teamMask) - || (cases[amr].building!=NOGBID) || (!canSwim && isWater(amr))) && - ((cases[aul].ressource.type != NO_RES_TYPE) || ((cases[adr].forbidden) &teamMask) - || (cases[adr].building!=NOGBID) || (!canSwim && isWater(adr))) && - ((cases[aul].ressource.type != NO_RES_TYPE) || ((cases[adm].forbidden) &teamMask) - || (cases[adm].building!=NOGBID) || (!canSwim && isWater(adm))) && - ((cases[aul].ressource.type != NO_RES_TYPE) || ((cases[adl].forbidden) &teamMask) - || (cases[adl].building!=NOGBID) || (!canSwim && isWater(adl))) && - ((cases[aul].ressource.type != NO_RES_TYPE) || ((cases[aml].forbidden) &teamMask) - || (cases[aml].building!=NOGBID) || (!canSwim && isWater(aml))) ) - { - gradient[i]= 1; - } - else - { - gradient[i]=254; - listedAddr[listCountWrite++] = i; - } - } - else - { - gradient[i] = 255; - } - } - // Then we propagate the gradient - updateGlobalGradient(gradient, listedAddr, listCountWrite, GT_FORBIDDEN, canSwim); -#endif - -#if defined(SIMONS_FORBIDDEN_GRADIENT_INIT) - Uint8 *testgradient = forbiddenGradient[teamNumber][canSwim]; - assert(testgradient); - size_t listCountWriteInit = 0; - - // We set the obstacle and free places - for (size_t i=0; i> wDec; // Calculate the coordinates of - size_t x = i & wMask; // the current field and of the - - size_t yu = ((y - 1) & hMask); // fields next to it. - size_t yd = ((y + 1) & hMask); - size_t xl = ((x - 1) & wMask); - size_t xr = ((x + 1) & wMask); - - size_t deltaAddrC[8]; - deltaAddrC[0] = (yu << wDec) | xl; - deltaAddrC[1] = (yu << wDec) | x ; - deltaAddrC[2] = (yu << wDec) | xr; - deltaAddrC[3] = (y << wDec) | xr; - deltaAddrC[4] = (yd << wDec) | xr; - deltaAddrC[5] = (yd << wDec) | x ; - deltaAddrC[6] = (yd << wDec) | xl; - deltaAddrC[7] = (y << wDec) | xl; - for( int ci=0; ci<8; ci++) - { - if( testgradient[ deltaAddrC[ci] ] == 255 ) - { - testgradient[i] = 254; - listedAddr[listCountWrite++] = i; - break; - } - } - } - - // Then we propagate the gradient - updateGlobalGradient(testgradient, listedAddr, listCountWrite, GT_FORBIDDEN, canSwim); -#endif - -#if defined(SIMPLE_FORBIDDEN_GRADIENT_INIT) - listCountWrite = 0; - #if defined(TEST_FORBIDDEN_GRADIENT_INIT) - Uint8 *gradient = new Uint8[size]; - #else - Uint8 *gradient = forbiddenGradient[teamNumber][canSwim]; - assert(gradient); - #endif - - for (size_t i=0; imapHeader.getNumberOfTeams(); i++) - updateForbiddenGradient(i); -} - -void Map::updateGuardAreasGradient(int teamNumber, bool canSwim) -{ - if (size <= 65536) - updateGuardAreasGradient(teamNumber, canSwim); - else - updateGuardAreasGradient(teamNumber, canSwim); -} - -template void Map::updateGuardAreasGradient(int teamNumber, bool canSwim) -{ - Uint8 *gradient = guardAreasGradient[teamNumber][canSwim]; - assert(gradient); - Tint *listedAddr = new Tint[size]; - size_t listCountWrite = 0; - - // We set the obstacle and free places - Uint32 teamMask = Team::teamNumberToMask(teamNumber); - for (size_t i=0; iteams[teamNumber]->allies)) - gradient[i] = 0; - else if (!canSwim && isWater(i)) - gradient[i] = 0; - else if (c.guardArea & teamMask) - { - gradient[i] = 255; - listedAddr[listCountWrite++] = i; - } - else - gradient[i] = 1; - } - - // Then we propagate the gradient - updateGlobalGradient(gradient, listedAddr, listCountWrite, GT_GUARD_AREA, canSwim); - delete[] listedAddr; -} - -void Map::updateGuardAreasGradient(int teamNumber) -{ - for (int i=0; i<2; i++) - updateGuardAreasGradient(teamNumber, i); -} - -void Map::updateGuardAreasGradient() -{ - for (int i=0; imapHeader.getNumberOfTeams(); i++) - updateGuardAreasGradient(i); -} - -void Map::updateClearAreasGradient(int teamNumber, bool canSwim) -{ - if (size <= 65536) - updateClearAreasGradient(teamNumber, canSwim); - else - updateClearAreasGradient(teamNumber, canSwim); -} - -template void Map::updateClearAreasGradient(int teamNumber, bool canSwim) -{ - Uint8 *gradient = clearAreasGradient[teamNumber][canSwim]; - assert(gradient); - Tint *listedAddr = new Tint[size]; - size_t listCountWrite = 0; - - // We set the obstacle and free places - Uint32 teamMask = Team::teamNumberToMask(teamNumber); - for (size_t i=0; imapHeader.getNumberOfTeams(); i++) - updateClearAreasGradient(i); -} - -bool Map::pathfindPointToPoint(int x, int y, int targetX, int targetY, int *dx, int *dy, bool canSwim, Uint32 teamMask, int maximumLength) -{ - //This implements a fairly standard A* algorithm, except that each node does not store the location - //of the node that lead to it, thus, you can't trace backwards to the starting point to get the path. - //Instead, each node holds the direction that you left from the initial node that lead to it, so you - //can't trace backwards to find the path, but you can instantly find the direction you need to go from - //the initial node, a small optimization since we don't need the whole path - targetX = (targetX + w) & wMask; - targetY = (targetY + h) & hMask; - - AStarComparator compare(aStarPoints); - - ///Priority queues use heaps internally, which I've read is the fastest for A* algorithm - std::priority_queue, AStarComparator> openList(compare); - openList.push((x << hDec) + y); - aStarPoints[(x << hDec) + y] = AStarAlgorithmPoint(x,y,0,0,0,0,false); - - //These are all the examined points, so that these positions on aStarPoints - //Can be reset later. Why not reset or re-allocate the whole thing every - //call? Its slow! Use reserve to avoid doing this multiple times - aStarExaminedPoints.reserve(maximumLength*2 + 6); - aStarExaminedPoints.push_back((x << hDec) + y); - - while(!openList.empty()) - { - ///Get the smallest from the heap - int position = openList.top(); - openList.pop(); - - AStarAlgorithmPoint& pos = aStarPoints[position]; - pos.isClosed = true; - - if((pos.x == targetX && pos.y == targetY) || (pos.moveCost > maximumLength)) - { - break; - } - - for(int lx=-1; lx<=1; ++lx) - { - for(int ly=-1; ly<=1; ++ly) - { - int nx = (pos.x + lx + w) & wMask; - int ny = (pos.y + ly + h) & hMask; - int n = (nx << hDec) + ny; - AStarAlgorithmPoint& npos = aStarPoints[n]; - if(npos.isClosed) - { - continue; - } - else - { - int moveCost = pos.moveCost + 1; - int totalCost = moveCost + warpDistMax(targetX, targetY, nx, ny); - - //If this cell hasn't been examined at all yet - if(npos.x == -1) - { - if(isFreeForGroundUnit(nx, ny, canSwim, teamMask) || (nx == targetX && ny == targetY)) - { - //If the parent cell is the starting cell, add in the starting direction - if(pos.dx == 0 && pos.dy == 0) - { - npos = AStarAlgorithmPoint(nx, ny, lx, ly, moveCost, totalCost, false); - openList.push(n); - } - //Else, the direction is the same as the parents node - else - { - npos = AStarAlgorithmPoint(nx, ny, pos.dx, pos.dy, moveCost, totalCost, false); - openList.push(n); - } - aStarExaminedPoints.push_back(n); - } - } - //Check if we can improve this cells value by taking this route - else if(npos.moveCost > moveCost) - { - npos.moveCost = moveCost; - npos.totalCost = totalCost; - npos.dx = pos.dx; - npos.dy = pos.dy; - } - } - } - } - } - - AStarAlgorithmPoint final = aStarPoints[(targetX << hDec) + targetY]; - - //Clear all of the examined points for the next call to this algorithm - for(unsigned i=0; iteams[teamNumber]); - assert(game->teams[teamNumber]->me); - assert(exploredArea[teamNumber]); - for (int x = 0; x < getW(); x++) { - for (int y = 0; y < getH(); y++) { - if (isMapDiscovered (x, y, game->teams[teamNumber]->me)) { - setMapExploredByUnit (x, y, 1, 1, teamNumber); }}} -} - -void Map::updateExploredArea(int teamNumber) -{ - for (size_t i = 0; i < size; i++) - if (exploredArea[teamNumber][i] > 0) - exploredArea[teamNumber][i]--; -} - -void Map::regenerateMap(int x, int y, int w, int h) -{ - for (int dx=x; dx>31); - } - }; - return cs; -} - -Sint32 Map::warpDist1d(int p, int q, int l) -{ - Sint32 d=abs(p-q); - d%=l; - if (d>l/2) - d=l-d; - return d; -} - -Sint32 Map::warpDistSquare(int px, int py, int qx, int qy) -{ - Sint32 dx=warpDist1d(px,qx,w); - Sint32 dy=warpDist1d(py,qy,h); - return ((dx*dx)+(dy*dy)); -} - -Sint32 Map::warpDistMax(int px, int py, int qx, int qy) -{ - Sint32 dx=warpDist1d(px,qx,w); - Sint32 dy=warpDist1d(py,qy,h); - if (dx>dy) - return dx; - else - return dy; -} - -Sint32 Map::warpDistSum(int px, int py, int qx, int qy) -{ - Sint32 dx=warpDist1d(px,qx,w); - Sint32 dy=warpDist1d(py,qy,h); - return dx + dy; -} - - -bool Map::isInLocalGradient(int ux, int uy, int bx, int by) -{ - Sint32 dx=warpDist1d(ux,bx,w); - Sint32 dy=warpDist1d(uy,by,h); - if (dx>dy) - { - if (dx<15) - return true; - if (dx>15) - return false; - - return ((bx+15) & wMask)==(ux & wMask); - } - else if (dx15) - return false; - - return ((by+15) & wMask)==(uy & wMask); - } - else - { - if (dx<15) - return true; - if (dx>15) - return false; - - return (((bx+15) & wMask)==(ux & wMask)) && (((by+15) & wMask)==(uy & wMask)); - } -} - -void Map::dumpGradient(Uint8 *gradient, const std::string filename) -{ - FILE *fp = globalContainer->fileManager->openFP(filename, "wb"); - if (fp) - { - fprintf(fp, "P5 %d %d 255\n", w, h); - fwrite(gradient, w, h, fp); - fclose(fp); - } -} - diff --git a/src/MapEdit.cpp b/src/MapEdit.cpp deleted file mode 100644 index c8dd40518..000000000 --- a/src/MapEdit.cpp +++ /dev/null @@ -1,3600 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include -#include "GameGUILoadSave.h" -#include "Game.h" -#include "GlobalContainer.h" -#include "MapEdit.h" -#include "MapEditKeyActions.h" -#include "ScriptEditorScreen.h" -#include -#include -#include -#include "UnitEditorScreen.h" -#include "Unit.h" -#include "UnitType.h" -#include "Utilities.h" -#include "FertilityCalculatorDialog.h" -#include "GUIMessageBox.h" -#include "SDLCompat.h" - - -#define RIGHT_MENU_WIDTH 160 -#define RIGHT_MENU_OFFSET (160-128)/2 - - -MapEditorWidget::MapEditorWidget(MapEdit& me, const widgetRectangle& rectangle, const std::string& group, const std::string& name, const std::string& action) - : me(me), area(rectangle), group(group), name(name), action(action), enabled(false) -{ - -} - - - -void MapEditorWidget::drawSelf() -{ - if(enabled) - draw(); -} - - - -void MapEditorWidget::disable() -{ - enabled=false; -} - - - -void MapEditorWidget::enable() -{ - enabled=true; -} - - - -void MapEditorWidget::handleClick(int relMouseX, int relMouseY) -{ - me.performAction(action, relMouseX, relMouseY); -} - - - -BuildingSelectorWidget::BuildingSelectorWidget(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& building_type, bool largeSelector) : MapEditorWidget(me, area, group, name, action), building_type(building_type), largeSelector(largeSelector) -{ - -} - - - -void BuildingSelectorWidget::draw() -{ - std::string &type = building_type; - - BuildingType *bt = globalContainer->buildingsTypes.getByType(type.c_str(), me.buildingLevel, false); - if(bt==NULL || !me.isUpgradable(IntBuildingType::shortNumberFromType(type))) - bt = globalContainer->buildingsTypes.getByType(type.c_str(), 0, false); - assert(bt); - - int imgid = bt->miniSpriteImage; - int x, y; - - x=area.x; - y=area.y; - - Sprite *buildingSprite; - if (imgid >= 0) - { - buildingSprite = bt->miniSpritePtr; - } - else - { - buildingSprite = bt->gameSpritePtr; - imgid = bt->gameSpriteImage; - } - - buildingSprite->setBaseColor(me.game.teams[me.team]->color); - globalContainer->gfx->drawSprite(x, y, buildingSprite, imgid); - - // draw selection if needed - if (me.selectionName == type) - { - if (largeSelector) - globalContainer->gfx->drawSprite(x-8, y-5, globalContainer->gamegui, 8); - else - globalContainer->gfx->drawSprite(x-4, y-3, globalContainer->gamegui, 23); - } - globalContainer->gfx->finishDrawingSprite(buildingSprite, 255); - globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); -} - - - -TeamColorSelector::TeamColorSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action) - : MapEditorWidget(me, area, group, name, action) -{ - -} - - - -void TeamColorSelector::draw() -{ - for(int n=0; n<16; ++n) - { - const int xpos = area.x + (n%6)*16; - const int ypos = area.y + (n/6)*16; - if(me.game.teams[n]) - { - if(me.team==n) - globalContainer->gfx->drawFilledRect(xpos, ypos, 16, 16, Color(me.game.teams[n]->color.r, me.game.teams[n]->color.g, me.game.teams[n]->color.b, 128)); - else - globalContainer->gfx->drawFilledRect(xpos, ypos, 16, 16, me.game.teams[n]->color); - - } - } -} - - - -SingleLevelSelector::SingleLevelSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, int level, int& levelNum) - : MapEditorWidget(me, area, group, name, action), level(level), levelNum(levelNum) -{ - -} - - - -void SingleLevelSelector::draw() -{ - globalContainer->gfx->drawSprite(area.x, area.y, me.menu, 30+level-1, (level-1)==levelNum ? 128 : 255); -} - - - -PanelIcon::PanelIcon(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, int iconNumber, int panelModeHilight) - : MapEditorWidget(me, area, group, name, action), iconNumber(iconNumber), panelModeHilight(panelModeHilight) -{ - -} - - - -void PanelIcon::draw() -{ - // draw buttons - if (me.panelMode==panelModeHilight) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, iconNumber+1); - else - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, iconNumber); - -} - - - -MenuIcon::MenuIcon(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action) - : MapEditorWidget(me, area, group, name, action) -{ - -} - - - -void MenuIcon::draw() -{ - // draw buttons - if (me.showingMenuScreen) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 7); - else - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 6); - -} - - - -ZoneSelector::ZoneSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, ZoneType zoneType) - : MapEditorWidget(me, area, group, name, action), zoneType(zoneType) -{ - -} - - - -void ZoneSelector::draw() -{ - bool isSelected=false; - if(zoneType==ForbiddenZone) - { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 13); - if(me.brushType==MapEdit::ForbiddenBrush) - isSelected=true; - } - else if(zoneType==GuardingZone) - { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 14); - if(me.brushType==MapEdit::GuardAreaBrush) - isSelected=true; - } - else if(zoneType==ClearingZone) - { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 25); - if(me.brushType==MapEdit::ClearAreaBrush) - isSelected=true; - } - if(me.selectionMode==MapEdit::PlaceZone && isSelected) - { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 22); - } -} - - - -BrushSelector::BrushSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, BrushTool& brushTool) - : MapEditorWidget(me, area, group, name, action), brushTool(brushTool) -{ - -} - - - -void BrushSelector::draw() -{ - brushTool.draw(area.x, area.y); -} - - - -UnitSelector::UnitSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, int unitType) - : MapEditorWidget(me, area, group, name, action), unitType(unitType) -{ - -} - - - -void UnitSelector::draw() -{ - // draw units - Sprite *unitSprite=globalContainer->units; - unitSprite->setBaseColor(me.game.teams[me.team]->color); - bool drawSelection=false; - if(unitType==WORKER) - { - if(me.selectionMode==MapEdit::PlaceUnit && me.placingUnit==MapEdit::Worker) - drawSelection=true; - globalContainer->gfx->drawSprite(area.x, area.y, unitSprite, 64); - } - else if(unitType==EXPLORER) - { - if(me.selectionMode==MapEdit::PlaceUnit && me.placingUnit==MapEdit::Explorer) - drawSelection=true; - globalContainer->gfx->drawSprite(area.x, area.y, unitSprite, 0); - } - else if(unitType==WARRIOR) - { - if(me.selectionMode==MapEdit::PlaceUnit && me.placingUnit==MapEdit::Warrior) - drawSelection=true; - globalContainer->gfx->drawSprite(area.x, area.y, unitSprite, 256); - } - if(drawSelection) - { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 23); - } -} - - -TerrainSelector::TerrainSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, TerrainType terrainType) - : MapEditorWidget(me, area, group, name, action), terrainType(terrainType) -{ - -} - - - - -void TerrainSelector::draw() -{ - if(terrainType==Grass) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->terrain, 0); - if(terrainType==Sand) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->terrain, 128); - if(terrainType==Water) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->terrain, 259); - if(terrainType==Wheat) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 19); - if(terrainType==Trees) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 2); - if(terrainType==Stone) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 34); - if(terrainType==Algae) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 44); - if(terrainType==Papyrus) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 24); - if(terrainType==CherryTree) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 54); - if(terrainType==OrangeTree) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 59); - if(terrainType==PruneTree) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 64); - if(me.terrainType==terrainType) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 22); - if (terrainType == Grass || terrainType == Sand || terrainType == Water) - globalContainer->gfx->finishDrawingSprite(globalContainer->terrain, 255); - else - globalContainer->gfx->finishDrawingSprite(globalContainer->ressources, 255); - if (me.terrainType == terrainType) - globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); -} - - - -BlueButton::BlueButton(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& text) - : MapEditorWidget(me, area, group, name, action), text(text), selected(false) -{ - -} - - - -void BlueButton::draw() -{ - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 12); - if(selected) - globalContainer->gfx->drawFilledRect(area.x+9, area.y+3, 94, 10, 128, 128, 192); - - std::string translatedText; - translatedText=Toolkit::getStringTable()->getString(text.c_str()); - int len=globalContainer->littleFont->getStringWidth(translatedText.c_str()); - int h=globalContainer->littleFont->getStringHeight(translatedText.c_str()); - globalContainer->gfx->drawString(area.x+9+((94-len)/2), area.y+((16-h)/2), globalContainer->littleFont, translatedText); -} - - - -void BlueButton::setSelected() -{ - selected=true; -} - - - -void BlueButton::setUnselected() -{ - selected=false; -} - - - -PlusIcon::PlusIcon(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action) - : MapEditorWidget(me, area, group, name, action) -{ -} - - - -void PlusIcon::draw() -{ - globalContainer->gfx->drawFilledRect(area.x, area.y, 32, 32, Color(75,0,200)); - globalContainer->gfx->drawRect(area.x, area.y, 32, 32, Color::white); - globalContainer->gfx->drawFilledRect(area.x + 15, area.y + 6, 2, 20, Color::white); - globalContainer->gfx->drawFilledRect(area.x + 6, area.y + 15, 20, 2, Color::white); -} - - - -MinusIcon::MinusIcon(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action) - : MapEditorWidget(me, area, group, name, action) -{ - -} - - - -void MinusIcon::draw() -{ - globalContainer->gfx->drawFilledRect(area.x, area.y, 32, 32, Color(75,0,200)); - globalContainer->gfx->drawRect(area.x, area.y, 32, 32, Color::white); - globalContainer->gfx->drawFilledRect(area.x + 6, area.y + 15, 20, 2, Color::white); -} - - - -UnitInfoTitle::UnitInfoTitle(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Unit* unit) - : MapEditorWidget(me, area, group, name, action), unit(unit) -{ - -} - - - -void UnitInfoTitle::draw() -{ - const int xpos=area.x; - const int ypos=area.y; - Unit* u=unit; - - // draw "unit of player" title - Uint8 r, g, b; - std::string title; - title += getUnitName(u->typeNum); - title += " ("; - - std::string textT=u->owner->getFirstPlayerName(); - if (textT.empty()) - textT=Toolkit::getStringTable()->getString("[Uncontrolled]"); - title += textT; - title += ")"; - - r=160; - g=160; - b=255; - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); - int titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); - int titlePos = xpos+((128-titleLen)/2); - globalContainer->gfx->drawString(titlePos, ypos, globalContainer->littleFont, title.c_str()); - globalContainer->littleFont->popStyle(); -} - - - -void UnitInfoTitle::setUnit(Unit* aUnit) -{ - unit=aUnit; -} - - - -UnitPicture::UnitPicture(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Unit* unit) - : MapEditorWidget(me, area, group, name, action), unit(unit) -{ - -} - - - -void UnitPicture::draw() -{ - const int xpos=area.x; - const int ypos=area.y; - - // draw unit's image - int imgid; - UnitType *ut=unit->race->getUnitType(unit->typeNum, 0); - assert(unit->action>=0); - assert(unit->actionstartImage[unit->action]; - - int dir=unit->direction; - int delta=unit->delta; - assert(dir>=0); - assert(dir<9); - assert(delta>=0); - assert(delta<256); - if (dir==8) - { - imgid+=8*(delta>>5); - } - else - { - imgid+=8*dir; - imgid+=(delta>>5); - } - - Sprite *unitSprite=globalContainer->units; - unitSprite->setBaseColor(unit->owner->color); - int decX = (32-unitSprite->getW(imgid))/2; - int decY = (32-unitSprite->getH(imgid))/2; - globalContainer->gfx->drawSprite(xpos+12+decX, ypos+7+decY, unitSprite, imgid); - globalContainer->gfx->drawSprite(xpos, ypos, globalContainer->gamegui, 18); -} - - - -void UnitPicture::setUnit(Unit* aUnit) -{ - unit=aUnit; -} - - - -FractionValueText::FractionValueText(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& label, Sint32* numerator, Sint32* denominator) - : MapEditorWidget(me, area, group, name, action), label(label), numerator(numerator), denominator(denominator), isDenominatorPreset(false) -{ - -} - - - -FractionValueText::FractionValueText(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& label, Sint32* numerator, Sint32 denominator) - : MapEditorWidget(me, area, group, name, action), label(label), numerator(numerator), denominator(new Sint32(denominator)), isDenominatorPreset(true) -{ - -} - - - -FractionValueText::~FractionValueText() -{ - if(isDenominatorPreset) - delete denominator; -} - - - -void FractionValueText::draw() -{ - globalContainer->gfx->drawString(area.x, area.y, globalContainer->littleFont, FormatableString("%0: %1/%2").arg(Toolkit::getStringTable()->getString(label.c_str())).arg(*numerator).arg(*denominator).c_str()); -} - - - -void FractionValueText::setValues(Sint32* aNumerator, Sint32* aDenominator) -{ - numerator=aNumerator; - denominator=aDenominator; -} - - - -void FractionValueText::setValues(Sint32* aNumerator) -{ - numerator=aNumerator; -} - - - -ValueScrollBox::ValueScrollBox(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Sint32* value, Sint32* max) - : MapEditorWidget(me, area, group, name, action), value(value), max(max), isMaxPreset(false) -{ - -} - - - -ValueScrollBox::ValueScrollBox(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Sint32* value, Sint32 max) - : MapEditorWidget(me, area, group, name, action), value(value), max(new Sint32(max)), isMaxPreset(true) -{ - -} - - - -ValueScrollBox::~ValueScrollBox() -{ - if(isMaxPreset) - delete max; -} - - - -void ValueScrollBox::draw() -{ - //Sometimes a scrollbox gets initiated with max-value 0. A turret construction site has 0/0 stone and 0/0 shots. To not run into arithmetic exceptions those cases are treated here. - if((*max) != 0) - { - globalContainer->gfx->setClipRect(area.x, area.y, 112, 16); - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 9); - int size=((*value)*92)/(*max); - globalContainer->gfx->setClipRect(area.x+10, area.y, size, 16); - globalContainer->gfx->drawSprite(area.x+10, area.y+3, globalContainer->gamegui, 10); - globalContainer->gfx->setClipRect(); - } -} - - - -void ValueScrollBox::handleClick(int relMouseX, int relMouseY) -{ - if(relMouseX<10) - (*value)=std::max((*value)-1, 0); - else if(relMouseX>102) - (*value)=std::min((*value)+1, (*max)); - else - (*value)=int(float(relMouseX-10) * (float(*max)/float(92))+0.5); - MapEditorWidget::handleClick(relMouseX, relMouseY); -} - - - -void ValueScrollBox::setValues(Sint32* aValue, Sint32* aMax) -{ - value=aValue; - max=aMax; -} - - - -void ValueScrollBox::setValues(Sint32* aValue) -{ - value=aValue; -} - - - -BuildingInfoTitle::BuildingInfoTitle(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Building* building) - : MapEditorWidget(me, area, group, name, action), building(building) -{ - -} - - - -void BuildingInfoTitle::draw() -{ - Building* selBuild = building; - BuildingType *buildingType = selBuild->type; - Uint8 r, g, b; - - // draw "building" of "player" - std::string title; - std::string key = "[" + buildingType->type + "]"; - title += Toolkit::getStringTable()->getString(key.c_str()); - { - title += " ("; - std::string textT=selBuild->owner->getFirstPlayerName(); - if (textT.empty()) - textT=Toolkit::getStringTable()->getString("[Uncontrolled]"); - title += textT; - title += ")"; - } - - r=160; - g=160; - b=255; - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); - int titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); - int titlePos = area.x+((area.width-titleLen)/2); - globalContainer->gfx->drawString(titlePos, area.y, globalContainer->littleFont, title.c_str()); - globalContainer->littleFont->popStyle(); -} - - - -void BuildingInfoTitle::setBuilding(Building* aBuilding) -{ - building=aBuilding; -} - - - -BuildingPicture::BuildingPicture(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Building* building) - : MapEditorWidget(me, area, group, name, action), building(building) -{ - -} - - - -void BuildingPicture::draw() -{ - Building* selBuild = building; - BuildingType *buildingType = selBuild->type; - - // building icon - Sprite *miniSprite; - int imgid; - if (buildingType->miniSpriteImage >= 0) - { - miniSprite = buildingType->miniSpritePtr; - imgid = buildingType->miniSpriteImage; - } - else - { - miniSprite = buildingType->gameSpritePtr; - imgid = buildingType->gameSpriteImage; - } - int dx = (56-miniSprite->getW(imgid))/2; - int dy = (46-miniSprite->getH(imgid))/2; - miniSprite->setBaseColor(selBuild->owner->color); - globalContainer->gfx->drawSprite(area.x+dx, area.y+dy, miniSprite, imgid); - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 18); - globalContainer->gfx->finishDrawingSprite(miniSprite, 255); - globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); -} - - - -void BuildingPicture::setBuilding(Building* aBuilding) -{ - building=aBuilding; -} - - - -TextLabel::TextLabel(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& label, bool centered, const std::string& emptyLabel) - : MapEditorWidget(me, area, group, name, action), label(label), emptyLabel(emptyLabel), centered(centered) -{ - -} - - - -void TextLabel::draw() -{ - std::string label=this->label; - if(label=="") - label=this->emptyLabel; - int titleWidth = globalContainer->littleFont->getStringWidth(label.c_str()); - int titleHeight = globalContainer->littleFont->getStringHeight(label.c_str()); - if(centered) - globalContainer->gfx->drawString(area.x+(area.width-titleWidth)/2, area.y+(area.height-titleHeight)/2, globalContainer->littleFont, label.c_str()); - else - globalContainer->gfx->drawString(area.x, area.y, globalContainer->littleFont, label.c_str()); -} - - - -void TextLabel::setLabel(const std::string& aLabel) -{ - label=aLabel; -} - - - -NumberCycler::NumberCycler(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, int maxNumber) - : MapEditorWidget(me, area, group, name, action), maxNumber(maxNumber), currentNumber(1) -{ - -} - - - -void NumberCycler::draw() -{ - std::stringstream s; - s<gfx->drawString(area.x, area.y, globalContainer->standardFont, s.str().c_str()); -} - - - -int NumberCycler::getIndex() -{ - return currentNumber-1; -} - - - -void NumberCycler::handleClick(int relMouseX, int relMouseY) -{ - currentNumber++; - if(currentNumber>maxNumber) - currentNumber=1; - MapEditorWidget::handleClick(relMouseX, relMouseY); -} - - - - -Checkbox::Checkbox(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& text, bool& isActivated) - : MapEditorWidget(me, area, group, name, action), text(text), isActivated(isActivated) -{ - -} - - - -void Checkbox::draw() -{ - globalContainer->gfx->drawRect(area.x, area.y, 16, 16, Color::white); - if(isActivated) - { - globalContainer->gfx->drawLine(area.x+4, area.y+4, area.x+12, area.y+12, Color::white); - globalContainer->gfx->drawLine(area.x+12, area.y+4, area.x+4, area.y+12, Color::white); - } - - std::string translatedText; - translatedText=Toolkit::getStringTable()->getString(text.c_str()); - - globalContainer->gfx->drawString(area.x+20, area.y, globalContainer->littleFont, translatedText); -} - - - -void Checkbox::handleClick(int relMouseX, int relMouseY) -{ - isActivated = !isActivated; - MapEditorWidget::handleClick(relMouseX, relMouseY); -} - - - -MapEdit::MapEdit() - : game(NULL, this), keyboardManager(MapEditShortcuts), - minimap(globalContainer->runNoX, - RIGHT_MENU_WIDTH, // menu width - globalContainer->gfx->getW(), // game width - 20, // x offset - 5, // y offset - 128, // width - 128, // height - Minimap::HideFOW) -{ - doQuit=false; - doFullQuit=false; - doQuitAfterLoadSave=false; - - // default value; - viewportX=0; - viewportY=0; - xSpeed=0; - ySpeed=0; - mouseX=0; - mouseY=0; - relMouseX=0; - relMouseY=0; - wasMinimapRendered=false; - - // load menu - menu=Toolkit::getSprite("data/gui/editor"); - - // editor facilities - hasMapBeenModified=false; - team=0; - - selectionMode=PlaceNothing; - - int decX = RIGHT_MENU_OFFSET; - - panelMode=AddBuildings; - buildingView = new PanelIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 136, 32, 32), "any", "building view icon", "switch to building view", 0, AddBuildings); - flagsView = new PanelIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, 136, 32, 32), "any", "flag view icon", "switch to flag view", 28, AddFlagsAndZones); - terrainView = new PanelIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, 136, 32, 32), "any", "terrain view icon", "switch to terrain view", 31, Terrain); - teamsView = new PanelIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+96+decX, 136, 32, 32), "any", "teams view icon", "switch to teams view", 33, Teams); - menuIcon = new MenuIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-32+decX, 0, 32, 32), "any", "menu icon", "open menu screen"); - mapCoordinatesLabel = new TextLabel(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+2+decX, globalContainer->gfx->getH()-95, 75, 10), "any", "map coordinates label", "do nothing", "", false, "0 0"); - addWidget(buildingView); - addWidget(flagsView); - addWidget(terrainView); - addWidget(teamsView); - addWidget(menuIcon); - addWidget(mapCoordinatesLabel); - swarm = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+6, 40, 40), "building view", "swarm", "set place building selection swarm", "swarm", true); - inn = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+6, 40, 40), "building view", "inn", "set place building selection inn", "inn", true); - hospital = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+46*1+6, 40, 40), "building view", "hospital", "set place building selection hospital", "hospital", true); - racetrack = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+46*1+6, 40, 40), "building view", "racetrack", "set place building selection racetrack", "racetrack", true); - swimmingpool = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+46*2+6, 40, 40), "building view", "swimmingpool", "set place building selection swimmingpool", "swimmingpool", true); - barracks = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+46*2+6, 40, 40), "building view", "barracks", "set place building selection barracks", "barracks", true); - school = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+46*3+6, 40, 40), "building view", "school", "set place building selection school", "school", true); - defencetower = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+46*3+6, 40, 40), "building view", "defencetower", "set place building selection defencetower", "defencetower", true); - stonewall = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+46*4+6, 40, 40), "building view", "stonewall", "set place building selection stonewall", "stonewall", true); - market = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+46*4+6, 40, 40), "building view", "market", "set place building selection market", "market", true); - building_view_tcs = new TeamColorSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 16+decX, globalContainer->gfx->getH()-74, 96, 32 ), "building view", "building view team selector", "select active team"); - building_view_level1 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, globalContainer->gfx->getH()-36, 32, 32), "building view", "building view level 1", "switch to building level 1", 1, buildingLevel); - building_view_level2 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, globalContainer->gfx->getH()-36, 32, 32), "building view", "building view level 2", "switch to building level 2", 2, buildingLevel); - building_view_level3 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, globalContainer->gfx->getH()-36, 32, 32), "building view", "building view level 3", "switch to building level 3", 3, buildingLevel); - addWidget(swarm); - addWidget(inn); - addWidget(hospital); - addWidget(racetrack); - addWidget(swimmingpool); - addWidget(barracks); - addWidget(school); - addWidget(defencetower); - addWidget(stonewall); - addWidget(market); - addWidget(building_view_tcs); - addWidget(building_view_level1); - addWidget(building_view_level2); - addWidget(building_view_level3); - explorationflag = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+5+decX, 128+32+7, 32, 32), "flag view", "explorationflag", "set place building selection explorationflag", "explorationflag", false); - warflag = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+5+42+decX, 128+32+7, 32, 32), "flag view", "warflag", "set place building selection warflag", "warflag", false); - clearingflag = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+5+84+decX, 128+32+7, 32, 32), "flag view", "clearingflag", "set place building selection clearingflag", "clearingflag", false); - forbiddenZone = new ZoneSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 216, 32, 32), "flag view", "forbidden zone", "select forbidden zone", ZoneSelector::ForbiddenZone); - guardZone = new ZoneSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+40+decX, 216, 32, 32), "flag view", "guard zone", "select guard zone", ZoneSelector::GuardingZone); - clearingZone = new ZoneSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+80+decX, 216, 32, 32), "flag view", "clearing zone", "select clearing zone", ZoneSelector::ClearingZone); - deleteButton = new BlueButton(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 8+decX, 216+40, 112, 16), "flag view", "delete button", "select delete objects", "[delete]"); - zoneBrushSelector = new BrushSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 216+65, 128, 96), "flag view", "zone brush selector", "handle zone click", brush); - worker = new UnitSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 385, 38, 38), "flag view", "worker selector", "select worker", WORKER); - explorer = new UnitSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+48+decX, 385, 38, 38), "flag view", "explorer selector", "select explorer", EXPLORER); - warrior = new UnitSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+88+decX, 385, 38, 38), "flag view", "warrior selector", "select warrior", WARRIOR); - flag_view_tcs = new TeamColorSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 16+decX, globalContainer->gfx->getH()-74, 96, 32 ), "flag view", "flag view team selector", "select active team"); - flag_view_level1 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, globalContainer->gfx->getH()-36, 32, 32), "flag view", "flag view level 1", "select unit level 1", 1, placingUnitLevel); - flag_view_level2 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, globalContainer->gfx->getH()-36, 32, 32), "flag view", "flag view level 2", "select unit level 2", 2, placingUnitLevel); - flag_view_level3 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, globalContainer->gfx->getH()-36, 32, 32), "flag view", "flag view level 3", "select unit level 3", 3, placingUnitLevel); - flag_view_level4 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+96+decX, globalContainer->gfx->getH()-36, 32, 32), "flag view", "flag view level 3", "select unit level 4", 4, placingUnitLevel); - addWidget(warflag); - addWidget(explorationflag); - addWidget(clearingflag); - addWidget(forbiddenZone); - addWidget(guardZone); - addWidget(clearingZone); - addWidget(deleteButton); - addWidget(zoneBrushSelector); - addWidget(worker); - addWidget(warrior); - addWidget(explorer); - addWidget(flag_view_tcs); - addWidget(flag_view_level1); - addWidget(flag_view_level2); - addWidget(flag_view_level3); - addWidget(flag_view_level4); - - grass = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 172, 32, 32), "terrain view", "grass selector", "select grass", TerrainSelector::Grass); - sand = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, 172, 32, 32), "terrain view", "sand selector", "select sand", TerrainSelector::Sand); - water = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, 172, 32, 32), "terrain view", "water selector", "select water", TerrainSelector::Water); - wheat = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+96+decX, 172, 32, 32), "terrain view", "wheat selector", "select wheat", TerrainSelector::Wheat); - trees = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 210, 32, 32), "terrain view", "trees selector", "select trees", TerrainSelector::Trees); - stone = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, 210, 32, 32), "terrain view", "stone selector", "select stone", TerrainSelector::Stone); - algae = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, 210, 32, 32), "terrain view", "algae selector", "select algae", TerrainSelector::Algae); - papyrus = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+96+decX, 210, 32, 32), "terrain view", "papyrus selector", "select papyrus", TerrainSelector::Papyrus); - orange = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 248, 32, 32), "terrain view", "orange selector", "select orange tree", TerrainSelector::OrangeTree); - cherry = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, 248, 32, 32), "terrain view", "cherry selector", "select cherry tree", TerrainSelector::CherryTree); - prune = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, 248, 32, 32), "terrain view", "prune selector", "select prune tree", TerrainSelector::PruneTree); - noRessourceGrowthButton = new BlueButton(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 8+decX, 294, 112, 16), "terrain view", "no ressources growth button", "select no ressources growth", "[no ressources growth areas]"); - areasButton = new BlueButton(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 8+decX, 320, 112, 16), "terrain view", "script areas button", "select change areas", "[Script Areas]"); - areaNumber = new NumberCycler(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 336, 8, 16), "terrain view", "script area number selector", "update script area number", 9); - areaNameLabel = new TextLabel(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+24+decX, 336, 104, 16), "terrain view", "script area name label", "open area name", "", false, Toolkit::getStringTable()->getString("[Unnamed Area]")); - terrainBrushSelector = new BrushSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 362, 128, 96), "terrain view", "terrain brush selector", "handle terrain click", brush); - showFertilityOverlay = new Checkbox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 466, 128, 16), "terrain view", "fertility checkbox", "compute fertility", "[Fertility Map]", isFertilityOn); - addWidget(grass); - addWidget(sand); - addWidget(water); - addWidget(wheat); - addWidget(trees); - addWidget(stone); - addWidget(algae); - addWidget(papyrus); - addWidget(orange); - addWidget(cherry); - addWidget(prune); - addWidget(noRessourceGrowthButton); - addWidget(areasButton); - addWidget(areaNumber); - addWidget(areaNameLabel); - addWidget(terrainBrushSelector); - addWidget(showFertilityOverlay); - - increaseTeams = new PlusIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 408, 32, 32), "teams view", "increase teams", "add team"); - decreaseTeams = new MinusIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+40+decX, 408, 32, 32), "teams view", "decrease teams", "remove team"); - team_view_tcs = new TeamColorSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 16+decX, 168, 96, 32 ), "teams view", "team view team selector", "select active team"); - addWidget(increaseTeams); - addWidget(decreaseTeams); - addWidget(team_view_tcs); - - unitInfoTitle = new UnitInfoTitle(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 173, 128, 16), "unit editor", "unit editor title", "", NULL); - unitPicture = new UnitPicture(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+2+decX, 203, 40, 40), "unit editor", "unit editor picture", "", NULL); - unitHPLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "unit editor", "unit editor hp label", "update unit", "[hp]", NULL, static_cast(NULL)); - unitHPScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 112, 16), "unit editor", "unit editor hp scroll box", "", NULL, static_cast(NULL)); - unitWalkLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 284, 128, 16), "unit editor", "unit editor walk level label", "", "[Walk]", NULL, 3); - unitWalkLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 300, 112, 16), "unit editor", "unit editor walk level scroll box", "update unit walk level", NULL, 3); - unitSwimLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 316, 128, 16), "unit editor", "unit editor swim level label", "", "[Swim]", NULL, 3); - unitSwimLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 332, 112, 16), "unit editor", "unit editor swim level scroll box", "update unit swim level", NULL, 3); - unitHarvestLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 348, 128, 16), "unit editor", "unit editor harvest level label", "", "[Harvest]", NULL, 3); - unitHarvestLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 364, 112, 16), "unit editor", "unit editor harvest level scroll box", "update unit harvest level", NULL, 3); - unitBuildLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 380, 128, 16), "unit editor", "unit editor build level label", "", "[Build]", NULL, 3); - unitBuildLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 396, 112, 16), "unit editor", "unit editor build level scroll box", "update unit build level", NULL, 3); - unitAttackSpeedLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 348, 128, 16), "unit editor", "unit editor attack speed level label", "", "[At. speed]", NULL, 3); - unitAttackSpeedLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 364, 112, 16), "unit editor", "unit editor attack speed level scroll box", "update unit attack speed level", NULL, 3); - unitAttackStrengthLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 380, 128, 16), "unit editor", "unit editor attack strength level label", "", "[At. strength]", NULL, 3); - unitAttackStrengthLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 396, 112, 16), "unit editor", "unit editor attack strength level scroll box", "update unit attack strength level", NULL, 3); - unitMagicGroundAttackLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 284, 128, 16), "unit editor", "unit editor ground attack level label", "", "[Magic At. Ground]", NULL, 3); - unitMagicGroundAttackLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 300, 112, 16), "unit editor", "unit editor magic ground attack level scroll box", "update unit magic ground attack level", NULL, 3); - addWidget(unitInfoTitle); - addWidget(unitPicture); - addWidget(unitHPLabel); - addWidget(unitHPScrollBox); - addWidget(unitWalkLevelLabel); - addWidget(unitWalkLevelScrollBox); - addWidget(unitSwimLevelLabel); - addWidget(unitSwimLevelScrollBox); - addWidget(unitHarvestLevelLabel); - addWidget(unitHarvestLevelScrollBox); - addWidget(unitBuildLevelLabel); - addWidget(unitBuildLevelScrollBox); - addWidget(unitAttackSpeedLevelLabel); - addWidget(unitAttackSpeedLevelScrollBox); - addWidget(unitAttackStrengthLevelLabel); - addWidget(unitAttackStrengthLevelScrollBox); - addWidget(unitMagicGroundAttackLevelLabel); - addWidget(unitMagicGroundAttackLevelScrollBox); - - buildingInfoTitle = new BuildingInfoTitle(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+2+decX, 173, 128, 16), "building editor", "building editor info title", "", NULL); - buildingPicture = new BuildingPicture(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+2+decX, 203, 56, 46), "building editor", "building editor picture", "", NULL); - buildingHPLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor hp label", "", "[hp]", NULL, static_cast(NULL)); - buildingHPScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor hp scroll box", "update building", NULL, static_cast(NULL)); - buildingFoodQuantityLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor food label", "", "[Wheat]", NULL, static_cast(NULL)); - buildingFoodQuantityScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor food scroll box", "update building", NULL, static_cast(NULL)); - buildingAssignedLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor assigned label", "", "[assigned]", NULL, 20); - buildingAssignedScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor assigned scroll box", "", NULL, 20); - buildingWorkerRatioLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor worker ratio label", "", "[Worker Ratio]", NULL, 16); - buildingWorkerRatioScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor worker ratio scroll box", "", NULL, 20); - buildingExplorerRatioLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor explorer ratio label", "", "[Explorer Ratio]", NULL, 16); - buildingExplorerRatioScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor explorer ratio scroll box", "", NULL, 20); - buildingWarriorRatioLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor warrior ratio label", "", "[Warrior Ratio]", NULL, 16); - buildingWarriorRatioScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor warrior ratio scroll box", "", NULL, 20); - buildingCherryLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor cherry label", "", "[Cherry]", NULL, static_cast(NULL)); - buildingCherryScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor cherry scroll box", "update building", NULL, static_cast(NULL)); - buildingOrangeLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor orange label", "", "[Orange]", NULL, static_cast(NULL)); - buildingOrangeScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor orange scroll box", "update building", NULL, static_cast(NULL)); - buildingPruneLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor prune label", "", "[Prune]", NULL, static_cast(NULL)); - buildingPruneScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor prune scroll box", "update building", NULL, static_cast(NULL)); - buildingStoneLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor stone label", "", "[Stone]", NULL, static_cast(NULL)); - buildingStoneScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor stone scroll box", "update building", NULL, static_cast(NULL)); - buildingBulletsLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor bullets label", "", "[Bullets]", NULL, static_cast(NULL)); - buildingBulletsScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor bullets scroll box", "update building", NULL, static_cast(NULL)); - buildingMinimumLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor minimum level to flag label", "", "[Minimum Level To Flag]", NULL, 3); - buildingMinimumLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor minimum level to flag scroll box", "update building", NULL, 3); - buildingRadiusLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor range label", "", "[range]", NULL, static_cast(NULL)); - buildingRadiusScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor range scroll box", "update building", NULL, static_cast(NULL)); - addWidget(buildingInfoTitle); - addWidget(buildingPicture); - addWidget(buildingHPLabel); - addWidget(buildingHPScrollBox); - addWidget(buildingFoodQuantityLabel); - addWidget(buildingFoodQuantityScrollBox); - addWidget(buildingAssignedLabel); - addWidget(buildingAssignedScrollBox); - addWidget(buildingWorkerRatioLabel); - addWidget(buildingWorkerRatioScrollBox); - addWidget(buildingExplorerRatioLabel); - addWidget(buildingExplorerRatioScrollBox); - addWidget(buildingWarriorRatioLabel); - addWidget(buildingWarriorRatioScrollBox); - addWidget(buildingCherryLabel); - addWidget(buildingCherryScrollBox); - addWidget(buildingOrangeLabel); - addWidget(buildingOrangeScrollBox); - addWidget(buildingPruneLabel); - addWidget(buildingPruneScrollBox); - addWidget(buildingStoneLabel); - addWidget(buildingStoneScrollBox); - addWidget(buildingBulletsLabel); - addWidget(buildingBulletsScrollBox); - addWidget(buildingMinimumLevelLabel); - addWidget(buildingMinimumLevelScrollBox); - addWidget(buildingRadiusLabel); - addWidget(buildingRadiusScrollBox); - - selectionName=""; - buildingLevel=0; - brushType = NoBrush; - enableOnlyGroup("building view"); - - isDraggingMinimap=false; - isDraggingZone=false; - isDraggingTerrain=false; - isDraggingDelete=false; - isScrollDragging=false; - isDraggingArea=false; - isDraggingNoRessourceGrowthArea=false; - - lastPlacementX=-1; - lastPlacementY=-1; - firstPlacementX=-1; - firstPlacementY=-1; - - menuScreen = NULL; - scriptEditor=NULL; - teamsEditor=NULL; - showingMenuScreen=false; - showingLoad=false; - showingSave=false; - showingScriptEditor=false; - showingTeamsEditor=false; - - terrainType=TerrainSelector::NoTerrain; - - teamViewSelectorKeys.push_back("[human]"); - teamViewSelectorKeys.push_back("[ai]"); - - - placingUnit=NoUnit; - placingUnitLevel=0; - - selectedUnitGID=NOGUID; - selectedBuildingGID=NOGBID; - - areaName=NULL; - isShowingAreaName=false; - - isFertilityOn=false; -} - - - -MapEdit::~MapEdit() -{ - Toolkit::releaseSprite("data/gui/editor"); - for(std::vector::iterator i=mew.begin(); i!=mew.end(); ++i) - { - delete *i; - } -} - - -bool MapEdit::load(const std::string filename) -{ - assert(filename.size()); - - InputStream *stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(filename)); - if (stream->isEndOfStream()) - { - std::cerr << "MapEdit::load(\"" << filename << "\") : error, can't open file." << std::endl; - delete stream; - return false; - } - else - { - bool rv; - - try - { - rv = game.load(stream); - } - catch (std::exception &e) - { - std::cerr << "Failed to open map: bad format." << std::endl; - - if (!globalContainer->runNoX) - { - // Display an error message - GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_ONEBUTTON, Toolkit::getStringTable()->getString("[ERROR_CANT_LOAD_MAP]"), Toolkit::getStringTable()->getString("[ok]")); - } - - // We can't recover from this, so we quit - doQuitAfterLoadSave = true; - - return false; - } - - delete stream; - if (!rv) - return false; - - // set the editor default values - team = 0; - - areaNameLabel->setLabel(game.map.getAreaName(areaNumber->getIndex())); - - minimap.resetMinimapDrawing(); - - game.map.computeLocalForbidden(team); - game.map.computeLocalClearArea(team); - game.map.computeLocalGuardArea(team); - - hasMapBeenModified = false; - return true; - } - return false; -} - - - -bool MapEdit::save(const std::string filename, const std::string name) -{ - FertilityCalculatorDialog dialog(globalContainer->gfx, game.map); - dialog.execute(); - - assert(filename.size()); - assert(name.size()); - - hasMapBeenModified = false; - - OutputStream *stream = new BinaryOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(filename)); - if (stream->isEndOfStream()) - { - std::cerr << "MapEdit::save(\"" << filename << "\",\"" << name << "\") : error, can't open file." << std::endl; - delete stream; - return false; - } - else - { - game.save(stream, true, name); - delete stream; - return true; - } -} - - - -int MapEdit::run(int sizeX, int sizeY, TerrainType terrainType) -{ - game.map.setSize(sizeX, sizeY, terrainType); - game.map.setGame(&game); - return run(); -} - - - -int MapEdit::run(void) -{ - //globalContainer->gfx->setRes(globalContainer->graphicWidth, globalContainer->graphicHeight , 32, globalContainer->graphicFlags, (DrawableSurface::GraphicContextType)globalContainer->settings.graphicType); - -// regenerateClipRect(); - - - minimap.setGame(game); - globalContainer->gfx->setClipRect(); - drawMap(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH(), true, true); - drawMiniMap(); - drawMenu(); - - - if(game.gameHeader.getNumberOfPlayers() == 0) - regenerateGameHeader(); - - bool isRunning=true; - int returnCode=0; - Uint64 startTick, endTick, deltaTick; - while (isRunning) - { - //SDL_Event event; - startTick=SDL_GetTicks64(); - - // we get all pending events but for mousemotion we only keep the last one - SDL_Event event; - while (SDL_PollEvent(&event)) - { - processEvent(event); - } - - // While processing events the user could've tried to load a map that failed. - // Then we can't go through drawing everything because that would segfault. - if(doQuitAfterLoadSave && !showingSave) - { - isRunning = false; - break; - } - - if(!showingMenuScreen && !showingLoad && !showingSave && !showingScriptEditor && !showingTeamsEditor) - { - handleMapScroll(); - viewportX+=xSpeed; - viewportY+=ySpeed; - viewportX&=game.map.getMaskW(); - viewportY&=game.map.getMaskH(); - } - - //special overrides here to allow for scrolling and painting terrain at the same time - if(xSpeed!=0 || ySpeed!=0) - { - if(isDraggingZone) - performAction("zone drag motion"); - else if(isDraggingTerrain) - performAction("terrain drag motion"); - else if(isDraggingDelete) - performAction("delete drag motion"); - else if(isDraggingArea) - performAction("area drag motion"); - else if(isDraggingNoRessourceGrowthArea) - performAction("no ressource growth area drag motion"); - } - - drawMap(0, 0, globalContainer->gfx->getW()-0, globalContainer->gfx->getH(), true, true); - - drawMenu(); - drawMiniMap(); - wasMinimapRendered=false; - drawWidgets(); - if(showingMenuScreen) - { - globalContainer->gfx->setClipRect(); - menuScreen->dispatchTimer(startTick); - menuScreen->dispatchPaint(); - globalContainer->gfx->drawSurface((int)menuScreen->decX, (int)menuScreen->decY, menuScreen->getSurface()); - } - if(showingLoad || showingSave) - { - globalContainer->gfx->setClipRect(); - loadSaveScreen->dispatchTimer(startTick); - loadSaveScreen->dispatchPaint(); - globalContainer->gfx->drawSurface((int)loadSaveScreen->decX, (int)loadSaveScreen->decY, loadSaveScreen->getSurface()); - } - if(showingScriptEditor) - { - globalContainer->gfx->setClipRect(); - scriptEditor->dispatchTimer(startTick); - scriptEditor->dispatchPaint(); - globalContainer->gfx->drawSurface((int)scriptEditor->decX, (int)scriptEditor->decY, scriptEditor->getSurface()); - } - if(showingTeamsEditor) - { - globalContainer->gfx->setClipRect(); - teamsEditor->dispatchTimer(startTick); - teamsEditor->dispatchPaint(); - globalContainer->gfx->drawSurface((int)teamsEditor->decX, (int)teamsEditor->decY, teamsEditor->getSurface()); - } - if(isShowingAreaName) - { - globalContainer->gfx->setClipRect(); - areaName->dispatchTimer(startTick); - areaName->dispatchPaint(); - globalContainer->gfx->drawSurface((int)areaName->decX, (int)areaName->decY, areaName->getSurface()); - } - - - globalContainer->gfx->nextFrame(); - - - endTick=SDL_GetTicks64(); - deltaTick=std::max(0, static_cast(endTick) - static_cast(startTick)); - if (deltaTick<33) - SDL_Delay(33-deltaTick); - if (returnCode==-1) - { - isRunning=false; - } - if(doQuitAfterLoadSave && !showingSave) - { - isRunning=false; - } - if(doQuit) - { - if(hasMapBeenModified) - { - int ret = GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_THREEBUTTONS, Toolkit::getStringTable()->getString("[save before quit?]"), Toolkit::getStringTable()->getString("[Yes]"), Toolkit::getStringTable()->getString("[No]"), Toolkit::getStringTable()->getString("[Cancel]")); - if(ret == 0) - { - doQuit=false; - doQuitAfterLoadSave=true; - performAction("open save screen"); - } - else if(ret == 1) - { - isRunning=false; - } - else - { - doQuit=false; - } - } - else - { - isRunning=false; - } - } - if(doFullQuit) - { - returnCode = -1; - } - if(!isRunning) - { - SDL_Event event; - while (SDL_PollEvent(&event)); - } - } - - //globalContainer->gfx->setRes(globalContainer->graphicWidth, globalContainer->graphicHeight , 32, globalContainer->graphicFlags, (DrawableSurface::GraphicContextType)globalContainer->settings.graphicType); - return returnCode; -} - - - -void MapEdit::drawMap(int sx, int sy, int sw, int sh, bool needUpdate, bool doPaintEditMode) -{ -// Utilities::rectClipRect(sx, sy, sw, sh, mapClip); - - globalContainer->gfx->setClipRect(sx, sy, sw, sh); - - Uint32 drawOptions = Game::DRAW_WHOLE_MAP | Game::DRAW_BUILDING_RECT | Game::DRAW_AREA | Game::DRAW_HEALTH_FOOD_BAR | Game::DRAW_SCRIPT_AREAS | Game::DRAW_NO_RESSOURCE_GROWTH_AREAS; - if(isFertilityOn) - { - drawOptions |= Game::DRAW_OVERLAY; - } - - game.drawMap(sx, sy, sw, sh, RIGHT_MENU_WIDTH, 16, viewportX, viewportY, team, drawOptions); -// if (doPaintEditMode) -// paintEditMode(false, false); - - if(widgetRectangle(sx, sy, sw, sh).is_in(mouseX, mouseY)) - { - if(selectionMode==PlaceBuilding) - drawBuildingSelectionOnMap(); - if(selectionMode==PlaceZone) - brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY); - if(selectionMode==PlaceTerrain) - brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY, (terrainType>TerrainSelector::Water ? 0 : 1)); - if(selectionMode==PlaceUnit) - drawPlacingUnitOnMap(); - if(selectionMode==RemoveObject) - brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY); - if(selectionMode==EditingBuilding) - { - Building* selBuild=game.teams[Building::GIDtoTeam(selectedBuildingGID)]->myBuildings[Building::GIDtoID(selectedBuildingGID)]; - globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); - int centerX, centerY; - game.map.buildingPosToCursor(selBuild->posXLocal, selBuild->posYLocal, selBuild->type->width, selBuild->type->height, ¢erX, ¢erY, viewportX, viewportY); - if (selBuild->owner->teamNumber==team) - globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 0, 0, 190); - else if ((game.teams[team]->allies) & (selBuild->owner->me)) - globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 255, 196, 0); - else if (!selBuild->type->isVirtual) - globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 190, 0, 0); - globalContainer->gfx->setClipRect(); - } - if(selectionMode==ChangeAreas) - { - brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY); - } - if(selectionMode==ChangeNoRessourceGrowthAreas) - brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY); - } - - globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW(), globalContainer->gfx->getH()); -} - - - -void MapEdit::drawMiniMap(void) -{ - minimap.draw(team, viewportX, viewportY, (globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)/32, globalContainer->gfx->getH()/32 ); -// paintCoordinates(); -} - - - -void MapEdit::drawMenu(void) -{ - int menuStartW=globalContainer->gfx->getW()-RIGHT_MENU_WIDTH; - int yposition=133; - - if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) - globalContainer->gfx->drawFilledRect(menuStartW, yposition, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128, 0, 0, 0); - else - globalContainer->gfx->drawFilledRect(menuStartW, yposition, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128, 0, 0, 40, 180); - - drawMenuEyeCandy(); -} - - - -void MapEdit::drawBuildingSelectionOnMap() -{ - if (selectionName!="") - { - // we get the type of building - int typeNum=globalContainer->buildingsTypes.getTypeNum(selectionName, buildingLevel, false); - if(!isUpgradable(IntBuildingType::shortNumberFromType(selectionName))) - typeNum = globalContainer->buildingsTypes.getTypeNum(selectionName, 0, false); - BuildingType *bt = globalContainer->buildingsTypes.get(typeNum); - Sprite *sprite = bt->gameSpritePtr; - - // we translate dimensions and situation - int tempX, tempY; - int mapX, mapY; - bool isRoom; - game.map.cursorToBuildingPos(mouseX, mouseY, bt->width, bt->height, &tempX, &tempY, viewportX, viewportY); - if (bt->isVirtual) - isRoom = game.checkRoomForBuilding(tempX, tempY, bt, &mapX, &mapY, team); - else - isRoom = game.checkHardRoomForBuilding(tempX, tempY, bt, &mapX, &mapY); - - // modifiy highlight given room -// if (isRoom) -// highlightSelection = std::min(highlightSelection + 0.1f, 1.0f); -// / else -// highlightSelection = std::max(highlightSelection - 0.1f, 0.0f); - - // we get the screen dimensions of the building - int batW = (bt->width)<<5; - int batH = sprite->getH(bt->gameSpriteImage); - int batX = (((mapX-viewportX)&(game.map.wMask))<<5); - int batY = (((mapY-viewportY)&(game.map.hMask))<<5)-(batH-(bt->height<<5)); - - // we draw the building - sprite->setBaseColor(game.teams[team]->color); - globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); -// int spriteIntensity = 127+static_cast(128.0f*splineInterpolation(1.f, 0.f, 1.f, highlightSelection)); - int spriteIntensity = 127; - globalContainer->gfx->drawSprite(batX, batY, sprite, bt->gameSpriteImage, spriteIntensity); - - if (!bt->isVirtual) - { - if (game.teams[team]->noMoreBuildingSitesCountdown>0) - { - globalContainer->gfx->drawRect(batX, batY, batW, batH, 255, 0, 0, 127); - globalContainer->gfx->drawLine(batX, batY, batX+batW-1, batY+batH-1, 255, 0, 0, 127); - globalContainer->gfx->drawLine(batX+batW-1, batY, batX, batY+batH-1, 255, 0, 0, 127); - - globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 255, 0, 0, 127)); - globalContainer->gfx->drawString(batX, batY-12, globalContainer->littleFont, FormatableString("%0.%1").arg(game.teams[team]->noMoreBuildingSitesCountdown/40).arg((game.teams[team]->noMoreBuildingSitesCountdown%40)/4).c_str()); - globalContainer->littleFont->popStyle(); - } - else - { - if (isRoom) - globalContainer->gfx->drawRect(batX, batY, batW, batH, 255, 255, 255, 127); - else - globalContainer->gfx->drawRect(batX, batY, batW, batH, 255, 0, 0, 127); - - // We look for its maximum extension size - // we find last's level type num: - BuildingType *lastbt=globalContainer->buildingsTypes.get(typeNum); - int lastTypeNum=typeNum; - int max=0; - while (lastbt->nextLevel>=0) - { - lastTypeNum=lastbt->nextLevel; - lastbt=globalContainer->buildingsTypes.get(lastTypeNum); - if (max++>200) - { - printf("GameGUI: Error: nextLevel architecture is broken.\n"); - assert(false); - break; - } - } - - int exMapX, exMapY; // ex prefix means EXtended building; the last level building type. - bool isExtendedRoom = game.checkHardRoomForBuilding(tempX, tempY, lastbt, &exMapX, &exMapY); - int exBatX=((exMapX-viewportX)&(game.map.wMask))<<5; - int exBatY=((exMapY-viewportY)&(game.map.hMask))<<5; - int exBatW=(lastbt->width)<<5; - int exBatH=(lastbt->height)<<5; - - if (isRoom && isExtendedRoom) - globalContainer->gfx->drawRect(exBatX-1, exBatY-1, exBatW+2, exBatH+2, 255, 255, 255, 127); - else - globalContainer->gfx->drawRect(exBatX-1, exBatY-1, exBatW+2, exBatH+2, 255, 0, 0, 127); - } - } - - } - -} - - - -bool MapEdit::isUpgradable(int buildingLevel) -{ - if(buildingLevel==IntBuildingType::SWARM_BUILDING) - return false; - if(buildingLevel==IntBuildingType::EXPLORATION_FLAG) - return false; - if(buildingLevel==IntBuildingType::WAR_FLAG) - return false; - if(buildingLevel==IntBuildingType::CLEARING_FLAG) - return false; - if(buildingLevel==IntBuildingType::STONE_WALL) - return false; - if(buildingLevel==IntBuildingType::MARKET_BUILDING) - return false; - return true; -} - - - -void MapEdit::drawMenuEyeCandy() -{ - globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW(), globalContainer->gfx->getH()); - - // bar background - if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) - globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 16, 0, 0, 0); - else - globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 16, 0, 0, 40, 180); - - // draw window bar - int pos=globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-32; - for (int i=0; i<=pos; i+=32) - { - globalContainer->gfx->drawSprite(i, 16, globalContainer->gamegui, 16); - } - for (int i=16; igfx->getH(); i+=32) - { - globalContainer->gfx->drawSprite(pos+28, i, globalContainer->gamegui, 17); - } -} - - - -void MapEdit::drawPlacingUnitOnMap() -{ - int type=0; - if(placingUnit==Worker) - type=WORKER; - else if(placingUnit==Warrior) - type=WARRIOR; - else if(placingUnit==Explorer) - type=EXPLORER; - - int level=placingUnitLevel; - - int cx=(mouseX>>5)+viewportX; - int cy=(mouseY>>5)+viewportY; - - int px=mouseX&0xFFFFFFE0; - int py=mouseY&0xFFFFFFE0; - int pw=32; - int ph=32; - - bool isRoom; - if (type==EXPLORER) - isRoom=game.map.isFreeForAirUnit(cx, cy); - else - { - UnitType *ut=game.teams[team]->race.getUnitType(type, level); - isRoom=game.map.isFreeForGroundUnit(cx, cy, ut->performance[SWIM], Team::teamNumberToMask(team)); - } - - int imgid; - if (type==WORKER) - imgid=64; - else if (type==EXPLORER) - imgid=0; - else if (type==WARRIOR) - imgid=256; - else - { - imgid=0; - assert(false); - } - - Sprite *unitSprite=globalContainer->units; - unitSprite->setBaseColor(game.teams[team]->color); - - globalContainer->gfx->drawSprite(px, py, unitSprite, imgid); - - if (isRoom) - globalContainer->gfx->drawRect(px, py, pw, ph, 255, 255, 255, 128); - else - globalContainer->gfx->drawRect(px, py, pw, ph, 255, 0, 0, 128); -} - -void MapEdit::processEvent(SDL_Event& event) -{ - if (event.type==SDL_QUIT) - { - doFullQuit=true; - } -# ifdef USE_OSX - else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_q && SDL_GetModState() & KMOD_GUI) - { - doFullQuit=true; - } -# endif -# ifdef USE_WIN32 - else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_F4 && SDL_GetModState() & KMOD_ALT) - { - doFullQuit=true; - } -# endif - - else if(showingMenuScreen || showingLoad || showingSave || showingScriptEditor || showingTeamsEditor || isShowingAreaName) - { - delegateMenu(event); - return; - } - else if(event.type==SDL_MOUSEMOTION) - { - mouseX=event.motion.x; - mouseY=event.motion.y; - relMouseX=event.motion.xrel; - relMouseY=event.motion.yrel; - updateCoordinatesLabel(); - if(isDraggingMinimap) - { - performAction("minimap drag motion", relMouseX, relMouseY); - performAction("scroll horizontal stop", relMouseX, relMouseY); - performAction("scroll vertical stop", relMouseX, relMouseY); - } - else if(isDraggingZone) - { - if(widgetRectangle(0, 16, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-16).is_in(mouseX, mouseY)) - performAction("zone drag motion", relMouseX, relMouseY); - } - else if(isDraggingTerrain) - { - if(widgetRectangle(0, 16, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-16).is_in(mouseX, mouseY)) - performAction("terrain drag motion", relMouseX, relMouseY); - } - else if(isScrollDragging) - { - performAction("scroll drag motion", relMouseX, relMouseY); - } - else if(isDraggingDelete) - { - performAction("delete drag motion", relMouseX, relMouseY); - } - else if(isDraggingArea) - { - performAction("area drag motion", relMouseX, relMouseY); - } - else if(isDraggingNoRessourceGrowthArea) - { - performAction("no ressource growth area drag motion", relMouseX, relMouseY); - } - } - else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT) - { - if(!findAction(event.button.x, event.button.y) && widgetRectangle(0, 16, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()).is_in(mouseX, mouseY)) - { - //The button wasn't clicked in any registered area - if(selectionMode==PlaceBuilding) - performAction("place building"); - else if(selectionMode==PlaceZone) - performAction("zone drag start"); - else if(selectionMode==PlaceTerrain) - performAction("terrain drag start"); - else if(selectionMode==PlaceUnit) - performAction("place unit"); - else if(selectionMode==RemoveObject) - performAction("delete drag start"); - else if(selectionMode==ChangeAreas) - performAction("area drag start"); - else if(selectionMode==ChangeNoRessourceGrowthAreas) - performAction("no ressource growth area drag start"); - else - { - performAction("select map unit"); - performAction("select map building"); - } - } - else if(widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET+14, 14, 100, 100).is_in(mouseX, mouseY)) - performAction("minimap drag start"); - } - else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_RIGHT) - { - if(selectionMode==PlaceNothing || selectionMode==EditingUnit || selectionMode==EditingBuilding) - performAction("change menu"); - if(selectionMode!=PlaceNothing) - performAction("unselect"); - } - else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_MIDDLE) - { - performAction("scroll drag start"); - } - else if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT) - { - if(isDraggingMinimap) - performAction("minimap drag stop"); - if(isDraggingZone) - performAction("zone drag end"); - if(isDraggingTerrain) - performAction("terrain drag end"); - if(isDraggingDelete) - performAction("delete drag end"); - if(isDraggingArea) - performAction("area drag end"); - if(isDraggingNoRessourceGrowthArea) - performAction("no ressource growth area drag end"); - } - else if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_MIDDLE) - { - if(isScrollDragging) - performAction("scroll drag stop"); - } - else if(event.type==SDL_KEYDOWN) - { - handleKeyPressed(event.key.keysym, true); - } - else if(event.type==SDL_KEYUP) - { - handleKeyPressed(event.key.keysym, false); - } -} - - - -void MapEdit::handleKeyPressed(SDL_Keysym key, bool pressed) -{ - Uint32 action_t = keyboardManager.getAction(KeyPress(key, pressed)); - switch(action_t) - { - case MapEditKeyActions::DoNothing: - break; - case MapEditKeyActions::SwitchToBuildingView: - { - performAction("switch to building view"); - } - break; - case MapEditKeyActions::SwitchToFlagView: - { - performAction("switch to flag view"); - } - break; - case MapEditKeyActions::SwitchToTerrainView: - { - performAction("switch to terrain view"); - } - break; - case MapEditKeyActions::SwitchToTeamsView: - { - performAction("switch to teams view"); - } - break; - case MapEditKeyActions::OpenSaveScreen: - { - performAction("open save screen"); - } - break; - case MapEditKeyActions::OpenLoadScreen: - { - performAction("open load screen"); - } - break; - case MapEditKeyActions::SelectSwarm: - { - performAction("unselect&switch to building view&set place building selection swarm"); - } - break; - case MapEditKeyActions::SelectInn: - { - performAction("unselect&switch to building view&set place building selection inn"); - } - break; - case MapEditKeyActions::SelectHospital: - { - performAction("unselect&switch to building view&set place building selection hospital"); - } - break; - case MapEditKeyActions::SelectRacetrack: - { - performAction("unselect&switch to building view&set place building selection racetrack"); - } - break; - case MapEditKeyActions::SelectSwimmingpool: - { - performAction("unselect&switch to building view&set place building selection swimmingpool"); - } - break; - case MapEditKeyActions::SelectSchool: - { - performAction("unselect&switch to building view&set place building selection school"); - } - break; - case MapEditKeyActions::SelectBarracks: - { - performAction("unselect&switch to building view&set place building selection barracks"); - } - break; - case MapEditKeyActions::SelectTower: - { - performAction("unselect&switch to building view&set place building selection defencetower"); - } - break; - case MapEditKeyActions::SelectStonewall: - { - performAction("unselect&switch to building view&set place building selection stonewall"); - } - break; - case MapEditKeyActions::SelectMarket: - { - performAction("unselect&switch to building view&set place building selection market"); - } - break; - case MapEditKeyActions::SelectExplorationFlag: - { - performAction("unselect&switch to flag view&set place building selection explorationflag"); - } - break; - case MapEditKeyActions::SelectWarFlag: - { - performAction("unselect&switch to flag view&set place building selection warflag"); - } - break; - case MapEditKeyActions::SelectClearingFlag: - { - performAction("unselect&switch to flag view&set place building selection clearingflag"); - } - break; - case MapEditKeyActions::ToggleMenuScreen: - { - if (showingMenuScreen==false) - performAction("open menu screen"); - else if (showingMenuScreen==true) - performAction("close menu screen"); - } - break; - case MapEditKeyActions::SelectDeleteTool: - { - performAction("switch to flag view&select delete objects"); - } - break; - } -} - - - -void MapEdit::performAction(const std::string& action, int relMouseX, int relMouseY) -{ -// std::cout<setUnselected(); - areasButton->setUnselected(); - noRessourceGrowthButton->setUnselected(); - isDraggingZone=false; - isDraggingTerrain=false; - isDraggingDelete=false; - isDraggingArea=false; - isDraggingNoRessourceGrowthArea=false; - if(panelMode==UnitEditor) - performAction("switch to building view"); - } - else if(action=="change menu") - { - if(panelMode==AddBuildings) - performAction("switch to flag view"); - else if(panelMode==AddFlagsAndZones) - performAction("switch to terrain view"); - else if(panelMode==Terrain) - performAction("switch to teams view"); - else if(panelMode==Teams) - performAction("switch to building view"); - else - performAction("switch to building view"); - } - else if(action=="minimap drag start") - { - isDraggingMinimap=true; - minimapMouseToPos(mouseX-globalContainer->gfx->getW()+RIGHT_MENU_WIDTH-RIGHT_MENU_OFFSET, mouseY, &viewportX, &viewportY, true); - } - else if(action=="minimap drag motion") - { - minimapMouseToPos(mouseX-globalContainer->gfx->getW()+RIGHT_MENU_WIDTH-RIGHT_MENU_OFFSET, mouseY, &viewportX, &viewportY, true); - } - else if(action=="minimap drag stop") - { - isDraggingMinimap=false; - } - else if(action=="place building") - { - int typeNum=globalContainer->buildingsTypes.getTypeNum(selectionName, buildingLevel, false); - if(!isUpgradable(IntBuildingType::shortNumberFromType(selectionName))) - typeNum = globalContainer->buildingsTypes.getTypeNum(selectionName, 0, false); - BuildingType *bt = globalContainer->buildingsTypes.get(typeNum); - int tempX, tempY, x, y; - game.map.cursorToBuildingPos(mouseX, mouseY, bt->width, bt->height, &tempX, &tempY, viewportX, viewportY); - - if (game.checkRoomForBuilding(tempX, tempY, bt, &x, &y, team, false)) - { - if(bt->maxUnitWorking) - game.addBuilding(x, y, typeNum, team, 1, 0); - else - game.addBuilding(x, y, typeNum, team, 0, 0); - if (selectionName=="swarm") - { - if (game.teams[team]->startPosSet<3) - { - game.teams[team]->startPosX=tempX; - game.teams[team]->startPosY=tempY; - game.teams[team]->startPosSet=3; - } - } - else - { - if (game.teams[team]->startPosSet<2) - { - game.teams[team]->startPosX=tempX; - game.teams[team]->startPosY=tempY; - game.teams[team]->startPosSet=2; - } - } - game.regenerateDiscoveryMap(); - hasMapBeenModified = true; - } - } - else if(action=="switch to building level 1") - { - buildingLevel=0; - } - else if(action=="switch to building level 2") - { - buildingLevel=1; - } - else if(action=="switch to building level 3") - { - buildingLevel=2; - } - else if(action=="open menu screen") - { - performAction("unselect"); - performAction("scroll horizontal stop"); - performAction("scroll vertical stop"); - menuScreen=new MapEditMenuScreen; - showingMenuScreen=true; - } - else if(action=="close menu screen") - { - delete menuScreen; - menuScreen=NULL; - showingMenuScreen=false; - } - else if(action=="open load screen") - { - performAction("unselect"); - performAction("scroll horizontal stop"); - performAction("scroll vertical stop"); - loadSaveScreen=new LoadSaveScreen("maps", "map", true, false, game.mapHeader.getMapName().c_str(), glob2FilenameToName, glob2NameToFilename); - showingLoad=true; - } - else if(action=="close load screen") - { - delete loadSaveScreen; - showingLoad=false; - loadSaveScreen=NULL; - } - else if(action=="open save screen") - { - performAction("unselect"); - performAction("scroll horizontal stop"); - performAction("scroll vertical stop"); - loadSaveScreen=new LoadSaveScreen("maps", "map", false, false, game.mapHeader.getMapName().c_str(), glob2FilenameToName, glob2NameToFilename); - showingSave=true; - } - else if(action=="close save screen") - { - delete loadSaveScreen; - showingSave=false; - loadSaveScreen=NULL; - } - else if(action=="open scenario editor") - { - performAction("unselect"); - performAction("scroll horizontal stop"); - performAction("scroll vertical stop"); - scriptEditor=new ScriptEditorScreen(&game); - showingScriptEditor=true; - hasMapBeenModified=true; - } - else if(action=="close scenario editor") - { - delete scriptEditor; - showingScriptEditor=false; - scriptEditor=NULL; - } - else if(action=="open teams editor") - { - performAction("unselect"); - performAction("scroll horizontal stop"); - performAction("scroll vertical stop"); - - for (int i=0; igetIndex())); - isShowingAreaName=true; - } - else if(action=="close area name") - { - game.map.setAreaName(areaNumber->getIndex(), areaName->getText()); - performAction("update script area number"); - delete areaName; - isShowingAreaName=false; - areaName=NULL; - } - else if(action=="select forbidden zone") - { - performAction("unselect"); - brushType = ForbiddenBrush; - selectionMode=PlaceZone; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select clearing zone") - { - performAction("unselect"); - brushType = ClearAreaBrush; - selectionMode=PlaceZone; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select guard zone") - { - performAction("unselect"); - brushType = GuardAreaBrush; - selectionMode=PlaceZone; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="handle zone click") - { - if(brushType==NoBrush) - { - performAction("unselect"); - performAction("select forbidden zone"); - } - brush.handleClick(relMouseX, relMouseY); - } - else if(action=="zone drag start") - { - isDraggingZone=true; - handleBrushClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="zone drag motion") - { - handleBrushClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="zone drag end") - { - isDraggingZone=false; - lastPlacementX=-1; - lastPlacementY=-1; - firstPlacementX=-1; - firstPlacementY=-1; - } - else if(action=="select grass") - { - performAction("unselect"); - terrainType=TerrainSelector::Grass; - selectionMode=PlaceTerrain; - - brush.defaultSelection(); - brush.setAddRemoveEnabledState(false); - } - else if(action=="select sand") - { - performAction("unselect"); - terrainType=TerrainSelector::Sand; - selectionMode=PlaceTerrain; - - brush.defaultSelection(); - brush.setAddRemoveEnabledState(false); - } - else if(action=="select water") - { - performAction("unselect"); - terrainType=TerrainSelector::Water; - selectionMode=PlaceTerrain; - - brush.defaultSelection(); - brush.setAddRemoveEnabledState(false); - } - else if(action=="select wheat") - { - performAction("unselect"); - terrainType=TerrainSelector::Wheat; - selectionMode=PlaceTerrain; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select trees") - { - performAction("unselect"); - terrainType=TerrainSelector::Trees; - selectionMode=PlaceTerrain; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select stone") - { - performAction("unselect"); - terrainType=TerrainSelector::Stone; - selectionMode=PlaceTerrain; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select algae") - { - performAction("unselect"); - terrainType=TerrainSelector::Algae; - selectionMode=PlaceTerrain; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select papyrus") - { - performAction("unselect"); - terrainType=TerrainSelector::Papyrus; - selectionMode=PlaceTerrain; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select cherry tree") - { - performAction("unselect"); - terrainType=TerrainSelector::CherryTree; - selectionMode=PlaceTerrain; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select orange tree") - { - performAction("unselect"); - terrainType=TerrainSelector::OrangeTree; - selectionMode=PlaceTerrain; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select prune tree") - { - performAction("unselect"); - terrainType=TerrainSelector::PruneTree; - selectionMode=PlaceTerrain; - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="select delete objects") - { - performAction("unselect"); - selectionMode=RemoveObject; - deleteButton->setSelected(); - - brush.defaultSelection(); - brush.setAddRemoveEnabledState(false); - } - else if(action=="select no ressources growth") - { - performAction("unselect"); - selectionMode=ChangeNoRessourceGrowthAreas; - noRessourceGrowthButton->setSelected(); - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="handle terrain click") - { - if(terrainType==TerrainSelector::NoTerrain && selectionMode!=RemoveObject && selectionMode!=ChangeAreas && selectionMode!=ChangeNoRessourceGrowthAreas) - performAction("select grass"); - brush.handleClick(relMouseX, relMouseY); - } - else if(action=="terrain drag start") - { - isDraggingTerrain=true; - handleTerrainClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="terrain drag motion") - { - handleTerrainClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="terrain drag end") - { - isDraggingTerrain=false; - lastPlacementX=-1; - lastPlacementY=-1; - firstPlacementX=-1; - firstPlacementY=-1; - } - else if(action=="delete drag start") - { - isDraggingDelete=true; - handleDeleteClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="delete drag motion") - { - handleDeleteClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="delete drag end") - { - isDraggingDelete=false; - lastPlacementX=-1; - lastPlacementY=-1; - firstPlacementX=-1; - firstPlacementY=-1; - } - else if(action=="update script area number") - { - areaNameLabel->setLabel(game.map.getAreaName(areaNumber->getIndex())); - hasMapBeenModified = true; - } - else if(action=="select change areas") - { - performAction("unselect"); - selectionMode=ChangeAreas; - areasButton->setSelected(); - if (brush.getType() == BrushTool::MODE_NONE) - brush.defaultSelection(); - brush.setAddRemoveEnabledState(true); - } - else if(action=="area drag start") - { - isDraggingArea=true; - handleAreaClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="area drag motion") - { - handleAreaClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="area drag end") - { - isDraggingArea=false; - lastPlacementX=-1; - lastPlacementY=-1; - firstPlacementX=-1; - firstPlacementY=-1; - } - else if(action=="no ressource growth area drag start") - { - isDraggingNoRessourceGrowthArea=true; - handleNoRessourceGrowthClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="no ressource growth area drag motion") - { - handleNoRessourceGrowthClick(mouseX, mouseY); - hasMapBeenModified = true; - } - else if(action=="no ressource growth area drag end") - { - isDraggingNoRessourceGrowthArea=false; - lastPlacementX=-1; - lastPlacementY=-1; - firstPlacementX=-1; - firstPlacementY=-1; - } - else if(action=="add team") - { - if(game.mapHeader.getNumberOfTeams() < 12) - { - game.addTeam(); - regenerateGameHeader(); - } - hasMapBeenModified = true; - } - else if(action=="remove team") - { - if(game.mapHeader.getNumberOfTeams() > 1) - { - if(team==game.mapHeader.getNumberOfTeams()-1) - team-=1; - game.removeTeam(); - regenerateGameHeader(); - } - hasMapBeenModified = true; - } - else if(action=="select active team") - { - int n=relMouseX/16 + (relMouseY/16)*6; - if(game.teams[n]) - { - team=n; - game.map.computeLocalForbidden(team); - game.map.computeLocalClearArea(team); - game.map.computeLocalGuardArea(team); - } - } - else if(action=="select worker") - { - performAction("unselect"); - placingUnit=Worker; - selectionMode=PlaceUnit; - } - else if(action=="select warrior") - { - performAction("unselect"); - placingUnit=Warrior; - selectionMode=PlaceUnit; - } - else if(action=="select explorer") - { - performAction("unselect"); - placingUnit=Explorer; - selectionMode=PlaceUnit; - } - else if(action=="select unit level 1") - { - placingUnitLevel=0; - } - else if(action=="select unit level 2") - { - placingUnitLevel=1; - } - else if(action=="select unit level 3") - { - placingUnitLevel=2; - } - else if(action=="select unit level 4") - { - placingUnitLevel=3; - } - else if(action=="place unit") - { - int type=0; - if(placingUnit==Worker) - type=WORKER; - else if(placingUnit==Warrior) - type=WARRIOR; - else if(placingUnit==Explorer) - type=EXPLORER; - int level=placingUnitLevel; - - int x; - int y; - game.map.displayToMapCaseAligned(mouseX, mouseY, &x, &y, viewportX, viewportY); - - Unit *unit=game.addUnit(x, y, team, type, level, rand()%256, 0, 0); - if (unit) - { - if (game.teams[team]->startPosSet<1) - { - game.teams[team]->startPosX=viewportX; - game.teams[team]->startPosY=viewportY; - game.teams[team]->startPosSet=1; - } - game.regenerateDiscoveryMap(); - hasMapBeenModified = true; - } - } - else if(action=="select map unit") - { - int x; - int y; - int gid=NOGUID; - game.map.displayToMapCaseAligned(mouseX, mouseY, &x, &y, viewportX, viewportY); - if(game.map.getAirUnit(x, y)!=NOGUID) - { - gid=game.map.getAirUnit(x, y); - } - else if(game.map.getGroundUnit(x, y)!=NOGUID) - { - gid=game.map.getGroundUnit(x, y); - } - if(gid!=NOGUID) - { - performAction("unselect"); - selectedUnitGID=gid; - game.selectedUnit=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; - selectionMode=EditingUnit; - panelMode=UnitEditor; - unitInfoTitle->setUnit(game.selectedUnit); - unitPicture->setUnit(game.selectedUnit); - unitHPLabel->setValues(&game.selectedUnit->hp, &game.selectedUnit->performance[HP]); - unitHPScrollBox ->setValues(&game.selectedUnit->hp, &game.selectedUnit->performance[HP]); - unitWalkLevelLabel->setValues(&game.selectedUnit->level[WALK]); - unitWalkLevelScrollBox->setValues(&game.selectedUnit->level[WALK]); - unitSwimLevelLabel->setValues(&game.selectedUnit->level[SWIM]); - unitSwimLevelScrollBox->setValues(&game.selectedUnit->level[SWIM]); - unitHarvestLevelLabel->setValues(&game.selectedUnit->level[HARVEST]); - unitHarvestLevelScrollBox->setValues(&game.selectedUnit->level[HARVEST]); - unitBuildLevelLabel->setValues(&game.selectedUnit->level[BUILD]); - unitBuildLevelScrollBox->setValues(&game.selectedUnit->level[BUILD]); - unitAttackSpeedLevelLabel->setValues(&game.selectedUnit->level[ATTACK_SPEED]); - unitAttackSpeedLevelScrollBox->setValues(&game.selectedUnit->level[ATTACK_SPEED]); - unitAttackStrengthLevelLabel->setValues(&game.selectedUnit->level[ATTACK_STRENGTH]); - unitAttackStrengthLevelScrollBox->setValues(&game.selectedUnit->level[ATTACK_STRENGTH]); - unitMagicGroundAttackLevelLabel->setValues(&game.selectedUnit->level[MAGIC_ATTACK_GROUND]); - unitMagicGroundAttackLevelScrollBox->setValues(&game.selectedUnit->level[MAGIC_ATTACK_GROUND]); - enableOnlyGroup("unit editor"); - if(!game.selectedUnit->canLearn[WALK]) - { - unitWalkLevelLabel->disable(); - unitWalkLevelScrollBox->disable(); - } - if(!game.selectedUnit->canLearn[SWIM]) - { - unitSwimLevelLabel->disable(); - unitSwimLevelScrollBox->disable(); - } - if(!game.selectedUnit->canLearn[HARVEST]) - { - unitHarvestLevelLabel->disable(); - unitHarvestLevelScrollBox->disable(); - } - if(!game.selectedUnit->canLearn[BUILD]) - { - unitBuildLevelLabel->disable(); - unitBuildLevelScrollBox->disable(); - } - if(!game.selectedUnit->canLearn[ATTACK_SPEED]) - { - unitAttackSpeedLevelLabel->disable(); - unitAttackSpeedLevelScrollBox->disable(); - } - if(!game.selectedUnit->canLearn[ATTACK_STRENGTH]) - { - unitAttackStrengthLevelLabel->disable(); - unitAttackStrengthLevelScrollBox->disable(); - } - if(!game.selectedUnit->canLearn[MAGIC_ATTACK_GROUND]) - { - unitMagicGroundAttackLevelLabel->disable(); - unitMagicGroundAttackLevelScrollBox->disable(); - } - } - } - else if(action=="update unit walk level") - { - Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; - UnitType *ut = u->race->getUnitType(u->typeNum, u->level[WALK]); - u->performance[WALK] = ut->performance[WALK]; - hasMapBeenModified = true; - } - else if(action=="update unit swim level") - { - Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; - UnitType *ut = u->race->getUnitType(u->typeNum, u->level[SWIM]); - u->performance[SWIM] = ut->performance[SWIM]; - hasMapBeenModified = true; - } - else if(action=="update unit harvest level") - { - Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; - UnitType *ut = u->race->getUnitType(u->typeNum, u->level[HARVEST]); - u->performance[HARVEST] = ut->performance[HARVEST]; - hasMapBeenModified = true; - } - else if(action=="update unit build level") - { - Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; - UnitType *ut = u->race->getUnitType(u->typeNum, u->level[BUILD]); - u->performance[BUILD] = ut->performance[BUILD]; - hasMapBeenModified = true; - } - else if(action=="update unit attack speed level") - { - Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; - UnitType *ut = u->race->getUnitType(u->typeNum, u->level[ATTACK_SPEED]); - u->performance[ATTACK_SPEED] = ut->performance[ATTACK_SPEED]; - hasMapBeenModified = true; - } - else if(action=="update unit attack strength level") - { - Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; - UnitType *ut = u->race->getUnitType(u->typeNum, u->level[ATTACK_STRENGTH]); - u->performance[ATTACK_STRENGTH] = ut->performance[ATTACK_STRENGTH]; - hasMapBeenModified = true; - } - else if(action=="update unit magic ground attack level") - { - Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; - UnitType *ut = u->race->getUnitType(u->typeNum, u->level[MAGIC_ATTACK_GROUND]); - u->performance[MAGIC_ATTACK_GROUND] = ut->performance[MAGIC_ATTACK_GROUND]; - hasMapBeenModified = true; - } - else if(action=="update unit") - { - hasMapBeenModified = true; - } - else if(action=="select map building") - { - int x; - int y; - game.map.displayToMapCaseAligned(mouseX, mouseY, &x, &y, viewportX, viewportY); - int gid=NOGBID; - for(int t=0; t<32; ++t) - { - if(game.teams[t] && gid==NOGBID) - { - for (std::list::iterator virtualIt=game.teams[t]->virtualBuildings.begin(); - virtualIt!=game.teams[t]->virtualBuildings.end(); ++virtualIt) - { - { - Building *b=*virtualIt; - if ((b->posX==x) && (b->posY==y)) - { - gid=b->gid; - break; - } - } - } - } - } - if(gid==NOGBID && game.map.getBuilding(x, y)!=NOGUID) - { - gid=game.map.getBuilding(x, y); - } - if(gid!=NOGBID) - { - performAction("unselect"); - Building* b=game.teams[Building::GIDtoTeam(gid)]->myBuildings[Building::GIDtoID(gid)]; - selectionMode=EditingBuilding; - panelMode=BuildingEditor; - selectedBuildingGID=gid; - enableOnlyGroup("building editor"); - buildingInfoTitle->setBuilding(b); - buildingPicture->setBuilding(b); - bool hpLabel=false; - buildingHPLabel->setValues(&b->hp, &b->type->hpMax); - buildingHPScrollBox->setValues(&b->hp, &b->type->hpMax); - bool foodLabel=false; - buildingFoodQuantityLabel->setValues(&b->ressources[CORN], &b->type->maxRessource[CORN]); - buildingFoodQuantityScrollBox->setValues(&b->ressources[CORN], &b->type->maxRessource[CORN]); - bool assignedLabel=false; - buildingAssignedLabel->setValues(&b->maxUnitWorking); - buildingAssignedScrollBox->setValues(&b->maxUnitWorking); - bool workerRatioLabel=false; - buildingWorkerRatioLabel->setValues(&b->ratio[WORKER]); - buildingWorkerRatioScrollBox->setValues(&b->ratio[WORKER]); - bool explorerRatioLabel=false; - buildingExplorerRatioLabel->setValues(&b->ratio[EXPLORER]); - buildingExplorerRatioScrollBox->setValues(&b->ratio[EXPLORER]); - bool warriorRatioLabel=false; - buildingWarriorRatioLabel->setValues(&b->ratio[WARRIOR]); - buildingWarriorRatioScrollBox->setValues(&b->ratio[WARRIOR]); - bool cherryLabel=false; - buildingCherryLabel->setValues(&b->ressources[CHERRY], &b->type->maxRessource[CHERRY]); - buildingCherryScrollBox->setValues(&b->ressources[CHERRY], &b->type->maxRessource[CHERRY]); - bool orangeLabel=false; - buildingOrangeLabel->setValues(&b->ressources[ORANGE], &b->type->maxRessource[ORANGE]); - buildingOrangeScrollBox->setValues(&b->ressources[ORANGE], &b->type->maxRessource[ORANGE]); - bool pruneLabel=false; - buildingPruneLabel->setValues(&b->ressources[PRUNE], &b->type->maxRessource[PRUNE]); - buildingPruneScrollBox->setValues(&b->ressources[PRUNE], &b->type->maxRessource[PRUNE]); - bool stoneLabel=false; - buildingStoneLabel->setValues(&b->ressources[STONE], &b->type->maxRessource[STONE]); - buildingStoneScrollBox->setValues(&b->ressources[STONE], &b->type->maxRessource[STONE]); - bool bulletsLabel=false; - buildingBulletsLabel->setValues(&b->bullets, &b->type->maxBullets); - buildingBulletsScrollBox->setValues(&b->bullets, &b->type->maxBullets); - bool minimumLevel=false; - buildingMinimumLevelLabel->setValues(&b->minLevelToFlag); - buildingMinimumLevelScrollBox->setValues(&b->minLevelToFlag); - bool radius=false; - buildingRadiusLabel->setValues(&b->unitStayRange, &b->type->maxUnitStayRange); - buildingRadiusScrollBox->setValues(&b->unitStayRange, &b->type->maxUnitStayRange); - if(b->type->isBuildingSite) - { - hpLabel=true; - assignedLabel=true; - } - else if(b->shortTypeNum==IntBuildingType::SWARM_BUILDING) - { - hpLabel=true; - foodLabel=true; - assignedLabel=true; - workerRatioLabel=true; - explorerRatioLabel=true; - warriorRatioLabel=true; - } - else if(b->shortTypeNum==IntBuildingType::FOOD_BUILDING) - { - hpLabel=true; - foodLabel=true; - assignedLabel=true; - } - else if(b->shortTypeNum==IntBuildingType::HEAL_BUILDING) - { - hpLabel=true; - } - else if(b->shortTypeNum==IntBuildingType::WALKSPEED_BUILDING) - { - hpLabel=true; - } - else if(b->shortTypeNum==IntBuildingType::SWIMSPEED_BUILDING) - { - hpLabel=true; - } - else if(b->shortTypeNum==IntBuildingType::ATTACK_BUILDING) - { - hpLabel=true; - } - else if(b->shortTypeNum==IntBuildingType::SCIENCE_BUILDING) - { - hpLabel=true; - } - if(b->shortTypeNum==IntBuildingType::DEFENSE_BUILDING) - { - hpLabel=true; - assignedLabel=true; - stoneLabel=true; - bulletsLabel=true; - } - else if(b->shortTypeNum==IntBuildingType::EXPLORATION_FLAG) - { - assignedLabel=true; - radius=true; - } - else if(b->shortTypeNum==IntBuildingType::WAR_FLAG) - { - assignedLabel=true; - minimumLevel=true; - radius=true; - } - else if(b->shortTypeNum==IntBuildingType::CLEARING_FLAG) - { - assignedLabel=true; - minimumLevel=true; - radius=true; - } - else if(b->shortTypeNum==IntBuildingType::STONE_WALL) - { - hpLabel=true; - } - else if(b->shortTypeNum==IntBuildingType::MARKET_BUILDING) - { - hpLabel=true; - assignedLabel=true; - cherryLabel=true; - orangeLabel=true; - pruneLabel=true; - } - - int ypos=252; - if(!hpLabel) - { - buildingHPLabel->disable(); - buildingHPScrollBox->disable(); - } - else - { - buildingHPLabel->area.y=ypos; - buildingHPScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!foodLabel) - { - buildingFoodQuantityLabel->disable(); - buildingFoodQuantityScrollBox->disable(); - } - else - { - buildingFoodQuantityLabel->area.y=ypos; - buildingFoodQuantityScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!assignedLabel) - { - buildingAssignedLabel->disable(); - buildingAssignedScrollBox->disable(); - } - else - { - buildingAssignedLabel->area.y=ypos; - buildingAssignedScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!workerRatioLabel) - { - buildingWorkerRatioLabel->disable(); - buildingWorkerRatioScrollBox->disable(); - } - else - { - buildingWorkerRatioLabel->area.y=ypos; - buildingWorkerRatioScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!explorerRatioLabel) - { - buildingExplorerRatioLabel->disable(); - buildingExplorerRatioScrollBox->disable(); - } - else - { - buildingExplorerRatioLabel->area.y=ypos; - buildingExplorerRatioScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!warriorRatioLabel) - { - buildingWarriorRatioLabel->disable(); - buildingWarriorRatioScrollBox->disable(); - } - else - { - buildingWarriorRatioLabel->area.y=ypos; - buildingWarriorRatioScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!cherryLabel) - { - buildingCherryLabel->disable(); - buildingCherryScrollBox->disable(); - } - else - { - buildingCherryLabel->area.y=ypos; - buildingCherryScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!orangeLabel) - { - buildingOrangeLabel->disable(); - buildingOrangeScrollBox->disable(); - } - else - { - buildingOrangeLabel->area.y=ypos; - buildingOrangeScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!pruneLabel) - { - buildingPruneLabel->disable(); - buildingPruneScrollBox->disable(); - } - else - { - buildingPruneLabel->area.y=ypos; - buildingPruneScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!stoneLabel) - { - buildingStoneLabel->disable(); - buildingStoneScrollBox->disable(); - } - else - { - buildingStoneLabel->area.y=ypos; - buildingStoneScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!bulletsLabel) - { - buildingBulletsLabel->disable(); - buildingBulletsScrollBox->disable(); - } - else - { - buildingBulletsLabel->area.y=ypos; - buildingBulletsScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!minimumLevel) - { - buildingMinimumLevelLabel->disable(); - buildingMinimumLevelScrollBox->disable(); - } - else - { - buildingMinimumLevelLabel->area.y=ypos; - buildingMinimumLevelScrollBox->area.y=ypos+16; - ypos+=32; - } - - if(!radius) - { - buildingRadiusLabel->disable(); - buildingRadiusScrollBox->disable(); - } - else - { - buildingRadiusLabel->area.y=ypos; - buildingRadiusScrollBox->area.y=ypos+16; - ypos+=32; - } - } - } - else if(action=="update building") - { - hasMapBeenModified = true; - } - else if(action=="compute fertility") - { - //Only compute when its x'ed in, not otherwise - if(isFertilityOn) - { - FertilityCalculatorDialog dialog(globalContainer->gfx, game.map); - dialog.execute(); - overlay.forceRecompute(); - overlay.compute(game, OverlayArea::Fertility, team); - } - } - else if(action=="quit editor") - { - doQuit=true; - } -} - - - -void MapEdit::delegateMenu(SDL_Event& event) -{ - if(showingMenuScreen) - { - menuScreen->translateAndProcessEvent(&event); - switch (menuScreen->endValue) - { - case MapEditMenuScreen::LOAD_MAP: - { - performAction("close menu screen"); - performAction("open load screen"); - } - break; - case MapEditMenuScreen::SAVE_MAP: - { - performAction("close menu screen"); - performAction("open save screen"); - } - break; - case MapEditMenuScreen::OPEN_SCRIPT_EDITOR: - { - performAction("close menu screen"); - performAction("open scenario editor"); - } - break; - case MapEditMenuScreen::OPEN_TEAMS_EDITOR: - { - performAction("close menu screen"); - performAction("open teams editor"); - } - break; - case MapEditMenuScreen::RETURN_EDITOR: - { - performAction("close menu screen"); - } - break; - case MapEditMenuScreen::QUIT_EDITOR: - { - performAction("close menu screen"); - performAction("quit editor"); - } - break; - } - } - if(showingLoad) - { - loadSaveScreen->translateAndProcessEvent(&event); - switch (loadSaveScreen->endValue) - { - case LoadSaveScreen::OK: - { - load(loadSaveScreen->getFileName()); - performAction("close load screen"); - } - break; - case LoadSaveScreen::CANCEL: - { - performAction("close load screen"); - } - break; - } - } - if(showingSave) - { - loadSaveScreen->translateAndProcessEvent(&event); - switch (loadSaveScreen->endValue) - { - case LoadSaveScreen::OK: - { - save(loadSaveScreen->getFileName(), loadSaveScreen->getName()); - performAction("close save screen"); - } - case LoadSaveScreen::CANCEL: - { - performAction("close save screen"); - } - } - } - if(showingScriptEditor) - { - scriptEditor->translateAndProcessEvent(&event); - switch(scriptEditor->endValue) - { - case ScriptEditorScreen::OK: - case ScriptEditorScreen::CANCEL: - { - performAction("close scenario editor"); - } - } - } - if(showingTeamsEditor) - { - teamsEditor->translateAndProcessEvent(&event); - switch(teamsEditor->endValue) - { - case ScriptEditorScreen::OK: - case ScriptEditorScreen::CANCEL: - { - performAction("close teams editor"); - } - } - } - if(isShowingAreaName) - { - areaName->translateAndProcessEvent(&event); - switch(areaName->endValue) - { - case AskForTextInput::OK: - case AskForTextInput::CANCEL: - { - performAction("close area name"); - } - } - } -} - -void MapEdit::handleMapScroll() -{ - xSpeed = 0; - ySpeed = 0; - int scrollAreaWidth=10; // if the cursor is that close to the border the viewport will scroll - - SDL_PumpEvents(); - const Uint8 *keystate = SDL_GetKeyboardState(NULL); - SDL_Keymod modState = SDL_GetModState(); - int xMotion = 1; - int yMotion = 1; - /* We check that only Control is held to avoid accidentally - matching window manager bindings for switching windows - and/or desktops. */ - if (!(modState & (KMOD_ALT|KMOD_SHIFT))) - { - /* It violates good abstraction principles that I - have to do the calculations in the next two - lines. There should be methods that abstract - these computations. */ - if ((modState & KMOD_CTRL)) - { - /* We move by half screens if Control is held while - the arrow keys are held. So we shift by 6 - instead of 5. (If we shifted by 5, it would be - good to subtract 1 so that there would be a small - overlap between what is viewable both before and - after the motion.) */ - xMotion = ((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); - yMotion = ((globalContainer->gfx->getH())>>6); - } - else - { - /* We move the screen by one square at a time if CTRL key - is not being help */ - xMotion = 1; - yMotion = 1; - } - } - else if (modState) - { - /* Probably some keys held down as part of window - manager operations. */ - xMotion = 0; - yMotion = 0; - } - if ( - keystate[SDL_SCANCODE_UP] || - keystate[SDL_SCANCODE_KP_7] || - keystate[SDL_SCANCODE_KP_8] || - keystate[SDL_SCANCODE_KP_9] || - mouseYgfx->getH()-mouseYgfx->getW()-mouseXTerrainSelector::Water ? 0 : 16), mouseY+(terrainType>TerrainSelector::Water ? 0 : 16), &x, &y, viewportX, viewportY); - else - game.map.displayToMapCaseAligned(mouseX, mouseY, &x, &y, viewportX, viewportY); - s << "X: " << x << " Y: " << y; - mapCoordinatesLabel->setLabel(s.str()); -} - -void MapEdit::addWidget(MapEditorWidget* widget) -{ - mew.push_back(widget); -} - -bool MapEdit::findAction(int x, int y) -{ - for(std::vector::iterator i=mew.begin(); i!=mew.end(); ++i) - { - MapEditorWidget* mi=*i; - if(mi->is_in(x, y) && mi->enabled) - { - mi->handleClick(mouseX-mi->area.x, mouseY-mi->area.y); - return true; - } - } - return false; -} - - - -void MapEdit::enableOnlyGroup(const std::string& group) -{ - for(std::vector::iterator i=mew.begin(); i!=mew.end(); ++i) - { - if((*i)->group == group || (*i)->group=="any") - { - (*i)->enable(); - } - else - (*i)->disable(); - } -} - - - -void MapEdit::drawWidgets() -{ - for(std::vector::iterator i=mew.begin(); i!=mew.end(); ++i) - { - (*i)->drawSelf(); - } -} - - -void MapEdit::minimapMouseToPos(int mx, int my, int *cx, int *cy, bool forScreenViewport) -{ - // get data for minimap - int mMax; - int szX, szY; - int decX, decY; - Utilities::computeMinimapData(100, game.map.getW(), game.map.getH(), &mMax, &szX, &szY, &decX, &decY); - - mx-=14+decX; - my-=14+decY; - *cx=((mx*game.map.getW())/szX); - *cy=((my*game.map.getH())/szY); - *cx+=game.teams[team]->startPosX-(game.map.getW()/2); - *cy+=game.teams[team]->startPosY-(game.map.getH()/2); - if (forScreenViewport) - { - *cx-=((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); - *cy-=((globalContainer->gfx->getH())>>6); - } - - *cx&=game.map.getMaskW(); - *cy&=game.map.getMaskH(); -} - - - -void MapEdit::handleBrushClick(int mx, int my) -{ - // if we have an area over 32x32, which mean over 128 bytes, send it -// if (brushAccumulator.getAreaSurface() > 32*32) -// { -// sendBrushOrders(); -// } - // we add brush to accumulator - int mapX, mapY; - game.map.displayToMapCaseAligned(mx, my, &mapX, &mapY, viewportX, viewportY); - if(lastPlacementX==mapX && lastPlacementY==mapY) - return; - - if(lastPlacementX == -1) - { - firstPlacementX=mapX; - firstPlacementY=mapY; - } - - int fig = brush.getFigure(); - brushAccumulator.applyBrush(BrushApplication(mapX, mapY, fig), &game.map); - // we get coordinates - int startX = mapX-BrushTool::getBrushDimXMinus(fig); - int startY = mapY-BrushTool::getBrushDimYMinus(fig); - int width = BrushTool::getBrushWidth(fig); - int height = BrushTool::getBrushHeight(fig); - // we update local values - if (brush.getType() == BrushTool::MODE_ADD) - { - for (int y=startY; y 32*32) -// { -// sendBrushOrders(); -// } - // we add brush to accumulator - int mapX, mapY; - game.map.displayToMapCaseAligned(mx+(terrainType>TerrainSelector::Water ? 0 : 16), my+(terrainType>TerrainSelector::Water ? 0 : 16), &mapX, &mapY, viewportX, viewportY); - if(lastPlacementX==mapX && lastPlacementY==mapY) - return; - - if(lastPlacementX == -1) - { - firstPlacementX=mapX; - firstPlacementY=mapY; - } - int fig = brush.getFigure(); - brushAccumulator.applyBrush(BrushApplication(mapX, mapY, fig), &game.map); - // we get coordinates - int startX = mapX-BrushTool::getBrushDimXMinus(fig); - int startY = mapY-BrushTool::getBrushDimYMinus(fig); - int width = BrushTool::getBrushWidth(fig); - int height = BrushTool::getBrushHeight(fig); - // we update local values - if (brush.getType() == BrushTool::MODE_ADD) - { - for (int y=startY; ygetIndex(), x, y); - break; - case BrushTool::CT_NO_RESOURCE_GROWTH: - game.map.getCase(x, y).canRessourcesGrow=false; - break; - } - } - } - else if (brush.getType() == BrushTool::MODE_DEL) - { - for (int y=startY; ygetIndex(), x, y); - break; - case BrushTool::CT_NO_RESOURCE_GROWTH: - game.map.getCase(x, y).canRessourcesGrow=true; - break; - default:break; - } - } - } - lastPlacementX=mapX; - lastPlacementY=mapY; - game.regenerateDiscoveryMap(); -} -void MapEdit::handleDeleteClick(int mx, int my) -{ - handleClick(mx,my,BrushTool::CT_DELETE); -} - - - -void MapEdit::handleAreaClick(int mx, int my) -{ - handleClick(mx,my,BrushTool::CT_AREA); -} - - - -void MapEdit::handleNoRessourceGrowthClick(int mx, int my) -{ - handleClick(mx,my,BrushTool::CT_NO_RESOURCE_GROWTH); -} - - -void MapEdit::regenerateGameHeader() -{ - GameHeader gameHeader; - MapHeader& mapHeader = game.mapHeader; - - int playerNumber=0; - for (int i=0; i or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include -#include - -#include "boost/integer_traits.hpp" -#include "boost/integer/common_factor.hpp" -//also the Perlin Noise stuff uses random that is not based on syncRand -#include "boost/random.hpp" -#include "Game.h" -#include "GlobalContainer.h" -#include "HeightMapGenerator.h" -#include "MapGenerationDescriptor.h" -#include "MapGenerator.h" -#include "Map.h" -#include -#include -#include -#include "Unit.h" -#include "Utilities.h" - -bool MapGenerator::generateMap(Game& game, MapGenerationDescriptor &descriptor) -{ - if (verbose) - printf("Generating map, please wait ....\n"); - game.map.setSize(descriptor.wDec, descriptor.hDec); - game.map.setGame(&game); - setRandomSyncRandSeed(); - - switch (descriptor.methode) - { - case MapGenerationDescriptor::eUNIFORM: - game.map.makeHomogenMap(descriptor.terrainType); - game.addTeam(); - break; - case MapGenerationDescriptor::eSWAMP: - case MapGenerationDescriptor::eISLANDS: - case MapGenerationDescriptor::eRIVER: - case MapGenerationDescriptor::eCRATERLAKES: - if (!game.map.makeRandomMap(descriptor)) - return false; - if (!game.makeRandomMap(descriptor)) - return false; - break; - case MapGenerationDescriptor::eCONCRETEISLANDS: - if (!computeConcreteIslands(game, descriptor)) - return false; - break; - case MapGenerationDescriptor::eISLES: - if (!computeIsles(game, descriptor)) - return false; - break; - case MapGenerationDescriptor::eOLDRANDOM: - if (!game.map.oldMakeRandomMap(descriptor)) - return false; - if (!game.makeRandomMap(descriptor)) - return false; - break; - case MapGenerationDescriptor::eOLDISLANDS: - if (!game.map.oldMakeIslandsMap(descriptor)) - return false; - if (!game.oldMakeIslandsMap(descriptor)) - return false; - break; - - default: - assert(false); - } - - // compile script - game.sgslScript.compileScript(&game); - - if (verbose) - printf(".... map generated.\n"); - return true; -} - - -bool MapGenerator::computeConcreteIslands(Game& game, MapGenerationDescriptor& descriptor) -{ - game.map.makeHomogenMap(descriptor.terrainType); - for(int i=0; i grid(game.map.getW() * game.map.getH(), 0); - std::vector teamPoints; - std::vector weights1; - std::vector weights2; - std::vector teamAreaNumbers; - std::vector islandAreaNumbers; - - //Add in team bases - for(int i=0; i areaNumbers = teamAreaNumbers; - areaNumbers.insert(areaNumbers.end(), islandAreaNumbers.begin(), islandAreaNumbers.end()); - - // Initially divide up the land - splitUpPoints(game, grid, 0, teamPoints, weights1); - splitUpArea(game, grid, 0, teamPoints, weights2, areaNumbers); - - // Create a heightmap that will be used to give the map a rough edge - std::vector heights(game.map.getW() * game.map.getH(), 75); - adjustHeightmapFromPerlinNoise(game, heights, 15); - - // Compute the distance of every square from the border - std::vector sources; - findBorderPoints(game, grid, sources); - std::vector obstacles; - std::vector distances; - computeDistances(game, sources, obstacles, distances); - - // Locations near the border are deaper, thus causing more water - for(int x=0; x=45 && total_height<=55) - game.map.setUMatPos(x, y, SAND, 1); - else - game.map.setUMatPos(x, y, GRASS, 1); - } - } - game.map.controlSand(); - - // Go through the map again and place alga - for(int x=0; x areaWeights; - std::vector areaNumbers; - for(int j=0; j<2; ++j) - { - areaWeights.push_back(1); - areaNumbers.push_back(areaNumber); - areaNumber+=1; - } - - // Divide the area. Its possible the area will be so small it can't be used - if(divideUpArea(game, grid, islandAreaNumbers[i], areaWeights, areaNumbers)) - { - // Fill in wheat - std::vector points; - getAllPoints(game, grid, areaNumbers[0], points); - fillInResource(game, points, CORN, 2); - points.clear(); - - // Place some fruit - int fruit_n = syncRand()%6+1; - getAllPoints(game, grid, areaNumbers[1], points); - chooseRandomPoints(game, points, fruit_n); - for(unsigned int j=0; jcreateLists(); - } - return true; -} - - - -bool MapGenerator::computeIsles(Game& game, MapGenerationDescriptor& descriptor) -{ - game.map.makeHomogenMap(descriptor.terrainType); - for(int i=0; i grid(game.map.getW() * game.map.getH(), 0); - - // Do the starting locations of the teams - std::vector teamPoints; - std::vector teamWeights; - std::vector teamAreaNumbers; - for(int i=0; i heightmap(game.map.getW() * game.map.getH(), 50); - std::vector teamAreaPoints; - getAllOtherPoints(game, grid, 0, teamAreaPoints); - std::vector obstacles; - - std::vector distances; - computeDistances(game, teamAreaPoints, obstacles, distances); - - // Stamp out the team areas - for(int x=0; x 1 && d <= 11) - heightmap[y * game.map.getW() + x] += (11-d)*10; - else if(d == 1) - heightmap[y * game.map.getW() + x] += 100; - } - } - - // Connect each teams area to each other players area - std::vector connectorPoints; - int connectorArea = areaNumber; - areaNumber+=1; - for(int i=0; i teamI; - std::vector teamJ; - getAllPoints(game, grid, teamAreaNumbers[i], teamI); - getAllPoints(game, grid, teamAreaNumbers[j], teamJ); - chooseRandomPoints(game, teamI, 1); - chooseRandomPoints(game, teamJ, 1); - - // Traverse between the two points - std::vector linePoints; - getAllPointsLine(game, teamI[0].x, teamI[0].y, teamJ[0].x, teamJ[0].y, linePoints); - // If a connection can be made without going through another teams area, then do it - bool failed=false; - for(unsigned int p=0; p5) - { - grid[ny * game.map.getW() + nx] = connectorArea; - } - } - } - } - } - } - } - computeDistances(game, connectorPoints, obstacles, distances); - - //computePercentageOfAreas(game, grid); - - // Stamp out the connectors - for(int x=0; x 1 && d <= 4) - heightmap[y * game.map.getW() + x] += (4-d)*33; - else if(d == 1) - heightmap[y * game.map.getW() + x] += 100; - } - } - - - // Use the heightmap to put in water, grass, and sand - adjustHeightmapFromPerlinNoise(game, heightmap, 45); - for(int x=0; x95 && total_height<105) - game.map.setUMatPos(x, y, SAND, 1); - else - game.map.setUMatPos(x, y, GRASS, 1); - } - } - game.map.controlSand(); - - // Reset the grid, and recompute within the boundaries of the various islands - for(int x=0; x connectorDistances = distances; - - // For each team, find a point just off the coast and place algae there - for(int i=0; i sources; - getAllPoints(game, grid, teamAreaNumbers[i], sources); - computeDistances(game, sources, obstacles, distances); - std::vector possible; - for(int x=0; x 4) - { - possible.push_back(MapGeneratorPoint(x, y)); - } - } - } - if(possible.size() == 0) - { - return false; - } - int r = syncRand() % possible.size(); - for(int x=-2; x<=2; ++x) - { - int nx = game.map.normalizeX(possible[r].x + x); - for(int y=-2; y<=2; ++y) - { - int ny = game.map.normalizeY(possible[r].y + y); - game.map.setRessource(nx, ny, ALGA, 1); - } - } - } - - if(!divideUpPlayerLands(game, descriptor, grid, teamAreaNumbers, areaNumber)) - { - return false; - } - - // Initialize final team info - for(int i=0; icreateLists(); - } - return true; -} - - - -bool MapGenerator::divideUpPlayerLands(Game& game, MapGenerationDescriptor& descriptor, std::vector& grid, std::vector& teamAreaNumbers, int& areaNumber) -{ - int typeNum=globalContainer->buildingsTypes.getTypeNum("swarm", 0, false); - BuildingType *swarm = globalContainer->buildingsTypes.get(typeNum); - - //Compute the distances from water - std::vector sources; - std::vector obstacles; - std::vector distances; - obstacles.clear(); - getAllPoints(game, grid, 0, sources); - computeDistances(game, sources, obstacles, distances); - - //Create a new heightmap from noise and distance to water - std::vector heightmap(game.map.getW() * game.map.getH(), 50); - adjustHeightmapFromPerlinNoise(game, heightmap, 5); - for(int x=0; x areaWeights; - std::vector areaNumbers; - for(int j=0; j<12; ++j) - { - areaWeights.push_back(1); - areaNumbers.push_back(areaNumber); - areaNumber+=1; - } - - // Divide the area. Its possible the area will be so small it can't be used - if(divideUpArea(game, grid, teamAreaNumbers[i], areaWeights, areaNumbers)) - { - // Sort the list of areas based on how close they are to water - std::vector areaDistances(areaNumbers.size()); - std::vector areaIndexes(areaNumbers.size()); - for(unsigned int j=0; j wheatWoodPoints; - std::vector wheatPoints; - //std::vector woodPoints; - getAllPoints(game, grid, areaNumbers[3], wheatWoodPoints); - getAllPoints(game, grid, areaNumbers[4], wheatWoodPoints); - getAllPoints(game, grid, areaNumbers[5], wheatWoodPoints); - adjustHeightmapFromPoints(game, wheatWoodPoints, heightmap, 10); - for(unsigned int j=0; j 50) - { - game.map.setRessource(wheatWoodPoints[j].x, wheatWoodPoints[j].y, WOOD, 1); - //woodPoints.push_back(wheatWoodPoints[j]); - } - } - wheatWoodPoints.clear(); - - // Place wheat - getAllPoints(game, grid, areaNumbers[0], wheatWoodPoints); - getAllPoints(game, grid, areaNumbers[1], wheatWoodPoints); - getAllPoints(game, grid, areaNumbers[2], wheatWoodPoints); - adjustHeightmapFromPoints(game, wheatWoodPoints, heightmap, 10); - for(unsigned int j=0; j 50) - { - game.map.setRessource(wheatWoodPoints[j].x, wheatWoodPoints[j].y, CORN, 1); - wheatPoints.push_back(wheatWoodPoints[j]); - } - } - - - // These are all points in the base - std::vector baseLocations; - getAllPoints(game, grid, areaNumbers[6], baseLocations); - getAllPoints(game, grid, areaNumbers[7], baseLocations); - getAllPoints(game, grid, areaNumbers[8], baseLocations); - getAllPoints(game, grid, areaNumbers[9], baseLocations); - getAllPoints(game, grid, areaNumbers[10], baseLocations); - getAllPoints(game, grid, areaNumbers[11], baseLocations); - - // Place stone - int numberOfStone = 6; - std::vector stoneLocations = baseLocations; - chooseRandomPoints(game, stoneLocations, numberOfStone); - for(unsigned int j=0; j wheatDistance; - computeDistances(game, wheatPoints, obstacles, wheatDistance); - - // Only consider points between 1 and 4 squares from wheat - std::vector startingLocations; - for(unsigned int j=0; j= 1 && minValue <= 2) - { - startingLocations.push_back(baseLocations[j]); - } - } - - // Place swarms - chooseFreeForBuildingSquares(game, startingLocations, swarm, i); - if(startingLocations.size() == 0) - { - return false; - } - int chosen = syncRand()%startingLocations.size(); - Building* b = addBuilding(game, startingLocations[chosen].x, startingLocations[chosen].y, i, IntBuildingType::SWARM_BUILDING, 1, false); - if(b == NULL) - { - return false; - } - - // Set the initial viewport location - game.teams[i]->startPosX=b->posX; - game.teams[i]->startPosY=b->posY; - game.teams[i]->startPosSet=3; - - // Place units around the swarm - std::vector unitLocations = baseLocations; - chooseFreeForGroundUnits(game, unitLocations, i); - chooseTouchingBuilding(game, unitLocations, b); - chooseRandomPoints(game, unitLocations, descriptor.nbWorkers); - for(unsigned int n=0; n& grid, int areaN, std::vector& weights, std::vector& areaNumbers) -{ - std::vector points; - std::vector splitWeights; - for(unsigned int i=0; i& grid, int areaN, int x, int y, int width, int height) -{ - int h2 = (height/2) * (height/2); - int w2 = (width/2) * (width/2); - int t2 = h2 * w2; - for(int px = -(width/2); px < (width/2); ++px) - { - int nx = game.map.normalizeX(x + px); - int px2 = px*px*h2; - for(int py = -(height/2); py < (height/2); ++py) - { - int ny = game.map.normalizeY(y + py); - int py2 = py*py*w2; - if(px2 + py2 < t2) - { - grid[ny * game.map.getW() + nx] = areaN; - } - } - } -} - - - -int MapGenerator::splitUpPoints(Game& game, std::vector& grid, int areaN, std::vector& points, std::vector& weights) -{ - std::vector startingPoints; - for(int x=0; x obstacles; - getAllOtherPoints(game, grid, areaN, obstacles); - std::vector sources; - sources.push_back(startingPoints[n]); - std::vector heights; - computeDistances(game, sources, obstacles, heights); - sources.clear(); - - for(unsigned int i=0; i possible; - for(int x=0; x max) - { - max = h; - possible.clear(); - } - if(h >= max) - { - possible.push_back(MapGeneratorPoint(x, y)); - } - } - } - int n = syncRand() % possible.size(); - points[i] = possible[n]; - sources.push_back(points[i]); - computeDistances(game, sources, obstacles, heights); - } - startingPoints.clear(); - heights.clear(); - sources.clear(); - obstacles.clear(); - - bool cont=true; - int minDist = boost::integer_traits::const_max; - while(cont) - { - minDist = boost::integer_traits::const_max; - bool changed=false; - for(unsigned int i=0; i::const_max; - for(unsigned int j=0; j::const_max; - bool invalid=false; - for(unsigned int j=0; j::iterator i = squares[p].begin(); - std::advance(i, randLocation); - squares[p].insert(i, deltaAddrC[ci]); - } - } - } - } - if(!found) - cont = false; - } -} - - - -void MapGenerator::getAllPoints(Game& game, std::vector& grid, int areaN, std::vector& points) -{ - for(int x=0; x& grid, int areaN, std::vector& points) -{ - for(int x=0; x& points) -{ - int startX = x1; - int endX = x2; - int startY = y1; - int endY = y2; - - int dirX = (endX > startX ? 1 : -1); - int distX = std::abs(endX - startX); - if(distX > game.map.getW()/2) - { - dirX = -dirX; - distX = game.map.getW() - distX; - } - - int dirY = (endY > startY ? 1 : -1); - int distY = std::abs(endY - startY); - if(distY > game.map.getH()/2) - { - dirY = -dirY; - distY = game.map.getH() - distY; - } - - if(distX > distY) - { - int px = 0; - int py = 0; - int y = startY; - for(int x=startX; x!=endX;) - { - px+=1; - points.push_back(MapGeneratorPoint(x, y)); - if(std::abs(px * distY - py * distX) > std::abs(px * distY - (py+1) * distX)) - { - y=game.map.normalizeY(y+dirY); - points.push_back(MapGeneratorPoint(x, y)); - py+=1; - } - x=game.map.normalizeX(x+dirX); - } - } - else - { - int px = 0; - int py = 0; - int x = startX; - for(int y=startY; y!=endY;) - { - py+=1; - points.push_back(MapGeneratorPoint(x, y)); - if(std::abs(py * distX - px * distY) > std::abs(py * distX - (px+1) * distY)) - { - x=game.map.normalizeX(x+dirX); - points.push_back(MapGeneratorPoint(x, y)); - px+=1; - } - y=game.map.normalizeY(y+dirY); - } - } -} - - - -void MapGenerator::findBorderPoints(Game& game, std::vector& grid, std::vector& points) -{ - for(int x=0; x& grid, int areaN, std::vector& points) -{ - for(unsigned int i=0; i& points, int ressourceType, int maxFillSize) -{ - for(unsigned int n=0; n& points, int n) -{ - n = std::min(int(points.size()), n); - for(int i=0; i& points, BuildingType* type, int team) -{ - std::vector newPoints; - for(unsigned int n=0; n& points, int team) -{ - std::vector newPoints; - for(unsigned int n=0; n& points, Building* building) -{ - std::vector newPoints; - for(unsigned int n=0; ngid)) - { - newPoints.push_back(MapGeneratorPoint(points[n].x, points[n].y)); - } - } - points = newPoints; -} - - - -void MapGenerator::computePercentageOfAreas(Game& game, std::vector& grid) -{ - // Compute land sizes - std::map amounts; - for(int x=0; x::iterator i=amounts.begin(); i!=amounts.end(); ++i) - { - int percent = i->second * 10000 / (game.map.getW() * game.map.getH()); - std::cout<<"Area "<first<<" takes up "<& points, std::vector& heightmap, int value) -{ - for(unsigned int i=0; i& heights, int spread) -{ - HeightMap noise(game.map.getW(), game.map.getH()); - noise.makePlain(4); - for(int x=0; x& sources, std::vector& obstacles, std::vector& heightmap) -{ - std::queue places; - heightmap.clear(); - heightmap.resize(game.map.getW() * game.map.getH(), 0); - for(unsigned int i=0; i> wDec; // Calculate the coordinates of - size_t x = deltaAddrG & wMask; // the current field and of the - - size_t yu = ((y - 1) & hMask); // fields next to it. - size_t yd = ((y + 1) & hMask); // We live on a torus! If we are on - size_t xl = ((x - 1) & wMask); // the "last line" of the map, the - size_t xr = ((x + 1) & wMask); // next line is the line 0 again. - - int g = heightmap[(y << wDec) | x] + 1; - - size_t deltaAddrC[8]; - int *addr; - int side; - - deltaAddrC[0] = (yu << wDec) | xl; // Calculate the positions of the - deltaAddrC[1] = (yu << wDec) | x ; // 8 fields next to us from their - deltaAddrC[2] = (yu << wDec) | xr; // coordinates. - deltaAddrC[3] = (y << wDec) | xr; - deltaAddrC[4] = (yd << wDec) | xr; - deltaAddrC[5] = (yd << wDec) | x ; - deltaAddrC[6] = (yd << wDec) | xl; - deltaAddrC[7] = (y << wDec) | xl; - for (int ci=0; ci<8; ci++) // Check for each of this fields if we - { // can improve its gradient value - addr = &heightmap[deltaAddrC[ci]]; - side = *addr; - if (side==0) - { - *addr = g; - places.push(deltaAddrC[ci]); - } - } - } -} - - - -int MapGenerator::computeAverageDistance(Game& game, std::vector& grid, int areaN, std::vector heightmap) -{ - long total = 0; - int count = 0; - for(int x=0; x& grid, std::vector toBeJoined, std::vector target) -{ - std::vector > borders(toBeJoined.size(), std::vector(toBeJoined.size(), false)); - - std::map newJoined; - for(unsigned int i=0; i nodes(toBeJoined.size()); - for(unsigned int i=0; i targets(toBeJoined.size()); - for(unsigned int i=0; i nodes, std::vector& result, int numberOfJoins) -{ - // Choose which one to work on - int whichOne=-1; - for(unsigned int i=0; i newNodes = nodes; - newNodes.erase(newNodes.begin() + whichOne); - boost::random_number_generator adapter(randomGenerator); - std::random_shuffle(newNodes.begin(), newNodes.end(), adapter); - - bool found=false; - for(unsigned int i=0; i stillNewNodes(newNodes); - stillNewNodes.erase(stillNewNodes.begin() + i); - stillNewNodes.insert(stillNewNodes.begin(), n); - - if(joinLoop(game, stillNewNodes, result, numberOfJoins)) - return true; - } - } - return false; -} - - - -Building* MapGenerator::addBuilding(Game& game, int x, int y, int team, int type, int level, bool underConstruction) -{ - std::string name = IntBuildingType::typeFromShortNumber(type); - int typeNum=globalContainer->buildingsTypes.getTypeNum(name, level-1, underConstruction); - BuildingType *bt = globalContainer->buildingsTypes.get(typeNum); - if(bt == NULL) - { - return NULL; - } - - if (game.checkRoomForBuilding(x, y, bt, team, false)) - { - if(bt->maxUnitWorking) - return game.addBuilding(x, y, typeNum, team, 1, 0); - else - return game.addBuilding(x, y, typeNum, team, 0, 0); - } - return NULL; -} - - - -///generates a map that is of one terrain type only -void Map::makeHomogenMap(TerrainType terrainType) -{ - for (int y=0; y=0)&&(d<40)&&(syncRand()&4)) - { - if (l<=(int)(syncRand()&3)) - setTerrain(x, y, d+273); - else - { - // we extand ressource: - int dx, dy; - Unit::dxDyFromDirection(syncRand()&7, &dx, &dy); - int nx=x+dx; - int ny=y+dy; - if (getGroundUnit(nx, ny)==NOGUID) - if (((r==WOOD||r==CORN||r==STONE)&&isGrass(nx, ny))||((r==ALGA)&&isWater(nx, ny))) - setTerrain(nx, ny, 272+(r*10)+((syncRand()&1)*5)); - } - } - } -} - -void simulateRandomMap(int smooth, double baseWater, double baseSand, double baseGrass, double *finalWater, double *finalSand, double *finalGrass) -{ - int w=32<<(smooth>>2); - int h=w; - int s=w*h; - int m=s-1; - VARARRAY(int,undermap,w*h); - - int totalRatio=0x7FFF; - int waterRatio=(int)(baseWater*((double)totalRatio)); - int sandRatio =(int)(baseSand *((double)totalRatio)); - int grassRatio=(int)(baseGrass*((double)totalRatio)); - totalRatio=waterRatio+sandRatio+grassRatio; - - if(totalRatio==0) - { - waterRatio = 1; - sandRatio = 1; - grassRatio = 1; - totalRatio = 3; - } - - - /// First, we create a fully random patchwork: - for (int y=0; yfinalWaters[j]) - ws++; - else - we++; - if (sffinalSands[j]) - ss++; - else - se++; - if (gffinalGrasses[j]) - gs++; - else - ge++; - } - if (abs(wb-ws)0) - allowed[0]=(Uint32)(pow(errWaterRatioCount, 0.125)*4294967296.0); - else - allowed[0]=0; - if (errSandRatioCount>0) - allowed[1]=(Uint32)(pow(errSandRatioCount , 0.125)*4294967296.0); - else - allowed[1]=0; - if (errGrassRatioCount>0) - allowed[2]=(Uint32)(pow(errGrassRatioCount, 0.125)*4294967296.0); - else - allowed[2]=0; - - assert(allowed[0]<=(Uint32)0xFFFFFFFF); - assert(allowed[1]<=(Uint32)0xFFFFFFFF); - assert(allowed[2]<=(Uint32)0xFFFFFFFF); - - if (i==0) - { - allowed[0]=0; - allowed[1]=0; - allowed[2]=0; - } - - for (int y=0; y0); - int* bootX=descriptor.bootX; - int* bootY=descriptor.bootY; - - //TODO: First pass to find the number of available places. - for (int team=0; team7) - { - int centerX=((x+startX)>>1); - int top, bot; - for (top=0; top0); - - int centerY=y+((bot-top)>>1); - bool farEnough=true; - for (int ti=0; timaxSurface && farEnough) - { - maxSurface=surface; - maxX=centerX; - maxY=centerY; - } - } - width=0; - startX=x; - } - } - } - - if (maxSurface<=0) - return false; - assert(maxSurface); - bootX[team]=maxX; - bootY[team]=maxY; - - for (int dx=-1; dx<6; dx++) - for (int dy=0; dy<6; dy++) - setUMTerrain(maxX+dx, maxY+dy, GRASS); - - - } - - // Let's add some green space for teams: - int squareSize=5+(int)(sqrt((double)minDistSquare)/4.5); - if (verbose) - printf("squareSize=%d.\n", squareSize); - for (int team=0; teamh/(pow(2,hPower2Divider))) - wPower2Divider++; - else - hPower2Divider++; - unsigned int wHeightMap=(unsigned int)(w/(pow(2,wPower2Divider))); - unsigned int hHeightMap=(unsigned int)(h/(pow(2,hPower2Divider))); - /// lets generate a patch of perlin noise. That's a smooth mapping R^2 to ]0;1[ - HeightMap hm(wHeightMap,hHeightMap); - /// 1 to avoid division by zero, - unsigned int tmpTotal=1+descriptor.waterRatio+descriptor.grassRatio; - unsigned int sectionIslandCount=std::max(1u, static_cast((descriptor.nbTeams+descriptor.extraIslands)/pow(2,power2Divider))); - switch (descriptor.methode) - { - case MapGenerationDescriptor::eSWAMP: - hm.makeSwamp(smoothingFactor); - waterTiles=(unsigned int)((float)descriptor.waterRatio*wHeightMap*hHeightMap/(float)tmpTotal); - sandTiles=0; - grassTiles=wHeightMap*hHeightMap-waterTiles; - break; - case MapGenerationDescriptor::eRIVER: - hm.makeRiver(descriptor.riverDiameter*(wHeightMap+hHeightMap)/2/100,smoothingFactor); - waterTiles=(unsigned int)((float)descriptor.waterRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); - sandTiles=(unsigned int)((float)descriptor.sandRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); - grassTiles =(unsigned int)((float)descriptor.grassRatio /(float)totalGSWFromUI*wHeightMap*hHeightMap); - break; - case MapGenerationDescriptor::eCRATERLAKES: - hm.makeCraters(wHeightMap*hHeightMap*descriptor.craterDensity/30000, 30, smoothingFactor); - waterTiles=(unsigned int)((float)descriptor.waterRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); - sandTiles=(unsigned int)((float)descriptor.sandRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); - grassTiles =(unsigned int)((float)descriptor.grassRatio /(float)totalGSWFromUI*wHeightMap*hHeightMap); - break; - case MapGenerationDescriptor::eISLANDS: - hm.makeIslands(sectionIslandCount, smoothingFactor); - waterTiles=(unsigned int)((float)descriptor.waterRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); - sandTiles=(unsigned int)((float)descriptor.sandRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); - grassTiles =(unsigned int)((float)descriptor.grassRatio /(float)totalGSWFromUI*wHeightMap*hHeightMap); - break; - default: assert(false); - break; - } - /// wheat/wood needs ground to stand on and water. So: - wheatWoodTiles=waterTiles= algaeTiles) - algaeLevel = (float)(i-1)/2048.0; - if (accumulatedHistogram >= waterTiles) - waterLevel = (float)(i-1)/2048.0; - } - while ((sandLevel==0) && (i<2048)) - { - accumulatedHistogram+=histogram[i++]; - if (accumulatedHistogram >= waterTiles+sandTiles) - sandLevel = (float)(i-1)/2048.0; - } - while ((grassLevel==0) && (i<2048)) - { - accumulatedHistogram+=histogram[i++]; - if (wheatWoodLevel==0 && accumulatedHistogram >= waterTiles+sandTiles+wheatWoodTiles) - wheatWoodLevel = (float)(i-1)/2048.0; - if (stoneLevel==0 && accumulatedHistogram >= waterTiles+sandTiles+(wheatWoodTiles / 3)) - stoneLevel = (float)(i-1)/2048.0; - if (accumulatedHistogram >= waterTiles+sandTiles+grassTiles) - grassLevel = (float)(i-1)/2048.0; - } - for (unsigned y=0; y0); - int* bootX=descriptor.bootX; - int* bootY=descriptor.bootY; - - //TODO: First pass to find the number of available places. - for (int team=0; team7) - { - int centerX=((x+startX)>>1); - int top, bot; - for (top=0; top0); - - int centerY=y+((bot-top)>>1); - bool farEnough=true; - for (int ti=0; timaxSurface && farEnough) - { - maxSurface=surface; - maxX=centerX; - maxY=centerY; - } - } - width=0; - startX=x; - } - } - } - - if (maxSurface<=0) - { - //std::cout << "debugoutput 2\n"; - return false; - } - assert(maxSurface); - bootX[team]=maxX; - bootY[team]=maxY; - } - - controlSand(); - regenerateMap(0, 0, w, h); - //now to add primary resources for current map generator - for (unsigned y=0; y 0) - { - for (int q1=0; q13) - break; - else - width=1; - - if (dist*distWeight[resI]+width*widthWeight[resI]>maxDist*distWeight[resI]+maxWidth*widthWeight[resI]) - { - maxWidth=width; - maxDist=dist; - maxDir=dir; - } - } - dirUsed[maxDir]=true; - if (maxWidth>1); - dx*=d; - dy*=d; - - int amount=descriptor.ressource[res]; - if (amount>0) - setRessource(bootX[team]+dx, bootY[team]+dy, res, amount); - } - - if (smallestWidth3) - break; - else - width=1; - - if (dist+width>maxDist+maxWidth) - { - maxWidth=width; - maxDist=dist; - maxDir=dir; - } - } - dirUsed[maxDir]=true; - - int dx, dy; - Unit::dxDyFromDirection(maxDir, &dx, &dy); - int d=maxDist-(maxWidth>>1); - dx*=d; - dy*=d; - - int amount=descriptor.ressource[smallestRessource]; - if (amount>0) - setRessource(bootX[team]+dx, bootY[team]+dy, smallestRessource, amount); - } - - int maxDir=0; - int maxWidth=0; - int maxDist=0; - for (int dir=0; dir<8; dir++) - { - int width=0; - int dx, dy, dist; - Unit::dxDyFromDirection(dir, &dx, &dy); - for (dist=0; dist<2*limiteDist; dist++) - if (isWater(bootX[team]+dx*dist, bootY[team]+dy*dist)) - width++; - else if (width>3) - break; - else - width=1; - - if (dist+width>width+maxWidth) - { - maxWidth=width; - maxDist=dist; - maxDir=dir; - } - } - - int dx, dy; - Unit::dxDyFromDirection(maxDir, &dx, &dy); - int d=maxDist-(maxWidth>>1); - dx*=d; - dy*=d; - - int amount=descriptor.ressource[ALGA]; - if (amount>0) - setRessource(bootX[team]+dx, bootY[team]+dy, ALGA, amount); - } - - // Let's smooth ressources... - int maxAmount=0; - for (int r=0; r<4; r++) - if (maxAmount65536) - { - minDistSquare=minDistSquare>>1; - //I think that you need to do this only once, in worst case. - //With a few luck you doesn't need to. - c=0; - - } - } - else - { - bootX[i]=x; - bootY[i]=y; - for (int dx=-1; dx<6; dx++) - for (int dy=0; dy<6; dy++) - setUMTerrain(x+dx, y+dy, GRASS); - } - } - - - - // Three, expands islands - for (int s=0; s0) - setRessource(bootX[s], bootY[s]-p, WOOD, amount); - smallestAmount=amount; - smallestRessource=WOOD; - - //WHEAT - for (d=0; d0) - setRessource(bootX[s]-p, bootY[s], CORN, amount); - if (amount0) - setRessource(bootX[s]+p, bootY[s]+p, smallestRessource, amount); - - //ALGAE - for (d=0; d<2*islandsSize; d++) - if (isWater(bootX[s]+d, bootY[s])) - break; - amount=descriptor.ressource[ALGA]; - amount=smoothRessources; - p=d+smoothRessources-1+amount/2; - if (amount>0) - setRessource(bootX[s]+p, bootY[s], ALGA, amount); - } - - // Let's smooth ressources... - this->smoothRessources(smoothRessources*2); -} - -bool Game::oldMakeIslandsMap(MapGenerationDescriptor &descriptor) -{ - for (int s=0; sbuildingsTypes.getTypeNum("swarm", 0, false); - if (!checkRoomForBuilding(descriptor.bootX[s], descriptor.bootY[s], globalContainer->buildingsTypes.get(typeNum), -1, false)) - { - if (verbose) - printf("Failed to add swarm of team %d\n", s); - return false; - } - teams[s]->startPosX=descriptor.bootX[s]; - teams[s]->startPosY=descriptor.bootY[s]; - Building *b=addBuilding(descriptor.bootX[s], descriptor.bootY[s], typeNum, s); - assert(b); - for (int i=0; icreateLists(); - } - map.smoothRessources(descriptor.oldIslandSize/10); - return true; -} - -bool Game::makeRandomMap(MapGenerationDescriptor &descriptor) -{ - for (int s=0; sbuildingsTypes.getTypeNum("swarm", 0, false); - if (!checkRoomForBuilding(descriptor.bootX[s], descriptor.bootY[s], globalContainer->buildingsTypes.get(typeNum), s, false)) - { - if (verbose) - printf("Failed to add swarm of team %d\n", s); - return false; - } - teams[s]->startPosX=descriptor.bootX[s]; - teams[s]->startPosY=descriptor.bootY[s]; - Building *b=addBuilding(descriptor.bootX[s], descriptor.bootY[s], typeNum, s); - assert(b); - for (int i=0; icreateLists(); - } - return true; -} - diff --git a/src/MapScript.cpp b/src/MapScript.cpp index d3d08363a..5dea3000e 100644 --- a/src/MapScript.cpp +++ b/src/MapScript.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "MapScript.h" #include diff --git a/src/MapScript.h b/src/MapScript.h index f857a26c3..ae2e938fd 100644 --- a/src/MapScript.h +++ b/src/MapScript.h @@ -1,25 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - - -#ifndef MapScript_h -#define MapScript_h +#pragma once #include "SDL.h" #include @@ -84,4 +66,3 @@ class MapScript MapScriptUSL usl; }; -#endif diff --git a/src/MapScriptError.cpp b/src/MapScriptError.cpp index 32ee0cc04..9c9ba8352 100644 --- a/src/MapScriptError.cpp +++ b/src/MapScriptError.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "MapScriptError.h" diff --git a/src/MapScriptError.h b/src/MapScriptError.h index 63438e0e4..bded50ba3 100644 --- a/src/MapScriptError.h +++ b/src/MapScriptError.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef MapScriptError_h -#define MapScriptError_h +#pragma once #include @@ -47,4 +31,3 @@ class MapScriptError }; -#endif diff --git a/src/MapScriptUSL.cpp b/src/MapScriptUSL.cpp index 0e3c241db..dff205150 100644 --- a/src/MapScriptUSL.cpp +++ b/src/MapScriptUSL.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include #include @@ -24,7 +8,7 @@ using namespace GAGCore; #include "MapScriptUSL.h" #include "GameGUI.h" -#include "error.h" +#include "position.h" #include "native.h" #include "Stream.h" diff --git a/src/MapScriptUSL.h b/src/MapScriptUSL.h index d63db558a..677da2c9e 100644 --- a/src/MapScriptUSL.h +++ b/src/MapScriptUSL.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef MapScriptUSL_h -#define MapScriptUSL_h +#pragma once #include "usl.h" #include "interpreter.h" @@ -68,4 +52,3 @@ class MapScriptUSL -#endif diff --git a/src/MarkManager.cpp b/src/MarkManager.cpp index be09f556e..cecd676ee 100644 --- a/src/MarkManager.cpp +++ b/src/MarkManager.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "MarkManager.h" #include "Utilities.h" @@ -22,6 +7,7 @@ #include "GlobalContainer.h" #include "Game.h" #include +#include Mark::Mark(int px, int py, GAGCore::Color color, int time) : showTicks(time), totalTime(time), px(px), py(py), color(color) @@ -37,15 +23,21 @@ Mark::Mark() -void Mark::draw(int x, int y, float scale) +void Mark::draw(int x, int y, float scale) const { - showTicks -= 1; - double ray = (sin((double)(showTicks * 2.0)/(double)(totalTime)*3.141592)*totalTime/2); - ray = (std::abs(ray) * showTicks) / totalTime * scale; + // Pulsing-circle radius. showTicks counts down from totalTime to 0 over + // the mark's lifetime, so lifetime_fraction ramps 1.0 -> 0.0. The phase + // sweeps a full 2Ï€ over that lifetime; |sin(phase)| is the oscillation + // envelope, and the trailing lifetime_fraction factor decays the pulse + // to zero as the mark expires. amplitude is in pixels at scale 1.0. + const double lifetime_fraction = static_cast(showTicks) / totalTime; + const double phase = lifetime_fraction * 2.0 * std::numbers::pi_v; + const double amplitude = totalTime / 2.0; + const double ray = std::abs(std::sin(phase)) * amplitude * lifetime_fraction * scale; int pixel_ray = static_cast(ray); - int line_length = static_cast(8 * scale); - int line_pos = static_cast(4 * scale); + int line_length = static_cast(MARK_LINE_LENGTH_PX * scale); + int line_pos = static_cast(MARK_LINE_OFFSET_PX * scale); globalContainer->gfx->drawCircle(x, y, pixel_ray, color); globalContainer->gfx->drawHorzLine(x + pixel_ray-line_pos+1, y, line_length, color.r, color.g, color.b); globalContainer->gfx->drawHorzLine(x-pixel_ray-line_pos, y, line_length, color.r, color.g, color.b); @@ -55,7 +47,7 @@ void Mark::draw(int x, int y, float scale) -void Mark::drawInMinimap(int s, int local, int x, int y, Game& game) +void Mark::drawInMinimap(int s, int local, int x, int y, Game& game) const { int mMax; int szX, szY; @@ -75,7 +67,7 @@ void Mark::drawInMinimap(int s, int local, int x, int y, Game& game) -void Mark::drawInMainView(int viewportX, int viewportY, Game& game) +void Mark::drawInMainView(int viewportX, int viewportY, Game& game) const { int nx, ny; game.map.mapCaseToDisplayable(px, py, &nx, &ny, viewportX, viewportY); @@ -96,12 +88,15 @@ void MarkManager::drawAll(int localTeam, int minimapX, int minimapY, int minimap { for(std::vector::iterator i=marks.begin(); i!=marks.end();) { + i->tick(); + if(i->expired()) + { + i = marks.erase(i); + continue; + } i->drawInMinimap(minimapSize, localTeam, minimapX, minimapY, game); i->drawInMainView(viewportX, viewportY, game); - if(i->showTicks == 0) - i = marks.erase(i); - else - i++; + ++i; } } diff --git a/src/MarkManager.h b/src/MarkManager.h index 069eb2088..7cf1e53d6 100644 --- a/src/MarkManager.h +++ b/src/MarkManager.h @@ -1,28 +1,22 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef MarkManager_h -#define MarkManager_h +#pragma once +#include "EngineTiming.h" #include "GraphicContext.h" class Game; +//! Length (in pixels at scale 1.0) of each cross-arm in the four-line +//! "+" decoration drawn around a Mark's pulsing circle. See MarkManager.cpp. +static constexpr int MARK_LINE_LENGTH_PX = 8; + +//! Inset (in pixels at scale 1.0) from the circle edge to where each +//! cross-arm starts. Half of MARK_LINE_LENGTH_PX so the line is centred +//! on the circle's tangent point. See MarkManager.cpp. +static constexpr int MARK_LINE_OFFSET_PX = 4; + ///This class represents a mark on the screen. Players are able to mark ///places on the map that show briefly to other players. This class ///manages and draws those marks @@ -31,25 +25,33 @@ class Mark public: ///Construct a Mark. The px and py cordinates are on the map, not on the screen ///r, g, and b are colors and time is how long the Mark is to stay on the screen - Mark(int px, int py, GAGCore::Color color, const int time=50); + Mark(int px, int py, GAGCore::Color color, const int time=MARK_DEFAULT_LIFETIME_TICKS); ///Construct an empty mark Mark(); -protected: +private: friend class MarkManager; + ///Advances the mark's lifetime by one engine tick. Called once per + ///MarkManager::drawAll iteration — the draw* methods are pure rendering + ///and must not mutate state. After tick(), the mark is expired iff + ///showTicks <= 0. + void tick() { showTicks -= 1; } + ///True once the mark's lifetime has elapsed and it should be erased. + ///Use <= rather than == to be robust against any future caller passing + ///an odd lifetime or any future code path that skips a tick. + bool expired() const { return showTicks <= 0; } ///x and y here indicate the x and y screen cordinates - void draw(int x, int y, float scale); + void draw(int x, int y, float scale) const; ///This draws the mark in a minimap where s is the size of the minimap (in pixels), ///local is the local team number, x and y are the locations of the minimap in ///pixels, and g is the game - void drawInMinimap(int s, int local, int x, int y, Game& game); + void drawInMinimap(int s, int local, int x, int y, Game& game) const; ///Draws this mark on the screen, where viewport x and viewport y are the ///positions of the viewport and game is the game - void drawInMainView(int viewportX, int viewportY, Game& game); + void drawInMainView(int viewportX, int viewportY, Game& game) const; int showTicks; int totalTime; -private: int px; int py; GAGCore::Color color; @@ -71,6 +73,3 @@ class MarkManager private: std::vector marks; }; - - -#endif diff --git a/src/Marshaling.h b/src/Marshaling.h index f079f27bf..eecc59da0 100644 --- a/src/Marshaling.h +++ b/src/Marshaling.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __MARSHALING_H -#define __MARSHALING_H +#pragma once #include #include @@ -98,5 +81,4 @@ inline Sint8 getSint8(const Uint8 *data, int pos) return *(((Sint8 *)data)+pos); } -#endif diff --git a/src/MultiplayerGame.cpp b/src/MultiplayerGame.cpp index 484cad90c..fd80b61d7 100644 --- a/src/MultiplayerGame.cpp +++ b/src/MultiplayerGame.cpp @@ -1,35 +1,28 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "MultiplayerGame.h" #include #include "Engine.h" +#include "Player.h" #include "YOGClientFileAssembler.h" #include "FormatableString.h" #include "Toolkit.h" #include "StringTable.h" -#include "NetMessage.h" +#include "FileTransferMessages.h" +#include "GameCreateMessages.h" +#include "GameHeaderMessages.h" +#include "GameJoinMessages.h" +#include "GameLaunchMessages.h" +#include "GameTeamMessages.h" +#include "OrderMessages.h" +#include "RouterMessages.h" #include "YOGClientGameListManager.h" -using boost::shared_ptr; -using boost::static_pointer_cast; +using std::shared_ptr; +using std::static_pointer_cast; -MultiplayerGame::MultiplayerGame(boost::shared_ptr client) +MultiplayerGame::MultiplayerGame(std::shared_ptr client) : client(client), creationState(YOGCreateRefusalUnknown), joinState(YOGJoinRefusalUnknown), playerManager(gameHeader) { netEngine=NULL; @@ -47,7 +40,7 @@ MultiplayerGame::MultiplayerGame(boost::shared_ptr client) isStarting=false; needToSendMapHeader=false; - previousPercentage = 255; + previousPercentage = MP_DOWNLOAD_PCT_UNREPORTED; numberOfConnectionAttempts=0; } @@ -81,7 +74,7 @@ void MultiplayerGame::update() } if(!client->getGameConnection()) { - client->setGameConnection(boost::shared_ptr(new NetConnection(gameRouterIP, YOG_ROUTER_PORT))); + client->setGameConnection(std::shared_ptr(new NetConnection(gameRouterIP, YOG_ROUTER_PORT))); } if(!client->getGameConnection()->isConnected() && !client->getGameConnection()->isConnecting()) { @@ -315,7 +308,7 @@ void MultiplayerGame::updateReadyState() if(client->getYOGClientFileAssembler(fileID)) { - if(client->getYOGClientFileAssembler(fileID)->getPercentage() != 100) + if(client->getYOGClientFileAssembler(fileID)->getPercentage() != DOWNLOAD_PCT_COMPLETE) ready = false; } @@ -400,7 +393,7 @@ int MultiplayerGame::getLocalPlayerNumber() -void MultiplayerGame::recieveMessage(boost::shared_ptr message) +void MultiplayerGame::recieveMessage(std::shared_ptr message) { Uint8 type = message->getMessageType(); //This recieves responces to creating a game @@ -486,7 +479,7 @@ void MultiplayerGame::recieveMessage(boost::shared_ptr message) { shared_ptr message(new NetRequestFile(fileID)); client->sendNetMessage(message); - boost::shared_ptr assembler(new YOGClientFileAssembler(client, fileID)); + std::shared_ptr assembler(new YOGClientFileAssembler(client, fileID)); assembler->startRecievingFile(mapHeader.getFileName()); client->setYOGClientFileAssembler(fileID, assembler); } @@ -526,13 +519,13 @@ void MultiplayerGame::recieveMessage(boost::shared_ptr message) shared_ptr info = static_pointer_cast(message); shared_ptr order = info->getOrder(); if(order->getOrderType() == ORDER_PLAYER_QUIT_GAME) - order->gameCheckSum = static_cast(-1); + order->gameCheckSum = ORDER_CHECKSUM_NONE; netEngine->pushOrder(order, order->sender, false); } } if(type==MNetRequestFile) { - boost::shared_ptr assembler(new YOGClientFileAssembler(client, fileID)); + std::shared_ptr assembler(new YOGClientFileAssembler(client, fileID)); assembler->startSendingFile(mapHeader.getFileName()); client->setYOGClientFileAssembler(fileID,assembler); } @@ -659,13 +652,13 @@ void MultiplayerGame::startEngine() void MultiplayerGame::setDefaultGameHeaderValues() { - gameHeader.setGameLatency(12); - gameHeader.setOrderRate(6); + gameHeader.setGameLatency(MP_DEFAULT_GAME_LATENCY_TICKS); + gameHeader.setOrderRate(MP_DEFAULT_ORDER_RATE_TICKS); } -void MultiplayerGame::sendToListeners(boost::shared_ptr event) +void MultiplayerGame::sendToListeners(std::shared_ptr event) { for(std::list::iterator i = listeners.begin(); i!=listeners.end(); ++i) { @@ -700,7 +693,7 @@ int MultiplayerGame::getLocalPlayer() return gameHeader.getBasePlayer(i).number; } } - return -1; + return LOCAL_PLAYER_NONE; } @@ -722,7 +715,7 @@ Uint32 MultiplayerGame::getChatChannel() const Uint8 MultiplayerGame::percentageDownloadFinished() { if(!client->getYOGClientFileAssembler(fileID)) - return 100; + return DOWNLOAD_PCT_COMPLETE; return client->getYOGClientFileAssembler(fileID)->getPercentage(); } diff --git a/src/MultiplayerGame.h b/src/MultiplayerGame.h index b167ee874..d6ed66b06 100644 --- a/src/MultiplayerGame.h +++ b/src/MultiplayerGame.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __MultiplayerGame_h -#define __MultiplayerGame_h +#pragma once #include "YOGClient.h" #include "MapHeader.h" @@ -28,13 +12,46 @@ #include "NetGamePlayerManager.h" #include "NetReteamingInformation.h" +// === Multiplayer-game sentinels and tunables === + +//! "Local player not found in this game header" sentinel returned by +//! MultiplayerGame::getLocalPlayer(). See MultiplayerGame.cpp:696. +static constexpr int LOCAL_PLAYER_NONE = -1; + +//! "Download percentage never reported yet" sentinel for +//! MultiplayerGame::previousPercentage. Distinct from 100% (= complete); +//! valid percentages are 0..100, so 255 is unambiguous. +//! See MultiplayerGame.cpp:43. +static constexpr Uint8 MP_DOWNLOAD_PCT_UNREPORTED = 255; + +//! Download "complete" percentage threshold. Used both as the equality +//! test for download-finished (MultiplayerGame.cpp:311) and as the +//! "no transfer in progress / done" return value of +//! MultiplayerGame::percentageDownloadFinished() +//! (MultiplayerGame.cpp:715, 719). +static constexpr Uint8 DOWNLOAD_PCT_COMPLETE = 100; + +//! Default network latency, in tick slots, written into the GameHeader +//! by MultiplayerGame::setDefaultGameHeaderValues(). +//! See MultiplayerGame.cpp:655. +static constexpr int MP_DEFAULT_GAME_LATENCY_TICKS = 12; + +//! Default order send-rate (1 send per N ticks) written into the +//! GameHeader by MultiplayerGame::setDefaultGameHeaderValues(). +//! See MultiplayerGame.cpp:656. +static constexpr int MP_DEFAULT_ORDER_RATE_TICKS = 6; + +//! "No chat channel selected yet" sentinel for YOGClientChatChannel +//! channel id (Uint32). See MultiplayerGameScreen.cpp:29. +static constexpr Uint32 YOG_CHAT_CHANNEL_NONE = static_cast(-1); + ///This class represents a multi-player game, both in the game and while waiting for players ///and setting up options. It channels its information through a YOGClient class MultiplayerGame { public: ///Creates a game instance and links it with the provided YOGClient - MultiplayerGame(boost::shared_ptr client); + MultiplayerGame(std::shared_ptr client); ~MultiplayerGame(); @@ -162,7 +179,7 @@ class MultiplayerGame friend class YOGClient; ///This receives a message that is sent to the game - void recieveMessage(boost::shared_ptr message); + void recieveMessage(std::shared_ptr message); ///This will start the game void startEngine(); @@ -171,14 +188,14 @@ class MultiplayerGame void setDefaultGameHeaderValues(); ///Sends the event to all listeners - void sendToListeners(boost::shared_ptr event); + void sendToListeners(std::shared_ptr event); ///Puts together reteaming information from the game header in the file NetReteamingInformation constructReteamingInformation(const std::string& file); int getLocalPlayer(); private: - boost::shared_ptr client; + std::shared_ptr client; //These are various states of the system MultiplayerMode mode; @@ -216,4 +233,3 @@ class MultiplayerGame }; -#endif diff --git a/src/MultiplayerGameEvent.cpp b/src/MultiplayerGameEvent.cpp index a2511c5cd..d5364977f 100644 --- a/src/MultiplayerGameEvent.cpp +++ b/src/MultiplayerGameEvent.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "MultiplayerGameEvent.h" #include diff --git a/src/MultiplayerGameEvent.h b/src/MultiplayerGameEvent.h index f54768ae2..52e2234d2 100644 --- a/src/MultiplayerGameEvent.h +++ b/src/MultiplayerGameEvent.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __MultiplayerGameEvent_h -#define __MultiplayerGameEvent_h +#pragma once #include #include "SDL_net.h" @@ -352,4 +336,3 @@ class MGPlayerReadyStatusChanged : public MultiplayerGameEvent //event_append_marker -#endif diff --git a/src/MultiplayerGameEventListener.cpp b/src/MultiplayerGameEventListener.cpp index 0fe9b94de..9b2ff901d 100644 --- a/src/MultiplayerGameEventListener.cpp +++ b/src/MultiplayerGameEventListener.cpp @@ -1,19 +1,4 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "MultiplayerGameEventListener.h" diff --git a/src/MultiplayerGameEventListener.h b/src/MultiplayerGameEventListener.h index 623ad0f90..f0d2999d9 100644 --- a/src/MultiplayerGameEventListener.h +++ b/src/MultiplayerGameEventListener.h @@ -1,26 +1,10 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __MultiplayerGameEventListener_h -#define __MultiplayerGameEventListener_h +#pragma once #include "MultiplayerGameEvent.h" -#include "boost/shared_ptr.hpp" +#include /// This is a mix-in class. Classes that want to respond to /// MultiplayerGameEvents should derive from this class @@ -30,8 +14,7 @@ class MultiplayerGameEventListener virtual ~MultiplayerGameEventListener() {} ///This responds to a Multiplayer Game event - virtual void handleMultiplayerGameEvent(boost::shared_ptr event) = 0; + virtual void handleMultiplayerGameEvent(std::shared_ptr event) = 0; }; -#endif diff --git a/src/MultiplayerGameScreen.cpp b/src/MultiplayerGameScreen.cpp index abc55e6fb..018c31e29 100644 --- a/src/MultiplayerGameScreen.cpp +++ b/src/MultiplayerGameScreen.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "MultiplayerGameScreen.h" #include "AINames.h" @@ -40,10 +23,10 @@ #include "YOGMessage.h" #include "CustomGameOtherOptions.h" -using boost::static_pointer_cast; +using std::static_pointer_cast; -MultiplayerGameScreen::MultiplayerGameScreen(TabScreen* parent, boost::shared_ptr game, boost::shared_ptr client, boost::shared_ptr ircChat) - : TabScreenWindow(parent, Toolkit::getStringTable()->getString("[Game]")), game(game), gameChat(new YOGClientChatChannel(static_cast(-1), client)), ircChat(ircChat) +MultiplayerGameScreen::MultiplayerGameScreen(TabScreen* parent, std::shared_ptr game, std::shared_ptr client, std::shared_ptr ircChat) + : TabScreenWindow(parent, Toolkit::getStringTable()->getString("[Game]")), game(game), gameChat(new YOGClientChatChannel(YOG_CHAT_CHANNEL_NONE, client)), ircChat(ircChat) { // we don't want to add AI_NONE for (size_t i=1; igetString("[awaiting players]"))); - for (int i=0; ileaveGame(); endExecute(Cancelled); } - else if ((par1 >= ADD_AI) && (par1 < ADD_AI + AI::SIZE)) + else if ((par1 >= ADD_AI) && (par1 < ADD_AI + static_cast(AI::SIZE))) { game->addAIPlayer((AI::ImplementitionID)(par1-ADD_AI)); } - else if ((par1>=CLOSE_BUTTONS)&&(par1=CLOSE_BUTTONS)&&(par1(CLOSE_BUTTONS)+Team::MAX_COUNT)) { game->kickPlayer(par1 - CLOSE_BUTTONS); } @@ -192,14 +175,14 @@ void MultiplayerGameScreen::onAction(Widget *source, Action action, int par1, in { if(par1 == READY) game->setHumanReady(isReady->getState()); - else if(par1 > COLOR_BUTTONS) + else if((par1 >= COLOR_BUTTONS) && (par1 < static_cast(COLOR_BUTTONS) + Team::MAX_COUNT)) game->changeTeam(par1 - COLOR_BUTTONS, par2); } else if (action==TEXT_VALIDATED) { if(textInput->getText() != "") { - boost::shared_ptr message(new YOGMessage(textInput->getText(), game->getUsername(), YOGNormalMessage)); + std::shared_ptr message(new YOGMessage(textInput->getText(), game->getUsername(), YOGNormalMessage)); gameChat->sendMessage(message); textInput->setText(""); } @@ -208,7 +191,7 @@ void MultiplayerGameScreen::onAction(Widget *source, Action action, int par1, in -void MultiplayerGameScreen::recieveTextMessage(boost::shared_ptr message) +void MultiplayerGameScreen::recieveTextMessage(std::shared_ptr message) { chatWindow->addText(message->formatForReading()); chatWindow->addText("\n"); @@ -217,7 +200,7 @@ void MultiplayerGameScreen::recieveTextMessage(boost::shared_ptr mes -void MultiplayerGameScreen::handleMultiplayerGameEvent(boost::shared_ptr event) +void MultiplayerGameScreen::handleMultiplayerGameEvent(std::shared_ptr event) { Uint8 type = event->getEventType(); if(type == MGEPlayerListChanged) @@ -287,7 +270,7 @@ void MultiplayerGameScreen::handleMultiplayerGameEvent(boost::shared_ptr info = static_pointer_cast(event); GameHeader& gh = game->getGameHeader(); - for (int i=0; igetPlayerID()) @@ -312,7 +295,7 @@ void MultiplayerGameScreen::updateJoinedPlayers() { GameHeader& gh = game->getGameHeader(); MapHeader& mh = game->getMapHeader(); - for (int i=0; iclearColors(); for (int j=0; j #include "MultiplayerGame.h" #include "AI.h" #include "MapHeader.h" +#include "Team.h" #include "YOGClientChatChannel.h" #include "YOGClientChatListener.h" #include "MultiplayerGameEventListener.h" @@ -50,7 +35,7 @@ class MultiplayerGameScreen : public TabScreenWindow, public YOGClientChatListen { public: ///The screen must be provided with the client, the irc connection and the multiplayer game - MultiplayerGameScreen(TabScreen* parent, boost::shared_ptr game, boost::shared_ptr client, boost::shared_ptr ircChat = boost::shared_ptr()); + MultiplayerGameScreen(TabScreen* parent, std::shared_ptr game, std::shared_ptr client, std::shared_ptr ircChat = std::shared_ptr()); virtual ~MultiplayerGameScreen(); enum @@ -74,48 +59,45 @@ class MultiplayerGameScreen : public TabScreenWindow, public YOGClientChatListen COLOR_BUTTONS=32, CLOSE_BUTTONS=64, - - + + ADD_AI = 100 }; - enum { MAX_NUMBER_OF_PLAYERS = 16}; - void onTimer(Uint32 tick); void onAction(Widget *source, Action action, int par1, int par2); - void recieveTextMessage(boost::shared_ptr message); + void recieveTextMessage(std::shared_ptr message); - void handleMultiplayerGameEvent(boost::shared_ptr event); + void handleMultiplayerGameEvent(std::shared_ptr event); ///This function will update the list of joined players void updateJoinedPlayers(); void updateVisibleButtons(); - + virtual void onActivated(); TextButton *startButton; TextButton *cancelButton; std::vector addAI; - ColorButton *color[MAX_NUMBER_OF_PLAYERS]; - Text *text[MAX_NUMBER_OF_PLAYERS]; - TextButton *kickButton[MAX_NUMBER_OF_PLAYERS]; + ColorButton *color[Team::MAX_COUNT]; + Text *text[Team::MAX_COUNT]; + TextButton *kickButton[Team::MAX_COUNT]; ProgressBar *percentDownloaded; TextButton *otherOptions; TextInput *textInput; TextArea *chatWindow; - + OnOffButton *isReady; Text *isReadyText; - boost::shared_ptr game; + std::shared_ptr game; - bool wasSlotUsed[MAX_NUMBER_OF_PLAYERS]; + bool wasSlotUsed[Team::MAX_COUNT]; Text *notReadyText; Text *gameStartWaitingText; - boost::shared_ptr gameChat; - boost::shared_ptr ircChat; + std::shared_ptr gameChat; + std::shared_ptr ircChat; }; -#endif diff --git a/src/MusicTrack.h b/src/MusicTrack.h new file mode 100644 index 000000000..dd245f304 --- /dev/null +++ b/src/MusicTrack.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +// Identifies one of the music tracks loaded by SoundMixer at game start. +// +// The numeric values are the indices used by SoundMixer::loadTrack / +// setNextTrack, so the order of entries here must match the load order in +// GlobalContainer::loadClient (intro/menu) and Engine::run (in-game tracks). +// The Count sentinel exists so callers can range-check. +enum class MusicTrack : unsigned +{ + Intro = 0, + Menu = 1, + InGameDefault = 2, + BuildingEvent = 3, + WarEvent = 4, + Count = 5, +}; diff --git a/src/NetBroadcastListener.h b/src/NetBroadcastListener.h deleted file mode 100644 index 71b965b7a..000000000 --- a/src/NetBroadcastListener.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __NetBroadcastListener_h -#define __NetBroadcastListener_h - -#include "SDL_net.h" -#include "LANGameInformation.h" -#include - -///This listens for sub-net broadcasts (finding a LAN game) -class NetBroadcastListener -{ -public: - ///Constructs a NetBroadcastListener, and begins listening - NetBroadcastListener(); - - ~NetBroadcastListener(); - - ///Updates the broadcast listener - void update(); - - ///Gets a list of all the LAN games - const std::vector& getLANGames(); - - ///Gets the IP address for the given lan game - std::string getIPAddress(size_t num); - - ///Enables listening - void enableListening(); - - ///Disables listening - void disableListening(); -private: - UDPsocket socket; - std::vector games; - std::vector timeouts; - std::vector addresses; - Uint64 lastTime; -}; - -#endif diff --git a/src/NetBroadcaster.h b/src/NetBroadcaster.h deleted file mode 100644 index dffb457dd..000000000 --- a/src/NetBroadcaster.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __NetBroadcaster_h -#define __NetBroadcaster_h - -#include "LANGameInformation.h" -#include "SDL_net.h" - -///This class allows for subnet broadcasting (hosting a LAN game) -class NetBroadcaster -{ -public: - ///Creates a new NetBroadcaster with the given information to broadcast - NetBroadcaster(LANGameInformation& info); - - ~NetBroadcaster(); - - ///Begins broadcasting the following game information - void broadcast(LANGameInformation& info); - - ///Updates the broadcaster - void update(); - - ///Disables broadcasting - void disableBroadcasting(); - - ///Enables broadcasting - void enableBroadcasting(); -private: - LANGameInformation info; - UDPsocket socket; - UDPsocket localsocket; - Uint64 lastTime; - Uint32 timer; -}; - -#endif diff --git a/src/NetConnectionThread.h b/src/NetConnectionThread.h deleted file mode 100644 index c3930aed6..000000000 --- a/src/NetConnectionThread.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef NetConnectionThread_h -#define NetConnectionThread_h - -#include "NetConnectionThreadMessage.h" -#include -#include -#include -#include - -///IRC thread manages IRC -class NetConnectionThread -{ -public: - NetConnectionThread(std::queue >& outgoing, boost::recursive_mutex& outgoingMutex); - - ~NetConnectionThread(); - - ///Runs the net thread - void operator()(); - - ///Sends this net thread a message - void sendMessage(boost::shared_ptr message); - - ///This returns whether the thread has exited - bool hasThreadExited(); - - ///Returns true if this object is connected - bool isConnected(); -private: - - ///Closes the connection - void closeConnection(); - - ///Sends this net message back to the main thread - void sendToMainThread(boost::shared_ptr message); - IPaddress address; - TCPsocket socket; - SDLNet_SocketSet set; - bool connected; - - std::queue > incoming; - std::queue >& outgoing; - boost::recursive_mutex incomingMutex; - boost::recursive_mutex& outgoingMutex; - bool hasExited; - //static Uint32 lastTime; - //static Uint32 amount; -}; - - -#endif diff --git a/src/NetConsts.h b/src/NetConsts.h deleted file mode 100644 index 504fae7e1..000000000 --- a/src/NetConsts.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __NET_CONSTS_H -#define __NET_CONSTS_H - -const unsigned int LAN_BROADCAST_PORT = 7486; -///This is the first port the system will try, and it will go incrementally up from there -const unsigned int P2P_CONNECTION_PORT_FIRST = 7485; -const unsigned int P2P_CONNECTION_PORT_LAST = 20001; - - -enum OrderTypes -{ - BAD_ORDER=0, - - ORDER_CREATE=20, - ORDER_MODIFY_BUILDING=22, - ORDER_MODIFY_EXCHANGE=23, - ORDER_MODIFY_SWARM=24, - ORDER_MODIFY_FLAG=30, - ORDER_MODIFY_CLEARING_FLAG=31, - ORDER_MODIFY_MIN_LEVEL_TO_FLAG=32, - ORDER_MOVE_FLAG=35, - ORDER_ALTERATE_FORBIDDEN=37, - ORDER_ALTERATE_GUARD_AREA=38, - ORDER_ALTERATE_CLEAR_AREA=39, - ORDER_DELETE=40, - ORDER_CANCEL_DELETE=41, - ORDER_CONSTRUCTION=42, - ORDER_CANCEL_CONSTRUCTION=43, - ORDER_CHANGE_PRIORITY=44, - - ORDER_NULL=51, - ORDER_PAUSE_GAME=59, - ORDER_PLAYER_QUIT_GAME=67, - - ORDER_TEXT_MESSAGE=71, - ORDER_VOICE_DATA=72, - ORDER_SET_ALLIANCE=73, - - ORDER_MAP_MARK=74, - - ORDER_ADJUST_LATENCY=100, - -}; - -#endif diff --git a/src/NetMessage.cpp b/src/NetMessage.cpp deleted file mode 100644 index 661fc968a..000000000 --- a/src/NetMessage.cpp +++ /dev/null @@ -1,4588 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "NetMessage.h" -#include -#include -#include -#include "Version.h" -#include "BinaryStream.h" - -using namespace GAGCore; - -shared_ptr NetMessage::getNetMessage(GAGCore::InputStream* stream) -{ - Uint8 netType = stream->readUint8("messageType"); - shared_ptr message; - switch(netType) - { - case MNetSendOrder: - message.reset(new NetSendOrder); - break; - case MNetSendClientInformation: - message.reset(new NetSendClientInformation); - break; - case MNetSendServerInformation: - message.reset(new NetSendServerInformation); - break; - case MNetAttemptLogin: - message.reset(new NetAttemptLogin); - break; - case MNetLoginSuccessful: - message.reset(new NetLoginSuccessful); - break; - case MNetRefuseLogin: - message.reset(new NetRefuseLogin); - break; - case MNetUpdateGameList: - message.reset(new NetUpdateGameList); - break; - case MNetDisconnect: - message.reset(new NetDisconnect); - break; - case MNetAttemptRegistration: - message.reset(new NetAttemptRegistration); - break; - case MNetAcceptRegistration: - message.reset(new NetAcceptRegistration); - break; - case MNetRefuseRegistration: - message.reset(new NetRefuseRegistration); - break; - case MNetUpdatePlayerList: - message.reset(new NetUpdatePlayerList); - break; - case MNetCreateGame: - message.reset(new NetCreateGame); - break; - case MNetAttemptJoinGame: - message.reset(new NetAttemptJoinGame); - break; - case MNetGameJoinAccepted: - message.reset(new NetGameJoinAccepted); - break; - case MNetGameJoinRefused: - message.reset(new NetGameJoinRefused); - break; - case MNetSendYOGMessage: - message.reset(new NetSendYOGMessage); - break; - case MNetSendMapHeader: - message.reset(new NetSendMapHeader); - break; - case MNetCreateGameAccepted: - message.reset(new NetCreateGameAccepted); - break; - case MNetCreateGameRefused: - message.reset(new NetCreateGameRefused); - break; - case MNetSendGameHeader: - message.reset(new NetSendGameHeader); - break; - case MNetStartGame: - message.reset(new NetStartGame); - break; - case MNetRequestFile: - message.reset(new NetRequestFile); - break; - case MNetSendFileInformation: - message.reset(new NetSendFileInformation); - break; - case MNetSendFileChunk: - message.reset(new NetSendFileChunk); - break; - case MNetKickPlayer: - message.reset(new NetKickPlayer); - break; - case MNetLeaveGame: - message.reset(new NetLeaveGame); - break; - case MNetReadyToLaunch: - message.reset(new NetReadyToLaunch); - break; - case MNetNotReadyToLaunch: - message.reset(new NetNotReadyToLaunch); - break; - case MNetSendGamePlayerInfo: - message.reset(new NetSendGamePlayerInfo); - break; - case MNetRemoveAI: - message.reset(new NetRemoveAI); - break; - case MNetChangePlayersTeam: - message.reset(new NetChangePlayersTeam); - break; - case MNetRequestGameStart: - message.reset(new NetRequestGameStart); - break; - case MNetRefuseGameStart: - message.reset(new NetRefuseGameStart); - break; - case MNetPing: - message.reset(new NetPing); - break; - case MNetPingReply: - message.reset(new NetPingReply); - break; - case MNetSetLatencyMode: - message.reset(new NetSetLatencyMode); - break; - case MNetPlayerJoinsGame: - message.reset(new NetPlayerJoinsGame); - break; - case MNetAddAI: - message.reset(new NetAddAI); - break; - case MNetSendReteamingInformation: - message.reset(new NetSendReteamingInformation); - break; - case MNetSendGameResult: - message.reset(new NetSendGameResult); - break; - case MNetPlayerIsBanned: - message.reset(new NetPlayerIsBanned); - break; - case MNetIPIsBanned: - message.reset(new NetIPIsBanned); - break; - case MNetRegisterRouter: - message.reset(new NetRegisterRouter); - break; - case MNetAcknowledgeRouter: - message.reset(new NetAcknowledgeRouter); - break; - case MNetSetGameInRouter: - message.reset(new NetSetGameInRouter); - break; - case MNetSendAfterJoinGameInformation: - message.reset(new NetSendAfterJoinGameInformation); - break; - case MNetRouterAdministratorLogin: - message.reset(new NetRouterAdministratorLogin); - break; - case MNetRouterAdministratorSendCommand: - message.reset(new NetRouterAdministratorSendCommand); - break; - case MNetRouterAdministratorSendText: - message.reset(new NetRouterAdministratorSendText); - break; - case MNetRouterAdministratorLoginAccepted: - message.reset(new NetRouterAdministratorLoginAccepted); - break; - case MNetRouterAdministratorLoginRefused: - message.reset(new NetRouterAdministratorLoginRefused); - break; - case MNetDownloadableMapInfos: - message.reset(new NetDownloadableMapInfos); - break; - case MNetRequestDownloadableMapList: - message.reset(new NetRequestDownloadableMapList); - break; - case MNetRequestMapUpload: - message.reset(new NetRequestMapUpload); - break; - case MNetAcceptMapUpload: - message.reset(new NetAcceptMapUpload); - break; - case MNetRefuseMapUpload: - message.reset(new NetRefuseMapUpload); - break; - case MNetCancelSendingFile: - message.reset(new NetCancelSendingFile); - break; - case MNetCancelRecievingFile: - message.reset(new NetCancelRecievingFile); - break; - case MNetRequestMapThumbnail: - message.reset(new NetRequestMapThumbnail); - break; - case MNetSendMapThumbnail: - message.reset(new NetSendMapThumbnail); - break; - case MNetSubmitRatingOnMap: - message.reset(new NetSubmitRatingOnMap); - break; - ///append_create_point - } - message->decodeData(stream); - return message; -} - - - -bool NetMessage::operator!=(const NetMessage& rhs) const -{ - return !(*this == rhs); -} - - - -NetSendOrder::NetSendOrder() -{ -} - - - -NetSendOrder::NetSendOrder(boost::shared_ptr newOrder) -{ - order=newOrder; -} - - - -void NetSendOrder::changeOrder(boost::shared_ptr newOrder) -{ - order = newOrder; -} - - - -boost::shared_ptr NetSendOrder::getOrder() -{ - return order; -} - - - -Uint8 NetSendOrder::getMessageType() const -{ - return MNetSendOrder; -} - - - -void NetSendOrder::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendOrder"); - Uint32 orderLength = order->getDataLength(); - stream->writeUint32(orderLength+1, "size"); - stream->writeUint8(order->getOrderType(), "orderType"); - stream->write(order->getData(), order->getDataLength(), "data"); - stream->writeUint8(order->sender, "sender"); - stream->writeUint32(order->gameCheckSum, "checksum"); - stream->writeLeaveSection(); -} - - - -void NetSendOrder::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendOrder"); - size_t size=stream->readUint32("size"); - Uint8* buffer = new Uint8[size]; - stream->read(buffer, size, "data"); - stream->readLeaveSection(); - - order = Order::getOrder(buffer, size, VERSION_MINOR); - - // If this couldn't be interpreted return it returned a NULL order, so we throw. - if (order == boost::shared_ptr()) - throw std::ios_base::failure("Couldn't decode data stream to an Order: bad format."); - - order->sender = stream->readUint8("sender"); - order->gameCheckSum = stream->readUint32("checksum"); - - delete[] buffer; -} - - - -std::string NetSendOrder::format() const -{ - std::stringstream s; - if(order==NULL) - { - s<<"NetSendOrder()"; - } - else - { - s<<"NetSendOrder(orderType="<(order->getOrderType())<<")"; - } - return s.str(); -} - - - -bool NetSendOrder::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetSendOrder)) - { - const NetSendOrder& r = dynamic_cast(rhs); - if(order==NULL || r.order==NULL) - { - return order == r.order; - } - if(typeid(r.order) == typeid(order)) - { - return true; - } - } - return false; -} - - - -NetSendClientInformation::NetSendClientInformation() -{ - netVersion=NET_PROTOCOL_VERSION; -} - - - -Uint8 NetSendClientInformation::getMessageType() const -{ - return MNetSendClientInformation; -} - - - -void NetSendClientInformation::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendClientInformation"); - stream->writeUint16(netVersion, "netVersion "); - stream->writeLeaveSection(); -} - - - -void NetSendClientInformation::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendClientInformation"); - netVersion=stream->readUint16("netVersion"); - stream->readLeaveSection(); -} - - - -std::string NetSendClientInformation::format() const -{ - std::ostringstream s; - s<<"NetSendClientInformation(netVersion="<(rhs); - if(r.netVersion == netVersion) - { - return true; - } - } - return false; -} - - -Uint16 NetSendClientInformation::getNetVersion() const -{ - return netVersion; -} - - - -NetSendServerInformation::NetSendServerInformation(YOGLoginPolicy loginPolicy, YOGGamePolicy gamePolicy, Uint16 playerID) - : loginPolicy(loginPolicy), gamePolicy(gamePolicy), playerID(playerID) -{ - -} - - - -NetSendServerInformation::NetSendServerInformation() - : loginPolicy(YOGRequirePassword), gamePolicy(YOGSingleGame), playerID(0) -{ - -} - - - -Uint8 NetSendServerInformation::getMessageType() const -{ - return MNetSendServerInformation; -} - - - -void NetSendServerInformation::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendServerInformation"); - stream->writeUint8(loginPolicy, "loginPolicy "); - stream->writeUint8(gamePolicy, "gamePolicy "); - stream->writeUint16(playerID, "playerID "); - stream->writeLeaveSection(); -} - - -void NetSendServerInformation::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendServerInformation"); - loginPolicy=static_cast(stream->readUint8("loginPolicy")); - gamePolicy=static_cast(stream->readUint8("gamePolicy")); - playerID=stream->readUint16("playerID"); - stream->readLeaveSection(); -} - - - -std::string NetSendServerInformation::format() const -{ - std::ostringstream s; - s<<"NetSendServerInformation("; - if(loginPolicy == YOGRequirePassword) - s<<"loginPolicy=YOGRequirePassword; "; - else if(loginPolicy == YOGAnonymousLogin) - s<<"loginPolicy=YOGAnonymousLogin; "; - - if(gamePolicy == YOGSingleGame) - s<<"gamePolicy=YOGSingleGame; "; - else if(gamePolicy == YOGMultipleGames) - s<<"gamePolicy=YOGMultipleGames; "; - - s<<"playerID="<(rhs); - if(r.loginPolicy == loginPolicy && r.gamePolicy == gamePolicy) - { - return true; - } - } - return false; -} - - - -YOGLoginPolicy NetSendServerInformation::getLoginPolicy() const -{ - return loginPolicy; -} - - - -YOGGamePolicy NetSendServerInformation::getGamePolicy() const -{ - return gamePolicy; -} - - - -Uint16 NetSendServerInformation::getPlayerID() const -{ - return playerID; -} - - - -NetAttemptLogin::NetAttemptLogin(const std::string& username, const std::string& password) - : username(username), password(password) -{ - -} - - - -NetAttemptLogin::NetAttemptLogin() -{ - -} - - - -Uint8 NetAttemptLogin::getMessageType() const -{ - return MNetAttemptLogin; -} - - - -void NetAttemptLogin::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetAttemptLogin"); - stream->writeText(username, "username"); - stream->writeText(password, "password"); - stream->writeLeaveSection(); -} - - - -void NetAttemptLogin::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetAttemptLogin"); - username=stream->readText("username"); - password=stream->readText("password"); - stream->readLeaveSection(); -} - - - -std::string NetAttemptLogin::format() const -{ - std::ostringstream s; - s<<"NetAttemptLogin("<<"username=\""<(rhs); - if(r.username == username && r.password==password) - { - return true; - } - } - return false; -} - - - -const std::string& NetAttemptLogin::getUsername() const -{ - return username; -} - - - -const std::string& NetAttemptLogin::getPassword() const -{ - return password; -} - - - -NetLoginSuccessful::NetLoginSuccessful() -{ - -} - - - -Uint8 NetLoginSuccessful::getMessageType() const -{ - return MNetLoginSuccessful; -} - - - -void NetLoginSuccessful::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetLoginSuccessful"); - stream->writeLeaveSection(); -} - - - -void NetLoginSuccessful::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetAttemptLogin"); - stream->readLeaveSection(); - -} - - - -std::string NetLoginSuccessful::format() const -{ - std::ostringstream s; - s<<"NetLoginSuccessful()"; - return s.str(); -} - - - -bool NetLoginSuccessful::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetLoginSuccessful)) - { -// const NetLoginSuccessful& r = dynamic_cast(rhs); - return true; - } - return false; -} - - -NetRefuseLogin::NetRefuseLogin() - : reason(YOGLoginSuccessful) -{ - -} - - - -NetRefuseLogin::NetRefuseLogin(YOGLoginState reason) - : reason(reason) -{ - -} - - - -Uint8 NetRefuseLogin::getMessageType() const -{ - return MNetRefuseLogin; -} - - - -void NetRefuseLogin::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRefuseLogin"); - stream->writeUint8(reason, "reason"); - stream->writeLeaveSection(); -} - - - -void NetRefuseLogin::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRefuseLogin"); - reason=static_cast(stream->readUint8("reason")); - stream->readLeaveSection(); -} - - - -std::string NetRefuseLogin::format() const -{ - std::ostringstream s; - std::string sreason; - if(reason == YOGLoginSuccessful) - sreason="YOGLoginSuccessful"; - if(reason == YOGLoginUnknown) - sreason="YOGLoginUnknown"; - if(reason == YOGPasswordIncorrect) - sreason="YOGPasswordIncorrect"; - if(reason == YOGUsernameAlreadyUsed) - sreason="YOGUsernameAlreadyUsed"; - if(reason == YOGUserNotRegistered) - sreason="YOGUserNotRegistered"; - s<<"NetRefuseLogin(reason="<(rhs); - if(r.reason == reason) - { - return true; - } - } - return false; -} - - - -YOGLoginState NetRefuseLogin::getRefusalReason() const -{ - return reason; -} - - -NetUpdateGameList::NetUpdateGameList() -{ - -} - - - -Uint8 NetUpdateGameList::getMessageType() const -{ - return MNetUpdateGameList; -} - - - -void NetUpdateGameList::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetUpdateGameList"); - stream->writeEnterSection("removedGames"); - stream->writeUint8(removedGames.size(), "size"); - for(Uint16 i=0; iwriteUint16(removedGames[i], "removedGames[i]"); - } - stream->writeLeaveSection(); - - stream->writeEnterSection("updatedGames"); - stream->writeUint8(updatedGames.size(), "size"); - for(Uint16 i=0; iwriteLeaveSection(); - - stream->writeLeaveSection(); -} - - - -void NetUpdateGameList::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetUpdateGameList"); - - stream->readEnterSection("removedGames"); - Uint8 size = stream->readUint8("size"); - removedGames.resize(size); - for(Uint16 i=0; ireadUint16("removedGames[i]"); - } - stream->readLeaveSection(); - - stream->readEnterSection("updatedGames"); - size = stream->readUint8("size"); - updatedGames.resize(size); - for(Uint16 i=0; ireadLeaveSection(); - - stream->readLeaveSection(); -} - - - -std::string NetUpdateGameList::format() const -{ - std::ostringstream s; - s<<"NetUpdateGameList(removedGames "<(rhs); - if(r.removedGames == removedGames && r.updatedGames == updatedGames) - { - return true; - } - } - return false; -} - - - -NetDisconnect::NetDisconnect() -{ - -} - - - -Uint8 NetDisconnect::getMessageType() const -{ - return MNetDisconnect; -} - - - -void NetDisconnect::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetDisconnect"); - stream->writeLeaveSection(); -} - - -void NetDisconnect::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetDisconnect"); - stream->readLeaveSection(); -} - - - -std::string NetDisconnect::format() const -{ - std::ostringstream s; - s<<"NetDisconnect()"; - return s.str(); -} - - - -bool NetDisconnect::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetDisconnect)) - { -// const NetDisconnect& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetAttemptRegistration::NetAttemptRegistration() -{ - -} - - - - -NetAttemptRegistration::NetAttemptRegistration(const std::string& username, const std::string& password) - : username(username), password(password) -{ - -} - - - - -Uint8 NetAttemptRegistration::getMessageType() const -{ - return MNetAttemptRegistration; -} - - - -void NetAttemptRegistration::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetAttemptRegistration"); - stream->writeText(username, "username"); - stream->writeText(password, "password"); - stream->writeLeaveSection(); -} - - - -void NetAttemptRegistration::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetAttemptRegistration"); - username=stream->readText("username"); - password=stream->readText("password"); - stream->readLeaveSection(); -} - - - -std::string NetAttemptRegistration::format() const -{ - std::ostringstream s; - s<<"NetAttemptRegistration(username=\""<(rhs); - if(username == r.username && password == r.password) - return true; - } - return false; -} - - - -std::string NetAttemptRegistration::getUsername() const -{ - return username; -} - - - -std::string NetAttemptRegistration::getPassword() const -{ - return password; -} - - - -NetAcceptRegistration::NetAcceptRegistration() -{ - -} - - - -Uint8 NetAcceptRegistration::getMessageType() const -{ - return MNetAcceptRegistration; -} - - - -void NetAcceptRegistration::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetAcceptRegistration"); - stream->writeLeaveSection(); -} - - - -void NetAcceptRegistration::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetAcceptRegistration"); - stream->readLeaveSection(); -} - - - -std::string NetAcceptRegistration::format() const -{ - std::ostringstream s; - s<<"NetAcceptRegistration()"; - return s.str(); -} - - - -bool NetAcceptRegistration::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetAcceptRegistration)) - { -// const NetAcceptRegistration& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetRefuseRegistration::NetRefuseRegistration() -{ - reason = YOGLoginUnknown; -} - - - -NetRefuseRegistration::NetRefuseRegistration(YOGLoginState reason) - : reason(reason) -{ - -} - - - -Uint8 NetRefuseRegistration::getMessageType() const -{ - return MNetRefuseRegistration; -} - - - -void NetRefuseRegistration::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRefuseRegistration"); - stream->writeUint8(reason, "reason"); - stream->writeLeaveSection(); -} - - -void NetRefuseRegistration::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRefuseRegistration"); - reason=static_cast(stream->readUint8("reason")); - stream->readLeaveSection(); -} - - - -std::string NetRefuseRegistration::format() const -{ - std::ostringstream s; - std::string sreason; - if(reason == YOGLoginSuccessful) - sreason="YOGLoginSuccessful"; - if(reason == YOGLoginUnknown) - sreason="YOGLoginUnknown"; - if(reason == YOGPasswordIncorrect) - sreason="YOGPasswordIncorrect"; - if(reason == YOGUsernameAlreadyUsed) - sreason="YOGUsernameAlreadyUsed"; - if(reason == YOGUserNotRegistered) - sreason="YOGUserNotRegistered"; - s<<"NetRefuseRegistration(reason="<(rhs); - if(reason == r.reason) - return true; - } - return false; -} - - - -YOGLoginState NetRefuseRegistration::getRefusalReason() const -{ - return reason; -} - - -NetUpdatePlayerList::NetUpdatePlayerList() -{ - -} - - - -Uint8 NetUpdatePlayerList::getMessageType() const -{ - return MNetUpdatePlayerList; -} - - - -void NetUpdatePlayerList::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetUpdatePlayerList"); - stream->writeEnterSection("removedPlayers"); - stream->writeUint8(removedPlayers.size(), "size"); - for(Uint16 i=0; iwriteUint16(removedPlayers[i], "removedPlayers[i]"); - } - stream->writeLeaveSection(); - - stream->writeEnterSection("updatedPlayers"); - stream->writeUint8(updatedPlayers.size(), "size"); - for(Uint16 i=0; iwriteLeaveSection(); - - stream->writeLeaveSection(); -} - - - -void NetUpdatePlayerList::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetUpdatePlayerList"); - - stream->readEnterSection("removedPlayers"); - Uint8 size = stream->readUint8("size"); - removedPlayers.resize(size); - for(Uint16 i=0; ireadUint16("removedPlayers[i]"); - } - stream->readLeaveSection(); - - stream->readEnterSection("updatedPlayers"); - size = stream->readUint8("size"); - updatedPlayers.resize(size); - for(Uint16 i=0; ireadLeaveSection(); - - stream->readLeaveSection(); -} - - - -std::string NetUpdatePlayerList::format() const -{ - std::ostringstream s; - s<<"NetUpdatePlayerList(updatedPlayers "<(rhs); - if(updatedPlayers == r.updatedPlayers && removedPlayers == r.removedPlayers) - return true; - } - return false; -} - - - -NetCreateGame::NetCreateGame() -{ - -} - - - -NetCreateGame::NetCreateGame(const std::string& gameName) - : gameName(gameName) -{ - -} - - - - -Uint8 NetCreateGame::getMessageType() const -{ - return MNetCreateGame; -} - - - -void NetCreateGame::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetCreateGame"); - stream->writeText(gameName, "gameName"); - stream->writeLeaveSection(); -} - - - -void NetCreateGame::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetCreateGame"); - gameName=stream->readText("gameName"); - stream->readLeaveSection(); -} - - - -std::string NetCreateGame::format() const -{ - std::ostringstream s; - s<<"NetCreateGame(gameName=\""<(rhs); - if(r.gameName == gameName) - return true; - } - return false; -} - - - -const std::string& NetCreateGame::getGameName() const -{ - return gameName; -} - - - -NetAttemptJoinGame::NetAttemptJoinGame() -{ - gameID = 0; -} - - - -NetAttemptJoinGame::NetAttemptJoinGame(Uint16 gameID) - : gameID(gameID) -{ - -} - - - -Uint8 NetAttemptJoinGame::getMessageType() const -{ - return MNetAttemptJoinGame; -} - - - -void NetAttemptJoinGame::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetAttemptJoinGame"); - stream->writeUint16(gameID, "gameID"); - stream->writeLeaveSection(); -} - - - -void NetAttemptJoinGame::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetAttemptJoinGame"); - gameID=stream->readUint16("gameID"); - stream->readLeaveSection(); -} - - - -std::string NetAttemptJoinGame::format() const -{ - std::ostringstream s; - s<<"NetAttemptJoinGame(gameID="<(rhs); - if(r.gameID == gameID) - return true; - } - return false; -} - - - -Uint16 NetAttemptJoinGame::getGameID() const -{ - return gameID; -} - - - -NetGameJoinAccepted::NetGameJoinAccepted() -{ - chatChannel = 0; -} - - - -NetGameJoinAccepted::NetGameJoinAccepted(Uint32 chatChannel) - : chatChannel(chatChannel) -{ - -} - - - -Uint8 NetGameJoinAccepted::getMessageType() const -{ - return MNetGameJoinAccepted; -} - - - -void NetGameJoinAccepted::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetGameJoinAccepted"); - stream->writeUint32(chatChannel, "chatChannel"); - stream->writeLeaveSection(); -} - - - -void NetGameJoinAccepted::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetGameJoinAccepted"); - chatChannel = stream->readUint32("chatChannel"); - stream->readLeaveSection(); -} - - - -std::string NetGameJoinAccepted::format() const -{ - std::ostringstream s; - s<<"NetGameJoinAccepted(chatChannel="<(rhs); - if(r.chatChannel != chatChannel) - { - return false; - } - return true; - } - return false; -} - - - -Uint32 NetGameJoinAccepted::getChatChannel() const -{ - return chatChannel; -} - - - -NetGameJoinRefused::NetGameJoinRefused() -{ - reason = YOGJoinRefusalUnknown; -} - - - -NetGameJoinRefused::NetGameJoinRefused(YOGServerGameJoinRefusalReason reason) - : reason(reason) -{ - -} - - - -Uint8 NetGameJoinRefused::getMessageType() const -{ - return MNetGameJoinRefused; -} - - - -void NetGameJoinRefused::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetGameJoinRefused"); - stream->writeUint8(reason, "reason"); - stream->writeLeaveSection(); -} - - - -void NetGameJoinRefused::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetGameJoinRefused"); - reason=static_cast(stream->readUint8("reason")); - stream->readLeaveSection(); -} - - - -std::string NetGameJoinRefused::format() const -{ - std::ostringstream s; - std::string sreason; - if(reason == YOGJoinRefusalUnknown) - sreason="YOGJoinRefusalUnknown"; - s<<"NetGameJoinRefused(reason="<(rhs); - if(r.reason == reason) - return true; - } - return false; -} - - - - -YOGServerGameJoinRefusalReason NetGameJoinRefused::getRefusalReason() const -{ - return reason; -} - - - -NetSendYOGMessage::NetSendYOGMessage(Uint32 channel, boost::shared_ptr message) - : channel(channel), message(message) -{ - -} - - - -NetSendYOGMessage::NetSendYOGMessage() - : channel(0) -{ - -} - - - -Uint8 NetSendYOGMessage::getMessageType() const -{ - return MNetSendYOGMessage; -} - - - -void NetSendYOGMessage::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendYOGMessage"); - stream->writeUint32(channel, "channel"); - message->encodeData(stream); - stream->writeLeaveSection(); -} - - - -void NetSendYOGMessage::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendYOGMessage"); - channel = stream->readUint32("channel"); - message.reset(new YOGMessage); - message->decodeData(stream); - stream->readLeaveSection(); -} - - - -std::string NetSendYOGMessage::format() const -{ - std::ostringstream s; - s<<"NetSendYOGMessage(channel="<(rhs); - if(channel != r.channel) - return false; - else if(!message && !r.message) - return true; - else if(!message && r.message) - return false; - else if(message && !r.message) - return false; - if((*message) == (*r.message)) - return true; - } - return false; -} - - - -Uint32 NetSendYOGMessage::getChannel() const -{ - return channel; -} - - - -boost::shared_ptr NetSendYOGMessage::getMessage() const -{ - return message; -} - - - -NetSendMapHeader::NetSendMapHeader() -{ - -} - - - -NetSendMapHeader::NetSendMapHeader(const MapHeader& mapHeader) - : mapHeader(mapHeader) -{ - -} - - - -Uint8 NetSendMapHeader::getMessageType() const -{ - return MNetSendMapHeader; -} - - - -void NetSendMapHeader::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendMapHeader"); - mapHeader.save(stream); - stream->writeLeaveSection(); -} - - - -void NetSendMapHeader::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendMapHeader"); - mapHeader.load(stream); - stream->readLeaveSection(); -} - - - -std::string NetSendMapHeader::format() const -{ - std::ostringstream s; - s<<"NetSendMapHeader(mapname="+mapHeader.getMapName()+")"; - return s.str(); -} - - - -bool NetSendMapHeader::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetSendMapHeader)) - { - //const NetSendMapHeader& r = dynamic_cast(rhs); - return true; - } - return false; -} - - -const MapHeader& NetSendMapHeader::getMapHeader() const -{ - return mapHeader; -} - - -NetCreateGameAccepted::NetCreateGameAccepted() -{ - chatChannel = 0; - gameID = 0; - routerIP = ""; - fileID = 0; -} - - -NetCreateGameAccepted::NetCreateGameAccepted(Uint32 chatChannel, Uint16 gameID, const std::string& routerIP, Uint16 fileID) - : chatChannel(chatChannel), gameID(gameID), routerIP(routerIP), fileID(fileID) -{ - -} - - - -Uint8 NetCreateGameAccepted::getMessageType() const -{ - return MNetCreateGameAccepted; -} - - - -void NetCreateGameAccepted::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetCreateGameAccepted"); - stream->writeUint32(chatChannel, "chatChannel"); - stream->writeUint16(gameID, "gameID"); - stream->writeText(routerIP, "routerIP"); - stream->writeUint16(fileID, "fileID"); - stream->writeLeaveSection(); -} - - - -void NetCreateGameAccepted::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetCreateGameAccepted"); - chatChannel = stream->readUint32("chatChannel"); - gameID = stream->readUint16("gameID"); - routerIP = stream->readText("routerIP"); - fileID = stream->readUint16("fileID"); - stream->readLeaveSection(); -} - - - -std::string NetCreateGameAccepted::format() const -{ - std::ostringstream s; - s<<"NetCreateGameAccepted(chatChannel="<(rhs); - if(chatChannel != r.chatChannel || gameID != r.gameID || routerIP != r.routerIP || fileID != r.fileID) - { - return false; - } - return true; - } - return false; -} - - - -Uint32 NetCreateGameAccepted::getChatChannel() const -{ - return chatChannel; -} - - - -Uint16 NetCreateGameAccepted::getGameID() const -{ - return gameID; -} - - - -const std::string NetCreateGameAccepted::getGameRouterIP() const -{ - return routerIP; -} - - - -Uint16 NetCreateGameAccepted::getFileID() const -{ - return fileID; -} - - - -NetCreateGameRefused::NetCreateGameRefused() -{ - reason = YOGCreateRefusalUnknown; -} - - - -NetCreateGameRefused::NetCreateGameRefused(YOGServerGameCreateRefusalReason reason) - : reason(reason) -{ - -} - - - -Uint8 NetCreateGameRefused::getMessageType() const -{ - return MNetCreateGameRefused; -} - - - -void NetCreateGameRefused::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetCreateGameRefused"); - stream->writeUint8(reason, "reason"); - stream->writeLeaveSection(); -} - - - -void NetCreateGameRefused::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetCreateGameRefused"); - reason = static_cast(stream->readUint8("reason")); - stream->readLeaveSection(); -} - - - -std::string NetCreateGameRefused::format() const -{ - std::ostringstream s; - s<<"NetCreateGameRefused(reason="<(rhs); - if(reason == r.reason) - return true; - } - return false; -} - - -YOGServerGameCreateRefusalReason NetCreateGameRefused::getRefusalReason() const -{ - return reason; -} - - - - -NetSendGameHeader::NetSendGameHeader() -{ - -} - - -NetSendGameHeader::NetSendGameHeader(const GameHeader& gameHeader) - : gameHeader(gameHeader) -{ - -} - - - -Uint8 NetSendGameHeader::getMessageType() const -{ - return MNetSendGameHeader; -} - - - -void NetSendGameHeader::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendGameHeader"); - gameHeader.saveWithoutPlayerInfo(stream); - stream->writeLeaveSection(); -} - - - -void NetSendGameHeader::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendGameHeader"); - gameHeader.loadWithoutPlayerInfo(stream, VERSION_MINOR); - stream->readLeaveSection(); -} - - - -std::string NetSendGameHeader::format() const -{ - std::ostringstream s; - s<<"NetSendGameHeader()"; - return s.str(); -} - - - -bool NetSendGameHeader::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetSendGameHeader)) - { - //const NetSendGameHeader& r = dynamic_cast(rhs); -// if(gameHeader == r.gameHeader) - return true; - } - return false; -} - - - -void NetSendGameHeader::downloadToGameHeader(GameHeader& newGameHeader) -{ - //This is a special trick used to avoid having to manually copy over every - //variable - MemoryStreamBackend* obackend = new MemoryStreamBackend; - GAGCore::BinaryOutputStream* ostream = new BinaryOutputStream(obackend); - gameHeader.saveWithoutPlayerInfo(ostream); - - - obackend->seekFromStart(0); - MemoryStreamBackend* ibackend = new MemoryStreamBackend(*obackend); - GAGCore::BinaryInputStream* istream = new BinaryInputStream(ibackend); - newGameHeader.loadWithoutPlayerInfo(istream, VERSION_MINOR); - - delete ostream; - delete istream; -} - - - - -NetSendGamePlayerInfo::NetSendGamePlayerInfo() -{ - -} - - - - -NetSendGamePlayerInfo::NetSendGamePlayerInfo(GameHeader& gameHeader) - : gameHeader(gameHeader) -{ -} - - - -Uint8 NetSendGamePlayerInfo::getMessageType() const -{ - return MNetSendGamePlayerInfo; -} - - - -void NetSendGamePlayerInfo::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendGamePlayerInfo"); - gameHeader.savePlayerInfo(stream); - stream->writeLeaveSection(); -} - - - -void NetSendGamePlayerInfo::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendGamePlayerInfo"); - gameHeader.loadPlayerInfo(stream, VERSION_MINOR); - stream->readLeaveSection(); -} - - - -std::string NetSendGamePlayerInfo::format() const -{ - std::ostringstream s; - s<<"NetSendGamePlayerInfo()"; - return s.str(); -} - - - -bool NetSendGamePlayerInfo::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetSendGamePlayerInfo)) - { - //const NetSendGamePlayerInfo& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -void NetSendGamePlayerInfo::downloadToGameHeader(GameHeader& header) -{ - //This is a special trick used to avoid having to manually copy over every - //variable - MemoryStreamBackend* obackend = new MemoryStreamBackend; - GAGCore::BinaryOutputStream* ostream = new BinaryOutputStream(obackend); - gameHeader.savePlayerInfo(ostream); - - obackend->seekFromStart(0); - MemoryStreamBackend* ibackend = new MemoryStreamBackend(*obackend); - GAGCore::BinaryInputStream* istream = new BinaryInputStream(ibackend); - header.loadPlayerInfo(istream, VERSION_MINOR); - - delete ostream; - delete istream; -} - - - -NetStartGame::NetStartGame() -{ - -} - - - -Uint8 NetStartGame::getMessageType() const -{ - return MNetStartGame; -} - - - -void NetStartGame::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetStartGame"); - stream->writeLeaveSection(); -} - - - -void NetStartGame::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetStartGame"); - stream->readLeaveSection(); -} - - - -std::string NetStartGame::format() const -{ - std::ostringstream s; - s<<"NetStartGame()"; - return s.str(); -} - - - -bool NetStartGame::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetStartGame)) - { - //const NetStartGame& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetRequestFile::NetRequestFile() - : fileID(0) -{ - -} - - - -NetRequestFile::NetRequestFile(Uint16 fileID) - : fileID(fileID) -{ - -} - - - -Uint8 NetRequestFile::getMessageType() const -{ - return MNetRequestFile; -} - - - -void NetRequestFile::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRequestFile"); - stream->writeUint16(fileID, "fileID"); - stream->writeLeaveSection(); -} - - - -void NetRequestFile::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRequestFile"); - fileID = stream->readUint16("fileID"); - stream->readLeaveSection(); -} - - - -std::string NetRequestFile::format() const -{ - std::ostringstream s; - s<<"NetRequestFile(fileID="<(rhs); - if(fileID == r.fileID) - return true; - } - return false; -} - - - -Uint16 NetRequestFile::getFileID() -{ - return fileID; -} - - - -NetSendFileInformation::NetSendFileInformation() - : size(0), fileID(0) -{ - -} - - -NetSendFileInformation::NetSendFileInformation(Uint32 filesize, Uint16 fileID) - : size(filesize), fileID(fileID) -{ -} - - - -Uint8 NetSendFileInformation::getMessageType() const -{ - return MNetSendFileInformation; -} - - - -void NetSendFileInformation::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendFileInformation"); - stream->writeUint32(size, "size"); - stream->writeUint16(fileID, "fileID"); - stream->writeLeaveSection(); -} - - - -void NetSendFileInformation::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendFileInformation"); - size = stream->readUint32("size"); - fileID = stream->readUint16("fileID"); - stream->readLeaveSection(); -} - - - -std::string NetSendFileInformation::format() const -{ - std::ostringstream s; - s<<"NetSendFileInformation(size="<(rhs); - if(r.size == size && r.fileID == fileID) - return true; - } - return false; -} - - - -Uint32 NetSendFileInformation::getFileSize() const -{ - return size; -} - - - -Uint16 NetSendFileInformation::getFileID() const -{ - return fileID; -} - - - -NetSendFileChunk::NetSendFileChunk() -{ - std::fill(data, data+4096, 0); - size=0; - fileID=0; -} - - - -NetSendFileChunk::NetSendFileChunk(boost::shared_ptr stream, Uint16 fileID) - : fileID(fileID) -{ - size=0; - int pos=0; - while(!stream->isEndOfStream() && size < 4096) - { - stream->read(data+pos, 1, ""); - //For some reason the last byte is an overread, so it should be ignored - if(!stream->isEndOfStream()) - { - pos+=1; - size+=1; - } - } -} - - - -Uint8 NetSendFileChunk::getMessageType() const -{ - return MNetSendFileChunk; -} - - - -void NetSendFileChunk::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendFileChunk"); - stream->writeUint32(size, "size"); - stream->write(data, size, "data"); - stream->writeUint16(fileID, "fileID"); - stream->writeLeaveSection(); -} - - - -void NetSendFileChunk::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendFileChunk"); - size = stream->readUint32("size"); - stream->read(data, size, "data"); - fileID = stream->readUint16("fileID"); - stream->readLeaveSection(); -} - - - -std::string NetSendFileChunk::format() const -{ - std::ostringstream s; - s<<"NetSendFileChunk(size="<(rhs); - for(int i=0; i<4096; ++i) - { - if(data[i] != r.data[i]) - return false; - } - if(fileID != r.fileID) - return false; - return true; - } - return false; -} - - - -const Uint8* NetSendFileChunk::getBuffer() const -{ - return data; -} - - - -Uint32 NetSendFileChunk::getChunkSize() const -{ - return size; -} - - - -Uint16 NetSendFileChunk::getFileID() const -{ - return fileID; -} - - - -NetKickPlayer::NetKickPlayer() - : playerID(0), reason(YOGUnknownKickReason) -{ -} - - - -NetKickPlayer::NetKickPlayer(Uint16 playerID, YOGKickReason reason) - : playerID(playerID), reason(reason) -{ -} - - - -Uint8 NetKickPlayer::getMessageType() const -{ - return MNetKickPlayer; -} - - - -void NetKickPlayer::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetKickPlayer"); - stream->writeUint16(playerID, "playerID"); - stream->writeUint8(reason, "reason"); - stream->writeLeaveSection(); -} - - - -void NetKickPlayer::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetKickPlayer"); - playerID = stream->readUint16("playerID"); - reason = static_cast(stream->readUint8("reason")); - stream->readLeaveSection(); -} - - - -std::string NetKickPlayer::format() const -{ - std::ostringstream s; - s<<"NetKickPlayer(playerID="<(rhs); - if(r.playerID == playerID && r.reason == reason) - return true; - } - return false; -} - - - -Uint16 NetKickPlayer::getPlayerID() -{ - return playerID; -} - - - -YOGKickReason NetKickPlayer::getReason() -{ - return reason; -} - - - - -NetLeaveGame::NetLeaveGame() -{ - -} - - - -Uint8 NetLeaveGame::getMessageType() const -{ - return MNetLeaveGame; -} - - - -void NetLeaveGame::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetLeaveGame"); - stream->writeLeaveSection(); -} - - - -void NetLeaveGame::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetLeaveGame"); - stream->readLeaveSection(); -} - - - -std::string NetLeaveGame::format() const -{ - std::ostringstream s; - s<<"NetLeaveGame()"; - return s.str(); -} - - - -bool NetLeaveGame::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetLeaveGame)) - { - //const NetLeaveGame& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetReadyToLaunch::NetReadyToLaunch() - : playerID(0) -{ - -} - - - -NetReadyToLaunch::NetReadyToLaunch(Uint16 playerID) - : playerID(playerID) -{ -} - - - -Uint8 NetReadyToLaunch::getMessageType() const -{ - return MNetReadyToLaunch; -} - - - -void NetReadyToLaunch::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetReadyToLaunch"); - stream->writeUint16(playerID, "playerID"); - stream->writeLeaveSection(); -} - - - -void NetReadyToLaunch::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetReadyToLaunch"); - playerID = stream->readUint16("playerID"); - stream->readLeaveSection(); -} - - - -std::string NetReadyToLaunch::format() const -{ - std::ostringstream s; - s<<"NetReadyToLaunch("<<"playerID="<(rhs); - if(r.playerID == playerID) - return true; - } - return false; -} - - -Uint16 NetReadyToLaunch::getPlayerID() const -{ - return playerID; -} - - - - -NetNotReadyToLaunch::NetNotReadyToLaunch() - : playerID(0) -{ - -} - - - -NetNotReadyToLaunch::NetNotReadyToLaunch(Uint16 playerID) - :playerID(playerID) -{ -} - - - -Uint8 NetNotReadyToLaunch::getMessageType() const -{ - return MNetNotReadyToLaunch; -} - - - -void NetNotReadyToLaunch::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetNotReadyToLaunch"); - stream->writeUint16(playerID, "playerID"); - stream->writeLeaveSection(); -} - - - -void NetNotReadyToLaunch::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetNotReadyToLaunch"); - playerID = stream->readUint16("playerID"); - stream->readLeaveSection(); -} - - - -std::string NetNotReadyToLaunch::format() const -{ - std::ostringstream s; - s<<"NetNotReadyToLaunch("<<"playerID="<(rhs); - if(r.playerID == playerID) - return true; - } - return false; -} - - -Uint16 NetNotReadyToLaunch::getPlayerID() const -{ - return playerID; -} - - - -NetRemoveAI::NetRemoveAI() - : playerNum(0) -{ - -} - - - -NetRemoveAI::NetRemoveAI(Uint8 playerNum) - :playerNum(playerNum) -{ -} - - - -Uint8 NetRemoveAI::getMessageType() const -{ - return MNetRemoveAI; -} - - - -void NetRemoveAI::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRemoveAI"); - stream->writeUint8(playerNum, "playerNum"); - stream->writeLeaveSection(); -} - - - -void NetRemoveAI::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRemoveAI"); - playerNum = stream->readUint8("playerNum"); - stream->readLeaveSection(); -} - - - -std::string NetRemoveAI::format() const -{ - std::ostringstream s; - s<<"NetRemoveAI("<<"playerNum="<(rhs); - if(r.playerNum == playerNum) - return true; - } - return false; -} - - -Uint8 NetRemoveAI::getPlayerNumber() const -{ - return playerNum; -} - - - - -NetChangePlayersTeam::NetChangePlayersTeam() - : player(0), team(0) -{ - -} - - - -NetChangePlayersTeam::NetChangePlayersTeam(Uint8 player, Uint8 team) - :player(player), team(team) -{ -} - - - -Uint8 NetChangePlayersTeam::getMessageType() const -{ - return MNetChangePlayersTeam; -} - - - -void NetChangePlayersTeam::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetChangePlayersTeam"); - stream->writeUint8(player, "player"); - stream->writeUint8(team, "team"); - stream->writeLeaveSection(); -} - - - -void NetChangePlayersTeam::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetChangePlayersTeam"); - player = stream->readUint8("player"); - team = stream->readUint8("team"); - stream->readLeaveSection(); -} - - - -std::string NetChangePlayersTeam::format() const -{ - std::ostringstream s; - s<<"NetChangePlayersTeam("<<"player="<(rhs); - if(r.player == player && r.team == team) - return true; - } - return false; -} - - -Uint8 NetChangePlayersTeam::getPlayer() const -{ - return player; -} - - - -Uint8 NetChangePlayersTeam::getTeam() const -{ - return team; -} - - - - -NetRequestGameStart::NetRequestGameStart() -{ - -} - - - -Uint8 NetRequestGameStart::getMessageType() const -{ - return MNetRequestGameStart; -} - - - -void NetRequestGameStart::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRequestGameStart"); - stream->writeLeaveSection(); -} - - - -void NetRequestGameStart::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRequestGameStart"); - stream->readLeaveSection(); -} - - - -std::string NetRequestGameStart::format() const -{ - std::ostringstream s; - s<<"NetRequestGameStart()"; - return s.str(); -} - - - -bool NetRequestGameStart::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetRequestGameStart)) - { - //const NetRequestGameStart& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetRefuseGameStart::NetRefuseGameStart() - : refusalReason(YOGUnknownStartRefusalReason) -{ - -} - - - -NetRefuseGameStart::NetRefuseGameStart(YOGServerGameStartRefusalReason refusalReason) - :refusalReason(refusalReason) -{ -} - - - -Uint8 NetRefuseGameStart::getMessageType() const -{ - return MNetRefuseGameStart; -} - - - -void NetRefuseGameStart::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRefuseGameStart"); - stream->writeUint8(refusalReason, "refusalReason"); - stream->writeLeaveSection(); -} - - - -void NetRefuseGameStart::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRefuseGameStart"); - refusalReason = static_cast(stream->readUint8("refusalReason")); - stream->readLeaveSection(); -} - - - -std::string NetRefuseGameStart::format() const -{ - std::ostringstream s; - s<<"NetRefuseGameStart("<<"refusalReason="<(rhs); - if(r.refusalReason == refusalReason) - return true; - } - return false; -} - - -YOGServerGameStartRefusalReason NetRefuseGameStart::getRefusalReason() const -{ - return refusalReason; -} - - - - -NetPing::NetPing() -{ - -} - - - -Uint8 NetPing::getMessageType() const -{ - return MNetPing; -} - - - -void NetPing::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetPing"); - stream->writeLeaveSection(); -} - - - -void NetPing::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetPing"); - stream->readLeaveSection(); -} - - - -std::string NetPing::format() const -{ - std::ostringstream s; - s<<"NetPing()"; - return s.str(); -} - - - -bool NetPing::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetPing)) - { - //const NetPing& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetPingReply::NetPingReply() -{ - -} - - - -Uint8 NetPingReply::getMessageType() const -{ - return MNetPingReply; -} - - - -void NetPingReply::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetPingReply"); - stream->writeLeaveSection(); -} - - - -void NetPingReply::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetPingReply"); - stream->readLeaveSection(); -} - - - -std::string NetPingReply::format() const -{ - std::ostringstream s; - s<<"NetPingReply()"; - return s.str(); -} - - - -bool NetPingReply::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetPingReply)) - { - //const NetPingReply& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetSetLatencyMode::NetSetLatencyMode() - : latencyAdjustment(0) -{ - -} - - - -NetSetLatencyMode::NetSetLatencyMode(Uint8 latencyAdjustment) - :latencyAdjustment(latencyAdjustment) -{ -} - - - -Uint8 NetSetLatencyMode::getMessageType() const -{ - return MNetSetLatencyMode; -} - - - -void NetSetLatencyMode::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSetLatencyMode"); - stream->writeUint8(latencyAdjustment, "latencyAdjustment"); - stream->writeLeaveSection(); -} - - - -void NetSetLatencyMode::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSetLatencyMode"); - latencyAdjustment = stream->readUint8("latencyAdjustment"); - stream->readLeaveSection(); -} - - - -std::string NetSetLatencyMode::format() const -{ - std::ostringstream s; - s<<"NetSetLatencyMode("<<"latencyAdjustment="<(latencyAdjustment)<<"; "<<")"; - return s.str(); -} - - - -bool NetSetLatencyMode::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetSetLatencyMode)) - { - const NetSetLatencyMode& r = dynamic_cast(rhs); - if(r.latencyAdjustment == latencyAdjustment) - return true; - } - return false; -} - - -Uint8 NetSetLatencyMode::getLatencyAdjustment() const -{ - return latencyAdjustment; -} - - - - -NetPlayerJoinsGame::NetPlayerJoinsGame() - : playerID(0), playerName("") -{ - -} - - - -NetPlayerJoinsGame::NetPlayerJoinsGame(Uint16 playerID, std::string playerName) - :playerID(playerID), playerName(playerName) -{ -} - - - -Uint8 NetPlayerJoinsGame::getMessageType() const -{ - return MNetPlayerJoinsGame; -} - - - -void NetPlayerJoinsGame::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetPlayerJoinsGame"); - stream->writeUint16(playerID, "playerID"); - stream->writeText(playerName, "playerName"); - stream->writeLeaveSection(); -} - - - -void NetPlayerJoinsGame::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetPlayerJoinsGame"); - playerID = stream->readUint16("playerID"); - playerName = stream->readText("playerName"); - stream->readLeaveSection(); -} - - - -std::string NetPlayerJoinsGame::format() const -{ - std::ostringstream s; - s<<"NetPlayerJoinsGame("<<"playerID="<(rhs); - if(r.playerID == playerID && r.playerName == playerName) - return true; - } - return false; -} - - -Uint16 NetPlayerJoinsGame::getPlayerID() const -{ - return playerID; -} - - - -std::string NetPlayerJoinsGame::getPlayerName() const -{ - return playerName; -} - - - - -NetAddAI::NetAddAI() - : type(0) -{ - -} - - - -NetAddAI::NetAddAI(Uint8 type) - :type(type) -{ -} - - - -Uint8 NetAddAI::getMessageType() const -{ - return MNetAddAI; -} - - - -void NetAddAI::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetAddAI"); - stream->writeUint8(type, "type"); - stream->writeLeaveSection(); -} - - - -void NetAddAI::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetAddAI"); - type = stream->readUint8("type"); - stream->readLeaveSection(); -} - - - -std::string NetAddAI::format() const -{ - std::ostringstream s; - s<<"NetAddAI("<<"type="<(rhs); - if(r.type == type) - return true; - } - return false; -} - - -Uint8 NetAddAI::getType() const -{ - return type; -} - - - - -NetSendReteamingInformation::NetSendReteamingInformation() -{ - -} - - - -NetSendReteamingInformation::NetSendReteamingInformation(NetReteamingInformation reteamingInfo) - :reteamingInfo(reteamingInfo) -{ -} - - - -Uint8 NetSendReteamingInformation::getMessageType() const -{ - return MNetSendReteamingInformation; -} - - - -void NetSendReteamingInformation::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendReteamingInformation"); - reteamingInfo.encodeData(stream); - stream->writeLeaveSection(); -} - - - -void NetSendReteamingInformation::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendReteamingInformation"); - reteamingInfo.decodeData(stream); - stream->readLeaveSection(); -} - - - -std::string NetSendReteamingInformation::format() const -{ - std::ostringstream s; - s<<"NetSendReteamingInformation()"; - return s.str(); -} - - - -bool NetSendReteamingInformation::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetSendReteamingInformation)) - { - const NetSendReteamingInformation& r = dynamic_cast(rhs); - if(r.reteamingInfo == reteamingInfo) - return true; - } - return false; -} - - -NetReteamingInformation NetSendReteamingInformation::getReteamingInfo() const -{ - return reteamingInfo; -} - - - - -NetSendGameResult::NetSendGameResult() - : result(YOGGameResultUnknown) -{ - -} - - - -NetSendGameResult::NetSendGameResult(YOGGameResult result) - :result(result) -{ -} - - - -Uint8 NetSendGameResult::getMessageType() const -{ - return MNetSendGameResult; -} - - - -void NetSendGameResult::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendGameResult"); - stream->writeUint8(static_cast(result), "result"); - stream->writeLeaveSection(); -} - - - -void NetSendGameResult::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendGameResult"); - result = static_cast(stream->readUint8("result")); - stream->readLeaveSection(); -} - - - -std::string NetSendGameResult::format() const -{ - std::ostringstream s; - s<<"NetSendGameResult("<<"result="<(rhs); - if(r.result == result) - return true; - } - return false; -} - - -YOGGameResult NetSendGameResult::getGameResult() const -{ - return result; -} - - - - -NetPlayerIsBanned::NetPlayerIsBanned() -{ - -} - - - -Uint8 NetPlayerIsBanned::getMessageType() const -{ - return MNetPlayerIsBanned; -} - - - -void NetPlayerIsBanned::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetPlayerIsBanned"); - stream->writeLeaveSection(); -} - - - -void NetPlayerIsBanned::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetPlayerIsBanned"); - stream->readLeaveSection(); -} - - - -std::string NetPlayerIsBanned::format() const -{ - std::ostringstream s; - s<<"NetPlayerIsBanned()"; - return s.str(); -} - - - -bool NetPlayerIsBanned::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetPlayerIsBanned)) - { - //const NetPlayerIsBanned& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetIPIsBanned::NetIPIsBanned() -{ - -} - - - -Uint8 NetIPIsBanned::getMessageType() const -{ - return MNetIPIsBanned; -} - - - -void NetIPIsBanned::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetIPIsBanned"); - stream->writeLeaveSection(); -} - - - -void NetIPIsBanned::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetIPIsBanned"); - stream->readLeaveSection(); -} - - - -std::string NetIPIsBanned::format() const -{ - std::ostringstream s; - s<<"NetIPIsBanned()"; - return s.str(); -} - - - -bool NetIPIsBanned::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetIPIsBanned)) - { - //const NetIPIsBanned& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetRegisterRouter::NetRegisterRouter() -{ - -} - - - -Uint8 NetRegisterRouter::getMessageType() const -{ - return MNetRegisterRouter; -} - - - -void NetRegisterRouter::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRegisterRouter"); - stream->writeLeaveSection(); -} - - - -void NetRegisterRouter::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRegisterRouter"); - stream->readLeaveSection(); -} - - - -std::string NetRegisterRouter::format() const -{ - std::ostringstream s; - s<<"NetRegisterRouter()"; - return s.str(); -} - - - -bool NetRegisterRouter::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetRegisterRouter)) - { - //const NetRegisterRouter& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetAcknowledgeRouter::NetAcknowledgeRouter() -{ - -} - - - -Uint8 NetAcknowledgeRouter::getMessageType() const -{ - return MNetAcknowledgeRouter; -} - - - -void NetAcknowledgeRouter::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetAcknowledgeRouter"); - stream->writeLeaveSection(); -} - - - -void NetAcknowledgeRouter::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetAcknowledgeRouter"); - stream->readLeaveSection(); -} - - - -std::string NetAcknowledgeRouter::format() const -{ - std::ostringstream s; - s<<"NetAcknowledgeRouter()"; - return s.str(); -} - - - -bool NetAcknowledgeRouter::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetAcknowledgeRouter)) - { - //const NetAcknowledgeRouter& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetSetGameInRouter::NetSetGameInRouter() - : gameID(0) -{ - -} - - - -NetSetGameInRouter::NetSetGameInRouter(Uint16 gameID) - :gameID(gameID) -{ -} - - - -Uint8 NetSetGameInRouter::getMessageType() const -{ - return MNetSetGameInRouter; -} - - - -void NetSetGameInRouter::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSetGameInRouter"); - stream->writeUint16(gameID, "gameID"); - stream->writeLeaveSection(); -} - - - -void NetSetGameInRouter::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSetGameInRouter"); - gameID = stream->readUint16("gameID"); - stream->readLeaveSection(); -} - - - -std::string NetSetGameInRouter::format() const -{ - std::ostringstream s; - s<<"NetSetGameInRouter("<<"gameID="<(rhs); - if(r.gameID == gameID) - return true; - } - return false; -} - - -Uint16 NetSetGameInRouter::getGameID() const -{ - return gameID; -} - - - - -NetSendAfterJoinGameInformation::NetSendAfterJoinGameInformation() - : info() -{ - -} - - - -NetSendAfterJoinGameInformation::NetSendAfterJoinGameInformation(YOGAfterJoinGameInformation info) - :info(info) -{ -} - - - -Uint8 NetSendAfterJoinGameInformation::getMessageType() const -{ - return MNetSendAfterJoinGameInformation; -} - - - -void NetSendAfterJoinGameInformation::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendAfterJoinGameInformation"); - info.encodeData(stream); - stream->writeLeaveSection(); -} - - - -void NetSendAfterJoinGameInformation::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendAfterJoinGameInformation"); - info.decodeData(stream); - stream->readLeaveSection(); -} - - - -std::string NetSendAfterJoinGameInformation::format() const -{ - std::ostringstream s; - s<<"NetSendAfterJoinGameInformation("<<"="<<"; "<<")"; - return s.str(); -} - - - -bool NetSendAfterJoinGameInformation::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetSendAfterJoinGameInformation)) - { - const NetSendAfterJoinGameInformation& r = dynamic_cast(rhs); - if(r.info == info) - return true; - } - return false; -} - - -YOGAfterJoinGameInformation NetSendAfterJoinGameInformation::getAfterJoinGameInformation() const -{ - return info; -} - - - - -NetRouterAdministratorLogin::NetRouterAdministratorLogin() - : password() -{ - -} - - - -NetRouterAdministratorLogin::NetRouterAdministratorLogin(std::string password) - :password(password) -{ -} - - - -Uint8 NetRouterAdministratorLogin::getMessageType() const -{ - return MNetRouterAdministratorLogin; -} - - - -void NetRouterAdministratorLogin::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRouterAdministratorLogin"); - stream->writeText(password, "password"); - stream->writeLeaveSection(); -} - - - -void NetRouterAdministratorLogin::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRouterAdministratorLogin"); - password = stream->readText("password"); - stream->readLeaveSection(); -} - - - -std::string NetRouterAdministratorLogin::format() const -{ - std::ostringstream s; - s<<"NetRouterAdministratorLogin("<<"password="<(rhs); - if(r.password == password) - return true; - } - return false; -} - - -std::string NetRouterAdministratorLogin::getPassword() const -{ - return password; -} - - - - -NetRouterAdministratorSendCommand::NetRouterAdministratorSendCommand() - : command("") -{ - -} - - - -NetRouterAdministratorSendCommand::NetRouterAdministratorSendCommand(std::string command) - :command(command) -{ -} - - - -Uint8 NetRouterAdministratorSendCommand::getMessageType() const -{ - return MNetRouterAdministratorSendCommand; -} - - - -void NetRouterAdministratorSendCommand::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRouterAdministratorSendCommand"); - stream->writeText(command, "command"); - stream->writeLeaveSection(); -} - - - -void NetRouterAdministratorSendCommand::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRouterAdministratorSendCommand"); - command = stream->readText("command"); - stream->readLeaveSection(); -} - - - -std::string NetRouterAdministratorSendCommand::format() const -{ - std::ostringstream s; - s<<"NetRouterAdministratorSendCommand("<<"command="<(rhs); - if(r.command == command) - return true; - } - return false; -} - - -std::string NetRouterAdministratorSendCommand::getCommand() const -{ - return command; -} - - - - -NetRouterAdministratorSendText::NetRouterAdministratorSendText() - : text("") -{ - -} - - - -NetRouterAdministratorSendText::NetRouterAdministratorSendText(std::string text) - :text(text) -{ -} - - - -Uint8 NetRouterAdministratorSendText::getMessageType() const -{ - return MNetRouterAdministratorSendText; -} - - - -void NetRouterAdministratorSendText::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRouterAdministratorSendText"); - stream->writeText(text, "text"); - stream->writeLeaveSection(); -} - - - -void NetRouterAdministratorSendText::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRouterAdministratorSendText"); - text = stream->readText("text"); - stream->readLeaveSection(); -} - - - -std::string NetRouterAdministratorSendText::format() const -{ - std::ostringstream s; - s<<"NetRouterAdministratorSendText("<<"text="<(rhs); - if(r.text == text) - return true; - } - return false; -} - - -std::string NetRouterAdministratorSendText::getText() const -{ - return text; -} - - - - -NetRouterAdministratorLoginAccepted::NetRouterAdministratorLoginAccepted() -{ - -} - - - -Uint8 NetRouterAdministratorLoginAccepted::getMessageType() const -{ - return MNetRouterAdministratorLoginAccepted; -} - - - -void NetRouterAdministratorLoginAccepted::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRouterAdministratorLoginAccepted"); - stream->writeLeaveSection(); -} - - - -void NetRouterAdministratorLoginAccepted::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRouterAdministratorLoginAccepted"); - stream->readLeaveSection(); -} - - - -std::string NetRouterAdministratorLoginAccepted::format() const -{ - std::ostringstream s; - s<<"NetRouterAdministratorLoginAccepted()"; - return s.str(); -} - - - -bool NetRouterAdministratorLoginAccepted::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetRouterAdministratorLoginAccepted)) - { - //const NetRouterAdministratorLoginAccepted& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetRouterAdministratorLoginRefused::NetRouterAdministratorLoginRefused() - : reason(YOGRouterLoginUnknown) -{ - -} - - - -NetRouterAdministratorLoginRefused::NetRouterAdministratorLoginRefused(YOGRouterAdministratorLoginRefusalReason reason) - :reason(reason) -{ -} - - - -Uint8 NetRouterAdministratorLoginRefused::getMessageType() const -{ - return MNetRouterAdministratorLoginRefused; -} - - - -void NetRouterAdministratorLoginRefused::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRouterAdministratorLoginRefused"); - stream->writeUint8(reason, "reason"); - stream->writeLeaveSection(); -} - - - -void NetRouterAdministratorLoginRefused::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRouterAdministratorLoginRefused"); - reason = static_cast(stream->readUint8("reason")); - stream->readLeaveSection(); -} - - - -std::string NetRouterAdministratorLoginRefused::format() const -{ - std::ostringstream s; - s<<"NetRouterAdministratorLoginRefused("<<"reason="<(rhs); - if(r.reason == reason) - return true; - } - return false; -} - - -YOGRouterAdministratorLoginRefusalReason NetRouterAdministratorLoginRefused::getReason() const -{ - return reason; -} - - - - -NetDownloadableMapInfos::NetDownloadableMapInfos() - : maps() -{ - -} - - - -NetDownloadableMapInfos::NetDownloadableMapInfos(std::vector maps) - :maps(maps) -{ -} - - - -Uint8 NetDownloadableMapInfos::getMessageType() const -{ - return MNetDownloadableMapInfos; -} - - - -void NetDownloadableMapInfos::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetDownloadableMapInfos"); - stream->writeEnterSection("maps"); - stream->writeUint32(maps.size(), "size"); - for(unsigned int i=0; iwriteEnterSection(i); - maps[i].encodeData(stream); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - stream->writeLeaveSection(); -} - - - -void NetDownloadableMapInfos::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetDownloadableMapInfos"); - stream->readEnterSection("maps"); - Uint32 size = stream->readUint32("maps"); - maps.resize(size); - for(unsigned int i=0; ireadEnterSection(i); - maps[i].decodeData(stream, VERSION_MINOR); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - stream->readLeaveSection(); -} - - - -std::string NetDownloadableMapInfos::format() const -{ - std::ostringstream s; - s<<"NetDownloadableMapInfos(maps.size()="<(rhs); - if(r.maps == maps) - return true; - } - return false; -} - - -std::vector NetDownloadableMapInfos::getMaps() const -{ - return maps; -} - - - - -NetRequestDownloadableMapList::NetRequestDownloadableMapList() -{ - -} - - - -Uint8 NetRequestDownloadableMapList::getMessageType() const -{ - return MNetRequestDownloadableMapList; -} - - - -void NetRequestDownloadableMapList::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRequestDownloadableMapList"); - stream->writeLeaveSection(); -} - - - -void NetRequestDownloadableMapList::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRequestDownloadableMapList"); - stream->readLeaveSection(); -} - - - -std::string NetRequestDownloadableMapList::format() const -{ - std::ostringstream s; - s<<"NetRequestDownloadableMapList()"; - return s.str(); -} - - - -bool NetRequestDownloadableMapList::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetRequestDownloadableMapList)) - { - //const NetRequestDownloadableMapList& r = dynamic_cast(rhs); - return true; - } - return false; -} - - - -NetRequestMapUpload::NetRequestMapUpload() - : mapInfo() -{ - -} - - - -NetRequestMapUpload::NetRequestMapUpload(YOGDownloadableMapInfo mapInfo) - :mapInfo(mapInfo) -{ -} - - - -Uint8 NetRequestMapUpload::getMessageType() const -{ - return MNetRequestMapUpload; -} - - - -void NetRequestMapUpload::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRequestMapUpload"); - mapInfo.encodeData(stream); - stream->writeLeaveSection(); -} - - - -void NetRequestMapUpload::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRequestMapUpload"); - mapInfo.decodeData(stream, VERSION_MINOR); - stream->readLeaveSection(); -} - - - -std::string NetRequestMapUpload::format() const -{ - std::ostringstream s; - s<<"NetRequestMapUpload("<<"""="<<""<<"; "<<")"; - return s.str(); -} - - - -bool NetRequestMapUpload::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(NetRequestMapUpload)) - { - const NetRequestMapUpload& r = dynamic_cast(rhs); - if(r.mapInfo == mapInfo) - return true; - } - return false; -} - - -YOGDownloadableMapInfo NetRequestMapUpload::getMapInfo() const -{ - return mapInfo; -} - - - - -NetAcceptMapUpload::NetAcceptMapUpload() - : fileID(0) -{ - -} - - - -NetAcceptMapUpload::NetAcceptMapUpload(Uint16 fileID) - :fileID(fileID) -{ -} - - - -Uint8 NetAcceptMapUpload::getMessageType() const -{ - return MNetAcceptMapUpload; -} - - - -void NetAcceptMapUpload::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetAcceptMapUpload"); - stream->writeUint16(fileID, "fileID"); - stream->writeLeaveSection(); -} - - - -void NetAcceptMapUpload::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetAcceptMapUpload"); - fileID = stream->readUint16("fileID"); - stream->readLeaveSection(); -} - - - -std::string NetAcceptMapUpload::format() const -{ - std::ostringstream s; - s<<"NetAcceptMapUpload("<<"fileID="<(rhs); - if(r.fileID == fileID) - return true; - } - return false; -} - - -Uint16 NetAcceptMapUpload::getFileID() const -{ - return fileID; -} - - - - -NetRefuseMapUpload::NetRefuseMapUpload() - : reason(YOGMapUploadReasonUnknown) -{ - -} - - - -NetRefuseMapUpload::NetRefuseMapUpload(YOGMapUploadRefusalReason reason) - :reason(reason) -{ -} - - - -Uint8 NetRefuseMapUpload::getMessageType() const -{ - return MNetRefuseMapUpload; -} - - - -void NetRefuseMapUpload::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRefuseMapUpload"); - stream->writeUint8(static_cast(reason), "reason"); - stream->writeLeaveSection(); -} - - - -void NetRefuseMapUpload::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRefuseMapUpload"); - reason = static_cast(stream->readUint8("reason")); - stream->readLeaveSection(); -} - - - -std::string NetRefuseMapUpload::format() const -{ - std::ostringstream s; - s<<"NetRefuseMapUpload("<<"reason="<(rhs); - if(r.reason == reason) - return true; - } - return false; -} - - -YOGMapUploadRefusalReason NetRefuseMapUpload::getReason() const -{ - return reason; -} - - - - -NetCancelSendingFile::NetCancelSendingFile() - : fileID(0) -{ - -} - - - -NetCancelSendingFile::NetCancelSendingFile(Uint16 fileID) - :fileID(fileID) -{ -} - - - -Uint8 NetCancelSendingFile::getMessageType() const -{ - return MNetCancelSendingFile; -} - - - -void NetCancelSendingFile::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetCancelSendingFile"); - stream->writeUint16(fileID, "fileID"); - stream->writeLeaveSection(); -} - - - -void NetCancelSendingFile::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetCancelSendingFile"); - fileID = stream->readUint16("fileID"); - stream->readLeaveSection(); -} - - - -std::string NetCancelSendingFile::format() const -{ - std::ostringstream s; - s<<"NetCancelSendingFile("<<"fileID="<(rhs); - if(r.fileID == fileID) - return true; - } - return false; -} - - -Uint16 NetCancelSendingFile::getFileID() const -{ - return fileID; -} - - - - -NetCancelRecievingFile::NetCancelRecievingFile() - : fileID(0) -{ - -} - - - -NetCancelRecievingFile::NetCancelRecievingFile(Uint16 fileID) - :fileID(fileID) -{ -} - - - -Uint8 NetCancelRecievingFile::getMessageType() const -{ - return MNetCancelRecievingFile; -} - - - -void NetCancelRecievingFile::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetCancelRecievingFile"); - stream->writeUint16(fileID, "fileID"); - stream->writeLeaveSection(); -} - - - -void NetCancelRecievingFile::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetCancelRecievingFile"); - fileID = stream->readUint16("fileID"); - stream->readLeaveSection(); -} - - - -std::string NetCancelRecievingFile::format() const -{ - std::ostringstream s; - s<<"NetCancelRecievingFile("<<"fileID="<(rhs); - if(r.fileID == fileID) - return true; - } - return false; -} - - -Uint16 NetCancelRecievingFile::getFileID() const -{ - return fileID; -} - - - - -NetRequestMapThumbnail::NetRequestMapThumbnail() - : mapID(0) -{ - -} - - - -NetRequestMapThumbnail::NetRequestMapThumbnail(Uint16 mapID) - : mapID(mapID) -{ -} - - - -Uint8 NetRequestMapThumbnail::getMessageType() const -{ - return MNetRequestMapThumbnail; -} - - - -void NetRequestMapThumbnail::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetRequestMapThumbnail"); - stream->writeUint16(mapID, "mapID"); - stream->writeLeaveSection(); -} - - - -void NetRequestMapThumbnail::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetRequestMapThumbnail"); - mapID = stream->readUint16("mapID"); - stream->readLeaveSection(); -} - - - -std::string NetRequestMapThumbnail::format() const -{ - std::ostringstream s; - s<<"NetRequestMapThumbnail("<<"mapID="<(rhs); - if(r.mapID == mapID) - return true; - } - return false; -} - - -Uint16 NetRequestMapThumbnail::getMapID() const -{ - return mapID; -} - - - - -NetSendMapThumbnail::NetSendMapThumbnail() - : mapID(0), thumbnail() -{ - -} - - - -NetSendMapThumbnail::NetSendMapThumbnail(Uint16 mapID, MapThumbnail thumbnail) - :mapID(mapID), thumbnail(thumbnail) -{ -} - - - -Uint8 NetSendMapThumbnail::getMessageType() const -{ - return MNetSendMapThumbnail; -} - - - -void NetSendMapThumbnail::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSendMapThumbnail"); - stream->writeUint16(mapID, "mapID"); - thumbnail.encodeData(stream); - stream->writeLeaveSection(); -} - - - -void NetSendMapThumbnail::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSendMapThumbnail"); - mapID = stream->readUint16("mapID"); - thumbnail.decodeData(stream, VERSION_MINOR); - stream->readLeaveSection(); -} - - - -std::string NetSendMapThumbnail::format() const -{ - std::ostringstream s; - s<<"NetSendMapThumbnail("<<"mapID="<(rhs); - if(r.mapID == mapID) - return true; - } - return false; -} - - -Uint16 NetSendMapThumbnail::getMapID() const -{ - return mapID; -} - - - -MapThumbnail NetSendMapThumbnail::getThumbnail() const -{ - return thumbnail; -} - - - - -NetSubmitRatingOnMap::NetSubmitRatingOnMap() - : mapID(0), rating(0) -{ - -} - - - -NetSubmitRatingOnMap::NetSubmitRatingOnMap(Uint16 mapID, Uint8 rating) - :mapID(mapID), rating(rating) -{ -} - - - -Uint8 NetSubmitRatingOnMap::getMessageType() const -{ - return MNetSubmitRatingOnMap; -} - - - -void NetSubmitRatingOnMap::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("NetSubmitRatingOnMap"); - stream->writeUint16(mapID, "mapID"); - stream->writeUint8(rating, "rating"); - stream->writeLeaveSection(); -} - - - -void NetSubmitRatingOnMap::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("NetSubmitRatingOnMap"); - mapID = stream->readUint16("mapID"); - rating = stream->readUint8("rating"); - stream->readLeaveSection(); -} - - - -std::string NetSubmitRatingOnMap::format() const -{ - std::ostringstream s; - s<<"NetSubmitRatingOnMap("<<"mapID="<(rhs); - if(r.mapID == mapID && r.rating == rating) - return true; - } - return false; -} - - -Uint16 NetSubmitRatingOnMap::getMapID() const -{ - return mapID; -} - - - -Uint8 NetSubmitRatingOnMap::getRating() const -{ - return rating; -} - - - -//append_code_position diff --git a/src/NetMessage.h b/src/NetMessage.h deleted file mode 100644 index 5855601a5..000000000 --- a/src/NetMessage.h +++ /dev/null @@ -1,2497 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __NetMessage_h -#define __NetMessage_h - -#include -#include "GameHeader.h" -#include "MapHeader.h" -#include "MapThumbnail.h" -#include "NetReteamingInformation.h" -#include "Order.h" -#include "Player.h" -#include "Stream.h" -#include -#include -#include "YOGConsts.h" -#include "YOGGameInfo.h" -#include "YOGDownloadableMapInfo.h" -#include "YOGMessage.h" -#include "YOGPlayerSessionInfo.h" -#include "YOGAfterJoinGameInformation.h" - - -using boost::shared_ptr; - -///This is the enum of message types -enum NetMessageType -{ - ///These must be kept in this order to maintain compatibility with future versions of glob2 - MNetAcceptRegistration, - MNetAttemptLogin, - MNetAttemptRegistration, - MNetDisconnect, - MNetLoginSuccessful, - MNetPing, - MNetPingReply, - MNetRefuseLogin, - MNetRefuseRegistration, - MNetSendClientInformation, - MNetSendServerInformation, - - //These are all glob2 version dependent and can be kept in any order - MNetAcknowledgeRouter, - MNetAddAI, - MNetAttemptJoinGame, - MNetChangePlayersTeam, - MNetCreateGame, - MNetCreateGameAccepted, - MNetCreateGameRefused, - MNetGameJoinAccepted, - MNetGameJoinRefused, - MNetIPIsBanned, - MNetKickPlayer, - MNetLeaveGame, - MNetNotReadyToLaunch, - MNetPlayerIsBanned, - MNetPlayerJoinsGame, - MNetReadyToLaunch, - MNetRefuseGameStart, - MNetRegisterRouter, - MNetRemoveAI, - MNetRequestGameStart, - MNetRequestFile, - MNetRouterAdministratorLogin, - MNetRouterAdministratorLoginAccepted, - MNetRouterAdministratorLoginRefused, - MNetRouterAdministratorSendCommand, - MNetRouterAdministratorSendText, - MNetSendAfterJoinGameInformation, - MNetSendFileChunk, - MNetSendFileInformation, - MNetSendGameHeader, - MNetSendGamePlayerInfo, - MNetSendGameResult, - MNetSendMapHeader, - MNetSendOrder, - MNetSendReteamingInformation, - MNetSendYOGMessage, - MNetSetGameInRouter, - MNetSetLatencyMode, - MNetStartGame, - MNetUpdateGameList, - MNetUpdatePlayerList, - MNetDownloadableMapInfos, - MNetRequestDownloadableMapList, - MNetRequestMapUpload, - MNetAcceptMapUpload, - MNetRefuseMapUpload, - MNetCancelSendingFile, - MNetCancelRecievingFile, - MNetRequestMapThumbnail, - MNetSendMapThumbnail, - MNetSubmitRatingOnMap, - //type_append_marker -}; - - -///This is bassically a message in the Net Engine. A Message has two parts, -///a type and a body. The NetMessage base class also has a static function -///that will read data in, and create the appropriette derived class. -class NetMessage -{ -public: - ///Virtual destructor for derived classes - virtual ~NetMessage() {} - - ///Returns the message type - virtual Uint8 getMessageType() const = 0; - - ///Reads the data, and returns a message containing the data. - ///The Message may be casted to its particular subclass, using - ///the getMessageType function and dynamic_cast - static shared_ptr getNetMessage(GAGCore::InputStream* stream); - - ///Encodes the data into its shrunken, serialized form. - virtual void encodeData(GAGCore::OutputStream* stream) const = 0; - - ///Decodes data from the serialized form. Returns true on success, false otherwise. - ///The first byte is the type from getMessageType, and can be safely ignored by - ///derived classes, as it is handled by getNetMessage - virtual void decodeData(GAGCore::InputStream* stream) = 0; - - ///This causes the message to be formated to a string, for debugging and/or logging - ///purposes - virtual std::string format() const = 0 ; - - ///Compares two NetMessages. All derived Messages must implement this by - ///first testing to see if NetMessage casts to the derived class, and then - ///comparing internal data. - virtual bool operator==(const NetMessage& rhs) const = 0; - ///This does not need to be overloaded, but can be for efficiency purposes. - virtual bool operator!=(const NetMessage& rhs) const; -}; - - - -///This message bassically wraps the Order class, meant to deliver an Order across a network. -class NetSendOrder : public NetMessage -{ -public: - ///Creates a NetSendOrder message with a NULL Order. - NetSendOrder(); - - ///Creates a NetSendOrder message with the provided Order. - ///This will assume ownership of the Order. - NetSendOrder(boost::shared_ptr newOrder); - - ///Changes the Order that NetSendOrder holds. This will - ///delete an Order that was already present. - void addOrder(boost::shared_ptr newOrder); - - ///Returns the Order that NetSendOrder holds - boost::shared_ptr getOrder(); - - ///Changes the Order that NetSendOrder holds - void changeOrder(boost::shared_ptr newOrder); - - ///Returns MNetSendOrder - Uint8 getMessageType() const; - - ///Encodes the data, wraps the encoding of the Order - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data, and reconstructs the Order. - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendOrder message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendOrder - bool operator==(const NetMessage& rhs) const; -private: - boost::shared_ptr order; -}; - - - -///This message sends local version information to the server -class NetSendClientInformation : public NetMessage -{ -public: - ///Creates a NetSendClientInformation message - NetSendClientInformation(); - - ///Returns MNetSendClientInformation - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendClientInformation message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendClientInformation - bool operator==(const NetMessage& rhs) const; - - ///Returns the net version - Uint16 getNetVersion() const; -private: - Uint16 netVersion; -}; - - - -///This message sends server information to the client. This includes -///login and game policies (for example anonymous / password required login), -///and the playerID for this connection -class NetSendServerInformation : public NetMessage -{ -public: - ///Creates a NetSendServerInformation message with the provided server information - NetSendServerInformation(YOGLoginPolicy loginPolicy, YOGGamePolicy gamePolicy, Uint16 playerID); - - ///Creates an empty NetSendServerInformation message - NetSendServerInformation(); - - ///Returns MNetSendServerInformation - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendServerInformation message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendServerInformation - bool operator==(const NetMessage& rhs) const; - - ///Returns the login policy - YOGLoginPolicy getLoginPolicy() const; - - ///Returns the game policy - YOGGamePolicy getGamePolicy() const; - - ///Returns the playerID - Uint16 getPlayerID() const; - -private: - YOGLoginPolicy loginPolicy; - YOGGamePolicy gamePolicy; - Uint16 playerID; -}; - - - -///This message sends login information (username and password) to the server. -class NetAttemptLogin : public NetMessage -{ -public: - ///Creates a NetAttemptLogin message with the given username and password - NetAttemptLogin(const std::string& username, const std::string& password); - - ///Creates an empty NetAttemptLogin message - NetAttemptLogin(); - - ///Returns MNetAttemptLogin - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetAttemptLogin message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetAttemptLogin - bool operator==(const NetMessage& rhs) const; - - ///Returns the username - const std::string& getUsername() const; - - ///Returns the password - const std::string& getPassword() const; - -private: - std::string username; - std::string password; -}; - - - -///This message informs the client its login was successfull -class NetLoginSuccessful : public NetMessage -{ -public: - ///Creates a NetLoginSuccessful message - NetLoginSuccessful(); - - ///Returns MNetLoginSuccessful - Uint8 getMessageType() const; - - ///Encodes the data, however, this message has no data, it must be atleast one byte. - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data. - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetLoginSuccessful message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetLoginSuccessful - bool operator==(const NetMessage& rhs) const; -}; - - - -///This message informs the client its login was refused. It carries with it the reason why. -class NetRefuseLogin : public NetMessage -{ -public: - ///Creates an empty NetRefuseLogin message - NetRefuseLogin(); - - ///Creates a NetRefuseLogin message with the given reason - NetRefuseLogin(YOGLoginState reason); - - ///Returns MNetRefuseLogin - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data. - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRefuseLogin message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRefuseLogin - bool operator==(const NetMessage& rhs) const; - - ///Returns the reason why this login was refused - YOGLoginState getRefusalReason() const; -private: - YOGLoginState reason; -}; - - -///This message updates the users pre-joining game list. Bassically, it takes what the user already has -///(the server should have a copy), and the new server game list, and sends a message with the differences -///between the two, and reassembles the completed list at the other end. This both reduces bandwidth, -///and eliminates the need for seperate GameAdded, GameRemoved, and GameChanged messages just to keep -///a connected user updated. For this to work, the server and the client should have synced versions -///of what the list is, and this message will just pass updates. -class NetUpdateGameList : public NetMessage -{ -public: - ///Creates an empty NetUpdateGameList message. - NetUpdateGameList(); - - ///Computes and stores the differences between the two provided lists of YOGGameInfo objects. - ///The container can be any container with a ::const_iterator, a begin(), and an end(), for - ///iterating over the ranges. std containers are most common. For this to work, the original - ///list has to be the same as the one on the client (while they don't have to be the same - ///type of container), they must be in sync. - template void updateDifferences(const container& original, const container& updated); - - ///Returns MNetUpdateGameList - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetUpdateGameList message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetUpdateGameList - bool operator==(const NetMessage& rhs) const; - - ///Applies the differences that this message has been given to the provided container. - ///The container must have the methods erase(iter), begin(), end(), and insert(iter, object) - template void applyDifferences(container& original) const; -private: - std::vector removedGames; - std::vector updatedGames; -}; - - -///NetDisconnect informs the server and/or client that the other is disconnecting -class NetDisconnect : public NetMessage -{ -public: - ///Creates a NetDisconnect message - NetDisconnect(); - - ///Returns MNetDisconnect - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetDisconnect message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetDisconnect - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetAttemptRegistration attempts to register the user -class NetAttemptRegistration : public NetMessage -{ -public: - ///Creates a NetAttemptRegistration message - NetAttemptRegistration(); - - ///Creates a NetAttemptRegistration message - NetAttemptRegistration(const std::string& username, const std::string& password); - - ///Returns MNetAttemptRegistration - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetAttemptRegistration message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetAttemptRegistration - bool operator==(const NetMessage& rhs) const; - - ///Returns the username - std::string getUsername() const; - - ///Returns the password - std::string getPassword() const; -private: - std::string username; - std::string password; -}; - - - - -///NetAcceptRegistration informs the user that their registration information was accepted. -class NetAcceptRegistration : public NetMessage -{ -public: - ///Creates a NetAcceptRegistration message - NetAcceptRegistration(); - - ///Returns MNetAcceptRegistration - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetAcceptRegistration message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetAcceptRegistration - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetRefuseRegistration informs the user that their registration attemp was denied -class NetRefuseRegistration : public NetMessage -{ -public: - ///Creates a NetRefuseRegistration message - NetRefuseRegistration(); - - ///Creates a NetRefuseRegistration message with the given reason - NetRefuseRegistration(YOGLoginState reason); - - ///Returns MNetRefuseRegistration - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRefuseRegistration message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRefuseRegistration - bool operator==(const NetMessage& rhs) const; - - ///Returns the reason why this registration was refused - YOGLoginState getRefusalReason() const; -private: - YOGLoginState reason; -}; - - - - -///NetUpdatePlayerList -class NetUpdatePlayerList : public NetMessage -{ -public: - ///Creates a NetUpdatePlayerList message - NetUpdatePlayerList(); - - ///This computes the differences between the two lists of players. These can be of any container, - ///so long as they store YOGPlayerSessionInfo - template void updateDifferences(const container& original, const container& updated); - - ///Returns MNetUpdatePlayerList - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetUpdatePlayerList message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetUpdatePlayerList - bool operator==(const NetMessage& rhs) const; - - ///This will apply the recorded differences to the given container - template void applyDifferences(container& original) const; - -private: - std::vector removedPlayers; - std::vector updatedPlayers; -}; - - - - -///NetCreateGame creates a new game on the server. -class NetCreateGame : public NetMessage -{ -public: - ///Creates a NetCreateGame message - NetCreateGame(const std::string& gameName); - - ///Creates a NetCreateGame message - NetCreateGame(); - - ///Returns MNetCreateGame - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetCreateGame message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetCreateGame - bool operator==(const NetMessage& rhs) const; - - ///Returns the game name - const std::string& getGameName() const; -private: - std::string gameName; -}; - - - - -///NetAttemptJoinGame tries to join a game. In the future, games may be password private and require a password, -///and so attempts to join a game may not always be successful -class NetAttemptJoinGame : public NetMessage -{ -public: - ///Creates a NetAttemptJoinGame message - NetAttemptJoinGame(); - - ///Creates a NetAttemptJoinGame message - NetAttemptJoinGame(Uint16 gameID); - - ///Returns MNetAttemptJoinGame - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetAttemptJoinGame message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetAttemptJoinGame - bool operator==(const NetMessage& rhs) const; - - ///Returns the game ID - Uint16 getGameID() const; -private: - Uint16 gameID; -}; - - - - -///NetGameJoinAccepted means that a NetAttemptJoinGame was accepted and the player is now -///joined in the game. It comes with some information about the joined game -class NetGameJoinAccepted : public NetMessage -{ -public: - ///Creates a NetGameJoinAccepted message - NetGameJoinAccepted(); - - ///Creates a NetGameJoinAccepted message with the chat channel of the joined game - NetGameJoinAccepted(Uint32 chatChannel); - - ///Returns MNetGameJoinAccepted - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetGameJoinAccepted message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetGameJoinAccepted - bool operator==(const NetMessage& rhs) const; - - ///Returns the chat channel of the joined game - Uint32 getChatChannel() const; -private: - Uint32 chatChannel; -}; - - - - -///NetGameJoinRefused means that the attempt to join a game was denied. -class NetGameJoinRefused : public NetMessage -{ -public: - ///Creates a NetGameJoinRefused message - NetGameJoinRefused(YOGServerGameJoinRefusalReason reason); - - ///Creates a NetGameJoinRefused message - NetGameJoinRefused(); - - ///Returns MNetGameJoinRefused - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetGameJoinRefused message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetGameJoinRefused - bool operator==(const NetMessage& rhs) const; - - ///Returns the reason why the player could not join the game. - YOGServerGameJoinRefusalReason getRefusalReason() const; -private: - YOGServerGameJoinRefusalReason reason; -}; - - - - -///NetSendYOGMessage -class NetSendYOGMessage : public NetMessage -{ -public: - ///Creates a NetSendYOGMessage message - NetSendYOGMessage(Uint32 channel, boost::shared_ptr message); - - ///Creates a NetSendYOGMessage message - NetSendYOGMessage(); - - ///Returns MNetSendYOGMessage - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendYOGMessage message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendYOGMessage - bool operator==(const NetMessage& rhs) const; - - ///Returns the channel - Uint32 getChannel() const; - - ///Returns the YOG message - boost::shared_ptr getMessage() const; -private: - Uint32 channel; - boost::shared_ptr message; -}; - - - - -///NetSendMapHeader sends a map header to the server -class NetSendMapHeader : public NetMessage -{ -public: - ///Creates a NetSendMapHeader message - NetSendMapHeader(); - - ///Creates a NetSendMapHeader message - NetSendMapHeader(const MapHeader& mapHeader); - - ///Returns MNetSendMapHeader - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendMapHeader message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendMapHeader - bool operator==(const NetMessage& rhs) const; - - ///Returns the map header - const MapHeader& getMapHeader() const; -private: - MapHeader mapHeader; -}; - - - - -///Tells the player that their game creation was accepted, along with a bit of information about the newly created game -class NetCreateGameAccepted : public NetMessage -{ -public: - ///Creates a NetCreateGameAccepted message - NetCreateGameAccepted(); - - ///Creates a NetCreateGameAccepted message with the chat channel for the new game - NetCreateGameAccepted(Uint32 chatChannel, Uint16 gameID, const std::string& routerIP, Uint16 fileID); - - ///Returns MNetCreateGameAccepted - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetCreateGameAccepted message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetCreateGameAccepted - bool operator==(const NetMessage& rhs) const; - - ///Retrieves the chat channel for the new game - Uint32 getChatChannel() const; - - ///Retrivees the game id for the new game - Uint16 getGameID() const; - - ///Retrieves the game-router ip address - const std::string getGameRouterIP() const; - - ///Retrieves the fileID for this games map - Uint16 getFileID() const; -private: - Uint32 chatChannel; - Uint16 gameID; - std::string routerIP; - Uint16 fileID; -}; - - - - -///NetCreateGameRefused -class NetCreateGameRefused : public NetMessage -{ -public: - ///Creates a NetCreateGameRefused message - NetCreateGameRefused(); - - ///Creates a NetCreateGameRefused message - NetCreateGameRefused(YOGServerGameCreateRefusalReason reason); - - ///Returns MNetCreateGameRefused - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetCreateGameRefused message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetCreateGameRefused - bool operator==(const NetMessage& rhs) const; - - ///Returns the reason why the player could not join the game. - YOGServerGameCreateRefusalReason getRefusalReason() const; -private: - YOGServerGameCreateRefusalReason reason; -}; - - - - -///NetSendGameHeader, sends the game header, but without any player information. Player information is sent in -///NetSendGamePlayerInfo -class NetSendGameHeader : public NetMessage -{ -public: - ///Creates a NetSendGameHeader message - NetSendGameHeader(); - - ///Creates a NetSendGameHeader message - NetSendGameHeader(const GameHeader& gameHeader); - - ///Returns MNetSendGameHeader - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendGameHeader message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendGameHeader - bool operator==(const NetMessage& rhs) const; - - ///Downloads information into the given game header - void downloadToGameHeader(GameHeader& header); -private: - GameHeader gameHeader; -}; - - - - -///NetSendGamePlayerInfo. This sends the BasePlayer portion of GameHeader -class NetSendGamePlayerInfo : public NetMessage -{ -public: - ///Creates a NetSendGamePlayerInfo message - NetSendGamePlayerInfo(); - - ///Creates a NetSendGamePlayerInfo message. - NetSendGamePlayerInfo(GameHeader& header); - - ///Returns MNetSendGamePlayerInfo - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendGamePlayerInfo message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendGamePlayerInfo - bool operator==(const NetMessage& rhs) const; - - ///Downloads all of the player info to this game header - void downloadToGameHeader(GameHeader& header); -private: - GameHeader gameHeader; - -}; - - - - -///NetStartGame -class NetStartGame : public NetMessage -{ -public: - ///Creates a NetStartGame message - NetStartGame(); - - ///Returns MNetStartGame - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetStartGame message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetStartGame - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetRequestFile -class NetRequestFile : public NetMessage -{ -public: - ///Creates a NetRequestFile message - NetRequestFile(); - - ///Creates a NetRequestFile message for the given fileID - NetRequestFile(Uint16 fileID); - - ///Returns MNetRequestFile - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRequestFile message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRequestFile - bool operator==(const NetMessage& rhs) const; - - ///Returns the fileID of the file being requested - Uint16 getFileID(); -private: - Uint16 fileID; -}; - - - - -///NetSendFileInformation -class NetSendFileInformation : public NetMessage -{ -public: - ///Creates a NetSendFileInformation message - NetSendFileInformation(); - - ///Creates a NetSendFileInformation message with the given file size for the given fileID - NetSendFileInformation(Uint32 filesize, Uint16 fileID); - - ///Returns MNetSendFileInformation - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendFileInformation message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendFileInformation - bool operator==(const NetMessage& rhs) const; - - ///Returns the file size - Uint32 getFileSize() const; - - ///Returns the file size - Uint16 getFileID() const; -private: - Uint32 size; - Uint16 fileID; -}; - - - - -///NetSendFileChunk -class NetSendFileChunk : public NetMessage -{ -public: - ///Creates a NetSendFileChunk message - NetSendFileChunk(); - - ///Creates a NetSendFileChunk message to read off of the given stream, - ///either untill the stream ends or the chunk size limit is reached - NetSendFileChunk(boost::shared_ptr stream, Uint16 fileID); - - ///Returns MNetSendFileChunk - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendFileChunk message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendFileChunk - bool operator==(const NetMessage& rhs) const; - - ///Provides the buffer of data - const Uint8* getBuffer() const; - - ///Returns the chunk size - Uint32 getChunkSize() const; - - ///Returns the fileID - Uint16 getFileID() const; -private: - Uint32 size; - Uint8 data[4096]; - Uint16 fileID; -}; - - - - -///NetKickPlayer -class NetKickPlayer : public NetMessage -{ -public: - ///Creates a NetKickPlayer message - NetKickPlayer(); - - ///Creates a NetKickPlayer message - NetKickPlayer(Uint16 playerID, YOGKickReason reason); - - ///Returns MNetKickPlayer - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetKickPlayer message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetKickPlayer - bool operator==(const NetMessage& rhs) const; - - ///Returns the playerID - Uint16 getPlayerID(); - - ///Returns the reason - YOGKickReason getReason(); -private: - Uint16 playerID; - YOGKickReason reason; -}; - - - - -///NetLeaveGame -class NetLeaveGame : public NetMessage -{ -public: - ///Creates a NetLeaveGame message - NetLeaveGame(); - - ///Returns MNetLeaveGame - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetLeaveGame message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetLeaveGame - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetReadyToLaunch -class NetReadyToLaunch : public NetMessage -{ -public: - ///Creates a NetReadyToLaunch message - NetReadyToLaunch(); - - ///Creates a NetReadyToLaunch message - NetReadyToLaunch(Uint16 playerID); - - ///Returns MNetReadyToLaunch - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetReadyToLaunch message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetReadyToLaunch - bool operator==(const NetMessage& rhs) const; - - ///Retrieves playerID - Uint16 getPlayerID() const; -private: - Uint16 playerID; -}; - - - - -///NetNotReadyToLaunch -class NetNotReadyToLaunch : public NetMessage -{ -public: - ///Creates a NetNotReadyToLaunch message - NetNotReadyToLaunch(); - - ///Creates a NetNotReadyToLaunch message - NetNotReadyToLaunch(Uint16 playerID); - - ///Returns MNetNotReadyToLaunch - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetNotReadyToLaunch message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetNotReadyToLaunch - bool operator==(const NetMessage& rhs) const; - - ///Retrieves playerID - Uint16 getPlayerID() const; -private: -private: - Uint16 playerID; -}; - - - - -///NetRemoveAI -class NetRemoveAI : public NetMessage -{ -public: - ///Creates a NetRemoveAI message - NetRemoveAI(); - - ///Creates a NetRemoveAI message - NetRemoveAI(Uint8 playerNum); - - ///Returns MNetRemoveAI - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRemoveAI message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRemoveAI - bool operator==(const NetMessage& rhs) const; - - ///Retrieves playerNum - Uint8 getPlayerNumber() const; -private: -private: - Uint8 playerNum; -}; - - - - -///NetChangePlayersTeam -class NetChangePlayersTeam : public NetMessage -{ -public: - ///Creates a NetChangePlayersTeam message - NetChangePlayersTeam(); - - ///Creates a NetChangePlayersTeam message - NetChangePlayersTeam(Uint8 player, Uint8 team); - - ///Returns MNetChangePlayersTeam - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetChangePlayersTeam message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetChangePlayersTeam - bool operator==(const NetMessage& rhs) const; - - ///Retrieves player - Uint8 getPlayer() const; - - ///Retrieves team - Uint8 getTeam() const; -private: -private: - Uint8 player; - Uint8 team; -}; - - - - -///NetRequestGameStart -class NetRequestGameStart : public NetMessage -{ -public: - ///Creates a NetRequestGameStart message - NetRequestGameStart(); - - ///Returns MNetRequestGameStart - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRequestGameStart message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRequestGameStart - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetRefuseGameStart -class NetRefuseGameStart : public NetMessage -{ -public: - ///Creates a NetRefuseGameStart message - NetRefuseGameStart(); - - ///Creates a NetRefuseGameStart message - NetRefuseGameStart(YOGServerGameStartRefusalReason refusalReason); - - ///Returns MNetRefuseGameStart - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRefuseGameStart message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRefuseGameStart - bool operator==(const NetMessage& rhs) const; - - ///Retrieves refusalReason - YOGServerGameStartRefusalReason getRefusalReason() const; -private: -private: - YOGServerGameStartRefusalReason refusalReason; -}; - - - - -///NetPing -class NetPing : public NetMessage -{ -public: - ///Creates a NetPing message - NetPing(); - - ///Returns MNetPing - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetPing message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetPing - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetPingReply -class NetPingReply : public NetMessage -{ -public: - ///Creates a NetPingReply message - NetPingReply(); - - ///Returns MNetPingReply - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetPingReply message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetPingReply - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetSetLatencyMode -class NetSetLatencyMode : public NetMessage -{ -public: - ///Creates a NetSetLatencyMode message - NetSetLatencyMode(); - - ///Creates a NetSetLatencyMode message - NetSetLatencyMode(Uint8 latencyAdjustment); - - ///Returns MNetSetLatencyMode - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSetLatencyMode message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSetLatencyMode - bool operator==(const NetMessage& rhs) const; - - ///Retrieves latencyAdjustment - Uint8 getLatencyAdjustment() const; -private: -private: - Uint8 latencyAdjustment; -}; - - - - -///NetPlayerJoinsGame -class NetPlayerJoinsGame : public NetMessage -{ -public: - ///Creates a NetPlayerJoinsGame message - NetPlayerJoinsGame(); - - ///Creates a NetPlayerJoinsGame message - NetPlayerJoinsGame(Uint16 playerID, std::string playerName); - - ///Returns MNetPlayerJoinsGame - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetPlayerJoinsGame message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetPlayerJoinsGame - bool operator==(const NetMessage& rhs) const; - - ///Retrieves playerID - Uint16 getPlayerID() const; - - ///Retrieves playerName - std::string getPlayerName() const; -private: -private: - Uint16 playerID; - std::string playerName; -}; - - - - -///NetAddAI -class NetAddAI : public NetMessage -{ -public: - ///Creates a NetAddAI message - NetAddAI(); - - ///Creates a NetAddAI message - NetAddAI(Uint8 type); - - ///Returns MNetAddAI - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetAddAI message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetAddAI - bool operator==(const NetMessage& rhs) const; - - ///Retrieves type - Uint8 getType() const; -private: -private: - Uint8 type; -}; - - - - -///NetSendReteamingInformation -class NetSendReteamingInformation : public NetMessage -{ -public: - ///Creates a NetSendReteamingInformation message - NetSendReteamingInformation(); - - ///Creates a NetSendReteamingInformation message - NetSendReteamingInformation(NetReteamingInformation reteamingInfo); - - ///Returns MNetSendReteamingInformation - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendReteamingInformation message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendReteamingInformation - bool operator==(const NetMessage& rhs) const; - - ///Retrieves reteamingInfo - NetReteamingInformation getReteamingInfo() const; -private: -private: - NetReteamingInformation reteamingInfo; -}; - - - - -///NetSendGameResult -class NetSendGameResult : public NetMessage -{ -public: - ///Creates a NetSendGameResult message - NetSendGameResult(); - - ///Creates a NetSendGameResult message - NetSendGameResult(YOGGameResult result); - - ///Returns MNetSendGameResult - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendGameResult message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendGameResult - bool operator==(const NetMessage& rhs) const; - - ///Retrieves result - YOGGameResult getGameResult() const; -private: -private: - YOGGameResult result; -}; - - - - -///NetPlayerIsBanned this bassically tells the client that their username was banned by the administrators -class NetPlayerIsBanned : public NetMessage -{ -public: - ///Creates a NetPlayerIsBanned message - NetPlayerIsBanned(); - - ///Returns MNetPlayerIsBanned - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetPlayerIsBanned message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetPlayerIsBanned - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetIPIsBanned -class NetIPIsBanned : public NetMessage -{ -public: - ///Creates a NetIPIsBanned message - NetIPIsBanned(); - - ///Returns MNetIPIsBanned - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetIPIsBanned message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetIPIsBanned - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetRegisterRouter -class NetRegisterRouter : public NetMessage -{ -public: - ///Creates a NetRegisterRouter message - NetRegisterRouter(); - - ///Returns MNetRegisterRouter - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRegisterRouter message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRegisterRouter - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetAcknowledgeRouter -class NetAcknowledgeRouter : public NetMessage -{ -public: - ///Creates a NetAcknowledgeRouter message - NetAcknowledgeRouter(); - - ///Returns MNetAcknowledgeRouter - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetAcknowledgeRouter message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetAcknowledgeRouter - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetSetGameInRouter -class NetSetGameInRouter : public NetMessage -{ -public: - ///Creates a NetSetGameInRouter message - NetSetGameInRouter(); - - ///Creates a NetSetGameInRouter message - NetSetGameInRouter(Uint16 gameID); - - ///Returns MNetSetGameInRouter - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSetGameInRouter message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSetGameInRouter - bool operator==(const NetMessage& rhs) const; - - ///Retrieves gameID - Uint16 getGameID() const; -private: -private: - Uint16 gameID; -}; - - - - -///NetSendAfterJoinGameInformation -class NetSendAfterJoinGameInformation : public NetMessage -{ -public: - ///Creates a NetSendAfterJoinGameInformation message - NetSendAfterJoinGameInformation(); - - ///Creates a NetSendAfterJoinGameInformation message - NetSendAfterJoinGameInformation(YOGAfterJoinGameInformation info); - - ///Returns MNetSendAfterJoinGameInformation - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendAfterJoinGameInformation message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendAfterJoinGameInformation - bool operator==(const NetMessage& rhs) const; - - ///Retrieves info - YOGAfterJoinGameInformation getAfterJoinGameInformation() const; -private: -private: - YOGAfterJoinGameInformation info; -}; - - - - -///NetRouterAdministratorLogin -class NetRouterAdministratorLogin : public NetMessage -{ -public: - ///Creates a NetRouterAdministratorLogin message - NetRouterAdministratorLogin(); - - ///Creates a NetRouterAdministratorLogin message - NetRouterAdministratorLogin(std::string password); - - ///Returns MNetRouterAdministratorLogin - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRouterAdministratorLogin message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRouterAdministratorLogin - bool operator==(const NetMessage& rhs) const; - - ///Retrieves password - std::string getPassword() const; -private: -private: - std::string password; -}; - - - - -///NetRouterAdministratorSendCommand -class NetRouterAdministratorSendCommand : public NetMessage -{ -public: - ///Creates a NetRouterAdministratorSendCommand message - NetRouterAdministratorSendCommand(); - - ///Creates a NetRouterAdministratorSendCommand message - NetRouterAdministratorSendCommand(std::string command); - - ///Returns MNetRouterAdministratorSendCommand - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRouterAdministratorSendCommand message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRouterAdministratorSendCommand - bool operator==(const NetMessage& rhs) const; - - ///Retrieves command - std::string getCommand() const; -private: -private: - std::string command; -}; - - - - -///NetRouterAdministratorSendText -class NetRouterAdministratorSendText : public NetMessage -{ -public: - ///Creates a NetRouterAdministratorSendText message - NetRouterAdministratorSendText(); - - ///Creates a NetRouterAdministratorSendText message - NetRouterAdministratorSendText(std::string text); - - ///Returns MNetRouterAdministratorSendText - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRouterAdministratorSendText message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRouterAdministratorSendText - bool operator==(const NetMessage& rhs) const; - - ///Retrieves text - std::string getText() const; -private: -private: - std::string text; -}; - - - - -///NetRouterAdministratorLoginAccepted -class NetRouterAdministratorLoginAccepted : public NetMessage -{ -public: - ///Creates a NetRouterAdministratorLoginAccepted message - NetRouterAdministratorLoginAccepted(); - - ///Returns MNetRouterAdministratorLoginAccepted - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRouterAdministratorLoginAccepted message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRouterAdministratorLoginAccepted - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetRouterAdministratorLoginRefused -class NetRouterAdministratorLoginRefused : public NetMessage -{ -public: - ///Creates a NetRouterAdministratorLoginRefused message - NetRouterAdministratorLoginRefused(); - - ///Creates a NetRouterAdministratorLoginRefused message - NetRouterAdministratorLoginRefused(YOGRouterAdministratorLoginRefusalReason reason); - - ///Returns MNetRouterAdministratorLoginRefused - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRouterAdministratorLoginRefused message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRouterAdministratorLoginRefused - bool operator==(const NetMessage& rhs) const; - - ///Retrieves reason - YOGRouterAdministratorLoginRefusalReason getReason() const; -private: -private: - YOGRouterAdministratorLoginRefusalReason reason; -}; - - - - -///NetDownloadableMapInfos -class NetDownloadableMapInfos : public NetMessage -{ -public: - ///Creates a NetDownloadableMapInfos message - NetDownloadableMapInfos(); - - ///Creates a NetDownloadableMapInfos message - NetDownloadableMapInfos(std::vector maps); - - ///Returns MNetDownloadableMapInfos - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetDownloadableMapInfos message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetDownloadableMapInfos - bool operator==(const NetMessage& rhs) const; - - ///Retrieves maps - std::vector getMaps() const; -private: -private: - std::vector maps; -}; - - - - -///NetRequestDownloadableMapList -class NetRequestDownloadableMapList : public NetMessage -{ -public: - ///Creates a NetRequestDownloadableMapList message - NetRequestDownloadableMapList(); - - ///Returns MNetRequestDownloadableMapList - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRequestDownloadableMapList message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRequestDownloadableMapList - bool operator==(const NetMessage& rhs) const; -}; - - - - -///NetRequestMapUpload -class NetRequestMapUpload : public NetMessage -{ -public: - ///Creates a NetRequestMapUpload message - NetRequestMapUpload(); - - ///Creates a NetRequestMapUpload message - NetRequestMapUpload(YOGDownloadableMapInfo mapInfo); - - ///Returns MNetRequestMapUpload - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRequestMapUpload message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRequestMapUpload - bool operator==(const NetMessage& rhs) const; - - ///Retrieves mapInfo - YOGDownloadableMapInfo getMapInfo() const; -private: -private: - YOGDownloadableMapInfo mapInfo; -}; - - - - -///NetAcceptMapUpload -class NetAcceptMapUpload : public NetMessage -{ -public: - ///Creates a NetAcceptMapUpload message - NetAcceptMapUpload(); - - ///Creates a NetAcceptMapUpload message - NetAcceptMapUpload(Uint16 fileID); - - ///Returns MNetAcceptMapUpload - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetAcceptMapUpload message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetAcceptMapUpload - bool operator==(const NetMessage& rhs) const; - - ///Retrieves fileID - Uint16 getFileID() const; -private: -private: - Uint16 fileID; -}; - - - - -///NetRefuseMapUpload -class NetRefuseMapUpload : public NetMessage -{ -public: - ///Creates a NetRefuseMapUpload message - NetRefuseMapUpload(); - - ///Creates a NetRefuseMapUpload message - NetRefuseMapUpload(YOGMapUploadRefusalReason reason); - - ///Returns MNetRefuseMapUpload - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRefuseMapUpload message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRefuseMapUpload - bool operator==(const NetMessage& rhs) const; - - ///Retrieves reason - YOGMapUploadRefusalReason getReason() const; -private: -private: - YOGMapUploadRefusalReason reason; -}; - - - - -///NetCancelSendingFile -class NetCancelSendingFile : public NetMessage -{ -public: - ///Creates a NetCancelSendingFile message - NetCancelSendingFile(); - - ///Creates a NetCancelSendingFile message - NetCancelSendingFile(Uint16 fileID); - - ///Returns MNetCancelSendingFile - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetCancelSendingFile message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetCancelSendingFile - bool operator==(const NetMessage& rhs) const; - - ///Retrieves fileID - Uint16 getFileID() const; -private: -private: - Uint16 fileID; -}; - - - - -///NetCancelRecievingFile -class NetCancelRecievingFile : public NetMessage -{ -public: - ///Creates a NetCancelRecievingFile message - NetCancelRecievingFile(); - - ///Creates a NetCancelRecievingFile message - NetCancelRecievingFile(Uint16 fileID); - - ///Returns MNetCancelRecievingFile - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetCancelRecievingFile message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetCancelRecievingFile - bool operator==(const NetMessage& rhs) const; - - ///Retrieves fileID - Uint16 getFileID() const; -private: -private: - Uint16 fileID; -}; - - - - -///NetRequestMapThumbnail -class NetRequestMapThumbnail : public NetMessage -{ -public: - ///Creates a NetRequestMapThumbnail message - NetRequestMapThumbnail(); - - ///Creates a NetRequestMapThumbnail message - NetRequestMapThumbnail(Uint16 mapID); - - ///Returns MNetRequestMapThumbnail - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetRequestMapThumbnail message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetRequestMapThumbnail - bool operator==(const NetMessage& rhs) const; - - ///Retrieves mapID - Uint16 getMapID() const; -private: -private: - Uint16 mapID; -}; - - - - -///NetSendMapThumbnail -class NetSendMapThumbnail : public NetMessage -{ -public: - ///Creates a NetSendMapThumbnail message - NetSendMapThumbnail(); - - ///Creates a NetSendMapThumbnail message - NetSendMapThumbnail(Uint16 mapID, MapThumbnail thumbnail); - - ///Returns MNetSendMapThumbnail - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSendMapThumbnail message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSendMapThumbnail - bool operator==(const NetMessage& rhs) const; - - ///Retrieves mapID - Uint16 getMapID() const; - - ///Retrieves thumbnail - MapThumbnail getThumbnail() const; -private: -private: - Uint16 mapID; - MapThumbnail thumbnail; -}; - - - - -///NetSubmitRatingOnMap -class NetSubmitRatingOnMap : public NetMessage -{ -public: - ///Creates a NetSubmitRatingOnMap message - NetSubmitRatingOnMap(); - - ///Creates a NetSubmitRatingOnMap message - NetSubmitRatingOnMap(Uint16 mapID, Uint8 rating); - - ///Returns MNetSubmitRatingOnMap - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the NetSubmitRatingOnMap message with a small amount - ///of information. - std::string format() const; - - ///Compares with another NetSubmitRatingOnMap - bool operator==(const NetMessage& rhs) const; - - ///Retrieves mapName - Uint16 getMapID() const; - - ///Retrieves rating - Uint8 getRating() const; -private: -private: - Uint16 mapID; - Uint8 rating; -}; - - - -//message_append_marker - -#include - -template void NetUpdateGameList::updateDifferences(const container& original, const container& updated) -{ - removedGames.clear(); - updatedGames.clear(); - ///Find all removed games - for(typename container::const_iterator i = original.begin(); i!=original.end(); ++i) - { - bool found=false; - for(typename container::const_iterator j = updated.begin(); j!=updated.end(); ++j) - { - if(i->getGameID() == j->getGameID()) - { - found=true; - break; - } - } - if(!found) - { - removedGames.push_back(i->getGameID()); - } - } - ///Find changed games - for(typename container::const_iterator i = original.begin(); i!=original.end(); ++i) - { - for(typename container::const_iterator j = updated.begin(); j!=updated.end(); ++j) - { - ///If the ID's are the same but some other property isn't, then - ///the game has changed and needs to be updated - if((i->getGameID() == j->getGameID()) && ((*i) != (*j))) - { - updatedGames.push_back(*j); - break; - } - } - } - ///Find added games - for(typename container::const_iterator i = updated.begin(); i!=updated.end(); ++i) - { - bool found=false; - for(typename container::const_iterator j = original.begin(); j!=original.end(); ++j) - { - if(i->getGameID() == j->getGameID()) - { - found=true; - break; - } - } - if(!found) - { - updatedGames.push_back(*i); - } - } -} - - - -template void NetUpdateGameList::applyDifferences(container& original) const -{ - //Remove the removed games - for(Uint16 i=0; igetGameID() == removedGames[i]) - { - game = j; - break; - } - } - original.erase(game); - } - - - //Change the changed games and add the rest - for(Uint16 i=0; igetGameID() == updatedGames[i].getGameID()) - { - (*j) = updatedGames[i]; - found=true; - break; - } - } - if(!found) - { - original.insert(original.end(), updatedGames[i]); - } - } -} - - - -template void NetUpdatePlayerList::updateDifferences(const container& original, const container& updated) -{ - removedPlayers.clear(); - updatedPlayers.clear(); - //find removed players - for(typename container::const_iterator i = original.begin(); i!=original.end(); ++i) - { - bool found=false; - for(typename container::const_iterator j = updated.begin(); j!=updated.end(); ++j) - { - if(i->getPlayerID() == j->getPlayerID()) - { - found=true; - break; - } - } - if(!found) - removedPlayers.push_back(i->getPlayerID()); - } - - //Find added or changed players - for(typename container::const_iterator i = updated.begin(); i!=updated.end(); ++i) - { - bool found=false; - bool changed=false; - for(typename container::const_iterator j = original.begin(); j!=original.end(); ++j) - { - if(i->getPlayerID() == j->getPlayerID()) - { - found=true; - if((*i) != (*j)) - { - changed=true; - } - break; - } - } - if(!found || changed) - updatedPlayers.push_back(*i); - } -} - - - -template void NetUpdatePlayerList::applyDifferences(container& original) const -{ - //Remove removed players - for(std::vector::const_iterator i = removedPlayers.begin(); i!=removedPlayers.end(); ++i) - { - for(typename container::iterator j=original.begin(); j!=original.end(); ++j) - { - if(*i == j->getPlayerID()) - { - original.erase(j); - break; - } - } - } - - //Change and/or add the players that are updated - for(std::vector::const_iterator i=updatedPlayers.begin(); i!=updatedPlayers.end(); ++i) - { - bool found=false; - for(typename container::iterator j=original.begin(); j!=original.end(); ++j) - { - //If the player id's are the same, then this player has somehow changed. - if(i->getPlayerID() == j->getPlayerID()) - { - (*j) = (*i); - found = true; - } - } - //Not found, meaning this player is a new one - if(!found) - { - original.insert(original.end(), (*i)); - } - } -} - -#endif diff --git a/src/NewMapScreen.cpp b/src/NewMapScreen.cpp index 08b4f4684..b593e645a 100644 --- a/src/NewMapScreen.cpp +++ b/src/NewMapScreen.cpp @@ -1,21 +1,6 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ #include "NewMapScreen.h" #include #include @@ -197,7 +182,7 @@ NewMapScreen::NewMapScreen() extraIslands->add(6); extraIslands->add(7); extraIslands->add(8); - extraIslands->setNth(descriptor.extraIslands+1); + extraIslands->setNth(descriptor.extraIslands); extraIslands->visible=false; addWidget(extraIslands); @@ -313,19 +298,28 @@ void NewMapScreen::onAction(Widget *source, Action action, int par1, int par2) descriptor.nbWorkers=nbWorkers->getNth()+1; descriptor.logRepeatAreaTimes=logRepeatAreaTimes->getNth(); + + // eISLANDS + descriptor.extraIslands=extraIslands->getNth(); } else if (action==LIST_ELEMENT_SELECTED) { // eUNIFORM if (source==terrains) - descriptor.terrainType=(TerrainType)terrains->getSelectionIndex(); - + { + if (auto sel = terrains->selection()) + descriptor.terrainType = (TerrainType)*sel; + } + // all if (source==methodes) { + auto sel = methodes->selection(); + if (!sel) + return; MapGenerationDescriptor::Methode old=descriptor.methode; - descriptor.methode=(MapGenerationDescriptor::Methode)methodes->getSelectionIndex(); - + descriptor.methode=(MapGenerationDescriptor::Methode)*sel; + if (old!=descriptor.methode) { terrains->visible=false; @@ -478,7 +472,6 @@ void NewMapScreen::onAction(Widget *source, Action action, int par1, int par2) descriptor.fruitRatio=fruitRatio->get(); descriptor.riverDiameter=riverDiameter->get(); descriptor.craterDensity=craterDensity->get(); - descriptor.extraIslands=extraIslands->get(); //eISLANDS descriptor.oldIslandSize=oldIslandSize->get(); } diff --git a/src/NewMapScreen.h b/src/NewMapScreen.h index 8a85b1052..4546e8bd3 100644 --- a/src/NewMapScreen.h +++ b/src/NewMapScreen.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __NEWMAPSCREEN_H -#define __NEWMAPSCREEN_H +#pragma once #include "Glob2Screen.h" #include "MapGenerationDescriptor.h" @@ -69,4 +52,3 @@ class NewMapScreen : public Glob2Screen void onAction(Widget *source, Action action, int par1, int par2); }; -#endif diff --git a/src/NonANSICStdWrapper.h b/src/NonANSICStdWrapper.h index 2abb9fd61..904be0d04 100644 --- a/src/NonANSICStdWrapper.h +++ b/src/NonANSICStdWrapper.h @@ -1,24 +1,6 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __NONANSICSTDWRAPPER_H -#define __NONANSICSTDWRAPPER_H - - -#endif diff --git a/src/Order.cpp b/src/Order.cpp index c310a5fba..75ffd123e 100644 --- a/src/Order.cpp +++ b/src/Order.cpp @@ -1,981 +1,71 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include - -#include "Marshaling.h" #include "Order.h" -#include "Utilities.h" -#include "Brush.h" Order::Order(void) { - sender=-1; - gameCheckSum=static_cast(-1); + sender=ORDER_SENDER_NONE; + gameCheckSum=ORDER_CHECKSUM_NONE; } -boost::shared_ptr Order::getOrder(const Uint8 *netData, int netDataLength, Uint32 versionMinor) +std::shared_ptr Order::getOrder(const Uint8 *netData, int netDataLength, Uint32 versionMinor) { if (netDataLength<1 || netData==NULL) - return boost::shared_ptr(); + return std::shared_ptr(); switch (netData[0]) { case ORDER_CREATE: - return boost::shared_ptr(new OrderCreate(netData+1, netDataLength-1, versionMinor)); + return OrderCreate::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_DELETE: - return boost::shared_ptr(new OrderDelete(netData+1, netDataLength-1, versionMinor)); + return OrderDelete::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_CANCEL_DELETE: - return boost::shared_ptr(new OrderCancelDelete(netData+1, netDataLength-1, versionMinor)); + return OrderCancelDelete::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_CONSTRUCTION: - return boost::shared_ptr(new OrderConstruction(netData+1, netDataLength-1, versionMinor)); + return OrderConstruction::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_CANCEL_CONSTRUCTION: - return boost::shared_ptr(new OrderCancelConstruction(netData+1, netDataLength-1, versionMinor)); + return OrderCancelConstruction::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_MODIFY_BUILDING: - return boost::shared_ptr(new OrderModifyBuilding(netData+1, netDataLength-1, versionMinor)); + return OrderModifyBuilding::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_MODIFY_EXCHANGE: - return boost::shared_ptr(new OrderModifyExchange(netData+1, netDataLength-1, versionMinor)); + return OrderModifyExchange::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_MODIFY_SWARM: - return boost::shared_ptr(new OrderModifySwarm(netData+1, netDataLength-1, versionMinor)); + return OrderModifySwarm::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_MODIFY_FLAG: - return boost::shared_ptr(new OrderModifyFlag(netData+1, netDataLength-1, versionMinor)); + return OrderModifyFlag::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_MODIFY_CLEARING_FLAG: - return boost::shared_ptr(new OrderModifyClearingFlag(netData+1, netDataLength-1, versionMinor)); + return OrderModifyClearingFlag::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_MODIFY_MIN_LEVEL_TO_FLAG: - return boost::shared_ptr(new OrderModifyMinLevelToFlag(netData+1, netDataLength-1, versionMinor)); + return OrderModifyMinLevelToFlag::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_MOVE_FLAG: - return boost::shared_ptr(new OrderMoveFlag(netData+1, netDataLength-1, versionMinor)); + return OrderMoveFlag::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_CHANGE_PRIORITY: - return boost::shared_ptr(new OrderChangePriority(netData+1, netDataLength-1, versionMinor)); + return OrderChangePriority::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_ALTERATE_FORBIDDEN: - return boost::shared_ptr(new OrderAlterateForbidden(netData+1, netDataLength-1, versionMinor)); + return OrderAlterateForbidden::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_ALTERATE_GUARD_AREA: - return boost::shared_ptr(new OrderAlterateGuardArea(netData+1, netDataLength-1, versionMinor)); + return OrderAlterateGuardArea::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_ALTERATE_CLEAR_AREA: - return boost::shared_ptr(new OrderAlterateClearArea(netData+1, netDataLength-1, versionMinor)); + return OrderAlterateClearArea::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_NULL: - return boost::shared_ptr(new NullOrder()); + return std::shared_ptr(new NullOrder()); case ORDER_TEXT_MESSAGE: - return boost::shared_ptr(new MessageOrder(netData+1, netDataLength-1, versionMinor)); + return MessageOrder::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_VOICE_DATA: - return boost::shared_ptr(new OrderVoiceData(netData+1, netDataLength-1, versionMinor)); + return OrderVoiceData::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_SET_ALLIANCE: - return boost::shared_ptr(new SetAllianceOrder(netData+1, netDataLength-1, versionMinor)); + return SetAllianceOrder::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_MAP_MARK: - return boost::shared_ptr(new MapMarkOrder(netData+1, netDataLength-1, versionMinor)); + return MapMarkOrder::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_PAUSE_GAME: - return boost::shared_ptr(new PauseGameOrder(netData+1, netDataLength-1, versionMinor)); + return PauseGameOrder::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_PLAYER_QUIT_GAME : - return boost::shared_ptr(new PlayerQuitsGameOrder(netData+1, netDataLength-1, versionMinor)); + return PlayerQuitsGameOrder::deserialize(netData+1, netDataLength-1, versionMinor); case ORDER_ADJUST_LATENCY : - return boost::shared_ptr(new AdjustLatency(netData+1, netDataLength-1, versionMinor)); + return AdjustLatency::deserialize(netData+1, netDataLength-1, versionMinor); default: printf("Bad packet recieved in Order.cpp (%d)\n", netData[0]); } - return boost::shared_ptr(); -} - -// OrderCreate's code - -OrderCreate::OrderCreate(const Uint8 *data, int dataLength, Uint32 versionMinor) -:Order() -{ - assert(dataLength==28);//if changed don't forget order.h update - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderCreate::OrderCreate(Sint32 teamNumber, Sint32 posX, Sint32 posY, Sint32 typeNum, Sint32 unitWorking, Sint32 unitWorkingFuture, Sint32 flagRadius) -{ - this->teamNumber=teamNumber; - this->posX=posX; - this->posY=posY; - this->typeNum=typeNum; - this->unitWorking=unitWorking; - this->unitWorkingFuture=unitWorkingFuture; - this->flagRadius=flagRadius; -} - -Uint8 *OrderCreate::getData(void) -{ - assert(sizeof(data) == getDataLength()); - - addSint32(data, this->teamNumber, 0); - addSint32(data, this->posX, 4); - addSint32(data, this->posY, 8); - addSint32(data, this->typeNum, 12); - addSint32(data, this->unitWorking, 16); - addSint32(data, this->unitWorkingFuture, 20); - addSint32(data, this->flagRadius, 24); - - return data; -} - -bool OrderCreate::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if(versionMinor<=77 && dataLength!=20) - return false; - else if (versionMinor>=78 && dataLength!=getDataLength()) - return false; - - this->teamNumber=getSint32(data, 0); - this->posX=getSint32(data, 4); - this->posY=getSint32(data, 8); - this->typeNum=getSint32(data, 12); - this->unitWorking=getSint32(data, 16); - this->unitWorkingFuture=getSint32(data, 20); - if(versionMinor>=78) - this->flagRadius=getSint32(data, 24); - - memcpy(this->data, data, dataLength); - - return true; -} - -// OrderDelete's code - -OrderDelete::OrderDelete(const Uint8 *data, int dataLength, Uint32 versionMinor) -:Order() -{ - assert(dataLength==2); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderDelete::OrderDelete(Uint16 gid) -{ - assert(gid<32768); - this->gid=gid; -} - -Uint8 *OrderDelete::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, this->gid, 0); - return data; -} - -bool OrderDelete::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength!=getDataLength()) - return false; - this->gid=getUint16(data, 0); - memcpy(this->data, data, dataLength); - return true; -} - -// OrderCancelDelete's code - -OrderCancelDelete::OrderCancelDelete(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - assert(dataLength==2); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderCancelDelete::OrderCancelDelete(Uint16 gid) -{ - assert(gid<32768); - this->gid=gid; -} - -Uint8 *OrderCancelDelete::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, this->gid, 0); - return data; -} - -bool OrderCancelDelete::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if(dataLength != getDataLength()) - return false; - this->gid = getUint16(data, 0); - memcpy(this->data, data, dataLength); - return true; -} - -// OrderConstruction's code - -OrderConstruction::OrderConstruction(const Uint8 *data, int dataLength, Uint32 versionMinor) -:Order() -{ - assert(dataLength==10); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderConstruction::OrderConstruction(Uint16 gid, Uint32 unitWorking, Uint32 unitWorkingFuture) -{ - assert(gid<32768); - this->gid=gid; - this->unitWorking=unitWorking; - this->unitWorkingFuture=unitWorkingFuture; -} - -Uint8 *OrderConstruction::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, this->gid, 0); - addUint32(data, this->unitWorking, 2); - addUint32(data, this->unitWorkingFuture, 6); - return data; -} - -bool OrderConstruction::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength!=getDataLength()) - return false; - this->gid=getUint16(data, 0); - this->unitWorking=getUint32(data, 2); - this->unitWorkingFuture=getUint32(data, 6); - memcpy(this->data, data, dataLength); - return true; -} - -// OrderCancelConstruction's code - -OrderCancelConstruction::OrderCancelConstruction(const Uint8 *data, int dataLength, Uint32 versionMinor) -:Order() -{ - assert(dataLength==6); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderCancelConstruction::OrderCancelConstruction(Uint16 gid, Uint32 unitWorking) -{ - assert(gid<32768); - this->gid=gid; - this->unitWorking=unitWorking; -} - -Uint8 *OrderCancelConstruction::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, this->gid, 0); - addUint32(data, this->unitWorking, 2); - return data; -} - -bool OrderCancelConstruction::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength!=getDataLength()) - return false; - this->gid=getUint16(data, 0); - this->unitWorking=getUint32(data, 2); - memcpy(this->data, data, dataLength); - return true; -} - -// OrderModify' code - -OrderModify::OrderModify() -:Order() -{ -} - -// OrderModifyBuildings' code - -OrderModifyBuilding::OrderModifyBuilding(const Uint8 *data, int dataLength, Uint32 versionMinor) -:OrderModify() -{ - assert(dataLength==4); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderModifyBuilding::OrderModifyBuilding(Uint16 gid, Uint16 numberRequested) -{ - assert(gid<32768); - this->gid=gid; - this->numberRequested=numberRequested; -} - -Uint8 *OrderModifyBuilding::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, gid, 0); - addUint16(data, numberRequested, 2); - return data; -} - -bool OrderModifyBuilding::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength!=4) - return false; - gid=getUint16(data, 0); - numberRequested=getUint16(data, 2); - return true; -} - -// OrderModifyExchange' code - -OrderModifyExchange::OrderModifyExchange(const Uint8 *data, int dataLength, Uint32 versionMinor) -:OrderModify() -{ - assert(dataLength==10); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderModifyExchange::OrderModifyExchange(Uint16 gid, Uint32 receiveRessourceMask, Uint32 sendRessourceMask) -{ - this->gid=gid; - this->receiveRessourceMask=receiveRessourceMask; - this->sendRessourceMask=sendRessourceMask; -} - -Uint8 *OrderModifyExchange::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, gid, 0); - addUint32(data, receiveRessourceMask, 2); - addUint32(data, sendRessourceMask, 6); - return data; -} - -bool OrderModifyExchange::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength!=10) - return false; - gid=getUint16(data, 0); - receiveRessourceMask=getUint32(data, 2); - sendRessourceMask=getUint32(data, 6); - return true; -} - -// OrderModifySwarm's code - -OrderModifySwarm::OrderModifySwarm(const Uint8 *data, int dataLength, Uint32 versionMinor) -:OrderModify() -{ - assert(dataLength == getDataLength()); - bool good = setData(data, dataLength, versionMinor); - assert(good); -} - -OrderModifySwarm::OrderModifySwarm(Uint16 gid, Sint32 ratio[NB_UNIT_TYPE]) -{ - this->gid = gid; - memcpy(this->ratio, ratio, 4*NB_UNIT_TYPE); -} - -Uint8 *OrderModifySwarm::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, gid, 0); - for (int i=0; igid=gid; - this->range=range; -} - -Uint8 *OrderModifyFlag::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, gid, 0); - addSint32(data, range, 2); - return data; -} - -bool OrderModifyFlag::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength!=6) - return false; - gid=getUint16(data, 0); - range=getSint32(data,2); - return true; -} - -// OrderModifyClearingFlags' code - -OrderModifyClearingFlag::OrderModifyClearingFlag(const Uint8 *data, int dataLength, Uint32 versionMinor) -:OrderModify() -{ - this->data=NULL; - assert(dataLength==2+BASIC_COUNT); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderModifyClearingFlag::OrderModifyClearingFlag(Uint16 gid, bool clearingRessources[BASIC_COUNT]) -{ - this->data=NULL; - this->gid=gid; - memcpy(this->clearingRessources, clearingRessources, sizeof(bool)*BASIC_COUNT); -} - -OrderModifyClearingFlag::~OrderModifyClearingFlag(void) -{ - if (data) - free(data); -} - -Uint8 *OrderModifyClearingFlag::getData(void) -{ - if (data==NULL) - data=(Uint8 *)malloc(2+BASIC_COUNT); - addUint16(data, gid, 0); - for (int i=0; igid=getUint16(data, 0); - for (int i=0; igid=gid; - this->minLevelToFlag=minLevelToFlag; -} - -OrderModifyMinLevelToFlag::~OrderModifyMinLevelToFlag(void) -{ -} - -Uint8 *OrderModifyMinLevelToFlag::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, gid, 0); - addUint16(data, minLevelToFlag, 2); - return data; -} - -bool OrderModifyMinLevelToFlag::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength!=getDataLength()) - return false; - this->gid=getUint16(data, 0); - this->minLevelToFlag=getUint16(data, 2); - return true; -} - -// OrderMoveFlags' code - -OrderMoveFlag::OrderMoveFlag(const Uint8 *data, int dataLength, Uint32 versionMinor) -:OrderModify() -{ - assert(dataLength==11); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderMoveFlag::OrderMoveFlag(Uint16 gid, Sint32 x, Sint32 y, bool drop) -{ - this->gid=gid; - this->x=x; - this->y=y; - this->drop=drop; -} - -Uint8 *OrderMoveFlag::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, gid, 0); - addSint32(data, x, 2); - addSint32(data, y, 6); - addUint8(data, (Uint8)drop, 10); - return data; -} - -bool OrderMoveFlag::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength!=11) - return false; - gid=getUint16(data, 0); - x=getSint32(data, 2); - y=getSint32(data, 6); - drop=(bool)getUint8(data, 10); - return true; -} -// OrderCancelConstruction's code - -OrderChangePriority::OrderChangePriority(const Uint8 *data, int dataLength, Uint32 versionMinor) -:Order() -{ - assert(dataLength==6); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -OrderChangePriority::OrderChangePriority(Uint16 gid, Sint32 priority) -{ - assert(gid<32768); - this->gid=gid; - this->priority=priority; -} - -Uint8 *OrderChangePriority::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, this->gid, 0); - addSint32(data, this->priority, 2); - return data; -} - -bool OrderChangePriority::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength!=getDataLength()) - return false; - this->gid=getUint16(data, 0); - this->priority=getUint32(data, 2); - memcpy(this->data, data, dataLength); - return true; -} - -// OrderAlterateArea's code - -OrderAlterateArea::OrderAlterateArea(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - _data = NULL; - - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -#ifndef YOG_SERVER_ONLY -OrderAlterateArea::OrderAlterateArea(Uint8 teamNumber, Uint8 type, BrushAccumulator *acc, const Map* map) -{ - assert(acc); - _data = NULL; - - BrushAccumulator::AreaDimensions dim; - acc->getBitmap(&mask, &dim, map); - this->teamNumber = teamNumber; - this->type = type; - centerX = dim.centerX; - centerY = dim.centerY; - minX = dim.minX; - minY = dim.minY; - maxX = dim.maxX; - maxY = dim.maxY; - assert(maxX-minX <= 512); - assert(maxY-minY <= 512); -} -#endif - -OrderAlterateArea::~OrderAlterateArea(void) -{ - if (_data) - free(_data); -} - -Uint8 *OrderAlterateArea::getData(void) -{ - if (_data) - free (_data); - this->_data = (Uint8 *)malloc(getDataLength()); - - addUint8(_data, teamNumber, 0); - addUint8(_data, type, 1); - addSint16(_data, centerX, 2); - addSint16(_data, centerY, 4); - addSint16(_data, minX, 6); - addSint16(_data, minY, 8); - addUint16(_data, maxX, 10); - addUint16(_data, maxY, 12); - mask.serialize(_data+14); - - return _data; -} - -bool OrderAlterateArea::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength < 14) - { - printf("OrderAlterateArea::setData(dataLength=%d) failure\n", dataLength); - for (int i=0; i=14); - return length; -} - -// MiscOrder's code - -MiscOrder::MiscOrder() -:Order() -{ -} - -// NullOrder's code - -NullOrder::NullOrder() -:MiscOrder() -{ -} - -// MessageOrder's code - -MessageOrder::MessageOrder(const Uint8 *data, int dataLength, Uint32 versionMinor) -:MiscOrder() -{ - this->data=NULL; - assert(dataLength>=9); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -MessageOrder::MessageOrder(Uint32 recepientsMask, Uint32 messageOrderType, const char * text) -{ - length=Utilities::strmlen(text, 256)+9; - data=(Uint8 *)malloc(length); - memcpy(data+9, text, length-9); - data[length-1]=0; - addUint32(data, recepientsMask, 0); - addUint32(data, messageOrderType, 4); - addUint8(data, (Uint8)(length-9), 8); - this->recepientsMask=recepientsMask; - this->messageOrderType=messageOrderType; -} - -MessageOrder::~MessageOrder() -{ - assert(data); - free(data); -} - -Uint8 *MessageOrder::getData(void) -{ - return data; -} - -bool MessageOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if (dataLength<9) - return false; - this->length=dataLength; - this->recepientsMask=getUint32(data, 0); - this->messageOrderType=getUint32(data, 4); - Uint8 textLength=getUint8(data, 8); - if (this->data!=NULL) - free(this->data); - this->data=(Uint8 *)malloc(dataLength); - memcpy(this->data, data, dataLength); - if (this->data[dataLength-1]!=0) - return false; - if (textLength!=Utilities::strmlen((const char *)(this->data+9), 256)) - return false; - if (textLength!=dataLength-9) - return false; - return true; -} - -// OrderVoiceData's code - -OrderVoiceData::OrderVoiceData(const Uint8 *data, int dataLength, Uint32 versionMinor) -:MiscOrder() -{ - this->data = NULL; - assert(dataLength >= 5); - bool good = setData(data, dataLength, versionMinor); - assert(good); -} - -OrderVoiceData::OrderVoiceData(Uint32 recepientsMask, size_t framesDatasLength, Uint8 frameCount, const Uint8 *framesDatas) -{ - this->recepientsMask = recepientsMask; - this->framesDatasLength = framesDatasLength; - this->frameCount = frameCount; - - data = (Uint8 *)malloc(framesDatasLength+5); - if (framesDatas) - memcpy(data+5, framesDatas, framesDatasLength); -} - -OrderVoiceData::~OrderVoiceData() -{ - assert(data); - free(data); -} - -Uint8 *OrderVoiceData::getData(void) -{ - addUint32(data, recepientsMask, 0); - addUint8(data, frameCount, 4); - return data; -} - -bool OrderVoiceData::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - assert(dataLength >= 5); - if (dataLength<5) - return false; - - this->framesDatasLength = (size_t)dataLength - 5; - this->recepientsMask = getUint32(data, 0); - this->frameCount = getUint8(data, 4); - - if (this->data != NULL) - free(this->data); - this->data = (Uint8 *)malloc(dataLength); - memcpy(this->data, data, dataLength); - return true; -} - -// SetAllianceOrder's code - -SetAllianceOrder::SetAllianceOrder(const Uint8 *data, int dataLength, Uint32 versionMinor) -:MiscOrder() -{ - assert(dataLength==24); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -SetAllianceOrder::SetAllianceOrder(Uint32 teamNumber, Uint32 alliedMask, Uint32 enemyMask, Uint32 visionExchangeMask, Uint32 visionFoodMask, Uint32 visionOtherMask) -{ - this->teamNumber=teamNumber; - this->alliedMask=alliedMask; - this->enemyMask=enemyMask; - this->visionExchangeMask=visionExchangeMask; - this->visionFoodMask=visionFoodMask; - this->visionOtherMask=visionOtherMask; -} - -Uint8 *SetAllianceOrder::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint32(data, this->teamNumber, 0); - addUint32(data, this->alliedMask, 4); - addUint32(data, this->enemyMask, 8); - addUint32(data, this->visionExchangeMask, 12); - addUint32(data, this->visionFoodMask, 16); - addUint32(data, this->visionOtherMask, 20); - return data; -} - -bool SetAllianceOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if(dataLength!=getDataLength()) - return false; - this->teamNumber=getUint32(data, 0); - this->alliedMask=getUint32(data, 4); - this->enemyMask=getUint32(data, 8); - this->visionExchangeMask=getUint32(data, 12); - this->visionFoodMask=getUint32(data, 16); - this->visionOtherMask=getUint32(data, 20); - memcpy(this->data, data, dataLength); - return true; -} - -// MapMarkOrder's code - -MapMarkOrder::MapMarkOrder(const Uint8 *data, int dataLength, Uint32 versionMinor) -:MiscOrder() -{ - assert(dataLength==12); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -MapMarkOrder::MapMarkOrder(Uint32 teamNumber, Sint32 x, Sint32 y) -{ - this->teamNumber=teamNumber; - this->x=x; - this->y=y; -} - -Uint8 *MapMarkOrder::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint32(data, this->teamNumber, 0); - addSint32(data, this->x, 4); - addSint32(data, this->y, 8); - return data; -} - -bool MapMarkOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if(dataLength!=getDataLength()) - return false; - - this->teamNumber=getUint32(data, 0); - this->x=getSint32(data, 4); - this->y=getSint32(data, 8); - - memcpy(this->data, data, dataLength); - - return true; -} - -// PauseGameOrder's code - -PauseGameOrder::PauseGameOrder(const Uint8 *data, int dataLength, Uint32 versionMinor) -:MiscOrder() -{ - assert(dataLength==1); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -PauseGameOrder::PauseGameOrder(bool pause) -{ - this->pause=pause; -} - -Uint8 *PauseGameOrder::getData(void) -{ - assert(sizeof(data) == getDataLength()); - data[0]=(Uint8)pause; - return data; -} - -bool PauseGameOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if(dataLength!=getDataLength()) - return false; - pause=(bool)data[0]; - memcpy(this->data, data, dataLength); - return true; -} - -// PlayerQuitsGameOrder code - -PlayerQuitsGameOrder::PlayerQuitsGameOrder(const Uint8 *data, int dataLength, Uint32 versionMinor) -:MiscOrder() -{ - assert(dataLength==4); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -PlayerQuitsGameOrder::PlayerQuitsGameOrder(Sint32 player) -{ - this->player=player; -} - -Uint8 *PlayerQuitsGameOrder::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint32(data, this->player, 0); - return data; -} - -bool PlayerQuitsGameOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if(dataLength!=getDataLength()) - return false; - - this->player=getUint32(data, 0); - - memcpy(this->data, data, dataLength); - - return true; -} - - -// PlayerQuitsGameOrder code - -AdjustLatency::AdjustLatency(const Uint8 *data, int dataLength, Uint32 versionMinor) -:MiscOrder() -{ - assert(dataLength==2); - bool good=setData(data, dataLength, versionMinor); - assert(good); -} - -AdjustLatency::AdjustLatency(Uint16 latencyAdjustment) -{ - this->latencyAdjustment=latencyAdjustment; -} - -Uint8 *AdjustLatency::getData(void) -{ - assert(sizeof(data) == getDataLength()); - addUint16(data, this->latencyAdjustment, 0); - return data; -} - -bool AdjustLatency::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) -{ - if(dataLength!=getDataLength()) - return false; - - this->latencyAdjustment=getUint16(data, 0); - - memcpy(this->data, data, dataLength); - - return true; + return std::shared_ptr(); } diff --git a/src/Order.h b/src/Order.h index 1cf09ae98..c4006217a 100644 --- a/src/Order.h +++ b/src/Order.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __ORDER_H -#define __ORDER_H +#pragma once #include @@ -28,10 +10,40 @@ #include "Ressource.h" #include "UnitConsts.h" #include "BitArray.h" -#include +#include +#include class Map; +// === Order-protocol sentinels (cross-slice) === + +//! "Checksum not yet set" sentinel for Order::gameCheckSum. The Order is +//! sent before the receiving end has computed its post-tick checksum, so +//! this value means "skip the cross-check this tick". See Order.cpp:9, +//! MultiplayerGame.cpp:522, NetEngine.cpp:158, 211, 217-219. +static constexpr Uint32 ORDER_CHECKSUM_NONE = static_cast(-1); + +//! "Sender unset" sentinel for Order::sender (set by NetGame::getOrder() +//! once the wire data has been mapped to a player number). See Order.cpp:8. +static constexpr int ORDER_SENDER_NONE = -1; + +//! Length, in bytes, of the big-endian length prefix that precedes every +//! framed network message (TCP and UDP alike). See +//! NetConnectionThread.cpp:111-115, 182, 192-194; NetBroadcaster.cpp:53-55; +//! NetBroadcastListener.cpp:38. +static constexpr int NET_FRAME_LENGTH_PREFIX_BYTES = 2; + +//! Maximum chat-message text length (including NUL terminator) used by +//! MessageOrder when validating wire-side text payloads, and matched by +//! the MultiplayerGameScreen TextInput widget's max-length. +//! See OrderMisc.cpp:37, 73; MultiplayerGameScreen.cpp:115. +static constexpr int ORDER_TEXT_MESSAGE_MAX_LEN = 256; + +//! Maximum width or height (in tiles) for the bounding box of an +//! area-alteration brush stroke encoded by OrderAlterateArea. +//! See OrderModify.cpp:302-303, 350-351. +static constexpr int ORDER_AREA_BRUSH_MAX_SIDE = 512; + //! An Order represents a synchronized event in the game class Order { @@ -43,7 +55,7 @@ class Order virtual Uint8 getOrderType(void)=0; ///Takes in an arbitrary amount of information and returns its assocciatted order - static boost::shared_ptr getOrder(const Uint8 *netData, int netDataLength, Uint32 versionMinor); + static std::shared_ptr getOrder(const Uint8 *netData, int netDataLength, Uint32 versionMinor); ///Returns the encoded data buffer of data for the Order virtual Uint8 *getData(void)=0; @@ -63,9 +75,13 @@ class Order class OrderCreate:public Order { public: - OrderCreate(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderCreate() = default; OrderCreate(Sint32 teamNumber, Sint32 posX, Sint32 posY, Sint32 typeNum, Sint32 unitWorking, Sint32 unitWorkingFuture, Sint32 flagRadius=-1); virtual ~OrderCreate(void) {} + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_CREATE; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -88,9 +104,13 @@ class OrderCreate:public Order class OrderDelete:public Order { public: - OrderDelete(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderDelete() = default; OrderDelete(Uint16 gid); virtual ~OrderDelete(void) {} + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_DELETE; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -106,9 +126,13 @@ class OrderDelete:public Order class OrderCancelDelete:public Order { public: - OrderCancelDelete(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderCancelDelete() = default; OrderCancelDelete(Uint16 gid); virtual ~OrderCancelDelete(void) {} + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_CANCEL_DELETE; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -124,9 +148,13 @@ class OrderCancelDelete:public Order class OrderConstruction:public Order { public: - OrderConstruction(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderConstruction() = default; OrderConstruction(Uint16 gid, Uint32 unitWorking, Uint32 unitWorkingFuture); virtual ~OrderConstruction(void) {} + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_CONSTRUCTION; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -144,9 +172,13 @@ class OrderConstruction:public Order class OrderCancelConstruction:public Order { public: - OrderCancelConstruction(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderCancelConstruction() = default; OrderCancelConstruction(Uint16 gid, Uint32 unitWorking); virtual ~OrderCancelConstruction(void) {} + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_CANCEL_CONSTRUCTION; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -164,9 +196,13 @@ class OrderCancelConstruction:public Order class OrderChangePriority:public Order { public: - OrderChangePriority(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderChangePriority() = default; OrderChangePriority(Uint16 gid, Sint32 priority); virtual ~OrderChangePriority(void) {} + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_CHANGE_PRIORITY; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -192,10 +228,14 @@ class OrderModify:public Order class OrderModifyBuilding:public OrderModify { public: - OrderModifyBuilding(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderModifyBuilding() = default; OrderModifyBuilding(Uint16 gid, Uint16 numberRequested); virtual ~OrderModifyBuilding(void) {} + //! Decode a wire payload (no leading order-type byte). Returns nullptr on + //! malformed input; Order::getOrder treats that as a dropped order. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void) { return 4; } @@ -203,19 +243,22 @@ class OrderModifyBuilding:public OrderModify Uint16 gid; Uint16 numberRequested; - + protected: Uint8 data[4]; }; -//! Change the +//! Change the class OrderModifyExchange:public OrderModify { public: - OrderModifyExchange(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderModifyExchange() = default; OrderModifyExchange(Uint16 gid, Uint32 receiveRessourceMask, Uint32 sendRessourceMask); virtual ~OrderModifyExchange(void) {} + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void) { return 10; } @@ -224,7 +267,7 @@ class OrderModifyExchange:public OrderModify Uint16 gid; Uint32 receiveRessourceMask; Uint32 sendRessourceMask; - + protected: Uint8 data[10]; }; @@ -232,10 +275,13 @@ class OrderModifyExchange:public OrderModify class OrderModifySwarm:public OrderModify { public: - OrderModifySwarm(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderModifySwarm() = default; OrderModifySwarm(Uint16 gid, Sint32 ratio[NB_UNIT_TYPE]); virtual ~OrderModifySwarm(void) {} + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void) { return 2+4*NB_UNIT_TYPE; } @@ -245,16 +291,21 @@ class OrderModifySwarm:public OrderModify Sint32 ratio[NB_UNIT_TYPE]; protected: - Uint8 data[14]; + //! Wire encoding buffer: Uint16 gid || Sint32 ratio[NB_UNIT_TYPE]. + //! Size must track getDataLength() — keep both as 2+4*NB_UNIT_TYPE. + Uint8 data[2+4*NB_UNIT_TYPE]; }; class OrderModifyFlag:public OrderModify { public: - OrderModifyFlag(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderModifyFlag() = default; OrderModifyFlag(Uint16 gid, Sint32 range); virtual ~OrderModifyFlag(void) {} + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void) { return 6; } @@ -270,10 +321,13 @@ class OrderModifyFlag:public OrderModify class OrderModifyClearingFlag:public OrderModify { public: - OrderModifyClearingFlag(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderModifyClearingFlag() = default; OrderModifyClearingFlag(Uint16 gid, bool clearingRessources[BASIC_COUNT]); virtual ~OrderModifyClearingFlag(void); + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void) { return 2+BASIC_COUNT; } @@ -283,16 +337,19 @@ class OrderModifyClearingFlag:public OrderModify bool clearingRessources[BASIC_COUNT]; protected: - Uint8 *data; + Uint8 *data = nullptr; }; class OrderModifyMinLevelToFlag:public OrderModify { public: - OrderModifyMinLevelToFlag(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderModifyMinLevelToFlag() = default; OrderModifyMinLevelToFlag(Uint16 gid, Uint16 minLevelToFlag); virtual ~OrderModifyMinLevelToFlag(void); + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void) { return 4; } @@ -308,10 +365,13 @@ class OrderModifyMinLevelToFlag:public OrderModify class OrderMoveFlag:public OrderModify { public: - OrderMoveFlag(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderMoveFlag() = default; OrderMoveFlag(Uint16 gid, Sint32 x, Sint32 y, bool drop); virtual ~OrderMoveFlag(void) {} + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void) { return 11; } @@ -328,19 +388,50 @@ class OrderMoveFlag:public OrderModify class BrushAccumulator; +//! Number of bytes in the fixed-width header that precedes the variable-length +//! BitArray mask payload in every OrderAlterateArea wire encoding: +//! teamNumber(1) + type(1) + centerX/Y(2*2) + minX/Y(2*2) + maxX/Y(2*2) = 14. +static constexpr int ALTERATE_AREA_HEADER_BYTES = 14; + class OrderAlterateArea:public OrderModify { public: - OrderAlterateArea(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderAlterateArea() = default; #ifndef YOG_SERVER_ONLY OrderAlterateArea(Uint8 teamNumber, Uint8 type, BrushAccumulator *acc, const Map* map); #endif virtual ~OrderAlterateArea(void); - + Uint8 *getData(void); + + //! Parse the wire format for an OrderAlterate{Forbidden,GuardArea,ClearArea} + //! packet. Layout: 14-byte fixed header + //! (teamNumber: Uint8, type: Uint8, centerX/Y: Sint16, minX/Y: Sint16, + //! maxX/Y: Sint16, all big-endian) + //! followed by ceil((maxX-minX) * (maxY-minY) / 8) bitmap bytes. + //! + //! Returns false (without mutating the bitmap) on any of: + //! - dataLength < ALTERATE_AREA_HEADER_BYTES + //! - maxX < minX or maxY < minY (negative-side dimensions) + //! - maxX-minX or maxY-minY > ORDER_AREA_BRUSH_MAX_SIDE + //! - dataLength does not equal header + expected bitmap byte count + //! + //! These rejections are required because the source `data` buffer comes + //! from network or replay traffic and its length is the only ground + //! truth for the bitmap-payload size — the header-declared dimensions + //! cannot be trusted, and BitArray::deserialize does no bound check. + //! See BH-195. bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void); - + + //! Returns the expected number of bitmap payload bytes for the given + //! brush bounding box, or std::nullopt if the box is invalid (negative + //! side, or side > ORDER_AREA_BRUSH_MAX_SIDE). Pure helper, separated + //! from setData so the malformed-packet rejections can be unit-tested + //! without a full wire-buffer round-trip. + static std::optional expectedBitmapBytes(Sint16 minX, Sint16 minY, + Sint16 maxX, Sint16 maxY); + Uint8 teamNumber; Uint8 type; Sint16 centerX; @@ -350,41 +441,50 @@ class OrderAlterateArea:public OrderModify Sint16 maxX; Sint16 maxY; Utilities::BitArray mask; - + protected: - Uint8 *_data; + Uint8 *_data = nullptr; }; class OrderAlterateForbidden:public OrderAlterateArea { public: - OrderAlterateForbidden(const Uint8 *data, int dataLength, Uint32 versionMinor) : OrderAlterateArea(data, dataLength, versionMinor) { } + OrderAlterateForbidden() = default; #ifndef YOG_SERVER_ONLY OrderAlterateForbidden(Uint8 teamNumber, Uint8 type, BrushAccumulator *acc, const Map* map) : OrderAlterateArea(teamNumber, type, acc, map) { } #endif - + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_ALTERATE_FORBIDDEN; } }; class OrderAlterateGuardArea:public OrderAlterateArea { public: - OrderAlterateGuardArea(const Uint8 *data, int dataLength, Uint32 versionMinor) : OrderAlterateArea(data, dataLength, versionMinor) { } + OrderAlterateGuardArea() = default; #ifndef YOG_SERVER_ONLY OrderAlterateGuardArea(Uint8 teamNumber, Uint8 type, BrushAccumulator *acc, const Map* map) : OrderAlterateArea(teamNumber, type, acc, map) { } #endif - + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_ALTERATE_GUARD_AREA; } }; class OrderAlterateClearArea:public OrderAlterateArea { public: - OrderAlterateClearArea(const Uint8 *data, int dataLength, Uint32 versionMinor) : OrderAlterateArea(data, dataLength, versionMinor) { } + OrderAlterateClearArea() = default; #ifndef YOG_SERVER_ONLY OrderAlterateClearArea(Uint8 teamNumber, Uint8 type, BrushAccumulator *acc, const Map* map) : OrderAlterateArea(teamNumber, type, acc, map) { } #endif - + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_ALTERATE_CLEAR_AREA; } }; @@ -413,10 +513,13 @@ class NullOrder:public MiscOrder class MessageOrder:public MiscOrder { public: - MessageOrder(const Uint8 *data, int dataLength, Uint32 versionMinor); + MessageOrder() = default; MessageOrder(Uint32 recepientsMask, Uint32 messageOrderType, const char * text); virtual ~MessageOrder(void); + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void) { return length; } @@ -434,18 +537,21 @@ class MessageOrder:public MiscOrder Uint32 messageOrderType; protected: - Uint8 *data; - int length; + Uint8 *data = nullptr; + int length = 0; }; //! A voice message class OrderVoiceData:public MiscOrder { public: - OrderVoiceData(const Uint8 *data, int dataLength, Uint32 versionMinor); + OrderVoiceData() = default; OrderVoiceData(Uint32 recepientsMask, size_t framesDatasLength, Uint8 frameCount, const Uint8 *framesDatas); virtual ~OrderVoiceData(void); + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); int getDataLength(void) { return framesDatasLength+5; } @@ -454,18 +560,21 @@ class OrderVoiceData:public MiscOrder Uint8 *getFramesData(void) { return data+5; } Uint32 recepientsMask; - size_t framesDatasLength; - Uint8 frameCount; - Uint8 *data; + size_t framesDatasLength = 0; + Uint8 frameCount = 0; + Uint8 *data = nullptr; }; class SetAllianceOrder:public MiscOrder { public: - SetAllianceOrder(const Uint8 *data, int dataLength, Uint32 versionMinor); + SetAllianceOrder() = default; SetAllianceOrder(Uint32 teamNumber, Uint32 alliedMask, Uint32 enemyMask, Uint32 visionExchangeMask, Uint32 visionFoodMask, Uint32 visionOtherMask); virtual ~SetAllianceOrder(void) { } + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_SET_ALLIANCE; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -485,10 +594,13 @@ class SetAllianceOrder:public MiscOrder class MapMarkOrder:public MiscOrder { public: - MapMarkOrder(const Uint8 *data, int dataLength, Uint32 versionMinor); + MapMarkOrder() = default; MapMarkOrder(Uint32 teamNumber, Sint32 x, Sint32 y); virtual ~MapMarkOrder(void) { } - + + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_MAP_MARK; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -507,10 +619,13 @@ class MapMarkOrder:public MiscOrder class PauseGameOrder:public MiscOrder { public: - PauseGameOrder(const Uint8 *data, int dataLength, Uint32 versionMinor); + PauseGameOrder() = default; PauseGameOrder(bool startPause); virtual ~PauseGameOrder(void) { } + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_PAUSE_GAME; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -525,10 +640,13 @@ class PauseGameOrder:public MiscOrder class PlayerQuitsGameOrder:public MiscOrder { public: - PlayerQuitsGameOrder(const Uint8 *data, int dataLength, Uint32 versionMinor); + PlayerQuitsGameOrder() = default; PlayerQuitsGameOrder(Sint32 player); virtual ~PlayerQuitsGameOrder(void) { } + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_PLAYER_QUIT_GAME; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -544,10 +662,13 @@ class PlayerQuitsGameOrder:public MiscOrder class AdjustLatency:public MiscOrder { public: - AdjustLatency(const Uint8 *data, int dataLength, Uint32 versionMinor); + AdjustLatency() = default; AdjustLatency(Uint16 latencyAdjustment); virtual ~AdjustLatency(void) { } + //! See OrderModifyBuilding::deserialize. + static std::shared_ptr deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor); + Uint8 getOrderType(void) { return ORDER_ADJUST_LATENCY; } Uint8 *getData(void); bool setData(const Uint8 *data, int dataLength, Uint32 versionMinor); @@ -558,6 +679,4 @@ class AdjustLatency:public MiscOrder private: Uint8 data[2]; }; - -#endif diff --git a/src/OrderBuilding.cpp b/src/OrderBuilding.cpp new file mode 100644 index 000000000..8e98514e4 --- /dev/null +++ b/src/OrderBuilding.cpp @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include "FileFormatVersions.h" +#include "Game.h" +#include "Marshaling.h" +#include "Order.h" + +// OrderCreate's code + +std::shared_ptr OrderCreate::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +OrderCreate::OrderCreate(Sint32 teamNumber, Sint32 posX, Sint32 posY, Sint32 typeNum, Sint32 unitWorking, Sint32 unitWorkingFuture, Sint32 flagRadius) +{ + this->teamNumber=teamNumber; + this->posX=posX; + this->posY=posY; + this->typeNum=typeNum; + this->unitWorking=unitWorking; + this->unitWorkingFuture=unitWorkingFuture; + this->flagRadius=flagRadius; +} + +Uint8 *OrderCreate::getData(void) +{ + assert(sizeof(data) == getDataLength()); + + addSint32(data, this->teamNumber, 0); + addSint32(data, this->posX, 4); + addSint32(data, this->posY, 8); + addSint32(data, this->typeNum, 12); + addSint32(data, this->unitWorking, 16); + addSint32(data, this->unitWorkingFuture, 20); + addSint32(data, this->flagRadius, 24); + + return data; +} + +bool OrderCreate::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if(versionMinor=FILE_FORMAT_VERSION_ORDER_CREATE_FLAG_RADIUS && dataLength!=getDataLength()) + return false; + + this->teamNumber=getSint32(data, 0); + this->posX=getSint32(data, 4); + this->posY=getSint32(data, 8); + this->typeNum=getSint32(data, 12); + this->unitWorking=getSint32(data, 16); + this->unitWorkingFuture=getSint32(data, 20); + if(versionMinor>=78) + this->flagRadius=getSint32(data, 24); + + return true; +} + +// OrderDelete's code + +std::shared_ptr OrderDelete::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +OrderDelete::OrderDelete(Uint16 gid) +{ + assert(gidgid=gid; +} + +Uint8 *OrderDelete::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, this->gid, 0); + return data; +} + +bool OrderDelete::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength!=getDataLength()) + return false; + this->gid=getUint16(data, 0); + return true; +} + +// OrderCancelDelete's code + +std::shared_ptr OrderCancelDelete::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +OrderCancelDelete::OrderCancelDelete(Uint16 gid) +{ + assert(gidgid=gid; +} + +Uint8 *OrderCancelDelete::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, this->gid, 0); + return data; +} + +bool OrderCancelDelete::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if(dataLength != getDataLength()) + return false; + this->gid = getUint16(data, 0); + return true; +} + +// OrderConstruction's code + +std::shared_ptr OrderConstruction::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +OrderConstruction::OrderConstruction(Uint16 gid, Uint32 unitWorking, Uint32 unitWorkingFuture) +{ + assert(gidgid=gid; + this->unitWorking=unitWorking; + this->unitWorkingFuture=unitWorkingFuture; +} + +Uint8 *OrderConstruction::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, this->gid, 0); + addUint32(data, this->unitWorking, 2); + addUint32(data, this->unitWorkingFuture, 6); + return data; +} + +bool OrderConstruction::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength!=getDataLength()) + return false; + this->gid=getUint16(data, 0); + this->unitWorking=getUint32(data, 2); + this->unitWorkingFuture=getUint32(data, 6); + return true; +} + +// OrderCancelConstruction's code + +std::shared_ptr OrderCancelConstruction::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +OrderCancelConstruction::OrderCancelConstruction(Uint16 gid, Uint32 unitWorking) +{ + assert(gidgid=gid; + this->unitWorking=unitWorking; +} + +Uint8 *OrderCancelConstruction::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, this->gid, 0); + addUint32(data, this->unitWorking, 2); + return data; +} + +bool OrderCancelConstruction::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength!=getDataLength()) + return false; + this->gid=getUint16(data, 0); + this->unitWorking=getUint32(data, 2); + return true; +} + +// OrderChangePriority's code + +std::shared_ptr OrderChangePriority::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +OrderChangePriority::OrderChangePriority(Uint16 gid, Sint32 priority) +{ + assert(gidgid=gid; + this->priority=priority; +} + +Uint8 *OrderChangePriority::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, this->gid, 0); + addSint32(data, this->priority, 2); + return data; +} + +bool OrderChangePriority::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength!=getDataLength()) + return false; + this->gid=getUint16(data, 0); + this->priority=getUint32(data, 2); + return true; +} diff --git a/src/OrderMisc.cpp b/src/OrderMisc.cpp new file mode 100644 index 000000000..8f9ddd712 --- /dev/null +++ b/src/OrderMisc.cpp @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include "Marshaling.h" +#include "Order.h" +#include "Utilities.h" + +// MiscOrder's code + +MiscOrder::MiscOrder() +:Order() +{ +} + +// NullOrder's code + +NullOrder::NullOrder() +:MiscOrder() +{ +} + +// MessageOrder's code + +std::shared_ptr MessageOrder::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +MessageOrder::MessageOrder(Uint32 recepientsMask, Uint32 messageOrderType, const char * text) +{ + length=Utilities::strmlen(text, ORDER_TEXT_MESSAGE_MAX_LEN)+9; + data=(Uint8 *)malloc(length); + memcpy(data+9, text, length-9); + data[length-1]=0; + addUint32(data, recepientsMask, 0); + addUint32(data, messageOrderType, 4); + addUint8(data, (Uint8)(length-9), 8); + this->recepientsMask=recepientsMask; + this->messageOrderType=messageOrderType; +} + +MessageOrder::~MessageOrder() +{ + if (data) + free(data); +} + +Uint8 *MessageOrder::getData(void) +{ + return data; +} + +bool MessageOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength<9) + return false; + this->length=dataLength; + this->recepientsMask=getUint32(data, 0); + this->messageOrderType=getUint32(data, 4); + Uint8 textLength=getUint8(data, 8); + if (this->data!=NULL) + free(this->data); + this->data=(Uint8 *)malloc(dataLength); + memcpy(this->data, data, dataLength); + if (this->data[dataLength-1]!=0) + return false; + if (textLength!=Utilities::strmlen((const char *)(this->data+9), ORDER_TEXT_MESSAGE_MAX_LEN)) + return false; + if (textLength!=dataLength-9) + return false; + return true; +} + +// OrderVoiceData's code + +std::shared_ptr OrderVoiceData::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +OrderVoiceData::OrderVoiceData(Uint32 recepientsMask, size_t framesDatasLength, Uint8 frameCount, const Uint8 *framesDatas) +{ + this->recepientsMask = recepientsMask; + this->framesDatasLength = framesDatasLength; + this->frameCount = frameCount; + + data = (Uint8 *)malloc(framesDatasLength+5); + if (framesDatas) + memcpy(data+5, framesDatas, framesDatasLength); +} + +OrderVoiceData::~OrderVoiceData() +{ + if (data) + free(data); +} + +Uint8 *OrderVoiceData::getData(void) +{ + addUint32(data, recepientsMask, 0); + addUint8(data, frameCount, 4); + return data; +} + +bool OrderVoiceData::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength<5) + return false; + + this->framesDatasLength = (size_t)dataLength - 5; + this->recepientsMask = getUint32(data, 0); + this->frameCount = getUint8(data, 4); + + if (this->data != NULL) + free(this->data); + this->data = (Uint8 *)malloc(dataLength); + memcpy(this->data, data, dataLength); + return true; +} + +// SetAllianceOrder's code + +std::shared_ptr SetAllianceOrder::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +SetAllianceOrder::SetAllianceOrder(Uint32 teamNumber, Uint32 alliedMask, Uint32 enemyMask, Uint32 visionExchangeMask, Uint32 visionFoodMask, Uint32 visionOtherMask) +{ + this->teamNumber=teamNumber; + this->alliedMask=alliedMask; + this->enemyMask=enemyMask; + this->visionExchangeMask=visionExchangeMask; + this->visionFoodMask=visionFoodMask; + this->visionOtherMask=visionOtherMask; +} + +Uint8 *SetAllianceOrder::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint32(data, this->teamNumber, 0); + addUint32(data, this->alliedMask, 4); + addUint32(data, this->enemyMask, 8); + addUint32(data, this->visionExchangeMask, 12); + addUint32(data, this->visionFoodMask, 16); + addUint32(data, this->visionOtherMask, 20); + return data; +} + +bool SetAllianceOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if(dataLength!=getDataLength()) + return false; + this->teamNumber=getUint32(data, 0); + this->alliedMask=getUint32(data, 4); + this->enemyMask=getUint32(data, 8); + this->visionExchangeMask=getUint32(data, 12); + this->visionFoodMask=getUint32(data, 16); + this->visionOtherMask=getUint32(data, 20); + return true; +} + +// MapMarkOrder's code + +std::shared_ptr MapMarkOrder::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +MapMarkOrder::MapMarkOrder(Uint32 teamNumber, Sint32 x, Sint32 y) +{ + this->teamNumber=teamNumber; + this->x=x; + this->y=y; +} + +Uint8 *MapMarkOrder::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint32(data, this->teamNumber, 0); + addSint32(data, this->x, 4); + addSint32(data, this->y, 8); + return data; +} + +bool MapMarkOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if(dataLength!=getDataLength()) + return false; + + this->teamNumber=getUint32(data, 0); + this->x=getSint32(data, 4); + this->y=getSint32(data, 8); + + return true; +} + +// PauseGameOrder's code + +std::shared_ptr PauseGameOrder::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +PauseGameOrder::PauseGameOrder(bool pause) +{ + this->pause=pause; +} + +Uint8 *PauseGameOrder::getData(void) +{ + assert(sizeof(data) == getDataLength()); + data[0]=(Uint8)pause; + return data; +} + +bool PauseGameOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if(dataLength!=getDataLength()) + return false; + pause=(bool)data[0]; + return true; +} + +// PlayerQuitsGameOrder code + +std::shared_ptr PlayerQuitsGameOrder::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +PlayerQuitsGameOrder::PlayerQuitsGameOrder(Sint32 player) +{ + this->player=player; +} + +Uint8 *PlayerQuitsGameOrder::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint32(data, this->player, 0); + return data; +} + +bool PlayerQuitsGameOrder::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if(dataLength!=getDataLength()) + return false; + + this->player=getUint32(data, 0); + + return true; +} + +// AdjustLatency code + +std::shared_ptr AdjustLatency::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +AdjustLatency::AdjustLatency(Uint16 latencyAdjustment) +{ + this->latencyAdjustment=latencyAdjustment; +} + +Uint8 *AdjustLatency::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, this->latencyAdjustment, 0); + return data; +} + +bool AdjustLatency::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if(dataLength!=getDataLength()) + return false; + + this->latencyAdjustment=getUint16(data, 0); + + return true; +} diff --git a/src/OrderModify.cpp b/src/OrderModify.cpp new file mode 100644 index 000000000..ab528d70e --- /dev/null +++ b/src/OrderModify.cpp @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include "Game.h" +#include "Marshaling.h" +#include "Order.h" +#include "Brush.h" + +// OrderModify' code + +OrderModify::OrderModify() +:Order() +{ +} + +// OrderModifyBuildings' code + +OrderModifyBuilding::OrderModifyBuilding(Uint16 gid, Uint16 numberRequested) +{ + assert(gidgid=gid; + this->numberRequested=numberRequested; +} + +std::shared_ptr OrderModifyBuilding::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +Uint8 *OrderModifyBuilding::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, gid, 0); + addUint16(data, numberRequested, 2); + return data; +} + +bool OrderModifyBuilding::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength!=getDataLength()) + return false; + gid=getUint16(data, 0); + numberRequested=getUint16(data, 2); + return true; +} + +// OrderModifyExchange' code + +OrderModifyExchange::OrderModifyExchange(Uint16 gid, Uint32 receiveRessourceMask, Uint32 sendRessourceMask) +{ + this->gid=gid; + this->receiveRessourceMask=receiveRessourceMask; + this->sendRessourceMask=sendRessourceMask; +} + +std::shared_ptr OrderModifyExchange::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +Uint8 *OrderModifyExchange::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, gid, 0); + addUint32(data, receiveRessourceMask, 2); + addUint32(data, sendRessourceMask, 6); + return data; +} + +bool OrderModifyExchange::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength!=getDataLength()) + return false; + gid=getUint16(data, 0); + receiveRessourceMask=getUint32(data, 2); + sendRessourceMask=getUint32(data, 6); + return true; +} + +// OrderModifySwarm's code + +OrderModifySwarm::OrderModifySwarm(Uint16 gid, Sint32 ratio[NB_UNIT_TYPE]) +{ + this->gid = gid; + memcpy(this->ratio, ratio, 4*NB_UNIT_TYPE); +} + +std::shared_ptr OrderModifySwarm::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +Uint8 *OrderModifySwarm::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, gid, 0); + for (int i=0; igid=gid; + this->range=range; +} + +std::shared_ptr OrderModifyFlag::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +Uint8 *OrderModifyFlag::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, gid, 0); + addSint32(data, range, 2); + return data; +} + +bool OrderModifyFlag::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength!=getDataLength()) + return false; + gid=getUint16(data, 0); + range=getSint32(data,2); + return true; +} + +// OrderModifyClearingFlags' code + +OrderModifyClearingFlag::OrderModifyClearingFlag(Uint16 gid, bool clearingRessources[BASIC_COUNT]) +{ + this->gid=gid; + memcpy(this->clearingRessources, clearingRessources, sizeof(bool)*BASIC_COUNT); +} + +std::shared_ptr OrderModifyClearingFlag::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +OrderModifyClearingFlag::~OrderModifyClearingFlag(void) +{ + if (data) + free(data); +} + +Uint8 *OrderModifyClearingFlag::getData(void) +{ + if (data==NULL) + data=(Uint8 *)malloc(2+BASIC_COUNT); + addUint16(data, gid, 0); + for (int i=0; igid=getUint16(data, 0); + for (int i=0; igid=gid; + this->minLevelToFlag=minLevelToFlag; +} + +std::shared_ptr OrderModifyMinLevelToFlag::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +OrderModifyMinLevelToFlag::~OrderModifyMinLevelToFlag(void) +{ +} + +Uint8 *OrderModifyMinLevelToFlag::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, gid, 0); + addUint16(data, minLevelToFlag, 2); + return data; +} + +bool OrderModifyMinLevelToFlag::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength!=getDataLength()) + return false; + this->gid=getUint16(data, 0); + this->minLevelToFlag=getUint16(data, 2); + return true; +} + +// OrderMoveFlags' code + +OrderMoveFlag::OrderMoveFlag(Uint16 gid, Sint32 x, Sint32 y, bool drop) +{ + this->gid=gid; + this->x=x; + this->y=y; + this->drop=drop; +} + +std::shared_ptr OrderMoveFlag::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +Uint8 *OrderMoveFlag::getData(void) +{ + assert(sizeof(data) == getDataLength()); + addUint16(data, gid, 0); + addSint32(data, x, 2); + addSint32(data, y, 6); + addUint8(data, (Uint8)drop, 10); + return data; +} + +bool OrderMoveFlag::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength!=getDataLength()) + return false; + gid=getUint16(data, 0); + x=getSint32(data, 2); + y=getSint32(data, 6); + drop=(bool)getUint8(data, 10); + return true; +} + +// OrderAlterateArea's code + +#ifndef YOG_SERVER_ONLY +OrderAlterateArea::OrderAlterateArea(Uint8 teamNumber, Uint8 type, BrushAccumulator *acc, const Map* map) +{ + assert(acc); + + BrushAccumulator::AreaDimensions dim; + acc->getBitmap(&mask, &dim, map); + this->teamNumber = teamNumber; + this->type = type; + centerX = dim.centerX; + centerY = dim.centerY; + minX = dim.minX; + minY = dim.minY; + maxX = dim.maxX; + maxY = dim.maxY; + assert(maxX-minX <= ORDER_AREA_BRUSH_MAX_SIDE); + assert(maxY-minY <= ORDER_AREA_BRUSH_MAX_SIDE); +} +#endif + +OrderAlterateArea::~OrderAlterateArea(void) +{ + if (_data) + free(_data); +} + +Uint8 *OrderAlterateArea::getData(void) +{ + if (_data) + free (_data); + this->_data = (Uint8 *)malloc(getDataLength()); + + addUint8(_data, teamNumber, 0); + addUint8(_data, type, 1); + addSint16(_data, centerX, 2); + addSint16(_data, centerY, 4); + addSint16(_data, minX, 6); + addSint16(_data, minY, 8); + addSint16(_data, maxX, 10); + addSint16(_data, maxY, 12); + mask.serialize(_data+ALTERATE_AREA_HEADER_BYTES); + + return _data; +} + +std::optional OrderAlterateArea::expectedBitmapBytes(Sint16 minX, Sint16 minY, + Sint16 maxX, Sint16 maxY) +{ + // Promote to int so the subtraction can't overflow Sint16. The brush-side + // cap below keeps the eventual size_t multiplication safely under 2^20. + const int sideX = static_cast(maxX) - static_cast(minX); + const int sideY = static_cast(maxY) - static_cast(minY); + if (sideX < 0 || sideY < 0) + return std::nullopt; + if (sideX > ORDER_AREA_BRUSH_MAX_SIDE || sideY > ORDER_AREA_BRUSH_MAX_SIDE) + return std::nullopt; + const size_t bits = static_cast(sideX) * static_cast(sideY); + return (bits + 7) / 8; +} + +bool OrderAlterateArea::setData(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + if (dataLength < ALTERATE_AREA_HEADER_BYTES) + { + printf("OrderAlterateArea::setData(dataLength=%d) failure\n", dataLength); + for (int i=0; i(dataLength) != ALTERATE_AREA_HEADER_BYTES + *expectedPayload) + return false; + + mask.deserialize(data + ALTERATE_AREA_HEADER_BYTES, + static_cast(maxX - minX) * static_cast(maxY - minY)); + + return true; +} + +int OrderAlterateArea::getDataLength(void) +{ + int length=ALTERATE_AREA_HEADER_BYTES+mask.getByteLength(); + assert(length>=ALTERATE_AREA_HEADER_BYTES); + return length; +} + +// OrderAlterate* concrete subclass factories. + +std::shared_ptr OrderAlterateForbidden::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +std::shared_ptr OrderAlterateGuardArea::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} + +std::shared_ptr OrderAlterateClearArea::deserialize(const Uint8 *data, int dataLength, Uint32 versionMinor) +{ + auto order = std::make_shared(); + if (!order->setData(data, dataLength, versionMinor)) + return nullptr; + return order; +} diff --git a/src/OverlayAreas.cpp b/src/OverlayAreas.cpp index 5c39b4d3c..5944a5f03 100644 --- a/src/OverlayAreas.cpp +++ b/src/OverlayAreas.cpp @@ -1,27 +1,11 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "OverlayAreas.h" #include #include "Unit.h" #include "BuildingType.h" #include -#include #include "Game.h" #include "Bullet.h" diff --git a/src/OverlayAreas.h b/src/OverlayAreas.h index d784243d9..e4e4cdfea 100644 --- a/src/OverlayAreas.h +++ b/src/OverlayAreas.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef OverlayAreas_h -#define OverlayAreas_h +#pragma once #include #include "Types.h" @@ -71,4 +55,3 @@ class OverlayArea void spreadPoint(int x, int y, int value, int distance, std::vector& field, Uint16& msx); }; -#endif diff --git a/src/PerlinNoise.cpp b/src/PerlinNoise.cpp index b48335835..923920f2c 100644 --- a/src/PerlinNoise.cpp +++ b/src/PerlinNoise.cpp @@ -115,7 +115,7 @@ float PerlinNoise::Noise2d( float pos[ 2 ] ) { int indexLD, indexRD, indexLU, indexRU; float distFromL, distFromR, distFromD, distFromU; float *q, sX, sY, a, b, t, u, v; - register int indexL, indexR; + int indexL, indexR; if ( ! initialized ) { reseed(); } @@ -160,7 +160,7 @@ float PerlinNoise::Noise3d( float pos[ 3 ] ) { int indexLD, indexLU, indexRD, indexRU; float distFromL, distFromR, distFromD, distFromU, distFromB, distFromF; float *q, sX, sY, sZ, a, b, c, d, t, u, v; - register int indexL, indexR; + int indexL, indexR; if ( ! initialized ) { reseed(); } diff --git a/src/PerlinNoise.h b/src/PerlinNoise.h index 992a3915f..643aae084 100644 --- a/src/PerlinNoise.h +++ b/src/PerlinNoise.h @@ -1,5 +1,4 @@ -#ifndef __PERLINNOISE_H__ -#define __PERLINNOISE_H__ +#pragma once #include //#include "Vector.h" @@ -51,4 +50,3 @@ class PerlinNoise { }; -#endif // __PERLINNOISE_H__ diff --git a/src/Player.cpp b/src/Player.cpp index 26806d6a2..1e209e6aa 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -1,25 +1,10 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include +#include "FileFormatVersions.h" #include "GlobalContainer.h" #include "LogFileManager.h" #include "Marshaling.h" @@ -118,9 +103,9 @@ void Player::setBasePlayer(const BasePlayer *initial, Team *teams[Team::MAX_COUN bool Player::load(GAGCore::InputStream *stream, Team *teams[Team::MAX_COUNT], Sint32 versionMinor) { stream->readEnterSection("Player"); - char signature[4]; - stream->read(signature, 4, "signatureStart"); - if (memcmp(signature,"PLYb",4)!=0) + char signature[FILE_SIG_LEN]; + stream->read(signature, FILE_SIG_LEN, "signatureStart"); + if (memcmp(signature,FILE_SIG_PLAYER_BEGIN,FILE_SIG_LEN)!=0) { fprintf(stderr, "Player::load: Signature missmatch at begin of Player\n"); stream->readLeaveSection(); @@ -164,8 +149,8 @@ bool Player::load(GAGCore::InputStream *stream, Team *teams[Team::MAX_COUNT], Si team->type = BaseTeam::T_HUMAN; } - stream->read(signature, 4, "signatureEnd"); - if (memcmp(signature,"PLYe",4)!=0) + stream->read(signature, FILE_SIG_LEN, "signatureEnd"); + if (memcmp(signature,FILE_SIG_PLAYER_END,FILE_SIG_LEN)!=0) { fprintf(stderr, "Player::load: Signature missmatch at end of Player\n"); stream->readLeaveSection(); @@ -179,7 +164,7 @@ bool Player::load(GAGCore::InputStream *stream, Team *teams[Team::MAX_COUNT], Si void Player::save(GAGCore::OutputStream *stream) { stream->writeEnterSection("Player"); - stream->write("PLYb", 4, "signatureStart"); + stream->write(FILE_SIG_PLAYER_BEGIN, FILE_SIG_LEN, "signatureStart"); // base player BasePlayer::save(stream); @@ -188,7 +173,7 @@ void Player::save(GAGCore::OutputStream *stream) stream->writeSint32(startPositionY, "startPositionY"); if (type>=P_AI) ai->save(stream); - stream->write("PLYe", 4, "signatureEnd"); + stream->write(FILE_SIG_PLAYER_END, FILE_SIG_LEN, "signatureEnd"); stream->writeLeaveSection(); } diff --git a/src/Player.h b/src/Player.h index 6d64044a7..f0db04428 100644 --- a/src/Player.h +++ b/src/Player.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __PLAYER_H -#define __PLAYER_H +#pragma once #include #include @@ -71,4 +54,3 @@ class Player:public BasePlayer Uint32 checkSum(std::vector *checkSumsVector); }; -#endif diff --git a/src/Race.cpp b/src/Race.cpp deleted file mode 100644 index b641d71e7..000000000 --- a/src/Race.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include - -#include - -#include - -#include -#include -#include - -#include "GlobalContainer.h" -#include "Version.h" -#include "Race.h" - -UnitType Race::unitTypes[NB_UNIT_TYPE][NB_UNIT_LEVELS]; -Sint32 Race::hungryness; - -Race::Race() -{ -} - -Race::~Race() -{ -} - -void Race::loadDefault() -{ - // read datas from backend - StreamBackend *backend = Toolkit::getFileManager()->openInputStreamBackend("data/units.txt"); - TextInputStream *stream = new TextInputStream(backend); - delete backend; - - if (stream->isEndOfStream()) - { - std::cerr << "Race::create : error, can't open file data/units.txt." << std::endl; - delete stream; - assert(false); - return; - } - - hungryness = stream->readSint32("hungryness"); - - stream->readEnterSection("worker"); - for (int i = 0; i < NB_UNIT_LEVELS; i++) - { - stream->readEnterSection(i); - unitTypes[0][i].load(stream, VERSION_MINOR); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->readEnterSection("explorer"); - for (int i = 0; i < NB_UNIT_LEVELS; i++) - { - stream->readEnterSection(i); - unitTypes[1][i].load(stream, VERSION_MINOR); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - stream->readEnterSection("warrior"); - for (int i = 0; i < NB_UNIT_LEVELS; i++) - { - stream->readEnterSection(i); - unitTypes[2][i].load(stream, VERSION_MINOR); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - delete stream; -} - -Uint32 Race::checkSumDefault() -{ - return checkSum(); -} - -void Race::load() -{ -} - -UnitType *Race::getUnitType(int type, int level) -{ - assert (level>=0); - assert (level=0); - assert (typewriteSint32(hungryness, "hungryness"); -} - -bool Race::load(GAGCore::InputStream *stream, Sint32 versionMinor) -{ - for (int i=0; ireadSint32("hungryness"); - - return true; -} - -Uint32 Race::checkSum(void) -{ - Uint32 cs = 0; - for (int i=0; i>31); - } - return cs; -} diff --git a/src/Race.h b/src/Race.h deleted file mode 100644 index 113066e58..000000000 --- a/src/Race.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __RACE_H -#define __RACE_H - -#include "UnitType.h" - -namespace GAGCore -{ - class InputStream; - class OutputStream; -} - -class Race -{ -public: - static UnitType unitTypes[NB_UNIT_TYPE][NB_UNIT_LEVELS]; - static Sint32 hungryness; - -public: - Race(); - virtual ~Race(); - - void load(); - static void loadDefault(); - static Uint32 checkSumDefault(); - - UnitType *getUnitType(int type, int level); - - void save(GAGCore::OutputStream *stream); - bool load(GAGCore::InputStream *stream, Sint32 versionMinor); - static Uint32 checkSum(void); -}; - -#endif diff --git a/src/ReplayReader.cpp b/src/ReplayReader.cpp index 0023f9219..19a04f339 100644 --- a/src/ReplayReader.cpp +++ b/src/ReplayReader.cpp @@ -1,26 +1,11 @@ -/* - Copyright (C) 2010 Michiel De Muynck - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2010 Michiel De Muynck #include "ReplayReader.h" #include "BinaryStream.h" #include "Order.h" -#include "NetMessage.h" +#include "OrderMessages.h" #include "GUIMessageBox.h" #include "GameGUI.h" #include "Version.h" @@ -113,7 +98,7 @@ bool ReplayReader::loadReplay(GAGCore::InputStream *inputStream, bool skipToOrde size_t pos = stream->getPosition(); // Calculate the length of this replay - boost::shared_ptr order; + std::shared_ptr order; numSteps = 0; numOrders = 0; stepsUntilNextOrder = stream->readUint16("replayStepCounter"); @@ -136,7 +121,7 @@ bool ReplayReader::loadReplay(GAGCore::InputStream *inputStream, bool skipToOrde std::cout << "Error reading replay: " << e.what() << std::endl; // If it was a replay with at least a few orders that were correct so far, use to plan B: play the replay up to this order - if (numOrders < 5) + if (numOrders < REPLAY_MIN_VALID_ORDERS) { // Fail delete stream; @@ -146,7 +131,7 @@ bool ReplayReader::loadReplay(GAGCore::InputStream *inputStream, bool skipToOrde else { // Overwrite the order as if it were a NullOrder - order = boost::shared_ptr(new NullOrder()); + order = std::shared_ptr(new NullOrder()); } } @@ -213,12 +198,12 @@ void ReplayReader::setCheckSum(Uint32 checksum) this->checksum = checksum; } -boost::shared_ptr ReplayReader::retrieveOrder() +std::shared_ptr ReplayReader::retrieveOrder() { - if (!hasMoreOrdersThisStep()) return boost::shared_ptr(new NullOrder()); + if (!hasMoreOrdersThisStep()) return std::shared_ptr(new NullOrder()); assert(isValid()); - boost::shared_ptr order; + std::shared_ptr order; try { @@ -239,7 +224,7 @@ boost::shared_ptr ReplayReader::retrieveOrder() delete stream; stream = NULL; - return boost::shared_ptr(new NullOrder()); + return std::shared_ptr(new NullOrder()); } } catch (const std::ios_base::failure &e) diff --git a/src/ReplayReader.h b/src/ReplayReader.h index b34256182..e195a03ad 100644 --- a/src/ReplayReader.h +++ b/src/ReplayReader.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2010 Michiel De Muynck +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2010 Michiel De Muynck - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __ReplayReader_h -#define __ReplayReader_h - -#include +#include #include #include #include "Types.h" @@ -31,6 +15,12 @@ namespace GAGCore class Order; +//! Minimum number of well-formed orders read from a replay before the +//! reader will treat a corrupt order as recoverable (substituting a +//! NullOrder). Below this threshold a malformed order aborts the replay. +//! See ReplayReader.cpp. +static constexpr Uint32 REPLAY_MIN_VALID_ORDERS = 5; + /// This class is used for reading replays. /// The replay stream is kept open and read every time you do retrieveOrder. /// If this replay stores checksums, they are checked every time an order is read. @@ -77,7 +67,7 @@ class ReplayReader void setCheckSum(Uint32 checksum = 0); /// Get the next order on the current step - boost::shared_ptr retrieveOrder(); + std::shared_ptr retrieveOrder(); /// Get the stream that this reader uses, or NULL if there is none GAGCore::InputStream *getStream() const; @@ -110,5 +100,3 @@ class ReplayReader /// The game's current checksum (or 0 if it's not given) Uint32 checksum; }; - -#endif diff --git a/src/ReplayWriter.cpp b/src/ReplayWriter.cpp index 2df4cce8a..c79657204 100644 --- a/src/ReplayWriter.cpp +++ b/src/ReplayWriter.cpp @@ -1,27 +1,12 @@ -/* - Copyright (C) 2010 Michiel De Muynck - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2010 Michiel De Muynck #include "ReplayWriter.h" #include "BinaryStream.h" #include "StreamBackend.h" #include "Order.h" -#include "NetMessage.h" +#include "OrderMessages.h" #include "GameGUI.h" #include "Version.h" #include "Toolkit.h" @@ -30,7 +15,7 @@ #include // Write an Order to the stream, with the given checksum -inline void writeOrder(GAGCore::OutputStream *stream, boost::shared_ptr order, Uint32 checksum = 0) +inline void writeOrder(GAGCore::OutputStream *stream, std::shared_ptr order, Uint32 checksum = 0) { // Write the checksum order->gameCheckSum = checksum; @@ -48,6 +33,7 @@ ReplayWriter::ReplayWriter() buffer = NULL; stepsSinceLastOrder = 0; checksum = 0; + ordersWritten = 0; } ReplayWriter::~ReplayWriter() @@ -62,11 +48,20 @@ void ReplayWriter::init(const std::string &backend, GameGUI &gui) // Avoid trouble checksum = 0; - // Initialise the buffer backend + // Initialise the buffer backend. + // Absolute paths (leading '/') bypass FileManager — its dirList prepend + // turns "/tmp/foo.replay" into "~/.glob2//tmp/foo.replay" and fails. The + // AI-trainer pipeline relies on this path being arbitrary (via + // GLOB2_REPLAY_PATH), so absolute paths must work as written. if (backend == "") { bufferBackend = new MemoryStreamBackend(); } + else if (!backend.empty() && backend[0] == '/') + { + FILE* fp = fopen(backend.c_str(), "w+"); + bufferBackend = new FileStreamBackend(fp); + } else { FILE* fp = Toolkit::getFileManager()->openFP(backend, "w+"); @@ -101,7 +96,7 @@ void ReplayWriter::setCheckSum(Uint32 checksum) this->checksum = checksum; } -void ReplayWriter::pushOrder(boost::shared_ptr order) +void ReplayWriter::pushOrder(std::shared_ptr order) { if (!isValid()) return; if (order->getOrderType() == ORDER_VOICE_DATA || order->getOrderType() == ORDER_NULL) return; @@ -113,6 +108,7 @@ void ReplayWriter::pushOrder(boost::shared_ptr order) writeOrder(buffer, order, checksum); stepsSinceLastOrder = 0; + ordersWritten++; // Don't flush the buffer. That is done when writing the last Order, in ReplayWriter::finish(). } @@ -125,7 +121,7 @@ void ReplayWriter::finish() buffer->writeUint16(stepsSinceLastOrder, "replayStepsSinceLastOrder"); // We write a NullOrder to mark the end of the replay (like terminating a string with \0) - writeOrder(buffer, boost::shared_ptr(new NullOrder()), 0); + writeOrder(buffer, std::shared_ptr(new NullOrder()), 0); // Flush the buffer now buffer->flush(); @@ -166,7 +162,7 @@ bool ReplayWriter::write(const std::string &filename) const file->writeUint16(0, "replayStepsSinceLastOrder"); // Write a NullOrder to the file to make sure it's a NullOrder-terminated replay - writeOrder(file, boost::shared_ptr(new NullOrder()), 0); + writeOrder(file, std::shared_ptr(new NullOrder()), 0); // Flush the file file->flush(); diff --git a/src/ReplayWriter.h b/src/ReplayWriter.h index eca8b2d03..cacc24ef7 100644 --- a/src/ReplayWriter.h +++ b/src/ReplayWriter.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2010 Michiel De Muynck +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2010 Michiel De Muynck - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __ReplayWriter_h -#define __ReplayWriter_h - -#include +#include #include #include "Types.h" @@ -59,7 +43,7 @@ class ReplayWriter void setCheckSum(Uint32 checksum = 0); /// Adds the order to the replay - void pushOrder(boost::shared_ptr order); + void pushOrder(std::shared_ptr order); /// Marks the end of the replay void finish(); @@ -71,6 +55,11 @@ class ReplayWriter /// Get the buffer, if for any reason you would need it GAGCore::OutputStream* getBuffer() const; + /// Number of orders pushed into the replay (excluding ORDER_VOICE_DATA + /// and ORDER_NULL, matching the actual write criteria in pushOrder). + /// Used by the AI-trainer pipeline for sidecar metadata. + Uint32 getOrderCount() const { return ordersWritten; } + private: /// You shouldn't copy-construct this class ReplayWriter(const ReplayWriter ©) { assert(false); }; @@ -89,6 +78,9 @@ class ReplayWriter /// The game's current checksum (or 0 if it's not given) Uint32 checksum; + + /// Counter of orders actually written to the buffer. Excludes voice and + /// null orders (matching the early-return in pushOrder). + Uint32 ordersWritten; }; -#endif diff --git a/src/Ressource.cpp b/src/Ressource.cpp index 97817b8bb..d6e0ff7ba 100644 --- a/src/Ressource.cpp +++ b/src/Ressource.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "Ressource.h" #include "StringTable.h" diff --git a/src/Ressource.h b/src/Ressource.h index bb2f432c4..6a6eacc43 100644 --- a/src/Ressource.h +++ b/src/Ressource.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __RESSOURCE_H -#define __RESSOURCE_H +#pragma once #include @@ -57,4 +40,3 @@ std::string getRessourceName(int type); #define HAPPYNESS_BASE 5 #define HAPPYNESS_COUNT (MAX_RESSOURCES-BASIC_COUNT) -#endif diff --git a/src/RessourceType.h b/src/RessourceType.h deleted file mode 100644 index 97952af09..000000000 --- a/src/RessourceType.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charri�e - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __RESSOURCE_TYPE_H -#define __RESSOURCE_TYPE_H - -#include "EntityType.h" -#include "Ressource.h" - -class RessourceType: public EntityType -{ -public: -#define __STARTDATA_R ((Uint32*)&terrain) - Sint32 terrain; - Sint32 gfxId; - Sint32 sizesCount; - Sint32 varietiesCount; - //The following values are integers, but are used like booleans. - Sint32 shrinkable; //whether the resource is depleted when it is collected. - Sint32 expendable; //probably a misspelling of 'extendable'. What it actually determines is whether - //the resource multiplies itself to adjacent squares over time. - Sint32 eternal; //whether the resource cannot be destroyed or completely consumed. - Sint32 granular; //whether the resource is decremented, rather than removed, when it is harvested/cleared. - Sint32 visibleToBeCollected; //whether the resource can only be collected if the fog of war is cleared on its location. - Sint32 minimapR, minimapG, minimapB; - -public: - RessourceType() { init(); } - RessourceType(GAGCore::InputStream *stream) { load(stream); } - Uint32 checkSum(void) { return shrinkable+(expendable<<1)+(eternal<<2)+(granular<<3)+(visibleToBeCollected<<4);} - virtual ~RessourceType() { } - virtual const char **getVars(size_t *size, Uint32 **data) - { - static const char *vars[] = - { - "terrain", - "gfxId", - "sizesCount", - "varietiesCount", - "shrinkable", - "expendable", - "eternal", - "granular", - "visibleToBeCollected", - "minimapR", - "minimapG", - "minimapB", - }; - if (size) - *size=(sizeof(vars)/sizeof(char *)); - if (data) - *data=__STARTDATA_R; - return vars; - } -}; - -#endif diff --git a/src/RessourcesTypes.cpp b/src/RessourcesTypes.cpp deleted file mode 100644 index 727854cf1..000000000 --- a/src/RessourcesTypes.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charri�e - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include - -#include "RessourcesTypes.h" -#include "GlobalContainer.h" - -Uint32 RessourcesTypes::checkSum(void) -{ - Uint32 cs = 0; - - for (std::vector ::iterator it=entitiesTypes.begin(); it!=entitiesTypes.end(); ++it) - { - cs ^= (*it)->checkSum(); - cs = (cs<<1) | (cs>>31); - } - - return cs; -} diff --git a/src/RessourcesTypes.h b/src/RessourcesTypes.h deleted file mode 100644 index 24d0b5f66..000000000 --- a/src/RessourcesTypes.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __RESSOURCES_TYPES_H -#define __RESSOURCES_TYPES_H - -#include "EntitiesTypes.h" -#include "RessourceType.h" - -class RessourcesTypes: public EntitiesTypes -{ - public: - Uint32 checkSum(void); -}; - -#endif diff --git a/src/SConscript b/src/SConscript index a45034e0e..6d916c863 100644 --- a/src/SConscript +++ b/src/SConscript @@ -1,22 +1,68 @@ source_files = Split(""" -AICastor.cpp -AI.cpp -AIDescriptionScreen.cpp -AIEcho.cpp -AINames.cpp -AINicowar.cpp -AINull.cpp -AINumbi.cpp -AIToubib.cpp -AIWarrush.cpp +ai/castor/Control.cpp +ai/castor/GetOrder.cpp +ai/castor/Lifecycle.cpp +ai/castor/Maps.cpp +ai/castor/Placement.cpp +ai/castor/Projects.cpp +ai/castor/State.cpp +ai/AI.cpp +ai/AIDescriptionScreen.cpp +ai/echo/BuildingOrder.cpp +ai/echo/BuildingRegister.cpp +ai/echo/Conditions.cpp +ai/echo/ConditionsBuilding.cpp +ai/echo/ConditionsPopulation.cpp +ai/echo/ConditionsTracker.cpp +ai/echo/Construction.cpp +ai/echo/ConstructionConstraints.cpp +ai/echo/Echo.cpp +ai/echo/EchoSerialization.cpp +ai/echo/Entities.cpp +ai/echo/EntitiesBuilding.cpp +ai/echo/EntitiesResource.cpp +ai/echo/EntitiesTerrain.cpp +ai/echo/Gradient.cpp +ai/echo/GradientBFS.cpp +ai/echo/Management.cpp +ai/echo/ManagementFlag.cpp +ai/echo/ManagementMisc.cpp +ai/echo/ManagementOrderBase.cpp +ai/echo/ManagementTracker.cpp +ai/echo/MapInfo.cpp +ai/echo/ReachToInfinity.cpp +ai/echo/ReachToInfinityBuilding.cpp +ai/echo/ReachToInfinityFlags.cpp +ai/echo/SearchTools.cpp +ai/AINames.cpp +ai/nicowar/Attack.cpp +ai/nicowar/Buildings.cpp +ai/nicowar/Farming.cpp +ai/nicowar/Flags.cpp +ai/nicowar/Lifecycle.cpp +ai/nicowar/Phases.cpp +ai/nicowar/Strategy.cpp +ai/nicowar/Upgrade.cpp +ai/AINull.cpp +ai/AINumbi.cpp +ai/AIToubib.cpp +ai/AIWarrush.cpp BasePlayer.cpp BaseTeam.cpp BitArray.cpp Brush.cpp -Building.cpp -BuildingsTypes.cpp -BuildingType.cpp -BuildingUtils.cpp +building/Lifecycle.cpp +building/Construction.cpp +building/Update.cpp +building/Step.cpp +building/TypeSteps.cpp +building/Misc.cpp +game/entities/Buildings.cpp +game/entities/BuildingsPartA.cpp +game/entities/BuildingsPartB.cpp +ChecksumSidecar.cpp +DatasetWriter.cpp +building/BuildingUtils.cpp Bullet.cpp Campaign.cpp CampaignEditor.cpp @@ -24,7 +70,6 @@ CampaignMainMenu.cpp CampaignMenuScreen.cpp CampaignSelectorScreen.cpp ChooseMapScreen.cpp -CPUStatisticsManager.cpp CreditScreen.cpp CustomGameOtherOptions.cpp CustomGameScreen.cpp @@ -32,21 +77,54 @@ DynamicClouds.cpp EditorMainMenu.cpp EndGameScreen.cpp Engine.cpp -EntityType.cpp +EngineInit.cpp +EngineLoaders.cpp +EngineRun.cpp Fatal.cpp +FertilityCalculator.cpp FertilityCalculatorDialog.cpp -FertilityCalculatorThread.cpp -FertilityCalculatorThreadMessage.cpp Game.cpp +Game_orders.cpp +Game_io.cpp +Game_sync.cpp +Game_editor.cpp +render/GameRender.cpp +render/GameRenderUnits.cpp +render/GameRenderBuildings.cpp +render/GameRenderTerrain.cpp +render/GameRenderOverlay.cpp +render/GameAnimations.cpp GameEvent.cpp -GameGUI.cpp -GameGUIDefaultAssignManager.cpp -GameGUIDialog.cpp -GameGUIGhostBuildingManager.cpp -GameGUIKeyActions.cpp -GameGUILoadSave.cpp -GameGUIMessageManager.cpp -GameGUIToolManager.cpp +gui/BuildingGuiState.cpp +gui/GameGUI.cpp +gui/GameGUIDefaultAssignManager.cpp +gui/GameGUIDialog.cpp +gui/GameGUIDraw.cpp +gui/GameGUIDrawChoice.cpp +gui/GameGUIDrawUnitInfos.cpp +gui/GameGUIDrawBuildingInfos.cpp +gui/GameGUIDrawBuildingHelpers.cpp +gui/GameGUIDrawMiscPanels.cpp +gui/GameGUIGhostBuildingManager.cpp +gui/GameGUIInput.cpp +gui/GameGUIInputKey.cpp +gui/GameGUIInputMenu.cpp +gui/GameGUIInputMenuClick.cpp +gui/GameGUIInputMenuClickBuilding.cpp +gui/GameGUIInputMouse.cpp +gui/GameGUIKeyActions.cpp +gui/GameGUILoadSave.cpp +gui/GameGUIMessageManager.cpp +gui/GameGUIOrders.cpp +gui/GameMusicController.cpp +gui/GameGUIParticles.cpp +gui/GameGUIPersistence.cpp +gui/GameGUIScript.cpp +gui/GameGUISelection.cpp +gui/GameGUIStep.cpp +gui/GameGUIToolManager.cpp +gui/TeamDisplay.cpp +gui/UnitDisplayNames.cpp GameHeader.cpp GameHints.cpp GameObjectives.cpp @@ -55,200 +133,299 @@ Glob2.cpp Glob2Screen.cpp Glob2Style.cpp GlobalContainer.cpp +GlobalContainerArgs.cpp Gradient.cpp GUIGlob2FileList.cpp GUIMapPreview.cpp -HeightMapGenerator.cpp -IntBuildingType.cpp -IRC.cpp -IRCTextMessageHandler.cpp -IRCThread.cpp -IRCThreadMessage.cpp +map/generator/HeightMapGenerator.cpp +building/IntBuildingType.cpp +net/irc/IRC.cpp +net/irc/IRCTextMessageHandler.cpp +net/irc/IRCThread.cpp +net/irc/IRCThreadMessage.cpp KeyboardManager.cpp LANFindScreen.cpp LANGameInformation.cpp LANMenuScreen.cpp LogFileManager.cpp MainMenuScreen.cpp -Map.cpp -MapEdit.cpp -MapEditDialog.cpp -MapEditKeyActions.cpp -MapGenerationDescriptor.cpp -MapGenerator.cpp -MapHeader.cpp +map/Map.cpp +map/gradient/MapGradientArea.cpp +map/gradient/MapGradientBuilding.cpp +map/gradient/MapGradientGlobal.cpp +map/gradient/MapGradientLocal.cpp +map/io/MapIO.cpp +map/gradient/MapMinigrad.cpp +map/MapMisc.cpp +map/pathfind/MapPathfindArea.cpp +map/pathfind/MapPathfindBuilding.cpp +map/pathfind/MapPathfindRessource.cpp +map/MapQuery.cpp +map/MapResources.cpp +map/MapStep.cpp +map/MapTerrain.cpp +render/MapView.cpp +map/edit/Widgets.cpp +map/edit/WidgetsTools.cpp +map/edit/WidgetsUnit.cpp +map/edit/WidgetsBuilding.cpp +map/edit/MapEditCtor.cpp +map/edit/MapEditIO.cpp +map/edit/MapEditDraw.cpp +map/edit/MapEditEvents.cpp +map/edit/MapEditAction.cpp +map/edit/MapEditDelegate.cpp +map/edit/MapEditClicks.cpp +map/edit/MapEditDialog.cpp +map/edit/MapEditKeyActions.cpp +map/generator/MapGenerationDescriptor.cpp +map/generator/Generator.cpp +map/generator/GeneratorDivide.cpp +map/generator/GeneratorSplit.cpp +map/generator/GeneratorPoints.cpp +map/generator/GeneratorHeightmap.cpp +map/generator/MapHomogen.cpp +map/generator/MapOldRandom.cpp +map/generator/MapRandom.cpp +map/generator/MapOldRandomRessources.cpp +map/generator/MapOldIslands.cpp +map/generator/MapOldIslandsRessources.cpp +map/generator/GameMaps.cpp +map/io/MapHeader.cpp MapScript.cpp MapScriptError.cpp MapScriptUSL.cpp -MapThumbnail.cpp +map/io/MapThumbnail.cpp MarkManager.cpp -Minimap.cpp +render/Minimap.cpp MultiplayerGame.cpp MultiplayerGameEvent.cpp MultiplayerGameEventListener.cpp MultiplayerGameScreen.cpp -NetBroadcaster.cpp -NetBroadcastListener.cpp -NetConnection.cpp -NetConnectionThread.cpp -NetConnectionThreadMessage.cpp -NetEngine.cpp -NetGamePlayerManager.cpp -NetListener.cpp -NetMessage.cpp -NetReteamingInformation.cpp -NetTestSuite.cpp +net/NetBroadcaster.cpp +net/NetBroadcastListener.cpp +net/NetConnection.cpp +net/NetConnectionThread.cpp +net/NetConnectionThreadMessage.cpp +net/NetEngine.cpp +net/NetGamePlayerManager.cpp +net/NetListener.cpp +net/message/NetMessage.cpp +net/message/AuthMessages.cpp +net/message/FileTransferMessages.cpp +net/message/GameCreateMessages.cpp +net/message/GameHeaderMessages.cpp +net/message/GameJoinMessages.cpp +net/message/GameLaunchMessages.cpp +net/message/GameTeamMessages.cpp +net/message/LobbyMessages.cpp +net/message/MapDatabaseMessages.cpp +net/message/MapUploadMessages.cpp +net/message/OrderMessages.cpp +net/message/RegistrationMessages.cpp +net/message/RouterAdminMessages.cpp +net/message/RouterMessages.cpp +net/NetReteamingInformation.cpp +net/NetTestSuite.cpp NewMapScreen.cpp Order.cpp +OrderBuilding.cpp +OrderModify.cpp +OrderMisc.cpp OverlayAreas.cpp PerlinNoise.cpp Player.cpp -Race.cpp +game/entities/Race.cpp ReplayReader.cpp ReplayWriter.cpp Ressource.cpp -RessourcesTypes.cpp +game/entities/Resources.cpp ScriptEditorScreen.cpp Sector.cpp Settings.cpp SettingsScreen.cpp +SettingsScreenGeneral.cpp +SettingsScreenBuildings.cpp +SettingsScreenKeyboard.cpp SGSL.cpp SimplexNoise.cpp SoundMixer.cpp -Team.cpp +team/Team.cpp +team/TeamSerialization.cpp +team/TeamLists.cpp +team/TeamRouting.cpp +team/TeamStep.cpp TeamStat.cpp -UnitConsts.cpp -Unit.cpp +unit/Unit.cpp +unit/UnitAction.cpp +unit/UnitActivity.cpp +unit/UnitDisplacement.cpp UnitEditorScreen.cpp -UnitSkin.cpp -UnitsSkins.cpp -UnitType.cpp -UnitUtils.cpp +unit/UnitGeometry.cpp +unit/UnitMedical.cpp +unit/UnitMovement.cpp +unit/UnitSerialization.cpp +render/UnitSkin.cpp +unit/UnitStats.cpp +game/entities/UnitType.cpp +unit/UnitUtils.cpp Utilities.cpp VoiceRecorder.cpp WinningConditions.cpp -YOGAfterJoinGameInformation.cpp -YOGClientBlockedList.cpp -YOGClientChatChannel.cpp -YOGClientChatListener.cpp -YOGClientCommandManager.cpp -YOGClientCommands.cpp -YOGClient.cpp -YOGClientDownloadableMapList.cpp -YOGClientDownloadableMapListener.cpp -YOGClientDownloadingMapScreen.cpp -YOGClientEvent.cpp -YOGClientEventListener.cpp -YOGClientFileAssembler.cpp -YOGClientGameConnectionDialog.cpp -YOGClientGameListListener.cpp -YOGClientGameListManager.cpp -YOGClientLobbyScreen.cpp -YOGClientMapDownloader.cpp -YOGClientMapDownloadScreen.cpp -YOGClientMapUploader.cpp -YOGClientMapUploadScreen.cpp -YOGClientOptionsScreen.cpp -YOGClientPlayerListListener.cpp -YOGClientPlayerListManager.cpp -YOGClientRatedMapList.cpp -YOGClientRouterAdministrator.cpp -YOGConsts.cpp -YOGDownloadableMapInfo.cpp -YOGGameInfo.cpp -YOGGameResults.cpp -YOGLoginScreen.cpp -YOGMessage.cpp -YOGPlayerPrivateInfo.cpp -YOGPlayerSessionInfo.cpp -YOGPlayerStoredInfo.cpp -YOGRegisterScreen.cpp -YOGServerAdministratorCommands.cpp -YOGServerAdministrator.cpp -YOGServerAdministratorList.cpp -YOGServerBannedIPListManager.cpp -YOGServerChatChannel.cpp -YOGServerChatChannelManager.cpp -YOGServer.cpp -YOGServerFileDistributationManager.cpp -YOGServerFileDistributor.cpp -YOGServerGame.cpp -YOGServerGameLog.cpp -YOGServerGameRouter.cpp -YOGServerMapDatabank.cpp -YOGServerPasswordRegistry.cpp -YOGServerPlayer.cpp -YOGServerPlayerScoreCalculator.cpp -YOGServerPlayerStoredInfoManager.cpp -YOGServerRouterAdministratorCommands.cpp -YOGServerRouterAdministrator.cpp -YOGServerRouter.cpp -YOGServerRouterManager.cpp -YOGServerRouterPlayer.cpp +yog/YOGAfterJoinGameInformation.cpp +yog/YOGClientBlockedList.cpp +yog/YOGClientChatChannel.cpp +yog/YOGClientChatListener.cpp +yog/YOGClientCommandManager.cpp +yog/YOGClientCommands.cpp +yog/YOGClient.cpp +yog/YOGClientDownloadableMapList.cpp +yog/YOGClientDownloadableMapListener.cpp +yog/YOGClientDownloadingMapScreen.cpp +yog/YOGClientEvent.cpp +yog/YOGClientEventListener.cpp +yog/YOGClientFileAssembler.cpp +yog/YOGClientGameConnectionDialog.cpp +yog/YOGClientGameListListener.cpp +yog/YOGClientGameListManager.cpp +yog/YOGClientLobbyScreen.cpp +yog/YOGClientMapDownloader.cpp +yog/YOGClientMapDownloadScreen.cpp +yog/YOGClientMapUploader.cpp +yog/YOGClientMapUploadScreen.cpp +yog/YOGClientOptionsScreen.cpp +yog/YOGClientPlayerListListener.cpp +yog/YOGClientPlayerListManager.cpp +yog/YOGClientRatedMapList.cpp +yog/YOGClientRouterAdministrator.cpp +yog/YOGConsts.cpp +yog/YOGDownloadableMapInfo.cpp +yog/YOGGameInfo.cpp +yog/YOGGameResults.cpp +yog/YOGLoginScreen.cpp +yog/YOGMessage.cpp +yog/YOGPlayerPrivateInfo.cpp +yog/YOGPlayerSessionInfo.cpp +yog/YOGPlayerStoredInfo.cpp +yog/YOGRegisterScreen.cpp +yog/YOGServerAdministratorCommands.cpp +yog/YOGServerAdministrator.cpp +yog/YOGServerAdministratorList.cpp +yog/YOGServerBannedIPListManager.cpp +yog/YOGServerChatChannel.cpp +yog/YOGServerChatChannelManager.cpp +yog/YOGServer.cpp +yog/YOGServerFileDistributationManager.cpp +yog/YOGServerFileDistributor.cpp +yog/YOGServerGame.cpp +yog/YOGServerGameLog.cpp +yog/YOGServerGameRouter.cpp +yog/YOGServerMapDatabank.cpp +yog/YOGServerPasswordRegistry.cpp +yog/YOGServerPlayer.cpp +yog/YOGServerPlayerScoreCalculator.cpp +yog/YOGServerPlayerStoredInfoManager.cpp +yog/YOGServerRouterAdministratorCommands.cpp +yog/YOGServerRouterAdministrator.cpp +yog/YOGServerRouter.cpp +yog/YOGServerRouterManager.cpp +yog/YOGServerRouterPlayer.cpp """) server_source_files=Split(""" -AINames.cpp +ai/AINames.cpp BasePlayer.cpp BaseTeam.cpp BitArray.cpp GameHeader.cpp LANGameInformation.cpp LogFileManager.cpp -MapHeader.cpp -NetBroadcaster.cpp -NetConnection.cpp -NetConnectionThread.cpp -NetConnectionThreadMessage.cpp -NetGamePlayerManager.cpp -NetListener.cpp -NetMessage.cpp -NetReteamingInformation.cpp -NetTestSuite.cpp +map/io/MapHeader.cpp +net/NetBroadcaster.cpp +net/NetConnection.cpp +net/NetConnectionThread.cpp +net/NetConnectionThreadMessage.cpp +net/NetGamePlayerManager.cpp +net/NetListener.cpp +net/message/NetMessage.cpp +net/message/AuthMessages.cpp +net/message/FileTransferMessages.cpp +net/message/GameCreateMessages.cpp +net/message/GameHeaderMessages.cpp +net/message/GameJoinMessages.cpp +net/message/GameLaunchMessages.cpp +net/message/GameTeamMessages.cpp +net/message/LobbyMessages.cpp +net/message/MapDatabaseMessages.cpp +net/message/MapUploadMessages.cpp +net/message/OrderMessages.cpp +net/message/RegistrationMessages.cpp +net/message/RouterAdminMessages.cpp +net/message/RouterMessages.cpp +net/NetReteamingInformation.cpp +net/NetTestSuite.cpp Order.cpp -Race.cpp -UnitType.cpp +OrderBuilding.cpp +OrderModify.cpp +OrderMisc.cpp +game/entities/Race.cpp +game/entities/UnitType.cpp Utilities.cpp -YOGConsts.cpp -YOGGameInfo.cpp -YOGGameResults.cpp -YOGMessage.cpp -YOGPlayerSessionInfo.cpp -YOGPlayerStoredInfo.cpp -BuildingUtils.cpp +yog/YOGConsts.cpp +yog/YOGGameInfo.cpp +yog/YOGGameResults.cpp +yog/YOGMessage.cpp +yog/YOGPlayerSessionInfo.cpp +yog/YOGPlayerStoredInfo.cpp +building/BuildingUtils.cpp Bullet.cpp -EntityType.cpp +game/entities/Resources.cpp Glob2.cpp GlobalContainer.cpp -Map.cpp -MapThumbnail.cpp +GlobalContainerArgs.cpp +map/Map.cpp +map/gradient/MapGradientArea.cpp +map/gradient/MapGradientBuilding.cpp +map/gradient/MapGradientGlobal.cpp +map/gradient/MapGradientLocal.cpp +map/io/MapIO.cpp +map/gradient/MapMinigrad.cpp +map/MapMisc.cpp +map/pathfind/MapPathfindArea.cpp +map/pathfind/MapPathfindRessource.cpp +map/MapQuery.cpp +map/MapResources.cpp +map/MapStep.cpp +map/MapTerrain.cpp +render/MapView.cpp +map/io/MapThumbnail.cpp Sector.cpp Settings.cpp -UnitUtils.cpp -YOGAfterJoinGameInformation.cpp -YOGDownloadableMapInfo.cpp +unit/UnitUtils.cpp +yog/YOGAfterJoinGameInformation.cpp +yog/YOGDownloadableMapInfo.cpp WinningConditions.cpp -YOGServerAdministratorCommands.cpp -YOGServerAdministrator.cpp -YOGServerAdministratorList.cpp -YOGServerBannedIPListManager.cpp -YOGServerChatChannel.cpp -YOGServerChatChannelManager.cpp -YOGServer.cpp -YOGServerFileDistributationManager.cpp -YOGServerFileDistributor.cpp -YOGServerGame.cpp -YOGServerGameLog.cpp -YOGServerGameRouter.cpp -YOGServerMapDatabank.cpp -YOGServerPasswordRegistry.cpp -YOGServerPlayer.cpp -YOGServerPlayerScoreCalculator.cpp -YOGServerPlayerStoredInfoManager.cpp -YOGServerRouterAdministratorCommands.cpp -YOGServerRouterAdministrator.cpp -YOGServerRouter.cpp -YOGServerRouterManager.cpp -YOGServerRouterPlayer.cpp +yog/YOGServerAdministratorCommands.cpp +yog/YOGServerAdministrator.cpp +yog/YOGServerAdministratorList.cpp +yog/YOGServerBannedIPListManager.cpp +yog/YOGServerChatChannel.cpp +yog/YOGServerChatChannelManager.cpp +yog/YOGServer.cpp +yog/YOGServerFileDistributationManager.cpp +yog/YOGServerFileDistributor.cpp +yog/YOGServerGame.cpp +yog/YOGServerGameLog.cpp +yog/YOGServerGameRouter.cpp +yog/YOGServerMapDatabank.cpp +yog/YOGServerPasswordRegistry.cpp +yog/YOGServerPlayer.cpp +yog/YOGServerPlayerScoreCalculator.cpp +yog/YOGServerPlayerStoredInfoManager.cpp +yog/YOGServerRouterAdministratorCommands.cpp +yog/YOGServerRouterAdministrator.cpp +yog/YOGServerRouter.cpp +yog/YOGServerRouterManager.cpp +yog/YOGServerRouterPlayer.cpp """) Import('env') local = env.Clone() @@ -271,7 +448,7 @@ else: Import('env') Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: env.Install(env["BINDIR"], "glob2") env.Alias("install", env["BINDIR"]) diff --git a/src/SGSL.cpp b/src/SGSL.cpp index 62ddec136..5510b1b09 100644 --- a/src/SGSL.cpp +++ b/src/SGSL.cpp @@ -1,23 +1,7 @@ -/* - Copyright (C) 2001-2008 Stephane Magnenat, Luc-Olivier de Charrière - and Martin S. Nyffenegger - for any question or comment contact us at , - or barock@ysagoon.com - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2008 Stephane Magnenat +// Copyright (C) 2001-2008 Luc-Olivier de Charrière +// Copyright (C) 2001-2008 Martin S. Nyffenegger /*! \file SGSL.cpp \brief SGSL: Simple Globulation Scripting Language: implementation of classes for map scripting @@ -839,7 +823,6 @@ bool Story::testCondition(GameGUI *gui) b->unitStayRange = r; b->maxUnitWorking = unitCount; b->maxUnitWorkingPreferred = unitCount; - b->maxUnitWorkingLocal = unitCount; b->update(); mapscript->flags[flagName] = b; @@ -1117,7 +1100,7 @@ std::string ErrorReport::getErrorString(void) const }; assert(type >= 0); assert(type < ET_NB_ET); - assert(ET_NB_ET == sizeof(strings)/sizeof(const char *)); + assert(ET_NB_ET == std::size(strings)); return strings[(int)type]; } @@ -1520,7 +1503,7 @@ void MapScriptSGSL::reset(void) flags.clear(); } -bool MapScriptSGSL::testMainTimer() +bool MapScriptSGSL::testMainTimer() const { return (mainTimer <= 0); } @@ -2442,7 +2425,7 @@ ErrorReport MapScriptSGSL::parseScript(Aquisition *donnees, Game *game) return er; } -bool MapScriptSGSL::hasTeamWon(unsigned teamNumber) +bool MapScriptSGSL::hasTeamWon(unsigned teamNumber) const { // Seb: Cheapo hack. Script should intialize hasWon first :-) if (testMainTimer() && hasWon.size()>teamNumber) @@ -2452,7 +2435,7 @@ bool MapScriptSGSL::hasTeamWon(unsigned teamNumber) return false; } -bool MapScriptSGSL::hasTeamLost(unsigned teamNumber) +bool MapScriptSGSL::hasTeamLost(unsigned teamNumber) const { // Seb: Cheapo hack. Script should intialize hasLost first :-) if(hasLost.size()>teamNumber) diff --git a/src/SGSL.h b/src/SGSL.h index 0239d9366..5d5eaa610 100644 --- a/src/SGSL.h +++ b/src/SGSL.h @@ -1,30 +1,13 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat, Luc-Olivier de Charrière - and Martin S. Nyffenegger - for any question or comment contact us at , - or barock@ysagoon.com - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat +// Copyright (C) 2001-2004 Luc-Olivier de Charrière +// Copyright (C) 2001-2004 Martin S. Nyffenegger /*! \file SGSL.h \brief SGSL: Simple Globulation Scripting Language: definition of classes for map scripting */ -#ifndef SGSL_H -#define SGSL_H +#pragma once #include #include @@ -331,8 +314,8 @@ class MapScriptSGSL void syncStep(GameGUI *gui); Sint32 checkSum(); - bool hasTeamWon(unsigned teamNumber); - bool hasTeamLost(unsigned teamNumber); + bool hasTeamWon(unsigned teamNumber) const; + bool hasTeamLost(unsigned teamNumber) const; int getMainTimer(void) { return mainTimer; } /// Adds a team @@ -352,7 +335,7 @@ class MapScriptSGSL friend class Story; ErrorReport parseScript(Aquisition *donnees, Game *game); - bool testMainTimer(void); + bool testMainTimer(void) const; Functions functions; @@ -365,7 +348,3 @@ class MapScriptSGSL BuildingMap flags; }; - - - -#endif diff --git a/src/ScriptEditorScreen.cpp b/src/ScriptEditorScreen.cpp index 7543923f9..e64a5b14f 100644 --- a/src/ScriptEditorScreen.cpp +++ b/src/ScriptEditorScreen.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "ScriptEditorScreen.h" #include "GlobalContainer.h" @@ -40,7 +24,7 @@ using namespace GAGGUI; #include "MapScript.h" #include -#include "boost/lexical_cast.hpp" +#include ScriptEditorScreen::ScriptEditorScreen(Game *game) @@ -87,13 +71,13 @@ ScriptEditorScreen::ScriptEditorScreen(Game *game) primaryObjectives[i] = new TextInput(30, 68 + 35*i, 560, 25, ALIGN_LEFT, ALIGN_TOP, "standard", ""); objectivesWidgets.push_back(primaryObjectives[i]); - primaryObjectiveLabels[i] = new Text(10, 68 + 35*i, ALIGN_LEFT, ALIGN_TOP, "standard", boost::lexical_cast(i+1)); + primaryObjectiveLabels[i] = new Text(10, 68 + 35*i, ALIGN_LEFT, ALIGN_TOP, "standard", std::to_string(i+1)); objectivesWidgets.push_back(primaryObjectiveLabels[i]); secondaryObjectives[i] = new TextInput(30, 68 + 35*i, 560, 25, ALIGN_LEFT, ALIGN_TOP, "standard", ""); objectivesWidgets.push_back(secondaryObjectives[i]); - secondaryObjectiveLabels[i] = new Text(10, 68 + 35*i, ALIGN_LEFT, ALIGN_TOP, "standard", boost::lexical_cast(i+9)); + secondaryObjectiveLabels[i] = new Text(10, 68 + 35*i, ALIGN_LEFT, ALIGN_TOP, "standard", std::to_string(i+9)); objectivesWidgets.push_back(secondaryObjectiveLabels[i]); } @@ -107,7 +91,7 @@ ScriptEditorScreen::ScriptEditorScreen(Game *game) hints[i] = new TextInput(30, 68 + 35*i, 560, 25, ALIGN_LEFT, ALIGN_TOP, "standard", ""); hintWidgets.push_back(hints[i]); - hintLabels[i] = new Text(10, 68 + 35*i, ALIGN_LEFT, ALIGN_TOP, "standard", boost::lexical_cast(i+1)); + hintLabels[i] = new Text(10, 68 + 35*i, ALIGN_LEFT, ALIGN_TOP, "standard", std::to_string(i+1)); hintWidgets.push_back(hintLabels[i]); } diff --git a/src/ScriptEditorScreen.h b/src/ScriptEditorScreen.h index cb80ecbb0..e66ace158 100644 --- a/src/ScriptEditorScreen.h +++ b/src/ScriptEditorScreen.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __SCRIPT_EDITOR_SCREEN_H -#define __SCRIPT_EDITOR_SCREEN_H +#pragma once #include namespace GAGGUI @@ -88,4 +71,3 @@ class ScriptEditorScreen:public OverlayScreen void loadSave(bool isLoad, const char *dir, const char *ext); }; -#endif diff --git a/src/Sector.cpp b/src/Sector.cpp index c7bd3f04b..1907e962c 100644 --- a/src/Sector.cpp +++ b/src/Sector.cpp @@ -1,39 +1,17 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "Bullet.h" #include "Game.h" #include "Sector.h" #include "Unit.h" +#include "UnitConsts.h" #include "BuildingType.h" #include "GlobalContainer.h" -#include #include #ifndef YOG_SERVER_ONLY -UnitDeathAnimation::UnitDeathAnimation(int x, int y, Team *team) -{ - this->x = x; - this->y = y; - this->team = team; - this->ticksLeft = globalContainer->deathAnimation->getFrameCount() - 1; -} +#include "render/GameAnimations.h" #endif // !YOG_SERVER_ONLY Sector::Sector(Game *game) @@ -59,16 +37,6 @@ void Sector::free(void) for (std::list::iterator it=bullets.begin();it!=bullets.end();it++) delete (*it); bullets.clear(); - - for (std::list::iterator it=explosions.begin();it!=explosions.end();it++) - delete (*it); - explosions.clear(); - -#ifndef YOG_SERVER_ONLY - for (std::list::iterator it=deathAnimations.begin();it!=deathAnimations.end();it++) - delete (*it); - deathAnimations.clear(); -#endif // !YOG_SERVER_ONLY game=NULL; map=NULL; @@ -113,7 +81,7 @@ void Sector::step(void) { assert(map); assert(game); - + for (std::list::iterator it=bullets.begin();it!=bullets.end();) { Bullet *bullet = (*it); @@ -132,17 +100,16 @@ void Sector::step(void) // we have hit a unit int team = Unit::GIDtoTeam(gid); int id = Unit::GIDtoID(gid); - - - boost::shared_ptr event(new UnitUnderAttackEvent(game->stepCounter, bullet->targetX, bullet->targetY, game->teams[team]->myUnits[id]->typeNum)); - game->teams[team]->pushGameEvent(event); - + + + game->teams[team]->pushGameEvent(GameEvent::unitUnderAttack(game->stepCounter, bullet->targetX, bullet->targetY, game->teams[team]->myUnits[id]->typeNum)); + if (bullet->revealW > 0 && bullet->revealH > 0) game->map.setMapDiscovered(bullet->revealX, bullet->revealY, bullet->revealW, bullet->revealH, Team::teamNumberToMask(team)); - + int degats = bullet->shootDamage - game->teams[team]->myUnits[id]->getRealArmor(false); if (degats <= 0) - degats = 1; + degats = BULLET_MIN_DAMAGE; game->teams[team]->myUnits[id]->hp -= degats; } else @@ -153,70 +120,30 @@ void Sector::step(void) // we have hit a building int team = Building::GIDtoTeam(gid); int id = Building::GIDtoID(gid); - + if (bullet->revealW > 0 && bullet->revealH > 0) game->map.setMapDiscovered(bullet->revealX, bullet->revealY, bullet->revealW, bullet->revealH, Team::teamNumberToMask(team)); - + Building *building = game->teams[team]->myBuildings[id]; - int damage = bullet->shootDamage-building->type->armor; - - boost::shared_ptr event(new BuildingUnderAttackEvent(game->stepCounter, bullet->targetX, bullet->targetY, building->shortTypeNum)); - game->teams[team]->pushGameEvent(event); - + int damage = bullet->shootDamage-building->type->armor; + + game->teams[team]->pushGameEvent(GameEvent::buildingUnderAttack(game->stepCounter, bullet->targetX, bullet->targetY, building->shortTypeNum)); + if (damage > 0) building->hp -= damage; else - building->hp--; + building->hp -= BULLET_MIN_DAMAGE; if (building->hp <= 0) building->kill(); } } - - if (!globalContainer->runNoX) - { - // create new explosion - BulletExplosion *explosion = new BulletExplosion(); - explosion->x = bullet->targetX; - explosion->y = bullet->targetY; - explosion->ticksLeft = globalContainer->bulletExplosion->getFrameCount(); - explosions.push_front(explosion); - } + + game->animations->onBulletImpact(*map, bullet->targetX, bullet->targetY); // remove bullet delete bullet; it = bullets.erase(it); } } - - // handle explosions timeout - for (std::list::iterator it=explosions.begin();it!=explosions.end();) - { - if ( (*it)->ticksLeft > 0 ) - { - (*it)->ticksLeft--; - ++it; - } - else - { - delete *it; - it = explosions.erase(it); - } - } - - // handle death animation - for (std::list::iterator it=deathAnimations.begin();it!=deathAnimations.end();) - { - if ( (*it)->ticksLeft > 0 ) - { - (*it)->ticksLeft--; - ++it; - } - else - { - delete *it; - it = deathAnimations.erase(it); - } - } } #endif // !YOG_SERVER_ONLY - diff --git a/src/Sector.h b/src/Sector.h index 94dad971b..ede2a7b0a 100644 --- a/src/Sector.h +++ b/src/Sector.h @@ -1,47 +1,31 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __SECTOR_H -#define __SECTOR_H +#pragma once #include class Map; class Game; class Bullet; -struct BulletExplosion; -class Explosion; -class Team; - -#ifndef YOG_SERVER_ONLY -struct UnitDeathAnimation -{ - UnitDeathAnimation(int x, int y, Team *team); - int x, y, ticksLeft; - Team *team; -}; -#endif // !YOG_SERVER_ONLY -// a 16x16 piece of Map +//! A 16x16-tile piece of Map. Pure simulation state: the bullets list is +//! ticked in step() and contributes to Map::checkSum via the damage it +//! applies. Render-only state (bullet explosions, unit death animations) +//! used to live here too and is now on GameAnimations — see +//! src/render/GameAnimations.h. class Sector { public: + // === Sector geometry (cross-slice) === + //! Bit-shift converting a tile coordinate to its sector index, i.e. + //! log2 of SECTOR_TILES. Used by Map::getSector and Map.cpp's sector + //! grid math. + static constexpr int SECTOR_SHIFT = 4; + //! Side length of a sector in tiles (1 << SECTOR_SHIFT). A sector is + //! the unit of bullet bookkeeping. + static constexpr int SECTOR_TILES = 16; + Sector() {} Sector(Game *); virtual ~Sector(void); @@ -51,10 +35,6 @@ class Sector void free(void); std::list bullets; - std::list explosions; -#ifndef YOG_SERVER_ONLY - std::list deathAnimations; -#endif // !YOG_SERVER_ONLY void save(GAGCore::OutputStream *stream); bool load(GAGCore::InputStream *stream, Game *game, Sint32 versionMinor); @@ -67,6 +47,3 @@ class Sector Map *map; Game *game; }; - -#endif - diff --git a/src/Settings.cpp b/src/Settings.cpp index 96671b427..6bf88a81d 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "Settings.h" #include "GUIBase.h" @@ -23,7 +7,7 @@ #include #include #include -#include "boost/lexical_cast.hpp" +#include using namespace GAGCore; @@ -134,17 +118,17 @@ void Settings::load(std::string filename) { for(int t=0; t<6; ++t) { - std::string keyname="defaultUnitsAssigned["+boost::lexical_cast(n)+"]["+boost::lexical_cast(t)+"]"; + std::string keyname="defaultUnitsAssigned["+std::to_string(n)+"]["+std::to_string(t)+"]"; if(parsed.find(keyname)!=parsed.end()) - defaultUnitsAssigned[n][t] = boost::lexical_cast(parsed[keyname]); + defaultUnitsAssigned[n][t] = std::stoi(parsed[keyname]); } } for(int n=0; n<3; ++n) { - std::string keyname="defaultFlagRadius["+boost::lexical_cast(n)+"]"; + std::string keyname="defaultFlagRadius["+std::to_string(n)+"]"; if(parsed.find(keyname)!=parsed.end()) - defaultFlagRadius[n] = boost::lexical_cast(parsed[keyname]); + defaultFlagRadius[n] = std::stoi(parsed[keyname]); } READ_PARSED_INT(cloudPatchSize); @@ -198,14 +182,14 @@ void Settings::save(std::string filename) { for(int t=0; t<6; ++t) { - std::string keyname="defaultUnitsAssigned["+boost::lexical_cast(n)+"]["+boost::lexical_cast(t)+"]"; + std::string keyname="defaultUnitsAssigned["+std::to_string(n)+"]["+std::to_string(t)+"]"; Utilities::streamprintf(stream, "%s=%i\n", keyname.c_str(), defaultUnitsAssigned[n][t]); } } for(int n=0; n<3; ++n) { - std::string keyname = "defaultFlagRadius["+boost::lexical_cast(n)+"]"; + std::string keyname = "defaultFlagRadius["+std::to_string(n)+"]"; Utilities::streamprintf(stream, "%s=%i\n", keyname.c_str(), defaultFlagRadius[n]); } diff --git a/src/Settings.h b/src/Settings.h index 4c3b3f816..1016326c9 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __SETTINGS_H -#define __SETTINGS_H +#pragma once #include "Header.h" #include @@ -109,4 +92,3 @@ class Settings //Version 1 - Resets default units assigned and keyboard shortcuts #define SETTINGS_VERSION 1 -#endif diff --git a/src/SettingsScreen.cpp b/src/SettingsScreen.cpp index 85d5ad573..a3b52638f 100644 --- a/src/SettingsScreen.cpp +++ b/src/SettingsScreen.cpp @@ -1,23 +1,16 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// Coordination layer for the settings screen: ctor that orchestrates the +// per-tab build helpers, onAction event-kind dispatcher and its handlers, +// the language re-translation pass, and the GfxContext / audio-mute +// visibility plumbing. +// +// Per-tab construction lives in: +// - SettingsScreenGeneral.cpp ("General Settings" tab widgets) +// - SettingsScreenBuildings.cpp ("Building Defaults" tab) +// - SettingsScreenKeyboard.cpp ("Keyboard Shortcuts" tab) #include "SettingsScreen.h" #include "GlobalContainer.h" @@ -28,323 +21,39 @@ #include #include #include -#include #include #include #include #include "SoundMixer.h" #include -#include -#include "boost/lexical_cast.hpp" -#include "GameGUIKeyActions.h" -#include "MapEditKeyActions.h" -#include "FormatableString.h" +#include SettingsScreen::SettingsScreen() : Glob2TabScreen(false, true), unitRatioGroupNumbers(), mapeditKeyboardManager(MapEditShortcuts), guiKeyboardManager(GameGUIShortcuts) { old_settings=globalContainer->settings; - + generalGroup = addGroup(Toolkit::getStringTable()->getString("[general settings]")); unitGroup = addGroup(Toolkit::getStringTable()->getString("[building settings]")); keyboardGroup = addGroup(Toolkit::getStringTable()->getString("[keyboard settings]")); - // Screen entry/quit part - ok=new TextButton( 230, 420, 180, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[ok]"), OK); - addWidget(ok); - cancel=new TextButton(440, 420, 180, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[Cancel]"), CANCEL); - addWidget(cancel); - - //following are all general settings - // language part - language=new Text(20, 60, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[language-tr]")); - addWidgetToGroup(language, generalGroup); - languageList=new List(20, 90, 180, 200, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); - for (int i=0; igetNumberOfLanguage(); i++) - { - if(!Toolkit::getStringTable()->isLangComplete(i)) - languageList->addText(Toolkit::getStringTable()->getStringInLang("[language incomplete]", i)); - else - languageList->addText(Toolkit::getStringTable()->getStringInLang("[language]", i)); - } - addWidgetToGroup(languageList, generalGroup); - - // graphics part - display=new Text(230, 60, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[display]")); - addWidgetToGroup(display, generalGroup); - actDisplay = new Text(440, 60, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", actDisplayModeToString().c_str()); - addWidgetToGroup(actDisplay, generalGroup); - modeList=new List(440, 90, 180, 190, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); - const auto modes = globalContainer->gfx->listVideoModes(); - const int standardResolutionsCount=5; - int standardResolutions[standardResolutionsCount][2]={{640,480},{800,600},{1024,768},{1280,1024},{1600,1200}}; - for (auto const& mode : modes) - { - std::ostringstream ost; - ost << mode.w << "x" << mode.h; - if (!modeList->isText(ost.str().c_str())) - modeList->addText(ost.str().c_str()); - } - for(int i=0; iisText(ost.str().c_str())) - { - ost << " *"; - modeList->addText(ost.str().c_str()); - } - } - addWidgetToGroup(modeList, generalGroup); - modeListNote=new Text(modeList->getLeft(), modeList->getTop()+modeList->getHeight(), ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[no fullscreen]")); - addWidgetToGroup(modeListNote, generalGroup); - - fullscreen=new OnOffButton(230, 90, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.screenFlags & GraphicContext::FULLSCREEN, FULLSCREEN); - addWidgetToGroup(fullscreen, generalGroup); - fullscreenText=new Text(260, 90, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[fullscreen]"), 180); - addWidgetToGroup(fullscreenText, generalGroup); - - usegpu=new OnOffButton(230, 90 + 30, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.screenFlags & GraphicContext::USEGPU, USEGL); - addWidgetToGroup(usegpu, generalGroup); - usegpuText=new Text(260, 90 + 30, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[OpenGL]"), 180); - addWidgetToGroup(usegpuText, generalGroup); - - lowquality=new OnOffButton(230, 90 + 60, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX, LOWQUALITY); - addWidgetToGroup(lowquality, generalGroup); - lowqualityText=new Text(260, 90 + 60, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[lowquality]"), 180); - addWidgetToGroup(lowqualityText, generalGroup); - - customcur=new OnOffButton(230, 90 + 90, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.screenFlags & GraphicContext::CUSTOMCURSOR, CUSTOMCUR); - addWidgetToGroup(customcur, generalGroup); - customcurText=new Text(260, 90 + 90, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[customcur]"), 180); - addWidgetToGroup(customcurText, generalGroup); - rememberUnitButton=new OnOffButton(230, 90 + 120, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.rememberUnit, REMEMBERUNIT); - addWidgetToGroup(rememberUnitButton, generalGroup); - rememberUnitText=new Text(260, 90 + 120, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[remember unit]"), 180); - addWidgetToGroup(rememberUnitText, generalGroup); - - scrollwheel=new OnOffButton(230, 90 + 150, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.scrollWheelEnabled, SCROLLWHEEL); - addWidgetToGroup(scrollwheel, generalGroup); - scrollwheelText=new Text(260, 90 + 150, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[scroll wheel enabled]"), 180); - addWidgetToGroup(scrollwheelText, generalGroup); - - - rebootWarning=new Text(0, 300, ALIGN_FILL, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Warning, you need to reboot the game for changes to take effect]")); - //TODO: warning style should be defined centrally. - rebootWarning->setStyle(Font::Style(Font::STYLE_BOLD, 255, 60, 60)); - addWidget(rebootWarning); - - setVisibilityFromGraphicType(); - rebootWarning->visible=false; - - // Username part - userName=new TextInput(20, 360, 180, 25, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", globalContainer->settings.getUsername(), true, 32); - addWidgetToGroup(userName, generalGroup); - usernameText=new Text(20, 330, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[username]")); - addWidgetToGroup(usernameText, generalGroup); - - // Audio part - audio=new Text(230, 330, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[audio]"), 300); - addWidgetToGroup(audio, generalGroup); - audioMute=new OnOffButton(230, 365, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.mute, MUTE); - addWidgetToGroup(audioMute, generalGroup); - audioMuteText=new Text(260, 365, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[mute]"), 200); - addWidgetToGroup(audioMuteText, generalGroup); - musicVol=new Selector(320, 350, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, 180, globalContainer->settings.musicVolume, 256, true); - addWidgetToGroup(musicVol, generalGroup); - voiceVol=new Selector(320, 385, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, 180, globalContainer->settings.voiceVolume, 256, true); - addWidgetToGroup(voiceVol, generalGroup); - musicVolText=new Text(320, 330, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Music volume]"), 300); - addWidgetToGroup(musicVolText, generalGroup); - voiceVolText=new Text(320, 365, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Voice volume]"), 300); - addWidgetToGroup(voiceVolText, generalGroup); - setVisibilityFromAudioSettings(); - - - - //This is all the second tab, the default values for various buildings - for(int t=0; tbuildingsTypes.getByType(name, l, false); - if(type != NULL && (type->foodable || type->fillable)) - { - int size = addDefaultUnitAssignmentWidget(t, l*2+1, group_current_column_x, 100 + 40*group_row, 1); - group_widest_element = std::max(group_widest_element, size); - - group_row += 1; - if(group_row == 4) - { - group_row = 0; - group_current_column_x += group_widest_element + 10; - group_widest_element = 0; - } - } - } - } - - group_row=0; - group_current_column_x=20; - group_widest_element=0; - ///Second group, new construction - for(int t=0; tbuildingsTypes.getByType(name, 0, true) != NULL) - { - int size = addDefaultUnitAssignmentWidget(t, 0, group_current_column_x, 100 + 40*group_row, 2); - group_widest_element = std::max(group_widest_element, size); - - group_row += 1; - if(group_row == 6) - { - group_row = 0; - group_current_column_x += group_widest_element + 10; - group_widest_element = 0; - } - } - } - - ///Third group, upgrades - group_row=0; - group_current_column_x=20; - group_widest_element=0; - for(int l=1; l<3; ++l) - { - for(int t=0; tbuildingsTypes.getByType(name, l, true) != NULL) - { - int size = addDefaultUnitAssignmentWidget(t, l*2, group_current_column_x, 100 + 40*group_row, 3); - group_widest_element = std::max(group_widest_element, size); - - group_row += 1; - if(group_row == 7) - { - group_row = 0; - group_current_column_x += group_widest_element + 10; - group_widest_element = 0; - } - } - } - } - - group_row=0; - group_current_column_x=20; - group_widest_element=0; - ///On the fourth screen, flags - for(int t=IntBuildingType::EXPLORATION_FLAG; t<=IntBuildingType::CLEARING_FLAG; ++t) - { - int size = addDefaultUnitAssignmentWidget(t, 1, group_current_column_x, 100 + 40*group_row, 4, true); - group_widest_element = std::max(group_widest_element, size); - - group_row += 1; - if(group_row == 8) - { - group_row = 0; - group_current_column_x += group_widest_element + 10; - group_widest_element = 0; - } - } - flagSettingsExplanation = new Text( 10, 100+40*3+10, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[flag settings explanation]")); - - for(int t=IntBuildingType::EXPLORATION_FLAG; t<=IntBuildingType::CLEARING_FLAG; ++t) - { - int size = addDefaultFlagRadiusWidget(t, group_current_column_x, 130 + 40*group_row, 4); - group_widest_element = std::max(group_widest_element, size); - - group_row += 1; - if(group_row == 8) - { - group_row = 0; - group_current_column_x += group_widest_element + 10; - group_widest_element = 0; - } - } - - buildings = new TextButton( 10, 60, 120, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Building Defaults]"), BUILDINGSETTINGS); - constructionsites = new TextButton( 140, 60, 220, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Construction Site Defaults]"), CONSTRUCTIONSITES); - upgrades = new TextButton( 370, 60, 120, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Upgrade Defaults]"), UPGRADES); - flags = new TextButton( 500, 60, 120, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Flag Defaults]"), FLAGSETTINGS); - - unitSettingsExplanation = new Text( 10, 80, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[unit settings explanation]")); - - addWidgetToGroup(unitSettingsExplanation, unitGroup); - addWidgetToGroup(buildings, unitGroup); - addWidgetToGroup(constructionsites, unitGroup); - addWidgetToGroup(upgrades, unitGroup); - addWidgetToGroup(flags, unitGroup); - addWidgetToGroup(flagSettingsExplanation, unitGroup); - - // This is the third tab, the keyboard shortcuts - game_shortcuts=new TextButton( 10, 60, 120, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[game shortcuts]"), GAMESHORTCUTS); - - editor_shortcuts=new TextButton( 140, 60, 120, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[editor shortcuts]"), EDITORSHORTCUTS); - - shortcut_list = new List(20, 110, 325, 160, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); - action_list = new List(365, 110 , 265, 190, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); - select_key_1 = new KeySelector(20, 275, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", 100, 25); - key_2_active = new OnOffButton(125, 275, 25, 25, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, false, SECONDKEY); - select_key_2 = new KeySelector(155, 275, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", 100, 25); - pressedUnpressedSelector = new MultiTextButton(260, 275, 80, 25, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", "", PRESSEDSELECTOR); - add_shortcut = new TextButton(20, 305, 158, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[add shortcut]"), ADDSHORTCUT); - remove_shortcut = new TextButton(188, 305, 157, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[remove shortcut]"), REMOVESHORTCUT); - restore_default_shortcuts = new TextButton(365, 305, 265, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[restore default shortcuts]"), RESTOREDEFAULTSHORTCUTS); + buildOkCancelButtons(); + buildLanguageWidgets(); + buildDisplayWidgets(); + buildGraphicsToggles(); + buildUsernameWidgets(); + buildAudioWidgets(); - pressedUnpressedSelector->clearTexts(); - pressedUnpressedSelector->addText(Toolkit::getStringTable()->getString("[on press]")); - pressedUnpressedSelector->addText(Toolkit::getStringTable()->getString("[on unpress]")); - - addWidgetToGroup(game_shortcuts, keyboardGroup); - addWidgetToGroup(editor_shortcuts, keyboardGroup); - addWidgetToGroup(shortcut_list, keyboardGroup); - addWidgetToGroup(action_list, keyboardGroup); - addWidgetToGroup(select_key_1, keyboardGroup); - addWidgetToGroup(key_2_active, keyboardGroup); - addWidgetToGroup(select_key_2, keyboardGroup); - addWidgetToGroup(pressedUnpressedSelector, keyboardGroup); - addWidgetToGroup(add_shortcut, keyboardGroup); - addWidgetToGroup(remove_shortcut, keyboardGroup); - addWidgetToGroup(restore_default_shortcuts, keyboardGroup); + buildBuildingDefaultsTab(); + buildKeyboardShortcutsTab(); currentMode = GameGUIShortcuts; - activateGroup(generalGroup); gfxAltered = false; } -void SettingsScreen::addNumbersFor(int low, int high, Number* widget) -{ - for(int i=low; i<=high; ++i) - { - widget->add(i); - } -} - - void SettingsScreen::setFullscreen() { if(fullscreen->getState()){ @@ -361,656 +70,319 @@ void SettingsScreen::onAction(Widget *source, Action action, int par1, int par2) { TabScreen::onAction(source, action, par1, par2); if ((action==BUTTON_RELEASED) || (action==BUTTON_SHORTCUT)) - { - if (par1==OK) - { - globalContainer->settings.setUsername(userName->getText()); - globalContainer->settings.language = Toolkit::getStringTable()->getStringInLang("[language-code]", Toolkit::getStringTable()->getLang()); - globalContainer->settings.save(); - mapeditKeyboardManager.saveKeyboardLayout(); - guiKeyboardManager.saveKeyboardLayout(); - endExecute(par1); - } - else if (par1==CANCEL) - { - globalContainer->settings=old_settings; - if (gfxAltered) - updateGfxCtx(); - - Toolkit::getStringTable()->setLang(Toolkit::getStringTable()->getLangCode(globalContainer->settings.language)); - - ///Send the old volume to the mixer - globalContainer->mix->setVolume(globalContainer->settings.musicVolume, globalContainer->settings.voiceVolume, globalContainer->settings.mute); - - endExecute(par1); - } - else if (par1==RESTOREDEFAULTSHORTCUTS) - { - loadDefaultKeyboardShortcuts(); - } - else if(par1==GAMESHORTCUTS) - { - currentMode = GameGUIShortcuts; - updateShortcutList(); - if(shortcut_list->getCount() == 0) - shortcut_list->setSelectionIndex(-1); - else - shortcut_list->setSelectionIndex(0); - updateActionList(); - updateShortcutInfoFromSelection(); - } - else if(par1==EDITORSHORTCUTS) - { - currentMode = MapEditShortcuts; - updateShortcutList(); - if(shortcut_list->getCount() == 0) - shortcut_list->setSelectionIndex(-1); - else - shortcut_list->setSelectionIndex(0); - updateActionList(); - updateShortcutInfoFromSelection(); - } - else if(par1==PRESSEDSELECTOR) - { - } - else if(par1==ADDSHORTCUT) - { - addNewShortcut(); - } - else if(par1==REMOVESHORTCUT) - { - removeShortcut(); - } - else if(par1==BUILDINGSETTINGS) - { - activateDefaultAssignedGroupNumber(1); - } - else if(par1==CONSTRUCTIONSITES) - { - activateDefaultAssignedGroupNumber(2); - } - else if(par1==UPGRADES) - { - activateDefaultAssignedGroupNumber(3); - } - else if(par1==FLAGSETTINGS) - { - activateDefaultAssignedGroupNumber(4); - } - } + handleButtonAction(par1); else if (action==NUMBER_ELEMENT_SELECTED) - { - for(int t=0; tgetNth() == 0) - unitRatios[t][l]->setNth(1); - globalContainer->settings.defaultUnitsAssigned[t][l]=unitRatios[t][l]->getNth(); - } - } - } - for(int t=0; t<3; ++t) - { - if(flagRadii[t]) - { - globalContainer->settings.defaultFlagRadius[t] = flagRadii[t]->getNth()+1; - } - } - } + flushDefaultsToSettings(); else if (action==LIST_ELEMENT_SELECTED) - { - if (source==languageList) - { - Toolkit::getStringTable()->setLang(par1); - ok->setText(Toolkit::getStringTable()->getString("[ok]")); - cancel->setText(Toolkit::getStringTable()->getString("[Cancel]")); - - modifyTitle(generalGroup, Toolkit::getStringTable()->getString("[general settings]")); - modifyTitle(unitGroup, Toolkit::getStringTable()->getString("[building settings]")); - modifyTitle(keyboardGroup, Toolkit::getStringTable()->getString("[keyboard settings]")); - - modeListNote->setText(Toolkit::getStringTable()->getString("[no fullscreen]")); - language->setText(Toolkit::getStringTable()->getString("[language-tr]")); - display->setText(Toolkit::getStringTable()->getString("[display]")); - usernameText->setText(Toolkit::getStringTable()->getString("[username]")); - audio->setText(Toolkit::getStringTable()->getString("[audio]")); - - fullscreenText->setText(Toolkit::getStringTable()->getString("[fullscreen]")); - usegpuText->setText(Toolkit::getStringTable()->getString("[OpenGL]")); - lowqualityText->setText(Toolkit::getStringTable()->getString("[lowquality]")); - customcurText->setText(Toolkit::getStringTable()->getString("[customcur]")); - - - rememberUnitText->setText(Toolkit::getStringTable()->getString("[remember unit]")); - scrollwheelText->setText(Toolkit::getStringTable()->getString("[scroll wheel enabled]")); - - musicVolText->setText(Toolkit::getStringTable()->getString("[Music volume]")); - audioMuteText->setText(Toolkit::getStringTable()->getString("[mute]")); - - rebootWarning->setText(Toolkit::getStringTable()->getString("[Warning, you need to reboot the game for changes to take effect]")); - - unitSettingsExplanation->setText(Toolkit::getStringTable()->getString("[unit settings explanation]")); - buildings->setText(Toolkit::getStringTable()->getString("[Building Defaults]")); - flags->setText(Toolkit::getStringTable()->getString("[Flag Defaults]")); - constructionsites->setText(Toolkit::getStringTable()->getString("[Construction Site Defaults]")); - upgrades->setText(Toolkit::getStringTable()->getString("[Upgrade Defaults]")); - setLanguageTextsForDefaultAssignmentWidgets(); - - game_shortcuts->setText(Toolkit::getStringTable()->getString("[game shortcuts]")); - editor_shortcuts->setText(Toolkit::getStringTable()->getString("[editor shortcuts]")); - restore_default_shortcuts->setText(Toolkit::getStringTable()->getString("[restore default shortcuts]")); - add_shortcut->setText(Toolkit::getStringTable()->getString("[add shortcut]")); - remove_shortcut->setText(Toolkit::getStringTable()->getString("[remove shortcut]")); - - pressedUnpressedSelector->clearTexts(); - pressedUnpressedSelector->addText(Toolkit::getStringTable()->getString("[on press]")); - pressedUnpressedSelector->addText(Toolkit::getStringTable()->getString("[on unpress]")); - } - else if (source==modeList) - { - int w, h, res; - char fso = 0; //full screen only - res = sscanf(modeList->getText(par1).c_str(), "%dx%d %c", &w, &h, &fso); - assert(res >= 2); - globalContainer->settings.screenWidth=w; - globalContainer->settings.screenHeight=h; - if(fso=='*') - { - fullscreen->setState(false); - fullscreen->setClickable(false); - modeListNote->setStyle(Font::Style(Font::STYLE_BOLD, 255, 60, 60)); - } - else - { - fullscreen->setClickable(true); - modeListNote->setStyle(Font::Style(Font::STYLE_NORMAL, 255, 255, 255)); - } - setFullscreen(); - } - else if (source == shortcut_list) - { - updateShortcutInfoFromSelection(); - } - else if(source == action_list) - { - updateKeyboardManagerFromShortcutInfo(); - } - } + handleListSelected(source, par1); else if (action==VALUE_CHANGED) - { - globalContainer->settings.musicVolume = musicVol->getValue(); - globalContainer->settings.voiceVolume = voiceVol->getValue(); - globalContainer->mix->setVolume(globalContainer->settings.musicVolume, globalContainer->settings.voiceVolume, globalContainer->settings.mute); - } + handleValueChanged(); else if (action==BUTTON_STATE_CHANGED) - { - if (source==rememberUnitButton) - { - globalContainer->settings.rememberUnit=rememberUnitButton->getState(); - } - else if (source==scrollwheel) - { - globalContainer->settings.scrollWheelEnabled=scrollwheel->getState(); - scrollWheelEnabled=scrollwheel->getState(); - } - else if (source==lowquality) - { - globalContainer->settings.optionFlags=lowquality->getState() ? GlobalContainer::OPTION_LOW_SPEED_GFX : 0; - } - else if (source==fullscreen) - { - setFullscreen(); - } - else if (source==usegpu) - { - if (usegpu->getState()) - { - globalContainer->settings.screenFlags |= GraphicContext::USEGPU; - } - else - { - globalContainer->settings.screenFlags &= ~(GraphicContext::USEGPU); - } - updateGfxCtx(); - } - else if (source==customcur) - { - if (customcur->getState()) - { - globalContainer->settings.screenFlags |= GraphicContext::CUSTOMCURSOR; - } - else - { - globalContainer->settings.screenFlags &= ~(GraphicContext::CUSTOMCURSOR); - } - updateGfxCtx(); - } - else if (source==audioMute) - { - globalContainer->settings.mute = audioMute->getState(); - globalContainer->mix->setVolume(globalContainer->settings.musicVolume, globalContainer->settings.voiceVolume, globalContainer->settings.mute); - setVisibilityFromAudioSettings(); - } - else if (source==key_2_active) - { - if(key_2_active->getState() == true) - { - select_key_2->setKey(KeyPress()); - select_key_2->visible=true; - } - else - { - select_key_2->visible=false; - } - updateKeyboardManagerFromShortcutInfo(); - } - } + handleButtonStateChanged(source); else if (action==KEY_CHANGED) - { updateKeyboardManagerFromShortcutInfo(); - } -} - - -void SettingsScreen::setVisibilityFromGraphicType(void) -{ - rebootWarning->visible = globalContainer->settings.screenFlags & GraphicContext::USEGPU; -} - -void SettingsScreen::setVisibilityFromAudioSettings(void) -{ - musicVol->visible = !globalContainer->settings.mute; - musicVolText->visible = !globalContainer->settings.mute; - voiceVol->visible = !globalContainer->settings.mute; - voiceVolText->visible = !globalContainer->settings.mute; -} - -void SettingsScreen::updateGfxCtx(void) -{ - if ((globalContainer->settings.screenFlags & GraphicContext::USEGPU) == 0) - globalContainer->gfx->setRes(globalContainer->settings.screenWidth, globalContainer->settings.screenHeight, globalContainer->settings.screenFlags); - setVisibilityFromGraphicType(); - actDisplay->setText(actDisplayModeToString().c_str()); - gfxAltered = true; -} - -std::string SettingsScreen::actDisplayModeToString(void) -{ - std::ostringstream oss; - oss << globalContainer->gfx->getW() << "x" << globalContainer->gfx->getH(); - if (globalContainer->gfx->getOptionFlags() & GraphicContext::USEGPU) - oss << " GL"; - else - oss << " SDL"; - return oss.str(); -} - - - -int SettingsScreen::addDefaultUnitAssignmentWidget(int type, int level, int x, int y, int group, bool flag) -{ - unitRatios[type][level] = new Number(x, y+20, 100, 18, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, 20, "menu"); - addNumbersFor(0, 20, unitRatios[type][level]); - unitRatios[type][level]->setNth(globalContainer->settings.defaultUnitsAssigned[type][level]); - unitRatios[type][level]->visible=false; - addWidgetToGroup(unitRatios[type][level], unitGroup); - - std::string text=getDefaultUnitAssignmentText(type, level, flag); - unitRatioTexts[type][level]=new Text(x, y, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", text); - - addWidgetToGroup(unitRatioTexts[type][level], unitGroup); - unitRatioTexts[type][level]->visible=false; - unitRatioGroupNumbers[type][level] = group; - - return std::max(unitRatioTexts[type][level]->getWidth(), unitRatios[type][level]->getWidth()); } - -int SettingsScreen::addDefaultFlagRadiusWidget(int type, int x, int y, int group) +void SettingsScreen::handleButtonAction(int par1) { - int n = type - IntBuildingType::EXPLORATION_FLAG; - flagRadii[n] = new Number(x, y+20, 100, 18, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, 20, "menu"); - addNumbersFor(1, 20, flagRadii[n]); - flagRadii[n]->setNth(std::max(0, globalContainer->settings.defaultFlagRadius[n]-1)); - flagRadii[n]->visible=false; - addWidgetToGroup(flagRadii[n], unitGroup); - - std::string text=getDefaultUnitAssignmentText(type, 1, true); - flagRadiusTexts[n]=new Text(x, y, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", text); - - addWidgetToGroup(flagRadiusTexts[n], unitGroup); - flagRadiusTexts[n]->visible=false; - flagRadiusGroupNumbers[n] = group; - - return std::max(flagRadiusTexts[n]->getWidth(), flagRadii[n]->getWidth()); -} - + if (par1==OK) + { + globalContainer->settings.setUsername(userName->getText()); + globalContainer->settings.language = Toolkit::getStringTable()->getStringInLang("[language-code]", Toolkit::getStringTable()->getLang()); + globalContainer->settings.save(); + mapeditKeyboardManager.saveKeyboardLayout(); + guiKeyboardManager.saveKeyboardLayout(); + endExecute(par1); + } + else if (par1==CANCEL) + { + globalContainer->settings=old_settings; + if (gfxAltered) + updateGfxCtx(); + Toolkit::getStringTable()->setLang(Toolkit::getStringTable()->getLangCode(globalContainer->settings.language)); -std::string SettingsScreen::getDefaultUnitAssignmentText(int type, int level, bool flag) -{ - std::string name="[" + IntBuildingType::typeFromShortNumber(type) + "]"; - std::string tname = Toolkit::getStringTable()->getString(name.c_str()); + ///Send the old volume to the mixer + globalContainer->mix->setVolume(globalContainer->settings.musicVolume, globalContainer->settings.voiceVolume, globalContainer->settings.mute); - std::string value; - if(flag) + endExecute(par1); + } + else if (par1==RESTOREDEFAULTSHORTCUTS) { - value = tname; + loadDefaultKeyboardShortcuts(); } - else if(level%2 == 0) + else if(par1==GAMESHORTCUTS) { - value = FormatableString(Toolkit::getStringTable()->getString("[build %0 level %1]")).arg(tname).arg(level/2 + 1); + currentMode = GameGUIShortcuts; + updateShortcutList(); + if(shortcut_list->getCount() == 0) + shortcut_list->setSelectionIndex(-1); + else + shortcut_list->setSelectionIndex(0); + updateActionList(); + updateShortcutInfoFromSelection(); } - else if(level == 1 && globalContainer->buildingsTypes.getByType(IntBuildingType::typeFromShortNumber(type), level+1, false) == NULL) + else if(par1==EDITORSHORTCUTS) { - value = tname; + currentMode = MapEditShortcuts; + updateShortcutList(); + if(shortcut_list->getCount() == 0) + shortcut_list->setSelectionIndex(-1); + else + shortcut_list->setSelectionIndex(0); + updateActionList(); + updateShortcutInfoFromSelection(); } - else + else if(par1==PRESSEDSELECTOR) { - value = FormatableString(Toolkit::getStringTable()->getString("[%0 level %1]")).arg(tname).arg(level/2+1); } - return value; -} - - - -void SettingsScreen::setLanguageTextsForDefaultAssignmentWidgets() -{ - for(int t=0; tsetText(getDefaultUnitAssignmentText(t, l, flag)); - } - } + addNewShortcut(); } - for(int t=0; t<3; ++t) + else if(par1==REMOVESHORTCUT) { - if(flagRadiusTexts[t]) - { - flagRadiusTexts[t]->setText(getDefaultUnitAssignmentText(t+IntBuildingType::EXPLORATION_FLAG, 1, true)); - } + removeShortcut(); + } + else if(par1==BUILDINGSETTINGS) + { + activateDefaultAssignedGroupNumber(kBuildingGroupCompleted); + } + else if(par1==CONSTRUCTIONSITES) + { + activateDefaultAssignedGroupNumber(kBuildingGroupNewConstruction); + } + else if(par1==UPGRADES) + { + activateDefaultAssignedGroupNumber(kBuildingGroupUpgrades); + } + else if(par1==FLAGSETTINGS) + { + activateDefaultAssignedGroupNumber(kBuildingGroupFlags); } } - -void SettingsScreen::activateDefaultAssignedGroupNumber(int group) +void SettingsScreen::handleListSelected(Widget* source, int par1) { - for(int i=0; ivisible=true; - if(unitRatioTexts[i][j]) - unitRatioTexts[i][j]->visible=true; - } - else - { - if(unitRatios[i][j]) - unitRatios[i][j]->visible=false; - if(unitRatioTexts[i][j]) - unitRatioTexts[i][j]->visible=false; - } - } + Toolkit::getStringTable()->setLang(par1); + retranslateUiStrings(); } - for(int i=0; i<3; ++i) + else if (source==modeList) { - - if(flagRadiusGroupNumbers[i] == group) + int w, h, res; + char fso = 0; //full screen only + res = sscanf(modeList->getText(par1).c_str(), "%dx%d %c", &w, &h, &fso); + assert(res >= 2); + globalContainer->settings.screenWidth=w; + globalContainer->settings.screenHeight=h; + if(fso=='*') { - if(flagRadii[i]) - flagRadii[i]->visible=true; - if(flagRadiusTexts[i]) - flagRadiusTexts[i]->visible=true; + fullscreen->setState(false); + fullscreen->setClickable(false); + modeListNote->setStyle(Font::Style(Font::STYLE_BOLD, 255, 60, 60)); } else { - if(flagRadii[i]) - flagRadii[i]->visible=false; - if(flagRadiusTexts[i]) - flagRadiusTexts[i]->visible=false; + fullscreen->setClickable(true); + modeListNote->setStyle(Font::Style(Font::STYLE_NORMAL, 255, 255, 255)); } + setFullscreen(); + } + else if (source == shortcut_list) + { + updateShortcutInfoFromSelection(); + } + else if(source == action_list) + { + updateKeyboardManagerFromShortcutInfo(); } - if(group == 4) - flagSettingsExplanation->visible=true; - else - flagSettingsExplanation->visible=false; } -void SettingsScreen::onGroupActivated(int group_n) +void SettingsScreen::handleValueChanged() { - if(group_n == generalGroup) + globalContainer->settings.musicVolume = musicVol->getValue(); + globalContainer->settings.voiceVolume = voiceVol->getValue(); + globalContainer->mix->setVolume(globalContainer->settings.musicVolume, globalContainer->settings.voiceVolume, globalContainer->settings.mute); +} + + +void SettingsScreen::handleButtonStateChanged(Widget* source) +{ + if (source==rememberUnitButton) { - setVisibilityFromAudioSettings(); + globalContainer->settings.rememberUnit=rememberUnitButton->getState(); } - else if(group_n == unitGroup) + else if (source==scrollwheel) { - activateDefaultAssignedGroupNumber(1); + globalContainer->settings.scrollWheelEnabled=scrollwheel->getState(); + scrollWheelEnabled=scrollwheel->getState(); } - else if(group_n == keyboardGroup) + else if (source==lowquality) { - currentMode = GameGUIShortcuts; - updateShortcutList(); - if(shortcut_list->getCount() == 0) - shortcut_list->setSelectionIndex(-1); - else - shortcut_list->setSelectionIndex(0); - updateActionList(); - updateShortcutInfoFromSelection(); + globalContainer->settings.optionFlags=lowquality->getState() ? GlobalContainer::OPTION_LOW_SPEED_GFX : 0; } -} - -void SettingsScreen::updateShortcutList(int an) -{ - KeyboardManager* m = NULL; - if(currentMode == GameGUIShortcuts) - m = &guiKeyboardManager; - else if(currentMode == MapEditShortcuts) - m = &mapeditKeyboardManager; - - const std::list& shortcuts = m->getKeyboardShortcuts(); - size_t n = 0; - for(std::list::const_iterator i = shortcuts.begin(); i!=shortcuts.end(); ++i) + else if (source==fullscreen) { - if(an==-1 || int(n) == an) + setFullscreen(); + } + else if (source==usegpu) + { + if (usegpu->getState()) + { + globalContainer->settings.screenFlags |= GraphicContext::USEGPU; + } + else { - std::string name = i->formatTranslated(currentMode); - if(n >= shortcut_list->getCount()) - shortcut_list->addText(name); - else if(shortcut_list->getText(n) != name) - shortcut_list->setText(n, name); + globalContainer->settings.screenFlags &= ~(GraphicContext::USEGPU); } - n += 1; + updateGfxCtx(); } - //Remove entries that are off the end - while(n < shortcut_list->getCount()) - shortcut_list->removeText(n); -} - - - -void SettingsScreen::updateActionList() -{ - action_list->clear(); - if(shortcut_list->getSelectionIndex() != -1) + else if (source==customcur) { - if(currentMode == GameGUIShortcuts) + if (customcur->getState()) { - for(int i=GameGUIKeyActions::DoNothing; iaddText(Toolkit::getStringTable()->getString(key.c_str())); - } + globalContainer->settings.screenFlags |= GraphicContext::CUSTOMCURSOR; } - else if(currentMode == MapEditShortcuts) + else { - for(int i=MapEditKeyActions::DoNothing; iaddText(Toolkit::getStringTable()->getString(key.c_str())); - } + globalContainer->settings.screenFlags &= ~(GraphicContext::CUSTOMCURSOR); } + updateGfxCtx(); } -} - - - -void SettingsScreen::updateShortcutInfoFromSelection() -{ - KeyboardManager* m = NULL; - if(currentMode == GameGUIShortcuts) - m = &guiKeyboardManager; - else if(currentMode == MapEditShortcuts) - m = &mapeditKeyboardManager; - - const std::list& shortcuts = m->getKeyboardShortcuts(); - int selection_n = shortcut_list->getSelectionIndex(); - - if(selection_n == -1) + else if (source==audioMute) { - select_key_1->visible=false; - key_2_active->visible=false; - select_key_2->visible=false; - action_list->visible=false; + globalContainer->settings.mute = audioMute->getState(); + globalContainer->mix->setVolume(globalContainer->settings.musicVolume, globalContainer->settings.voiceVolume, globalContainer->settings.mute); + setVisibilityFromAudioSettings(); } - else + else if (source==key_2_active) { - std::list::const_iterator i = shortcuts.begin(); - std::advance(i, selection_n); - select_key_1->setKey(i->getKeyPress(0)); - if(i->getKeyPressCount() == 1) + if(key_2_active->getState() == true) { - key_2_active->setState(false); - select_key_2->visible=false; + select_key_2->setKey(KeyPress()); + select_key_2->visible=true; } else { - select_key_2->setKey(i->getKeyPress(1)); - key_2_active->setState(true); - select_key_2->visible=true; + select_key_2->visible=false; } - - if(i->getKeyPress(0).getPressed()) - pressedUnpressedSelector->setIndex(0); - else - pressedUnpressedSelector->setIndex(1); - - action_list->setSelectionIndex(i->getAction()); - action_list->centerOnItem(action_list->getSelectionIndex()); + updateKeyboardManagerFromShortcutInfo(); } } - -void SettingsScreen::updateKeyboardManagerFromShortcutInfo() +void SettingsScreen::retranslateUiStrings() { - KeyboardManager* m = NULL; - if(currentMode == GameGUIShortcuts) - m = &guiKeyboardManager; - else if(currentMode == MapEditShortcuts) - m = &mapeditKeyboardManager; + ok->setText(Toolkit::getStringTable()->getString("[ok]")); + cancel->setText(Toolkit::getStringTable()->getString("[Cancel]")); - std::list& shortcuts = m->getKeyboardShortcuts(); - int selection_n = shortcut_list->getSelectionIndex(); + modifyTitle(generalGroup, Toolkit::getStringTable()->getString("[general settings]")); + modifyTitle(unitGroup, Toolkit::getStringTable()->getString("[building settings]")); + modifyTitle(keyboardGroup, Toolkit::getStringTable()->getString("[keyboard settings]")); - if(selection_n != -1) - { - std::list::iterator i = shortcuts.begin(); - std::advance(i, selection_n); - KeyboardShortcut new_shortcut; - - KeyPress first = KeyPress(select_key_1->getKey(), (pressedUnpressedSelector->getIndex() == 0 ? true : false)); - KeyPress second = KeyPress(select_key_2->getKey(), (pressedUnpressedSelector->getIndex() == 0 ? true : false)); - - new_shortcut.addKeyPress(first); - if(key_2_active->getState()) - new_shortcut.addKeyPress(second); - new_shortcut.setAction(action_list->getSelectionIndex()); - (*i) = new_shortcut; - updateShortcutList(selection_n); - } -} + modeListNote->setText(Toolkit::getStringTable()->getString("[no fullscreen]")); + language->setText(Toolkit::getStringTable()->getString("[language-tr]")); + display->setText(Toolkit::getStringTable()->getString("[display]")); + usernameText->setText(Toolkit::getStringTable()->getString("[username]")); + audio->setText(Toolkit::getStringTable()->getString("[audio]")); + fullscreenText->setText(Toolkit::getStringTable()->getString("[fullscreen]")); + usegpuText->setText(Toolkit::getStringTable()->getString("[OpenGL]")); + lowqualityText->setText(Toolkit::getStringTable()->getString("[lowquality]")); + customcurText->setText(Toolkit::getStringTable()->getString("[customcur]")); -void SettingsScreen::loadDefaultKeyboardShortcuts() -{ - KeyboardManager* m = NULL; - if(currentMode == GameGUIShortcuts) - m = &guiKeyboardManager; - else if(currentMode == MapEditShortcuts) - m = &mapeditKeyboardManager; - m->loadDefaultShortcuts(); - updateShortcutList(); - updateShortcutInfoFromSelection(); + rememberUnitText->setText(Toolkit::getStringTable()->getString("[remember unit]")); + scrollwheelText->setText(Toolkit::getStringTable()->getString("[scroll wheel enabled]")); + + musicVolText->setText(Toolkit::getStringTable()->getString("[Music volume]")); + audioMuteText->setText(Toolkit::getStringTable()->getString("[mute]")); + + rebootWarning->setText(Toolkit::getStringTable()->getString("[Warning, you need to reboot the game for changes to take effect]")); + + unitSettingsExplanation->setText(Toolkit::getStringTable()->getString("[unit settings explanation]")); + buildings->setText(Toolkit::getStringTable()->getString("[Building Defaults]")); + flags->setText(Toolkit::getStringTable()->getString("[Flag Defaults]")); + constructionsites->setText(Toolkit::getStringTable()->getString("[Construction Site Defaults]")); + upgrades->setText(Toolkit::getStringTable()->getString("[Upgrade Defaults]")); + setLanguageTextsForDefaultAssignmentWidgets(); + + game_shortcuts->setText(Toolkit::getStringTable()->getString("[game shortcuts]")); + editor_shortcuts->setText(Toolkit::getStringTable()->getString("[editor shortcuts]")); + restore_default_shortcuts->setText(Toolkit::getStringTable()->getString("[restore default shortcuts]")); + add_shortcut->setText(Toolkit::getStringTable()->getString("[add shortcut]")); + remove_shortcut->setText(Toolkit::getStringTable()->getString("[remove shortcut]")); + + pressedUnpressedSelector->clearTexts(); + pressedUnpressedSelector->addText(Toolkit::getStringTable()->getString("[on press]")); + pressedUnpressedSelector->addText(Toolkit::getStringTable()->getString("[on unpress]")); } +void SettingsScreen::setVisibilityFromGraphicType(void) +{ + rebootWarning->visible = globalContainer->settings.screenFlags & GraphicContext::USEGPU; +} -void SettingsScreen::addNewShortcut() +void SettingsScreen::setVisibilityFromAudioSettings(void) { - KeyboardShortcut ks; - ks.addKeyPress(KeyPress()); - if(currentMode == GameGUIShortcuts) - { - ks.setAction(GameGUIKeyActions::DoNothing); - std::list& shortcuts = guiKeyboardManager.getKeyboardShortcuts(); - shortcuts.push_back(ks); - } - else if(currentMode == MapEditShortcuts) - { - ks.setAction(MapEditKeyActions::DoNothing); - std::list& shortcuts = mapeditKeyboardManager.getKeyboardShortcuts(); - shortcuts.push_back(ks); - } - updateShortcutList(shortcut_list->getCount()); - shortcut_list->setSelectionIndex(shortcut_list->getCount()-1); - shortcut_list->centerOnItem(shortcut_list->getCount()-1); - updateShortcutInfoFromSelection(); + musicVol->visible = !globalContainer->settings.mute; + musicVolText->visible = !globalContainer->settings.mute; + voiceVol->visible = !globalContainer->settings.mute; + voiceVolText->visible = !globalContainer->settings.mute; +} + +void SettingsScreen::updateGfxCtx(void) +{ + if ((globalContainer->settings.screenFlags & GraphicContext::USEGPU) == 0) + globalContainer->gfx->setRes(globalContainer->settings.screenWidth, globalContainer->settings.screenHeight, globalContainer->settings.screenFlags); + setVisibilityFromGraphicType(); + actDisplay->setText(actDisplayModeToString().c_str()); + gfxAltered = true; } +std::string SettingsScreen::actDisplayModeToString(void) +{ + std::ostringstream oss; + oss << globalContainer->gfx->getW() << "x" << globalContainer->gfx->getH(); + if (globalContainer->gfx->getOptionFlags() & GraphicContext::USEGPU) + oss << " GL"; + else + oss << " SDL"; + return oss.str(); +} -void SettingsScreen::removeShortcut() +void SettingsScreen::onGroupActivated(int group_n) { - int selection_n = shortcut_list->getSelectionIndex(); - if(currentMode == GameGUIShortcuts) + if(group_n == generalGroup) + { + setVisibilityFromAudioSettings(); + } + else if(group_n == unitGroup) { - std::list& shortcuts = guiKeyboardManager.getKeyboardShortcuts(); - std::list::iterator i = shortcuts.begin(); - std::advance(i, selection_n); - shortcuts.erase(i); + activateDefaultAssignedGroupNumber(kBuildingGroupCompleted); } - else if(currentMode == MapEditShortcuts) + else if(group_n == keyboardGroup) { - std::list& shortcuts = mapeditKeyboardManager.getKeyboardShortcuts(); - std::list::iterator i = shortcuts.begin(); - std::advance(i, selection_n); - shortcuts.erase(i); + currentMode = GameGUIShortcuts; + updateShortcutList(); + if(shortcut_list->getCount() == 0) + shortcut_list->setSelectionIndex(-1); + else + shortcut_list->setSelectionIndex(0); + updateActionList(); + updateShortcutInfoFromSelection(); } - shortcut_list->setSelectionIndex(std::max(0, selection_n-1)); - updateShortcutList(); - updateShortcutInfoFromSelection(); } - int SettingsScreen::menu(void) { return SettingsScreen().execute(globalContainer->gfx, 30); } - std::string value; diff --git a/src/SettingsScreen.h b/src/SettingsScreen.h index b632f754a..363da6fa2 100644 --- a/src/SettingsScreen.h +++ b/src/SettingsScreen.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __SETTINGSSCREEN_H -#define __SETTINGSSCREEN_H +#pragma once #include "Glob2Screen.h" #include "Settings.h" @@ -61,7 +43,6 @@ class SettingsScreen : public Glob2TabScreen GAMESHORTCUTS=13, EDITORSHORTCUTS=14, SECONDKEY=15, - PRESSEDSELECTOR=15, ADDSHORTCUT=16, REMOVESHORTCUT=17, SCROLLWHEEL=18, @@ -69,7 +50,17 @@ class SettingsScreen : public Glob2TabScreen CONSTRUCTIONSITES=20, UPGRADES=21, FLAGSETTINGS=22, + PRESSEDSELECTOR=23, }; + + // IDs for the four sub-groups inside the "Building Defaults" tab. Stored in + // unitRatioGroupNumbers / flagRadiusGroupNumbers and matched in + // activateDefaultAssignedGroupNumber to control which set of widgets is + // currently visible. + static constexpr int kBuildingGroupCompleted = 1; + static constexpr int kBuildingGroupNewConstruction = 2; + static constexpr int kBuildingGroupUpgrades = 3; + static constexpr int kBuildingGroupFlags = 4; private: Settings old_settings; List *languageList; @@ -97,6 +88,32 @@ class SettingsScreen : public Glob2TabScreen void addNumbersFor(int low, int high, Number* widget); + // Constructor helpers — each builds a logical chunk of widgets for the screen. + // Split out so the construction order reads top-to-bottom without buried sub-loops. + void buildOkCancelButtons(); + void buildLanguageWidgets(); + void buildDisplayWidgets(); + void buildGraphicsToggles(); + void buildUsernameWidgets(); + void buildAudioWidgets(); + void buildBuildingDefaultsTab(); + void buildCompletedBuildingsGroup(); + void buildNewConstructionGroup(); + void buildUpgradesGroup(); + void buildFlagsGroup(); + void buildKeyboardShortcutsTab(); + + // onAction dispatch helpers — one per event kind. + void handleButtonAction(int par1); + void flushDefaultsToSettings(); + void handleListSelected(Widget* source, int par1); + void handleValueChanged(); + void handleButtonStateChanged(Widget* source); + // Re-applies the current locale to every string-bearing widget. Called after the + // user picks a new language in the language list — every label, button, and text + // has to be re-resolved against the new string table. + void retranslateUiStrings(); + TextButton* game_shortcuts; TextButton* editor_shortcuts; TextButton* restore_default_shortcuts; @@ -171,4 +188,3 @@ class SettingsScreen : public Glob2TabScreen static int menu(void); }; -#endif diff --git a/src/SettingsScreenBuildings.cpp b/src/SettingsScreenBuildings.cpp new file mode 100644 index 000000000..0bb4630ea --- /dev/null +++ b/src/SettingsScreenBuildings.cpp @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// All construction and event handling for the "Building Defaults" tab of the +// settings screen. Split out of SettingsScreen.cpp to keep each file under +// 500 lines and to isolate the four near-identical sub-group loops where the +// differences (which type filter, which level, which row-wrap threshold) are +// the easy place for transcription bugs to slip in. + +#include "SettingsScreen.h" +#include "GlobalContainer.h" +#include +#include +#include +#include +#include +#include +#include +#include "FormatableString.h" + +namespace +{ + // Per-group column-wrap thresholds: number of rows before the layout starts a + // new column. Tuned so each group fits within its available vertical space. + constexpr int kRowsCompleted = 4; + constexpr int kRowsNewConstruction = 6; + constexpr int kRowsUpgrades = 7; + constexpr int kRowsFlags = 8; +} + + +void SettingsScreen::addNumbersFor(int low, int high, Number* widget) +{ + for(int i=low; i<=high; ++i) + { + widget->add(i); + } +} + + +void SettingsScreen::buildBuildingDefaultsTab() +{ + // Initialise the per-building unit-ratio widget grid to null. Each cell is + // populated lazily by addDefaultUnitAssignmentWidget() in the four sub-group + // builds below, but the cells that never apply (flag types in non-flag groups, + // missing building levels) must stay null so activateDefaultAssignedGroupNumber() + // and flushDefaultsToSettings() can null-check them. + for(int t=0; tgetString("[Building Defaults]"), BUILDINGSETTINGS); + constructionsites = new TextButton( 140, 60, 220, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Construction Site Defaults]"), CONSTRUCTIONSITES); + upgrades = new TextButton( 370, 60, 120, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Upgrade Defaults]"), UPGRADES); + flags = new TextButton( 500, 60, 120, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Flag Defaults]"), FLAGSETTINGS); + + unitSettingsExplanation = new Text( 10, 80, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[unit settings explanation]")); + + addWidgetToGroup(unitSettingsExplanation, unitGroup); + addWidgetToGroup(buildings, unitGroup); + addWidgetToGroup(constructionsites, unitGroup); + addWidgetToGroup(upgrades, unitGroup); + addWidgetToGroup(flags, unitGroup); + addWidgetToGroup(flagSettingsExplanation, unitGroup); +} + + +void SettingsScreen::buildCompletedBuildingsGroup() +{ + // Group 1 — already-built buildings that accept units (foodable or fillable). + // Iterates all non-flag building types and the three completed levels (1, 3, 5). + int group_row=0; + int group_current_column_x=20; + int group_widest_element=0; + + for(int t=0; tbuildingsTypes.getByType(name, l, false); + if(type != NULL && (type->foodable || type->fillable)) + { + int size = addDefaultUnitAssignmentWidget(t, l*2+1, group_current_column_x, 100 + 40*group_row, kBuildingGroupCompleted); + group_widest_element = std::max(group_widest_element, size); + + group_row += 1; + if(group_row == kRowsCompleted) + { + group_row = 0; + group_current_column_x += group_widest_element + 10; + group_widest_element = 0; + } + } + } + } +} + + +void SettingsScreen::buildNewConstructionGroup() +{ + // Group 2 — new construction sites (level 0, before any building has been raised). + // Iterates all building types whose level-0 (under-construction) variant exists. + int group_row=0; + int group_current_column_x=20; + int group_widest_element=0; + + for(int t=0; tbuildingsTypes.getByType(name, 0, true) != NULL) + { + int size = addDefaultUnitAssignmentWidget(t, 0, group_current_column_x, 100 + 40*group_row, kBuildingGroupNewConstruction); + group_widest_element = std::max(group_widest_element, size); + + group_row += 1; + if(group_row == kRowsNewConstruction) + { + group_row = 0; + group_current_column_x += group_widest_element + 10; + group_widest_element = 0; + } + } + } +} + + +void SettingsScreen::buildUpgradesGroup() +{ + // Group 3 — upgrade construction sites (under-construction levels 2 and 4). + // Outer loop walks levels {1, 2} and the slot index l*2 yields {2, 4} — the + // under-construction-upgrade variants. + int group_row=0; + int group_current_column_x=20; + int group_widest_element=0; + + for(int l=1; l<3; ++l) + { + for(int t=0; tbuildingsTypes.getByType(name, l, true) != NULL) + { + int size = addDefaultUnitAssignmentWidget(t, l*2, group_current_column_x, 100 + 40*group_row, kBuildingGroupUpgrades); + group_widest_element = std::max(group_widest_element, size); + + group_row += 1; + if(group_row == kRowsUpgrades) + { + group_row = 0; + group_current_column_x += group_widest_element + 10; + group_widest_element = 0; + } + } + } + } +} + + +void SettingsScreen::buildFlagsGroup() +{ + // Group 4 — flags. Builds two stacked widget rows: per-flag default unit count + // (top), and per-flag default radius (bottom). The explanation text sits between + // them at the row-offset measured from the original layout (3 rows down from + // the first row's y origin). + int group_row=0; + int group_current_column_x=20; + int group_widest_element=0; + + for(int t=IntBuildingType::EXPLORATION_FLAG; t<=IntBuildingType::CLEARING_FLAG; ++t) + { + int size = addDefaultUnitAssignmentWidget(t, 1, group_current_column_x, 100 + 40*group_row, kBuildingGroupFlags, true); + group_widest_element = std::max(group_widest_element, size); + + group_row += 1; + if(group_row == kRowsFlags) + { + group_row = 0; + group_current_column_x += group_widest_element + 10; + group_widest_element = 0; + } + } + flagSettingsExplanation = new Text( 10, 100+40*3+10, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[flag settings explanation]")); + + for(int t=IntBuildingType::EXPLORATION_FLAG; t<=IntBuildingType::CLEARING_FLAG; ++t) + { + int size = addDefaultFlagRadiusWidget(t, group_current_column_x, 130 + 40*group_row, kBuildingGroupFlags); + group_widest_element = std::max(group_widest_element, size); + + group_row += 1; + if(group_row == kRowsFlags) + { + group_row = 0; + group_current_column_x += group_widest_element + 10; + group_widest_element = 0; + } + } +} + + +// Creates a single (label, number-spinner) widget pair for one (building type, level) +// slot. The `level` parameter encodes the slot inside the per-type 6-wide array using +// the parity rule: even = under-construction site for level/2+1, odd = completed +// building at level (level+1)/2. The `flag` flag forces the label to be the bare +// building name (used for flags, which have no construction-level concept). Widgets +// start hidden; activateDefaultAssignedGroupNumber(group) reveals them when its tab +// is active. Returns the wider of the two widgets so the caller can advance its +// column-x cursor. +int SettingsScreen::addDefaultUnitAssignmentWidget(int type, int level, int x, int y, int group, bool flag) +{ + unitRatios[type][level] = new Number(x, y+20, 100, 18, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, 20, "menu"); + addNumbersFor(0, 20, unitRatios[type][level]); + unitRatios[type][level]->setNth(globalContainer->settings.defaultUnitsAssigned[type][level]); + unitRatios[type][level]->visible=false; + addWidgetToGroup(unitRatios[type][level], unitGroup); + + std::string text=getDefaultUnitAssignmentText(type, level, flag); + unitRatioTexts[type][level]=new Text(x, y, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", text); + + addWidgetToGroup(unitRatioTexts[type][level], unitGroup); + unitRatioTexts[type][level]->visible=false; + unitRatioGroupNumbers[type][level] = group; + + return std::max(unitRatioTexts[type][level]->getWidth(), unitRatios[type][level]->getWidth()); +} + + + +// Creates a (label, radius-spinner) pair for one flag type. Indexed by +// (type - EXPLORATION_FLAG) into the flagRadii/flagRadiusTexts arrays. The spinner +// stores radius-1 internally (range 1..20 maps to internal nth 0..19) so the +// settings round-trip preserves user choice with a default of 0 meaning "no +// override". Returns the wider widget for column advancement. +int SettingsScreen::addDefaultFlagRadiusWidget(int type, int x, int y, int group) +{ + int n = type - IntBuildingType::EXPLORATION_FLAG; + flagRadii[n] = new Number(x, y+20, 100, 18, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, 20, "menu"); + addNumbersFor(1, 20, flagRadii[n]); + flagRadii[n]->setNth(std::max(0, globalContainer->settings.defaultFlagRadius[n]-1)); + flagRadii[n]->visible=false; + addWidgetToGroup(flagRadii[n], unitGroup); + + std::string text=getDefaultUnitAssignmentText(type, 1, true); + flagRadiusTexts[n]=new Text(x, y, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", text); + + addWidgetToGroup(flagRadiusTexts[n], unitGroup); + flagRadiusTexts[n]->visible=false; + flagRadiusGroupNumbers[n] = group; + + return std::max(flagRadiusTexts[n]->getWidth(), flagRadii[n]->getWidth()); +} + + + +std::string SettingsScreen::getDefaultUnitAssignmentText(int type, int level, bool flag) +{ + std::string name="[" + IntBuildingType::typeFromShortNumber(type) + "]"; + std::string tname = Toolkit::getStringTable()->getString(name.c_str()); + + std::string value; + if(flag) + { + value = tname; + } + else if(level%2 == 0) + { + // Even level = under-construction site; label as "build level N". + value = FormatableString(Toolkit::getStringTable()->getString("[build %0 level %1]")).arg(tname).arg(level/2 + 1); + } + else if(level == 1 && globalContainer->buildingsTypes.getByType(IntBuildingType::typeFromShortNumber(type), level+1, false) == NULL) + { + // Single-level building (no level-2 variant exists): omit the level suffix. + value = tname; + } + else + { + // Odd level >= 1: completed building; label as " level N". + value = FormatableString(Toolkit::getStringTable()->getString("[%0 level %1]")).arg(tname).arg(level/2+1); + } + return value; +} + + + +void SettingsScreen::setLanguageTextsForDefaultAssignmentWidgets() +{ + for(int t=0; tsetText(getDefaultUnitAssignmentText(t, l, flag)); + } + } + } + for(int t=0; t<3; ++t) + { + if(flagRadiusTexts[t]) + { + flagRadiusTexts[t]->setText(getDefaultUnitAssignmentText(t+IntBuildingType::EXPLORATION_FLAG, 1, true)); + } + } +} + + + +void SettingsScreen::activateDefaultAssignedGroupNumber(int group) +{ + for(int i=0; ivisible=true; + if(unitRatioTexts[i][j]) + unitRatioTexts[i][j]->visible=true; + } + else + { + if(unitRatios[i][j]) + unitRatios[i][j]->visible=false; + if(unitRatioTexts[i][j]) + unitRatioTexts[i][j]->visible=false; + } + } + } + for(int i=0; i<3; ++i) + { + + if(flagRadiusGroupNumbers[i] == group) + { + if(flagRadii[i]) + flagRadii[i]->visible=true; + if(flagRadiusTexts[i]) + flagRadiusTexts[i]->visible=true; + } + else + { + if(flagRadii[i]) + flagRadii[i]->visible=false; + if(flagRadiusTexts[i]) + flagRadiusTexts[i]->visible=false; + } + } + if(group == kBuildingGroupFlags) + flagSettingsExplanation->visible=true; + else + flagSettingsExplanation->visible=false; +} + + +void SettingsScreen::flushDefaultsToSettings() +{ + for(int t=0; tgetNth() == 0) + unitRatios[t][l]->setNth(1); + globalContainer->settings.defaultUnitsAssigned[t][l]=unitRatios[t][l]->getNth(); + } + } + } + for(int t=0; t<3; ++t) + { + if(flagRadii[t]) + { + globalContainer->settings.defaultFlagRadius[t] = flagRadii[t]->getNth()+1; + } + } +} diff --git a/src/SettingsScreenGeneral.cpp b/src/SettingsScreenGeneral.cpp new file mode 100644 index 000000000..2efc100b9 --- /dev/null +++ b/src/SettingsScreenGeneral.cpp @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// Construction of the "General Settings" tab: OK / Cancel buttons (which sit +// on every tab), language list, display / video-mode list, graphics toggles +// (fullscreen / OpenGL / lowquality / customcursor / remember-unit / +// scrollwheel) plus the reboot warning, the username field, and the audio +// section. Split out of SettingsScreen.cpp to keep each file under 500 lines. +// +// Event handling for these widgets lives in SettingsScreen.cpp alongside the +// onAction dispatcher and the GfxContext / audio-mute visibility plumbing. + +#include "SettingsScreen.h" +#include "GlobalContainer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +void SettingsScreen::buildOkCancelButtons() +{ + ok=new TextButton( 230, 420, 180, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[ok]"), OK); + addWidget(ok); + cancel=new TextButton(440, 420, 180, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[Cancel]"), CANCEL); + addWidget(cancel); +} + + +void SettingsScreen::buildLanguageWidgets() +{ + language=new Text(20, 60, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[language-tr]")); + addWidgetToGroup(language, generalGroup); + languageList=new List(20, 90, 180, 200, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); + for (int i=0; igetNumberOfLanguage(); i++) + { + if(!Toolkit::getStringTable()->isLangComplete(i)) + languageList->addText(Toolkit::getStringTable()->getStringInLang("[language incomplete]", i)); + else + languageList->addText(Toolkit::getStringTable()->getStringInLang("[language]", i)); + } + addWidgetToGroup(languageList, generalGroup); +} + + +void SettingsScreen::buildDisplayWidgets() +{ + display=new Text(230, 60, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[display]")); + addWidgetToGroup(display, generalGroup); + actDisplay = new Text(440, 60, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", actDisplayModeToString().c_str()); + addWidgetToGroup(actDisplay, generalGroup); + modeList=new List(440, 90, 180, 190, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); + const auto modes = globalContainer->gfx->listVideoModes(); + const int standardResolutionsCount=5; + int standardResolutions[standardResolutionsCount][2]={{640,480},{800,600},{1024,768},{1280,1024},{1600,1200}}; + for (auto const& mode : modes) + { + std::ostringstream ost; + ost << mode.w << "x" << mode.h; + if (!modeList->isText(ost.str().c_str())) + modeList->addText(ost.str().c_str()); + } + for(int i=0; iisText(ost.str().c_str())) + { + ost << " *"; + modeList->addText(ost.str().c_str()); + } + } + addWidgetToGroup(modeList, generalGroup); + modeListNote=new Text(modeList->getLeft(), modeList->getTop()+modeList->getHeight(), ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[no fullscreen]")); + addWidgetToGroup(modeListNote, generalGroup); +} + + +void SettingsScreen::buildGraphicsToggles() +{ + fullscreen=new OnOffButton(230, 90, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.screenFlags & GraphicContext::FULLSCREEN, FULLSCREEN); + addWidgetToGroup(fullscreen, generalGroup); + fullscreenText=new Text(260, 90, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[fullscreen]"), 180); + addWidgetToGroup(fullscreenText, generalGroup); + + usegpu=new OnOffButton(230, 90 + 30, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.screenFlags & GraphicContext::USEGPU, USEGL); + addWidgetToGroup(usegpu, generalGroup); + usegpuText=new Text(260, 90 + 30, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[OpenGL]"), 180); + addWidgetToGroup(usegpuText, generalGroup); + + lowquality=new OnOffButton(230, 90 + 60, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX, LOWQUALITY); + addWidgetToGroup(lowquality, generalGroup); + lowqualityText=new Text(260, 90 + 60, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[lowquality]"), 180); + addWidgetToGroup(lowqualityText, generalGroup); + + customcur=new OnOffButton(230, 90 + 90, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.screenFlags & GraphicContext::CUSTOMCURSOR, CUSTOMCUR); + addWidgetToGroup(customcur, generalGroup); + customcurText=new Text(260, 90 + 90, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[customcur]"), 180); + addWidgetToGroup(customcurText, generalGroup); + + rememberUnitButton=new OnOffButton(230, 90 + 120, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.rememberUnit, REMEMBERUNIT); + addWidgetToGroup(rememberUnitButton, generalGroup); + rememberUnitText=new Text(260, 90 + 120, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[remember unit]"), 180); + addWidgetToGroup(rememberUnitText, generalGroup); + + scrollwheel=new OnOffButton(230, 90 + 150, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.scrollWheelEnabled, SCROLLWHEEL); + addWidgetToGroup(scrollwheel, generalGroup); + scrollwheelText=new Text(260, 90 + 150, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[scroll wheel enabled]"), 180); + addWidgetToGroup(scrollwheelText, generalGroup); + + rebootWarning=new Text(0, 300, ALIGN_FILL, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Warning, you need to reboot the game for changes to take effect]")); + //TODO: warning style should be defined centrally. + rebootWarning->setStyle(Font::Style(Font::STYLE_BOLD, 255, 60, 60)); + addWidget(rebootWarning); + + setVisibilityFromGraphicType(); + rebootWarning->visible=false; +} + + +void SettingsScreen::buildUsernameWidgets() +{ + userName=new TextInput(20, 360, 180, 25, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", globalContainer->settings.getUsername(), true, 32); + addWidgetToGroup(userName, generalGroup); + usernameText=new Text(20, 330, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[username]")); + addWidgetToGroup(usernameText, generalGroup); +} + + +void SettingsScreen::buildAudioWidgets() +{ + audio=new Text(230, 330, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[audio]"), 300); + addWidgetToGroup(audio, generalGroup); + audioMute=new OnOffButton(230, 365, 20, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, globalContainer->settings.mute, MUTE); + addWidgetToGroup(audioMute, generalGroup); + audioMuteText=new Text(260, 365, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[mute]"), 200); + addWidgetToGroup(audioMuteText, generalGroup); + musicVol=new Selector(320, 350, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, 180, globalContainer->settings.musicVolume, 256, true); + addWidgetToGroup(musicVol, generalGroup); + voiceVol=new Selector(320, 385, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, 180, globalContainer->settings.voiceVolume, 256, true); + addWidgetToGroup(voiceVol, generalGroup); + musicVolText=new Text(320, 330, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Music volume]"), 300); + addWidgetToGroup(musicVolText, generalGroup); + voiceVolText=new Text(320, 365, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[Voice volume]"), 300); + addWidgetToGroup(voiceVolText, generalGroup); + setVisibilityFromAudioSettings(); +} diff --git a/src/SettingsScreenKeyboard.cpp b/src/SettingsScreenKeyboard.cpp new file mode 100644 index 000000000..51dc6789a --- /dev/null +++ b/src/SettingsScreenKeyboard.cpp @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// All construction and event handling for the "Keyboard Shortcuts" tab of the +// settings screen. Split out of SettingsScreen.cpp to keep each file under +// 500 lines. Owns the two KeyboardManager mirrors (game GUI / map editor) +// and the widget state that lets the user list, add, remove, and re-bind +// individual shortcuts. + +#include "SettingsScreen.h" +#include +#include +#include +#include +#include +#include +#include +#include "GameGUIKeyActions.h" +#include "MapEditKeyActions.h" + + +void SettingsScreen::buildKeyboardShortcutsTab() +{ + game_shortcuts=new TextButton( 10, 60, 120, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[game shortcuts]"), GAMESHORTCUTS); + + editor_shortcuts=new TextButton( 140, 60, 120, 20, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[editor shortcuts]"), EDITORSHORTCUTS); + + shortcut_list = new List(20, 110, 325, 160, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); + action_list = new List(365, 110 , 265, 190, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard"); + select_key_1 = new KeySelector(20, 275, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", 100, 25); + key_2_active = new OnOffButton(125, 275, 25, 25, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, false, SECONDKEY); + select_key_2 = new KeySelector(155, 275, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", 100, 25); + pressedUnpressedSelector = new MultiTextButton(260, 275, 80, 25, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", "", PRESSEDSELECTOR); + add_shortcut = new TextButton(20, 305, 158, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[add shortcut]"), ADDSHORTCUT); + remove_shortcut = new TextButton(188, 305, 157, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[remove shortcut]"), REMOVESHORTCUT); + restore_default_shortcuts = new TextButton(365, 305, 265, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "standard", Toolkit::getStringTable()->getString("[restore default shortcuts]"), RESTOREDEFAULTSHORTCUTS); + + pressedUnpressedSelector->clearTexts(); + pressedUnpressedSelector->addText(Toolkit::getStringTable()->getString("[on press]")); + pressedUnpressedSelector->addText(Toolkit::getStringTable()->getString("[on unpress]")); + + addWidgetToGroup(game_shortcuts, keyboardGroup); + addWidgetToGroup(editor_shortcuts, keyboardGroup); + addWidgetToGroup(shortcut_list, keyboardGroup); + addWidgetToGroup(action_list, keyboardGroup); + addWidgetToGroup(select_key_1, keyboardGroup); + addWidgetToGroup(key_2_active, keyboardGroup); + addWidgetToGroup(select_key_2, keyboardGroup); + addWidgetToGroup(pressedUnpressedSelector, keyboardGroup); + addWidgetToGroup(add_shortcut, keyboardGroup); + addWidgetToGroup(remove_shortcut, keyboardGroup); + addWidgetToGroup(restore_default_shortcuts, keyboardGroup); +} + + +void SettingsScreen::updateShortcutList(int an) +{ + KeyboardManager* m = NULL; + if(currentMode == GameGUIShortcuts) + m = &guiKeyboardManager; + else if(currentMode == MapEditShortcuts) + m = &mapeditKeyboardManager; + + const std::list& shortcuts = m->getKeyboardShortcuts(); + size_t n = 0; + for(std::list::const_iterator i = shortcuts.begin(); i!=shortcuts.end(); ++i) + { + if(an==-1 || int(n) == an) + { + std::string name = i->formatTranslated(currentMode); + if(n >= shortcut_list->getCount()) + shortcut_list->addText(name); + else if(shortcut_list->getText(n) != name) + shortcut_list->setText(n, name); + } + n += 1; + } + //Remove entries that are off the end + while(n < shortcut_list->getCount()) + shortcut_list->removeText(n); +} + + + +void SettingsScreen::updateActionList() +{ + action_list->clear(); + if(shortcut_list->selection()) + { + if(currentMode == GameGUIShortcuts) + { + for(int i=GameGUIKeyActions::DoNothing; iaddText(Toolkit::getStringTable()->getString(key.c_str())); + } + } + else if(currentMode == MapEditShortcuts) + { + for(int i=MapEditKeyActions::DoNothing; iaddText(Toolkit::getStringTable()->getString(key.c_str())); + } + } + } +} + + + +void SettingsScreen::updateShortcutInfoFromSelection() +{ + KeyboardManager* m = NULL; + if(currentMode == GameGUIShortcuts) + m = &guiKeyboardManager; + else if(currentMode == MapEditShortcuts) + m = &mapeditKeyboardManager; + + const std::list& shortcuts = m->getKeyboardShortcuts(); + auto sel = shortcut_list->selection(); + + if(!sel) + { + select_key_1->visible=false; + key_2_active->visible=false; + select_key_2->visible=false; + action_list->visible=false; + } + else + { + std::list::const_iterator i = shortcuts.begin(); + std::advance(i, *sel); + select_key_1->setKey(i->getKeyPress(0)); + if(i->getKeyPressCount() == 1) + { + key_2_active->setState(false); + select_key_2->visible=false; + } + else + { + select_key_2->setKey(i->getKeyPress(1)); + key_2_active->setState(true); + select_key_2->visible=true; + } + + if(i->getKeyPress(0).getPressed()) + pressedUnpressedSelector->setIndex(0); + else + pressedUnpressedSelector->setIndex(1); + + action_list->setSelectionIndex(i->getAction()); + action_list->centerOnItem(action_list->getSelectionIndex()); + } +} + + + +void SettingsScreen::updateKeyboardManagerFromShortcutInfo() +{ + KeyboardManager* m = NULL; + if(currentMode == GameGUIShortcuts) + m = &guiKeyboardManager; + else if(currentMode == MapEditShortcuts) + m = &mapeditKeyboardManager; + + std::list& shortcuts = m->getKeyboardShortcuts(); + auto sel = shortcut_list->selection(); + + if(sel) + { + std::list::iterator i = shortcuts.begin(); + std::advance(i, *sel); + KeyboardShortcut new_shortcut; + + KeyPress first = KeyPress(select_key_1->getKey(), (pressedUnpressedSelector->getIndex() == 0 ? true : false)); + KeyPress second = KeyPress(select_key_2->getKey(), (pressedUnpressedSelector->getIndex() == 0 ? true : false)); + + new_shortcut.addKeyPress(first); + if(key_2_active->getState()) + new_shortcut.addKeyPress(second); + new_shortcut.setAction(action_list->getSelectionIndex()); + (*i) = new_shortcut; + updateShortcutList(*sel); + } +} + + + +void SettingsScreen::loadDefaultKeyboardShortcuts() +{ + KeyboardManager* m = NULL; + if(currentMode == GameGUIShortcuts) + m = &guiKeyboardManager; + else if(currentMode == MapEditShortcuts) + m = &mapeditKeyboardManager; + m->loadDefaultShortcuts(); + updateShortcutList(); + updateShortcutInfoFromSelection(); +} + + + +void SettingsScreen::addNewShortcut() +{ + KeyboardShortcut ks; + ks.addKeyPress(KeyPress()); + if(currentMode == GameGUIShortcuts) + { + ks.setAction(GameGUIKeyActions::DoNothing); + std::list& shortcuts = guiKeyboardManager.getKeyboardShortcuts(); + shortcuts.push_back(ks); + } + else if(currentMode == MapEditShortcuts) + { + ks.setAction(MapEditKeyActions::DoNothing); + std::list& shortcuts = mapeditKeyboardManager.getKeyboardShortcuts(); + shortcuts.push_back(ks); + } + updateShortcutList(shortcut_list->getCount()); + shortcut_list->setSelectionIndex(shortcut_list->getCount()-1); + shortcut_list->centerOnItem(shortcut_list->getCount()-1); + updateShortcutInfoFromSelection(); +} + + + +void SettingsScreen::removeShortcut() +{ + int selection_n = shortcut_list->getSelectionIndex(); + if(currentMode == GameGUIShortcuts) + { + std::list& shortcuts = guiKeyboardManager.getKeyboardShortcuts(); + std::list::iterator i = shortcuts.begin(); + std::advance(i, selection_n); + shortcuts.erase(i); + } + else if(currentMode == MapEditShortcuts) + { + std::list& shortcuts = mapeditKeyboardManager.getKeyboardShortcuts(); + std::list::iterator i = shortcuts.begin(); + std::advance(i, selection_n); + shortcuts.erase(i); + } + shortcut_list->setSelectionIndex(std::max(0, selection_n-1)); + updateShortcutList(); + updateShortcutInfoFromSelection(); +} diff --git a/src/SimplexNoise.cpp b/src/SimplexNoise.cpp index 34b3c99e2..634af229c 100644 --- a/src/SimplexNoise.cpp +++ b/src/SimplexNoise.cpp @@ -39,9 +39,6 @@ namespace SimplexNoise { }; */ - static const int F3D = (int)(256 * 1.0/3.0); // 1/3 in 256ths - static const int G3D = (int)(256 * 1.0/6.0); // 1/6 in 256ths - typedef unsigned char byte; #define FLOOR_MASK (~255) #define FRAC_MASK (255) diff --git a/src/SimplexNoise.h b/src/SimplexNoise.h index 6e85342cd..6169b74fb 100644 --- a/src/SimplexNoise.h +++ b/src/SimplexNoise.h @@ -1,5 +1,4 @@ -#ifndef __SIMPLEXNOISE_H__ -#define __SIMPLEXNOISE_H__ +#pragma once namespace SimplexNoise { /** Returns the noise value for a given point. @@ -10,4 +9,3 @@ namespace SimplexNoise { } -#endif diff --git a/src/SoundMixer.cpp b/src/SoundMixer.cpp index 0d592ec52..bbba2cd6f 100644 --- a/src/SoundMixer.cpp +++ b/src/SoundMixer.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "SoundMixer.h" #include "Order.h" @@ -346,28 +330,32 @@ int SoundMixer::loadTrack(const std::string name, int index) return -1; } - OggVorbis_File *oggFile = new OggVorbis_File; + // Hold the OggVorbis_File in a unique_ptr until ownership transfers into + // `tracks` via release(). If ov_open fails, the unique_ptr's destructor frees + // it on return — fixing the leak that existed when this was raw `new`. + auto oggFile = std::make_unique(); #ifdef _MSC_VER - if (ov_open_callbacks(fp, oggFile, NULL, 0, OV_CALLBACKS_DEFAULT) < 0) + if (ov_open_callbacks(fp, oggFile.get(), NULL, 0, OV_CALLBACKS_DEFAULT) < 0) #else - if (ov_open(fp, oggFile, NULL, 0) < 0) + if (ov_open(fp, oggFile.get(), NULL, 0) < 0) #endif { std::cerr << "SoundMixer : File " << name << " does not appear to be an Ogg bitstream." << std::endl; fclose(fp); return -2; } + // ov_open succeeded: the OggVorbis_File now owns `fp` and will close it via ov_clear. SDL_LockAudio(); if (index >= 0 && index< (int)tracks.size()) { ov_clear(tracks[index]); delete tracks[index]; - tracks[index] = oggFile; + tracks[index] = oggFile.release(); } else { - tracks.push_back(oggFile); + tracks.push_back(oggFile.release()); index = (int)tracks.size()-1; } SDL_UnlockAudio(); @@ -380,13 +368,13 @@ void SoundMixer::setNextTrack(unsigned i, bool earlyChange) if ((soundEnabled) && (i= 0) nextTrack = i; else nextTrack = actTrack = i; - + // Select mode if (mode == MODE_STOPPED) { @@ -397,27 +385,36 @@ void SoundMixer::setNextTrack(unsigned i, bool earlyChange) { mode = MODE_EARLY_CHANGE; } - + SDL_UnlockAudio(); } } +int SoundMixer::loadTrack(const std::string name, MusicTrack track) +{ + return loadTrack(name, static_cast(track)); +} + +void SoundMixer::setNextTrack(MusicTrack track, bool earlyChange) +{ + setNextTrack(static_cast(track), earlyChange); +} + +// All writes to musicVolume/voiceVolume must hold SDL_LockAudio — mixaudio() +// reads them on the audio thread. openAudio() is called *before* taking the +// lock: SDL_OpenAudio opens the device in the paused state, so the callback +// cannot fire until SDL_PauseAudio(0) is called from setNextTrack(). void SoundMixer::setVolume(unsigned musicVolume, unsigned voiceVolume, bool mute) { if (!soundEnabled) { - if (!mute) - { - openAudio(); - this->musicVolume = musicVolume; - this->voiceVolume = voiceVolume; - } - else - { + if (mute) return; - } + openAudio(); } - else if (mute) + + SDL_LockAudio(); + if (mute) { this->musicVolume = 0; this->voiceVolume = 0; @@ -427,11 +424,15 @@ void SoundMixer::setVolume(unsigned musicVolume, unsigned voiceVolume, bool mute this->musicVolume = musicVolume; this->voiceVolume = voiceVolume; } + SDL_UnlockAudio(); } +// mode is read by mixaudio() on the audio thread; the write must hold the lock. void SoundMixer::stopMusic(void) { + SDL_LockAudio(); mode = MODE_STOP; + SDL_UnlockAudio(); } @@ -449,7 +450,7 @@ bool SoundMixer::isPlayerTransmittingVoice(int player) } -void SoundMixer::addVoiceData(boost::shared_ptr order) +void SoundMixer::addVoiceData(std::shared_ptr order) { if (soundEnabled) { diff --git a/src/SoundMixer.h b/src/SoundMixer.h index 5d91471c3..f182a545e 100644 --- a/src/SoundMixer.h +++ b/src/SoundMixer.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __SOUNDMIXER_H -#define __SOUNDMIXER_H +#pragma once #include #include @@ -27,7 +10,9 @@ #include #include #include -#include +#include + +#include "MusicTrack.h" class OrderVoiceData; @@ -76,11 +61,24 @@ class SoundMixer ~SoundMixer(); - //! load an ogg file. Return the index in the track list. If index is given, attempt to replace the current track at this index + //! Load an ogg file and add (or replace at `index`) into the track list. + //! Returns the resulting track index on success, -1 if the file cannot be + //! opened, or -2 if it is not a valid ogg bitstream. On success the + //! OggVorbis_File takes ownership of the underlying FILE* and closes it via + //! ov_clear in ~SoundMixer. int loadTrack(const std::string name, int index = -1); + //! Load `name` into the slot for the given enum track. Convenience wrapper + //! over the int-indexed overload so callers don't hard-code track numbers. + int loadTrack(const std::string name, MusicTrack track); + void setNextTrack(unsigned i, bool earlyChange=false); + //! Enum-typed overload of setNextTrack. Prefer this in new code so call + //! sites read as `setNextTrack(MusicTrack::WarEvent, true)` rather than + //! `setNextTrack(4, true)`. + void setNextTrack(MusicTrack track, bool earlyChange=false); + void setVolume(unsigned musicVolume, unsigned voiceVolume, bool mute); void stopMusic(void); @@ -89,10 +87,9 @@ class SoundMixer bool isPlayerTransmittingVoice(int player); //! Add voice data from order. Data should be copied as order will be destroyed after this call - void addVoiceData(boost::shared_ptr order); + void addVoiceData(std::shared_ptr order); }; -#endif diff --git a/src/Team.cpp b/src/Team.cpp deleted file mode 100644 index 309f0542b..000000000 --- a/src/Team.cpp +++ /dev/null @@ -1,1360 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -//#include -#include - -#include -#include - -#include "BuildingType.h" -#include "Game.h" -#include "GlobalContainer.h" -#include "LogFileManager.h" -#include "Marshaling.h" -#include "NetConsts.h" -#include "Team.h" -#include "Unit.h" -#include "Utilities.h" -#include "Player.h" -#include "Integrity.h" - -Team::Team(Game *game) -:BaseTeam() -{ - logFile = globalContainer->logFileManager->getFile("Team.log"); - assert(game); - this->game=game; - this->map=&game->map; - init(); -} - - - - -Team::Team(GAGCore::InputStream *stream, Game *game, Sint32 versionMinor) -:BaseTeam() -{ - logFile = globalContainer->logFileManager->getFile("Team.log"); - assert(game); - this->game=game; - this->map=&game->map; - init(); - bool success = load(stream, &(globalContainer->buildingsTypes), versionMinor); - assert(success); -} - - - - -Team::~Team() -{ - if (!disableRecursiveDestruction) - { - clearMem(); - delete [] myUnits; - delete [] myBuildings; - } -} - - - - -void Team::init(void) -{ - myUnits = new Unit*[Unit::MAX_COUNT]; - myBuildings = new Building*[Building::MAX_COUNT]; - for (int i=0; iteamNumber; - numberOfPlayer=initial->numberOfPlayer; - playersMask=initial->playersMask; - fprintf(logFile, "Team::setBaseTeam(), teamNumber=%d, playersMask=%d\n", teamNumber, playersMask); - - setCorrectColor(initial->color); - setCorrectMasks(); -} - - - - -bool Team::load(GAGCore::InputStream *stream, BuildingsTypes *buildingstypes, Sint32 versionMinor) -{ - assert(stream); - assert(buildingsToBeDestroyed.size()==0); - buildingsTryToBuildingSiteRoom.clear(); - - // loading baseteam - if(!BaseTeam::load(stream, versionMinor)) - return false; - - stream->readEnterSection("Team"); - - // normal load - stream->readEnterSection("myUnits"); - for (int i=0; ireadEnterSection(i); - Uint32 isUsed = stream->readUint32("isUsed"); - if (isUsed) - myUnits[i] = new Unit(stream, this, versionMinor); - else - myUnits[i] = NULL; - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - swarms.clear(); - turrets.clear(); - canExchange.clear(); - virtualBuildings.clear(); - clearingFlags.clear(); - - prestige = 0; - stream->readEnterSection("myBuildings"); - for (int i=0; ireadEnterSection(i); - Uint32 isUsed = stream->readUint32("isUsed"); - if (isUsed) - { - myBuildings[i] = new Building(stream, buildingstypes, this, versionMinor); - if (myBuildings[i]->type->unitProductionTime) - swarms.push_back(myBuildings[i]); - if (myBuildings[i]->type->shootingRange) - turrets.push_back(myBuildings[i]); - if (myBuildings[i]->type->canExchange) - canExchange.push_back(myBuildings[i]); - if (myBuildings[i]->type->isVirtual) - virtualBuildings.push_back(myBuildings[i]); - if (myBuildings[i]->type->zonable[WORKER]) - clearingFlags.push_back(myBuildings[i]); - } - else - myBuildings[i] = NULL; - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - // resolve cross reference - stream->readEnterSection("myUnits"); - for (int i=0; ireadEnterSection(i); - myUnits[i]->loadCrossRef(stream, this, versionMinor); - stream->readLeaveSection(); - } - } - stream->readLeaveSection(); - - stream->readEnterSection("myBuildings"); - for (int i=0; ireadEnterSection(i); - myBuildings[i]->loadCrossRef(stream, buildingstypes, this, versionMinor); - if (myBuildings[i]->type->canExchange) - canExchange.push_back(myBuildings[i]); - stream->readLeaveSection(); - } - } - stream->readLeaveSection(); - - allies = stream->readUint32("allies"); - enemies = stream->readUint32("enemies"); - sharedVisionExchange = stream->readUint32("sharedVisionExchange"); - sharedVisionFood = stream->readUint32("sharedVisionFood"); - sharedVisionOther = stream->readUint32("sharedVisionOther"); - me = stream->readUint32("me"); - startPosX = stream->readSint32("startPosX"); - startPosY = stream->readSint32("startPosY"); - startPosSet = stream->readSint32("startPosSet"); - unitConversionLost = stream->readSint32("unitConversionLost"); - unitConversionGained = stream->readSint32("unitConversionGained"); - - stream->readEnterSection("teamRessources"); - for (unsigned int i=0; ireadEnterSection(i); - teamRessources[i] = stream->readUint32("teamRessources"); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - - for(int i=0; ireadLeaveSection(); - return false; - } - stats.step(this, true); - - if(versionMinor >= 73) - { - if(!race.load(stream, versionMinor)) - { - stream->readLeaveSection(); - return false; - } - } - else - { - race.load(); - } - - isAlive = true; - - stream->readLeaveSection(); - return true; -} - - - - -void Team::save(GAGCore::OutputStream *stream) -{ - // saving baseteam - BaseTeam::save(stream); - - stream->writeEnterSection("Team"); - - // saving team - stream->writeEnterSection("myUnits"); - for (int i=0; iwriteEnterSection(i); - if (myUnits[i]) - { - stream->writeUint32(true, "isUsed"); - myUnits[i]->save(stream); - } - else - { - stream->writeUint32(false, "isUsed"); - } - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->writeEnterSection("myBuildings"); - for (int i=0; iwriteEnterSection(i); - if (myBuildings[i]) - { - stream->writeUint32(true, "isUsed"); - myBuildings[i]->save(stream); - } - else - { - stream->writeUint32(false, "isUsed"); - } - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - // save cross reference - stream->writeEnterSection("myUnits"); - for (int i=0; iwriteEnterSection(i); - myUnits[i]->saveCrossRef(stream); - stream->writeLeaveSection(); - } - } - stream->writeLeaveSection(); - - stream->writeEnterSection("myBuildings"); - for (int i=0; iwriteEnterSection(i); - myBuildings[i]->saveCrossRef(stream); - stream->writeLeaveSection(); - } - } - stream->writeLeaveSection(); - - stream->writeUint32(allies, "allies"); - stream->writeUint32(enemies, "enemies"); - stream->writeUint32(sharedVisionOther, "sharedVisionExchange"); - stream->writeUint32(sharedVisionFood, "sharedVisionFood"); - stream->writeUint32(sharedVisionOther, "sharedVisionOther"); - stream->writeUint32(me, "me"); - stream->writeSint32(startPosX, "startPosX"); - stream->writeSint32(startPosY, "startPosY"); - stream->writeSint32(startPosSet, "startPosSet"); - stream->writeSint32(unitConversionLost, "unitConversionLost"); - stream->writeSint32(unitConversionGained, "unitConversionGained"); - - stream->writeEnterSection("teamRessources"); - for (unsigned int i=0; iwriteEnterSection(i); - stream->writeUint32(teamRessources[i], "teamRessources"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stats.save(stream); - race.save(stream); - - stream->writeLeaveSection(); -} - - - - -void Team::createLists(void) -{ - assert(swarms.size()==0); - assert(turrets.size()==0); - assert(virtualBuildings.size()==0); - - swarms.clear(); - turrets.clear(); - virtualBuildings.clear(); - - for (int i=0; itype->unitProductionTime) - swarms.push_back(myBuildings[i]); - if (myBuildings[i]->type->shootingRange) - turrets.push_back(myBuildings[i]); - if (myBuildings[i]->type->isVirtual) - virtualBuildings.push_back(myBuildings[i]); - if (myBuildings[i]->type->zonable[WORKER]) - clearingFlags.push_back(myBuildings[i]); - myBuildings[i]->update(); - } -} - - - - -void Team::clearLists(void) -{ - for (int i=0; iperformance[FLY]) - { - map->setAirUnit(myUnits[i]->posX, myUnits[i]->posY, NOGUID); - } - else - { - map->setGroundUnit(myUnits[i]->posX, myUnits[i]->posY, NOGUID); - } - } - } - - for (int i=0; itype->isVirtual) - { - map->setBuilding(myBuildings[i]->posX, myBuildings[i]->posY, myBuildings[i]->type->width, myBuildings[i]->type->height, NOGBID); - } - } - } - -} - - - - -void Team::clearMem(void) -{ - for (int i=0; iintegrity()); - } - for (std::list::iterator it=virtualBuildings.begin(); it!=virtualBuildings.end(); ++it) - { - checkInvariant(*it); - checkInvariant((*it)->type); - checkInvariant((*it)->type->isVirtual); - checkInvariant(myBuildings[Building::GIDtoID((*it)->gid)]); - } - for (std::list::iterator it=clearingFlags.begin(); it!=clearingFlags.end(); ++it) - { - checkInvariant(*it); - checkInvariant((*it)->type); - checkInvariant((*it)->type->isVirtual); - checkInvariant(myBuildings[Building::GIDtoID((*it)->gid)]); - } - - for (int i=0; iintegrity()); - } - return true; -} - - - - -void Team::setCorrectMasks(void) -{ - me=teamNumberToMask(teamNumber); - allies=me; - enemies=~allies; - sharedVisionExchange=me; - sharedVisionFood=me; - sharedVisionOther=me; -} - - - - -void Team::setCorrectColor(const GAGCore::Color& color) -{ - this->color = color; -} - -void Team::setCorrectColor(float value) -{ - float r, g, b; - Utilities::HSVtoRGB(&r, &g, &b, value, 0.8f, 0.9f); - color = Color((Uint8)(255.0f*r), (Uint8)(255.0f*g), (Uint8)(255.0f*b)); -} - - - - -void Team::update() -{ - for (int i=0; iupdate(); -} - - - - -bool Team::openMarket() -{ - int numberOfTeam=game->mapHeader.getNumberOfTeams(); - for (int ti=0; titeams[ti]->sharedVisionExchange & me)) - return true; - return false; -} - - - - -Building *Team::findNearestHeal(Unit *unit) -{ - if (unit->hungry < 0) - return NULL; - if (unit->performance[FLY]) - { - Sint32 x = unit->posX; - Sint32 y = unit->posY; - Sint32 maxDist = unit->hungry / unit->race->hungryness + unit->hp; - Building *choosen = NULL; - Sint32 bestDist2 = maxDist * maxDist; - for (std::list::iterator bi=canHealUnit.begin(); bi!=canHealUnit.end(); ++bi) - { - Building *b=(*bi); - Sint32 dist2 = map->warpDistSquare(x, y, b->posX, b->posY); - if (dist2 < bestDist2) - { - choosen = b; - bestDist2 = dist2; - } - } - return choosen; - } - else - { - Sint32 x = unit->posX; - Sint32 y = unit->posY; - Sint32 maxDist = unit->hungry / race.hungryness + unit->hp; - bool canSwim = unit->performance[SWIM]; - Building *choosen= NULL; - Sint32 bestDist = maxDist; - for (std::list::iterator bi=canHealUnit.begin(); bi!=canHealUnit.end(); ++bi) - { - int buildingDist;//initialized in buildingAvailable next line - if (map->buildingAvailable((*bi), canSwim, x, y, &buildingDist) && (buildingDist < bestDist)) - { - choosen = (*bi); - bestDist = buildingDist; - } - } - return choosen; - } -} - - - - -Building *Team::findNearestFood(Unit *unit) -{ - MapHeader& header=game->mapHeader; - - bool concurency = false;//Becomes true if there is a team whose inn-view is on for us but who is not allied to us. - for (int ti= 0; ti < header.getNumberOfTeams(); ti++) - if (ti != teamNumber && (game->teams[ti]->sharedVisionFood & me) && !(game->teams[ti]->allies & me)) - { - concurency = true; - break; - } - - // first, we check for the best food an enemy can offer: - Sint32 bestEnemyHappyness = 0; - Sint32 maxDist = std::max(0, unit->hungry) / unit->race->hungryness + unit->hp; - Building *bestEnemyFood = NULL; - if (concurency) - { - if (unit->verbose) - printf("guid=(%d), Team::findNearestFood(), concurency\n", unit->gid); - if (unit->performance[FLY]) - { - Sint32 bestDist = maxDist; - for (int ti = 0; ti < header.getNumberOfTeams(); ti++) - { - if (ti == teamNumber) - continue; - Team *team = game->teams[ti]; - if (!(team->sharedVisionFood & me) || (team->allies & me)) - continue; - for (std::list::iterator bi = team->canFeedUnit.begin(); bi != team->canFeedUnit.end(); ++bi) - { - Sint32 dist = 1 + (Sint32)sqrt(map->warpDistSquare(unit->posX, unit->posY, (*bi)->posX, (*bi)->posY)); - if (dist >= maxDist - || !(*bi)->canConvertUnit() - ) - { - continue; - } - int happyness = (*bi)->availableHappynessLevel(); - if (happyness > bestEnemyHappyness) - { - bestEnemyHappyness = happyness; - bestDist = dist; - bestEnemyFood = *bi; - } - else if (happyness == bestEnemyHappyness && dist < bestDist) - { - bestDist = dist; - bestEnemyFood = *bi; - } - } - } - } - else - { - Sint32 bestDist = maxDist; - bool canSwim = (unit->performance[SWIM] > 0); - for (int ti = 0; ti < header.getNumberOfTeams(); ti++) - { - if (ti == teamNumber) - continue; - Team *team = game->teams[ti]; - if (!(team->sharedVisionFood & me) || (team->allies & me)) - continue; - for (std::list::iterator bi = team->canFeedUnit.begin(); bi != team->canFeedUnit.end(); ++bi) - { - int dist = 1 + (Sint32)sqrt(map->warpDistSquare(unit->posX, unit->posY, (*bi)->posX, (*bi)->posY)); - if (dist >= maxDist - || !(*bi)->canConvertUnit() - ) - { - continue; - } - if (!map->buildingAvailable(*bi, canSwim, unit->posX, unit->posY, &dist)) - continue; - if (dist >= maxDist) - continue; - int happyness = (*bi)->availableHappynessLevel(); - if (happyness > bestEnemyHappyness) - { - bestEnemyHappyness = happyness; - bestDist = dist; - bestEnemyFood = *bi; - } - else if (happyness == bestEnemyHappyness && dist < bestDist) - { - bestDist = dist; - bestEnemyFood = *bi; - } - } - } - } - if (unit->verbose && bestEnemyFood) - printf("guid=(%d), Team::findNearestFood(), bestEnemyHappyness=%d, bestEnemyFood->gid=%d\n", unit->gid, bestEnemyHappyness, bestEnemyFood->gid); - } - - //Second, we check if we have any satisfactory inns on our team. - // That mean it has to be better or equal than the ennemy food. - if (unit->performance[FLY]) - { - Sint32 bestDist = maxDist; - Building *choosenFood = NULL; - for (std::list::iterator bi=canFeedUnit.begin(); bi!=canFeedUnit.end(); ++bi) - { - if ((*bi)->availableHappynessLevel() < bestEnemyHappyness) - continue; - Sint32 dist = 1 + (Sint32)sqrt(map->warpDistSquare(unit->posX, unit->posY, (*bi)->posX, (*bi)->posY)); - if (dist >= bestDist) - continue; - bestDist = dist; - choosenFood = *bi; - } - if (choosenFood) - return choosenFood; - } - else - { - bool canSwim = (unit->performance[SWIM] > 0); - Sint32 bestDist = maxDist; - Building *choosenFood = NULL; - for (std::list::iterator bi=canFeedUnit.begin(); bi!=canFeedUnit.end(); ++bi) - { - if ((*bi)->availableHappynessLevel() < bestEnemyHappyness) - continue; - int dist = 1 + (Sint32)sqrt(map->warpDistSquare(unit->posX, unit->posY, (*bi)->posX, (*bi)->posY)); - if (dist >= bestDist) - continue; - - if (!map->buildingAvailable(*bi, canSwim, unit->posX, unit->posY, &dist)) - continue; - if (dist >= bestDist) - continue; - bestDist = dist; - choosenFood = *bi; - } - if (choosenFood) - return choosenFood; - } - - return bestEnemyFood; -} - - - - -Building *Team::findBestUpgrade(Unit *unit) -{ - Building *choosen=NULL; - Sint32 score=0x7FFFFFFF; - int x=unit->posX; - int y=unit->posY; - //TODO: This is bad code. If WALK ever ceases to be the first ability or ARMOR ever ceases - //to be the last, this code will fail. - for (int ability=(int)WALK; ability<(int)ARMOR; ability++) - { - if (unit->canLearn[ability]) - { - if (unit->verbose) - printf("guid=(%d) unit->canLearn[ability=%d]\n", unit->gid, ability); - int actLevel=unit->level[ability]; - for (std::list::iterator bi=upgrade[ability].begin(); bi!=upgrade[ability].end(); ++bi) - { - Building *b=(*bi); - if (unit->verbose) - printf("guid=(%d) b->gid=%d, b->type->level=%d, actLevel=%d\n", unit->gid, b->gid, b->type->level, actLevel); - if (b->type->level >= actLevel) - { - Sint32 newScore=(map->warpDistSquare(b->posX, b->posY, x, y)<<8)/(b->maxUnitInside-b->unitsInside.size()); - if (newScoredestinationPurpose=(Sint32)ability; - //fprintf(logFile, "[%d] score=%d, newScore=%d\n", unit->gid, score, newScore); - fprintf(logFile, "[%d] tdp6 destinationPurpose=%d\n", unit->gid, unit->destinationPurpose); - choosen=b; - score=newScore; - } - } - } - } - } - return choosen; -} - - -bool Team::prioritize_building(Building* lhs, Building* rhs) -{ - if(lhs->priority != rhs->priority) - return lhs->priority > rhs->priority; - - int priority_lhs=0; - if(lhs->type->shortTypeNum==IntBuildingType::FOOD_BUILDING && !lhs->type->isBuildingSite) - priority_lhs=2+lhs->type->level*10; - else - priority_lhs=1+lhs->type->level*10; - - int priority_rhs=0; - if(rhs->type->shortTypeNum==IntBuildingType::FOOD_BUILDING && !rhs->type->isBuildingSite) - priority_rhs=2+rhs->type->level*10; - else - priority_rhs=1+rhs->type->level*10; - - if(priority_lhs != priority_rhs) - { - return priority_lhs > priority_rhs; - } - else - { - //This uses some fraction math in order to be able to compare the relative percent of units needed - //for each building. The fractions are (needed_units / wanted_units) for both lhs and rhs. - //The trick is to put them into a common denominator, which is done by cross multiplying. - //The denominators don't actually need to be computed, only the numerators. - int ratio_lhs_unit = (lhs->maxUnitWorking - lhs->unitsWorking.size()) * rhs->unitsWorking.size(); - int ratio_rhs_unit = (rhs->maxUnitWorking - rhs->unitsWorking.size()) * lhs->unitsWorking.size(); - if(ratio_lhs_unit == ratio_rhs_unit) - { - int ratio_lhs_ressource = lhs->totalWishedRessource(); - int ratio_rhs_ressource = rhs->totalWishedRessource(); - return ratio_lhs_ressource > ratio_rhs_ressource; - } - else - { - return ratio_lhs_unit > ratio_rhs_unit; - } - } - return false; -} - - -void Team::add_building_needing_work(Building* b, Sint32 priority) -{ - bool did_find_position=false; - Sint32 p = priority; - std::vector& blist = buildingsNeedingUnits[p]; - for(std::vector::iterator i=blist.begin(); i!=blist.end(); ++i) - { - if(prioritize_building(b, *i)) - { - buildingsNeedingUnits[p].insert(i, b); - did_find_position=true; - break; - } - } - if(!did_find_position) - buildingsNeedingUnits[p].push_back(b); -} - - -void Team::remove_building_needing_work(Building* b, Sint32 priority) -{ - Sint32 p = priority; - buildingsNeedingUnits[p].erase(std::find(buildingsNeedingUnits[p].begin(), buildingsNeedingUnits[p].end(), b)); -} - - - -void Team::updateAllBuildingTasks() -{ - for(std::map, std::greater >::iterator i = buildingsNeedingUnits.begin(); i!=buildingsNeedingUnits.end(); ++i) - { - std::sort(i->second.begin(), i->second.end(), Team::prioritize_building); - bool cont=true; - std::vector foundPer(i->second.size(), true); - while(cont) - { - bool found=false; - for(unsigned j=0; j<(i->second.size()); ++j) - { - if(foundPer[j]) - { - bool thisFound=false; - if(i->second[j]->type->isVirtual) - thisFound |= (i->second)[j]->subscribeForFlagingStep(); - else - thisFound |= (i->second)[j]->subscribeToBringRessourcesStep(); - found |= thisFound; - foundPer[j] = thisFound; - } - } - if(!found) - cont = false; - } - } -} - - - -int Team::maxBuildLevel(void) -{ - int maxLevel=0; - for (int i=0; iperformance[BUILD]) - { - int unitLevel=u->level[BUILD]; - if (unitLevel>maxLevel) - maxLevel=unitLevel; - } - } - return maxLevel; -} - - - - -void Team::removeFromAbilitiesLists(Building *building) -{ - for (int ui=0; uitype->upgrade[ui]) - upgrade[ui].remove(building); - - if (building->type->canFeedUnit) - canFeedUnit.remove(building); - if (building->type->canHealUnit) - canHealUnit.remove(building); - if (building->type->canExchange) - canExchange.remove(building); - - if (building->type->unitProductionTime) - swarms.remove(building); - if (building->type->shootingRange) - turrets.remove(building); - - if (building->type->zonable[WORKER]) - clearingFlags.remove(building); - - if (building->type->isVirtual) - virtualBuildings.remove(building); -} - - - - -void Team::addToStaticAbilitiesLists(Building *building) -{ - if (building->type->canExchange) - canExchange.push_back(building); - - if (building->type->unitProductionTime) - swarms.push_back(building); - - if (building->type->shootingRange) - turrets.push_back(building); - - if (building->type->zonable[WORKER]) - clearingFlags.push_back(building); -; - if (building->type->isVirtual) - virtualBuildings.push_back(building); -} - - - - -void Team::syncStep(void) -{ - integrity(); - - if (noMoreBuildingSitesCountdown>0) - noMoreBuildingSitesCountdown--; - - int nbUsefullUnits = 0; - int nbUsefullUnitsAlone = 0; - for (int i = 0; i < Unit::MAX_COUNT; i++) - { - Unit *u = myUnits[i]; - if (u) - { - if (u->typeNum != EXPLORER) - { - nbUsefullUnits++; - if (u->medical == Unit::MED_FREE || (u->insideTimeout < 0 && u->attachedBuilding && u->attachedBuilding->type->canFeedUnit)) - nbUsefullUnitsAlone++; - } - u->syncStep(); - if (u->isDead) - { - fprintf(logFile, "unit guid=%d deleted\n", u->gid); - if (u->attachedBuilding) - fprintf(logFile, " attachedBuilding->bgid=%d\n", u->attachedBuilding->gid); - if(game->selectedUnit == u) - game->selectedUnit = NULL; - delete u; - myUnits[i] = NULL; - } - } - } - - bool isDirtyGlobalGradient=false; - for (std::list::iterator it=buildingsWaitingForDestruction.begin(); it!=buildingsWaitingForDestruction.end();) - { - Building *building=*it; - if (building->unitsInside.size()==0) - { - if (building->buildingState==Building::WAITING_FOR_DESTRUCTION) - { - if (!building->type->isVirtual) - { - map->setBuilding(building->posX, building->posY, building->type->width, building->type->height, NOGBID); - map->dirtyLocalGradient(building->posX-16, building->posY-16, 31+building->type->width, 31+building->type->height, teamNumber); - isDirtyGlobalGradient=true; - } - building->buildingState=Building::DEAD; - prestige-=(*it)->type->prestige; - buildingsToBeDestroyed.push_front(building); - } - - std::list::iterator ittemp=it; - it=buildingsWaitingForDestruction.erase(ittemp); - } - else - ++it; - } - if (isDirtyGlobalGradient) - { - dirtyGlobalGradient(); - map->updateForbiddenGradient(teamNumber); - map->updateGuardAreasGradient(teamNumber); - map->updateClearAreasGradient(teamNumber); - } - - for (std::list::iterator it=buildingsToBeDestroyed.begin(); it!=buildingsToBeDestroyed.end(); ++it) - { - Building *building=*it; - fprintf(logFile, "building guid=%d deleted\n", building->gid); - fflush(logFile); - - removeFromAbilitiesLists(building); - - assert(building->unitsWorking.size()==0); - assert(building->unitsInside.size()==0); - - //TODO: optimisation: we can avoid some of thoses remove(Building *) by keeping a building state to detect which remove() are needed. - buildingsTryToBuildingSiteRoom.remove(building); - - if (game->selectedBuilding==building) - game->selectedBuilding=NULL; - - myBuildings[Building::GIDtoID(building->gid)]=NULL; - delete building; - } - - if (buildingsToBeDestroyed.size()) - buildingsToBeDestroyed.clear(); - - for (std::list::iterator it=buildingsTryToBuildingSiteRoom.begin(); it!=buildingsTryToBuildingSiteRoom.end();) - { - if ((*it)->tryToBuildingSiteRoom()) - { - std::list::iterator ittemp=it; - it=buildingsTryToBuildingSiteRoom.erase(ittemp); - } - else - ++it; - } - - updateAllBuildingTasks(); - - bool isEnoughFoodInSwarm=false; - - for (int i=0; istep(); - } - } - - for (std::list::iterator it=swarms.begin(); it!=swarms.end(); ++it) - { - if (!(*it)->locked[1] && (*it)->ressources[CORN]>(*it)->type->ressourceForOneUnit) - isEnoughFoodInSwarm=true; - (*it)->swarmStep(); - } - - for (std::list::iterator it=turrets.begin(); it!=turrets.end(); ++it) - (*it)->turretStep(game->stepCounter); - - for (std::list::iterator it=clearingFlags.begin(); it!=clearingFlags.end(); ++it) - (*it)->clearingFlagStep(); - - bool isDying= (playersMask==0) - || (!isEnoughFoodInSwarm && nbUsefullUnitsAlone==0 && (nbUsefullUnits==0 || (canFeedUnit.size()==0 && canHealUnit.size()==0))); - if (isAlive && isDying) - { - isAlive=false; - fprintf(logFile, "Team %d is dead:\n", teamNumber); - fprintf(logFile, " isEnoughFoodInSwarm=%d\n", isEnoughFoodInSwarm); - fprintf(logFile, " nbUsefullUnitsAlone=%d\n", nbUsefullUnitsAlone); - fprintf(logFile, " nbUsefullUnits=%d\n", nbUsefullUnits); - fprintf(logFile, " canFeedUnit.size()=%zd\n", canFeedUnit.size()); - fprintf(logFile, " canHealUnit.size()=%zd\n", canHealUnit.size()); - } - - stats.step(this); - updateEvents(); -} - - - - -void Team::checkControllingPlayers(void) -{ - if (!hasWon) - { - bool stillInControl = false; - for (int i=0; igameHeader.getNumberOfPlayers(); i++) - { - if ((game->players[i]->teamNumber == teamNumber) && - game->players[i]->type != Player::P_LOST_DROPPING && - game->players[i]->type != Player::P_LOST_FINAL) - stillInControl = true; - } - isAlive = isAlive && stillInControl; - } -} - - - -void Team::pushGameEvent(boost::shared_ptr event) -{ - ///Ignore events when the cooldown is above 0 - if(eventCooldownTimers[event->getEventType()] == 0) - { - events.push(event); - eventCooldownTimers[event->getEventType()]=50; - } -} - - - -boost::shared_ptr Team::getEvent() -{ - if(events.empty()) - return boost::shared_ptr(); - - boost::shared_ptr event = events.front(); - events.pop(); - return event; -} - - - -void Team::updateEvents() -{ - for(int i=0; i0) - eventCooldownTimers[i]-=1; - } - - - bool testAnother=true; - while(testAnother && !events.empty()) - { - boost::shared_ptr event = events.front(); - if((game->stepCounter - event->getStep()) > 100) - { - events.pop(); - } - else - { - testAnother=false; - } - } -} - - -bool Team::wasRecentEvent(GameEventType type) -{ - return eventCooldownTimers[type]==50; -} - - -void Team::dirtyGlobalGradient() -{ - game->dirtyWarFlagGradient(); - for (int id=0; idglobalGradient[canSwim]) - { - //printf("freeing globalGradient for gbid=%d (%p)\n", b->gid, b->globalGradient[canSwim]); - delete[] b->globalGradient[canSwim]; - b->globalGradient[canSwim]=NULL; - b->locked[canSwim]=false; - } - } -} - -void Team::dirtyWarFlagGradient() -{ - for (std::list::const_iterator it = virtualBuildings.begin(); it != virtualBuildings.end(); ++it) - { - Building *b = *it; - if (b->type->zonable[WARRIOR]) - for (int canSwim=0; canSwim<2; canSwim++) - if (b->globalGradient[canSwim]) - { - delete[] b->globalGradient[canSwim]; - b->globalGradient[canSwim]=NULL; - b->locked[canSwim]=false; - } - } -} - -Uint32 Team::checkSum(std::vector *checkSumsVector, std::vector *checkSumsVectorForBuildings, std::vector *checkSumsVectorForUnits) -{ - Uint32 cs=0; - - cs^=BaseTeam::checkSum(); - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [1+t*20] - - for (int i=0; icheckSum(checkSumsVectorForUnits); - cs=(cs<<31)|(cs>>1); - } - if (checkSumsVector) - checkSumsVector->push_back(cs); // [2+t*20] - - for (int i=0; icheckSum(checkSumsVectorForBuildings); - cs=(cs<<31)|(cs>>1); - } - if (checkSumsVector) - checkSumsVector->push_back(cs); // [3+t*20] - - for (int i=0; i>1); - } - if (checkSumsVector) - checkSumsVector->push_back(cs); // [4+t*20] - - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [7+t*20] - - cs^=canExchange.size(); - cs^=canFeedUnit.size(); - cs^=canHealUnit.size(); - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [8+t*20] - - cs^=buildingsToBeDestroyed.size(); - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [9+t*20] - cs^=buildingsTryToBuildingSiteRoom.size(); - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [10+t*20] - - cs^=swarms.size(); - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [11+t*20] - cs^=turrets.size(); - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [12+t*20] - - cs^=allies; - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [13+t*20] - cs^=enemies; - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [14+t*20] - cs^=sharedVisionExchange; - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [15+t*20] - cs^=sharedVisionFood; - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [16+t*20] - cs^=sharedVisionOther; - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [17+t*20] - cs^=me; - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [18+t*20] - - cs^=noMoreBuildingSitesCountdown; - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [19+t*20] - - cs^=prestige; - cs=(cs<<31)|(cs>>1); - if (checkSumsVector) - checkSumsVector->push_back(cs); // [20+t*20] - - return cs; -} - - - - -std::string Team::getFirstPlayerName(void) const -{ - for (int i=0; igameHeader.getNumberOfPlayers(); i++) - { - if (game->players[i]->team == this) - return game->players[i]->name; - } - return Toolkit::getStringTable()->getString("[Uncontrolled]"); -} - - - -void Team::checkWinConditions() -{ - std::list >& conditions = game->gameHeader.getWinningConditions(); - for(std::list >::iterator i = conditions.begin(); i!=conditions.end(); ++i) - { - if((*i)->hasTeamWon(teamNumber, game)) - { - hasWon=true; - hasLost=false; - winCondition = (*i)->getType(); - break; - } - else if((*i)->hasTeamLost(teamNumber, game)) - { - hasWon=false; - hasLost=true; - winCondition = (*i)->getType(); - break; - } - else - { - hasWon=false; - hasLost=false; - winCondition = WCUnknown; - } - } -} - diff --git a/src/TeamStat.cpp b/src/TeamStat.cpp index c7b619eae..3d29e71db 100644 --- a/src/TeamStat.cpp +++ b/src/TeamStat.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include @@ -60,13 +44,13 @@ void TeamStat::reset() } totalFree=0; totalNeeded=0; - for(int i=0; i<4; ++i) + for(int i=0; igame->stepCounter & 0x1FF) == 0) && !reloaded) + if (((team->game->stepCounter & END_OF_GAME_STAT_INTERVAL_MASK) == 0) && !reloaded) { endOfGameStats.push_back(EndOfGameStat(stats[statsIndex].totalUnit, stats[statsIndex].totalBuilding, team->prestige, stats[statsIndex].totalHP, stats[statsIndex].totalAttackPower, stats[statsIndex].totalDefensePower)); @@ -187,7 +171,7 @@ void TeamStats::step(Team *team, bool reloaded) { maxStat.totalNeeded=smoothedStat.totalNeeded; } - for(int k=0; k<4; ++k) + for(int k=0; kmaxStat.totalNeededPerLevel[k]) maxStat.totalNeededPerLevel[k]=smoothedStat.totalNeededPerLevel[k]; @@ -267,7 +251,7 @@ void TeamStats::step(Team *team, bool reloaded) stat.numberBuildingPerType[b->type->shortTypeNum]++; int longLevel=b->getLongLevel(); assert(longLevel>=0); - assert(longLevel<=5); + assert(longLevel<=MAX_BUILDING_LONG_LEVEL); stat.numberBuildingPerTypePerLevel[b->type->shortTypeNum][longLevel]++; stat.totalHP += b->hp; stat.totalDefensePower += (b->type->shootDamage*b->type->shootRythme) >> SHOOTING_COOLDOWN_MAGNITUDE; @@ -281,7 +265,7 @@ void TeamStats::step(Team *team, bool reloaded) for (int j=0; jdrawString(textStartPosX, textStartPosY+30, font, FormatableString("%0 %1 (%2 %)").arg(newStats.numberUnitPerType[0]).arg(strings->getString("[workers]")).arg(((float)newStats.numberUnitPerType[0])*100.0f/((float)newStats.totalUnit), 0, 0).c_str()); + gfx->drawString(textStartPosX, textStartPosY+30, font, FormatableString("%0 %1 (%2 %)").arg(newStats.numberUnitPerType[WORKER]).arg(strings->getString("[workers]")).arg(((float)newStats.numberUnitPerType[WORKER])*100.0f/((float)newStats.totalUnit), 0, 0).c_str()); gfx->drawString(textStartPosX+5, textStartPosY+42, font, FormatableString("%0 %1 %2").arg(strings->getString("[of which]")).arg(free).arg(strings->getString("[free]")).c_str()); gfx->drawString(textStartPosX+5, textStartPosY+54, font, FormatableString("%0 %1 %2").arg(strings->getString("[and]")).arg(seeking).arg(strings->getString("[seeking a job]")).c_str()); // explorer - gfx->drawString(textStartPosX, textStartPosY+69, font, FormatableString("%0 %1 (%2 %)").arg(newStats.numberUnitPerType[1]).arg(strings->getString("[explorers]")).arg(((float)newStats.numberUnitPerType[1])*100.0f/((float)newStats.totalUnit), 0, 0).c_str()); - gfx->drawString(textStartPosX+5, textStartPosY+81, font, FormatableString("%0 %1 %2").arg(strings->getString("[of which]")).arg(newStats.isFree[1]).arg(strings->getString("[free]")).c_str()); + gfx->drawString(textStartPosX, textStartPosY+69, font, FormatableString("%0 %1 (%2 %)").arg(newStats.numberUnitPerType[EXPLORER]).arg(strings->getString("[explorers]")).arg(((float)newStats.numberUnitPerType[EXPLORER])*100.0f/((float)newStats.totalUnit), 0, 0).c_str()); + gfx->drawString(textStartPosX+5, textStartPosY+81, font, FormatableString("%0 %1 %2").arg(strings->getString("[of which]")).arg(newStats.isFree[EXPLORER]).arg(strings->getString("[free]")).c_str()); // warrior - gfx->drawString(textStartPosX, textStartPosY+96, font, FormatableString("%0 %1 (%2 %)").arg(newStats.numberUnitPerType[2]).arg(strings->getString("[warriors]")).arg(((float)newStats.numberUnitPerType[2])*100.0f/((float)newStats.totalUnit), 0, 0).c_str()); - gfx->drawString(textStartPosX+5, textStartPosY+108, font, FormatableString("%0 %1 %2").arg(strings->getString("[of which]")).arg(newStats.isFree[2]).arg(strings->getString("[free]")).c_str()); + gfx->drawString(textStartPosX, textStartPosY+96, font, FormatableString("%0 %1 (%2 %)").arg(newStats.numberUnitPerType[WARRIOR]).arg(strings->getString("[warriors]")).arg(((float)newStats.numberUnitPerType[WARRIOR])*100.0f/((float)newStats.totalUnit), 0, 0).c_str()); + gfx->drawString(textStartPosX+5, textStartPosY+108, font, FormatableString("%0 %1 %2").arg(strings->getString("[of which]")).arg(newStats.isFree[WARRIOR]).arg(strings->getString("[free]")).c_str()); // living state gfx->drawString(textStartPosX, textStartPosY+123, font, FormatableString("%0 %1 (%2 %)").arg(newStats.needNothing).arg(strings->getString("[are ok]")).arg(((float)newStats.needNothing)*100.0f/((float)newStats.totalUnit), 0, 0).c_str()); @@ -370,8 +354,8 @@ void TeamStats::drawStat(int posx, int posy) }*/ int maxWorker=0; for (int i=0; imaxWorker) - maxWorker=stats[i].numberUnitPerType[0]; + if (stats[i].numberUnitPerType[WORKER]>maxWorker) + maxWorker=stats[i].numberUnitPerType[WORKER]; if (maxWorker==0) return; @@ -444,10 +428,10 @@ void TeamStats::drawStat(int posx, int posy) } // graph - for (int i=0; i<128; i++) + for (int i=0; i or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __TEAM_STAT_H -#define __TEAM_STAT_H +#pragma once #include "UnitConsts.h" #include "IntBuildingType.h" @@ -28,6 +11,23 @@ class Map; +//! Number of "long level" slots for the per-building-type histogram. The +//! long level is `(type->level << 1) + 1 - isBuildingSite` (see +//! Building::getLongLevel in building/Misc.cpp), giving the range 0..5 +//! inclusive — six slots — so that finished buildings and their sites at +//! each level land in distinct bins. Distinct from Game.h's +//! MAX_BUILDING_LEVELS even though they happen to share the value 6. +static constexpr int NB_BUILDING_LONG_LEVELS = 6; +//! Highest valid long-level index (= NB_BUILDING_LONG_LEVELS - 1). +//! Used by the assertion in TeamStat.cpp guarding the histogram write. +static constexpr int MAX_BUILDING_LONG_LEVEL = NB_BUILDING_LONG_LEVELS - 1; + +//! Bitmask used by TeamStats::step to append an EndOfGameStat snapshot +//! every 512 ticks (~20.5 s at 25 Hz): `(stepCounter & MASK) == 0`. +//! The 512-tick cadence is the gameplay-meaningful constant — the mask +//! width is independent of Team::MAX_COUNT. See TeamStat.cpp:122. +static constexpr int END_OF_GAME_STAT_INTERVAL_MASK = 0x1FF; + struct TeamStat { TeamStat(); @@ -42,7 +42,7 @@ struct TeamStat int totalBuilding; // Note that this is the total number of *finished* buildings, building sites are ignored int numberBuildingPerType[IntBuildingType::NB_BUILDING]; - int numberBuildingPerTypePerLevel[IntBuildingType::NB_BUILDING][6]; + int numberBuildingPerTypePerLevel[IntBuildingType::NB_BUILDING][NB_BUILDING_LONG_LEVELS]; int needFoodCritical; // Number of units that are hungry but there aren't able to eat @@ -73,7 +73,7 @@ struct TeamSmoothedStat int totalFree; int isFree[NB_UNIT_TYPE]; int totalNeeded; - int totalNeededPerLevel[4]; + int totalNeededPerLevel[NB_UNIT_LEVELS]; }; struct EndOfGameStat @@ -144,4 +144,3 @@ class TeamStats TeamStat *getLatestStat(void) { return &(stats[statsIndex]); } }; -#endif diff --git a/src/TerrainType.h b/src/TerrainType.h deleted file mode 100644 index aab1ecf7d..000000000 --- a/src/TerrainType.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __TERRAIN_TYPE_H -#define __TERRAIN_TYPE_H - -enum TerrainType -{ - WATER=0, - SAND=1, - GRASS=2, -}; - -#endif diff --git a/src/ThreadMessageQueues.h b/src/ThreadMessageQueues.h new file mode 100644 index 000000000..f6e8b37d7 --- /dev/null +++ b/src/ThreadMessageQueues.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +/// Bidirectional message-queue plumbing shared by worker-thread classes. +/// The subclass owns `incoming` (main thread writes via sendMessage, worker drains). +/// `outgoing` is a reference to a queue owned by the main thread (worker writes via +/// sendToMainThread, main thread drains). +template +class ThreadMessageQueues +{ +public: + using MessagePtr = std::shared_ptr; + using MessageQueue = std::queue; + + ThreadMessageQueues(MessageQueue& outgoing, std::recursive_mutex& outgoingMutex) + : outgoing(outgoing), outgoingMutex(outgoingMutex), hasExited(false) + { + } + + void sendMessage(MessagePtr message) + { + std::lock_guard lock(incomingMutex); + incoming.push(message); + } + + bool hasThreadExited() const + { + return hasExited; + } + +protected: + void sendToMainThread(MessagePtr message) + { + std::lock_guard lock(outgoingMutex); + outgoing.push(message); + } + + MessageQueue incoming; + MessageQueue& outgoing; + std::recursive_mutex incomingMutex; + std::recursive_mutex& outgoingMutex; + bool hasExited; +}; diff --git a/src/Unit.cpp b/src/Unit.cpp deleted file mode 100644 index ee43a5571..000000000 --- a/src/Unit.cpp +++ /dev/null @@ -1,2628 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "Unit.h" -#include "Race.h" -#include "UnitSkin.h" -#include "UnitsSkins.h" -#include "Team.h" -#include "Map.h" -#include "Game.h" - -#include "Building.h" -#include "Integrity.h" - -#include "Utilities.h" -#include "GlobalContainer.h" -#include "LogFileManager.h" -#include -#include -#include - -Unit::Unit(GAGCore::InputStream *stream, Team *owner, Sint32 versionMinor) -{ - init(0,0,0,0,owner,0); - load(stream, owner, versionMinor); -} - -Unit::Unit(int x, int y, Uint16 gid, Sint32 typeNum, Team *team, int level) -{ - init(x, y, gid, typeNum, team, level); -} - -void Unit::init(int x, int y, Uint16 gid, Sint32 typeNum, Team *team, int level) -{ - logFile = globalContainer->logFileManager->getFile("Unit.log"); - - // unit specification - this->typeNum = typeNum; - defaultSkinNameFromType(); - skinPointerFromName(); - - assert(team); - race=&(team->race); - assert(race); - - // identity - this->gid=gid; - owner=team; - isDead=false; - - // position - posX=x; - posY=y; - delta=0; - dx=0; - dy=0; - direction=8; - insideTimeout=0; - speed=32; - - // quality parameters - for (int i=0; iperformance[i]=race->getUnitType(typeNum, level)->performance[i]; - this->level[i]=level; - this->canLearn[i]=(bool)race->getUnitType(typeNum, 3)->performance[i]; //TODO: is is a better way to hack this? - // This hack prevent units from unlearning. Units level 3 must have all the abilities of all preceedings levels - } - - experience = 0; - experienceLevel = 0; - - // states - needToRecheckMedical=true; - medical=MED_FREE; - activity=ACT_RANDOM; - displacement=DIS_RANDOM; - if (performance[FLY]) - movement=MOV_RANDOM_FLY; - else - movement=MOV_RANDOM_GROUND; - - targetX = 0; - targetY = 0; - validTarget = false; - magicActionTimeout = 0; - - underAttackTimer = 0; - - // trigger parameters - hp=0; - - // warriors fight to death TODO: this is overridden !?!? - if (performance[ATTACK_SPEED]) - trigHP = 0; - else - trigHP = 20; - - // warriors wait more tiem before going to eat - hungry = HUNGRY_MAX; - hungryness = race->hungryness; - if (performance[ATTACK_SPEED]) - trigHungry = (hungry*2)/10; - else - trigHungry = hungry/4; - trigHungryCarying = hungry/10; - fruitMask = 0; - fruitCount = 0; - - // NOTE : rewrite hp from level - hp = this->performance[HP]; - trigHP = (hp*3)/10; - - attachedBuilding=NULL; - targetBuilding=NULL; - ownExchangeBuilding=NULL; - destinationPurpose=-1; - carriedRessource=-1; - jobTimer = 0; - - previousClearingAreaX=static_cast(-1); - previousClearingAreaY=static_cast(-1); - previousClearingAreaDistance=0; - - // gui - levelUpAnimation = 0; - magicActionAnimation = 0; - - // debug vars: - verbose=false; -} - -void Unit::load(GAGCore::InputStream *stream, Team *owner, Sint32 versionMinor) -{ - stream->readEnterSection("Unit"); - - // unit specification - typeNum = stream->readSint32("typeNum"); - skinName = stream->readText("skinName"); - skinPointerFromName(); - race = &(owner->race); - assert(race); - - // identity - gid = stream->readUint16("gid"); - this->owner = owner; - isDead = stream->readSint32("isDead"); - - // position - posX = stream->readSint32("posX"); - posY = stream->readSint32("posY"); - delta = stream->readSint32("delta"); - dx = stream->readSint32("dx"); - dy = stream->readSint32("dy"); - direction = stream->readSint32("direction"); - insideTimeout = stream->readSint32("insideTimeout"); - speed = stream->readSint32("speed"); - - // states - needToRecheckMedical = (bool)stream->readUint32("needToRecheckMedical"); - medical = (Medical)stream->readUint32("medical"); - activity = (Activity)stream->readUint32("activity"); - displacement = (Displacement)stream->readUint32("displacement"); - movement = (Movement)stream->readUint32("movement"); - action = (Abilities)stream->readUint32("action"); - targetX = (Sint32)stream->readSint32("targetX"); - targetY = (Sint32)stream->readSint32("targetY"); - validTarget = (bool)stream->readSint32("validTarget"); - magicActionTimeout = stream->readSint32("magicActionTimeout"); - - // under attack timer - if(versionMinor >= 61) - underAttackTimer = stream->readUint8("underAttackTimer"); - else - underAttackTimer = 0; - - - // trigger parameters - hp = stream->readSint32("hp"); - trigHP = stream->readSint32("trigHP"); - - // hungry - hungry = stream->readSint32("hungry"); - hungryness = stream->readSint32("hungryness"); - trigHungry = stream->readSint32("trigHungry"); - trigHungryCarying = (trigHungry*4)/10; - fruitMask = stream->readUint32("fruitMask"); - fruitCount = stream->readUint32("fruitCount"); - - // quality parameters - stream->readEnterSection("abilities"); - for (int i=0; ireadEnterSection(i); - performance[i] = stream->readSint32("performance"); - level[i] = stream->readSint32("level"); - canLearn[i] = (bool)stream->readUint32("canLearn"); - stream->readLeaveSection(); - } - stream->readLeaveSection(); - - - experience = stream->readSint32("experience"); - experienceLevel = stream->readSint32("experienceLevel"); - - destinationPurpose = stream->readSint32("destinationPurpose"); - carriedRessource = stream->readSint32("carriedRessource"); - - jobTimer = stream->readSint32("jobTimer"); - - previousClearingAreaX=static_cast(-1); - previousClearingAreaY=static_cast(-1); - previousClearingAreaDistance=0; - - // gui - levelUpAnimation = 0; - magicActionAnimation = 0; - jobTimer = 0; - - verbose = false; - - stream->readLeaveSection(); -} - -void Unit::save(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Unit"); - - // unit specification - // we drop the unittype pointer, we save only the number - stream->writeSint32(typeNum, "typeNum"); - stream->writeText(skinName, "skinName"); - - // identity - stream->writeUint16(gid, "gid"); - stream->writeSint32(isDead, "isDead"); - - // position - stream->writeSint32(posX, "posX"); - stream->writeSint32(posY, "posY"); - stream->writeSint32(delta, "delta"); - stream->writeSint32(dx, "dx"); - stream->writeSint32(dy, "dy"); - stream->writeSint32(direction, "direction"); - stream->writeSint32(insideTimeout, "insideTimeout"); - stream->writeSint32(speed, "speed"); - - // states - stream->writeUint32((Uint32)needToRecheckMedical, "needToRecheckMedical"); - stream->writeUint32((Uint32)medical, "medical"); - stream->writeUint32((Uint32)activity, "activity"); - stream->writeUint32((Uint32)displacement, "displacement"); - stream->writeUint32((Uint32)movement, "movement"); - stream->writeUint32((Uint32)action, "action"); - stream->writeSint32(targetX, "targetX"); - stream->writeSint32(targetY, "targetY"); - stream->writeSint32(validTarget, "validTarget"); - stream->writeSint32(magicActionTimeout, "magicActionTimeout"); - - // attack timer - stream->writeUint8(underAttackTimer, "underAttackTimer"); - - // trigger parameters - stream->writeSint32(hp, "hp"); - stream->writeSint32(trigHP, "trigHP"); - - // hungry - stream->writeSint32(hungry, "hungry"); - stream->writeSint32(hungryness, "hungryness"); - stream->writeSint32(trigHungry, "trigHungry"); - stream->writeUint32(fruitMask, "fruitMask"); - stream->writeUint32(fruitCount, "fruitCount"); - - // quality parameters - stream->writeEnterSection("abilities"); - for (int i=0; iwriteEnterSection(i); - stream->writeUint32(performance[i], "performance"); - stream->writeUint32(level[i], "level"); - stream->writeUint32((Uint32)canLearn[i], "canLearn"); - stream->writeLeaveSection(); - } - stream->writeLeaveSection(); - - stream->writeSint32(experience, "experience"); - stream->writeSint32(experienceLevel, "experienceLevel"); - - stream->writeSint32(destinationPurpose, "destinationPurpose"); - stream->writeSint32(carriedRessource, "carriedRessource"); - stream->writeSint32(jobTimer, "jobTimer"); - - - stream->writeLeaveSection(); -} - -void Unit::loadCrossRef(GAGCore::InputStream *stream, Team *owner, Sint32 versionMinor) -{ - stream->readEnterSection("Unit"); - Uint16 gbid; - - gbid = stream->readUint16("attachedBuilding"); - if (gbid == NOGBID) - attachedBuilding = NULL; - else - attachedBuilding = owner->myBuildings[Building::GIDtoID(gbid)]; - - gbid = stream->readUint16("targetBuilding"); - if (gbid == NOGBID) - targetBuilding = NULL; - else - targetBuilding = owner->myBuildings[Building::GIDtoID(gbid)]; - - gbid = stream->readUint16("ownExchangeBuilding"); - if (gbid == NOGBID) - ownExchangeBuilding = NULL; - else - ownExchangeBuilding = owner->myBuildings[Building::GIDtoID(gbid)]; - - stream->readLeaveSection(); -} - -void Unit::saveCrossRef(GAGCore::OutputStream *stream) -{ - stream->writeEnterSection("Unit"); - - if (attachedBuilding) - stream->writeUint16(attachedBuilding->gid, "attachedBuilding"); - else - stream->writeUint16(NOGBID, "attachedBuilding"); - - if (targetBuilding) - stream->writeUint16(targetBuilding->gid, "targetBuilding"); - else - stream->writeUint16(NOGBID, "targetBuilding"); - - if (ownExchangeBuilding) - stream->writeUint16(ownExchangeBuilding->gid, "ownExchangeBuilding"); - else - stream->writeUint16(NOGBID, "ownExchangeBuilding"); - - stream->writeLeaveSection(); -} - -void Unit::setTargetBuilding(Building * b) -{ - if(targetBuilding!=NULL) { - targetBuilding->removeUnitFromHarvesting(this); - } - if(b!=NULL) - { - targetX=b->getMidX(); - targetY=b->getMidY(); - } -//TODO: Deal with "validTarget=true;" - targetBuilding = b; -} - -void Unit::subscriptionSuccess(Building* building, bool inside) -{ - Building* b=building; - - if (building->type->isVirtual) - { - destinationPurpose=-1; - fprintf(logFile, "[%d] sdp1 destinationPurpose=%d\n", gid, destinationPurpose); - activity=ACT_FLAG; - attachedBuilding=b; - setTargetBuilding(b); - if (verbose) - printf("guid=(%d) unitsWorkingSubscribe(findBestZonable) dp=(%d), gbid=(%d)\n", gid, destinationPurpose, b->gid); - } - else if(inside == false) - { - assert(destinationPurpose>=0); - assert(b->neededRessource(destinationPurpose)); - activity=ACT_FILLING; - attachedBuilding=b; - setTargetBuilding(NULL); - if (verbose) - printf("guid=(%d) unitsWorkingSubscribe(findBestZonable) dp=(%d), gbid=(%d)\n", gid, destinationPurpose, b->gid); - } - else - { - activity=ACT_UPGRADING; - attachedBuilding=b; - setTargetBuilding(b); - if (verbose) - printf("guid=(%d) unitsWorkingSubscribe(findBestZonable) dp=(%d), gbid=(%d)\n", gid, destinationPurpose, b->gid); - } - - if (verbose) - printf("guid=(%d), subscriptionSuccess()\n", gid); - - switch(medical) - { - case MED_HUNGRY : - case MED_DAMAGED : - case MED_FREE: - { - switch(activity) - { - case ACT_FLAG: - { - displacement=DIS_GOING_TO_FLAG; - assert(targetBuilding==attachedBuilding); - //targetX=attachedBuilding->getMidX(); - //targetY=attachedBuilding->getMidY(); - validTarget=true; - } - break; - case ACT_UPGRADING: - { - displacement=DIS_GOING_TO_BUILDING; - assert(targetBuilding==attachedBuilding); - //targetX=targetBuilding->getMidX(); - //targetY=targetBuilding->getMidY(); - validTarget=true; - } - break; - case ACT_FILLING: - { - assert(attachedBuilding); - if (carriedRessource==destinationPurpose) - { - displacement=DIS_GOING_TO_BUILDING; - setTargetBuilding(attachedBuilding); - //targetX=targetBuilding->getMidX(); - //targetY=targetBuilding->getMidY(); - validTarget=true; - } - else - { - displacement=DIS_GOING_TO_RESSOURCE; - targetBuilding=NULL; - owner->map->ressourceAvailableUpdate(owner->teamNumber, destinationPurpose, performance[SWIM], posX, posY, &targetX, &targetY, NULL); - validTarget=true; - //fprintf(logFile, "[%d] raa targetXY=(%d, %d)=%d\n", gid, targetX, targetY, rv); - } - } - break; - case ACT_RANDOM : - { - displacement=DIS_RANDOM; - validTarget=false; - } - break; - default: - assert(false); - } - } - break; - } -} - -void Unit::syncStep(void) -{ - //warrior attacks? - assert(speed>0); - if ((action==ATTACK_SPEED) && (delta>=128) && (delta<(128+speed))) - { - Uint16 enemyGUID=owner->map->getGroundUnit(posX+dx, posY+dy); - if (enemyGUID!=NOGUID) - { - int enemyID=GIDtoID(enemyGUID); - int enemyTeam=GIDtoTeam(enemyGUID); - Unit *enemy=owner->game->teams[enemyTeam]->myUnits[enemyID]; - - int degats=getRealAttackStrength()-enemy->getRealArmor(false); - if (degats<=0) - degats=1; - enemy->hp-=degats; - - enemy->underAttackTimer = 240; - - boost::shared_ptr event(new UnitUnderAttackEvent(owner->game->stepCounter, enemy->posX, enemy->posY, enemy->typeNum)); - enemy->owner->pushGameEvent(event); - - incrementExperience(degats); - } - else - { - Uint16 enemyGBID=owner->map->getBuilding(posX+dx, posY+dy); - if (enemyGBID!=NOGBID) - { - int enemyID=Building::GIDtoID(enemyGBID); - int enemyTeam=Building::GIDtoTeam(enemyGBID); - Building *enemy=owner->game->teams[enemyTeam]->myBuildings[enemyID]; - int degats=getRealAttackStrength()-enemy->type->armor; - if (degats<=0) - degats=1; - enemy->hp-=degats; - - enemy->underAttackTimer = 240; - - boost::shared_ptr event(new BuildingUnderAttackEvent(owner->game->stepCounter, enemy->posX, enemy->posY, enemy->shortTypeNum)); - enemy->owner->pushGameEvent(event); - - if (enemy->hp<0) - enemy->kill(); - incrementExperience(degats); - } - } - } - - //We give globs 32 ticks to wait for a job before moving onto - //another activity like upgrading - if (medical==MED_FREE && activity==ACT_RANDOM) - { - jobTimer++; - } - - if(underAttackTimer > 0) - underAttackTimer -= 1; - -//#define BURST_UNIT_MODE -#ifdef BURST_UNIT_MODE - delta=0; -#else - if (delta<=255-speed) - { - delta+=speed; - } - else -#endif - { - //printf("action=%d, speed=%d, perf[a]=%d, t->perf[a]=%d\n", action, speed, performance[action], race->getUnitType(typeNum, 0)->performance[action]); - delta+=(speed-256); - - endOfAction(); - - if (performance[FLY]) - { - owner->map->setMapDiscovered(posX-3, posY-3, 7, 7, owner->sharedVisionOther); - owner->map->setMapBuildingsDiscovered(posX-3, posY-3, 7, 7, owner->sharedVisionOther, owner->game->teams); - owner->map->setMapExploredByUnit(posX-3, posY-3, 7, 7, owner->teamNumber); - } - else - { - owner->map->setMapDiscovered(posX-1, posY-1, 3, 3, owner->sharedVisionOther); - owner->map->setMapBuildingsDiscovered(posX-1, posY-1, 3, 3, owner->sharedVisionOther, owner->game->teams); - owner->map->setMapExploredByUnit(posX-1, posY-1, 3, 3, owner->teamNumber); - } - } - - // gui - if (levelUpAnimation > 0) - levelUpAnimation--; - if (magicActionAnimation > 0) - magicActionAnimation--; -} - -void Unit::selectPreferredMovement(void) -{ - if (performance[FLY]) - action=FLY; - else if ((performance[SWIM]) && (owner->map->isWater(posX, posY)) ) - action=SWIM; - else if ((performance[WALK]) && (!owner->map->isWater(posX, posY)) ) - action=WALK; - else - assert(false); -} - -void Unit::selectPreferredGroundMovement(void) -{ - assert(!performance[FLY]); - if ((performance[SWIM]) && (owner->map->isWater(posX, posY)) ) - action=SWIM; - else if ((performance[WALK]) && (!owner->map->isWater(posX, posY)) ) - action=WALK; - else - assert(false); -} - -bool Unit::isUnitHungry(void) -{ - int realTrigHungry; - if (carriedRessource==-1) - realTrigHungry=trigHungry; - else - realTrigHungry=trigHungryCarying; - - return (hungry<=realTrigHungry); -} - -void Unit::standardRandomActivity() -{ - attachedBuilding=NULL; - setTargetBuilding(NULL); - ownExchangeBuilding=NULL; - activity=Unit::ACT_RANDOM; - displacement=Unit::DIS_RANDOM; - validTarget=false; - needToRecheckMedical=true; -} - -void Unit::stopAttachedForBuilding(bool goingInside) -{ - if (verbose) - printf("guid=(%d) stopAttachedForBuilding()\n", gid); - assert(attachedBuilding); - - if (goingInside) - { - attachedBuilding->removeUnitFromInside(this); - if (activity==ACT_UPGRADING) - { - assert(displacement==DIS_GOING_TO_BUILDING); - if (destinationPurpose==HEAL || destinationPurpose==FEED) - needToRecheckMedical=true; - } - } - else - { - for (std::list::iterator it=attachedBuilding->unitsInside.begin(); it!=attachedBuilding->unitsInside.end(); ++it) - assert(*it!=this); - } - - activity=ACT_RANDOM; - displacement=DIS_RANDOM; - validTarget=false; - - attachedBuilding->removeUnitFromWorking(this); - attachedBuilding=NULL; - setTargetBuilding(NULL); - ownExchangeBuilding=NULL; - assert(needToRecheckMedical); -} - -void Unit::handleMagic(void) -{ - assert(medical==MED_FREE); - assert((displacement!=DIS_ENTERING_BUILDING) && (displacement!=DIS_INSIDE) && (displacement!=DIS_EXITING_BUILDING)); - - magicActionTimeout--; - if (magicActionTimeout > 0) - return; - - Map *map = &owner->game->map; - Team **teams = owner->game->teams; - - bool hasUsedMagicAction = false; - if (performance[MAGIC_ATTACK_AIR] || performance[MAGIC_ATTACK_GROUND]) - { - std::set damagedBuildings; - damagedBuildings.insert(NOGBID); - int ATTACK_RANGE=3; - for (int yi=posY-ATTACK_RANGE; yi<=posY+ATTACK_RANGE; yi++) - for (int xi=posX-ATTACK_RANGE; xi<=posX+ATTACK_RANGE; xi++) - { - // damaging enemy units: - for (int altitude=0; altitude<2; altitude++) - { - Uint16 targetGUID; - Sint32 attackForce; - if ((altitude == 1) && performance[MAGIC_ATTACK_AIR]) - { - targetGUID = map->getAirUnit(xi, yi); - attackForce = performance[MAGIC_ATTACK_AIR]; - } - else if ((altitude == 0) && performance[MAGIC_ATTACK_GROUND]) - { - targetGUID = map->getGroundUnit(xi, yi); - attackForce = performance[MAGIC_ATTACK_GROUND]; - } - else - continue; - if (targetGUID != NOGUID) - { - Sint32 targetTeam = Unit::GIDtoTeam(targetGUID); - Uint16 targetID = Unit::GIDtoID(targetGUID); - Uint32 targetTeamMask = 1<enemies & targetTeamMask) - { - Unit *enemyUnit = teams[targetTeam]->myUnits[targetID]; - Sint32 damage = attackForce + experienceLevel - enemyUnit->getRealArmor(true); - if (damage > 0) - { - enemyUnit->hp -= damage; - - boost::shared_ptr event(new UnitUnderAttackEvent(owner->game->stepCounter, xi, yi, enemyUnit->typeNum)); - enemyUnit->owner->pushGameEvent(event); - - incrementExperience(damage); - magicActionAnimation = MAGIC_ACTION_ANIMATION_FRAME_COUNT; - hasUsedMagicAction = true; - } - } - } - } - - // damaging enemy buildings: this has been removed for balance purposes - } - - Sint32 magicLevel = std::max(level[MAGIC_ATTACK_AIR], level[MAGIC_ATTACK_GROUND]); - if (hasUsedMagicAction) - magicActionTimeout = race->getUnitType(typeNum, level[magicLevel])->magicActionCooldown; - } -} - -void Unit::handleMedical(void) -{ - /* Make sure explorers try to immediately feed after healing to increase their range. */ - if ((typeNum == EXPLORER) && (displacement == DIS_EXITING_BUILDING)) - { - medical=MED_FREE; - if ((destinationPurpose == HEAL) && (hungry < ((HUNGRY_MAX * 9) / 10))) - { - // fprintf (stderr, "forcing explorer hunger: gid: %d, hungry: %d\n", gid, hungry); - needToRecheckMedical = 1; - medical = MED_HUNGRY; - return; - } - else if ((destinationPurpose == FEED) && (hp < (((performance[HP]) * 9) / 10))) - { - // fprintf (stderr, "forcing explorer healing: gid: %d, hp: %d\n", gid, hp); - needToRecheckMedical = 1; - medical = MED_DAMAGED; - return; - } - } - - if ((displacement==DIS_ENTERING_BUILDING) || (displacement==DIS_INSIDE) || (displacement==DIS_EXITING_BUILDING)) - return; - - if (verbose) - printf("guid=(%d) handleMedical...\n", gid); - hungry -= hungryness; - if (hungry<=0) - hp--; - - medical=MED_FREE; - if (isUnitHungry()) - medical=MED_HUNGRY; - else if (hp<=trigHP) - medical=MED_DAMAGED; - - if (hp<0) - { - fprintf(logFile, "guid=%d, set isDead(%d), beacause hungry.\n", gid, isDead); - if (attachedBuilding) - fprintf(logFile, " attachedBuilding->gid=%d.\n", attachedBuilding->gid); - - if (!isDead) - { - // disconnect from building - if (attachedBuilding) - { - assert((displacement!=DIS_ENTERING_BUILDING) && (displacement!=DIS_INSIDE) && (displacement!=DIS_EXITING_BUILDING)); - attachedBuilding->removeUnitFromWorking(this); - attachedBuilding->removeUnitFromInside(this); - attachedBuilding=NULL; - ownExchangeBuilding=NULL; - } - setTargetBuilding(NULL); - // //TODO: in beta4 this line was ommitted. delete? - // ownExchangeBuilding=NULL; - - activity=ACT_RANDOM; - validTarget=false; - - // remove from map - if (performance[FLY]) - owner->map->setAirUnit(posX, posY, NOGUID); - else - owner->map->setGroundUnit(posX, posY, NOGUID); - - if(previousClearingAreaX!=static_cast(-1)) - { - owner->map->setClearingAreaUnclaimed(previousClearingAreaX, previousClearingAreaY, owner->teamNumber); - } - owner->map->clearImmobileUnit(posX, posY); - - // generate death animation - if (!globalContainer->runNoX) - owner->map->getSector(posX, posY)->deathAnimations.push_back(new UnitDeathAnimation(posX, posY, owner)); - } - isDead = true; - } -} - -void Unit::handleActivity(void) -{ - if ((displacement==DIS_EXITING_BUILDING) - && (typeNum == EXPLORER)) { - // fprintf (stderr, "exiting explorer: gid: %d, medical: %d, destinationPurpose: %d\n", gid, medical, destinationPurpose); - } - - // freeze unit health when inside a building - if ((displacement==DIS_ENTERING_BUILDING) || (displacement==DIS_INSIDE) - || ((displacement==DIS_EXITING_BUILDING) - && ! ((typeNum == EXPLORER) && (medical != MED_FREE)))) - return; - - if (verbose) - printf("guid=(%d) handleActivity (medical=%d, activity=%d) (needToRecheckMedical=%d) (attachedBuilding=%p)...\n", - gid, medical, activity, needToRecheckMedical, attachedBuilding); - - if(activity!=ACT_RANDOM) - jobTimer=0; - - if (medical==MED_FREE) - { - handleMagic(); - - if (activity==ACT_RANDOM) - { - // nothing to do: - //Wait for 32 ticks before doing something else, to allow buildings time to hire units - if(jobTimer>32) - { - // We look for an upgrade - Building* b=owner->findBestUpgrade(this); - if (b) - { - assert(destinationPurpose>=WALK); - assert(destinationPurposegid); - b->subscribeUnitForInside(this); - return; - } - - // we go to a heal building if we'r not fully healed: (1/8 trigger) - if (hp+(performance[HP]/10) < performance[HP]) - { - Building *b; - b=owner->findNearestHeal(this); - if (b) - { - destinationPurpose=HEAL; - fprintf(logFile, "[%d] sdp2 destinationPurpose=%d\n", gid, destinationPurpose); - activity=ACT_UPGRADING; - attachedBuilding=b; - setTargetBuilding(b); - needToRecheckMedical=false; - if (verbose) - printf("guid=(%d) Going to heal building\n", gid); - targetX=attachedBuilding->getMidX(); - targetY=attachedBuilding->getMidY(); - validTarget=true; - b->subscribeUnitForInside(this); - } - else - activity=ACT_RANDOM; - } - } - } - } - else if (needToRecheckMedical) - { - // disconnect from building - if (attachedBuilding) - { - if (verbose) - printf("guid=(%d) Need medical while working, abort work\n", gid); - attachedBuilding->removeUnitFromWorking(this); - attachedBuilding->removeUnitFromInside(this); - attachedBuilding=NULL; - ownExchangeBuilding=NULL; - } - setTargetBuilding(NULL); - - if (medical==MED_HUNGRY) - { - Building *b; - b=owner->findNearestFood(this); - /*if (typeNum == EXPLORER) { - fprintf (stderr, "gid: %d, b: %x\n", gid, b); - }*/ - - if (b!=NULL) - { - Team *currentTeam=owner; - Team *targetTeam=b->owner; - if (currentTeam != targetTeam) - { - // Unit conversion code - - // Send events and keep track of number of unit converted - boost::shared_ptr event(new UnitLostConversionEvent(owner->game->stepCounter, posX, posY, targetTeam->getFirstPlayerName())); - currentTeam->pushGameEvent(event); - currentTeam->unitConversionLost++; - - boost::shared_ptr event2(new UnitGainedConversionEvent(owner->game->stepCounter, posX, posY, currentTeam->getFirstPlayerName())); - targetTeam->pushGameEvent(event2); - targetTeam->unitConversionGained++; - - // Find free slot in other team - int targetID=-1; - for (int i=0; imyUnits[i]==NULL) - { - targetID=i; - break; - } - - // If free slot, do the conversion, change owner and ID - if (targetID!=-1) - { - Sint32 currentID=Unit::GIDtoID(gid); - assert(currentTeam->myUnits[currentID]); - currentTeam->myUnits[currentID]=NULL; - targetTeam->myUnits[targetID]=this; - Uint16 targetGID=(GIDfrom(targetID, targetTeam->teamNumber)); - if (verbose) - printf("Unit guid=%d (%d) switched to guid=%d (%d)\n", gid, Unit::GIDtoTeam(gid), targetGID, Unit::GIDtoTeam(targetGID)); - if (performance[FLY]) - { - assert(owner->map->getAirUnit(posX, posY)==gid); - owner->map->setAirUnit(posX, posY, targetGID); - } - else - { - assert(owner->map->getGroundUnit(posX, posY)==gid); - owner->map->setGroundUnit(posX, posY, targetGID); - } - gid=targetGID; - owner=targetTeam; - } - } - - destinationPurpose=FEED; - fprintf(logFile, "[%d] sdp3 destinationPurpose=%d\n", gid, destinationPurpose); - activity=ACT_UPGRADING; - attachedBuilding=b; - setTargetBuilding(b); - needToRecheckMedical=false; - if (verbose) - printf("guid=(%d) Subscribed to food at building gbid=(%d)\n", gid, b->gid); - b->subscribeUnitForInside(this); - } - else - activity=ACT_RANDOM; - } - else if (medical==MED_DAMAGED) - { - Building *b; - b=owner->findNearestHeal(this); - if (b!=NULL) - { - destinationPurpose=HEAL; - fprintf(logFile, "[%d] sdp4 destinationPurpose=%d\n", gid, destinationPurpose); - activity=ACT_UPGRADING; - attachedBuilding=b; - setTargetBuilding(b); - needToRecheckMedical=false; - if (verbose) - printf("guid=(%d) Subscribed to heal at building gbid=(%d)\n", gid, b->gid); - b->subscribeUnitForInside(this); - } - else - activity=ACT_RANDOM; - } - else - assert(false); - } -} - -void Unit::handleDisplacement(void) -{ - switch (activity) - { - case ACT_RANDOM: - { - if ((medical==MED_FREE)&&((displacement==DIS_RANDOM)||(displacement==DIS_REMOVING_BLACK_AROUND)||(displacement==DIS_ATTACKING_AROUND))) - { - if (performance[FLY]) - displacement=DIS_REMOVING_BLACK_AROUND; - else if (performance[ATTACK_SPEED]) - displacement=DIS_ATTACKING_AROUND; - } - else - displacement=DIS_RANDOM; - validTarget=false; - } - break; - - case ACT_FILLING: - { - assert(attachedBuilding); - assert(displacement!=DIS_RANDOM); - - if (verbose) - printf("guid=(%d) handleDisplacement() ACT_FILLING, displacement=%d\n", gid, displacement); - - if (displacement==DIS_GOING_TO_RESSOURCE) - { - if (owner->map->doesUnitTouchRessource(this, destinationPurpose, &dx, &dy)) - { - displacement=DIS_HARVESTING; - validTarget=false; - } - } - else if (displacement==DIS_HARVESTING) - { - // we got the ressource. - carriedRessource=destinationPurpose; - fprintf(logFile, "[%d] sdp5 destinationPurpose=%d\n", gid, destinationPurpose); - owner->map->decRessource(posX+dx, posY+dy, carriedRessource); - assert(movement == MOV_HARVESTING); - movement = MOV_RANDOM_GROUND; // we do this to avoid the handleMovement() to aditionaly decRessource() the same ressource. - - setTargetBuilding(attachedBuilding); - if (owner->map->doesUnitTouchBuilding(this, attachedBuilding->gid, &dx, &dy)) - { - displacement=DIS_FILLING_BUILDING; - validTarget=false; - } - else - { - displacement=DIS_GOING_TO_BUILDING; - targetX=targetBuilding->getMidX(); - targetY=targetBuilding->getMidY(); - validTarget=true; - } - } - else if (displacement==DIS_GOING_TO_BUILDING) - { - assert(targetBuilding); - if (owner->map->doesUnitTouchBuilding(this, targetBuilding->gid, &dx, &dy)) - { - displacement=DIS_FILLING_BUILDING; - validTarget=false; - } - } - else if (displacement==DIS_FILLING_BUILDING) - { - bool loopMove=false; - bool exchangeReady=false; - assert(targetBuilding); - if (targetBuilding==ownExchangeBuilding) - { - assert(targetBuilding); - assert(ownExchangeBuilding); - assert(targetBuilding->type->canExchange); - assert(ownExchangeBuilding->type->canExchange); - assert(owner==targetBuilding->owner); - assert(owner==ownExchangeBuilding->owner); - - assert(attachedBuilding); - assert(attachedBuilding->type->canFeedUnit); - assert(destinationPurpose>=HAPPYNESS_BASE); - - // Let's grab the right ressource. - - if (targetBuilding->ressources[destinationPurpose]>0) - { - targetBuilding->removeRessourceFromBuilding(destinationPurpose); - carriedRessource=destinationPurpose; - fprintf(logFile, "[%d] sdp6 destinationPurpose=%d\n", gid, destinationPurpose); - - setTargetBuilding(attachedBuilding); - displacement=DIS_GOING_TO_BUILDING; - targetX=targetBuilding->getMidX(); - targetY=targetBuilding->getMidY(); - validTarget=true; - exchangeReady=true; - if (verbose) - printf("guid=(%d) took a foreign fruit in our exhange building to food\n", gid); - } - } - else if ((carriedRessource>=0) && (targetBuilding->ressources[carriedRessource]type->maxRessource[carriedRessource])) - { - if (verbose) - printf("guid=(%d) Giving ressource (%d) to building gbid=(%d) old-amount=(%d)\n", gid, destinationPurpose, targetBuilding->gid, targetBuilding->ressources[carriedRessource]); - targetBuilding->addRessourceIntoBuilding(carriedRessource); - carriedRessource=-1; - } - - if (!loopMove && !exchangeReady) - { - //NOTE: if attachedBuilding has become NULL; it's beacause the building doesn't need me anymore. - if (!attachedBuilding) - { - if (verbose) - printf("guid=(%d) The building doesn't need me any more.\n", gid); - activity=ACT_RANDOM; - displacement=DIS_RANDOM; - validTarget=false; - assert(needToRecheckMedical); - } - else - { - ///Find a ressource that the building wants and a location to get it from - ///The location may be a market, or the harvesting the ressource from the - ///map. - int needs[MAX_NB_RESSOURCES]; - attachedBuilding->wishedRessources(needs); - int teamNumber=owner->teamNumber; - bool canSwim=performance[SWIM]; - int timeLeft = numberOfStepsLeftUntilHungry(); - if (timeLeft > 0) - { - int bestRessource=-1; - int minValue=owner->map->getW()+owner->map->getW(); - bool takeInExchangeBuilding=false; - Map* map=owner->map; - for (int r=0; r0) - { - int distToRessource; - if (map->ressourceAvailable(teamNumber, r, canSwim, posX, posY, &distToRessource)) - { - if ((distToRessource<<1)>=timeLeft) - continue; //We don't choose this ressource, because it won't have time to reach the ressource and bring it back. - int value=distToRessource/need; - if (valuetype->canFeedUnit) - for (std::list::iterator bi=owner->canExchange.begin(); bi!=owner->canExchange.end(); ++bi) - if ((*bi)->ressources[r]>0) - { - int buildingDist; - if (map->buildingAvailable(*bi, canSwim, posX, posY, &buildingDist)) - { - // We increase the cost to get a ressource in an exchange building to reflect the costs to get the ressources to the exchange building. - // increase is +5 as markets will in general be very close to fruits as they are the fruit teleporters. - int value=(buildingDist+5)/need; - if (value=0) - { - destinationPurpose=bestRessource; - fprintf(logFile, "[%d] sdp7 destinationPurpose=%d\n", gid, destinationPurpose); - assert(activity==ACT_FILLING); - if (takeInExchangeBuilding) - { - displacement=DIS_GOING_TO_BUILDING; - targetX=targetBuilding->getMidX(); - targetY=targetBuilding->getMidY(); - targetBuilding->insertUnitToHarvesting(this); - validTarget=true; - } - else - { - int dummyDist; - if (owner->map->doesUnitTouchRessource(this, destinationPurpose, &dx, &dy)) - { - displacement=DIS_HARVESTING; - validTarget=false; - } - else if (map->ressourceAvailableUpdate(teamNumber, destinationPurpose, canSwim, posX, posY, &targetX, &targetY, &dummyDist)) - { - fprintf(logFile, "[%d] rab targetXY=(%d, %d)\n", gid, targetX, targetY); - displacement=DIS_GOING_TO_RESSOURCE; - validTarget=true; - } - else - { - assert(false);//You can remove this assert(), but *do* notice me! - stopAttachedForBuilding(false); - } - } - } - else - { - if (verbose) - printf("guid=(%d) can't find any wished ressource, unsubscribing.\n", gid); - stopAttachedForBuilding(false); - } - } - else - { - if (verbose) - printf("guid=(%d) not enough time for anything, unsubscribing.\n", gid); - stopAttachedForBuilding(false); - } - } - } - } - else - { - displacement=DIS_RANDOM; - validTarget=false; - } - } - break; - - case ACT_UPGRADING: - { - assert(attachedBuilding); - - if (displacement==DIS_GOING_TO_BUILDING) - { - if (owner->map->doesUnitTouchBuilding(this, attachedBuilding->gid, &dx, &dy)) - { - displacement=DIS_ENTERING_BUILDING; - validTarget=false; - } - } - else if (displacement==DIS_ENTERING_BUILDING) - { - // The unit has already its room in the building, - // then we are sure that the unit can enter. - - if (performance[FLY]) - owner->map->setAirUnit(posX-dx, posY-dy, NOGUID); - else - owner->map->setGroundUnit(posX-dx, posY-dy, NOGUID); - displacement=DIS_INSIDE; - validTarget=false; - - if (destinationPurpose==FEED) - { - insideTimeout=-attachedBuilding->type->timeToFeedUnit; - speed=attachedBuilding->type->insideSpeed; - } - else if (destinationPurpose==HEAL) - { - //insideTimeout=-(attachedBuilding->type->timeToHealUnit*(performance[HP]-hp))/performance[HP]; - insideTimeout=-attachedBuilding->type->timeToHealUnit; - speed=(attachedBuilding->type->insideSpeed*performance[HP])/(performance[HP]-hp); - } - else - { - int levelsToBeUpgraded=attachedBuilding->type->level+1-level[destinationPurpose]; - insideTimeout=-attachedBuilding->type->upgradeTime[destinationPurpose]; - speed=attachedBuilding->type->insideSpeed/levelsToBeUpgraded; - } - } - else if (displacement==DIS_INSIDE) - { - // we stay inside while the unit upgrades. - if (insideTimeout>=0) - { - //printf("Exiting building\n"); - displacement=DIS_EXITING_BUILDING; - validTarget=false; - - if (destinationPurpose==FEED) - { - hungry=HUNGRY_MAX; - fruitCount=attachedBuilding->eatOnce(&fruitMask); - needToRecheckMedical=true; - } - else if (destinationPurpose==HEAL) - { - hp=performance[HP]; - //printf("I'm healed : healt h %d/%d\n", hp, performance[HP]); - needToRecheckMedical=true; - } - else - { - if (attachedBuilding->type->upgradeInParallel) - { - for (int ability = (int)WALK; ability < (int)ARMOR; ability++) - if (canLearn[ability] && attachedBuilding->type->upgrade[ability]) - { - level[ability] = attachedBuilding->type->level + 1; - UnitType *ut = race->getUnitType(typeNum, level[ability]); - performance[ability] = ut->performance[ability]; - } - } - else - { - //printf("Ability %d got level %d\n", destinationPurpose, attachedBuilding->type->level+1); - assert(canLearn[destinationPurpose]); - level[destinationPurpose] = attachedBuilding->type->level + 1; - UnitType *ut = race->getUnitType(typeNum, level[destinationPurpose]); - performance[destinationPurpose] = ut->performance[destinationPurpose]; - //printf("New performance[%d]=%d\n", destinationPurpose, performance[destinationPurpose]); - } - - - } - } - else - { - insideTimeout++; - } - } - else if (displacement==DIS_EXITING_BUILDING) - { - // we want to get out, so we still stay in displacement==DIS_EXITING_BUILDING. - } - else - { - displacement=DIS_RANDOM; - validTarget=false; - } - } - break; - - case ACT_FLAG: - { - assert(attachedBuilding); - displacement=DIS_GOING_TO_FLAG; - targetX=attachedBuilding->posX; - targetY=attachedBuilding->posY; - validTarget=true; - int distance=owner->map->warpDistSquare(targetX, targetY, posX, posY); - int usr=attachedBuilding->unitStayRange; - int usr2=usr*usr; - if (verbose) - printf("guid=(%d) ACT_FLAG distance=%d, usr2=%d\n", gid, distance, usr2); - - if (distance<=usr2) - { - validTarget=false; - if (typeNum==WORKER) - displacement=DIS_CLEARING_RESSOURCES; - else if (typeNum==EXPLORER) - displacement=DIS_REMOVING_BLACK_AROUND; - else if (typeNum==WARRIOR) - displacement=DIS_ATTACKING_AROUND; - else - assert(false); - } - else if (typeNum==WORKER) - { - int usr2plus=1+(usr+1)*(usr+1); - if (distance<=usr2plus) - { - Map *map=owner->map; - for (int tdx=-1; tdx<=1; tdx++) - for (int tdy=-1; tdy<=1; tdy++) - { - int x=posX+tdx; - int y=posY+tdy; - if (map->warpDistSquare(x, y, targetX, targetY)<=usr2 - && map->isRessourceTakeable(x, y, attachedBuilding->clearingRessources)) - { - dx=tdx; - dy=tdy; - validTarget=false; - displacement=DIS_CLEARING_RESSOURCES; - //movement=MOV_HARVESTING; - return; - } - } - } - } - } - break; - - default: - { - assert(false); - break; - } - } -} - -bool Unit::locationIsInEnemyGuardTowerRange(int x, int y)const -{ - //TODO: totally fix this totally hacky implementation. - for(int i=0;igame->teams[i]; - if((t)&&(owner->enemies & t->me)) - { - for(int j=0;jmyBuildings[j]; - if((b)&&(b->shortTypeNum==IntBuildingType::DEFENSE_BUILDING)&&(owner->map->warpDistMax(b->posX,b->posY,posX,posY) <= b->type->shootingRange + 1))return true; - } - } - } - return false; -} - -void Unit::handleMovement(void) -{ - // This variable says whether the unit is going to a clearing area - if(previousClearingAreaX != static_cast(-1)) - { - owner->map->setClearingAreaUnclaimed(previousClearingAreaX, previousClearingAreaY, owner->teamNumber); - previousClearingAreaX = static_cast(-1); - previousClearingAreaY = static_cast(-1); - } - - - // clearArea code, override behaviour locally - if (typeNum == WORKER && - medical == MED_FREE && - (displacement == DIS_RANDOM - || displacement == DIS_GOING_TO_FLAG - || displacement == DIS_GOING_TO_RESSOURCE - || displacement == DIS_GOING_TO_BUILDING)) - { - Map *map = owner->map; - // TODO : be sure this is the right thing to do and add a decent comment - if (movement == MOV_HARVESTING) - { - map->decRessource(posX + dx, posY + dy); - hp -= race->getUnitType(typeNum, level[HARVEST])->harvestDamage; - } - for (int tdx = -1; tdx <= 1; tdx++) - for (int tdy = -1; tdy <= 1; tdy++) - { - int x = (posX + tdx) & map->wMask; - int y = (posY + tdy) & map->hMask; - Case mapCase = map->cases[(y << map->wDec) + x]; - if ((mapCase.clearArea & owner->me) - && (mapCase.ressource.type != NO_RES_TYPE) - && ((mapCase.ressource.type == WOOD) - || (mapCase.ressource.type == CORN) - || (mapCase.ressource.type == PAPYRUS) - || (mapCase.ressource.type == ALGA)) - && !(mapCase.forbidden & owner->me)) - { - owner->map->setClearingAreaClaimed(posX+tdx, posY+tdy, owner->teamNumber, gid); - previousClearingAreaX = (posX+tdx) & map->wMask; - previousClearingAreaY = (posY+tdy) & map->hMask; - dx = tdx; - dy = tdy; - movement = MOV_HARVESTING; - return; - } - } - } - - switch (displacement) - { - case DIS_REMOVING_BLACK_AROUND: - { - assert(performance[FLY]); - if (verbose) - printf("guid=(%d) DIS_REMOVING_BLACK_AROUND\n", gid); - if (attachedBuilding) - { - movement=MOV_GOING_DX_DY; - int bposX=attachedBuilding->posX; - int bposY=attachedBuilding->posY; - - int ldx=bposX-posX; - int ldy=bposY-posY; - int cdx, cdy; - simplifyDirection(ldx, ldy, &cdx, &cdy); - - dx=-cdy; - dy=cdx; - if (!owner->map->isMapDiscovered(posX+4*cdx, posY+4*cdy, owner->sharedVisionOther)) - { - dx=cdx; - dy=cdy; - } - } - else if ((movement!=MOV_GOING_DX_DY)||((syncRand()&0xFF)<0xEF)) - { - // "c" is the center of the unit, "x" are the sample spots: - // oxoooxo - //ooooooooo - //xooooooox - //ooooooooo - //oooocoooo - //ooooooooo - //xooooooox - //ooooooooo - // oxoooxo - bool found = false; - const int dxTab[8] = {-4, -2, +2, +4, +4, +2, -2, -4}; - const int dyTab[8] = {-2, -4, -4, -2, +2, +4, +4, +2}; - int tab[8]; - for (int i = 0; i < 8; i++) - { - tab[i] = owner->map->getExplored(posX + dxTab[i], posY + dyTab[i], owner->teamNumber); - //also move around enemy towers: - if(locationIsInEnemyGuardTowerRange(posX + dxTab[i], posY + dyTab[i]))tab[i]=1; - } - //printf("tab "); - //for (int i = 0; i < 8; i++) - // printf("%3d; ", tab[i]); - //printf("d=%d\n", direction); - for (int di = 0; di < 8; di++) - { - int d = (di + direction + 4) % 8; - //Move in a direction in which you circle counter-clockwise - //about explored area, while exploring. - if ((tab[d] > 0) && (tab[(d + 1) % 8] == 0) && (tab[(d + 2) % 8] == 0)) - { - direction = (d + 1) % 8; - dxDyFromDirection(); - movement = MOV_GOING_DX_DY; - found = true; - /* - fprintf (stderr, "gid = %d; changed direction: direction = %d, dx = %d, dy = %d; tab = {", gid, direction, dx, dy); - for (int i = 0; i < 8; i++) { - fprintf (stderr, "%d%s", tab[i], ((i < 7) ? ", " : "")); } - fprintf (stderr, "}\n"); - */ - break; - } - } - if (!found) - { - int scoreX = 0; - int scoreY = 0; - /* The next line should really be calculated only once per game. How to do this? The point is to avoid wrapping around the torus in considering what area is closer to us. */ - int maxRange = (std::min(owner->map->getW(), owner->map->getH())) / 2; - /* We sample cells at various - distances to decide in what - direction there is more - unexplored territory. */ - for (int range = 1; range <= maxRange; range *= 2) - { - for (int delta = -3; delta <= 3; delta++) - { - scoreX += owner->map->getExplored(posX - (4*range), posY + (delta*range), owner->teamNumber); - scoreX -= owner->map->getExplored(posX + (4*range), posY + (delta*range), owner->teamNumber); - scoreY += owner->map->getExplored(posX + (delta*range), posY - (4*range), owner->teamNumber); - scoreY -= owner->map->getExplored(posX + (delta*range), posY + (4*range), owner->teamNumber); - } - } - int cdx, cdy; - simplifyDirection(scoreX, scoreY, &cdx, &cdy); - // fprintf(stderr, "gid = %d, maxRange = %d, score = (%2d, %2d), cd = (%d, %d)\n", gid, maxRange, scoreX, scoreY, cdx, cdy); - if (cdx == 0 && cdy == 0) - movement = MOV_RANDOM_FLY; - else - { - dx = cdx; - dy = cdy; - directionFromDxDy(); - movement = MOV_GOING_DX_DY; - } - } - } - if (movement!=MOV_GOING_DX_DY || owner->map->getAirUnit(posX+dx, posY+dy)!=NOGUID) - movement=MOV_RANDOM_FLY; - } - break; - - case DIS_ATTACKING_AROUND: - { - assert(performance[ATTACK_SPEED]); - int quality=INT_MAX; // Smaller is better. - movement=MOV_RANDOM_GROUND; - if (verbose) - printf("guid=(%d) selecting movement\n", gid); - - ///Don't change targets if we still have a valid target - if(owner->map->doesUnitTouchEnemy(this, &dx, &dy)) - { - targetX = posX+dx; - targetY = posY+dy; - movement=MOV_ATTACKING_TARGET; - } - else - { - Building *tempTargetBuilding=NULL; - // we look for the best target to attack around us - for (int x=-8; x<=8; x++) - { - for (int y=-8; y<=8; y++) - { - if (owner->map->isFOWDiscovered(posX+x, posY+y, owner->sharedVisionOther)) - { - if (attachedBuilding && - owner->map->warpDistSquare(posX+x, posY+y, attachedBuilding->posX, attachedBuilding->posY) - >((int)attachedBuilding->unitStayRange*(int)attachedBuilding->unitStayRange)) - continue; - Uint16 gid; - gid=owner->map->getBuilding(posX+x, posY+y); - if (gid!=NOGBID) - { - int team=Building::GIDtoTeam(gid); - if (owner->enemies & (1<game->teams[team]->myBuildings[id]; - BuildingType *bt=b->type; - int shootDamage=bt->shootDamage; - newQuality/=(1+shootDamage); - if (verbose) - printf("guid=(%d) warrior found building with newQuality=%d\n", this->gid, newQuality); - if (newQualitymap->pathfindPointToPoint(posX, posY, posX+x, posY+y, &dx, &dy, (performance[SWIM] > 0 ? true : false), owner->me, 12); - if(pathfind) - { - if (abs(x)<=1 && abs(y)<=1) - { - movement=MOV_ATTACKING_TARGET; - dx=x; - dy=y; - } - else - { - movement=MOV_GOING_TARGET; - tempTargetBuilding=b; - } - targetX=posX+x; - targetY=posY+y; - validTarget=true; - quality=newQuality; - } - } - } - } - gid=owner->map->getGroundUnit(posX+x, posY+y); - if (gid!=NOGUID) - { - int team=Unit::GIDtoTeam(gid); - Uint32 tm=(1<enemies & tm) - { - int id=Building::GIDtoID(gid); - Unit *u=owner->game->teams[team]->myUnits[id]; - if (((owner->sharedVisionExchange & tm)==0)) - { - int attackStrength=u->getRealAttackStrength(); - int newQuality=((x*x+y*y)<<8)/(1+attackStrength); - if (verbose) - printf("guid=(%d) warrior found unit with newQuality=%d\n", this->gid, newQuality); - if (newQualitymap->pathfindPointToPoint(posX, posY, posX+x, posY+y, &dx, &dy, (performance[SWIM] > 0 ? true : false), owner->me, 12); - if(pathfind) - { - if (abs(x)<=1 && abs(y)<=1) - { - movement=MOV_ATTACKING_TARGET; - dx=x; - dy=y; - } - else - { - movement=MOV_GOING_TARGET; - tempTargetBuilding=NULL; - } - targetX=posX+x; - targetY=posY+y; - validTarget=true; - quality=newQuality; - } - } - } - } - } - } - } - } - } - - // if we haven't find anything satisfactory, follow guard area gradients - if (movement == MOV_RANDOM_GROUND) - { - if (!attachedBuilding && owner->map->pathfindGuardArea(owner->teamNumber, (performance[SWIM]>0), posX, posY, &dx, &dy)) - { - directionFromDxDy(); - movement = MOV_GOING_DX_DY; - // get the target position of guard area for display - owner->map->getGlobalGradientDestination(owner->map->guardAreasGradient[owner->teamNumber][performance[SWIM]>0], posX, posY, &targetX, &targetY); - validTarget=true; - } - else if (attachedBuilding || (owner->map->getGuardAreasGradient(posX, posY, performance[SWIM]>0, owner->teamNumber) == 255)) - { - // are we into the guard area or war flag and we have to go to the least known area. - int bestExplored = 3*255; - int bestDirection = -1; - for (int di = 0; di < 8; di++) - { - int d = (direction + di) & 7; - int cdx, cdy; - dxDyFromDirection(d, &cdx, &cdy); - if (!owner->map->isFreeForGroundUnit(posX + cdx, posY + cdy, performance[SWIM]>0, owner->me)) - continue; - if (attachedBuilding) - { - if (owner->map->warpDistSquare(posX + cdx, posY + cdy, attachedBuilding->posX, attachedBuilding->posY) - > ((int)attachedBuilding->unitStayRange * (int)attachedBuilding->unitStayRange)) - continue; - } - else - { - if (owner->map->getGuardAreasGradient(posX + cdx, posY + cdy, performance[SWIM]>0, owner->teamNumber) != 255) - continue; - } - Uint8 explored = owner->map->getExplored(posX + 2*cdx, posY + 2*cdy, owner->teamNumber); - explored += owner->map->getExplored(posX + 2*cdx - cdy, posY + 2*cdy + cdx, owner->teamNumber); - explored += owner->map->getExplored(posX + 2*cdx + cdy, posY + 2*cdy - cdx, owner->teamNumber); - if (bestExplored > explored) - { - bestExplored = explored; - bestDirection = d; - } - } - if (bestDirection >= 0) - { - direction = bestDirection; - dxDyFromDirection(); - movement = MOV_GOING_DX_DY; - validTarget = false; - } - else - { - movement = MOV_RANDOM_GROUND; - validTarget = false; - } - } - else - { - // this case happens when no movement could be found because of busy places or because we are in a guard area or because there is no guard area - movement = MOV_RANDOM_GROUND; - validTarget = false; - } - } - } - break; - - case DIS_CLEARING_RESSOURCES: - { - Map *map=owner->map; - if (movement==MOV_HARVESTING) - { - map->decRessource(posX+dx, posY+dy); - hp -= race->getUnitType(typeNum, level[HARVEST])->harvestDamage; - } - - int bx=attachedBuilding->posX; - int by=attachedBuilding->posY; - int usr=attachedBuilding->unitStayRange; - int usr2=usr*usr; - for (int tdx=-1; tdx<=1; tdx++) - for (int tdy=-1; tdy<=1; tdy++) - { - int x=posX+tdx; - int y=posY+tdy; - if (map->warpDistSquare(x, y, bx, by)<=usr2 && map->isRessourceTakeable(x, y, attachedBuilding->clearingRessources) && !(owner->map->isForbidden(x, y, owner->me))) - { - dx=tdx; - dy=tdy; - movement=MOV_HARVESTING; - return; - } - } - bool canSwim=performance[SWIM]; - assert(attachedBuilding); - if (map->pathfindLocalRessource(attachedBuilding, canSwim, posX, posY, &dx, &dy)) - { - directionFromDxDy(); - movement=MOV_GOING_DX_DY; - } - else if (attachedBuilding->anyRessourceToClear[canSwim]==2) - { - stopAttachedForBuilding(false); - movement=MOV_RANDOM_GROUND; - } - else - movement=MOV_RANDOM_GROUND; - } - break; - - case DIS_RANDOM: - { - Map *map=owner->map; - if ((performance[ATTACK_SPEED]) && (medical==MED_FREE) && (map->doesUnitTouchEnemy(this, &dx, &dy))) - movement=MOV_ATTACKING_TARGET; - else if (performance[FLY]) - movement=MOV_RANDOM_FLY; - else if (map->getForbidden(posX, posY)&owner->me) - { - if (map->pathfindForbidden(NULL, owner->teamNumber, (performance[SWIM]>0), posX, posY, &dx, &dy, verbose)) - directionFromDxDy(); - else - { - dx=0; - dy=0; - direction=8; - } - movement=MOV_GOING_DX_DY; - } - else if(performance[HARVEST]) - { - ///Value of 254 means nothing found - int distance = 255-owner->map->getClearingGradient(owner->teamNumber,performance[SWIM]>0, posX, posY); - if(distance < ((hungry-trigHungry) / race->hungryness) && distance < 254 && medical == MED_FREE) - { - int tempTargetX, tempTargetY; - bool path = owner->map->getGlobalGradientDestination(owner->map->clearAreasGradient[owner->teamNumber][performance[SWIM]>0], posX, posY, &tempTargetX, &tempTargetY); - int guid = owner->map->isClearingAreaClaimed(tempTargetX, tempTargetY, owner->teamNumber); - int other_distance = INT_MAX; - if(guid != NOGUID) - { - Unit* unit = owner->myUnits[GIDtoID(guid)]; - if(unit) - other_distance = unit->previousClearingAreaDistance; - } - if(path && distance < other_distance) - { - dx=0; - dy=0; - owner->map->pathfindClearArea(owner->teamNumber, (performance[SWIM]>0), posX, posY, &dx, &dy); - - targetX = tempTargetX; - targetY = tempTargetY; - previousClearingAreaX = tempTargetX; - previousClearingAreaY = tempTargetY; - previousClearingAreaDistance = distance; - - if(guid != NOGUID) - { - Unit* unit = owner->myUnits[GIDtoID(guid)]; - if(unit) - { - unit->previousClearingAreaX=static_cast(-1); - unit->previousClearingAreaY=static_cast(-1); - unit->previousClearingAreaDistance=static_cast(-1); - } - } - - //Find clearing ressource - directionFromDxDy(); - movement = MOV_GOING_DX_DY; - owner->map->setClearingAreaClaimed(targetX, targetY, owner->teamNumber, gid); - validTarget=true; - } - else - movement=MOV_RANDOM_GROUND; - } - else - movement=MOV_RANDOM_GROUND; - } - else - movement=MOV_RANDOM_GROUND; - } - break; - - case DIS_GOING_TO_FLAG: - case DIS_GOING_TO_BUILDING: - { - Map *map=owner->map; - bool canSwim=performance[SWIM]; - - if ((performance[ATTACK_SPEED]) && (medical==MED_FREE) && (owner->map->doesUnitTouchEnemy(this, &dx, &dy))) - movement=MOV_ATTACKING_TARGET; - else if (performance[FLY]) - { - movement=MOV_FLYING_TARGET; - } - else if (map->pathfindBuilding(targetBuilding, canSwim, posX, posY, &dx, &dy, verbose)) - { - if (verbose) - printf("guid=(%d) Unit found path b pos=(%d, %d) to building %d, d=(%d, %d)\n", gid, posX, posY, attachedBuilding->gid, dx, dy); - movement=MOV_GOING_DX_DY; - } - else - { - if (verbose) - printf("guid=(%d) Unit failed path b pos=(%d, %d) to building %d, d=(%d, %d)\n", gid, posX, posY, attachedBuilding->gid, dx, dy); - stopAttachedForBuilding(true); - movement=MOV_RANDOM_GROUND; - } - } - break; - - case DIS_ENTERING_BUILDING: - { - movement=MOV_ENTERING_BUILDING; - } - break; - - case DIS_INSIDE: - { - movement=MOV_INSIDE; - } - break; - - case DIS_EXITING_BUILDING: - { - bool exitFound; - if (performance[FLY]) - exitFound=attachedBuilding->findAirExit(&posX, &posY, &dx, &dy); - else - exitFound=attachedBuilding->findGroundExit(&posX, &posY, &dx, &dy, performance[SWIM]); - if (exitFound) - { - activity=ACT_RANDOM; - movement=MOV_EXITING_BUILDING; - fprintf(logFile, "guid=(%d) exiting gbid=%d\n", gid, attachedBuilding->gid); - attachedBuilding->removeUnitFromInside(this); - attachedBuilding->updateConstructionState(); - attachedBuilding=NULL; - setTargetBuilding(NULL); - assert(ownExchangeBuilding==NULL); - assert(needToRecheckMedical); - } - else - { - fprintf(logFile, "guid=(%d) can't find exit, gbid=%d\n", gid, attachedBuilding->gid); - movement=MOV_INSIDE; - } - } - break; - - case DIS_GOING_TO_RESSOURCE: - { - Map *map=owner->map; - int teamNumber=owner->teamNumber; - bool canSwim=performance[SWIM]>0; - bool stopWork; - if (map->pathfindRessource(teamNumber, destinationPurpose, canSwim, posX, posY, &dx, &dy, &stopWork, verbose)) - { - if (verbose) - printf("guid=(%d) Unit found path r pos=(%d, %d) to ressource %d, d=(%d, %d)\n", gid, posX, posY, destinationPurpose, dx, dy); - directionFromDxDy(); - movement=MOV_GOING_DX_DY; - } - else - { - if (verbose) - printf("guid=(%d) Unit failed path r pos=(%d, %d) to ressource %d, aborting work.\n", gid, posX, posY, destinationPurpose); - - if (stopWork) - stopAttachedForBuilding(false); - movement=MOV_RANDOM_GROUND; - } - } - break; - - case DIS_HARVESTING: - { - movement=MOV_HARVESTING; - } - break; - - case DIS_FILLING_BUILDING: - { - movement=MOV_FILLING; - } - break; - - default: - { - assert (false); - } - break; - } -} - -void Unit::handleAction(void) -{ - owner->map->clearImmobileUnit(posX, posY); - switch (movement) - { - case MOV_RANDOM_GROUND: - { - assert(!performance[FLY]); - owner->map->setGroundUnit(posX, posY, NOGUID); - owner->map->pathfindRandom(this, verbose); - posX=(posX+dx)&(owner->map->getMaskW()); - posY=(posY+dy)&(owner->map->getMaskH()); - selectPreferredGroundMovement(); - speed=performance[action]; - assert(owner->map->getGroundUnit(posX, posY)==NOGUID); - owner->map->setGroundUnit(posX, posY, gid); - break; - } - - case MOV_RANDOM_FLY: - { - assert(performance[FLY]); - owner->map->setAirUnit(posX, posY, NOGUID); - for(int q = 0; q < 5; ++q) //hack - look for a direction safe from guard towers - { - dx=-1+syncRand()%3; - dy=-1+syncRand()%3; - if(locationIsInEnemyGuardTowerRange(posX + dx, posY + dy))continue; - else break; - } - directionFromDxDy(); - setNewValidDirectionAir(); - posX=(posX+dx)&(owner->map->getMaskW()); - posY=(posY+dy)&(owner->map->getMaskH()); - action=FLY; - speed=performance[FLY]; - assert(owner->map->getAirUnit(posX, posY)==NOGUID); - owner->map->setAirUnit(posX, posY, gid); - break; - } - - case MOV_GOING_TARGET: - { - assert(!performance[FLY]); - owner->map->setGroundUnit(posX, posY, NOGUID); - owner->map->pathfindPointToPoint(posX, posY, targetX, targetY, &dx, &dy, (performance[SWIM] > 0 ? true : false), owner->me, 12); - directionFromDxDy(); - posX=(posX+dx)&(owner->map->getMaskW()); - posY=(posY+dy)&(owner->map->getMaskH()); - - if(dx == 0 && dy == 0) - owner->map->markImmobileUnit(posX, posY, owner->teamNumber); - - selectPreferredGroundMovement(); - speed=performance[action]; - assert(owner->map->getGroundUnit(posX, posY)==NOGUID); - owner->map->setGroundUnit(posX, posY, gid); - break; - } - - case MOV_FLYING_TARGET: - { - owner->map->setAirUnit(posX, posY, NOGUID); - - flyToTarget(); - - posX=(posX+dx)&(owner->map->getMaskW()); - posY=(posY+dy)&(owner->map->getMaskH()); - - action=FLY; - speed=performance[FLY]; - - owner->map->setAirUnit(posX, posY, gid); - break; - } - - case MOV_GOING_DX_DY: - { - bool fly=performance[FLY]; - if (fly) - owner->map->setAirUnit(posX, posY, NOGUID); - else - owner->map->setGroundUnit(posX, posY, NOGUID); - - directionFromDxDy(); - - posX=(posX+dx)&(owner->map->getMaskW()); - posY=(posY+dy)&(owner->map->getMaskH()); - - if(dx == 0 && dy == 0) - owner->map->markImmobileUnit(posX, posY, owner->teamNumber); - - selectPreferredMovement(); - speed=performance[action]; - - if (fly) - { - assert(owner->map->getAirUnit(posX, posY)==NOGUID); - owner->map->setAirUnit(posX, posY, gid); - } - else - { - assert(owner->map->getGroundUnit(posX, posY)==NOGUID); - owner->map->setGroundUnit(posX, posY, gid); - } - - if (verbose) - printf("guid=(%d) MOV_GOING_DX_DY d=(%d, %d; %d).\n", gid, direction, dx, dy); - break; - } - - case MOV_ENTERING_BUILDING: - { - // NOTE : this is a hack : We don't delete the unit on the map - // because we have to draw it while it is entering. - // owner->map->setUnit(posX, posY, NOUID); - posX=(posX+dx)&(owner->map->getMaskW()); - posY=(posY+dy)&(owner->map->getMaskH()); - directionFromDxDy(); - selectPreferredMovement(); - speed=performance[action]; - break; - } - - case MOV_EXITING_BUILDING: - { - directionFromDxDy(); - selectPreferredMovement(); - speed=performance[action]; - - if (performance[FLY]) - { - assert(owner->map->getAirUnit(posX, posY)==NOGUID); - owner->map->setAirUnit(posX, posY, gid); - } - else - { - assert(owner->map->getGroundUnit(posX, posY)==NOGUID); - owner->map->setGroundUnit(posX, posY, gid); - } - break; - } - - case MOV_INSIDE: - { - break; - } - - case MOV_FILLING: - { - owner->map->markImmobileUnit(posX, posY, owner->teamNumber); - directionFromDxDy(); - action=BUILD; - speed=performance[action]; - break; - } - - case MOV_ATTACKING_TARGET: - { - owner->map->markImmobileUnit(posX, posY, owner->teamNumber); - directionFromDxDy(); - action=ATTACK_SPEED; - speed=performance[action]; - break; - } - - case MOV_HARVESTING: - { - owner->map->markImmobileUnit(posX, posY, owner->teamNumber); - directionFromDxDy(); - action=HARVEST; - speed=performance[action]; - assert(speed!=0); - break; - } - - default: - { - assert (false); - break; - } - } -} - -void Unit::setNewValidDirectionGround(void) -{ - assert(!performance[FLY]); - int i=0; - bool swim=(performance[SWIM]>0); - Uint32 me=owner->me; - while ( i<8 && !owner->map->isFreeForGroundUnit(posX+dx, posY+dy, swim, me)) - { - direction=(direction+1)&7; - dxDyFromDirection(); - i++; - } - if (i==8) - { - direction=8; - dxDyFromDirection(); - } -} - -void Unit::setNewValidDirectionAir(void) -{ - assert(performance[FLY]); - int i=0; - while ( i<8 && !owner->map->isFreeForAirUnit(posX+dx, posY+dy)) - { - direction=(direction+1)&7; - dxDyFromDirection(); - i++; - } - if (i==8) - { - direction=8; - dx=0; - dy=0; - } -} - -void Unit::flyToTarget() -{ - assert(performance[FLY]); - int ldx=targetX-posX; - int ldy=targetY-posY; - simplifyDirection(ldx, ldy, &dx, &dy); - directionFromDxDy(); - Map *map=owner->map; - if (map->isFreeForAirUnit(posX+dx, posY+dy)) - return; - int cDirection=direction; - direction=(cDirection+1)&7; - dxDyFromDirection(); - if (map->isFreeForAirUnit(posX+dx, posY+dy)) - return; - direction=(cDirection+7)&7; - dxDyFromDirection(); - if (map->isFreeForAirUnit(posX+dx, posY+dy)) - return; - direction=(cDirection+2)&7; - dxDyFromDirection(); - if (map->isFreeForAirUnit(posX+dx, posY+dy)) - return; - direction=(cDirection+6)&7; - dxDyFromDirection(); - if (map->isFreeForAirUnit(posX+dx, posY+dy)) - return; - direction=(cDirection+3)&7; - dxDyFromDirection(); - if (map->isFreeForAirUnit(posX+dx, posY+dy)) - return; - direction=(cDirection+5)&7; - dxDyFromDirection(); - if (map->isFreeForAirUnit(posX+dx, posY+dy)) - return; - direction=(cDirection+4)&7; - dxDyFromDirection(); - if (map->isFreeForAirUnit(posX+dx, posY+dy)) - return; - dx=0; - dy=0; - direction=8; - if (verbose) - printf("guid=(%d) flyto failed pos=(%d, %d) \n", gid, posX, posY); -} - - -void Unit::escapeGroundTarget() -{ - int ldx=posX-targetX; - int ldy=posY-targetY; - simplifyDirection(ldx, ldy, &dx, &dy); - directionFromDxDy(); - bool canSwim=performance[SWIM]; - Map *map=owner->map; - if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) - return; - int cDirection=direction; - direction=(cDirection+1)&7; - dxDyFromDirection(); - if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) - return; - direction=(cDirection+7)&7; - dxDyFromDirection(); - if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) - return; - direction=(cDirection+2)&7; - dxDyFromDirection(); - if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) - return; - direction=(cDirection+6)&7; - dxDyFromDirection(); - if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) - return; - direction=(cDirection+3)&7; - dxDyFromDirection(); - if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) - return; - direction=(cDirection+5)&7; - dxDyFromDirection(); - if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) - return; - direction=(cDirection+4)&7; - dxDyFromDirection(); - if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) - return; - dx=0; - dy=0; - direction=8; - if (verbose) - printf("guid=(%d) escapeGroundTarget failed pos=(%d, %d) \n", gid, posX, posY); -} - -void Unit::endOfAction(void) -{ - handleMedical(); - if (isDead) - return; - handleActivity(); - handleDisplacement(); - handleMovement(); - handleAction(); -} - -// NOTE : position 0 is top left (-1, -1) then run clockwise - -void Unit::directionFromDxDy(void) -{ - const int tab[3][3]={ {0, 1, 2}, - {7, 8, 3}, - {6, 5, 4} }; - assert(dx>=-1); - assert(dx<=1); - assert(dy>=-1); - assert(dy<=1); - direction=tab[dy+1][dx+1]; -} - -void Unit::dxDyFromDirection(void) -{ - dxDyFromDirection(direction,&dx,&dy); -} - -int Unit::directionFromDxDy(int dx, int dy) -{ - const int tab[3][3]={ {0, 1, 2}, - {7, 8, 3}, - {6, 5, 4} }; - assert(dx>=-1); - assert(dx<=1); - assert(dy>=-1); - assert(dy<=1); - return tab[dy+1][dx+1]; -} - -void Unit::simplifyDirection(int ldx, int ldy, int *cdx, int *cdy) -{ - int mapW=owner->map->getW(); - int mapH=owner->map->getH(); - if (ldx>(mapW>>1)) - ldx-=mapW; - else if (ldx<-(mapW>>1)) - ldx+=mapW; - if (ldy>(mapH>>1)) - ldy-=mapH; - else if (ldy<-(mapH>>1)) - ldy+=mapH; - - /* We consider a cell to be vertical or horizontal in - direction (rather than diagonal) if it is 2.41 times more - vertical than horizontal, or vice versa. This is because - the halfway point between 45 degrees and 90 degrees is 67.5 - degrees and sin(67.5 deg) / cos(67.5 deg) = - 2.41421356237. */ - if ((100 * abs(ldx)) > (241 * abs(ldy))) - { - *cdx=SIGN(ldx); - *cdy=0; - } - else if ((100 * abs(ldy)) > (241 * abs(ldx))) - { - *cdx=0; - *cdy=SIGN(ldy); - } - else - { - *cdx=SIGN(ldx); - *cdy=SIGN(ldy); - } -} - -//! Return the real armor, taking into account the reduction due to fruits -int Unit::getRealArmor(bool isMagic) const -{ - int armorReductionPerHappyness = race->getUnitType(typeNum, level[ARMOR])->armorReductionPerHappyness; - if (isMagic) //magic bypasses armor yet fruit penalties still apply - return 0 - fruitCount * armorReductionPerHappyness; - else - return performance[ARMOR] - fruitCount * armorReductionPerHappyness; -} - -//! Return the real attack strengh, taking into account the experience level -int Unit::getRealAttackStrength(void) const -{ - return performance[ATTACK_STRENGTH] + experienceLevel; -} - -//! Return the amount of experience to level-up -int Unit::getNextLevelThreshold(void) const -{ - return (experienceLevel + 1) * (experienceLevel + 1) * race->getUnitType(typeNum, level[ATTACK_STRENGTH])->experiencePerLevel; -} - -//! Increment experience. If level-up occures, handle it. Multiple level-up may occur at once. -void Unit::incrementExperience(int increment) -{ - experience += increment; - int nextLevelThreshold = getNextLevelThreshold(); - while (experience > nextLevelThreshold) - { - experience -= nextLevelThreshold; - experienceLevel++; - nextLevelThreshold = getNextLevelThreshold(); - levelUpAnimation = LEVEL_UP_ANIMATION_FRAME_COUNT; - } -} - -//! Compute the skin pointer from a skin name -void Unit::skinPointerFromName(void) -{ - if (!globalContainer->runNoX) - { - skin = globalContainer->unitsSkins->getSkin(skinName); - if (skin == NULL) - { - // if skin is invalid, retry with default - std::cerr << "Unit::skinPointerFromName : invalid skin name " << skinName << std::endl; - defaultSkinNameFromType(); - skin = globalContainer->unitsSkins->getSkin(skinName); - if (!skin) - abort(); - } - } - else - skin = NULL; -} - - -//! Compute the skin name from the unit type -void Unit::defaultSkinNameFromType(void) -{ - switch (typeNum) - { - case WORKER: skinName = "worker"; break; - case EXPLORER: skinName = "explorer"; break; - case WARRIOR: skinName = "warrior"; break; - default: assert(false); break; - } -} - -//! Return how many steps we can do until we are hungry -int Unit::numberOfStepsLeftUntilHungry(void) -{ - int timeLeft; - if (hungryness) - timeLeft = (hungry-trigHungry) / hungryness; - else - timeLeft = INT_MAX; - stepsLeftUntilHungry = timeLeft; - return timeLeft; -} - -//! Iterate on all resource types to see if it is gettable -void Unit::computeMinDistToResources(void) -{ - bool allResourcesAreTooFar = true; - for (size_t ri = 0; ri < MAX_RESSOURCES; ri++) - if (!owner->map->ressourceAvailable(owner->teamNumber, ri, performance[SWIM], posX, posY, &minDistToResource[ri])) - minDistToResource[ri] = -1; - else if (minDistToResource[ri] < stepsLeftUntilHungry) - allResourcesAreTooFar = false; - // the dist to an already carried resource is zero - if (carriedRessource >= 0) - minDistToResource[carriedRessource] = 0; -} - -bool Unit::integrity() -{ - checkInvariant(gid<32768); - if (isDead) - return true; - - if (!needToRecheckMedical) - { - checkInvariant(activity==ACT_UPGRADING); - checkInvariant(destinationPurpose==HEAL || destinationPurpose==FEED); - } - return true; -} - -Uint32 Unit::checkSum(std::vector *checkSumsVector) -{ - Uint32 cs=0; - - cs^=typeNum; - if (checkSumsVector) - checkSumsVector->push_back(typeNum);// [0] - cs=(cs<<1)|(cs>>31); - - cs^=isDead; - if (checkSumsVector) - checkSumsVector->push_back(isDead);// [1] - cs=(cs<<1)|(cs>>31); - cs^=gid; - if (checkSumsVector) - checkSumsVector->push_back(gid);// [2] - cs=(cs<<1)|(cs>>31); - - cs^=posX; - if (checkSumsVector) - checkSumsVector->push_back(posX);// [3] - cs=(cs<<1)|(cs>>31); - cs^=posY; - if (checkSumsVector) - checkSumsVector->push_back(posY);// [4] - cs=(cs<<1)|(cs>>31); - cs^=delta; - if (checkSumsVector) - checkSumsVector->push_back(delta);// [5] - cs=(cs<<1)|(cs>>31); - cs^=dx; - if (checkSumsVector) - checkSumsVector->push_back(dx);// [6] - cs^=dy; - if (checkSumsVector) - checkSumsVector->push_back(dy);// [7] - cs^=direction; - if (checkSumsVector) - checkSumsVector->push_back(direction);// [8] - cs=(cs<<1)|(cs>>31); - cs^=insideTimeout; - if (checkSumsVector) - checkSumsVector->push_back(insideTimeout);// [9] - cs=(cs<<1)|(cs>>31); - cs^=speed; - if (checkSumsVector) - checkSumsVector->push_back(speed);// [10] - cs=(cs<<1)|(cs>>31); - - cs^=(int)needToRecheckMedical; - if (checkSumsVector) - checkSumsVector->push_back(needToRecheckMedical);// [11] - cs=(cs<<1)|(cs>>31); - cs^=medical; - if (checkSumsVector) - checkSumsVector->push_back(medical);// [12] - cs^=activity; - if (checkSumsVector) - checkSumsVector->push_back(activity);// [13] - cs^=displacement; - if (checkSumsVector) - checkSumsVector->push_back(displacement);// [14] - cs^=movement; - if (checkSumsVector) - checkSumsVector->push_back(movement);// [15] - cs^=action; - if (checkSumsVector) - checkSumsVector->push_back(action);// [16] - cs=(cs<<1)|(cs>>31); - cs^=targetX; - if (checkSumsVector) - checkSumsVector->push_back(targetX);// [17] - cs^=targetY; - if (checkSumsVector) - checkSumsVector->push_back(targetY);// [18] - cs=(cs<<1)|(cs>>31); - - cs^=hp; - if (checkSumsVector) - checkSumsVector->push_back(hp);// [19] - cs^=trigHP; - if (checkSumsVector) - checkSumsVector->push_back(trigHP);// [20] - cs=(cs<<1)|(cs>>31); - - cs^=hungry; - if (checkSumsVector) - checkSumsVector->push_back(hungry);// [21] - cs^=trigHungry; - if (checkSumsVector) - checkSumsVector->push_back(trigHungry);// [22] - cs^=trigHungryCarying; - if (checkSumsVector) - checkSumsVector->push_back(trigHungryCarying);// [23] - cs=(cs<<1)|(cs>>31); - - cs^=fruitMask; - if (checkSumsVector) - checkSumsVector->push_back(fruitMask);// [24] - cs^=fruitCount; - if (checkSumsVector) - checkSumsVector->push_back(fruitCount);// [25] - cs=(cs<<1)|(cs>>31); - - for (int i=0; i>31); - cs^=level[i]; - cs=(cs<<1)|(cs>>31); - cs^=(Uint32)canLearn[i]; - cs=(cs<<1)|(cs>>31); - } - if (checkSumsVector) - checkSumsVector->push_back(cs);// [26] - cs=(cs<<1)|(cs>>31); - - cs^=(attachedBuilding!=NULL ? 1:0); - if (checkSumsVector) - checkSumsVector->push_back((attachedBuilding!=NULL ? 1:0));// [27] - cs=(cs<<1)|(cs>>31); - cs^=(targetBuilding!=NULL ? 1:0); - if (checkSumsVector) - checkSumsVector->push_back((targetBuilding!=NULL ? 1:0));// [28] - cs^=(ownExchangeBuilding!=NULL ? 2:0); - if (checkSumsVector) - checkSumsVector->push_back((ownExchangeBuilding!=NULL ? 1:0));// [29] - cs=(cs<<1)|(cs>>31); - - cs^=destinationPurpose; - if (checkSumsVector) - checkSumsVector->push_back(destinationPurpose);// [31] - cs^=carriedRessource; - if (checkSumsVector) - checkSumsVector->push_back(carriedRessource);// [33] - - if (checkSumsVector) - checkSumsVector->push_back(0);// [34] - if (checkSumsVector) - checkSumsVector->push_back(0);// [35] - if (checkSumsVector) - checkSumsVector->push_back(0);// [36] - if (checkSumsVector) - checkSumsVector->push_back(0);// [37] - if (checkSumsVector) - checkSumsVector->push_back(0);// [38] - if (checkSumsVector) - checkSumsVector->push_back(0);// [39] - - return cs; -} diff --git a/src/UnitConsts.cpp b/src/UnitConsts.cpp deleted file mode 100644 index 4f4a9cc45..000000000 --- a/src/UnitConsts.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (C) Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "Toolkit.h" -#include "StringTable.h" - -#include "UnitConsts.h" - -using namespace GAGCore; - -std::string getUnitName(int type) -{ - switch(type) - { - case WORKER: - return Toolkit::getStringTable()->getString("[Worker]"); - case WARRIOR: - return Toolkit::getStringTable()->getString("[Warrior]"); - case EXPLORER: - return Toolkit::getStringTable()->getString("[Explorer]"); - default: - assert(false); - return "";//to satisfy -Wall - } -} diff --git a/src/UnitConsts.h b/src/UnitConsts.h deleted file mode 100644 index c29046a24..000000000 --- a/src/UnitConsts.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - Copyright (C) Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __UNIT_CONSTS_H -#define __UNIT_CONSTS_H - -#include -#include - -enum Abilities -{ - STOP_WALK=0, - STOP_SWIM=1, - STOP_FLY=2, - - WALK=3, - SWIM=4, - FLY=5, - BUILD=6, - HARVEST=7, - ATTACK_SPEED=8, - ATTACK_STRENGTH=9, - - MAGIC_ATTACK_AIR=10, - MAGIC_ATTACK_GROUND=11, - MAGIC_CREATE_WOOD=12, - MAGIC_CREATE_CORN=13, - MAGIC_CREATE_ALGA=14, - - ARMOR=15, /* old 10 */ - HP=16, /* old 11 */ - - HEAL=17, /* old 12 */ - FEED=18 /* old 13 */ -}; -const int NB_MOVE=9; -const int NB_ABILITY=17; - -const int WORKER=0; -const int EXPLORER=1; -const int WARRIOR=2; -const int NB_UNIT_TYPE=3; - -const int NB_UNIT_LEVELS=4; - -std::string getUnitName(int type); - -#endif - diff --git a/src/UnitEditorScreen.cpp b/src/UnitEditorScreen.cpp index abe309daf..079a98b3f 100644 --- a/src/UnitEditorScreen.cpp +++ b/src/UnitEditorScreen.cpp @@ -1,62 +1,38 @@ -/* - Copyright (C) 2001-2006 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2006 Stephane Magnenat & Luc-Olivier de Charrière #include "UnitEditorScreen.h" #include "GlobalContainer.h" #include "Unit.h" -#include "UnitsSkins.h" #include #include #include #include +#include UnitEditorScreen::UnitEditorScreen(Unit *toEdit) : OverlayScreen(globalContainer->gfx, 300, 400) { assert(toEdit); unit = toEdit; - + // window title addWidget(new Text(0, 5, ALIGN_FILL, ALIGN_TOP, "menu", Toolkit::getStringTable()->getString("[Unit editor]"))); - + // parameters int ypos = 50; - addWidget(new Text(10, ypos, ALIGN_LEFT, ALIGN_TOP, "standard", Toolkit::getStringTable()->getString("[skin]"))); - skin = new MultiTextButton(10, ypos, 100, 25, ALIGN_RIGHT, ALIGN_TOP, "standard", "", -1); - addWidget(skin); -// - ypos += 30; addWidget(new Text(10, ypos, ALIGN_LEFT, ALIGN_TOP, "standard", Toolkit::getStringTable()->getString("[hungryness]"))); hungryness = new TextInput(10, ypos, 100, 25, ALIGN_RIGHT, ALIGN_TOP, "standard", ""); addWidget(hungryness); - + // ok / cancel addWidget(new TextButton(10, 10, 135, 40, ALIGN_LEFT, ALIGN_BOTTOM, "menu", Toolkit::getStringTable()->getString("[ok]"), OK, 13)); addWidget(new TextButton(10, 10, 135, 40, ALIGN_RIGHT, ALIGN_BOTTOM, "menu", Toolkit::getStringTable()->getString("[Cancel]"), CANCEL, 27)); - + // important, widgets must be initialised by hand as we use custom event loop dispatchInit(); - - // change widgets's properties - globalContainer->unitsSkins->buildSkinsList(skin); - skin->setIndexFromText(unit->skinName); + hungryness->setText(unit->hungryness); } @@ -72,20 +48,11 @@ void UnitEditorScreen::onAction(Widget *source, Action action, int par1, int par if (par1 == OK) { endValue = par1; - unit->skinName = skin->getText(); - unit->skinPointerFromName(); unit->hungryness = hungryness->getText(); } else if (par1 == CANCEL) { endValue = par1; } - }/* - if (action==TEXT_ACTIVATED) - { - if (source==skin) - hungryness->deactivate(); - else if (source==hungryness) - skin->deactivate(); - }*/ + } } diff --git a/src/UnitEditorScreen.h b/src/UnitEditorScreen.h index c90dd6f74..ee73aacb9 100644 --- a/src/UnitEditorScreen.h +++ b/src/UnitEditorScreen.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2006 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2006 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __UNIT_EDITOR_SCREEN_H -#define __UNIT_EDITOR_SCREEN_H +#pragma once #include @@ -26,7 +9,6 @@ namespace GAGGUI { class TextInput; - class MultiTextButton; } using namespace GAGGUI; class Unit; @@ -50,8 +32,6 @@ class UnitEditorScreen : public OverlayScreen protected: Unit *unit; //!< unit being edited - MultiTextButton *skin; TextInput *hungryness; }; -#endif diff --git a/src/UnitSkin.cpp b/src/UnitSkin.cpp deleted file mode 100644 index 40bcbd619..000000000 --- a/src/UnitSkin.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "UnitSkin.h" -#include -#include -#include - -bool UnitSkin::load(GAGCore::InputStream *stream) -{ - std::string spriteName = stream->readText("spriteName"); - if (spriteName == "") - return false; - - sprite = Toolkit::getSprite(spriteName); - if (!sprite) - { - std::cerr << "Can't load unit sprite " << spriteName << ", abording" << std::endl; - return false; - } - startImage[STOP_WALK] = stream->readUint32("startImageStopWalk"); - startImage[STOP_SWIM] = stream->readUint32("startImageStopSwim"); - startImage[STOP_FLY] = stream->readUint32("startImageStopFly"); - startImage[WALK] = stream->readUint32("startImageWalk"); - startImage[SWIM] = stream->readUint32("startImageSwim"); - startImage[FLY] = stream->readUint32("startImageFly"); - startImage[BUILD] = stream->readUint32("startImageBuild"); - startImage[HARVEST] = stream->readUint32("startImageHarvest"); - startImage[ATTACK_SPEED] = stream->readUint32("startImageAttack"); - - return true; -} diff --git a/src/UnitSkin.h b/src/UnitSkin.h deleted file mode 100644 index b485653ef..000000000 --- a/src/UnitSkin.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __UNITSKIN_H -#define __UNITSKIN_H - -#include -#include "UnitConsts.h" - -namespace GAGCore -{ - class InputStream; - class Sprite; -} -using namespace GAGCore; - -class UnitSkin -{ -public: - Sprite *sprite; - Uint32 startImage[NB_MOVE]; - -public: - bool load(GAGCore::InputStream *stream); -}; - -#endif diff --git a/src/UnitType.h b/src/UnitType.h deleted file mode 100644 index 68acf825f..000000000 --- a/src/UnitType.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __UNITTYPE_H -#define __UNITTYPE_H - -#include -#include "UnitConsts.h" - -namespace GAGCore -{ - class InputStream; - class OutputStream; -} - -class UnitType -{ -public: - // caracteristic modulated by player choice, if 0, feature disabled - // display infos - Uint32 startImage[NB_MOVE]; - - Sint32 hungryness; - - Sint32 performance[NB_ABILITY]; - - Sint32 harvestDamage; - Sint32 armorReductionPerHappyness; - Sint32 experiencePerLevel; - - Sint32 magicActionCooldown; - -public: - UnitType() {} - UnitType(GAGCore::InputStream *stream, Sint32 versionMinor) { load(stream, versionMinor); } - virtual ~UnitType() {} - -public: - UnitType& operator+=(const UnitType &a); - UnitType operator+(const UnitType &a); - UnitType& operator/=(int a); - UnitType operator/(int a); - UnitType& operator*=(int a); - UnitType operator*(int a); - int operator*(const UnitType &a); - - void copyIf(const UnitType a, const UnitType b); - void copyIfNot(const UnitType a, const UnitType b); - - void load(GAGCore::InputStream *stream, Sint32 versionMinor); - void save(GAGCore::OutputStream *stream); - Uint32 checkSum(void); -}; - -#endif - diff --git a/src/UnitUtils.cpp b/src/UnitUtils.cpp deleted file mode 100644 index 61945611f..000000000 --- a/src/UnitUtils.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "UnitUtils.h" -#include "Team.h" - - -Sint32 UnitUtils::GIDtoID(Uint16 gid) -{ - assert(gid < UnitUtils::MAX_COUNT * Team::MAX_COUNT); - return (gid % UnitUtils::MAX_COUNT); -} - -Sint32 UnitUtils::GIDtoTeam(Uint16 gid) -{ - assert(gid < UnitUtils::MAX_COUNT * Team::MAX_COUNT); - return (gid / UnitUtils::MAX_COUNT); -} - -Uint16 UnitUtils::GIDfrom(Sint32 id, Sint32 team) -{ - assert(id >= 0); - assert(id < UnitUtils::MAX_COUNT); - assert(team >= 0); - assert(team < Team::MAX_COUNT); - return id + team * UnitUtils::MAX_COUNT; -} diff --git a/src/UnitUtils.h b/src/UnitUtils.h deleted file mode 100644 index ccb017bdd..000000000 --- a/src/UnitUtils.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __UNIT_UTILS_H -#define __UNIT_UTILS_H - -#include - -class UnitUtils -{ - public: - static Sint32 GIDtoID(Uint16 gid); - static Sint32 GIDtoTeam(Uint16 gid); - static Uint16 GIDfrom(Sint32 id, Sint32 team); - - static const int MAX_COUNT = 1024; -}; - - -#endif // __UNIT_UTILS_H - diff --git a/src/UnitsSkins.cpp b/src/UnitsSkins.cpp deleted file mode 100644 index dff6df168..000000000 --- a/src/UnitsSkins.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "UnitsSkins.h" -#include "UnitSkin.h" -#include -#include -#include -#include -#include - -//! Constructor, open a stream from data/unitsSkins.txt -UnitsSkins::UnitsSkins() -{ - StreamBackend *backend = Toolkit::getFileManager()->openInputStreamBackend("data/unitsSkins.txt"); - TextInputStream *stream = new TextInputStream(backend); - delete backend; - if (stream->isEndOfStream()) - { - std::cerr << "UnitsSkins::UnitsSkins() : error, can't open file data/unitsSkins.txt." << std::endl; - delete stream; - abort(); - return; - } - - // read all entries - std::set entries; - stream->getSubSections("", &entries); - - for (std::set::const_iterator it = entries.begin(); it != entries.end(); ++it) - { - const std::string &name = *it; - UnitSkin *unitSkin = new UnitSkin; - - stream->readEnterSection(name.c_str()); - bool result = unitSkin->load(stream); - stream->readLeaveSection(); - - if (result) - unitsSkins[name] = unitSkin; - else - delete unitSkin; - } - - delete stream; -} - -//! Destructor, close the stream from data/unitsSkins.txt -UnitsSkins::~UnitsSkins() -{ - for (std::map::iterator it = unitsSkins.begin(); it != unitsSkins.end(); ++it) - delete it->second; - -} - -//! Return the skin corresponding to name. If no such skin exist, return NULL -UnitSkin *UnitsSkins::getSkin(const std::string &name) -{ - std::map::const_iterator it = unitsSkins.find(name); - if (it != unitsSkins.end()) - return it->second; - else - return NULL; - /*else - { - UnitSkin *unitSkin = new UnitSkin; - stream->readEnterSection(name.c_str()); - bool result = unitSkin->load(stream); - stream->readLeaveSection(); - if (result) - { - unitsSkins[name] = unitSkin; - return unitSkin; - } - else - { - delete unitSkin; - return NULL; - } - }*/ -} - -//! Fill target with the list of names of all available skins -void UnitsSkins::buildSkinsList(MultiTextButton *target) const -{ - assert(target); - for (std::map::const_iterator it = unitsSkins.begin(); it != unitsSkins.end(); ++it) - { - target->addText(it->first); - } -} diff --git a/src/UnitsSkins.h b/src/UnitsSkins.h deleted file mode 100644 index c668a72b5..000000000 --- a/src/UnitsSkins.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __UNITSSKINS_H -#define __UNITSSKINS_H - -#include -#include - -namespace GAGCore -{ - class TextInputStream; -} -using namespace GAGCore; -namespace GAGGUI -{ - class MultiTextButton; -} -using namespace GAGGUI; -class UnitSkin; - -class UnitsSkins -{ -public: - UnitsSkins(); - virtual ~UnitsSkins(); - - UnitSkin *getSkin(const std::string &name); - void buildSkinsList(MultiTextButton *target) const; - -protected: - std::map unitsSkins; -}; - -#endif diff --git a/src/Utilities.cpp b/src/Utilities.cpp index ecf7f9e0b..ee902f6ae 100644 --- a/src/Utilities.cpp +++ b/src/Utilities.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #ifdef _MSC_VER #include // for _open, _write diff --git a/src/Utilities.h b/src/Utilities.h index f8697dba4..94d2c4076 100644 --- a/src/Utilities.h +++ b/src/Utilities.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __UTILITIES_H_GZ -#define __UTILITIES_H_GZ +#pragma once #include #include @@ -41,6 +24,22 @@ inline Uint32 syncRand(void) return randomGenerator(); } +// 32-bit right-rotate by 1 bit. Used to mix per-section checksums into the +// running game/network checksum so that re-ordered identical inputs produce +// different outputs. Open-coded as `(x<<31)|(x>>1)` so the compiler emits a +// single ROR instruction without a branch on the shift amount; modern +// compilers also recognize this exact named-helper form. +// +// Determinism note: this rotate feeds the on-the-wire lockstep checksum -- +// match it exactly in the Rust port (use `u32::rotate_right(1)`). Do not +// substitute a similar-looking expression like `x >> 1` (a divide). +inline Uint32 rotr1(Uint32 x) { return (x << 31) | (x >> 1); } + +// 32-bit left-rotate by 1 bit. Sibling of rotr1 -- used by Unit and Map +// checksum mixers. Same determinism caveats apply: the Rust port must use +// `u32::rotate_left(1)`. +inline Uint32 rotl1(Uint32 x) { return (x << 1) | (x >> 31); } + ///The actual random seeds are stored in GameHeader, which automatically randomizes them void setSyncRandSeed(); void setSyncRandSeed(Uint32 seed); @@ -150,5 +149,4 @@ namespace Utilities void write(int fd, const void *buf, size_t count); }; -#endif diff --git a/src/Version.h b/src/Version.h index 1f8d325bb..49bf5d406 100644 --- a/src/Version.h +++ b/src/Version.h @@ -1,29 +1,12 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __VERSION_H -#define __VERSION_H +#pragma once // This is the version of map and savegame format, and all of the recorded datas on the server #define VERSION_MAJOR 0 #define MINIMUM_VERSION_MINOR 58 -#define VERSION_MINOR 83 +#define VERSION_MINOR 84 // version 10 adds script saved in game // version 11 the gamesfiles do saves which building has been seen under fog of war. // version 12 saves map name into SessionGame instead of BaseMap. @@ -102,6 +85,7 @@ //beta5: // version 82 integrated new map script system // version 83 added a description to campaigns +// version 84 dropped per-unit skinName (skin is now derived from typeNum) //This must be updated when there are changes to YOG, MapHeader, GameHeader, BasePlayer, BaseTeam, //NetMessage, and the likes, in parrallel to change of the VERSION_MINOR above @@ -117,4 +101,3 @@ // version 27 reordered the NetMessages so that reverse compatibility with future game versions can be done, added random seed in GameHeader // version 28 Nicowar's behavior was changed -#endif diff --git a/src/VoiceRecorder.cpp b/src/VoiceRecorder.cpp index 4a14d4e1e..a1bd53e09 100644 --- a/src/VoiceRecorder.cpp +++ b/src/VoiceRecorder.cpp @@ -1,23 +1,7 @@ -/* - This file is part of Globulation 2, a free software real-time strategy game - http://www.globulation2.org - Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charriere and other contributors - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charriere and other contributors - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// This file is part of Globulation 2, a free software real-time strategy game #include "VoiceRecorder.h" #include @@ -75,7 +59,7 @@ void PaFlushVoiceData(VoiceRecorder* recorder) SpeexBits& bits = recorder->bits; int byteLength = speex_bits_nbytes(&bits); - boost::shared_ptr order(new OrderVoiceData(0, byteLength, recorder->frameCount, NULL)); + std::shared_ptr order(new OrderVoiceData(0, byteLength, recorder->frameCount, NULL)); int nbBytes = speex_bits_write(&bits, (char *)order->getFramesData(), byteLength); assert(byteLength == nbBytes); @@ -295,7 +279,7 @@ int record(void *pointer) int byteLength = speex_bits_nbytes(&bits); if (byteLength > MAX_VOICE_MULTI_FRAME_LENGTH || totalRead > MAX_VOICE_MULTI_FRAME_SAMPLE_COUNT) { - boost::shared_ptr order(new OrderVoiceData(0, byteLength, frameCount, NULL)); + std::shared_ptr order(new OrderVoiceData(0, byteLength, frameCount, NULL)); int nbBytes = speex_bits_write(&bits, (char *)order->getFramesData(), byteLength); assert(byteLength == nbBytes); @@ -314,7 +298,7 @@ int record(void *pointer) int byteLength = speex_bits_nbytes(&bits); if (byteLength > 0) { - boost::shared_ptr order(new OrderVoiceData(0, byteLength, frameCount, NULL)); + std::shared_ptr order(new OrderVoiceData(0, byteLength, frameCount, NULL)); int nbBytes = speex_bits_write(&bits, (char *)order->getFramesData(), byteLength); assert(byteLength == nbBytes); @@ -449,13 +433,13 @@ void VoiceRecorder::stopRecording(void) #endif } -boost::shared_ptr VoiceRecorder::getNextOrder(void) +std::shared_ptr VoiceRecorder::getNextOrder(void) { - boost::shared_ptr order; + std::shared_ptr order; SDL_LockMutex(ordersMutex); if (orders.empty()) { - order = boost::shared_ptr(); + order = std::shared_ptr(); } else { diff --git a/src/VoiceRecorder.h b/src/VoiceRecorder.h index a1a8e46d0..f6961ed39 100644 --- a/src/VoiceRecorder.h +++ b/src/VoiceRecorder.h @@ -1,31 +1,14 @@ -/* - This file is part of Globulation 2, a free software real-time strategy game - http://www.globulation2.org - Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charriere and other contributors - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charriere and other contributors - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +// This file is part of Globulation 2, a free software real-time strategy game - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __VOICE_RECORDER_H -#define __VOICE_RECORDER_H +#pragma once #include #include #include -#include +#include #include "config.h" #ifdef HAVE_PORTAUDIO @@ -52,7 +35,7 @@ class VoiceRecorder //! Mutex for orders SDL_mutex *ordersMutex; //! Queue of orders to be sent through the network - std::queue > orders; + std::queue > orders; //! True when recording bool recordingNow; @@ -81,6 +64,5 @@ class VoiceRecorder //! Stop recording void stopRecording(void); //! Return the next voice data order from the internal queue - boost::shared_ptr getNextOrder(void); + std::shared_ptr getNextOrder(void); }; -#endif diff --git a/src/WinningConditions.cpp b/src/WinningConditions.cpp index a14aacb43..509ccf68c 100644 --- a/src/WinningConditions.cpp +++ b/src/WinningConditions.cpp @@ -1,104 +1,83 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "WinningConditions.h" #include "Game.h" #include #include "Stream.h" -boost::shared_ptr WinningCondition::getWinningCondition(GAGCore::InputStream* stream, Uint32 versionMinor) +namespace +{ + bool teamsAreMutuallyAllied(const Game* game, int a, int b) + { + const Uint32 aInBsAllies = game->teams[a]->me & game->teams[b]->allies; + const Uint32 bInAsAllies = game->teams[b]->me & game->teams[a]->allies; + return aInBsAllies && bInAsAllies; + } + + int maximumPrestige(const Game* game) + { + int maximum = 0; + for (int i = 0; i < game->mapHeader.getNumberOfTeams(); ++i) + maximum = std::max(maximum, game->teams[i]->prestige); + return maximum; + } + + template + std::shared_ptr decodeAs(GAGCore::InputStream* stream, Uint32 versionMinor) + { + auto condition = std::make_shared(); + condition->decodeData(stream, versionMinor); + return condition; + } +} + +std::shared_ptr WinningCondition::getWinningCondition(GAGCore::InputStream* stream, Uint32 versionMinor) { if (stream->isEndOfStream()) - return boost::shared_ptr(); - + return std::shared_ptr(); + Uint8 type = stream->readUint8("type"); - + switch (type) { - case WCDeath: - { - boost::shared_ptr condition(new WinningConditionDeath); - condition->decodeData(stream, versionMinor); - return condition; - } - break; - case WCAllies: - { - boost::shared_ptr condition(new WinningConditionAllies); - condition->decodeData(stream, versionMinor); - return condition; - } - break; - case WCPrestige: - { - boost::shared_ptr condition(new WinningConditionPrestige); - condition->decodeData(stream, versionMinor); - return condition; - } - break; - case WCScript: - { - boost::shared_ptr condition(new WinningConditionScript); - condition->decodeData(stream, versionMinor); - return condition; - } - break; - case WCOpponentsDefeated: - { - boost::shared_ptr condition(new WinningConditionOpponentsDefeated); - condition->decodeData(stream, versionMinor); - return condition; - } - break; + case WCDeath: return decodeAs(stream, versionMinor); + case WCAllies: return decodeAs(stream, versionMinor); + case WCPrestige: return decodeAs(stream, versionMinor); + case WCScript: return decodeAs(stream, versionMinor); + case WCOpponentsDefeated: return decodeAs(stream, versionMinor); case WCUnknown: default: break; } assert(false); - return boost::shared_ptr();//to satisfy -Wall + return std::shared_ptr();//to satisfy -Wall } -std::list > WinningCondition::getDefaultWinningConditions() +std::list > WinningCondition::getDefaultWinningConditions() { - std::list > conditions; - conditions.push_back(boost::shared_ptr(new WinningConditionDeath)); - conditions.push_back(boost::shared_ptr(new WinningConditionAllies)); - conditions.push_back(boost::shared_ptr(new WinningConditionPrestige)); - conditions.push_back(boost::shared_ptr(new WinningConditionScript)); - conditions.push_back(boost::shared_ptr(new WinningConditionOpponentsDefeated)); - return conditions; + return { + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared(), + }; } -bool WinningConditionDeath::hasTeamWon(int team, Game* game) +bool WinningConditionDeath::hasTeamWon(int team, const Game* game) const { return false; } -bool WinningConditionDeath::hasTeamLost(int team, Game* game) +bool WinningConditionDeath::hasTeamLost(int team, const Game* game) const { - if(game->teams[team]->isAlive) - return false; - return true; + return !game->teams[team]->isAlive; } @@ -127,25 +106,21 @@ void WinningConditionDeath::decodeData(GAGCore::InputStream* stream, Uint32 vers -bool WinningConditionAllies::hasTeamWon(int team, Game* game) +bool WinningConditionAllies::hasTeamWon(int team, const Game* game) const { for(int i=0; imapHeader.getNumberOfTeams(); ++i) { - Uint32 playerToMeAllyMask = game->teams[team]->me & game->teams[i]->allies; - Uint32 meToPlayerAllyMask = game->teams[i]->me & game->teams[team]->allies; - if(playerToMeAllyMask && meToPlayerAllyMask && game->teams[i]->hasWon) - { + if(teamsAreMutuallyAllied(game, team, i) && game->teams[i]->hasWon) return true; - } } return false; } -bool WinningConditionAllies::hasTeamLost(int team, Game* game) +bool WinningConditionAllies::hasTeamLost(int team, const Game* game) const { - return false; + return false; } @@ -174,42 +149,20 @@ void WinningConditionAllies::decodeData(GAGCore::InputStream* stream, Uint32 ver -bool WinningConditionPrestige::hasTeamWon(int team, Game* game) +bool WinningConditionPrestige::hasTeamWon(int team, const Game* game) const { - if(game->totalPrestige >= game->prestigeToReach) - { - int totalPrestige=0; - int maximum = 0; - for(int i=0; imapHeader.getNumberOfTeams(); ++i) - { - totalPrestige += game->teams[i]->prestige; - maximum = std::max(maximum, game->teams[i]->prestige); - } - - if(game->teams[team]->prestige == maximum) - return true; - } - return false; + if(game->totalPrestige < game->prestigeToReach) + return false; + return game->teams[team]->prestige == maximumPrestige(game); } -bool WinningConditionPrestige::hasTeamLost(int team, Game* game) +bool WinningConditionPrestige::hasTeamLost(int team, const Game* game) const { - if(game->totalPrestige >= game->prestigeToReach) - { - int totalPrestige=0; - int maximum = 0; - for(int i=0; imapHeader.getNumberOfTeams(); ++i) - { - totalPrestige += game->teams[i]->prestige; - maximum = std::max(maximum, game->teams[i]->prestige); - } - - if(game->teams[team]->prestige < maximum) - return true; - } - return false; + if(game->totalPrestige < game->prestigeToReach) + return false; + return game->teams[team]->prestige < maximumPrestige(game); } @@ -237,27 +190,31 @@ void WinningConditionPrestige::decodeData(GAGCore::InputStream* stream, Uint32 v } -#ifndef YOG_SERVER_ONLY -bool WinningConditionScript::hasTeamWon(int team, Game* game) +bool WinningConditionScript::hasTeamWon(int team, const Game* game) const { - if(game->sgslScript.hasTeamWon(team)) - { - return true; - } +#ifdef YOG_SERVER_ONLY + // SGSL.cpp is not linked into the server; the server never calls this + // (Team::checkWinConditions is client-only). Stub keeps the class concrete. + (void)team; + (void)game; return false; +#else + return game->sgslScript.hasTeamWon(team); +#endif } -bool WinningConditionScript::hasTeamLost(int team, Game* game) +bool WinningConditionScript::hasTeamLost(int team, const Game* game) const { - if(game->sgslScript.hasTeamLost(team)) - { - return true; - } +#ifdef YOG_SERVER_ONLY + (void)team; + (void)game; return false; +#else + return game->sgslScript.hasTeamLost(team); +#endif } -#endif // !YOG_SERVER_ONLY WinningConditionType WinningConditionScript::getType() const @@ -284,26 +241,19 @@ void WinningConditionScript::decodeData(GAGCore::InputStream* stream, Uint32 ver -bool WinningConditionOpponentsDefeated::hasTeamWon(int team, Game* game) +bool WinningConditionOpponentsDefeated::hasTeamWon(int team, const Game* game) const { - bool allEnemiesLost = true; for(int i=0; imapHeader.getNumberOfTeams(); ++i) { - Uint32 playerToMeAllyMask = game->teams[team]->me & game->teams[i]->allies; - Uint32 meToPlayerAllyMask = game->teams[i]->me & game->teams[team]->allies; - if((playerToMeAllyMask == 0 || meToPlayerAllyMask==0) && game->teams[i]->hasLost == false) - { - allEnemiesLost=false; - } + if(!teamsAreMutuallyAllied(game, team, i) && !game->teams[i]->hasLost) + return false; } - if(allEnemiesLost) - return true; - return false; + return true; } -bool WinningConditionOpponentsDefeated::hasTeamLost(int team, Game* game) +bool WinningConditionOpponentsDefeated::hasTeamLost(int team, const Game* game) const { return false; } diff --git a/src/WinningConditions.h b/src/WinningConditions.h index 48e55b0b8..e92824823 100644 --- a/src/WinningConditions.h +++ b/src/WinningConditions.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef WinningConditions_h -#define WinningConditions_h - -#include "boost/shared_ptr.hpp" +#include #include "SDL_net.h" #include @@ -48,48 +32,46 @@ enum WinningConditionType class WinningCondition { public: - // These two methods in WinningConditionScript depend on SGSL.cpp, - // but they aren't needed for server. -#ifndef YOG_SERVER_ONLY + virtual ~WinningCondition() = default; + ///Returns true if the particular player has won according to this winning condition - virtual bool hasTeamWon(int team, Game* game)=0; + virtual bool hasTeamWon(int team, const Game* game) const = 0; ///Returns true if the particular player has lost according to this winning condition - virtual bool hasTeamLost(int team, Game* game)=0; -#endif // !YOG_SERVER_ONLY + virtual bool hasTeamLost(int team, const Game* game) const = 0; ///Returns the winning condition type virtual WinningConditionType getType() const=0; ///This will encode the data in this winning condition to a stream. All derived class must start by saving a Uint8 from getType() virtual void encodeData(GAGCore::OutputStream* stream) const = 0; ///This will decode data. It is important that, unlike encodeData, this must ignore the initial Uint8 virtual void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor)=0; - + ///This will reconstruct a winning condition from serialized data - static boost::shared_ptr getWinningCondition(GAGCore::InputStream* stream, Uint32 versionMinor); + static std::shared_ptr getWinningCondition(GAGCore::InputStream* stream, Uint32 versionMinor); ///This will set the given list to the default set of winning conditions, in their default order - static std::list > getDefaultWinningConditions(); - + static std::list > getDefaultWinningConditions(); + }; ///A team has lost if its dead. class WinningConditionDeath : public WinningCondition { public: - bool hasTeamWon(int team, Game* game); - bool hasTeamLost(int team, Game* game); - WinningConditionType getType() const; - void encodeData(GAGCore::OutputStream* stream) const; - void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor); + bool hasTeamWon(int team, const Game* game) const override; + bool hasTeamLost(int team, const Game* game) const override; + WinningConditionType getType() const override; + void encodeData(GAGCore::OutputStream* stream) const override; + void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor) override; }; ///A team has won if one of its allies has won class WinningConditionAllies : public WinningCondition { public: - bool hasTeamWon(int team, Game* game); - bool hasTeamLost(int team, Game* game); - WinningConditionType getType() const; - void encodeData(GAGCore::OutputStream* stream) const; - void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor); + bool hasTeamWon(int team, const Game* game) const override; + bool hasTeamLost(int team, const Game* game) const override; + WinningConditionType getType() const override; + void encodeData(GAGCore::OutputStream* stream) const override; + void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor) override; }; ///A team has won if the prestige limit is reached and its above the prestige amount @@ -97,37 +79,36 @@ class WinningConditionAllies : public WinningCondition class WinningConditionPrestige : public WinningCondition { public: - bool hasTeamWon(int team, Game* game); - bool hasTeamLost(int team, Game* game); - WinningConditionType getType() const; - void encodeData(GAGCore::OutputStream* stream) const; - void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor); + bool hasTeamWon(int team, const Game* game) const override; + bool hasTeamLost(int team, const Game* game) const override; + WinningConditionType getType() const override; + void encodeData(GAGCore::OutputStream* stream) const override; + void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor) override; }; -///A team has won if the script says it has won, and lost if the script says it has lost +///A team has won if the script says it has won, and lost if the script says it has lost. +///In server builds, hasTeamWon/hasTeamLost are stubbed to false: SGSL.cpp is not in the +///server link, and Team::checkWinConditions (the sole caller) is client-only. class WinningConditionScript : public WinningCondition { public: -#ifndef YOG_SERVER_ONLY - bool hasTeamWon(int team, Game* game); - bool hasTeamLost(int team, Game* game); -#endif // !YOG_SERVER_ONLY - WinningConditionType getType() const; - void encodeData(GAGCore::OutputStream* stream) const; - void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor); + bool hasTeamWon(int team, const Game* game) const override; + bool hasTeamLost(int team, const Game* game) const override; + WinningConditionType getType() const override; + void encodeData(GAGCore::OutputStream* stream) const override; + void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor) override; }; ///A team has won if all enemies have lost class WinningConditionOpponentsDefeated : public WinningCondition { public: - bool hasTeamWon(int team, Game* game); - bool hasTeamLost(int team, Game* game); - WinningConditionType getType() const; - void encodeData(GAGCore::OutputStream* stream) const; - void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor); + bool hasTeamWon(int team, const Game* game) const override; + bool hasTeamLost(int team, const Game* game) const override; + WinningConditionType getType() const override; + void encodeData(GAGCore::OutputStream* stream) const override; + void decodeData(GAGCore::InputStream* stream, Uint32 versionMinor) override; }; -#endif diff --git a/src/YOGClientBlockedList.h b/src/YOGClientBlockedList.h deleted file mode 100644 index e4ea91f36..000000000 --- a/src/YOGClientBlockedList.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientBlockedList_h -#define YOGClientBlockedList_h - -#include -#include - -///This holds a player-end blocked list -class YOGClientBlockedList -{ -public: - YOGClientBlockedList(const std::string& username); - - ///Loads from the blocked list text file - void load(); - - ///Saves to the blocked list text file - void save(); - - ///Adds a player as blocked - void addBlockedPlayer(const std::string& name); - - ///Returns true if the given player is blocked - bool isPlayerBlocked(const std::string& name); - - ///Removes a player from the blocked list - void removeBlockedPlayer(const std::string& name); - - ///Returns a set containing all blocked players - const std::set& getBlockedPlayers() const; -private: - std::set blockedPlayers; - std::string username; -}; - - -#endif diff --git a/src/YOGClientChatChannel.cpp b/src/YOGClientChatChannel.cpp deleted file mode 100644 index ddb110ac9..000000000 --- a/src/YOGClientChatChannel.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGClientChatChannel.h" -#include "YOGClient.h" -#include "YOGMessage.h" -#include "YOGClientChatListener.h" -#include "NetMessage.h" - -YOGClientChatChannel::YOGClientChatChannel(Uint32 channelID, boost::shared_ptr client) - : client(client), channelID(channelID) -{ - client->addYOGClientChatChannel(this); -} - - - -YOGClientChatChannel::~YOGClientChatChannel() -{ - client->removeYOGClientChatChannel(this); -} - - - -Uint32 YOGClientChatChannel::getHistorySize() const -{ - return messageHistory.size(); -} - - - -const boost::shared_ptr YOGClientChatChannel::getMessage(Uint32 n) const -{ - return messageHistory[n].get<0>(); -} - - - -boost::posix_time::ptime YOGClientChatChannel::getMessageTime(Uint32 n) const -{ - return messageHistory[n].get<1>(); -} - - - -void YOGClientChatChannel::sendMessage(boost::shared_ptr message) -{ - if(channelID != static_cast(-1)) - { - messageHistory.push_back(boost::make_tuple(message, boost::posix_time::second_clock::local_time())); - boost::shared_ptr netmessage(new NetSendYOGMessage(channelID, message)); - client->sendNetMessage(netmessage); - sendToListeners(message); - } -} - - - -Uint32 YOGClientChatChannel::getChannelID() const -{ - return channelID; -} - - - -void YOGClientChatChannel::setChannelID(Uint32 channel) -{ - client->removeYOGClientChatChannel(this); - channelID = channel; - client->addYOGClientChatChannel(this); -} - - - -void YOGClientChatChannel::addListener(YOGClientChatListener* listener) -{ - listeners.push_back(listener); -} - - - -void YOGClientChatChannel::removeListener(YOGClientChatListener* listener) -{ - listeners.remove(listener); -} - - - -void YOGClientChatChannel::recieveMessage(boost::shared_ptr message) -{ - messageHistory.push_back(boost::make_tuple(message, boost::posix_time::second_clock::local_time())); - sendToListeners(message); -} - - - -void YOGClientChatChannel::sendToListeners(boost::shared_ptr message) -{ - for(std::list::iterator i = listeners.begin(); i!=listeners.end(); ++i) - { - (*i)->recieveTextMessage(message); - } -} - - diff --git a/src/YOGClientChatListener.cpp b/src/YOGClientChatListener.cpp deleted file mode 100644 index 578f8377f..000000000 --- a/src/YOGClientChatListener.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGClientChatListener.h" - diff --git a/src/YOGClientChatListener.h b/src/YOGClientChatListener.h deleted file mode 100644 index fc81cc379..000000000 --- a/src/YOGClientChatListener.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#ifndef __YOGClientChatListener_h -#define __YOGClientChatListener_h - -#include "boost/shared_ptr.hpp" - -class YOGMessage; - -///This class is a mix-in class for objects that want to listen for recieved texts -class YOGClientChatListener -{ -public: - virtual ~YOGClientChatListener() {} - - ///Recieves a text message - virtual void recieveTextMessage(boost::shared_ptr message)=0; -}; - -#endif diff --git a/src/YOGClientCommandManager.h b/src/YOGClientCommandManager.h deleted file mode 100644 index c7fb91596..000000000 --- a/src/YOGClientCommandManager.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientCommandManager_h -#define YOGClientCommandManager_h - -#include -#include - -class YOGClient; -class YOGClientCommand; - -///This manages client commands, like /block -class YOGClientCommandManager -{ -public: - YOGClientCommandManager(YOGClient* client); - - ///Destroys the administration engine - ~YOGClientCommandManager(); - - ///Interprets whether the given message is a client command, and if so - ///executes it. If it wasn't a command, the string this returns will be - ///empty - std::string executeClientCommand(const std::string& message); - -private: - YOGClient* client; - std::vector commands; -}; - - -#endif diff --git a/src/YOGClientCommands.h b/src/YOGClientCommands.h deleted file mode 100644 index 96194dbfa..000000000 --- a/src/YOGClientCommands.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientCommand_h -#define YOGClientCommand_h - -#include -#include -#include "boost/shared_ptr.hpp" - -class YOGClient; - -///This defines a generic command -class YOGClientCommand -{ -public: - virtual ~YOGClientCommand() {} - - ///Returns this YOGClientCommand help message - virtual std::string getHelpMessage()=0; - - ///Returns the command name for this YOGClientCommand - virtual std::string getCommandName()=0; - - ///Returns true if the given set of tokens match whats required for this YOGClientCommand - virtual bool doesMatch(const std::vector& tokens)=0; - - ///Executes the code for the administrator command, returns the output from the command - virtual std::string execute(YOGClient* client, const std::vector& tokens)=0; -}; - -class YOGClientBlockPlayerCommand : public YOGClientCommand -{ -public: - ///Returns this YOGClientCommand help message - std::string getHelpMessage(); - - ///Returns the command name for this YOGClientCommand - std::string getCommandName(); - - ///Returns true if the given set of tokens match whats required for this YOGClientCommand - bool doesMatch(const std::vector& tokens); - - ///Executes the code for the administrator command - std::string execute(YOGClient* client, const std::vector& tokens); -}; - -#endif diff --git a/src/YOGClientDownloadableMapListener.cpp b/src/YOGClientDownloadableMapListener.cpp deleted file mode 100644 index 56f1a4429..000000000 --- a/src/YOGClientDownloadableMapListener.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#include "YOGClientDownloadableMapListener.h" - - diff --git a/src/YOGClientDownloadableMapListener.h b/src/YOGClientDownloadableMapListener.h deleted file mode 100644 index bd4214efe..000000000 --- a/src/YOGClientDownloadableMapListener.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientDownloadableMapListener_h -#define YOGClientDownloadableMapListener_h - -class YOGClientDownloadableMapListener -{ -public: - virtual ~YOGClientDownloadableMapListener() {} - - virtual void mapListUpdated() = 0; - virtual void mapThumbnailsUpdated() = 0; -}; - - - -#endif diff --git a/src/YOGClientDownloadingMapScreen.h b/src/YOGClientDownloadingMapScreen.h deleted file mode 100644 index d7de28c3e..000000000 --- a/src/YOGClientDownloadingMapScreen.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientDownloadingMapScreen_h -#define YOGClientDownloadingMapScreen_h - - -#include -#include "Glob2Screen.h" -#include "boost/shared_ptr.hpp" -#include "YOGDownloadableMapInfo.h" -#include "YOGClientMapDownloader.h" - -namespace GAGGUI -{ - class Text; - class TextInput; - class TextArea; - class TextButton; - class TabScreen; - class Widget; - class List; - class ProgressBar; -} - -class YOGClient; -class MapPreview; - -using namespace GAGGUI; - -///This screen appears when you are downloading a map -class YOGClientDownloadingMapScreen : public Glob2Screen -{ -public: - - /// Constructor - YOGClientDownloadingMapScreen(boost::shared_ptr client, const YOGDownloadableMapInfo& info); - - ///Responds to widget events - void onAction(Widget *source, Action action, int par1, int par2); - ///Responds to timer events - void onTimer(Uint32 tick); - - enum - { - CANCEL, - CONNECTIONLOST, - FINISHED, - }; -private: - YOGDownloadableMapInfo info; - MapPreview* preview; - boost::shared_ptr client; - //! The textual informations about the selected map - Text *mapName, *mapInfo, *mapSize, *varPrestigeText; - Text *authorName; - ProgressBar* downloadStatus; - YOGClientMapDownloader downloader; -}; - - - - - -#endif diff --git a/src/YOGClientEventListener.cpp b/src/YOGClientEventListener.cpp deleted file mode 100644 index 47f5905ac..000000000 --- a/src/YOGClientEventListener.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGClientEventListener.h" diff --git a/src/YOGClientEventListener.h b/src/YOGClientEventListener.h deleted file mode 100644 index e5ec2d5f0..000000000 --- a/src/YOGClientEventListener.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGClientEventListener_h -#define __YOGClientEventListener_h - -#include "boost/shared_ptr.hpp" - -class YOGClientEvent; - -/// This is a mix-in class. Classes that want to respond to YOG -/// events derive from this class -class YOGClientEventListener -{ -public: - virtual ~YOGClientEventListener() {} - - ///This responds to a YOG event - virtual void handleYOGClientEvent(boost::shared_ptr event) = 0; -}; - - -#endif diff --git a/src/YOGClientGameConnectionDialog.h b/src/YOGClientGameConnectionDialog.h deleted file mode 100644 index 7b349f51f..000000000 --- a/src/YOGClientGameConnectionDialog.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - Copyright (C) 2007-2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientGameConnectionDialog_h -#define YOGClientGameConnectionDialog_h - -#include "GUIBase.h" -#include "MultiplayerGame.h" -#include "boost/shared_ptr.hpp" -#include "MultiplayerGameEvent.h" -#include "MultiplayerGameEventListener.h" - -class Map; -namespace GAGGUI -{ - class Text; - class ProgressBar; -} -namespace GAGCore -{ - class DrawableSurface; -} - -///This dialog shows progress of the fertility computation -class YOGClientGameConnectionDialog:public GAGGUI::OverlayScreen, public MultiplayerGameEventListener -{ -public: - YOGClientGameConnectionDialog(GAGCore::GraphicContext *parentCtx, boost::shared_ptr game); - virtual ~YOGClientGameConnectionDialog(); - virtual void onAction(GAGGUI::Widget *source, GAGGUI::Action action, int par1, int par2); - - ///This screen is modal, this executes it - void execute(); - - ///These are the possible end values - enum EndValue - { - Success, - Failed, - }; -private: - ///This function updates the multiplayer game - void updateGame(); - ///This handles an event from the multiplayer game - void handleMultiplayerGameEvent(boost::shared_ptr event); - - GAGGUI::Text* information; - GAGCore::GraphicContext *parentCtx; - boost::shared_ptr game; -}; - - -#endif diff --git a/src/YOGClientGameListListener.cpp b/src/YOGClientGameListListener.cpp deleted file mode 100644 index 4acfeab79..000000000 --- a/src/YOGClientGameListListener.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGClientGameListListener.h" - diff --git a/src/YOGClientGameListListener.h b/src/YOGClientGameListListener.h deleted file mode 100644 index aa3b2bfbf..000000000 --- a/src/YOGClientGameListListener.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientGameListListener_h -#define YOGClientGameListListener_h - -///This class represents a listener for game list changes -class YOGClientGameListListener -{ -public: - virtual ~YOGClientGameListListener() {} - - ///This is called when the game list is updated - virtual void gameListUpdated() = 0; -}; - -#endif - diff --git a/src/YOGClientMapDownloader.h b/src/YOGClientMapDownloader.h deleted file mode 100644 index 81e9c073b..000000000 --- a/src/YOGClientMapDownloader.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientMapDownloader_h -#define YOGClientMapDownloader_h - -#include "YOGDownloadableMapInfo.h" -#include "boost/shared_ptr.hpp" -#include - -class YOGClient; -class NetMessage; - -///This class manages the downloading of a map from the server -class YOGClientMapDownloader -{ -public: - ///Constructs a map uploader - YOGClientMapDownloader(boost::shared_ptr client); - - ///Removes the map uploader - ~YOGClientMapDownloader(); - - ///Starts downloading the given map - void startDownloading(const YOGDownloadableMapInfo& map); - - ///If this downloader is downloading a map, this will cancel the download - void cancelDownload(); - - ///This recieves a message from the server - void recieveMessage(boost::shared_ptr message); - - ///This updates the downloader - void update(); - - enum DownloadingState - { - Nothing, - DownloadingMap, - Finished, - }; - ///Returns the current downloading state - DownloadingState getDownloadingState(); - - ///Returns the percent downloaded - int getPercentUploaded(); -private: - DownloadingState state; - boost::shared_ptr client; - Uint16 fileID; - std::string mapFile; -}; - -#endif diff --git a/src/YOGClientMapUploadScreen.h b/src/YOGClientMapUploadScreen.h deleted file mode 100644 index dfcec5ad0..000000000 --- a/src/YOGClientMapUploadScreen.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientMapUploadScreen_h -#define YOGClientMapUploadScreen_h - -#include -#include "Glob2Screen.h" -#include "boost/shared_ptr.hpp" -#include "YOGClientMapUploader.h" - -namespace GAGGUI -{ - class Text; - class TextInput; - class TextArea; - class TextButton; - class TabScreen; - class Widget; - class List; - class ProgressBar; -} - -class YOGClient; -class MapPreview; - -using namespace GAGGUI; - -/// A widget that maintains the list of players, and draws an icon based -/// on whether that player is from YOG or from IRC -class YOGClientMapUploadScreen : public Glob2Screen -{ -public: - - /// Constructor - YOGClientMapUploadScreen(boost::shared_ptr client, const std::string mapFile); - - ///Responds to widget events - void onAction(Widget *source, Action action, int par1, int par2); - ///Responds to timer events - void onTimer(Uint32 tick); - - enum - { - CANCEL, - UPLOAD, - UPLOADFAILED, - UPLOADFINISHED, - CONNECTIONLOST, - }; -private: - enum - { - }; - - MapPreview* preview; - boost::shared_ptr client; - YOGClientMapUploader uploader; - Text* uploadStatusText; - ProgressBar* uploadStatus; - //! The textual informations about the selected map - Text *mapInfo, *mapVersion, *mapSize, *mapDate, *varPrestigeText; - TextInput* mapName; - Text *authorNameText; - TextInput* authorName; - std::string mapFile; - bool isUploading; -}; - -#endif diff --git a/src/YOGClientPlayerListListener.cpp b/src/YOGClientPlayerListListener.cpp deleted file mode 100644 index 9e56f52ad..000000000 --- a/src/YOGClientPlayerListListener.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGClientPlayerListListener.h" - - diff --git a/src/YOGClientPlayerListListener.h b/src/YOGClientPlayerListListener.h deleted file mode 100644 index d24487630..000000000 --- a/src/YOGClientPlayerListListener.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientPlayerListListener_h -#define YOGClientPlayerListListener_h - -class YOGClientPlayerListListener -{ -public: - virtual ~YOGClientPlayerListListener() {} - - virtual void playerListUpdated() = 0; -}; - - - -#endif diff --git a/src/YOGClientRatedMapList.h b/src/YOGClientRatedMapList.h deleted file mode 100644 index b68ed5d2e..000000000 --- a/src/YOGClientRatedMapList.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientRatedMapList_h -#define YOGClientRatedMapList_h - -#include -#include - -///This class holds the list of rated maps -class YOGClientRatedMapList -{ -public: - ///Loads the list of rated maps - YOGClientRatedMapList(const std::string& username); - - ///Sets a map that the user has rated by the user - void addRatedMap(const std::string& mapname); - - ///Returns true if the given map has been rated by the user, false otherwise - bool isMapRated(const std::string& mapname); - -private: - ///Saves the list - void save(); - ///Loads the list - void load(); - - std::set maps; - std::string username; -}; - -#endif diff --git a/src/YOGClientRouterAdministrator.h b/src/YOGClientRouterAdministrator.h deleted file mode 100644 index 2fc25c876..000000000 --- a/src/YOGClientRouterAdministrator.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientRouterAdministrator_h -#define YOGClientRouterAdministrator_h - -///This class allows a player to connect to a YOG router and send and recieve -///administrator commands to it. It is meant to be standalone with control of -///program flow -class YOGClientRouterAdministrator -{ -public: - ///Constructs this router admnistrator - YOGClientRouterAdministrator(); - - ///Executes, running the console to output output and recieve commands - int execute(); - -private: -}; - - -#endif diff --git a/src/YOGConsts.cpp b/src/YOGConsts.cpp deleted file mode 100644 index fbb39b622..000000000 --- a/src/YOGConsts.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGConsts.h" - -const Uint16 YOG_SERVER_PORT = 7489; -const Uint16 YOG_SERVER_ROUTER_PORT = 7490; -const Uint16 YOG_ROUTER_PORT = 7491; -const std::string YOG_SERVER_IP = "yog.globulation2.org"; -//const std::string YOG_SERVER_IP = "127.0.0.1"; - -const std::string YOG_SERVER_FOLDER = "beta4/"; diff --git a/src/YOGPlayerPrivateInfo.cpp b/src/YOGPlayerPrivateInfo.cpp deleted file mode 100644 index 6dd7e0020..000000000 --- a/src/YOGPlayerPrivateInfo.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ -#include - -#include "YOGPlayerPrivateInfo.h" -#include "SDL_net.h" -#include "Stream.h" - -YOGPlayerPrivateInfo::YOGPlayerPrivateInfo() -{ - -} - - -void YOGPlayerPrivateInfo::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("YOGPlayerPrivateInfo"); - stream->writeLeaveSection(); -} - - - -void YOGPlayerPrivateInfo::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("YOGPlayerPrivateInfo"); - stream->readLeaveSection(); -} - - - -bool YOGPlayerPrivateInfo::operator==(const YOGPlayerPrivateInfo& rhs) const -{ - //TODO: what's the point of this? -Wall found it - assert(false); -} - - - -bool YOGPlayerPrivateInfo::operator!=(const YOGPlayerPrivateInfo& rhs) const -{ - assert(false); -} - diff --git a/src/YOGPlayerPrivateInfo.h b/src/YOGPlayerPrivateInfo.h deleted file mode 100644 index 5f4d698bf..000000000 --- a/src/YOGPlayerPrivateInfo.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGPlayerPrivateInfo_h -#define YOGPlayerPrivateInfo_h - -#include -#include "SDL_net.h" - -namespace GAGCore -{ - class OutputStream; - class InputStream; -} - -///This class stores information about players that isn't not sent to the client -class YOGPlayerPrivateInfo -{ -public: - ///Constructs a default YOGPlayerPrivateInfo - YOGPlayerPrivateInfo(); - - ///Encodes this YOGPlayerPrivateInfo into a bit stream - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes this YOGPlayerPrivateInfo from a bit stream - void decodeData(GAGCore::InputStream* stream); - - ///Test for equality between two YOGPlayerPrivateInfo - bool operator==(const YOGPlayerPrivateInfo& rhs) const; - bool operator!=(const YOGPlayerPrivateInfo& rhs) const; -private: -}; - -#endif diff --git a/src/YOGRegisterScreen.h b/src/YOGRegisterScreen.h deleted file mode 100644 index 2bb0ad4a6..000000000 --- a/src/YOGRegisterScreen.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGRegisterScreen_h -#define YOGRegisterScreen_h - -#include "Glob2Screen.h" -#include "YOGClientEventListener.h" - - -namespace GAGGUI -{ - class OnOffButton; - class Text; - class TextInput; - class TextArea; - class Animation; -} - -class YOGClient; - -class YOGRegisterScreen : public Glob2Screen, public YOGClientEventListener -{ -public: - ///Construct with the given YOG client. - ///The provided client should not yet be connected to YOG. - YOGRegisterScreen(boost::shared_ptr client); - ///Destroy the screen - ~YOGRegisterScreen(); - enum - { - Cancelled, - Connected, - }; - - -private: - enum - { - CANCEL, - REGISTER, - }; - - void onTimer(Uint32 tick); - void onAction(Widget *source, Action action, int par1, int par2); - - ///Responds to YOG events - void handleYOGClientEvent(boost::shared_ptr event); - - - ///Attempt a registration with the entered information - void attemptRegistration(); - - TextArea *statusText; - TextInput *login, *password, *passwordRepeat; - Animation *animation; - bool wasConnecting; - bool changeTabAgain; - - - boost::shared_ptr client; -}; - -#endif diff --git a/src/YOGServerAdministrator.h b/src/YOGServerAdministrator.h deleted file mode 100644 index 3ad169abb..000000000 --- a/src/YOGServerAdministrator.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGServerAdministrator_h -#define __YOGServerAdministrator_h - -#include -#include "boost/shared_ptr.hpp" -#include - -class YOGServer; -class YOGServerPlayer; -class YOGServerAdministratorCommand; - -///This governs the system of administrative commands to the YOG server -class YOGServerAdministrator -{ -public: - ///Constructs the administration engine - YOGServerAdministrator(YOGServer* server); - - ///Destroys the administration engine - ~YOGServerAdministrator(); - - ///Interprets whether the given message is an administrative command, - ///and if so, executes it. If it was, returns true, otherwise, returns - ///false - bool executeAdministrativeCommand(const std::string& message, boost::shared_ptr player, bool moderator); - - ///This sends a message to the player from the administrator engine - void sendTextMessage(const std::string& message, boost::shared_ptr player); - -private: - - YOGServer* server; - - std::vector commands; -}; - -#endif diff --git a/src/YOGServerAdministratorList.h b/src/YOGServerAdministratorList.h deleted file mode 100644 index e55724353..000000000 --- a/src/YOGServerAdministratorList.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerAdministratorList_h -#define YOGServerAdministratorList_h - -#include -#include - -///This class reads the administrator list -class YOGServerAdministratorList -{ -public: - ///This will read the administrator list - YOGServerAdministratorList(); - - ///Returns true if the given username is an administrator, false otherwise - bool isAdministrator(const std::string& playerName); - - ///Adds the specificed user as an administrator - void addAdministrator(const std::string& playerName); - - ///Removes the specified user from the administrator list - void removeAdministrator(const std::string& playerName); -private: - ///Saves the list of administrators - void save(); - - ///Loads the list of administrators - void load(); - - std::set admins; -}; - - -#endif diff --git a/src/YOGServerChatChannel.cpp b/src/YOGServerChatChannel.cpp deleted file mode 100644 index 5f8cdb0cb..000000000 --- a/src/YOGServerChatChannel.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGServerChatChannel.h" -#include "YOGServerPlayer.h" -#include "YOGMessage.h" -#include "NetMessage.h" - - -YOGServerChatChannel::YOGServerChatChannel(Uint32 channel) -: channel(channel) -{ - -} - - - -void YOGServerChatChannel::addPlayer(boost::shared_ptr player) -{ - players.push_back(player); -} - - - -void YOGServerChatChannel::removePlayer(boost::shared_ptr player) -{ - players.remove(player); -} - - - -void YOGServerChatChannel::routeMessage(boost::shared_ptr message, boost::shared_ptr sender) -{ - boost::shared_ptr netmessage(new NetSendYOGMessage(channel, message)); - for(std::list >::iterator i = players.begin(); i!=players.end(); ++i) - { - if(*i != sender) - (*i)->sendMessage(netmessage); - } -} - - - -size_t YOGServerChatChannel::getNumberOfPlayers() const -{ - return players.size(); -} - diff --git a/src/YOGServerChatChannel.h b/src/YOGServerChatChannel.h deleted file mode 100644 index cc190224f..000000000 --- a/src/YOGServerChatChannel.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#ifndef YOGServerChatChannel_h -#define YOGServerChatChannel_h - -#include -#include "SDL_net.h" -#include - -class YOGMessage; -class YOGServerPlayer; - -///This represents a chat channel server-side -class YOGServerChatChannel -{ -public: - ///Creates a new chat channel - YOGServerChatChannel(Uint32 channel); - - ///Adds a player to this chat channel - void addPlayer(boost::shared_ptr player); - - ///Removes a player from this chat channel - void removePlayer(boost::shared_ptr player); - - ///Routes a YOG message to all players in this channel, except for sender - void routeMessage(boost::shared_ptr message, boost::shared_ptr sender); - - ///Returns the number of players in this chat channel - size_t getNumberOfPlayers() const; -private: - Uint32 channel; - std::list > players; -}; - -#endif diff --git a/src/YOGServerChatChannelManager.cpp b/src/YOGServerChatChannelManager.cpp deleted file mode 100644 index 417bd8068..000000000 --- a/src/YOGServerChatChannelManager.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGServerChatChannelManager.h" -#include "YOGServerChatChannel.h" -#include "YOGConsts.h" -#include "NetMessage.h" - - -YOGServerChatChannelManager::YOGServerChatChannelManager() -{ - currentChannelID = LOBBY_CHAT_CHANNEL+1; - - boost::shared_ptr newChannel(new YOGServerChatChannel(LOBBY_CHAT_CHANNEL)); - channels.insert(std::make_pair(LOBBY_CHAT_CHANNEL, newChannel)); -} - - - -YOGServerChatChannelManager::~YOGServerChatChannelManager() -{ - -} - - - -void YOGServerChatChannelManager::update() -{ - for(std::map >::iterator i = channels.begin(); i!=channels.end();) - { - if(i->first != LOBBY_CHAT_CHANNEL) - { - if(i->second->getNumberOfPlayers() == 0) - { - std::map >::iterator i2 = i; - i++; - channels.erase(i2); - continue; - } - } - ++i; - } -} - - - -Uint32 YOGServerChatChannelManager::createNewChatChannel() -{ - //This finds an unused channel ID - while(channels.find(currentChannelID) != channels.end()) - { - currentChannelID += 1; - } - Uint32 newChannelID = currentChannelID; - currentChannelID += 1; - - //Creates the channel - boost::shared_ptr newChannel(new YOGServerChatChannel(newChannelID)); - channels.insert(std::make_pair(newChannelID, newChannel)); - - return newChannelID; -} - - - -Uint32 YOGServerChatChannelManager::getLobbyChannel() -{ - return LOBBY_CHAT_CHANNEL; -} - - - -boost::shared_ptr YOGServerChatChannelManager::getChannel(Uint32 channel) -{ - return channels[channel]; -} - - - diff --git a/src/YOGServerChatChannelManager.h b/src/YOGServerChatChannelManager.h deleted file mode 100644 index 5cc7b9970..000000000 --- a/src/YOGServerChatChannelManager.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerChatChannelManager_h -#define YOGServerChatChannelManager_h - -#include -#include "SDL_net.h" -#include "boost/shared_ptr.hpp" - -class YOGServerChatChannel; - -///This does serverside management of YOG chat channels -class YOGServerChatChannelManager -{ -public: - ///Creates the YOGServerChatChannelManager - YOGServerChatChannelManager(); - - ///Destroys the YOGServerChatChannelManager - ~YOGServerChatChannelManager(); - - ///This updates the chat channel manager. Removes all chat channels that have no players, except for the lobby - void update(); - - ///Creates a new chat channel, returning its number - Uint32 createNewChatChannel(); - - ///Returns the lobbys channel - Uint32 getLobbyChannel(); - - ///Returns the YOGServerChatChannel for the particular channel - boost::shared_ptr getChannel(Uint32 channel); - -private: - Uint32 currentChannelID; - std::map > channels; -}; - - -#endif diff --git a/src/YOGServerFileDistributationManager.cpp b/src/YOGServerFileDistributationManager.cpp deleted file mode 100644 index 2718b7d70..000000000 --- a/src/YOGServerFileDistributationManager.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGServerFileDistributationManager.h" - - -YOGServerFileDistributationManager::YOGServerFileDistributationManager() -{ - currentID=1; -} - - - -int YOGServerFileDistributationManager::allocateFileDistributor() -{ - int id = chooseTransferID(); - files[id] = boost::shared_ptr(new YOGServerFileDistributor(id)); - return id; -} - - - -void YOGServerFileDistributationManager::update() -{ - for(std::map >::iterator i = files.begin(); i!=files.end(); ++i) - { - if(i->second) - i->second->update(); - } -} - - - -boost::shared_ptr YOGServerFileDistributationManager::getDistributor(Uint16 transferID) -{ - return files[transferID]; -} - - - -void YOGServerFileDistributationManager::removeDistributor(Uint16 transferID) -{ - std::map >::iterator i = files.find(transferID); - if(i != files.end()) - { - files.erase(i); - } -} - - - -Uint16 YOGServerFileDistributationManager::chooseTransferID() -{ - while(files.find(currentID) != files.end()) - { - currentID+=1; - } - return currentID; -} - diff --git a/src/YOGServerFileDistributationManager.h b/src/YOGServerFileDistributationManager.h deleted file mode 100644 index 60031ef4f..000000000 --- a/src/YOGServerFileDistributationManager.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerFileDistributationManager_h -#define YOGServerFileDistributationManager_h - -#include -#include "SDL_net.h" -#include "YOGServerFileDistributor.h" - -///This class manages all file transfers on the server -class YOGServerFileDistributationManager -{ -public: - ///Constructs a distributor - YOGServerFileDistributationManager(); - - ///Allocates a file distributor, returns the transfer ID - int allocateFileDistributor(); - - ///This updates this distributor - void update(); - - ///This returns the file distributor for the given id - boost::shared_ptr getDistributor(Uint16 transferID); - - ///This removes the file distributor - void removeDistributor(Uint16 transferID); -private: - ///Finds an available transfer id - Uint16 chooseTransferID(); - - std::map > files; - Uint16 currentID; -}; - -#endif diff --git a/src/YOGServerGameLog.h b/src/YOGServerGameLog.h deleted file mode 100644 index c2ee5c750..000000000 --- a/src/YOGServerGameLog.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGServerGameLog_h -#define __YOGServerGameLog_h - -#include "YOGGameResults.h" -#include "boost/date_time/posix_time/posix_time.hpp" -#include "SDL_net.h" - -///This class keeps a complete list of games played -class YOGServerGameLog -{ -public: - ///Constructs the game log - YOGServerGameLog(); - - ///Adds a game result to the log - void addGameResults(YOGGameResults results); - - ///Updates this game log, periodically saving and changing the log file - void update(); -private: - ///This saves the game log - void save(); - ///This loads the game log - void load(); - ///This is the current hour - boost::posix_time::ptime hour; - ///This is the list of games from this hour - std::vector games; - ///This is the next time the list will be flushed - boost::posix_time::ptime flushTime; - ///This is set when the list has changed - bool modified; -}; - -#endif diff --git a/src/YOGServerGameRouter.cpp b/src/YOGServerGameRouter.cpp deleted file mode 100644 index a4604e850..000000000 --- a/src/YOGServerGameRouter.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGServerGameRouter.h" -#include "YOGServerRouterPlayer.h" -#include "NetMessage.h" - - -YOGServerGameRouter::YOGServerGameRouter() -{ - -} - - - -void YOGServerGameRouter::addPlayer(boost::shared_ptr player) -{ - players.push_back(player); -} - - - -void YOGServerGameRouter::update() -{ - for(std::vector >::iterator i=players.begin(); i!=players.end();) - { - if(!(*i)->isConnected()) - { - Uint32 n = i - players.begin(); - players.erase(i); - i = players.begin() + n; - } - else - { - ++i; - } - } -} - - - -bool YOGServerGameRouter::isEmpty() -{ - if(players.empty()) - return true; - return false; -} - - - -void YOGServerGameRouter::routeMessage(boost::shared_ptr message, YOGServerRouterPlayer* sender) -{ - for(std::vector >::iterator i=players.begin(); i!=players.end(); ++i) - { - if(i->get() != sender) - { - (*i)->sendNetMessage(message); - } - } -} - diff --git a/src/YOGServerGameRouter.h b/src/YOGServerGameRouter.h deleted file mode 100644 index 1a2561454..000000000 --- a/src/YOGServerGameRouter.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerGameRouter_h -#define YOGServerGameRouter_h - -#include -#include "boost/shared_ptr.hpp" - -class YOGServerRouterPlayer; -class NetMessage; - -///This class acts is the router for games, it routes messages between all connected players -class YOGServerGameRouter -{ -public: - ///Constructs a YOGServerGameRouter - YOGServerGameRouter(); - - ///Adds a player to this router group - void addPlayer(boost::shared_ptr player); - - ///Updates this game - void update(); - - ///Returns true if this game is empty - bool isEmpty(); - - ///Removes a net message to all players - void routeMessage(boost::shared_ptr message, YOGServerRouterPlayer* sender); -private: - std::vector > players; -}; - - -#endif diff --git a/src/YOGServerPlayerScoreCalculator.h b/src/YOGServerPlayerScoreCalculator.h deleted file mode 100644 index b1441d588..000000000 --- a/src/YOGServerPlayerScoreCalculator.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerPlayerScoreCalculator_h -#define YOGServerPlayerScoreCalculator_h - -#include "YOGGameResults.h" -#include "GameHeader.h" - -class YOGServer; - -//This class does the function of calculating and updating player scores -class YOGServerPlayerScoreCalculator -{ -public: - ///Constructs the score calculator - YOGServerPlayerScoreCalculator(YOGServer* server); - - ///Proccesses the result of a single game - void proccessResults(YOGGameResults& results, GameHeader& header); -private: - YOGServer* server; -}; - -#endif diff --git a/src/YOGServerRouterAdministratorCommands.h b/src/YOGServerRouterAdministratorCommands.h deleted file mode 100644 index eb5140506..000000000 --- a/src/YOGServerRouterAdministratorCommands.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerRouterAdministratorCommand_h -#define YOGServerRouterAdministratorCommand_h - -#include -#include -#include "boost/shared_ptr.hpp" - -class YOGServerRouterAdministrator; -class YOGServerRouter; -class YOGServerRouterPlayer; - -///This defines a generic command -class YOGServerRouterAdministratorCommand -{ -public: - virtual ~YOGServerRouterAdministratorCommand() {} - - ///Returns this YOGServerRouterAdministratorCommand help message - virtual std::string getHelpMessage()=0; - - ///Returns the command name for this YOGServerRouterAdministratorCommand - virtual std::string getCommandName()=0; - - ///Returns true if the given set of tokens match whats required for this YOGServerRouterAdministratorCommand - virtual bool doesMatch(const std::vector& tokens)=0; - - ///Executes the code for the administrator command - virtual void execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player)=0; -}; - -///This command hard shuts down the router -class YOGServerRouterAbortCommand : public YOGServerRouterAdministratorCommand -{ -public: - ///Returns this YOGServerRouterAbortCommand help message - std::string getHelpMessage(); - - ///Returns the command name for this YOGServerRouterAbortCommand - std::string getCommandName(); - - ///Returns true if the given set of tokens match whats required for this YOGServerRouterAbortCommand - bool doesMatch(const std::vector& tokens); - - ///Executes the code for the administrator command - void execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player); -}; - - -///This command causes a router to disconnect from the server and turn off once all clients disconnecty -class YOGServerRouterShutdownCommand : public YOGServerRouterAdministratorCommand -{ -public: - ///Returns this YOGServerRouterShutdownCommand help message - std::string getHelpMessage(); - - ///Returns the command name for this YOGServerRouterShutdownCommand - std::string getCommandName(); - - ///Returns true if the given set of tokens match whats required for this YOGServerRouterShutdownCommand - bool doesMatch(const std::vector& tokens); - - ///Executes the code for the administrator command - void execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player); -}; - - -///This command prints a status report of the YOG server -class YOGServerRouterStatusCommand : public YOGServerRouterAdministratorCommand -{ -public: - ///Returns this YOGServerRouterStatusCommand help message - std::string getHelpMessage(); - - ///Returns the command name for this YOGServerRouterStatusCommand - std::string getCommandName(); - - ///Returns true if the given set of tokens match whats required for this YOGServerRouterStatusCommand - bool doesMatch(const std::vector& tokens); - - ///Executes the code for the administrator command - void execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player); -}; - -#endif diff --git a/src/YOGServerRouterManager.cpp b/src/YOGServerRouterManager.cpp deleted file mode 100644 index a3de922bb..000000000 --- a/src/YOGServerRouterManager.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "YOGServerRouterManager.h" -#include "YOGServer.h" -#include "NetConnection.h" -#include "NetMessage.h" - -using boost::static_pointer_cast; - -YOGServerRouterManager::YOGServerRouterManager(YOGServer& server) - : listener(YOG_SERVER_ROUTER_PORT), server(server) -{ - new_connection.reset(new NetConnection); - n=0; -} - - - -void YOGServerRouterManager::addRouter(boost::shared_ptr connection) -{ - shared_ptr info(new NetAcknowledgeRouter); - connection->sendMessage(info); - routers.push_back(connection); -} - - -void YOGServerRouterManager::update() -{ - //First attempt connections with new routers - while(listener.attemptConnection(*new_connection)) - { - addRouter(new_connection); - new_connection.reset(new NetConnection); - } - - //Update all routers - for(std::vector >::iterator i = routers.begin(); i!=routers.end(); ++i) - { - (*i)->update(); - //Parse incoming messages. - shared_ptr message = (*i)->getMessage(); - if(message) - { - Uint8 type = message->getMessageType(); - //This recieves the router information - if(type==MNetRegisterRouter) - { - shared_ptr info = static_pointer_cast(message); - } - } - } - - for(std::vector >::iterator i = routers.begin(); i!=routers.end();) - { - if(!(*i)->isConnected()) - { - Uint32 n = i - routers.begin(); - routers.erase(i); - i = routers.begin() + n; - } - else - { - ++i; - } - } -} - - -boost::shared_ptr YOGServerRouterManager::chooseYOGRouter() -{ - n+=1; - if(n == (int)routers.size()) - n = 0; - return routers[n]; -} - diff --git a/src/YOGServerRouterManager.h b/src/YOGServerRouterManager.h deleted file mode 100644 index 104a3af57..000000000 --- a/src/YOGServerRouterManager.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerRouterManager_h -#define YOGServerRouterManager_h - -#include "boost/shared_ptr.hpp" -#include -#include "NetListener.h" - -class NetConnection; -class YOGServer; - -///This class manages the list of YOGServerRouters -class YOGServerRouterManager -{ -public: - ///Creates a YOGServerRouter - YOGServerRouterManager(YOGServer& server); - - ///Adds a connection to a YOG - void addRouter(boost::shared_ptr connection); - - ///Updates this manager - void update(); - - ///This chooses a new yog router - boost::shared_ptr chooseYOGRouter(); -private: - std::vector > routers; - NetListener listener; - boost::shared_ptr new_connection; - YOGServer& server; - int n; -}; - - -#endif diff --git a/src/YOGServerRouterPlayer.h b/src/YOGServerRouterPlayer.h deleted file mode 100644 index 539b2f211..000000000 --- a/src/YOGServerRouterPlayer.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#ifndef YOGServerRouterPlayer_h -#define YOGServerRouterPlayer_h - -#include "boost/shared_ptr.hpp" -#include "boost/weak_ptr.hpp" -#include "YOGServerRouterAdministrator.h" - -class NetConnection; -class NetMessage; -class YOGServerGameRouter; -class YOGServerRouter; - -///This represents a single connectee to the YOGServerRouterPlayer -class YOGServerRouterPlayer -{ -public: - ///Constructs a YOGServerRouterPlayer to use the given net connection - YOGServerRouterPlayer(boost::shared_ptr connection, YOGServerRouter* router); - - ///Provides a weak pointer to this class - void setPointer(boost::weak_ptr pointer); - - ///Sends a message to the player - void sendNetMessage(boost::shared_ptr message); - - ///Updates this player - void update(); - - ///Returns true if this player is still connected - bool isConnected(); - - ///Returns true if this player is an admin - bool isAdministrator(); - -private: - boost::shared_ptr connection; - boost::shared_ptr game; - YOGServerRouter* router; - boost::weak_ptr pointer; - bool isAdmin; -}; - -#endif diff --git a/src/add_fertility_calculator_thread_message.py b/src/add_fertility_calculator_thread_message.py deleted file mode 100644 index e5304db6a..000000000 --- a/src/add_fertility_calculator_thread_message.py +++ /dev/null @@ -1,103 +0,0 @@ -from add_stuff_base import * - -backup("FertilityCalculatorThreadMessage.h") -backup("FertilityCalculatorThreadMessage.cpp") - -print("Name? ") -name = input() -tname = "FCTM"+name.replace("FCT", "") - -variables = assemble_variables(False, False) -vn = len(variables) - -constructor=assemble_constructor_define(variables) - -declare_functions=assemble_declare_get_functions(variables) -declare_variables=assemble_declare_variables(variables) - - -initialize_variables="" -if vn: - initialize_variables=" : " - initialize_variables+=assemble_initialize_variables(variables) - initialize_variables+="\n" - - -format_variables = assemble_format_variables(variables) -compare_variables = assemble_compare_variables(variables) -get_function_defines = assemble_get_function_definitions(variables) - -hcode = """ -///mname -class mname : public FertilityCalculatorThreadMessage -{ -public: - ///Creates a mname event - """ + constructor + """; - - ///Returns tname - Uint8 getMessageType() const; - - ///Returns a formatted version of the event - std::string format() const; - - ///Compares two FertilityCalculatorThreadMessage - bool operator==(const FertilityCalculatorThreadMessage& rhs) const; -""" -hcode+=declare_functions -hcode+=declare_variables -hcode+="""}; - - - -""" - -scode="" - -scode+="mname::%s\n" % constructor -scode+=initialize_variables -scode+="{\n}\n\n\n\n" -scode+="""Uint8 mname::getMessageType() const -{ - return tname; -} - - - -std::string mname::format() const -{ -""" + format_variables + """ - return s.str(); -} - - - -bool mname::operator==(const FertilityCalculatorThreadMessage& rhs) const -{ - if(typeid(rhs)==typeid(mname)) - { -""" + compare_variables + """ - } - return false; -} - - -""" - -scode += get_function_defines - - -lines = readLines("FertilityCalculatorThreadMessage.h") -i = findMarker(lines,"type_append_marker") -lines.insert(i, " %s,\n" % tname) - - -i = findMarker(lines,"event_append_marker") -lines.insert(i, hcode.replace("mname", name).replace("tname", tname)) -writeLines("FertilityCalculatorThreadMessage.h", lines) - -lines = readLines("FertilityCalculatorThreadMessage.cpp") -i = findMarker(lines, "code_append_marker") -lines.insert(i, scode.replace("mname", name).replace("tname", tname)) -writeLines("FertilityCalculatorThreadMessage.cpp", lines) - diff --git a/src/add_net_message.py b/src/add_net_message.py deleted file mode 100644 index f356529a5..000000000 --- a/src/add_net_message.py +++ /dev/null @@ -1,160 +0,0 @@ -from add_stuff_base import * - -backup("NetMessage.h") -backup("NetMessage.cpp") - -print("Name? ") -name = input() - -variables = assemble_variables(True, True) -vn = len(variables) - - -constructor="" -if vn: - constructor = assemble_constructor_define(variables) - -declare_functions = assemble_declare_get_functions(variables) -declare_variables = assemble_declare_variables(variables) -initialize_variable_defaults = assemble_initialize_variable_defaults(variables) -initialize_variables = assemble_initialize_variables(variables) -format_variables = assemble_format_variables(variables) -compare_variables = assemble_compare_variables(variables) -get_function_defines = assemble_get_function_definitions(variables) - -hcode = """ -///mname -class mname : public NetMessage -{ -public: - ///Creates a mname message - mname(); - -""" - -if vn: - hcode+=" ///Creates a mname message\n" - hcode+=" %s;\n\n" % constructor -hcode+=""" ///Returns Mmname - Uint8 getMessageType() const; - - ///Encodes the data - void encodeData(GAGCore::OutputStream* stream) const; - - ///Decodes the data - void decodeData(GAGCore::InputStream* stream); - - ///Formats the mname message with a small amount - ///of information. - std::string format() const; - - ///Compares with another mname - bool operator==(const NetMessage& rhs) const; -""" -if vn: - hcode+=declare_functions - hcode+="private:\n" - hcode+=declare_variables -hcode+="""}; - - - -""" - -scode=""" -mname::mname() -""" -if vn: - scode+=" :" - scode+=initialize_variable_defaults - scode+="\n" -scode+="""{ - -} - - - -""" - -if vn: - scode+="mname::%s\n" % constructor - scode+=" :" - scode+=initialize_variables - scode+="\n" - scode+="{\n}\n\n\n\n" -scode+="""Uint8 mname::getMessageType() const -{ - return Mmname; -} - - - -void mname::encodeData(GAGCore::OutputStream* stream) const -{ - stream->writeEnterSection("mname"); -""" -if vn: - for v in variables: - scode+=" stream->write%s(%s, \"%s\");\n" % (v[5], v[1], v[1]) -scode+=""" stream->writeLeaveSection(); -} - - - -void mname::decodeData(GAGCore::InputStream* stream) -{ - stream->readEnterSection("mname"); -""" -if vn: - for v in variables: - scode+=" %s = stream->read%s(\"%s\");\n" % (v[1], v[5], v[1]) -scode+=""" stream->readLeaveSection(); -} - - - -std::string mname::format() const -{ -""" + format_variables + """ - return s.str(); -} - - - -bool mname::operator==(const NetMessage& rhs) const -{ - if(typeid(rhs)==typeid(mname)) - { -""" + compare_variables + """ - } - return false; -} - - -""" - -scode += get_function_defines - - -lines = readLines("NetMessage.h") -i = findMarker(lines,"type_append_marker") -lines.insert(i, " M%s,\n" % name) - - -i = findMarker(lines,"message_append_marker") -lines.insert(i, hcode.replace("mname", name)) -writeLines("NetMessage.h", lines) - - -lines = readLines("NetMessage.cpp") -i = findMarker(lines, "append_create_point") -lines.insert(i, """ case Mmname: - message.reset(new mname); - break; -""".replace("mname", name)) - -i = findMarker(lines, "append_code_position") -lines.insert(i, scode.replace("mname", name)) - -writeLines("NetMessage.cpp", lines) - diff --git a/src/AI.cpp b/src/ai/AI.cpp similarity index 78% rename from src/AI.cpp rename to src/ai/AI.cpp index 52042bcd7..e62f4e0f9 100644 --- a/src/AI.cpp +++ b/src/ai/AI.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "AI.h" #include "Player.h" @@ -33,9 +17,9 @@ #include "AIToubib.h" #include "AIWarrush.h" #include "AINicowar.h" -#include "AIEcho.h" +#include "echo/Echo.h" -using boost::shared_ptr; +using std::shared_ptr; /*AI::AI(Player *player) { @@ -96,7 +80,7 @@ AI::~AI() aiImplementation=NULL; } -boost::shared_ptr AI::getOrder(bool paused) +std::shared_ptr AI::getOrder(bool paused) { assert(player); if (paused || !player->team->isAlive) diff --git a/src/AI.h b/src/ai/AI.h similarity index 54% rename from src/AI.h rename to src/ai/AI.h index 5686a8370..6b70e6bb5 100644 --- a/src/AI.h +++ b/src/ai/AI.h @@ -1,28 +1,11 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière -#ifndef __AI_H -#define __AI_H +#pragma once #include -#include +#include namespace GAGCore { class InputStream; @@ -77,9 +60,8 @@ class AI static std::string getAIText(int id); - boost::shared_ptr getOrder(bool paused); + std::shared_ptr getOrder(bool paused); // Uint32 step; }; -#endif diff --git a/src/AICastor.h b/src/ai/AICastor.h similarity index 64% rename from src/AICastor.h rename to src/ai/AICastor.h index bdef43399..2a8d37160 100644 --- a/src/AICastor.h +++ b/src/ai/AICastor.h @@ -1,32 +1,16 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __AI_CASTOR_H -#define __AI_CASTOR_H +#pragma once #include #include #include "IntBuildingType.h" #include "AIImplementation.h" +#include "AICastorTuning.h" -#include +#include struct Case; class Game; @@ -41,6 +25,55 @@ class AICastor : public AIImplementation static const bool verbose = false; public: static const int NB_HARD_BUILDING=8; + + // "Never run yet" sentinel for AICastor's per-map computation timers + // (lastFreeWorkersComputed, lastWheatGrowthMapComputed, lastEnemy*MapComputed, + // and Project::timer). Stored as Uint32, compared as ">timer+N" so the + // all-ones bit pattern always reads as "in the distant future" until first set. + // C++: Lifecycle.cpp:68, 135-139. + static constexpr Uint32 AI_CASTOR_TIMER_NEVER = static_cast(-1); + + // "Unset" sentinel for Project worker counts (mainWorkers, foodWorkers, + // otherWorkers, finalWorkers). A negative value means "this project does + // not override this worker class"; continueProject only writes worker + // counts when the field is >= 0. + // C++: Lifecycle.cpp:58-64. + static constexpr Sint32 AI_CASTOR_WORKERS_UNSET = -1; + + // "No critical project found" sentinel for the lower-is-better project + // priority scan in getOrder(). Set to INT32_MAX so the first real + // project priority always wins the min-search. + // C++: GetOrder.cpp:143. + static constexpr Sint32 AI_CASTOR_PRIORITY_NONE = 0x7FFFFFFF; + + // "No enemy building seen yet" sentinel in controlStrikes() while + // scanning for the highest enemy building level. + // C++: Control.cpp:391. + static constexpr int AI_CASTOR_LEVEL_NONE = -1; + + // "No candidate target team yet" sentinel for the controlStrikes() + // per-team score search. Score is non-negative; -1 forces the first + // real score to win. + // C++: Control.cpp:410. + static constexpr int AI_CASTOR_SCORE_NONE = -1; + + // Project::subPhase state-machine values. The original code uses raw + // ints 0,1,2,3,5,6 — value 4 is intentionally absent (a retired phase + // that was never renumbered). Names mirror the comments at each + // branch in continueProject() (Projects.cpp:258 onward). + // C++: Projects.cpp:258-499. + enum SubPhase : int + { + AI_CASTOR_SUBPHASE_BOOT = 0, // initial / boot + AI_CASTOR_SUBPHASE_FIND_PLACE = 1, // find good building place + AI_CASTOR_SUBPHASE_CHECK_SITES = 2, // do we have enough building sites? + AI_CASTOR_SUBPHASE_BALANCE_MAIN = 3, // balance workers across building/food/other + // value 4 unused — retired phase, intentionally skipped to preserve + // numeric values of the surviving phases. + AI_CASTOR_SUBPHASE_WAIT_FINISHED = 5, // wait for buildings to finish + AI_CASTOR_SUBPHASE_BALANCE_FINAL = 6, // balance final workers + }; + class Project { public: @@ -132,25 +165,25 @@ class AICastor : public AIImplementation bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); void save(GAGCore::OutputStream *stream); - boost::shared_ptrgetOrder(void); + std::shared_ptrgetOrder(void); private: void init(Player *player); void defineStrategy(); - boost::shared_ptrcontrolSwarms(); - boost::shared_ptrexpandFood(); - boost::shared_ptrcontrolFood(); - boost::shared_ptrcontrolUpgrades(); - boost::shared_ptrcontrolStrikes(); -// boost::shared_ptrcontrolBaseDefense(); + std::shared_ptrcontrolSwarms(); + std::shared_ptrexpandFood(); + std::shared_ptrcontrolFood(); + std::shared_ptrcontrolUpgrades(); + std::shared_ptrcontrolStrikes(); +// std::shared_ptrcontrolBaseDefense(); bool addProject(Project *project); void addProjects(); void choosePhase(); - boost::shared_ptrcontinueProject(Project *project); + std::shared_ptrcontinueProject(Project *project); bool enoughFreeWorkers(); void computeCanSwim(); @@ -176,7 +209,7 @@ class AICastor : public AIImplementation void computeEnemyRangeMap(); void computeEnemyWarriorsMap(); - boost::shared_ptrfindGoodBuilding(Sint32 typeNum, bool food, bool defense, bool critical); + std::shared_ptrfindGoodBuilding(Sint32 typeNum, bool food, bool defense, bool critical); void computeRessourcesCluster(); @@ -251,12 +284,8 @@ class AICastor : public AIImplementation Uint8 *enemyWarriorsMap; Uint16 *ressourcesCluster; - -private: - FILE *logFile; }; -#endif diff --git a/src/ai/AICastorTuning.h b/src/ai/AICastorTuning.h new file mode 100644 index 000000000..069079c97 --- /dev/null +++ b/src/ai/AICastorTuning.h @@ -0,0 +1,587 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +// AICastor tuning constants — Phase 3b deferred per-slice rename pass. +// +// Pure rename: every value here was a raw literal in glob2/src/ai/castor/*.cpp. +// Behavior is unchanged (network checksums and replay output match). +// +// Convention: every constant is prefixed `AI_CASTOR_*` and declared at file +// scope as `static constexpr int` (or `unsigned`/`Uint32` where the +// surrounding code uses that wider type). Comments cite the C++ call site +// the value originated from. +// +// Strategy-table values inside `AICastor::defineStrategy` (GetOrder.cpp: +// 257-347 — `strategy.build[X].*`, `strategy.isFreePart`, `warTimeTrigger`, +// `warAmountTrigger`, `warLevelTrigger`, `strikeWarPowerTriggerUp/Down`, +// `strikeTimeTrigger`, `maxAmountGoal`) are intentionally NOT named here; +// that table is the subject of a separate strategy-struct refactor. +// +// `Building::MAX_COUNT` (=1024) and `NB_UNIT_LEVELS` (=4) already exist in +// `building/Building.h` and `unit/UnitConsts.h` and are used directly at +// the call sites instead of being re-declared here. + +// --------------------------------------------------------------------------- +// Tick / time intervals +// +// All AICastor cadences are measured in 40 ms engine ticks +// (GAME_TICKS_PER_SECOND = 25, see EngineTiming.h). +// --------------------------------------------------------------------------- + +// Initial / post-upgrade cooldown (~1.3 s) before controlUpgrades fires +// the next upgrade order. +// C++: Lifecycle.cpp:145, Control.cpp:357. +static constexpr int AI_CASTOR_UPGRADE_DELAY_TICKS = 32; + +// "Each 10 s" cooldown between controlSwarms invocations. +// C++: GetOrder.cpp:151. +static constexpr int AI_CASTOR_CONTROL_SWARMS_INTERVAL = 256; + +// "Each 10 s" cooldown between expandFood invocations. +// C++: GetOrder.cpp:195. +static constexpr int AI_CASTOR_EXPAND_FOOD_INTERVAL = 256; + +// "Each 41 s" refresh cadence for the enemy-range and enemy-warriors maps. +// C++: GetOrder.cpp:201, 205. +static constexpr int AI_CASTOR_ENEMY_RANGE_REFRESH = 1024; +static constexpr int AI_CASTOR_ENEMY_WARRIORS_REFRESH = 1024; + +// Commented-out enemy-power refresh cadences ("each 5 s" under strike, +// "each 2 min 44 s" idle). The branches at GetOrder.cpp:212/217 are +// dormant; constants are defined for completeness so the future re-enable +// matches the documented intent. +// C++: GetOrder.cpp:212, 217. +static constexpr int AI_CASTOR_ENEMY_POWER_STRIKE_REFRESH = 128; +static constexpr int AI_CASTOR_ENEMY_POWER_IDLE_REFRESH = 4096; + +// controlStrikes cadence (~2.6 s). +// C++: Control.cpp:380. +static constexpr int AI_CASTOR_CONTROL_STRIKES_INTERVAL = 64; + +// Ignore food-lock stop-units logic until ~82 s (2048 ticks) of game time. +// C++: Control.cpp:63. +static constexpr int AI_CASTOR_FOODLOCK_GRACE_TICKS = 2048; + +// "Every 41 s" swim recompute cadence. +// C++: Projects.cpp:101. +static constexpr int AI_CASTOR_NEED_SWIM_REFRESH = 1024; + +// Per-project rate limit (~1.3 s) — continueProject early-out gate. +// C++: Projects.cpp:245. +static constexpr int AI_CASTOR_PROJECT_STEP_INTERVAL = 32; + +// Stalled-swarm waits when a SWARM project hits a foodLock and we are +// either starving (long backoff) or just locked (short backoff). +// C++: Projects.cpp:251, 253. +static constexpr int AI_CASTOR_SWARM_STARVE_BACKOFF = 8192; // 5 min 28 s +static constexpr int AI_CASTOR_SWARM_FOODLOCK_BACKOFF = 2048; // 1 min 22 s + +// Project abort backoff after exhausting placement tries. +// C++: Projects.cpp:307. +static constexpr int AI_CASTOR_PROJECT_ABORT_BACKOFF = 8192; // 5 min 27 s + +// Wheat-history rotation mask: every 512 ticks (~20.5 s) the +// oldWheatGradient[] ring rotates and re-snapshots map->ressourcesGradient. +// C++: GetOrder.cpp:105. +static constexpr int AI_CASTOR_WHEAT_HISTORY_INTERVAL_MASK = 511; + +// Verbose-log mask: every 8192 ticks (~5 min 28 s) computeBuildingSum +// dumps the buildingLevels table when verbose logging is on. +// C++: State.cpp:143. +static constexpr int AI_CASTOR_VERBOSE_LOG_INTERVAL_MASK = 8191; + + +// --------------------------------------------------------------------------- +// computeBoot — boot-time deferred map computations +// +// `computeBoot` doubles as both a counter and an offset: ticks 0..31 are +// pure idle, ticks 32..48 select one of 17 boot-time map computations +// via `switch(computeBoot - 32)`. +// --------------------------------------------------------------------------- + +// Initial idle ticks before the boot compute schedule kicks in. +// C++: GetOrder.cpp:30, 35, 37. +static constexpr int AI_CASTOR_BOOT_IDLE_TICKS = 32; + +// Number of one-shot map-compute steps fired during boot. +// C++: GetOrder.cpp:35 (`computeBoot < 17 + 32`), 39-99 (case 0..16). +static constexpr int AI_CASTOR_BOOT_COMPUTE_STEPS = 17; + + +// --------------------------------------------------------------------------- +// controlSwarms — food / explorer / worker thresholds +// --------------------------------------------------------------------------- + +// Food-warning slack offsets (units): foodWarning trips when we have only +// ~half the food production needed; foodLock trips earlier still. +// C++: Control.cpp:47, 48. +static constexpr int AI_CASTOR_FOODWARN_OFFSET = 11; +static constexpr int AI_CASTOR_FOODLOCK_OFFSET = 3; + +// foodSurplus margin: too many food buildings if we have this many fewer +// units than food-production capacity. +// C++: Control.cpp:51. +static constexpr int AI_CASTOR_FOODSURPLUS_OFFSET = 4; + +// Starving-warning predicate: trip if `(unitSumAll >> 5) + 3` is below the +// number of starving units (i.e. >1/32 of the population is starving plus +// a +3 bias for very small populations). +// C++: Control.cpp:53. +static constexpr int AI_CASTOR_STARVING_RATIO_SHIFT = 5; +static constexpr int AI_CASTOR_STARVING_OFFSET = 3; + +// Real-foodLock multipliers: at warriorGoal>1 we tolerate 3x population +// over food, otherwise only 2x, before switching off swarm production. +// C++: Control.cpp:59, 61. +static constexpr int AI_CASTOR_REAL_FOODLOCK_MULT_WAR = 3; +static constexpr int AI_CASTOR_REAL_FOODLOCK_MULT_PEACE = 2; + +// Explorer goal thresholds. +// C++: Control.cpp:101-108. +static constexpr int AI_CASTOR_EXPLORER_MIN_WORKERS = 4; // need 4+ workers before any explorer +static constexpr int AI_CASTOR_EXPLORER_GOAL_HIGH = 2; // spawn-explorers ratio +static constexpr int AI_CASTOR_EXPLORER_COUNT_TARGET = 3; // desired-count cap (early) +static constexpr int AI_CASTOR_EXPLORER_RATIO_SHIFT_EARLY = 2; // 1:4 ratio (shift by 2) +static constexpr int AI_CASTOR_DISCOVERY_RATIO_SHIFT = 2; // <25% map discovered (size << 2) +static constexpr int AI_CASTOR_EXPLORER_RATIO_SHIFT_LATE = 4; // 1:16 ratio (shift by 4) +static constexpr int AI_CASTOR_EXPLORER_GOAL_LOW = 1; +static constexpr int AI_CASTOR_WORKER_GOAL_LOW = 1; +static constexpr int AI_CASTOR_WORKER_GOAL_HIGH = 4; + + +// --------------------------------------------------------------------------- +// controlFood — wheat-care thresholds and INN worker assignments +// --------------------------------------------------------------------------- + +// Up to 8 retries to find a non-NULL building when the rotation slot lands +// on an empty Building::myBuildings cell. +// C++: Control.cpp:181. +static constexpr int AI_CASTOR_CONTROL_FOOD_RETRIES = 8; + +// "Stop workers" wheat-care threshold: care above this means the field is +// being neglected, drop maxUnitWorking to zero. +// C++: Control.cpp:219. +static constexpr int AI_CASTOR_WHEATCARE_STOP_THRESHOLD = 4; + +// "Reduce to 1 worker" wheat-care threshold (used between STOP and OK). +// C++: Control.cpp:231. +static constexpr int AI_CASTOR_WHEATCARE_LIMIT_THRESHOLD = 2; + +// FOOD_BUILDING worker base counts. +// Note: Control.cpp:249 carries a `//TODO: random 2 or 3` comment — the +// behavior is deterministic at 3 and is preserved verbatim. +// C++: Control.cpp:249, 251. +static constexpr int AI_CASTOR_FOODWARN_INN_SITE_WORKERS = 3; // foodWarning + isBuildingSite +static constexpr int AI_CASTOR_INN_WORKERS_BASE = 1; // peace-time + +// SWARM_BUILDING worker counts under foodWarning vs. normal. +// C++: Control.cpp:261, 263. +static constexpr int AI_CASTOR_SWARM_WORKERS_FOODWARN = 1; +static constexpr int AI_CASTOR_SWARM_WORKERS_NORMAL = 2; + + +// --------------------------------------------------------------------------- +// controlUpgrades — gating, repair HP ratios, science rules +// --------------------------------------------------------------------------- + +// Gates that must all pass before any upgrade fires. +// C++: Control.cpp:297. +static constexpr int AI_CASTOR_UPGRADE_MIN_ABLE_WORKERS = 2; // numberOfAbleWorkers > 2 +static constexpr int AI_CASTOR_UPGRADE_MIN_FREE_WORKERS = 4; // numberOfFreeWorkers > 4 +static constexpr int AI_CASTOR_UPGRADE_ABLE_FREE_RATIO_DIV = 8; // able > free / 8 + +// Per-class repair-trigger HP ratios: trigger repair when +// `b->hp * AI_CASTOR_REPAIR_HP_RATIO_DIV < b->type->hpMax * `. +// (DIV is implicit at 4 — i.e., the comparisons read "less than 25/75/50%".) +// C++: Control.cpp:304, 309, 314. +static constexpr int AI_CASTOR_REPAIR_HP_RATIO_DIV = 4; +static constexpr int AI_CASTOR_REPAIR_HP_RATIO_DEFENCE_NUM = 1; // defencetower: <25% +static constexpr int AI_CASTOR_REPAIR_HP_RATIO_INSIDE_NUM = 3; // has maxUnitInside: <75% +static constexpr int AI_CASTOR_REPAIR_HP_RATIO_OTHER_NUM = 2; // others: <50% + +// Standard `(unitsWorking, unitsWorkingFinal)` pair sent in +// `OrderConstruction(b->gid, 1, 1)` — used both for repair triggers and +// for the upgrade trigger at the end of controlUpgrades(). +// C++: Control.cpp:305, 310, 315, 360. +static constexpr int AI_CASTOR_CONSTRUCTION_ORDER_UNITS = 1; + +// Upgrade level goal: ceil(buildsAmount / 2), capped at 3. +// C++: Control.cpp:324, 325, 326. +static constexpr int AI_CASTOR_UPGRADE_LEVEL_FORMULA_BIAS = 1; +static constexpr int AI_CASTOR_UPGRADE_LEVEL_FORMULA_SHIFT = 1; +static constexpr int AI_CASTOR_UPGRADE_LEVEL_MAX = 3; + +// SCIENCE_BUILDING: require >=2 same-level science buildings before +// triggering the upgrade. +// C++: Control.cpp:352. +static constexpr int AI_CASTOR_SCIENCE_UPGRADE_MIN_COUNT = 2; + + +// --------------------------------------------------------------------------- +// controlStrikes — warflag formula, scoring, flag-move thresholds +// --------------------------------------------------------------------------- + +// War-flag count formula: `(warriors + 16) / 32` — so we want roughly one +// warflag per 32 warriors, with a +16 bias (round-half). +// C++: Control.cpp:386. +static constexpr int AI_CASTOR_WARFLAG_FORMULA_BIAS = 16; +static constexpr int AI_CASTOR_WARRIORS_PER_WARFLAG = 32; + +// Enemy-team scoring: ATTACK and SCIENCE buildings count as 2, others as 1. +// C++: Control.cpp:427, 429. +static constexpr int AI_CASTOR_STRIKE_TEAM_SCORE_HIGH = 2; +static constexpr int AI_CASTOR_STRIKE_TEAM_SCORE_LOW = 1; + +// Per-building strike-target score formula: +// score = (1 + workRange) * (1 + level) +// if isBuildingSite: score >>= 2 (quarter for unfinished sites) +// if ATTACK / SCIENCE: score <<= 1 (double for high-value targets) +// C++: Control.cpp:465, 467, 471. +static constexpr int AI_CASTOR_STRIKE_BUILDING_SCORE_BIAS = 1; +static constexpr int AI_CASTOR_STRIKE_BUILDING_SITE_SHIFT = 2; +static constexpr int AI_CASTOR_STRIKE_HIGH_VALUE_SHIFT = 1; + +// Min squared distance between an existing warflag and the new target +// before we issue a "move flag" order. +// C++: Control.cpp:506. +static constexpr int AI_CASTOR_FLAG_MOVE_SQ_DIST = 2; + +// Desired warriors-on-warflag count. +// C++: Control.cpp:512, 514. +static constexpr int AI_CASTOR_WARFLAG_WORKER_GOAL = 20; + + +// --------------------------------------------------------------------------- +// enoughFreeWorkers — buildsAmount tier balance +// --------------------------------------------------------------------------- + +// Early- and mid-game balance thresholds against `buildsAmount`. +// C++: State.cpp:30, 32. +static constexpr int AI_CASTOR_BUILDS_LOW = 2; +static constexpr int AI_CASTOR_BUILDS_MID = 4; + +// Late-game multiplier on excess workers (`partFree << 1`). +// C++: State.cpp:35. +static constexpr int AI_CASTOR_BALANCE_LATE_SHIFT = 1; + +// foodLock balance bias: require an extra 3 free workers when we are food-locked. +// C++: State.cpp:37. +static constexpr int AI_CASTOR_FOODLOCK_BALANCE_BIAS = 3; + +// Initial value memset into the static `oldEnough[]` cache: a tri-state +// "unknown" marker (vs the boolean 0/1 the cache later holds). +// C++: State.cpp:48. +static constexpr int AI_CASTOR_TRISTATE_UNKNOWN = 2; + +// "Uninitialised" sentinels for the per-step verbose log statics. -1 is +// never a real warLevel / warPowerSum so the first comparison always +// triggers a log update. +// C++: State.cpp:178, 195. +static constexpr int AI_CASTOR_WAR_LEVEL_UNSET = -1; +static constexpr int AI_CASTOR_WAR_POWER_UNSET = -1; + + +// --------------------------------------------------------------------------- +// computeNeedSwim — "swim helps" predicate +// +// `(baseCount << 4) > 7 * extendedCount` i.e. swimming-extended reach +// must improve coverage by more than 16/7 (≈ 43%). +// C++: State.cpp:106. +// --------------------------------------------------------------------------- +static constexpr int AI_CASTOR_SWIM_GAIN_NUMER = 16; // (1 << 4) +static constexpr int AI_CASTOR_SWIM_GAIN_NUMER_SHIFT = 4; +static constexpr int AI_CASTOR_SWIM_GAIN_DENOM = 7; + + +// --------------------------------------------------------------------------- +// computeWarLevel — trigger-level promotion / cap +// --------------------------------------------------------------------------- + +// Grow `warTimeTrigger` by ~1.5x once the previous threshold elapses. +// (`warTimeTrigger += (1 + warTimeTrigger) >> 1`.) +// C++: State.cpp:153. +static constexpr int AI_CASTOR_WARTIME_TRIGGER_GROWTH_BIAS = 1; +static constexpr int AI_CASTOR_WARTIME_TRIGGER_GROWTH_SHIFT = 1; + +// Cap on the warTime trigger level (held to <=2). +// C++: State.cpp:156-157. +static constexpr int AI_CASTOR_WARTIME_LEVEL_CAP = 2; + +// War-level / war-amount trigger level values. +// C++: State.cpp:163, 164, 166, 171, 173. +static constexpr int AI_CASTOR_WARLEVEL_BUILDINGS_HIGH = 1; // sum > 1 -> level 2 +static constexpr int AI_CASTOR_WAR_LEVEL_HIGH = 2; +static constexpr int AI_CASTOR_WAR_LEVEL_MID = 1; + +// `strikeWarPowerTriggerUp` growth divisor when we abort a strike. +// C++: State.cpp:209 (`+= strikeWarPowerTriggerUp / 2`). +static constexpr int AI_CASTOR_STRIKE_TRIGGER_GROWTH_DIV = 2; + + +// --------------------------------------------------------------------------- +// Project boot defaults — Lifecycle.cpp + Projects.cpp boot tiers +// --------------------------------------------------------------------------- + +// Project::init defaults. +// C++: Lifecycle.cpp:38, 55, 56. +static constexpr int AI_CASTOR_PROJECT_DEFAULT_AMOUNT = 1; +static constexpr int AI_CASTOR_PROJECT_DEFAULT_PRIORITY = 1; +static constexpr int AI_CASTOR_PROJECT_TRIES_LEFT = 64; + +// "Highest priority" critical-project bucket. +// C++: Projects.cpp:63, 83, 109. +static constexpr int AI_CASTOR_PROJECT_PRIORITY_CRITICAL = 0; + +// FOOD boot project worker counts. +// C++: Projects.cpp:66, 67, 68, 72. +static constexpr int AI_CASTOR_BOOT_FOOD_MAIN_WORKERS = 3; +static constexpr int AI_CASTOR_BOOT_FOOD_FOOD_WORKERS = 2; +static constexpr int AI_CASTOR_BOOT_OTHER_WORKERS_OFF = 0; +static constexpr int AI_CASTOR_BOOT_FOOD_FINAL_WORKERS = 1; + +// SWARM boot project worker counts. +// C++: Projects.cpp:86, 87, 92. +static constexpr int AI_CASTOR_BOOT_SWARM_MAIN_WORKERS = 10; +static constexpr int AI_CASTOR_BOOT_SWARM_FOOD_WORKERS = 1; +static constexpr int AI_CASTOR_BOOT_SWARM_FINAL_WORKERS = 2; + +// SWIM and ATTACK boot project amount / mainWorkers. +// C++: Projects.cpp:106, 116. +static constexpr int AI_CASTOR_BOOT_SWIM_AMOUNT = 1; +static constexpr int AI_CASTOR_BOOT_SWIM_MAIN_WORKERS = 2; +static constexpr int AI_CASTOR_BOOT_ATTACK_AMOUNT = 1; +static constexpr int AI_CASTOR_BOOT_ATTACK_MAIN_WORKERS = 2; + + +// --------------------------------------------------------------------------- +// addProjects — expansion-tier loop +// --------------------------------------------------------------------------- + +// `for (int li = 1; li < NB_UNIT_LEVELS; li++)` — upgrade levels start at 1 +// (level 0 is the base building, not an upgrade). +// C++: Projects.cpp:178. +static constexpr int AI_CASTOR_FIRST_UPGRADE_LEVEL = 1; + +// Encode tier into `buildsAmount` at three sub-phases per outer iteration: +// buildsAmount = TIER_BASE_PRE + (agi << SHIFT) -> 2,4,6 +// buildsAmount = TIER_BASE_MID + (agi << SHIFT) -> 3,5,7 +// buildsAmount = TIER_BASE_POST + (agi << SHIFT) -> 4,6,8 +// Workers are scaled by `(agi - 1)` on top of strategy.build[bi].newWorkers. +// C++: Projects.cpp:195, 213, 219, 232. +static constexpr int AI_CASTOR_BUILDS_TIER_BASE_PRE = 0; +static constexpr int AI_CASTOR_BUILDS_TIER_BASE_MID = 1; +static constexpr int AI_CASTOR_BUILDS_TIER_BASE_POST = 2; +static constexpr int AI_CASTOR_BUILDS_TIER_SHIFT = 1; +static constexpr int AI_CASTOR_TIER_WORKERS_SCALE_BIAS = 1; // workers + (agi - 1) + + +// --------------------------------------------------------------------------- +// continueProject — sub-phase logic thresholds +// --------------------------------------------------------------------------- + +// Low-free-workers threshold: BALANCE_MAIN clamps `mainWorkers` toward 3 +// when isFree is at or below this. +// C++: Projects.cpp:350, 352. +static constexpr int AI_CASTOR_FREE_WORKERS_LOW = 3; + +// "Have a spare worker" gate that re-enters FIND_PLACE during a +// multipleStart project's BALANCE_MAIN phase. +// C++: Projects.cpp:442. +static constexpr int AI_CASTOR_FREE_WORKERS_SPARE = 1; + + +// --------------------------------------------------------------------------- +// findGoodBuilding — placement scoring (Placement.cpp:22-180) +// --------------------------------------------------------------------------- + +// Initial floor for the "best work score" scan: any cell with workAbility +// above 2 wins this seed. +// C++: Placement.cpp:38. +static constexpr int AI_CASTOR_BEST_WORK_SCORE_FLOOR = 2; + +// `minWork = bestWorkScore * 2`, then clamped to either 15*4 (critical) or +// 30*4 (normal). The trailing `* 4` is "4 corners per building footprint". +// C++: Placement.cpp:47, 50, 51, 55. +static constexpr int AI_CASTOR_MINWORK_MULT = 2; +static constexpr int AI_CASTOR_MINWORK_CRITICAL_CAP_PER_CORNER = 15; +static constexpr int AI_CASTOR_MINWORK_NORMAL_CAP_PER_CORNER = 30; +static constexpr int AI_CASTOR_CORNERS = 4; + +// Wheat-gradient comparisons sample the four corners of the candidate +// footprint, so the limit is `(255 - ) * AI_CASTOR_CORNERS`. Larger +// offsets are more permissive (lower limit means more cells qualify). +// FOOD buildings want HIGH wheat (wheatGradient must be >= limit) so a +// SMALL offset is the strict variant ("critical" path picks 16, normal 4). +// NON-FOOD buildings want LOW wheat (wheatGradient must be < limit) so a +// SMALL offset is the strict variant ("critical" picks 5, normal 7). +// C++: Placement.cpp:64, 66, 71, 73. +static constexpr int AI_CASTOR_WHEAT_GRADIENT_PEAK = 255; +static constexpr int AI_CASTOR_WHEAT_GRADIENT_CRITICAL_FOOD_OFFSET = 16; +static constexpr int AI_CASTOR_WHEAT_GRADIENT_NORMAL_FOOD_OFFSET = 4; +static constexpr int AI_CASTOR_WHEAT_GRADIENT_CRITICAL_OTHER_OFFSET = 5; +static constexpr int AI_CASTOR_WHEAT_GRADIENT_NORMAL_OTHER_OFFSET = 7; + +// "Too close to enemy" reject threshold: +// enemyRange (sum over 4 corners) > AI_CASTOR_CORNERS * (255 - ). +// C++: Placement.cpp:133. +static constexpr int AI_CASTOR_ENEMY_RANGE_REJECT_OFFSET = 8; + +// Bit packing used by `buildingNeighbourMap`: +// bit 0 : "dirty" flag (this cell is too close to a neighbour) +// bits [1..3] : direct-neighbours count (mask 7, shift 1) +// bit 4 : zero / centre-flag bit (cleared via `& ~16`) +// bits [5..7] : far-neighbours count (mask 7, shift 5) +// C++: Placement.cpp:140-142, Maps.cpp:172, 198, 202, 221. +static constexpr int AI_CASTOR_NEIGHBOUR_DIRECT_SHIFT = 1; +static constexpr int AI_CASTOR_NEIGHBOUR_FAR_SHIFT = 5; +static constexpr int AI_CASTOR_NEIGHBOUR_MASK = 7; +static constexpr int AI_CASTOR_NEIGHBOUR_DIRTY_BIT = 1; +static constexpr int AI_CASTOR_NEIGHBOUR_MAX_DIRECT = 1; +static constexpr int AI_CASTOR_NEIGHBOUR_DIRECT_INCR = 2; +static constexpr int AI_CASTOR_NEIGHBOUR_CENTRE_BIT = 16; +static constexpr int AI_CASTOR_NEIGHBOUR_FAR_INCR = 32; +static constexpr int AI_CASTOR_NEIGHBOUR_OUT_OF_VISION = 127; + +// Score formula coefficients (Placement.cpp:149/151/153). Each formula is: +// defense: ((work<<1) + wheatGradient + (enemyRange<<4)) * (16 + (direct<<2) + far) +// food : ((wheatGrowth<<8) + work + (wheatGradient>>1) - enemyRange) +// * (8 + (direct<<2) + far) +// normal : (4096 + work - (wheatGrowth<<8) - enemyRange) +// * (8 + (direct<<2) + far) +// C++: Placement.cpp:149, 151, 153. +static constexpr int AI_CASTOR_SCORE_DEFENSE_WORK_SHIFT = 1; +static constexpr int AI_CASTOR_SCORE_DEFENSE_ENEMY_SHIFT = 4; +static constexpr int AI_CASTOR_SCORE_DEFENSE_NEIGHBOUR_BIAS = 16; +static constexpr int AI_CASTOR_SCORE_FOOD_GROWTH_SHIFT = 8; +static constexpr int AI_CASTOR_SCORE_FOOD_GRADIENT_SHIFT = 1; +static constexpr int AI_CASTOR_SCORE_FOOD_NEIGHBOUR_BIAS = 8; +static constexpr int AI_CASTOR_SCORE_NORMAL_BIAS = 4096; +static constexpr int AI_CASTOR_SCORE_NORMAL_GROWTH_SHIFT = 8; +static constexpr int AI_CASTOR_SCORE_NORMAL_NEIGHBOUR_BIAS = 8; +static constexpr int AI_CASTOR_SCORE_NEIGHBOUR_DIRECT_SHIFT = 2; // (direct << 2) + +// Defense-score normalisation: clamp `score >> 12` into [0, 255]. +// C++: Placement.cpp:159-162. +static constexpr int AI_CASTOR_DEFENSE_SCORE_NORMALISE_SHIFT = 12; +static constexpr int AI_CASTOR_DEFENSE_SCORE_CAP = 255; + + +// --------------------------------------------------------------------------- +// computeRessourcesCluster +// --------------------------------------------------------------------------- + +// Cluster id space = Uint16::MAX + 1. +// C++: Placement.cpp:195, 196. +static constexpr int AI_CASTOR_CLUSTER_ID_SPACE = 65536; + +// Starting cluster id (id 0 means "unset"). +// C++: Placement.cpp:218. +static constexpr int AI_CASTOR_CLUSTER_FIRST_ID = 1; + + +// --------------------------------------------------------------------------- +// updateGlobalGradient(NoObstacle) — propagation sentinels +// --------------------------------------------------------------------------- + +// Sentinel value an obstacle cell holds in the no-obstacle gradient; the +// propagation loops skip cells equal to this. Also the "not grass" marker +// stamped into notGrassMap by computeNotGrassMap (Maps.cpp:519, 520). +// C++: Placement.cpp:271, 302, 334, 363; Maps.cpp:520. +static constexpr int AI_CASTOR_GRADIENT_OBSTACLE_NO_OBSTACLE = 16; + +// "Wall" sentinel for the standard updateGlobalGradient propagation: a +// cell already at this value is a fixed source, propagation stops. +// Also seeded into enemyRangeMap by computeEnemyRangeMap (Maps.cpp:711) +// as the "enemy here" gradient source — `GRADIENT_AT_GOAL` (defined in +// MapInternal.h) is the canonical name and is used at the call site. +// C++: Placement.cpp:404, 435, 467, 496. +static constexpr int AI_CASTOR_GRADIENT_WALL = 255; + + +// --------------------------------------------------------------------------- +// Maps.cpp — terrain ranges, gradient stamps, hydration / wheat +// --------------------------------------------------------------------------- + +// Terrain ID layout (matches the engine's tile-set encoding): +// [0, 16) : grass (16 tiles) +// [256, 272) : water (16 tiles) +// [256, 272) : sand (16 tiles, same range as water in this code) +// `Maps.cpp:42` uses `>=256 && <256+16` for "is water"; `Maps.cpp:469` uses +// the identical range for "is sand"; `Maps.cpp:67` uses `>=16` and +// `Maps.cpp:519` uses `>16` for "is not grass". See bug M6 — the >=16 +// vs >16 asymmetry in `notGrassMap` vs `obstacleBuildingMap` is preserved +// verbatim by this rename pass (do NOT change the operator). +// C++: Maps.cpp:42, 67, 469, 519. +static constexpr int AI_CASTOR_TERRAIN_GRASS_COUNT = 16; +static constexpr int AI_CASTOR_TERRAIN_WATER_FIRST = 256; +static constexpr int AI_CASTOR_TERRAIN_WATER_COUNT = 16; +static constexpr int AI_CASTOR_TERRAIN_SAND_FIRST = 256; +static constexpr int AI_CASTOR_TERRAIN_SAND_COUNT = 16; + +// computeWorkPowerMap: max gradient radius and the "/2" half-map cap. +// C++: Maps.cpp:324, 325, 326. +static constexpr int AI_CASTOR_WORK_POWER_MAX_RANGE = 64; +static constexpr int AI_CASTOR_HALFMAP_DIV = 2; + +// `(u->hungry - u->trigHungry) >> 1` divides remaining hunger by 2 when +// computing per-worker gradient range. +// C++: Maps.cpp:338, 413. +static constexpr int AI_CASTOR_HUNGER_RANGE_SHIFT = 1; + +// Power-stamping reducer (>>3, i.e. /8) applied to `range` before adding +// to the per-cell gradient — also called `reducer` in the source. +// C++: Maps.cpp:346, 627. +static constexpr int AI_CASTOR_POWER_STAMP_REDUCER = 3; + +// computeWorkAbilityMap: normalise `(workPower * workRange)` by 32. +// C++: Maps.cpp:443. +static constexpr int AI_CASTOR_WORK_ABILITY_NORM_SHIFT = 5; + +// Uint8 clamp value, used at half a dozen sites that clamp Uint16 sums or +// `range` counters into the [0, 255] Uint8 range. +// C++: Maps.cpp:107, 351, 362, 373, 385, 416, 443, 496, etc. +static constexpr int AI_CASTOR_UINT8_MAX_VALUE = 255; + +// computeHydratationMap: stamp radius and the >>4 divide on the final +// per-cell accumulator. +// C++: Maps.cpp:464, 496. +static constexpr int AI_CASTOR_HYDRATATION_RANGE = 16; +static constexpr int AI_CASTOR_HYDRATATION_NORM_SHIFT = 4; + +// computeWheatCareMap: per-cell predicates (notGrass exact-15 sentinel, +// previous-care threshold, and the two "high"/"low" care values written +// into wheatCareMap[0]). +// C++: Maps.cpp:546, 547, 550, 552. +static constexpr int AI_CASTOR_NOTGRASS_NEIGHBOUR_VAL = 15; +static constexpr int AI_CASTOR_WHEATCARE_PREV_HIGH_THRESHOLD = 7; +static constexpr int AI_CASTOR_WHEATCARE_HIGH = 10; +static constexpr int AI_CASTOR_WHEATCARE_LOW = 8; +// Wheat-gradient sentinel comparisons in the same predicate +// (`==255`, `<255`, `<254`). +// C++: Maps.cpp:548, 550. +static constexpr int AI_CASTOR_WHEAT_GRADIENT_NEAR_PEAK = 254; + +// computeWheatGrowthMap: base growth offset + hydratation divisor; +// minimum-growth floor. +// C++: Maps.cpp:576, 583, 589. +static constexpr int AI_CASTOR_WHEAT_GROWTH_BASE = 1; +static constexpr int AI_CASTOR_WHEAT_GROWTH_HYDRATATION_SHIFT = 3; +static constexpr int AI_CASTOR_WHEAT_CARE_SUBTRACT_THRESHOLD = 1; +static constexpr int AI_CASTOR_WHEAT_GROWTH_MIN = 1; + +// computeEnemyPowerMap stamp radius (max 32 per the inline comment). +// C++: Maps.cpp:628. +static constexpr int AI_CASTOR_ENEMY_POWER_RANGE = 32; + +// computeEnemyWarriorsMap: `gradient[i] = 32` seeds the warrior gradient +// so it propagates only ~32 cells (vs. enemyRangeMap which uses +// `GRADIENT_AT_GOAL` = 255 to propagate the whole map). +// C++: Maps.cpp:746. +static constexpr int AI_CASTOR_ENEMY_WARRIOR_GRADIENT_SEED = 32; + +// `guid >> 10` extracts the team number from a ground-unit gid +// (gid = team * Unit::MAX_COUNT + index, with Unit::MAX_COUNT = 1024). +// C++: Maps.cpp:743. +static constexpr int AI_CASTOR_GUID_TEAM_SHIFT = 10; diff --git a/src/AIDescriptionScreen.cpp b/src/ai/AIDescriptionScreen.cpp similarity index 57% rename from src/AIDescriptionScreen.cpp rename to src/ai/AIDescriptionScreen.cpp index c2f3b3db4..bb59ccc53 100644 --- a/src/AIDescriptionScreen.cpp +++ b/src/ai/AIDescriptionScreen.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "AIDescriptionScreen.h" #include "GUIButton.h" @@ -55,9 +40,9 @@ void AIDescriptionScreen::onAction(Widget *source, Action action, int par1, int } if (action == LIST_ELEMENT_SELECTED) { - if(ailist->getSelectionIndex() != -1) + if (auto sel = ailist->selection()) { - description->setText(AINames::getAIDescription(ailist->getSelectionIndex()).c_str()); + description->setText(AINames::getAIDescription(*sel).c_str()); } } } diff --git a/src/ai/AIDescriptionScreen.h b/src/ai/AIDescriptionScreen.h new file mode 100644 index 000000000..42459c237 --- /dev/null +++ b/src/ai/AIDescriptionScreen.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include "Glob2Screen.h" + +namespace GAGGUI +{ + class TextButton; + class TextArea; + class List; + class Text; +}; + +///This screen shows descriptions for the various types of AI +class AIDescriptionScreen : public Glob2Screen +{ +public: + ///This shows descriptions for the various types of AI + AIDescriptionScreen(); + + virtual void onAction(Widget *source, Action action, int par1, int par2); + + enum + { + OK, + }; + +private: + TextButton* ok; + TextArea *description; + List *ailist; + Text *title; +}; + diff --git a/src/ai/AIEchoTuning.h b/src/ai/AIEchoTuning.h new file mode 100644 index 000000000..d35d31b16 --- /dev/null +++ b/src/ai/AIEchoTuning.h @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#pragma once + +// AI Echo per-slice tuning constants — Phase 3b (RTI scheduler / tuning). +// +// These name the magic numbers used by AIEcho::ReachToInfinity (the simple +// economic test AI in glob2/src/ai/echo/ReachToInfinity.cpp) and a handful +// of supporting classes (Gradient, Management, Construction, Conditions, +// Echo). The Phase 3b high-value pass already named the *sentinel* values +// in echo/Echo.h — this header covers the remaining *tuning* values. +// +// Pure rename pass: every literal value is preserved byte-for-byte. + +namespace AIEcho +{ + // ---- RTI master scheduler ------------------------------------------------ + // ReachToInfinity::tick rotates through five "primary builders" on a + // 2000-tick master cycle. The offsets are coupled — change BIG_CYCLE_TICKS + // and you must shift the offsets too, or staggering breaks. + + /// Master cycle period for the RTI scheduler (~80s at 25 ticks/s). + /// (ReachToInfinity.cpp: 375, 445, 500, 543, 591.) + static constexpr int AI_ECHO_RTI_BIG_CYCLE_TICKS = 2000; + + /// Phase offsets within the master cycle. Swarm fires at offset 0, racetrack + /// at 500, swimming pool at 1000, school at 1500. + static constexpr int AI_ECHO_RTI_SWARM_OFFSET_TICKS = 0; + static constexpr int AI_ECHO_RTI_RACETRACK_OFFSET_TICKS = 500; + static constexpr int AI_ECHO_RTI_SWIMMINGPOOL_OFFSET_TICKS = 1000; + static constexpr int AI_ECHO_RTI_SCHOOL_OFFSET_TICKS = 1500; + + /// Inn build attempt cadence (~8s); inhibited on master-cycle boundary. + /// (ReachToInfinity.cpp:375.) + static constexpr int AI_ECHO_RTI_INN_INTERVAL_TICKS = 200; + + /// Cadence for the enemy-swarm exploration-flag pass (~5s). + /// (ReachToInfinity.cpp:341.) + static constexpr int AI_ECHO_RTI_ENEMY_SCAN_INTERVAL_TICKS = 120; + + /// Cadence for the fruit-tree exploration-flag pass (~4s). + /// (ReachToInfinity.cpp:236.) + static constexpr int AI_ECHO_RTI_FRUIT_FLAG_INTERVAL_TICKS = 100; + + /// Cadence for both upgrade scheduler scopes — L1->L2 and L2->L3 + /// (~12s). (ReachToInfinity.cpp:630, 691.) + static constexpr int AI_ECHO_RTI_UPGRADE_INTERVAL_TICKS = 300; + + /// Cadence for the destroy-failing-buildings scan (~20s). + /// (ReachToInfinity.cpp:761.) + static constexpr int AI_ECHO_RTI_DELETE_SCAN_INTERVAL_TICKS = 500; + + /// Cadence for the forbid-farming-area scan (~10s). + /// (ReachToInfinity.cpp:804.) + static constexpr int AI_ECHO_RTI_FARMING_INTERVAL_TICKS = 250; + + + // ---- Inn (FOOD_BUILDING) sizing ------------------------------------------ + // The inn-population check sums "level1*POP_L1 + level2*POP_L2 + level3*POP_L3" + // against the total unit count to decide when another inn is warranted. + // (ReachToInfinity.cpp:392.) + + /// Population that a level-1 inn supports. + static constexpr int AI_ECHO_RTI_INN_POP_PER_L1 = 8; + /// Population that a level-2 inn supports. + static constexpr int AI_ECHO_RTI_INN_POP_PER_L2 = 12; + /// Population that a level-3 inn supports. + static constexpr int AI_ECHO_RTI_INN_POP_PER_L3 = 16; + + + // ---- Stale-inn / stale-swarm destroy thresholds -------------------------- + // (ReachToInfinity.cpp:771, 773, 791, 793.) + + /// Min resource-tracker age (~60s) before an inn becomes a destroy candidate. + static constexpr int AI_ECHO_RTI_INN_DELETE_AGE_TICKS = 1500; + /// Per-level food threshold; inn destroyed if total_level < THIS * level. + static constexpr int AI_ECHO_RTI_INN_DELETE_FOOD_PER_LEVEL = 24; + /// Min resource-tracker age (~100s) before a swarm becomes a destroy candidate. + static constexpr int AI_ECHO_RTI_SWARM_DELETE_AGE_TICKS = 2500; + /// Total-corn threshold below which a swarm is destroyed. + static constexpr int AI_ECHO_RTI_SWARM_DELETE_FOOD = 18; + + + // ---- Exploration-flag radii / explorer-count gates ----------------------- + + /// Radius of fruit-tree exploration flags (cherry/orange/prune sites). + /// (ReachToInfinity.cpp:265, 295, 325.) + static constexpr int AI_ECHO_RTI_FRUIT_FLAG_RADIUS = 4; + /// Radius of an exploration flag placed on an enemy swarm. + /// (ReachToInfinity.cpp:358.) + static constexpr int AI_ECHO_RTI_ENEMY_FLAG_RADIUS = 12; + /// Min explorer count before the AI sets fruit-tree flags. + /// (ReachToInfinity.cpp:240.) + static constexpr int AI_ECHO_RTI_FRUIT_FLAG_EXPLORER_MIN = 6; + /// Min explorer count before the AI flags enemy swarms. + /// (ReachToInfinity.cpp:343.) + static constexpr int AI_ECHO_RTI_ENEMY_FLAG_EXPLORER_MIN = 3; + + + // ---- Initial / steady-state swarm setup --------------------------------- + + /// Workers assigned to the very first existing swarm at game start. + /// (ReachToInfinity.cpp:97.) + static constexpr int AI_ECHO_RTI_INITIAL_SWARM_WORKERS = 5; + /// Resource-tracker history length (in tracker samples; tracker samples + /// every 10 ticks, so 12 = ~120 ticks of history). Used in 6 sites. + /// (ReachToInfinity.cpp:104, 109, 198, 438, 492, 892.) + static constexpr int AI_ECHO_RTI_TRACKER_LENGTH = 12; + /// Workers assigned to a freshly-ordered swarm site. + /// (ReachToInfinity.cpp:455.) + static constexpr int AI_ECHO_RTI_SWARM_WORKERS_NEW = 3; + /// Workers reassigned once a swarm finishes construction. + /// (ReachToInfinity.cpp:482.) + static constexpr int AI_ECHO_RTI_SWARM_WORKERS_FINISHED = 5; + + /// Initial / steady-state swarm ratio (worker:explorer:warrior). Used at + /// game start (ReachToInfinity.cpp:100) and on every new swarm completion + /// (ReachToInfinity.cpp:487). + static constexpr int AI_ECHO_RTI_SWARM_RATIO_WORKER = 15; + static constexpr int AI_ECHO_RTI_SWARM_RATIO_EXPLORER = 1; + static constexpr int AI_ECHO_RTI_SWARM_RATIO_WARRIOR = 0; + + + // ---- Swarm cadence: early vs late population thresholds ------------------ + // "if (number<=EARLY_LIMIT && totalUnit/EARLY_RATIO >= number) || totalUnit/LATE_RATIO >= number" + // (ReachToInfinity.cpp:450.) + + static constexpr int AI_ECHO_RTI_SWARM_EARLY_LIMIT = 3; + static constexpr int AI_ECHO_RTI_SWARM_EARLY_RATIO = 20; + static constexpr int AI_ECHO_RTI_SWARM_LATE_RATIO = 50; + + + // ---- Inn placement constraint weights / distances ------------------------ + // Used by the standard inn order (ReachToInfinity.cpp:401, 403, 410, 416, + // 426) and the "construct inn" message handler (ReachToInfinity.cpp:855, + // 857, 864, 870, 880). + + /// Constraint weight: minimize distance to wheat (inn placement). + static constexpr int AI_ECHO_RTI_INN_WHEAT_WEIGHT = 4; + /// Constraint cap: inn must be within this many tiles of wheat. + static constexpr int AI_ECHO_RTI_INN_WHEAT_MAX_DIST = 10; + /// Constraint weight: prefer clustering with friendly buildings. + static constexpr int AI_ECHO_RTI_BUILD_CLUSTER_WEIGHT = 2; + /// Min-distance from friendly construction sites for an inn order. + static constexpr int AI_ECHO_RTI_INN_CONSTRUCTION_MIN_DIST = 3; + /// Constraint weight: light pull toward fruit (inn placement). + static constexpr int AI_ECHO_RTI_INN_FRUIT_WEIGHT = 1; + + + // ---- Swarm placement constraint weights / distances ---------------------- + // (ReachToInfinity.cpp:461, 468, 474.) + + /// Constraint weight: lighter cluster pull for swarms (vs inns). + static constexpr int AI_ECHO_RTI_SWARM_CLUSTER_WEIGHT = 1; + + + // ---- Racetrack (WALKSPEED_BUILDING) placement ---------------------------- + // (ReachToInfinity.cpp:505, 508, 514, 520, 522, 529, 535.) + + /// Workers assigned to a racetrack construction site. + static constexpr int AI_ECHO_RTI_RACETRACK_WORKERS = 6; + /// Constraint weight: minimize distance to wood. + static constexpr int AI_ECHO_RTI_RACETRACK_WOOD_WEIGHT = 4; + /// Constraint weight: minimize distance to stone. + static constexpr int AI_ECHO_RTI_RACETRACK_STONE_WEIGHT = 1; + /// Min-distance from stone (room to upgrade). + static constexpr int AI_ECHO_RTI_RACETRACK_STONE_MIN_DIST = 2; + /// Min-distance from friendly construction sites (room for racetrack upgrade). + static constexpr int AI_ECHO_RTI_RACETRACK_CONSTR_MIN_DIST = 4; + + + // ---- Swimming pool (SWIMSPEED_BUILDING) placement ------------------------ + // (ReachToInfinity.cpp:548, 551, 557, 563, 569, 576, 582.) + + static constexpr int AI_ECHO_RTI_SWIMMINGPOOL_WORKERS = 6; + static constexpr int AI_ECHO_RTI_SWIMMINGPOOL_WOOD_WEIGHT = 4; + static constexpr int AI_ECHO_RTI_SWIMMINGPOOL_WHEAT_WEIGHT = 1; + static constexpr int AI_ECHO_RTI_SWIMMINGPOOL_STONE_MIN_DIST = 2; + static constexpr int AI_ECHO_RTI_SWIMMINGPOOL_CONSTR_MIN_DIST = 4; + + + // ---- School (SCIENCE_BUILDING) placement -------------------------------- + // (ReachToInfinity.cpp:596, 599, 606, 612, 621.) + + static constexpr int AI_ECHO_RTI_SCHOOL_WORKERS = 5; + /// Min-distance from friendly construction sites (room to upgrade school). + static constexpr int AI_ECHO_RTI_SCHOOL_CONSTR_MIN_DIST = 4; + /// Constraint weight: maximize distance from enemy buildings. + static constexpr int AI_ECHO_RTI_SCHOOL_ENEMY_DIST_WEIGHT = 3; + + + // ---- Secondary-building (racetrack/swimmingpool/school) population gating + // "if (totalUnit/SECONDARY_BLDG_RATIO) >= number && number < MAX_*" + // (ReachToInfinity.cpp:505, 548, 596.) + + /// Population per allowed secondary building (one racetrack per 60 units, etc.). + static constexpr int AI_ECHO_RTI_SECONDARY_BLDG_RATIO = 60; + /// Hard cap on number of racetracks. + static constexpr int AI_ECHO_RTI_RACETRACK_MAX = 3; + /// Hard cap on number of swimming pools. + static constexpr int AI_ECHO_RTI_SWIMMINGPOOL_MAX = 3; + /// Hard cap on number of schools. + static constexpr int AI_ECHO_RTI_SCHOOL_MAX = 4; + + + // ---- Upgrade scheduler (level 1->2 and 2->3) ---------------------------- + // (ReachToInfinity.cpp:644, 712 — concurrent fraction; 649, 717 — school + // gate; 662, 730 — workers during; 676 — L2 finished; 744 — L3 finished; + // 633, 694 — target-level args.) + + /// Concurrent upgrades capped to ~1/THIS of all level-N buildings. + static constexpr int AI_ECHO_RTI_CONCURRENT_UPGRADE_FRACTION = 15; + /// Below this many schools, upgrade pass excludes schools (avoid bricking + /// the upgrade pipeline). + static constexpr int AI_ECHO_RTI_SCHOOL_THRESHOLD_FOR_UPGRADE = 2; + /// Workers assigned to a building while it is being upgraded. + static constexpr int AI_ECHO_RTI_UPGRADE_WORKERS_DURING = 8; + /// Workers reassigned to a finished L2 inn. + static constexpr int AI_ECHO_RTI_INN_L2_WORKERS_FINISHED = 3; + /// Workers reassigned to a finished L3 inn. + static constexpr int AI_ECHO_RTI_INN_L3_WORKERS_FINISHED = 6; + /// User-facing 1-based target level for the L1->L2 upgrade pass. + static constexpr int AI_ECHO_RTI_UPGRADE_TARGET_LEVEL_2 = 2; + /// User-facing 1-based target level for the L2->L3 upgrade pass. + static constexpr int AI_ECHO_RTI_UPGRADE_TARGET_LEVEL_3 = 3; + + + // ---- Farming-area pattern ----------------------------------------------- + // The forbidden-farming-area scan applies a brush only on a checker + // pattern (every 4th tile) to leave aisles between rows. + // (ReachToInfinity.cpp:816, 830.) + + /// Stride modulus for the farming pattern: brush only on (x % STRIDE == 1 + /// && y % STRIDE == 1) tiles. + static constexpr int AI_ECHO_RTI_FARMING_PATTERN_STRIDE = 2; + /// Max distance from water for forbidden-farming-area placement. + static constexpr int AI_ECHO_RTI_FARMING_WATER_MAX_DIST = 10; + + + // ---- GradientManager / pending-building timing --------------------------- + // (Gradient.cpp:265, 297, 308, 326, 329; Construction.cpp:802.) + + /// Maximum age (ticks) before a referenced gradient is force-recalculated. + /// (~6s at 25 ticks/s; doc-string at Gradients.h:299-302.) + static constexpr int AI_ECHO_GRADIENT_STALE_TICKS = 150; + /// Pre-stale value seeded for newly-queued gradients so they age past the + /// QUEUE_MIN_AGE gate immediately and recompute on the next update tick. + static constexpr int AI_ECHO_GRADIENT_INITIAL_AGE_TICKS = 200; + /// Min age (ticks) before a queued gradient is actually recomputed. + static constexpr int AI_ECHO_GRADIENT_QUEUE_MIN_AGE_TICKS = 50; + /// Tick timeout — drop a pending building if the engine hasn't placed it + /// within this many ticks (~12s at 25 ticks/s). + /// (Construction.cpp:802.) + static constexpr int AI_ECHO_PENDING_BUILDING_TIMEOUT_TICKS = 300; + + + // ---- Resource tracker sampling ------------------------------------------ + + /// Sampling cadence for RessourceTracker — samples building resources + /// every THIS many ticks. (Management.cpp:346.) + static constexpr int AI_ECHO_TRACKER_SAMPLE_INTERVAL_TICKS = 10; + + + // ---- Save signature ----------------------------------------------------- + + /// Length (bytes) of the literal "EchoSig" save-stream signature (does NOT + /// include a NUL terminator). (Echo.cpp:34, 42, 43.) + static constexpr int AI_ECHO_SIGNATURE_LENGTH = 7; + + + // ---- Misc level / priority codecs --------------------------------------- + + /// Engine BuildingType::level cap — engine level 2 is the third (max) + /// level, so a building cannot be upgraded above this. + /// (Conditions.cpp:561, Upgradable::passes.) + static constexpr int AI_ECHO_MAX_BUILDING_LEVEL_INDEX = 2; + + /// On-disk and on-Order encoding of AdjustPriority::BuildingPriority. + /// Stored as a Sint32 (-1/0/1) inside MAdjustPriority orders. Distinct + /// from the AI_ECHO_TRIBOOL_* domain — those encode boost::logic::tribool + /// in BuildingRegister/ChangeAlliances save streams. + /// (Management.cpp:733-738, 769-773, 786-791.) + static constexpr int AI_ECHO_PRIORITY_LOW = -1; + static constexpr int AI_ECHO_PRIORITY_MEDIUM = 0; + static constexpr int AI_ECHO_PRIORITY_HIGH = 1; +}; diff --git a/src/AIImplementation.h b/src/ai/AIImplementation.h similarity index 55% rename from src/AIImplementation.h rename to src/ai/AIImplementation.h index 2d627e381..b82fac509 100644 --- a/src/AIImplementation.h +++ b/src/ai/AIImplementation.h @@ -1,33 +1,16 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière -#ifndef __AI_IMPLEMENTATION_H -#define __AI_IMPLEMENTATION_H +#pragma once /* What's in AI ? AI represents the behaviour of an artificial intelligence player. -The main method is boost::shared_ptr getOrder() which return the order to be used by the AI's team. +The main method is std::shared_ptr getOrder() which return the order to be used by the AI's team. */ #include "BuildingType.h" -#include +#include namespace GAGCore { @@ -79,10 +62,9 @@ class AIImplementation virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; virtual void save(GAGCore::OutputStream *stream)=0; - virtual boost::shared_ptr getOrder(void)=0; + virtual std::shared_ptr getOrder(void)=0; }; -#endif diff --git a/src/AINames.cpp b/src/ai/AINames.cpp similarity index 56% rename from src/AINames.cpp rename to src/ai/AINames.cpp index a6bfb579f..61bdd5854 100644 --- a/src/AINames.cpp +++ b/src/ai/AINames.cpp @@ -1,25 +1,11 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +#include #include "AI.h" +#include "AINames.h" #include "Toolkit.h" #include "StringTable.h" @@ -76,4 +62,27 @@ namespace AINames } return Toolkit::getStringTable()->getString(sAi); } + + int parseAIName(const std::string& name) + { + // Single source of truth for CLI-friendly AI names. Both + // --ai-types and --matchup parsers in GlobalContainer.cpp + // use this to avoid drift. + static const struct { const char* name; int id; } table[] = { + {"numbi", AI::NUMBI}, + {"castor", AI::CASTOR}, + {"warrush", AI::WARRUSH}, + {"reachtoinfinity", AI::REACHTOINFINITY}, + {"nicowar", AI::NICOWAR}, + {"toubib", AI::TOUBIB}, + }; + std::string lower = name; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + for (size_t i = 0; i < sizeof(table)/sizeof(table[0]); i++) + { + if (lower == table[i].name) + return table[i].id; + } + return AI_UNKNOWN_NAME; + } } diff --git a/src/ai/AINames.h b/src/ai/AINames.h new file mode 100644 index 000000000..08e613423 --- /dev/null +++ b/src/ai/AINames.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "AI.h" + +namespace AINames +{ + /// Sentinel returned by parseAIName() when the supplied name does + /// not match any known AI::ImplementitionID. Distinct from any + /// valid AI id (which are 0..7); callers compare with `== AI_UNKNOWN_NAME`. + static const int AI_UNKNOWN_NAME = -1; + + std::string getAIText(int id); + std::string getAIDescription(int id); + + /// Resolve a CLI-friendly AI name (case-insensitive) to its + /// AI::ImplementitionID value (1..6). Returns AI_UNKNOWN_NAME on unknown. + /// Used by --ai-types and --matchup parsers in GlobalContainer.cpp; + /// keep the name table here to avoid drift between the two CLIs. + int parseAIName(const std::string& name); +} diff --git a/src/AINicowar.h b/src/ai/AINicowar.h similarity index 96% rename from src/AINicowar.h rename to src/ai/AINicowar.h index b767efdb6..a6023cad3 100644 --- a/src/AINicowar.h +++ b/src/ai/AINicowar.h @@ -1,25 +1,10 @@ -/* - Copyright (C) 2006 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef AINicowar_h -#define AINicowar_h - -#include "AIEcho.h" +#include "echo/Echo.h" +#include "AINicowarTuning.h" #include "ConfigFiles.h" ///This class represents the configuragle strategy that Nicowar will take. It uses the same algorithms, @@ -206,6 +191,14 @@ class NicowarStrategyLoader : ConfigVector NicowarStrategy getParticularStrategy(const std::string& name); }; +/// Sentinel value for `NewNicowar::target` meaning "no enemy team +/// currently targeted." `target` is a Sint8/int that otherwise holds an +/// enemy team index. Used at init, when a chosen target dies/becomes +/// unreachable, and as a guard before launching attacks. +/// Distinct from AINames::AI_UNKNOWN_NAME and from the Echo +/// enemy_building_iterator wildcard `-1` args (those are kept literal). +static const int AI_NICOWAR_NO_TARGET = -1; + ///Nicowar is a new powerhouse AI for Globulation 2 class NewNicowar : public AIEcho::EchoAI { @@ -404,4 +397,3 @@ class NewNicowar : public AIEcho::EchoAI }; -#endif diff --git a/src/ai/AINicowarTuning.h b/src/ai/AINicowarTuning.h new file mode 100644 index 000000000..1098c5b9f --- /dev/null +++ b/src/ai/AINicowarTuning.h @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault +// +// AINicowarTuning.h +// +// Behavior-preserving tuning constants for AINicowar (the "NewNicowar" +// AIEcho-based AI), extracted from glob2/src/ai/nicowar/*.cpp during the +// magic-number cleanup pass that prepares the codebase for the Rust port. +// Every value here is byte-for-byte identical to the literal it replaces; +// nothing in the AI's decision logic changes. +// +// Sentinel-style constants that already had names from Phase 3a are NOT +// redeclared here: +// - AI_NICOWAR_NO_TARGET (in AINicowar.h) +// +// Constants are file-scope `static constexpr int` per the slice convention. + +#pragma once + +// --------------------------------------------------------------------------- +// Save-format minor-version gates (NewNicowar::load). +// versionMinor >= 59 enables the bulk of the Nicowar-specific section; >= 60 +// adds the per-strategy name and `can_swim` flag; >= 66 adds the +// defense_flags / explorer_attack_flags vectors. Below the V59 cutoff, load +// silently returns true with a default-initialized AI (legacy save-compat). +// --------------------------------------------------------------------------- +static constexpr int AI_NICOWAR_SAVE_FORMAT_V59 = 59; +static constexpr int AI_NICOWAR_SAVE_FORMAT_V60 = 60; +static constexpr int AI_NICOWAR_SAVE_FORMAT_V66 = 66; + +// --------------------------------------------------------------------------- +// Lifecycle / decision-cycle scheduling (NewNicowar::tick). +// On the first tick (timer == AI_NICOWAR_INIT_TICK), the AI selects a +// strategy, evaluates its phase booleans, and initializes existing buildings. +// Thereafter the decision cycle runs every AI_NICOWAR_DECISION_CYCLE_TICKS +// ticks, split across six staggered sub-phases (each phase fires once per +// cycle at its own offset within that cycle). +// --------------------------------------------------------------------------- +static constexpr int AI_NICOWAR_INIT_TICK = 1; +static constexpr int AI_NICOWAR_DECISION_CYCLE_TICKS = 100; +static constexpr int AI_NICOWAR_QUEUE_BUILDINGS_PHASE = 0; +static constexpr int AI_NICOWAR_CHECK_PHASES_PHASE = 17; +static constexpr int AI_NICOWAR_MANAGE_BUILDINGS_PHASE = 33; +static constexpr int AI_NICOWAR_UPGRADE_PHASE = 50; +static constexpr int AI_NICOWAR_CONTROL_ATTACKS_PHASE = 67; +static constexpr int AI_NICOWAR_DEFENSE_FLAG_PHASE = 84; + +// Farming runs on its own slower 250-tick cycle, with two phases: +// timer % 250 == 0 -> update_farming +// timer % 250 == 85 -> update_fruit_flags +static constexpr int AI_NICOWAR_FARMING_INTERVAL_TICKS = 250; +static constexpr int AI_NICOWAR_FRUIT_PHASE_OFFSET = 85; + +// Explorer-attack repositioning runs on a 1000-tick cycle, fired at offset +// 570 within that cycle (so once every 1000 ticks). +static constexpr int AI_NICOWAR_EXPLORER_ATTACK_INTERVAL_TICKS = 1000; +static constexpr int AI_NICOWAR_EXPLORER_ATTACK_OFFSET = 570; + +// --------------------------------------------------------------------------- +// Ressource tracker depth: AddRessourceTracker(N, CORN, id) records the last +// N resource samples per tracked building. Used at every tracker-creation +// site (initialization, every newly-ordered inn/swarm, and the per-level +// inn assignment math which multiplies a wheat-trigger threshold by this +// same N to convert per-tick wheat into the tracker's accumulated total). +// --------------------------------------------------------------------------- +static constexpr int AI_NICOWAR_RESSOURCE_TRACKER_DEPTH = 25; + +// --------------------------------------------------------------------------- +// Phase iteration / level dimensions (Phases.cpp). +// Globulation 2 building/skill levels run 0..3 (four levels total). +// --------------------------------------------------------------------------- +// Inclusive upper bound when summing trained warriors at minimum-level..3 +// in the war-preparation gate (`for(i = strategy.min_warrior_level...; i<=3; ...)`). +static constexpr int AI_NICOWAR_MAX_UPGRADE_LEVEL = 3; + +// Avoid div-by-zero when computing the starvation percentage: +// only compute `needFoodNoInns * 100 / totalUnit` when totalUnit > 1. +static constexpr int AI_NICOWAR_STARVATION_MIN_UNITS = 1; + +// Number of skill levels (0..3 inclusive) iterated when summing +// upgradeStatePerType[WORKER][SWIM][i] for the can-swim phase test. +static constexpr int AI_NICOWAR_LEVEL_COUNT = 4; + +// Hardcoded "max-level explorer" index used by the explorer-attack-phase +// gate: only level-3 (fully upgraded) MAGIC_ATTACK_GROUND explorers are +// counted for the threshold. +static constexpr int AI_NICOWAR_EXPLORER_MAX_LEVEL = 3; + +// --------------------------------------------------------------------------- +// Building-order tuning (Buildings.cpp). Each constraint constant is named +// after its (building-type, gradient-source, constraint-kind) triple. +// +// Naming convention: AI_NICOWAR__. +// _ORDER_WORKERS -> initial worker count passed to BuildingOrder +// __PREF -> MinimizedDistance (we want to be near RES) +// __MIN -> MinimumDistance (we don't want to be too close to RES) +// __MAX -> MaximumDistance (hard cap on distance from RES) +// _BUILDING_PREF -> MinimizedDistance to friendly buildings +// _CONSTRUCTION_MIN -> MinimumDistance to friendly buildings under construction +// _ENEMY_<...> -> distance to any enemy team building +// --------------------------------------------------------------------------- + +// --- Inn (FOOD_BUILDING) --- +static constexpr int AI_NICOWAR_INN_ORDER_WORKERS = 2; +static constexpr int AI_NICOWAR_INN_WHEAT_MIN_DIST = 8; +static constexpr int AI_NICOWAR_INN_WHEAT_MAX_DIST = 10; +static constexpr int AI_NICOWAR_INN_WATER_MIN_DIST = 6; +static constexpr int AI_NICOWAR_INN_BUILDING_PREF = 4; +static constexpr int AI_NICOWAR_INN_CONSTRUCTION_MIN = 4; +static constexpr int AI_NICOWAR_INN_ENEMY_MAX_DIST = 1; +static constexpr int AI_NICOWAR_INN_FRUIT_PREF = 1; + +// --- Swarm (SWARM_BUILDING) --- +static constexpr int AI_NICOWAR_SWARM_ORDER_WORKERS = 4; +static constexpr int AI_NICOWAR_SWARM_WHEAT_PREF = 6; +static constexpr int AI_NICOWAR_SWARM_WATER_MIN_DIST = 6; +static constexpr int AI_NICOWAR_SWARM_BUILDING_PREF = 1; +static constexpr int AI_NICOWAR_SWARM_CONSTRUCTION_MIN = 2; + +// --- Racetrack (WALKSPEED_BUILDING) --- +static constexpr int AI_NICOWAR_RACETRACK_ORDER_WORKERS = 6; +static constexpr int AI_NICOWAR_RACETRACK_WOOD_PREF = 4; +static constexpr int AI_NICOWAR_RACETRACK_WATER_MIN_DIST = 6; +static constexpr int AI_NICOWAR_RACETRACK_STONE_PREF = 1; +static constexpr int AI_NICOWAR_RACETRACK_STONE_MIN = 2; +static constexpr int AI_NICOWAR_RACETRACK_BUILDING_PREF = 2; +static constexpr int AI_NICOWAR_RACETRACK_SAND_MIN = 2; +static constexpr int AI_NICOWAR_RACETRACK_CONSTRUCTION_MIN = 4; + +// --- Swimmingpool (SWIMSPEED_BUILDING) --- +static constexpr int AI_NICOWAR_SWIMMINGPOOL_ORDER_WORKERS = 6; +static constexpr int AI_NICOWAR_SWIMMINGPOOL_WOOD_PREF = 4; +static constexpr int AI_NICOWAR_SWIMMINGPOOL_WATER_MIN_DIST = 6; +static constexpr int AI_NICOWAR_SWIMMINGPOOL_WHEAT_PREF = 1; +static constexpr int AI_NICOWAR_SWIMMINGPOOL_STONE_MIN = 2; +static constexpr int AI_NICOWAR_SWIMMINGPOOL_BUILDING_PREF = 2; +static constexpr int AI_NICOWAR_SWIMMINGPOOL_SAND_MIN = 2; +static constexpr int AI_NICOWAR_SWIMMINGPOOL_CONSTRUCTION_MIN = 4; + +// --- School (SCIENCE_BUILDING) --- +static constexpr int AI_NICOWAR_SCHOOL_ORDER_WORKERS = 5; +static constexpr int AI_NICOWAR_SCHOOL_BUILDING_PREF = 2; +static constexpr int AI_NICOWAR_SCHOOL_WATER_MIN_DIST = 6; +static constexpr int AI_NICOWAR_SCHOOL_CONSTRUCTION_MIN = 4; +static constexpr int AI_NICOWAR_SCHOOL_ENEMY_MAX_DIST = 3; + +// --- Barracks (ATTACK_BUILDING) --- +static constexpr int AI_NICOWAR_BARRACKS_ORDER_WORKERS = 6; +static constexpr int AI_NICOWAR_BARRACKS_WATER_MIN_DIST = 6; +static constexpr int AI_NICOWAR_BARRACKS_STONE_PREF = 5; +static constexpr int AI_NICOWAR_BARRACKS_WOOD_PREF = 2; +static constexpr int AI_NICOWAR_BARRACKS_BUILDING_PREF = 2; +static constexpr int AI_NICOWAR_BARRACKS_CONSTRUCTION_MIN = 2; + +// Halve the queued barracks-demand by free-warrior count: barracks demand +// is min(strategy.war_prep_barracks, isFree[WARRIOR] / N). Splits the +// "/2" so the divisor is named. +static constexpr int AI_NICOWAR_BARRACKS_FREE_WARRIOR_DIVISOR = 2; + +// --- Hospital (HEAL_BUILDING) --- +static constexpr int AI_NICOWAR_HOSPITAL_ORDER_WORKERS = 2; +static constexpr int AI_NICOWAR_HOSPITAL_WOOD_PREF = 2; +static constexpr int AI_NICOWAR_HOSPITAL_WATER_MIN_DIST = 6; +static constexpr int AI_NICOWAR_HOSPITAL_BUILDING_PREF = 3; +static constexpr int AI_NICOWAR_HOSPITAL_CONSTRUCTION_MIN = 2; + +// --------------------------------------------------------------------------- +// Explorer cap (manage_swarm in Buildings.cpp): +// needed_explorers = min(strategy.base_number_of_explorers, +// totalUnit / DIVISOR + MIN); +// "Min one explorer, plus one per N pop." +// --------------------------------------------------------------------------- +static constexpr int AI_NICOWAR_EXPLORER_POP_DIVISOR = 10; +static constexpr int AI_NICOWAR_EXPLORER_MIN = 1; + +// --------------------------------------------------------------------------- +// Upgrade selection (Upgrade.cpp). +// --------------------------------------------------------------------------- +// Need at least this many level-2 OR level-3 schools before allowing any +// further school upgrades during upgrading_phase_2. +static constexpr int AI_NICOWAR_LVL2_SCHOOL_THRESHOLD = 2; + +// Sentinel returned by choose_building_upgrade_type / _level1 / _level2 when +// no eligible building type is available for upgrading. Distinct from +// AI_NICOWAR_NO_TARGET (target-team sentinel) and from the choose-building- +// to-attack "no building" sentinel in Attack.cpp. +static constexpr int AI_NICOWAR_NO_BUILDING_TYPE = -1; + +// --------------------------------------------------------------------------- +// Attack control (Attack.cpp). +// --------------------------------------------------------------------------- +// The Echo gradient layer reports -2 for "unreachable" cells. Used to skip +// enemy buildings we can't path to. Distinct from Echo's wildcard `-1` args +// to enemy_building_iterator and from AI_NICOWAR_NO_TARGET, even though all +// three are negative integers in adjacent code. +static constexpr int AI_NICOWAR_GRADIENT_UNREACHABLE = -2; + +// ChangeFlagMinimumLevel(N, war_flag): only warriors level-N or higher +// participate in war-flag attacks. +static constexpr int AI_NICOWAR_WAR_FLAG_MIN_LEVEL = 2; + +// Pseudo-INT_MAX initial value for the "closest reachable cell" distance +// scan in dig_out_enemy(). Kept as a plain literal at the call site rather +// than INT_MAX because the loop computes `dist < closest_distance` with +// gradient values that are bounded well below 10000. +static constexpr int AI_NICOWAR_DIG_OUT_INIT_DIST = 10000; + +// Initial value of the per-step "ticks since last clearing flag" counter. +// Set to AI_NICOWAR_DIG_FLAG_INTERVAL so the very first iteration of the +// dig-out loop places a clearing flag at the start of the path. +static constexpr int AI_NICOWAR_DIG_FLAG_INIT_COUNTER = 3; + +// Tolerance in the dig-out pathfind: at each step we accept a neighbor cell +// whose gradient height is at most `current + N`. Keeps the path moving +// toward the goal while allowing minor backtracks. +static constexpr int AI_NICOWAR_PATHFIND_TOLERANCE = 2; + +// Place a clearing flag every N steps along the dig-out path +// (`if(flag_dist_count > N) ...`). +static constexpr int AI_NICOWAR_DIG_FLAG_INTERVAL = 3; + +// Workers assigned to each clearing flag spawned during a dig-out. +static constexpr int AI_NICOWAR_DIG_CLEARING_WORKERS = 10; + +// Radius assigned (via ChangeFlagSize) to each clearing flag during dig-out. +static constexpr int AI_NICOWAR_DIG_FLAG_SIZE = 3; + +// --------------------------------------------------------------------------- +// Defense flag positioning (Flags.cpp::compute_defense_flag_positioning). +// --------------------------------------------------------------------------- +// Detection radius used to flag-paint cells around each unit/building under +// attack. Defines the candidate-flag-area; multiple under-attack entities +// within RADIUS contribute additively to a square's score. +static constexpr int AI_NICOWAR_DEFENSE_FLAG_RADIUS = 4; + +// When iterating over the (px, py) area to clear scored entities and count +// nearby enemy warriors, we extend the loop by this margin so that buildings +// (which contribute their score at an offset position determined by +// type->decLeft / decTop) are still found just outside RADIUS. +static constexpr int AI_NICOWAR_DEFENSE_BUILDING_OFFSET_MARGIN = 3; + +// Cap on workers assigned to a single defense flag (`std::min(20, enemy_count)`). +static constexpr int AI_NICOWAR_MAX_DEFENSE_FLAG_WORKERS = 20; + +// Squared distance limit (in tiles^2) for moving an existing defense flag +// to a new candidate position rather than destroying and re-creating it. +// At source: `if(min_dist < (8*8))`. Kept as `8 * 8` at the call site so +// the "8 tiles, squared" intent stays visible. +static constexpr int AI_NICOWAR_DEFENSE_FLAG_MAX_MOVE_TILES = 8; + +// Half-extent (in tiles) of the per-cell scan used by the second-pass +// "destroy unmoved defense flags whose enemy_count is now zero" loop. +// At source: `for(int px = -3; px <= 3; ++px)`. +static constexpr int AI_NICOWAR_DEFENSE_REASSIGN_RADIUS = 3; + +// Radius assigned (via ChangeFlagSize) to each freshly-created defense flag. +static constexpr int AI_NICOWAR_DEFENSE_FLAG_SIZE = 4; + +// --------------------------------------------------------------------------- +// Explorer-attack flag positioning (compute_explorer_flag_attack_positioning). +// Groups enemy units into clusters; each cluster radiates outward from a +// seed unit, picking up neighbors within EXPLORER_GROUP_SEARCH_RADIUS as +// long as they stay within the centered cohesion ball EXPLORER_GROUP_COHESION +// (warpDistSquare from cluster centroid). +// --------------------------------------------------------------------------- +// Half-extent in tiles for the (dx, dy) neighbor-scan around each unit. +// Source uses `for(int dx = -4; dx<=4; ++dx)` etc. +static constexpr int AI_NICOWAR_EXPLORER_GROUP_SEARCH_RADIUS = 4; + +// Cluster cohesion in tiles: candidates are accepted only if their +// warpDistSquare from the running cluster centroid is < N*N. Kept as the +// raw tile count so the call site retains the `(N * N)` literal. +static constexpr int AI_NICOWAR_EXPLORER_GROUP_COHESION_TILES = 6; + +// Radius assigned (via ChangeFlagSize) to each explorer-attack flag. +static constexpr int AI_NICOWAR_EXPLORER_ATTACK_FLAG_SIZE = 6; + +// Min explorer level required to participate in an explorer-attack flag. +// [POSSIBLE BUG / preserved] Skill levels run 0..3; setting min level to 4 +// either locks the flag entirely or is silently capped at MAX_LEVEL=3 by the +// engine. See bugs_surfaced_during_magic_number_audit.md M8 -- the literal +// is preserved verbatim, only named. +static constexpr int AI_NICOWAR_EXPLORER_ATTACK_MIN_LEVEL = 4; + +// --------------------------------------------------------------------------- +// Farming (Farming.cpp::update_farming, ::update_fruit_flags). +// --------------------------------------------------------------------------- +// Maximum distance (in water-gradient steps) from water at which the AI +// will permit a wood-farm spot. +static constexpr int AI_NICOWAR_FARM_WOOD_WATER_DIST = 6; + +// Maximum distance (in water-gradient steps) from water at which the AI +// will permit a wheat-farm spot. +static constexpr int AI_NICOWAR_FARM_WHEAT_WATER_DIST = 10; + +// Stride of the checkerboard farming pattern: permanent farm spots are at +// every (x % N == 1 && y % N == 1); horizontal expansion at (0, 1) and +// vertical expansion at (1, 0). Single shared stride. +static constexpr int AI_NICOWAR_FARM_PATTERN_STRIDE = 2; + +// Workers ordered onto each fruit-tree exploration flag (cherry, orange, +// prune). One BuildingOrder per fruit type. +static constexpr int AI_NICOWAR_FRUIT_FLAG_WORKERS = 2; + +// MinimizedDistance(gi_building, N) on each fruit flag -- prefer fruit +// trees closer to our settlement. +static constexpr int AI_NICOWAR_FRUIT_FLAG_BUILDING_PREF = 1; + +// MaximumDistance(gi_, N) on each fruit flag -- the flag must sit +// directly on top of fruit (distance 0). +static constexpr int AI_NICOWAR_FRUIT_FLAG_ON_FRUIT_DIST = 0; + +// Radius assigned (via ChangeFlagSize) to each fruit-tree exploration flag. +static constexpr int AI_NICOWAR_FRUIT_FLAG_SIZE = 4; diff --git a/src/ai/AINull.cpp b/src/ai/AINull.cpp new file mode 100644 index 000000000..5cc92f485 --- /dev/null +++ b/src/ai/AINull.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "AINull.h" +#include "Order.h" + +std::shared_ptr AINull::getOrder(void) +{ + return std::shared_ptr(new NullOrder()); +} diff --git a/src/ai/AINull.h b/src/ai/AINull.h new file mode 100644 index 000000000..b3d09410b --- /dev/null +++ b/src/ai/AINull.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include "AIImplementation.h" + +class AINull : public AIImplementation +{ +public: + AINull() { } + ~AINull() { } + + void init(Player *player) { } + + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) { return true; } + void save(GAGCore::OutputStream *stream) { } + + std::shared_ptr getOrder(void); + +private: +}; + + + + diff --git a/src/AINumbi.cpp b/src/ai/AINumbi.cpp similarity index 51% rename from src/AINumbi.cpp rename to src/ai/AINumbi.cpp index a14bf2c46..989abcd96 100644 --- a/src/AINumbi.cpp +++ b/src/ai/AINumbi.cpp @@ -1,23 +1,9 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include +#include +#include #include "AINumbi.h" #include "Game.h" @@ -27,7 +13,7 @@ #include "Utilities.h" #include "Unit.h" -using boost::shared_ptr; +using std::shared_ptr; AINumbi::AINumbi(Player *player) { @@ -44,10 +30,10 @@ void AINumbi::init(Player *player) { timer=0; phase=0; - phaseTime=1024; + phaseTime=AI_NUMBI_PHASE_TIME_DEFAULT_TICKS; attackPhase=0; - critticalWarriors=20; - critticalTime=1024; + critticalWarriors=AI_NUMBI_CRITICAL_WARRIORS_DEFAULT; + critticalTime=AI_NUMBI_CRITICAL_TIME_DEFAULT_TICKS; attackTimer=0; for (int i=0; iAINumbi::getOrder(void) +std::shared_ptrAINumbi::getOrder(void) { timer++; @@ -128,132 +114,132 @@ boost::shared_ptrAINumbi::getOrder(void) if (phase==0) { // rush for food building, explore for room. - switch (timer&0x1F) + switch (timer&AI_NUMBI_DECISION_SLOT_MASK) { case 0: - return swarmsForWorkers(1, 4, 7, 1, 0); + return swarmsForWorkers(AI_NUMBI_PHASE0_SWARM_MIN, AI_NUMBI_PHASE0_SWARM_FACTOR, AI_NUMBI_PHASE0_SWARM_WORKERS, AI_NUMBI_PHASE0_SWARM_EXPLORER, AI_NUMBI_PHASE0_SWARM_WARRIOR); case 1: - return adjustBuildings(4, 1, 3, IntBuildingType::FOOD_BUILDING); + return adjustBuildings(AI_NUMBI_PHASE0_INN_NUMBERS, AI_NUMBI_PHASE0_INN_NUMBERS_INC, AI_NUMBI_PHASE0_INN_WORKERS, IntBuildingType::FOOD_BUILDING); } } else if (phase==1) { // rush for food building - switch (timer&0x1F) + switch (timer&AI_NUMBI_DECISION_SLOT_MASK) { case 0: - return swarmsForWorkers(1, 5, 14, 0, 0); + return swarmsForWorkers(AI_NUMBI_PHASE1_SWARM_MIN, AI_NUMBI_PHASE1_SWARM_FACTOR, AI_NUMBI_PHASE1_SWARM_WORKERS, AI_NUMBI_PHASE1_SWARM_EXPLORER, AI_NUMBI_PHASE1_SWARM_WARRIOR); case 1: - return adjustBuildings(4, 1, 3, IntBuildingType::FOOD_BUILDING); + return adjustBuildings(AI_NUMBI_PHASE1_INN_NUMBERS, AI_NUMBI_PHASE1_INN_NUMBERS_INC, AI_NUMBI_PHASE1_INN_WORKERS, IntBuildingType::FOOD_BUILDING); } } - else if (phase<4) + else if (phaseisRessourceTakeable(rx+i, ry, CORN)||map->isRessourceTakeable(rx+i, ry-1, CORN)) w++; else if (hole--<0) break; rxr=rx+i; - hole=2; - for (i=0; i<32; i++) + hole=AI_NUMBI_CORN_SCAN_HOLE_TOLERANCE; + for (i=0; iisRessourceTakeable(rx-i, ry, CORN)||map->isRessourceTakeable(rx-i, ry-1, CORN)) w++; else if (hole--<0) @@ -303,15 +289,15 @@ int AINumbi::estimateFood(Building *building) rx=((rxr+rxl)>>1); - hole=2; - for (i=0; i<32; i++) + hole=AI_NUMBI_CORN_SCAN_HOLE_TOLERANCE; + for (i=0; iisRessourceTakeable(rx, ry+i, CORN)||map->isRessourceTakeable(rx-1, ry+i, CORN)) h++; else if (hole--<0) break; ryb=ry+i; - hole=2; - for (i=0; i<32; i++) + hole=AI_NUMBI_CORN_SCAN_HOLE_TOLERANCE; + for (i=0; iisRessourceTakeable(rx, ry-i, CORN)||map->isRessourceTakeable(rx-1, ry-i, CORN)) h++; else if (hole--<0) @@ -321,15 +307,15 @@ int AINumbi::estimateFood(Building *building) ry=((ryb+ryt)>>1); - hole=2; - for (i=0; i<32; i++) + hole=AI_NUMBI_CORN_SCAN_HOLE_TOLERANCE; + for (i=0; iisRessourceTakeable(rx, ry+i, CORN)||map->isRessourceTakeable(rx+1, ry+i, CORN)) h++; else if (hole--<0) break; ryb=ry+i; - hole=2; - for (i=0; i<32; i++) + hole=AI_NUMBI_CORN_SCAN_HOLE_TOLERANCE; + for (i=0; iisRessourceTakeable(rx, ry-i, CORN)||map->isRessourceTakeable(rx+1, ry-i, CORN)) h++; else if (hole--<0) @@ -338,14 +324,14 @@ int AINumbi::estimateFood(Building *building) ry=((ryt+ryb)>>1); w=0; - hole=2; - for (i=0; i<32; i++) + hole=AI_NUMBI_CORN_SCAN_HOLE_TOLERANCE; + for (i=0; iisRessourceTakeable(rx+i, ry, CORN)||map->isRessourceTakeable(rx+i, ry+1, CORN)) w++; else if (hole--<0) break; - hole=2; - for (i=0; i<32; i++) + hole=AI_NUMBI_CORN_SCAN_HOLE_TOLERANCE; + for (i=0; iisRessourceTakeable(rx-i, ry, CORN)||map->isRessourceTakeable(rx-i, ry+1, CORN)) w++; else if (hole--<0) @@ -387,7 +373,7 @@ int AINumbi::countUnits(const int medicalState) return 0; } -boost::shared_ptrAINumbi::swarmsForWorkers(const int minSwarmNumbers, const int nbWorkersFator, const int workers, const int explorers, const int warriors) +std::shared_ptrAINumbi::swarmsForWorkers(const int minSwarmNumbers, const int nbWorkersFator, const int workers, const int explorers, const int warriors) { std::list swarms=team->swarms; int ss=swarms.size(); @@ -411,16 +397,15 @@ boost::shared_ptrAINumbi::swarmsForWorkers(const int minSwarmNumbers, con int f=estimateFood(b); int numberRequestedTemp=numberRequested; int numberRequestedLoca=b->maxUnitWorking; - if (f<(nbu*3-1)) + if (f<(nbu*AI_NUMBI_LOW_FOOD_PER_UNIT-1)) numberRequestedTemp=0; else if (numberRequestedLoca==0) - if (f<(nbu*5+1)) + if (f<(nbu*AI_NUMBI_HIGH_FOOD_PER_UNIT+1)) numberRequestedTemp=0; if (numberRequestedLoca!=numberRequestedTemp) { //printf("AI: (%d) numberRequested changed to (nrt=%d) (nrl=%d)(f=%d) (nbu=%d).\n", b->UID, numberRequestedTemp, numberRequestedLoca, f, nbu); - b->maxUnitWorkingLocal=numberRequestedTemp; return shared_ptr(new OrderModifyBuilding(b->gid, numberRequestedTemp)); } } @@ -465,10 +450,16 @@ void AINumbi::nextMainBuilding(const int buildingType) { //printf("AI: nextMainBuilding uid=%d\n", b->UID); int id=Building::GIDtoID(b->gid); + // [POSSIBLE BUG H1] The mask AI_NUMBI_BUILDING_INDEX_MASK (=0xFF, i.e. 255) + // is hardcoded but the loop bound is Building::MAX_COUNT (=1024). When + // (i+id) exceeds 255 the index wraps within the first 256 building slots, + // missing buildings 256..1023. The constant intentionally does NOT alias + // `Building::MAX_COUNT - 1` — renaming would change behavior. Preserved + // verbatim; flagged for fix-time review (do not "fix" here). for (int i=1; itype->shortTypeNum==buildingType)||(myBuildings[(i+id)&0xFF]->type->shortTypeNum==0))*/) + if ((myBuildings[(i+id)&AI_NUMBI_BUILDING_INDEX_MASK])/*&&((myBuildings[(i+id)&AI_NUMBI_BUILDING_INDEX_MASK]->type->shortTypeNum==buildingType)||(myBuildings[(i+id)&AI_NUMBI_BUILDING_INDEX_MASK]->type->shortTypeNum==0))*/) { - b=myBuildings[(i+id)&0xFF]; + b=myBuildings[(i+id)&AI_NUMBI_BUILDING_INDEX_MASK]; break; } mainBuilding[buildingType]=Building::GIDtoID(b->gid); @@ -482,23 +473,23 @@ int AINumbi::nbFreeAround(const int buildingType, int posX, int posY, int width, int py=posY+map->getH(); int x, y; - int valid=256+96; + int valid=AI_NUMBI_PLACEMENT_SCORE_INIT; int r; - for (r=2; r<=3; r++) + for (r=AI_NUMBI_OUTER_MARGIN_R_MIN; r<=AI_NUMBI_OUTER_MARGIN_R_MAX; r++) { y=py-r; int ew=1; for (x=px-ew; xisFreeForBuilding(x, y)) { - valid-=4+(r-2)*4; + valid-=AI_NUMBI_OUTER_EDGE_PENALTY+(r-AI_NUMBI_OUTER_MARGIN_R_MIN)*AI_NUMBI_OUTER_EDGE_PENALTY; break; } y=py+height-1+r; for (x=px-ew; xisFreeForBuilding(x, y)) { - valid-=4+(r-2)*4; + valid-=AI_NUMBI_OUTER_EDGE_PENALTY+(r-AI_NUMBI_OUTER_MARGIN_R_MIN)*AI_NUMBI_OUTER_EDGE_PENALTY; break; } @@ -506,14 +497,14 @@ int AINumbi::nbFreeAround(const int buildingType, int posX, int posY, int width, for (y=py-ew; yisFreeForBuilding(x, y)) { - valid-=4+(r-2)*4; + valid-=AI_NUMBI_OUTER_EDGE_PENALTY+(r-AI_NUMBI_OUTER_MARGIN_R_MIN)*AI_NUMBI_OUTER_EDGE_PENALTY; break; } x=px+width-1+r; for (y=py-ew; yisFreeForBuilding(x, y)) { - valid-=4+(r-2)*4; + valid-=AI_NUMBI_OUTER_EDGE_PENALTY+(r-AI_NUMBI_OUTER_MARGIN_R_MIN)*AI_NUMBI_OUTER_EDGE_PENALTY; break; } } @@ -523,14 +514,14 @@ int AINumbi::nbFreeAround(const int buildingType, int posX, int posY, int width, for (x=px; xisFreeForBuilding(x, y)) { - valid-=12; + valid-=AI_NUMBI_INNER_EDGE_PENALTY; break; } y=py+height-1+r; for (x=px; xisFreeForBuilding(x, y)) { - valid-=12; + valid-=AI_NUMBI_INNER_EDGE_PENALTY; break; } @@ -538,19 +529,19 @@ int AINumbi::nbFreeAround(const int buildingType, int posX, int posY, int width, for (y=py; yisFreeForBuilding(x, y)) { - valid-=12; + valid-=AI_NUMBI_INNER_EDGE_PENALTY; break; } x=px+width-1+r; for (y=py; yisFreeForBuilding(x, y)) { - valid-=12; + valid-=AI_NUMBI_INNER_EDGE_PENALTY; break; } } - for (r=1; r<=8; r++) + for (r=1; r<=AI_NUMBI_FREE_REGION_SCAN_RANGE; r++) { y=py-r; bool anyBuild=false; @@ -564,7 +555,7 @@ int AINumbi::nbFreeAround(const int buildingType, int posX, int posY, int width, break; } int wu=r; - for (r=1; r<=8; r++) + for (r=1; r<=AI_NUMBI_FREE_REGION_SCAN_RANGE; r++) { y=py+height-1+r; bool anyBuild=false; @@ -578,7 +569,7 @@ int AINumbi::nbFreeAround(const int buildingType, int posX, int posY, int width, break; } wu+=r; - for (r=1; r<=8; r++) + for (r=1; r<=AI_NUMBI_FREE_REGION_SCAN_RANGE; r++) { bool anyBuild=false; x=px-r; @@ -592,7 +583,7 @@ int AINumbi::nbFreeAround(const int buildingType, int posX, int posY, int width, break; } int hu=r; - for (r=1; r<=8; r++) + for (r=1; r<=AI_NUMBI_FREE_REGION_SCAN_RANGE; r++) { bool anyBuild=false; x=px+width-1+r; @@ -679,13 +670,16 @@ bool AINumbi::findNewEmplacement(const int buildingType, int *posX, int *posY) int valid=nbFreeAround(buildingType, b->posX, b->posY, width, height); //printf("AI: findNewEmplacement(%d) valid=(%d), uid=(%d), s=(%d, %d).\n", buildingType, valid, b->UID, width, height); - if (valid>299) + if (valid>AI_NUMBI_PLACEMENT_SCORE_MIN) { - int maxr; + // [POSSIBLE BUG L9] `maxr` is computed below but never read — the spiral + // scan further down uses AI_NUMBI_SCAN_ITERATIONS (=4096) directly. + // Preserved verbatim for replay determinism; do not "fix". + [[maybe_unused]] int maxr; if (b->type->shortTypeNum==0) - maxr=64; + maxr=AI_NUMBI_SWARM_SEARCH_RADIUS; else - maxr=16; + maxr=AI_NUMBI_NONSWARM_SEARCH_RADIUS; //for (int r=0; r<=maxr; r++) // for (int d=0; d<8; d++) @@ -694,7 +688,7 @@ bool AINumbi::findNewEmplacement(const int buildingType, int *posX, int *posY) if (b->type->shortTypeNum) margin=0; else - margin=2; + margin=AI_NUMBI_SWARM_MARGIN; int bposX=b->posX+map->getW(); int bposY=b->posY+map->getH(); @@ -714,7 +708,9 @@ bool AINumbi::findNewEmplacement(const int buildingType, int *posX, int *posY) dy=0; int bestValid=-1; - for (int i=0; i<4096; i++) + // Note: AI_NUMBI_SCAN_ITERATIONS is intentionally NOT derived from `maxr` + // above (see L9 comment); it is the original literal preserved as-is. + for (int i=0; iisFreeForBuilding(px, py, width, height)) { int valid=nbFreeAround(buildingType, px, py, width, height); - if ((valid>299)&&(game->checkRoomForBuilding(px, py, bt, player->team->teamNumber))) + if ((valid>AI_NUMBI_PLACEMENT_SCORE_MIN)&&(game->checkRoomForBuilding(px, py, bt, player->team->teamNumber))) { int rx, ry, dist; bool nr=map->ressourceAvailableUpdate(team->teamNumber, CORN, 0, px, py, &rx, &ry, &dist); if (nr) { //int dist=map->warpDistSquare(px+1, py+1, rx, ry); - if (((dist<=(64+width*height))&&(buildingType<=1))||((dist>=(64+width*height))&&(buildingType>1))) + if (((dist<=(AI_NUMBI_CORN_DISTANCE_BIAS+width*height))&&(buildingType<=AI_NUMBI_NEAR_CORN_TYPE_CUTOFF))||((dist>=(AI_NUMBI_CORN_DISTANCE_BIAS+width*height))&&(buildingType>AI_NUMBI_NEAR_CORN_TYPE_CUTOFF))) { //printf("AI: findNewEmplacement d=%d valid=%d.\n", d, valid); if (valid>bestValid) @@ -747,7 +743,7 @@ bool AINumbi::findNewEmplacement(const int buildingType, int *posX, int *posY) } } } - else if (buildingType!=1) + else if (buildingType!=AI_NUMBI_NEAR_CORN_TYPE_CUTOFF) { //printf("AI: findNewEmplacement d=%d valid=%d.\n", d, valid); if (valid>bestValid) @@ -771,7 +767,7 @@ bool AINumbi::findNewEmplacement(const int buildingType, int *posX, int *posY) return false; } -boost::shared_ptrAINumbi::mayAttack(int critticalMass, int critticalTimeout, Sint32 numberRequested) +std::shared_ptrAINumbi::mayAttack(int critticalMass, int critticalTimeout, Sint32 numberRequested) { Unit **myUnits=team->myUnits; int ft=0; @@ -797,7 +793,7 @@ boost::shared_ptrAINumbi::mayAttack(int critticalMass, int critticalTimeo } else if (attackPhase==1) { - if (ft<=(critticalMass/2)) + if (ft<=(critticalMass/AI_NUMBI_STOP_ATTACK_DIVISOR)) { attackPhase=3; //printf("AI:stop attack.\n"); @@ -841,7 +837,7 @@ boost::shared_ptrAINumbi::mayAttack(int critticalMass, int critticalTimeo ex=b->posX; ey=b->posY; - if ((syncRand()&0x1F)==0) + if ((syncRand()&AI_NUMBI_ENEMY_FLAG_CHANCE_MASK)==0) { bool already=false; count=0; @@ -864,11 +860,11 @@ boost::shared_ptrAINumbi::mayAttack(int critticalMass, int critticalTimeo } } - if (ex!=-1 && ey!=-1 && found && count<5) + if (ex!=-1 && ey!=-1 && found && countbuildingsTypes.getTypeNum("warflag", 0, false); //printf("AI: OrderCreateWarFlag(%d, %d)\n", ex, ey); - return shared_ptr(new OrderCreate(teamNumber, ex, ey, typeNum, 1, 1)); + return shared_ptr(new OrderCreate(teamNumber, ex, ey, typeNum, AI_NUMBI_WAR_FLAG_INIT_UNITS_WORKING, AI_NUMBI_WAR_FLAG_INIT_FLAG_RADIUS)); } else return shared_ptr(new NullOrder); @@ -884,8 +880,8 @@ boost::shared_ptrAINumbi::mayAttack(int critticalMass, int critticalTimeo if ((*bit)->type->shortTypeNum==IntBuildingType::WAR_FLAG) return shared_ptr(new OrderDelete((*bit)->gid)); attackPhase=0; - critticalWarriors*=2; - critticalTime*=2; + critticalWarriors*=AI_NUMBI_ATTACK_BACKOFF_MULTIPLIER; + critticalTime*=AI_NUMBI_ATTACK_BACKOFF_MULTIPLIER; return shared_ptr(new NullOrder); } else @@ -896,7 +892,7 @@ boost::shared_ptrAINumbi::mayAttack(int critticalMass, int critticalTimeo } -boost::shared_ptrAINumbi::adjustBuildings(const int numbers, const int numbersInc, const int workers, const int buildingType) +std::shared_ptrAINumbi::adjustBuildings(const int numbers, const int numbersInc, const int workers, const int buildingType) { Building **myBuildings=team->myBuildings; //Unit **myUnits=player->team->myUnits; @@ -917,9 +913,9 @@ boost::shared_ptrAINumbi::adjustBuildings(const int numbers, const int nu int wr=countUnits(); if (buildingType==IntBuildingType::FOOD_BUILDING) - wr+=2*countUnits(Unit::MED_HUNGRY); + wr+=AI_NUMBI_HUNGRY_INN_DEMAND_MULT*countUnits(Unit::MED_HUNGRY); else if (buildingType==IntBuildingType::HEAL_BUILDING) - wr+=4*countUnits(Unit::MED_DAMAGED); + wr+=AI_NUMBI_DAMAGED_HEAL_DEMAND_MULT*countUnits(Unit::MED_DAMAGED); if (fb<((wr/numbers)+numbersInc)) { @@ -929,7 +925,7 @@ boost::shared_ptrAINumbi::adjustBuildings(const int numbers, const int nu { Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum(IntBuildingType::typeFromShortNumber(buildingType), 0, true); int teamNumber=team->teamNumber; - return shared_ptr(new OrderCreate(teamNumber, x, y, typeNum, 1, 1)); + return shared_ptr(new OrderCreate(teamNumber, x, y, typeNum, AI_NUMBI_BUILD_ORDER_UNITS_WORKING, AI_NUMBI_BUILD_ORDER_FLAG_RADIUS)); } //printf("AI: findNewEmplacement(%d) failed.\n", buildingType); return shared_ptr(new NullOrder); @@ -938,7 +934,7 @@ boost::shared_ptrAINumbi::adjustBuildings(const int numbers, const int nu return shared_ptr(new NullOrder); } -boost::shared_ptrAINumbi::checkoutExpands(const int numbers, const int workers) +std::shared_ptrAINumbi::checkoutExpands(const int numbers, const int workers) { //std::list swarms=team->swarms; //int ss=swarms.size(); @@ -962,7 +958,7 @@ boost::shared_ptrAINumbi::checkoutExpands(const int numbers, const int wo { Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum("swarm", 0, true); int teamNumber=team->teamNumber; - return shared_ptr(new OrderCreate(teamNumber, x, y, typeNum, 1, 1)); + return shared_ptr(new OrderCreate(teamNumber, x, y, typeNum, AI_NUMBI_BUILD_ORDER_UNITS_WORKING, AI_NUMBI_BUILD_ORDER_FLAG_RADIUS)); } return shared_ptr(new NullOrder); } @@ -970,189 +966,167 @@ boost::shared_ptrAINumbi::checkoutExpands(const int numbers, const int wo return shared_ptr(new NullOrder); } -boost::shared_ptrAINumbi::mayUpgrade(const int ptrigger, const int ntrigger) +namespace { + +// The five building kinds AINumbi considers for level upgrades. Iteration +// order is the upgrade-priority order — food first, defense last — and is +// part of the deterministic order stream; do not reorder without rebaselining. +enum UpgradeKind { - Building **myBuildings=team->myBuildings; - int numberFood[4]={0, 0, 0, 0}; // number of food buildings - int numberUpgradingFood[4]={0, 0, 0, 0}; // number of upgrading food buildings - Building *foodBuilding[4]={0, 0, 0, 0}; - - int numberHealth[4]={0, 0, 0, 0}; // number of food buildings - int numberUpgradingHealth[4]={0, 0, 0, 0}; // number of upgrading food buildings - Building *healthBuilding[4]={0, 0, 0, 0}; - - int numberAttack[4]={0, 0, 0, 0}; // number of food buildings - int numberUpgradingAttack[4]={0, 0, 0, 0}; // number of upgrading food buildings - Building *attackBuilding[4]={0, 0, 0, 0}; - - int numberScience[4]={0, 0, 0, 0}; // number of Science buildings - int numberUpgradingScience[4]={0, 0, 0, 0}; // number of upgrading Science buildings - Building *scienceBuilding[4]={0, 0, 0, 0}; - - int numberDefense[4]={0, 0, 0, 0}; // number of Defense buildings - int numberUpgradingDefense[4]={0, 0, 0, 0}; // number of upgrading Science buildings - Building *defenseBuilding[4]={0, 0, 0, 0}; - - for (int i=0; itype; - int l=bt->level; - if (bt->shortTypeNum==IntBuildingType::FOOD_BUILDING) - { - if (bt->isBuildingSite) - numberUpgradingFood[l]++; - else - { - numberFood[l]++; - if (syncRand()&1) - foodBuilding[l]=b; - } - } - else if (bt->shortTypeNum==IntBuildingType::HEAL_BUILDING) - { - if (bt->isBuildingSite) - numberUpgradingHealth[l]++; - else - { - numberHealth[l]++; - if (syncRand()&1) - healthBuilding[l]=b; - } - } - else if (bt->shortTypeNum==IntBuildingType::ATTACK_BUILDING) - { - if (bt->isBuildingSite) - numberUpgradingAttack[l]++; - else - { - numberAttack[l]++; - if (syncRand()&1) - attackBuilding[l]=b; - } - } - else if (bt->shortTypeNum==IntBuildingType::SCIENCE_BUILDING) - { - if (bt->isBuildingSite) - numberUpgradingScience[l]++; - else - { - numberScience[l]++; - if (syncRand()&1) - scienceBuilding[l]=b; - } - } - else if (bt->shortTypeNum==IntBuildingType::DEFENSE_BUILDING) - { - if (bt->isBuildingSite) - numberUpgradingDefense[l]++; - else - { - numberDefense[l]++; - if (syncRand()&1) - defenseBuilding[l]=b; - } - } - } + case IntBuildingType::FOOD_BUILDING: return UK_FOOD; + case IntBuildingType::HEAL_BUILDING: return UK_HEAL; + case IntBuildingType::ATTACK_BUILDING: return UK_ATTACK; + case IntBuildingType::SCIENCE_BUILDING: return UK_SCIENCE; + case IntBuildingType::DEFENSE_BUILDING: return UK_DEFENSE; + default: return -1; } - - Unit **myUnits=team->myUnits; - int wun[4]={0, 0, 0, 0};//working units - int fun[4]={0, 0, 0, 0};//free units +} + +// Walks every building owned by `team` and tallies, per (kind, level): the +// count of completed buildings, the count of upgrading sites, and one +// "exemplar" — a deterministically chosen building used as the target for +// the next upgrade order. The exemplar is selected by an unbiased syncRand +// coin flip on each completed building, so for k buildings at one (kind, +// level) the last one wins with probability 1/2, the previous with 1/4, +// etc. syncRand() is the lockstep RNG, so the result is identical across +// networked clients. +std::array collectUpgradeInventory(Team *team) +{ + std::array inv{}; + Building **myBuildings = team->myBuildings; + for (int i = 0; i < Building::MAX_COUNT; i++) { - for (int i=0; itype->shortTypeNum); + if (kind < 0) + continue; + const int l = b->type->level; + if (b->type->isBuildingSite) + inv[kind].upgrading[l]++; + else { - Unit *u=myUnits[i]; - if (u) - { - int l=u->level[BUILD]; - if (u->activity==Unit::ACT_RANDOM) - fun[l]++; - wun[l]++; - } + inv[kind].number[l]++; + if (syncRand() & 1) + inv[kind].exemplar[l] = b; } } - - //printf("sbu=(%d, %d, %d, %d) wun=(%d, %d, %d, %d)\n", sbu[0], sbu[1], sbu[2], sbu[3], wun[0], wun[1], wun[2], wun[3]); - - // We calculate if we may upgrade to level 1: - int potential=wun[1]+wun[2]+wun[3]+4*(numberScience[0]+numberScience[1]+numberScience[2]+numberScience[3]); - int now=fun[1]+fun[2]+fun[3]; - //printf("potential=(%d/%d), now=(%d/%d).\n", potential, ptrigger, now, ntrigger); - if ((potential>ptrigger)&&(now>ntrigger)) + return inv; +} + +// Tries one ladder rung: for each upgradeable kind in priority order, +// checks whether the colony has more completed level-srcLevel buildings +// than are currently being upgraded to level srcLevel+1 (plus the per-kind +// tolerance). Returns an OrderConstruction targeting the first eligible +// kind's exemplar at srcLevel, or nullptr if none. +// +// Pre BH-220, the C++ original passed exemplar[0] for both rungs (level +// 0→1 and 1→2), so the level-1→2 path always re-issued level-0→1 upgrades +// and AINumbi's tech tree stalled at level 1. This helper reads +// exemplar[srcLevel] uniformly, fixing that behavior. +std::shared_ptr tryUpgradeRung( + const std::array &inv, + int srcLevel) +{ + for (int kind = 0; kind < NB_UPGRADE_KINDS; ++kind) { - if (numberFood[0]>numberUpgradingFood[1]) - { - Building *b=foodBuilding[0]; - if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - if (numberHealth[0]>numberUpgradingHealth[1]) - { - Building *b=healthBuilding[0]; - if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - if (numberAttack[0]>numberUpgradingAttack[1]) + const UpgradeInventory &slot = inv[kind]; + if (slot.number[srcLevel] > slot.upgrading[srcLevel + 1] + kUpgradeKindTolerance[kind]) { - Building *b=attackBuilding[0]; + Building *b = slot.exemplar[srcLevel]; if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - if (numberScience[0]>numberUpgradingScience[1]+1) - { - Building *b=scienceBuilding[0]; - if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - if (numberDefense[0]>numberUpgradingDefense[1]) - { - Building *b=defenseBuilding[0]; - if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); + return std::make_shared(b->gid, AI_NUMBI_UPGRADE_ORDER_LEVEL, AI_NUMBI_UPGRADE_ORDER_REPAIR); } } - - // We calculate if we may upgrade to leverl 2: - potential=wun[2]+wun[3]+4*(numberScience[1]+numberScience[2]+numberScience[3]); - now=fun[2]+fun[3]; - if ((potential>ptrigger)&&(now>ntrigger)) + return nullptr; +} + +} // namespace + +// Issues one building-upgrade order if (a) the colony has enough free or +// schooled units to staff higher-level buildings — gated against ptrigger +// (potential = working units at higher levels, weighted by SCIENCE stock) +// and ntrigger (now = free units at higher levels) — and (b) there is a +// completed building of an upgradeable kind that is not already saturated +// with in-flight upgrades. Tries level 0→1 first, then 1→2; returns +// NullOrder if neither rung is eligible. +std::shared_ptr AINumbi::mayUpgrade(const int ptrigger, const int ntrigger) +{ + const auto inv = collectUpgradeInventory(team); + + Unit **myUnits = team->myUnits; + int wun[NB_UNIT_LEVELS] = {}; // working units per BUILD level + int fun[NB_UNIT_LEVELS] = {}; // free (ACT_RANDOM) units per BUILD level + for (int i = 0; i < Unit::MAX_COUNT; i++) { - if (numberFood[1]>numberUpgradingFood[2]) - { - Building *b=foodBuilding[0]; - if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - if (numberHealth[1]>numberUpgradingHealth[2]) - { - Building *b=healthBuilding[0]; - if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - if (numberAttack[1]>numberUpgradingAttack[2]) - { - Building *b=attackBuilding[0]; - if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); - } - if (numberScience[1]>numberUpgradingScience[2]+1) + Unit *u = myUnits[i]; + if (!u) + continue; + const int l = u->level[BUILD]; + if (u->activity == Unit::ACT_RANDOM) + fun[l]++; + wun[l]++; + } + + const UpgradeInventory &science = inv[UK_SCIENCE]; + + // Level 0 → 1 rung. + { + const int sciencePool = science.number[0] + science.number[1] + science.number[2] + science.number[3]; + const int potential = wun[1] + wun[2] + wun[3] + AI_NUMBI_SCHOOL_POTENTIAL_WEIGHT * sciencePool; + const int now = fun[1] + fun[2] + fun[3]; + if (potential > ptrigger && now > ntrigger) { - Building *b=scienceBuilding[0]; - if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); + if (auto order = tryUpgradeRung(inv, 0)) + return order; } - if (numberDefense[1]>numberUpgradingDefense[2]) + } + + // Level 1 → 2 rung. + { + const int sciencePool = science.number[1] + science.number[2] + science.number[3]; + const int potential = wun[2] + wun[3] + AI_NUMBI_SCHOOL_POTENTIAL_WEIGHT * sciencePool; + const int now = fun[2] + fun[3]; + if (potential > ptrigger && now > ntrigger) { - Building *b=defenseBuilding[0]; - if (b) - return shared_ptr(new OrderConstruction(b->gid, 1, 1)); + if (auto order = tryUpgradeRung(inv, 1)) + return order; } } - - return shared_ptr(new NullOrder); + + return std::make_shared(); } diff --git a/src/ai/AINumbi.h b/src/ai/AINumbi.h new file mode 100644 index 000000000..b02c0d37b --- /dev/null +++ b/src/ai/AINumbi.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include "BuildingType.h" +#include "AIImplementation.h" +#include "AINumbiTuning.h" + +class Game; +class Map; +class Order; +class Player; +class Team; +class Building; + +class AINumbi : public AIImplementation +{ +public: + AINumbi(Player *player); + AINumbi(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + ~AINumbi(); + + Player *player; + Team *team; + Game *game; + Map *map; + + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + + std::shared_ptrgetOrder(void); + +private: + int timer; + int phase; + int attackPhase; + int phaseTime; + int critticalWarriors; + int critticalTime; + int attackTimer; + // [POSSIBLE BUG M7] Sized AI_NUMBI_LEGACY_NB_BUILDING (=15) for save-format + // compatibility; comment originally read "BuildingType::NB_BUILDING=15 with + // lover versions". Today IntBuildingType::NB_BUILDING is smaller, but the + // loops below still index by NB_BUILDING — preserved verbatim. + int mainBuilding[AI_NUMBI_LEGACY_NB_BUILDING]; + void init(Player *player); + int estimateFood(Building *building); + int countUnits(void); + int countUnits(const int medicalState); + std::shared_ptrswarmsForWorkers(const int minSwarmNumbers, const int nbWorkersFator, const int workers, const int explorers, const int warriors); + void nextMainBuilding(const int buildingType); + int nbFreeAround(const int buildingType, int posX, int posY, int width, int height); + bool parseBuildingType(const int buildingType); + void squareCircleScann(int &dx, int &dy, int &sx, int &sy, int &x, int &y, int &mx, int &my); + bool findNewEmplacement(const int buildingType, int *posX, int *posY); + std::shared_ptrmayAttack(int critticalMass, int critticalTimeout, Sint32 numberRequested); + std::shared_ptradjustBuildings(const int numbers, const int numbersInc, const int workers, const int buildingType); + std::shared_ptrcheckoutExpands(const int numbers, const int workers); + std::shared_ptrmayUpgrade(const int ptrigger, const int ntrigger); +}; + + + + diff --git a/src/ai/AINumbiTuning.h b/src/ai/AINumbiTuning.h new file mode 100644 index 000000000..f0450828e --- /dev/null +++ b/src/ai/AINumbiTuning.h @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// +// AINumbi tuning constants. Behavior-preserving names for previously-bare +// literals scattered across AINumbi.h / AINumbi.cpp. Each value here is the +// exact value that was hardcoded in the original source — nothing is +// reinterpreted, recomputed, or unified across phases. Cross-phase numeric +// coincidences (e.g. several phases passing the same FOOD adjustBuildings +// args) are preserved as separate per-phase names so a tuning change to one +// phase never silently moves another. +// +// All values are `static constexpr int` at file scope. The file has no +// runtime side effects and may be included from anywhere. + +#pragma once + +// ---------------------------------------------------------------------------- +// Phase / attack timer defaults (init values) +// ---------------------------------------------------------------------------- + +// Initial ticks per AI strategy phase (timer wraps and increments `phase`). +static constexpr int AI_NUMBI_PHASE_TIME_DEFAULT_TICKS = 1024; +// Initial warrior count required before launching an attack. +static constexpr int AI_NUMBI_CRITICAL_WARRIORS_DEFAULT = 20; +// Initial timeout (ticks) before an attack is forced regardless of warrior count. +static constexpr int AI_NUMBI_CRITICAL_TIME_DEFAULT_TICKS = 1024; + +// Legacy save-format compatibility: the header `mainBuilding[]` array is +// hardcoded to 15 because IntBuildingType::NB_BUILDING was 15 in older save +// versions. Today NB_BUILDING is smaller, so 15 is the upper-bound "legacy" +// dimension. [POSSIBLE BUG M7] — preserved verbatim. +static constexpr int AI_NUMBI_LEGACY_NB_BUILDING = 15; + +// Round-robin slot mask: getOrder() runs one of up to 32 sub-decisions per +// tick by picking `timer & 0x1F` as the slot index. +static constexpr int AI_NUMBI_DECISION_SLOT_MASK = 0x1F; + +// ---------------------------------------------------------------------------- +// Phase tier boundaries (compared against `phase`) +// ---------------------------------------------------------------------------- + +static constexpr int AI_NUMBI_MID_GAME_PHASE = 4; // phase < 4 +static constexpr int AI_NUMBI_LATE_MID_PHASE = 6; // phase < 6 +static constexpr int AI_NUMBI_SCIENCE_PHASE = 8; // phase < 8 +static constexpr int AI_NUMBI_DEFEND_PHASE = 10; // phase < 10 + +// ---------------------------------------------------------------------------- +// Per-phase swarmsForWorkers tuples +// (minSwarmNumbers, nbWorkersFator, workers, explorers, warriors) +// ---------------------------------------------------------------------------- + +// phase 0: rush food +static constexpr int AI_NUMBI_PHASE0_SWARM_MIN = 1; +static constexpr int AI_NUMBI_PHASE0_SWARM_FACTOR = 4; +static constexpr int AI_NUMBI_PHASE0_SWARM_WORKERS = 7; +static constexpr int AI_NUMBI_PHASE0_SWARM_EXPLORER = 1; +static constexpr int AI_NUMBI_PHASE0_SWARM_WARRIOR = 0; + +// phase 1: rush food (more workers) +static constexpr int AI_NUMBI_PHASE1_SWARM_MIN = 1; +static constexpr int AI_NUMBI_PHASE1_SWARM_FACTOR = 5; +static constexpr int AI_NUMBI_PHASE1_SWARM_WORKERS = 14; +static constexpr int AI_NUMBI_PHASE1_SWARM_EXPLORER = 0; +static constexpr int AI_NUMBI_PHASE1_SWARM_WARRIOR = 0; + +// phase 2-3: produce units, improve health/science +static constexpr int AI_NUMBI_PHASE2_SWARM_MIN = 1; +static constexpr int AI_NUMBI_PHASE2_SWARM_FACTOR = 9; +static constexpr int AI_NUMBI_PHASE2_SWARM_WORKERS = 14; +static constexpr int AI_NUMBI_PHASE2_SWARM_EXPLORER = 0; +static constexpr int AI_NUMBI_PHASE2_SWARM_WARRIOR = 0; + +// phase 4-5 +static constexpr int AI_NUMBI_PHASE4_SWARM_MIN = 1; +static constexpr int AI_NUMBI_PHASE4_SWARM_FACTOR = 9; +static constexpr int AI_NUMBI_PHASE4_SWARM_WORKERS = 14; +static constexpr int AI_NUMBI_PHASE4_SWARM_EXPLORER = 1; +static constexpr int AI_NUMBI_PHASE4_SWARM_WARRIOR = 0; + +// phase 6-7: improve science +static constexpr int AI_NUMBI_PHASE6_SWARM_MIN = 1; +static constexpr int AI_NUMBI_PHASE6_SWARM_FACTOR = 4; +static constexpr int AI_NUMBI_PHASE6_SWARM_WORKERS = 14; +static constexpr int AI_NUMBI_PHASE6_SWARM_EXPLORER = 0; +static constexpr int AI_NUMBI_PHASE6_SWARM_WARRIOR = 0; + +// phase 8-9: produce good units, defend +static constexpr int AI_NUMBI_PHASE8_SWARM_MIN = 1; +static constexpr int AI_NUMBI_PHASE8_SWARM_FACTOR = 9; +static constexpr int AI_NUMBI_PHASE8_SWARM_WORKERS = 14; +static constexpr int AI_NUMBI_PHASE8_SWARM_EXPLORER = 1; +static constexpr int AI_NUMBI_PHASE8_SWARM_WARRIOR = 1; + +// phase 10+: produce warriors +static constexpr int AI_NUMBI_PHASE10_SWARM_MIN = 1; +static constexpr int AI_NUMBI_PHASE10_SWARM_FACTOR = 10; +static constexpr int AI_NUMBI_PHASE10_SWARM_WORKERS = 3; +static constexpr int AI_NUMBI_PHASE10_SWARM_EXPLORER = 1; +static constexpr int AI_NUMBI_PHASE10_SWARM_WARRIOR = 14; + +// ---------------------------------------------------------------------------- +// Per-phase adjustBuildings tuples (numbers, numbersInc, workers) +// Each phase has its own tuple even when the values match, so a future tuning +// change to one phase doesn't silently move another. +// ---------------------------------------------------------------------------- + +// phase 0 +static constexpr int AI_NUMBI_PHASE0_INN_NUMBERS = 4; +static constexpr int AI_NUMBI_PHASE0_INN_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE0_INN_WORKERS = 3; + +// phase 1 +static constexpr int AI_NUMBI_PHASE1_INN_NUMBERS = 4; +static constexpr int AI_NUMBI_PHASE1_INN_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE1_INN_WORKERS = 3; + +// phase 2-3 +static constexpr int AI_NUMBI_PHASE2_INN_NUMBERS = 4; +static constexpr int AI_NUMBI_PHASE2_INN_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE2_INN_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE2_HEAL_NUMBERS = 44; +static constexpr int AI_NUMBI_PHASE2_HEAL_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE2_HEAL_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE2_SCIENCE_NUMBERS = 40; +static constexpr int AI_NUMBI_PHASE2_SCIENCE_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE2_SCIENCE_WORKERS = 2; +static constexpr int AI_NUMBI_PHASE2_RACETRACK_NUMBERS = 70; +static constexpr int AI_NUMBI_PHASE2_RACETRACK_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE2_RACETRACK_WORKERS = 0; +static constexpr int AI_NUMBI_PHASE2_BARRACKS_NUMBERS = 70; +static constexpr int AI_NUMBI_PHASE2_BARRACKS_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE2_BARRACKS_WORKERS = 0; +static constexpr int AI_NUMBI_PHASE2_DEFENSE_NUMBERS = 25; +static constexpr int AI_NUMBI_PHASE2_DEFENSE_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE2_DEFENSE_WORKERS = 1; + +// phase 4-5 +static constexpr int AI_NUMBI_PHASE4_INN_NUMBERS = 5; +static constexpr int AI_NUMBI_PHASE4_INN_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE4_INN_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE4_HEAL_NUMBERS = 37; +static constexpr int AI_NUMBI_PHASE4_HEAL_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE4_HEAL_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE4_SCIENCE_NUMBERS = 32; +static constexpr int AI_NUMBI_PHASE4_SCIENCE_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE4_SCIENCE_WORKERS = 2; +static constexpr int AI_NUMBI_PHASE4_DEFENSE_NUMBERS = 25; +static constexpr int AI_NUMBI_PHASE4_DEFENSE_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE4_DEFENSE_WORKERS = 1; +// mayUpgrade(ptrigger=16, ntrigger=8) +static constexpr int AI_NUMBI_PHASE4_UPGRADE_PTRIGGER = 16; +static constexpr int AI_NUMBI_PHASE4_UPGRADE_NTRIGGER = 8; + +// phase 6-7 +static constexpr int AI_NUMBI_PHASE6_INN_NUMBERS = 5; +static constexpr int AI_NUMBI_PHASE6_INN_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE6_INN_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE6_HEAL_NUMBERS = 34; +static constexpr int AI_NUMBI_PHASE6_HEAL_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE6_HEAL_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE6_SCIENCE_NUMBERS = 32; +static constexpr int AI_NUMBI_PHASE6_SCIENCE_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE6_SCIENCE_WORKERS = 4; +// mayUpgrade(ptrigger=16, ntrigger=4) +static constexpr int AI_NUMBI_PHASE6_UPGRADE_PTRIGGER = 16; +static constexpr int AI_NUMBI_PHASE6_UPGRADE_NTRIGGER = 4; + +// phase 8-9 +static constexpr int AI_NUMBI_PHASE8_INN_NUMBERS = 5; +static constexpr int AI_NUMBI_PHASE8_INN_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE8_INN_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE8_HEAL_NUMBERS = 32; +static constexpr int AI_NUMBI_PHASE8_HEAL_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE8_HEAL_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE8_SCIENCE_NUMBERS = 40; +static constexpr int AI_NUMBI_PHASE8_SCIENCE_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE8_SCIENCE_WORKERS = 3; +static constexpr int AI_NUMBI_PHASE8_RACETRACK_NUMBERS = 70; +static constexpr int AI_NUMBI_PHASE8_RACETRACK_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE8_RACETRACK_WORKERS = 5; +static constexpr int AI_NUMBI_PHASE8_DEFENSE_NUMBERS = 20; +static constexpr int AI_NUMBI_PHASE8_DEFENSE_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE8_DEFENSE_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE8_BARRACKS_NUMBERS = 70; +static constexpr int AI_NUMBI_PHASE8_BARRACKS_NUMBERS_INC = 1; +static constexpr int AI_NUMBI_PHASE8_BARRACKS_WORKERS = 3; +// checkoutExpands(numbers=80, workers=5) +static constexpr int AI_NUMBI_PHASE8_EXPAND_NUMBERS = 80; +static constexpr int AI_NUMBI_PHASE8_EXPAND_WORKERS = 5; +// mayUpgrade(ptrigger=16, ntrigger=4) +static constexpr int AI_NUMBI_PHASE8_UPGRADE_PTRIGGER = 16; +static constexpr int AI_NUMBI_PHASE8_UPGRADE_NTRIGGER = 4; + +// phase 10+ +static constexpr int AI_NUMBI_PHASE10_INN_NUMBERS = 6; +static constexpr int AI_NUMBI_PHASE10_INN_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE10_INN_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE10_HEAL_NUMBERS = 37; +static constexpr int AI_NUMBI_PHASE10_HEAL_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE10_HEAL_WORKERS = 1; +static constexpr int AI_NUMBI_PHASE10_SCIENCE_NUMBERS = 38; +static constexpr int AI_NUMBI_PHASE10_SCIENCE_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE10_SCIENCE_WORKERS = 2; +static constexpr int AI_NUMBI_PHASE10_RACETRACK_NUMBERS = 70; +static constexpr int AI_NUMBI_PHASE10_RACETRACK_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE10_RACETRACK_WORKERS = 5; +static constexpr int AI_NUMBI_PHASE10_DEFENSE_NUMBERS = 20; +static constexpr int AI_NUMBI_PHASE10_DEFENSE_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE10_DEFENSE_WORKERS = 2; +static constexpr int AI_NUMBI_PHASE10_BARRACKS_NUMBERS = 70; +static constexpr int AI_NUMBI_PHASE10_BARRACKS_NUMBERS_INC = 2; +static constexpr int AI_NUMBI_PHASE10_BARRACKS_WORKERS = 3; +// mayAttack numberRequested arg (warriors per war flag). +static constexpr int AI_NUMBI_WAR_FLAG_UNITS = 10; +// checkoutExpands(numbers=40, workers=5) +static constexpr int AI_NUMBI_PHASE10_EXPAND_NUMBERS = 40; +static constexpr int AI_NUMBI_PHASE10_EXPAND_WORKERS = 5; +// mayUpgrade(ptrigger=16, ntrigger=4) +static constexpr int AI_NUMBI_PHASE10_UPGRADE_PTRIGGER = 16; +static constexpr int AI_NUMBI_PHASE10_UPGRADE_NTRIGGER = 4; + +// ---------------------------------------------------------------------------- +// Corn scan (estimateFood) +// ---------------------------------------------------------------------------- + +// Tolerated gap-cells when scanning a CORN patch row/column for size. +static constexpr int AI_NUMBI_CORN_SCAN_HOLE_TOLERANCE = 2; +// Maximum scan radius along each direction when measuring a CORN patch. +static constexpr int AI_NUMBI_CORN_SCAN_MAX_RADIUS = 32; + +// ---------------------------------------------------------------------------- +// Food-per-unit thresholds (swarmsForWorkers) +// ---------------------------------------------------------------------------- + +// If estimated food < nbu*LOW - 1, the swarm is starved → numberRequested=0. +static constexpr int AI_NUMBI_LOW_FOOD_PER_UNIT = 3; +// If estimated food < nbu*HIGH + 1 (and currently 0 workers), keep at 0. +static constexpr int AI_NUMBI_HIGH_FOOD_PER_UNIT = 5; + +// ---------------------------------------------------------------------------- +// nextMainBuilding: building-index round-robin mask +// ---------------------------------------------------------------------------- + +// Mask applied to (i+id) when scanning team->myBuildings[]. [POSSIBLE BUG H1]: +// the mask is 0xFF (256) but Building::MAX_COUNT is 1024; preserved verbatim. +static constexpr int AI_NUMBI_BUILDING_INDEX_MASK = 0xFF; + +// ---------------------------------------------------------------------------- +// nbFreeAround: placement-score knobs +// ---------------------------------------------------------------------------- + +// Initial score before subtracting penalties. +static constexpr int AI_NUMBI_PLACEMENT_SCORE_INIT = 256 + 96; +// Outer-margin scan radius range (inclusive). +static constexpr int AI_NUMBI_OUTER_MARGIN_R_MIN = 2; +static constexpr int AI_NUMBI_OUTER_MARGIN_R_MAX = 3; +// Outer-edge penalty base (multiplied by (r-2)*4 + this base). +static constexpr int AI_NUMBI_OUTER_EDGE_PENALTY = 4; +// Inner-edge penalty (single-tile margin block). +static constexpr int AI_NUMBI_INNER_EDGE_PENALTY = 12; +// Free-region scan range (max ring distance to look for clear space). +static constexpr int AI_NUMBI_FREE_REGION_SCAN_RANGE = 8; + +// ---------------------------------------------------------------------------- +// findNewEmplacement: scan + scoring + corn proximity +// ---------------------------------------------------------------------------- + +// Minimum acceptable placement score (used for both pre-check and per-cell test). +static constexpr int AI_NUMBI_PLACEMENT_SCORE_MIN = 299; + +// Search radius for swarm placement. [POSSIBLE BUG L9]: `maxr` is computed +// from this but never read — the spiral scan below uses a literal iteration +// count instead. Preserve both values; do not "fix". +static constexpr int AI_NUMBI_SWARM_SEARCH_RADIUS = 64; +// Search radius for non-swarm placement. (Same dead-computation note.) +static constexpr int AI_NUMBI_NONSWARM_SEARCH_RADIUS = 16; + +// Padding around a swarm building when looking for a placement. +static constexpr int AI_NUMBI_SWARM_MARGIN = 2; + +// Square-spiral scan iteration count. NOT derived from the search-radius +// constants above; used as a literal in the original code. +static constexpr int AI_NUMBI_SCAN_ITERATIONS = 4096; + +// Distance bias added to width*height when checking corn proximity. +static constexpr int AI_NUMBI_CORN_DISTANCE_BIAS = 64; +// Building-type cutoff for "must be near corn" heuristic. +// (FOOD_BUILDING and SWARM_BUILDING short-type-num indices fall in [0..1].) +static constexpr int AI_NUMBI_NEAR_CORN_TYPE_CUTOFF = 1; + +// ---------------------------------------------------------------------------- +// mayAttack +// ---------------------------------------------------------------------------- + +// Stop-attack threshold divisor: `ft <= critticalMass / DIVISOR` ends attack. +static constexpr int AI_NUMBI_STOP_ATTACK_DIVISOR = 2; +// 1-in-32 chance per enemy-building scan to drop a war flag. +static constexpr int AI_NUMBI_ENEMY_FLAG_CHANCE_MASK = 0x1F; +// Maximum simultaneous war flags during an attack. +static constexpr int AI_NUMBI_MAX_WAR_FLAGS = 5; +// OrderCreate war-flag init: unitsInside / unitsWorking flags (=1, =1). +static constexpr int AI_NUMBI_WAR_FLAG_INIT_UNITS_WORKING = 1; +static constexpr int AI_NUMBI_WAR_FLAG_INIT_FLAG_RADIUS = 1; +// Exponential backoff multiplier applied to critticalWarriors and critticalTime +// after a stop-attack. +static constexpr int AI_NUMBI_ATTACK_BACKOFF_MULTIPLIER = 2; + +// ---------------------------------------------------------------------------- +// adjustBuildings demand multipliers +// ---------------------------------------------------------------------------- + +// Hungry-unit pressure multiplier on inn count. +static constexpr int AI_NUMBI_HUNGRY_INN_DEMAND_MULT = 2; +// Damaged-unit pressure multiplier on hospital count. +static constexpr int AI_NUMBI_DAMAGED_HEAL_DEMAND_MULT = 4; + +// ---------------------------------------------------------------------------- +// mayUpgrade tuning +// ---------------------------------------------------------------------------- + +// School-count weighting in the "upgrade potential" formula: +// potential = wun[L+1..3] + WEIGHT * sum(numberScience[L..3]) +static constexpr int AI_NUMBI_SCHOOL_POTENTIAL_WEIGHT = 4; +// Allow one extra upgrading school slot before throttling (rounding tolerance). +static constexpr int AI_NUMBI_SCIENCE_UPGRADE_TOLERANCE = 1; +// OrderConstruction(b->gid, level=1, repair=1) target args. +static constexpr int AI_NUMBI_UPGRADE_ORDER_LEVEL = 1; +static constexpr int AI_NUMBI_UPGRADE_ORDER_REPAIR = 1; + +// adjustBuildings/checkoutExpands OrderCreate args (unitsWorking=1, flagRadius=1). +static constexpr int AI_NUMBI_BUILD_ORDER_UNITS_WORKING = 1; +static constexpr int AI_NUMBI_BUILD_ORDER_FLAG_RADIUS = 1; diff --git a/src/AIToubib.cpp b/src/ai/AIToubib.cpp similarity index 52% rename from src/AIToubib.cpp rename to src/ai/AIToubib.cpp index 8835b0d3d..339765ee5 100644 --- a/src/AIToubib.cpp +++ b/src/ai/AIToubib.cpp @@ -1,31 +1,16 @@ -/* - This file is part of Globulation 2, a free software real-time strategy game - http://www.globulation2.org - Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charriere and other contributors - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charriere and other contributors + +// This file is part of Globulation 2, a free software real-time strategy game #include #include "AIToubib.h" +#include "AIToubibTuning.h" #include "Order.h" #include "Player.h" -using boost::shared_ptr; +using std::shared_ptr; AIToubib::AIToubib(Player *player) { @@ -84,7 +69,7 @@ void AIToubib::save(GAGCore::OutputStream *stream) stream->writeUint32(now, "now"); } -boost::shared_ptr AIToubib::getOrderBuildingStep(void) +std::shared_ptr AIToubib::getOrderBuildingStep(void) { return shared_ptr(new NullOrder()); } @@ -94,11 +79,11 @@ void AIToubib::computeMyStatsStep(void) } -boost::shared_ptr AIToubib::getOrder(void) +std::shared_ptr AIToubib::getOrder(void) { now++; - switch (now % 2) + switch (now % AI_TOUBIB_STEP_MODULUS) { case 0: return getOrderBuildingStep(); default: computeMyStatsStep(); return shared_ptr(new NullOrder()); diff --git a/src/AIToubib.h b/src/ai/AIToubib.h similarity index 60% rename from src/AIToubib.h rename to src/ai/AIToubib.h index dad5f13db..990b13c93 100644 --- a/src/AIToubib.h +++ b/src/ai/AIToubib.h @@ -1,26 +1,9 @@ -/* - This file is part of Globulation 2, a free software real-time strategy game - http://www.globulation2.org - Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charriere and other contributors - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charriere and other contributors - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +// This file is part of Globulation 2, a free software real-time strategy game - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __AI_TOUBIB_H -#define __AI_TOUBIB_H +#pragma once #include "AIImplementation.h" @@ -44,7 +27,7 @@ class AIToubib : public AIImplementation void init(Player *player); //! Create a building if possible - boost::shared_ptr getOrderBuildingStep(void); + std::shared_ptr getOrderBuildingStep(void); //! Compute internal stats used by other parts of the code void computeMyStatsStep(void); @@ -64,7 +47,7 @@ class AIToubib : public AIImplementation void save(GAGCore::OutputStream *stream); //! return a new order in response to last events - boost::shared_ptr getOrder(void); + std::shared_ptr getOrder(void); private: /* @@ -112,4 +95,3 @@ class AIToubib : public AIImplementation */ }; -#endif diff --git a/src/ai/AIToubibTuning.h b/src/ai/AIToubibTuning.h new file mode 100644 index 000000000..60618b948 --- /dev/null +++ b/src/ai/AIToubibTuning.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2005 Stephane Magnenat & Luc-Olivier de Charriere and other contributors +// +// AIToubibTuning.h +// +// Behavior-preserving tuning constants for AIToubib, extracted from +// AIToubib.cpp during the magic-number cleanup pass that prepares the +// codebase for the Rust port. AIToubib is a stub AI: per `AI::getOrder()` it +// alternates each tick between a no-op build step and a no-op stats step. +// Only one live constant comes out of that file -- everything else +// (MAX_NB_PROJECTS, NB_HISTORY_STATES) is dead, lives inside `/* */` blocks +// in AIToubib.h, and is intentionally NOT named here. +// +// Constants are file-scope `static constexpr int` per the slice convention. + +#pragma once + +// --------------------------------------------------------------------------- +// Decision-cycle modulus (AIToubib::getOrder): +// switch (now % AI_TOUBIB_STEP_MODULUS) { +// case 0: return getOrderBuildingStep(); // no-op: NullOrder +// default: computeMyStatsStep(); return NullOrder; // no-op +// } +// Alternates between the two stub steps every tick. +// --------------------------------------------------------------------------- +static constexpr int AI_TOUBIB_STEP_MODULUS = 2; diff --git a/src/AIWarrush.cpp b/src/ai/AIWarrush.cpp similarity index 73% rename from src/AIWarrush.cpp rename to src/ai/AIWarrush.cpp index 2445f5892..e41e3fc5a 100644 --- a/src/AIWarrush.cpp +++ b/src/ai/AIWarrush.cpp @@ -1,40 +1,68 @@ - /* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2005 Eli Dupree - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2005 Eli Dupree #include "AIWarrush.h" +#include "AIWarrushTuning.h" #include "Building.h" #include "Unit.h" -#include "Building.h" #include "Game.h" #include "GlobalContainer.h" #include "Order.h" #include "Player.h" #include "Brush.h" #include "Utilities.h" -#include #include -#define BUILDING_DELAY 30 -#define AREAS_DELAY 50 +using std::shared_ptr; -using boost::shared_ptr; +namespace { + template + int countUnitsIf(const Team *team, Pred p) + { + int n = 0; + for (int i = 0; i < Unit::MAX_COUNT; i++) + { + Unit *u = team->myUnits[i]; + if (u && p(u)) n++; + } + return n; + } + + template + Unit *findUnitIf(const Team *team, Pred p) + { + for (int i = 0; i < Unit::MAX_COUNT; i++) + { + Unit *u = team->myUnits[i]; + if (u && p(u)) return u; + } + return nullptr; + } + + template + int countBuildingsIf(const Team *team, Pred p) + { + int n = 0; + for (int i = 0; i < Building::MAX_COUNT; i++) + { + Building *b = team->myBuildings[i]; + if (b && p(b)) n++; + } + return n; + } + + template + Building *findBuildingIf(const Team *team, Pred p) + { + for (int i = 0; i < Building::MAX_COUNT; i++) + { + Building *b = team->myBuildings[i]; + if (b && p(b)) return b; + } + return nullptr; + } +} void AIWarrush::init(Player *player) { @@ -78,87 +106,41 @@ void AIWarrush::save(GAGCore::OutputStream *stream) int AIWarrush::numberOfUnitsWithSkillGreaterThanValue(const int skill, const int value)const { - Unit **myUnits=team->myUnits; - int count = 0; - for (int i=0; iperformance[skill]>value)) - { - count++; - } - } - return count; + return countUnitsIf(team, [skill, value](Unit *u) { return u->performance[skill] > value; }); } int AIWarrush::numberOfUnitsWithSkillEqualToValue(const int skill, const int value)const { - Unit **myUnits=team->myUnits; - int count = 0; - for (int i=0; iperformance[skill]==value)) - { - count++; - } - } - return count; + return countUnitsIf(team, [skill, value](Unit *u) { return u->performance[skill] == value; }); } bool AIWarrush::isAnyUnitWithLessThanOneThirdFood()const { - Unit **myUnits=team->myUnits; - for (int i=0; ihungry<(Unit::HUNGRY_MAX/2))) //Yeah, it's a half, not a third. Weird huh? :P - { - return true; - } - } - return false; + //Yeah, it's a half, not a third. Weird huh? :P + return findUnitIf(team, [](Unit *u) { return u->hungry < (Unit::HUNGRY_MAX/AI_WARRUSH_HUNGRY_THRESHOLD_DIVISOR); }) != nullptr; } Building *AIWarrush::getSwarmWithoutSettings(const int workerRatio, const int explorerRatio, const int warriorRatio)const { - Building **myBuildings=team->myBuildings; - for (int i=0; itype->shortTypeNum==IntBuildingType::SWARM_BUILDING) - && (b->constructionResultState == Building::NO_CONSTRUCTION) - && ((b->ratio[0] != workerRatio) || (b->ratio[1] != explorerRatio) || (b->ratio[2] != warriorRatio)) - ) - { - return b; - } - } - return NULL; + return findBuildingIf(team, [=](Building *b) { + return b->type->shortTypeNum == IntBuildingType::SWARM_BUILDING + && b->constructionResultState == Building::NO_CONSTRUCTION + && (b->ratio[0] != workerRatio || b->ratio[1] != explorerRatio || b->ratio[2] != warriorRatio); + }); } Building *AIWarrush::getBuildingWithoutWorkersAssigned(Sint32 shortTypeNum, int num_workers)const { - Building **myBuildings=team->myBuildings; - for (int i=0; itype->shortTypeNum == shortTypeNum) - && (b->maxUnitWorking != num_workers) - && (b->constructionResultState != Building::NO_CONSTRUCTION - || ((shortTypeNum != IntBuildingType::ATTACK_BUILDING) - && (shortTypeNum != IntBuildingType::HEAL_BUILDING) - && (shortTypeNum != IntBuildingType::WALKSPEED_BUILDING) - && (shortTypeNum != IntBuildingType::SWIMSPEED_BUILDING) - && (shortTypeNum != IntBuildingType::SCIENCE_BUILDING) - ))) - { - return b; - } - } - return NULL; + return findBuildingIf(team, [=](Building *b) { + return b->type->shortTypeNum == shortTypeNum + && b->maxUnitWorking != num_workers + && (b->constructionResultState != Building::NO_CONSTRUCTION + || (shortTypeNum != IntBuildingType::ATTACK_BUILDING + && shortTypeNum != IntBuildingType::HEAL_BUILDING + && shortTypeNum != IntBuildingType::WALKSPEED_BUILDING + && shortTypeNum != IntBuildingType::SWIMSPEED_BUILDING + && shortTypeNum != IntBuildingType::SCIENCE_BUILDING)); + }); } Building *AIWarrush::getSwarmAtRandom()const @@ -183,96 +165,46 @@ Building *AIWarrush::getSwarmAtRandom()const bool AIWarrush::allOfBuildingTypeAreCompleted(Sint32 shortTypeNum)const { - Building **myBuildings=team->myBuildings; - for (int i=0; itype->shortTypeNum==shortTypeNum) - && ( - b->constructionResultState != Building::NO_CONSTRUCTION - || b->buildingState == Building::DEAD - ) - ) - { - return false; - } - } - return true; + return findBuildingIf(team, [shortTypeNum](Building *b) { + return b->type->shortTypeNum == shortTypeNum + && (b->constructionResultState != Building::NO_CONSTRUCTION + || b->buildingState == Building::DEAD); + }) == nullptr; } bool AIWarrush::allOfBuildingTypeAreFull(Sint32 shortTypeNum)const { - Building **myBuildings=team->myBuildings; - for (int i=0; itype->shortTypeNum==shortTypeNum) - && ( - b->unitsInside.size() < (size_t)b->maxUnitInside - ) - ) - { - return false; - } - } - return true; + return findBuildingIf(team, [shortTypeNum](Building *b) { + return b->type->shortTypeNum == shortTypeNum + && b->unitsInside.size() < (size_t)b->maxUnitInside; + }) == nullptr; } int AIWarrush::numberOfBuildingsOfType(Sint32 shortTypeNum)const { - Building **myBuildings=team->myBuildings; - int count = 0; - for (int i=0; ishortTypeNum == shortTypeNum - ))++count; - } - return count; + return countBuildingsIf(team, [shortTypeNum](Building *b) { + return b->shortTypeNum == shortTypeNum; + }); } int AIWarrush::numberOfExtraBuildings()const { - Building **myBuildings=team->myBuildings; - int count = 0; - for (int i=0; ishortTypeNum == IntBuildingType::HEAL_BUILDING - || b->shortTypeNum == IntBuildingType::WALKSPEED_BUILDING - || b->shortTypeNum == IntBuildingType::SWIMSPEED_BUILDING - || b->shortTypeNum == IntBuildingType::SCIENCE_BUILDING - || b->shortTypeNum == IntBuildingType::DEFENSE_BUILDING - ))++count; - } - return count; + return countBuildingsIf(team, [](Building *b) { + return b->shortTypeNum == IntBuildingType::HEAL_BUILDING + || b->shortTypeNum == IntBuildingType::WALKSPEED_BUILDING + || b->shortTypeNum == IntBuildingType::SWIMSPEED_BUILDING + || b->shortTypeNum == IntBuildingType::SCIENCE_BUILDING + || b->shortTypeNum == IntBuildingType::DEFENSE_BUILDING; + }); } bool AIWarrush::allOfBuildingTypeAreFullyWorked(Sint32 shortTypeNum)const { - Building **myBuildings=team->myBuildings; - for (int i=0; ishortTypeNum == shortTypeNum)) - { - if(b->unitsWorking.size() == (size_t)b->maxUnitWorking) - { - } - else - { - return false; - } - } - } - return true; + return findBuildingIf(team, [shortTypeNum](Building *b) { + return b->shortTypeNum == shortTypeNum + && b->unitsWorking.size() != (size_t)b->maxUnitWorking; + }) == nullptr; } bool AIWarrush::percentageOfBuildingsAreFullyWorked(int percentage)const @@ -296,7 +228,7 @@ bool AIWarrush::percentageOfBuildingsAreFullyWorked(int percentage)const && b->constructionResultState == Building::NO_CONSTRUCTION && - (b->ressources[CORN]) > ((b->wishedResources[CORN]) * 2 / 3)) + (b->ressources[CORN]) > ((b->wishedResources[CORN]) * AI_WARRUSH_HEAVILY_WORKED_RATIO_NUM / AI_WARRUSH_HEAVILY_WORKED_RATIO_DEN)) {//heavily worked swarms and inns sometimes are full and have no workers ++num_worked_buildings; if(verbose)std::cout << "C"; @@ -307,7 +239,7 @@ bool AIWarrush::percentageOfBuildingsAreFullyWorked(int percentage)const return num_worked_buildings * 100 >= num_buildings * percentage; } -boost::shared_ptr AIWarrush::getOrder(void) +std::shared_ptr AIWarrush::getOrder(void) { // reduce delays if (buildingDelay > 0) @@ -315,32 +247,32 @@ boost::shared_ptr AIWarrush::getOrder(void) if (areaUpdatingDelay > 0) areaUpdatingDelay--; - if(game->stepCounter < 64 && game->stepCounter%2 == 0) + if(game->stepCounter < AI_WARRUSH_BOOTSTRAP_EXPLORE_WINDOW && game->stepCounter%AI_WARRUSH_BOOTSTRAP_EXPLORE_INTERVAL == 0) { - int teamIndex = game->stepCounter / 2; + int teamIndex = game->stepCounter / AI_WARRUSH_BOOTSTRAP_EXPLORE_INTERVAL; Team *enemy_team = game->teams[teamIndex]; if((enemy_team)&&(team->enemies & enemy_team->me))return setupExploreFlagForTeam(enemy_team); } //keep those areas up to date - if(areaUpdatingDelay == AREAS_DELAY*2/3) + if(areaUpdatingDelay == AI_WARRUSH_AREAS_DELAY_TICKS*AI_WARRUSH_AREAS_PRUNE_PHASE_NUM/AI_WARRUSH_AREAS_PRUNE_PHASE_DEN) return pruneGuardAreas(); - if(areaUpdatingDelay == AREAS_DELAY/3) + if(areaUpdatingDelay == AI_WARRUSH_AREAS_DELAY_TICKS/AI_WARRUSH_AREAS_PLACE_PHASE_DEN) return placeGuardAreas(); if(areaUpdatingDelay <= 0) { - areaUpdatingDelay = AREAS_DELAY; + areaUpdatingDelay = AI_WARRUSH_AREAS_DELAY_TICKS; return farm(); } - + //assuming we didn't have to mess with areas or explore flags, check if we can build stuff if (buildingDelay <= 0) { - bool shouldBuildMore = percentageOfBuildingsAreFullyWorked(70); + bool shouldBuildMore = percentageOfBuildingsAreFullyWorked(AI_WARRUSH_BUILD_MORE_PCT_THRESHOLD); if(verbose)if(shouldBuildMore)std::cout << "AIWarrush is ready to build more stuff!"; //Build another swarm if all are swarms are working at capacity, and if we have other random stuff we should-have / need if(verbose)std::cout << "Chance to build swarm: "; - if(shouldBuildMore && allOfBuildingTypeAreCompleted(IntBuildingType::SWARM_BUILDING) && numberOfExtraBuildings() >= numberOfBuildingsOfType(IntBuildingType::SWARM_BUILDING) && numberOfBuildingsOfType(IntBuildingType::FOOD_BUILDING) >= numberOfBuildingsOfType(IntBuildingType::SWARM_BUILDING) * 2) + if(shouldBuildMore && allOfBuildingTypeAreCompleted(IntBuildingType::SWARM_BUILDING) && numberOfExtraBuildings() >= numberOfBuildingsOfType(IntBuildingType::SWARM_BUILDING) && numberOfBuildingsOfType(IntBuildingType::FOOD_BUILDING) >= numberOfBuildingsOfType(IntBuildingType::SWARM_BUILDING) * AI_WARRUSH_INNS_PER_SWARM_RATIO) { if(verbose)std::cout << "TAKEN!\n"; return buildBuildingOfType(IntBuildingType::SWARM_BUILDING); @@ -355,7 +287,7 @@ boost::shared_ptr AIWarrush::getOrder(void) && shouldBuildMore && - numberOfExtraBuildings() >= numberOfBuildingsOfType(IntBuildingType::FOOD_BUILDING) - 1 + numberOfExtraBuildings() >= numberOfBuildingsOfType(IntBuildingType::FOOD_BUILDING) - AI_WARRUSH_INN_LOOKAHEAD && ( (allOfBuildingTypeAreCompleted(IntBuildingType::FOOD_BUILDING) @@ -387,11 +319,11 @@ boost::shared_ptr AIWarrush::getOrder(void) { if(verbose)std::cout << "TAKEN!\n"; Sint32 type; - int random_number = syncRand()%100; - if(random_number < 70 || numberOfBuildingsOfType(IntBuildingType::HEAL_BUILDING) == 0)type = IntBuildingType::HEAL_BUILDING; - else if(random_number < 80 || numberOfBuildingsOfType(IntBuildingType::WALKSPEED_BUILDING) == 0)type = IntBuildingType::WALKSPEED_BUILDING; - else if(random_number < 87)type = IntBuildingType::SWIMSPEED_BUILDING; - else if(random_number < 94)type = IntBuildingType::SCIENCE_BUILDING; + int random_number = syncRand()%AI_WARRUSH_RANDOM_BUILDING_DENOM; + if(random_number < AI_WARRUSH_HEAL_PCT_THRESHOLD || numberOfBuildingsOfType(IntBuildingType::HEAL_BUILDING) == 0)type = IntBuildingType::HEAL_BUILDING; + else if(random_number < AI_WARRUSH_WALKSPEED_PCT_THRESHOLD || numberOfBuildingsOfType(IntBuildingType::WALKSPEED_BUILDING) == 0)type = IntBuildingType::WALKSPEED_BUILDING; + else if(random_number < AI_WARRUSH_SWIMSPEED_PCT_THRESHOLD)type = IntBuildingType::SWIMSPEED_BUILDING; + else if(random_number < AI_WARRUSH_SCIENCE_PCT_THRESHOLD)type = IntBuildingType::SCIENCE_BUILDING; else type = IntBuildingType::DEFENSE_BUILDING; return buildBuildingOfType(type); } @@ -399,37 +331,37 @@ boost::shared_ptr AIWarrush::getOrder(void) } //If we have enough workers, we can switch to dedicated warrushing production. - if(numberOfUnitsWithSkillGreaterThanValue(HARVEST,0) >= 6) + if(numberOfUnitsWithSkillGreaterThanValue(HARVEST,0) >= AI_WARRUSH_HARVESTER_THRESHOLD) { //This is basically a way to change all the swarms without bothering to remember //anything. (It can only issue one order per tick, so it has to do it over several //ticks and calculate the orders seperately.) - Building *out_of_date_swarm = getSwarmWithoutSettings(4,1,3); + Building *out_of_date_swarm = getSwarmWithoutSettings(AI_WARRUSH_SWARM_RATIO_WORKER, AI_WARRUSH_SWARM_RATIO_EXPLORER, AI_WARRUSH_SWARM_RATIO_WARRIOR); if(out_of_date_swarm) { - Sint32 settings[3] = {4,1,3}; + Sint32 settings[3] = {AI_WARRUSH_SWARM_RATIO_WORKER, AI_WARRUSH_SWARM_RATIO_EXPLORER, AI_WARRUSH_SWARM_RATIO_WARRIOR}; return shared_ptr(new OrderModifySwarm(out_of_date_swarm->gid, settings)); } } - + //all swarms should always have 5 workers at them! - Building *weak_swarm = getBuildingWithoutWorkersAssigned(IntBuildingType::SWARM_BUILDING, 5); - if (weak_swarm) return shared_ptr(new OrderModifyBuilding(weak_swarm->gid, 5)); + Building *weak_swarm = getBuildingWithoutWorkersAssigned(IntBuildingType::SWARM_BUILDING, AI_WARRUSH_SWARM_WORKER_COUNT); + if (weak_swarm) return shared_ptr(new OrderModifyBuilding(weak_swarm->gid, AI_WARRUSH_SWARM_WORKER_COUNT)); //all inns should always have 3 workers at them! (best to build fast, make sure they're fed) - Building *weak_inn = getBuildingWithoutWorkersAssigned(IntBuildingType::FOOD_BUILDING, 3); - if (weak_inn) return shared_ptr(new OrderModifyBuilding(weak_inn->gid, 3)); - + Building *weak_inn = getBuildingWithoutWorkersAssigned(IntBuildingType::FOOD_BUILDING, AI_WARRUSH_INN_WORKER_COUNT); + if (weak_inn) return shared_ptr(new OrderModifyBuilding(weak_inn->gid, AI_WARRUSH_INN_WORKER_COUNT)); + //work barracks more too. - Building *weak_barracks = getBuildingWithoutWorkersAssigned(IntBuildingType::ATTACK_BUILDING, 3); + Building *weak_barracks = getBuildingWithoutWorkersAssigned(IntBuildingType::ATTACK_BUILDING, AI_WARRUSH_BARRACKS_WORKER_COUNT); if (weak_barracks && weak_barracks->constructionResultState != Building::NO_CONSTRUCTION) - return shared_ptr(new OrderModifyBuilding(weak_barracks->gid, 3)); + return shared_ptr(new OrderModifyBuilding(weak_barracks->gid, AI_WARRUSH_BARRACKS_WORKER_COUNT)); //nothing at all to do?! return shared_ptr(new NullOrder); } -boost::shared_ptr AIWarrush::pruneGuardAreas() +std::shared_ptr AIWarrush::pruneGuardAreas() { //If we have any guard areas that aren't adjacent to an enemy building, we remove them. BrushAccumulator acc; @@ -470,7 +402,7 @@ boost::shared_ptr AIWarrush::pruneGuardAreas() else return shared_ptr(new NullOrder); } -boost::shared_ptr AIWarrush::placeGuardAreas() +std::shared_ptr AIWarrush::placeGuardAreas() { BrushAccumulator guard_add_acc; //Place guard area on an enemy building if there is one... @@ -506,7 +438,7 @@ boost::shared_ptr AIWarrush::placeGuardAreas() { for(int y = 0; y < bt->height; y++) { - guard_add_acc.applyBrush(BrushApplication((b->posX+x) % map->getW(), (b->posY+y) & map->getH(),6), map); + guard_add_acc.applyBrush(BrushApplication((b->posX+x) % map->getW(), (b->posY+y) % map->getH(),AI_WARRUSH_GUARD_BRUSH_SIZE), map); } } } @@ -523,7 +455,7 @@ boost::shared_ptr AIWarrush::placeGuardAreas() else return shared_ptr(new NullOrder); } -boost::shared_ptr AIWarrush::farm() +std::shared_ptr AIWarrush::farm() { // Algorithm initially stolen from Nicowar. DynamicGradientMapArray water_gradient(map->w,map->h); @@ -533,7 +465,7 @@ boost::shared_ptr AIWarrush::farm() { if (map->isWater(x,y)) { - water_gradient(x, y) = 255; + water_gradient(x, y) = AI_WARRUSH_GRADIENT_MAX; } else { @@ -541,7 +473,7 @@ boost::shared_ptr AIWarrush::farm() } } } - map->updateGlobalGradientSlow(water_gradient.c_array()); + map->updateGlobalGradient(water_gradient.c_array()); BrushAccumulator del_acc; BrushAccumulator add_acc; @@ -614,18 +546,18 @@ boost::shared_ptr AIWarrush::farm() { if(map->isRessourceTakeable(x, y, WOOD)) { - if(!map->isForbidden(x, y, team->me) && !map->isClearArea(x, y, team->me) && map->isMapDiscovered(x, y, team->me) && water_gradient(x, y) > (255 - 15)) - { + if(!map->isForbidden(x, y, team->me) && !map->isClearArea(x, y, team->me) && map->isMapDiscovered(x, y, team->me) && water_gradient(x, y) > (AI_WARRUSH_GRADIENT_MAX - AI_WARRUSH_WATER_NEAR_OFFSET)) + { add_acc.applyBrush(BrushApplication(x, y, 0), map); } } } - + if(x%2==y%2) { if(map->isRessourceTakeable(x, y, CORN)) { - if(!map->isForbidden(x, y, team->me) && map->isMapDiscovered(x, y, team->me) && water_gradient(x, y) > (255 - 15)) + if(!map->isForbidden(x, y, team->me) && map->isMapDiscovered(x, y, team->me) && water_gradient(x, y) > (AI_WARRUSH_GRADIENT_MAX - AI_WARRUSH_WATER_NEAR_OFFSET)) { add_acc.applyBrush(BrushApplication(x, y, 0), map); } @@ -661,7 +593,7 @@ boost::shared_ptr AIWarrush::farm() } //Simple hack to place explore flags on opponents' starting swarms. -boost::shared_ptr AIWarrush::setupExploreFlagForTeam(Team *enemy_team) +std::shared_ptr AIWarrush::setupExploreFlagForTeam(Team *enemy_team) { if(verbose)std::cout << "looking for swarms:\n"; for(int j=0;jgetCase(x,y); if (c.ressource.type==resource_type) { - gradient(x, y) = 255; + gradient(x, y) = AI_WARRUSH_GRADIENT_MAX; } else if (c.ressource.type!=NO_RES_TYPE) { @@ -746,13 +678,13 @@ void AIWarrush::initializeGradientWithResource(DynamicGradientMapArray &gradient } } - map->updateGlobalGradientSlow(gradient.c_array()); + map->updateGlobalGradient(gradient.c_array()); for(int x=0;xw;x++) { for(int y=0;yh;y++) { - if (gradient(x, y) == 255) + if (gradient(x, y) == AI_WARRUSH_GRADIENT_MAX) gradient(x, y) = 0; else gradient(x, y)++; @@ -760,14 +692,14 @@ void AIWarrush::initializeGradientWithResource(DynamicGradientMapArray &gradient } } -boost::shared_ptr AIWarrush::buildBuildingOfType(Sint32 shortTypeNum) +std::shared_ptr AIWarrush::buildBuildingOfType(Sint32 shortTypeNum) { // set delay // now doing this first in order to avoid repeated failed builds // WARNING THIS IS A HACK FIX // in reality, if it fails to build, it should go on and get another order. - buildingDelay = BUILDING_DELAY; + buildingDelay = AI_WARRUSH_BUILDING_DELAY_TICKS; DynamicGradientMapArray wood_gradient(map->w,map->h); DynamicGradientMapArray wheat_gradient(map->w,map->h); @@ -795,15 +727,15 @@ boost::shared_ptr AIWarrush::buildBuildingOfType(Sint32 shortTypeNum) { availability_gradient(x, y) = 0; } - else if(map->isHardSpaceForBuilding(x-(bt->width / 2),y-(bt->width / 2),bt->width*2,bt->height*2) && locationIsAvailableForBuilding(x,y,bt->width,bt->height)) //the extra numbers at the ends expand the building + else if(map->isHardSpaceForBuilding(x-(bt->width / AI_WARRUSH_BUILDING_CENTER_DIVISOR),y-(bt->width / AI_WARRUSH_BUILDING_CENTER_DIVISOR),bt->width*AI_WARRUSH_BUILDING_CLEARANCE_MULT,bt->height*AI_WARRUSH_BUILDING_CLEARANCE_MULT) && locationIsAvailableForBuilding(x,y,bt->width,bt->height)) //the extra numbers at the ends expand the building { - availability_gradient(x, y) = 255; + availability_gradient(x, y) = AI_WARRUSH_GRADIENT_MAX; } else availability_gradient(x, y) = 1; } } - map->updateGlobalGradientSlow(availability_gradient.c_array()); + map->updateGlobalGradient(availability_gradient.c_array()); Building *swarm = getSwarmAtRandom(); if (!swarm) @@ -833,7 +765,7 @@ boost::shared_ptr AIWarrush::buildBuildingOfType(Sint32 shortTypeNum) bool result = map->getGlobalGradientDestination(availability_gradient.c_array(), x, y, &destination_x, &destination_y); if(verbose)std::cout << "Trying to build " << shortTypeNum << "(" << bt->width << " x " << bt->height << ")" << " at " << destination_x << "," << destination_y << " from " << x << "," << y << " swarm is " << swarm->posX << "," << swarm->posY << ", found = " << result << std::endl; - if(verbose)if((int)availability_gradient(destination_x, destination_y) != 255)std::cout << "Could not find valid location for building! Best spot: " << destination_x << "," << destination_y << " (" << (int)availability_gradient(destination_x, destination_y) << ")\n"; + if(verbose)if((int)availability_gradient(destination_x, destination_y) != AI_WARRUSH_GRADIENT_MAX)std::cout << "Could not find valid location for building! Best spot: " << destination_x << "," << destination_y << " (" << (int)availability_gradient(destination_x, destination_y) << ")\n"; } // create and return order diff --git a/src/AIWarrush.h b/src/ai/AIWarrush.h similarity index 67% rename from src/AIWarrush.h rename to src/ai/AIWarrush.h index 3ae12fea0..943258461 100644 --- a/src/AIWarrush.h +++ b/src/ai/AIWarrush.h @@ -1,25 +1,9 @@ - /* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2005 Eli Dupree - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2005 Eli Dupree - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ -#ifndef __AI_WARRUSH_H -#define __AI_WARRUSH_H +#pragma once #include "AIImplementation.h" #include @@ -40,19 +24,22 @@ struct DynamicGradientMapArray DynamicGradientMapArray(std::size_t w, std::size_t h) : width(w), - height(h), array(w*h) { } - + //usage: gradient(x, y) const element_type &operator()(size_t x, size_t y) const { return array[y * width + x]; } element_type &operator()(size_t x, size_t y) { return array[y * width + x]; } element_type* c_array() { return &array[0]; } - + private: + // Only width is stored: it's the row stride for the row-major flat + // buffer below (array[y*width + x]). Height isn't needed for indexing + // and we don't bounds-check, so storing it would be dead weight. All + // callers size the array to map->w * map->h and iterate within those + // dimensions, so the height bound is enforced externally. std::size_t width; - std::size_t height; std::valarray array; }; @@ -77,7 +64,7 @@ class AIWarrush : public AIImplementation bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); void save(GAGCore::OutputStream *stream); - boost::shared_ptr getOrder(void); + std::shared_ptr getOrder(void); private: void init(Player *player); //implementation functions to make the code more like the pseudocode; @@ -96,15 +83,14 @@ class AIWarrush : public AIImplementation Building *getSwarmAtRandom()const; //functions called by getOrder, filled with pseudocode and its product, //real code. - boost::shared_ptr placeGuardAreas(void); - boost::shared_ptr pruneGuardAreas(void); - boost::shared_ptr farm(void); - boost::shared_ptr setupExploreFlagForTeam(Team *enemy_team); + std::shared_ptr placeGuardAreas(void); + std::shared_ptr pruneGuardAreas(void); + std::shared_ptr farm(void); + std::shared_ptr setupExploreFlagForTeam(Team *enemy_team); bool locationIsAvailableForBuilding(int x, int y, int width, int height); void initializeGradientWithResource(DynamicGradientMapArray &gradient, Uint8 resource_type); - boost::shared_ptr buildBuildingOfType(Sint32 shortTypeNum); + std::shared_ptr buildBuildingOfType(Sint32 shortTypeNum); }; -#endif diff --git a/src/ai/AIWarrushTuning.h b/src/ai/AIWarrushTuning.h new file mode 100644 index 000000000..bae152773 --- /dev/null +++ b/src/ai/AIWarrushTuning.h @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charriere +// Copyright (C) 2005 Eli Dupree +// +// AIWarrushTuning.h +// +// Behavior-preserving tuning constants for AIWarrush, extracted from +// AIWarrush.cpp during the magic-number cleanup pass that prepares the +// codebase for the Rust port. Every value here is byte-for-byte identical +// to the literal it replaces; nothing in the AI's decision logic changes. +// +// Constants are file-scope `static constexpr int` per the slice convention. + +#pragma once + +#include "Team.h" + +// --------------------------------------------------------------------------- +// Tick / phase delays (formerly the BUILDING_DELAY and AREAS_DELAY #defines +// at the top of AIWarrush.cpp). +// --------------------------------------------------------------------------- + +// Cooldown (in 40ms ticks) after AIWarrush issues a build order. Prevents +// the AI from spamming repeated build requests at the same target tile. +static constexpr int AI_WARRUSH_BUILDING_DELAY_TICKS = 30; + +// Recurring period (in ticks) for the guard-area maintenance cycle. The +// cycle is divided into three phases (prune at 2/3, place at 1/3, refill +// at 0) using AI_WARRUSH_AREAS_PRUNE_PHASE_* and AI_WARRUSH_AREAS_PLACE_*. +static constexpr int AI_WARRUSH_AREAS_DELAY_TICKS = 50; + +// --------------------------------------------------------------------------- +// Bootstrap: place an exploration flag on each enemy team's starting swarm +// during the first AI_WARRUSH_BOOTSTRAP_EXPLORE_WINDOW ticks of the game, +// stepping by AI_WARRUSH_BOOTSTRAP_EXPLORE_INTERVAL (so two ticks per team, +// mapping tick -> teamIndex via division by the interval). The window must +// cover exactly Team::MAX_COUNT teams — making it any longer would index past +// the end of game->teams[] (this was a latent OOB read when MAX_COUNT was 32 +// and the literal 64 happened to match; the derived form keeps them aligned). +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_BOOTSTRAP_EXPLORE_INTERVAL = 2; +static constexpr int AI_WARRUSH_BOOTSTRAP_EXPLORE_WINDOW = Team::MAX_COUNT * AI_WARRUSH_BOOTSTRAP_EXPLORE_INTERVAL; + +// --------------------------------------------------------------------------- +// Guard-area cycle phase shifts. Original code: +// if(areaUpdatingDelay == AREAS_DELAY*2/3) prune +// if(areaUpdatingDelay == AREAS_DELAY/3) place +// Splitting numerator and denominator keeps the original arithmetic visible. +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_AREAS_PRUNE_PHASE_NUM = 2; +static constexpr int AI_WARRUSH_AREAS_PRUNE_PHASE_DEN = 3; +static constexpr int AI_WARRUSH_AREAS_PLACE_PHASE_DEN = 3; + +// --------------------------------------------------------------------------- +// Build-more gate: percentageOfBuildingsAreFullyWorked(70) decides whether +// the AI is "ready to build more stuff." +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_BUILD_MORE_PCT_THRESHOLD = 70; + +// --------------------------------------------------------------------------- +// Swarm/inn balance: build a new swarm only if FOOD >= SWARM * 2. +// Build a new inn only if extras >= FOOD - 1 (look-ahead of one inn). +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_INNS_PER_SWARM_RATIO = 2; +static constexpr int AI_WARRUSH_INN_LOOKAHEAD = 1; + +// --------------------------------------------------------------------------- +// Random-building probability ladder (syncRand() % 100): +// +// <70 -> HEAL (or HEAL when there are zero heal buildings; see +// bug L11 in bugs_surfaced_during_magic_number_audit.md +// -- the OR clause is intentionally preserved.) +// <80 -> WALKSPEED (or WALKSPEED when there are zero of those) +// <87 -> SWIMSPEED +// <94 -> SCIENCE +// else -> DEFENSE +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_RANDOM_BUILDING_DENOM = 100; +static constexpr int AI_WARRUSH_HEAL_PCT_THRESHOLD = 70; +static constexpr int AI_WARRUSH_WALKSPEED_PCT_THRESHOLD = 80; +static constexpr int AI_WARRUSH_SWIMSPEED_PCT_THRESHOLD = 87; +static constexpr int AI_WARRUSH_SCIENCE_PCT_THRESHOLD = 94; + +// --------------------------------------------------------------------------- +// Production switchover: once the team has at least this many units with +// HARVEST > 0, AIWarrush retunes its swarms to the dedicated war ratio. +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_HARVESTER_THRESHOLD = 6; + +// --------------------------------------------------------------------------- +// Swarm production ratio used by the dedicated-warrushing mode: +// (worker, explorer, warrior) = (4, 1, 3). +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_SWARM_RATIO_WORKER = 4; +static constexpr int AI_WARRUSH_SWARM_RATIO_EXPLORER = 1; +static constexpr int AI_WARRUSH_SWARM_RATIO_WARRIOR = 3; + +// --------------------------------------------------------------------------- +// Default worker counts AIWarrush forces onto each building type. +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_SWARM_WORKER_COUNT = 5; +static constexpr int AI_WARRUSH_INN_WORKER_COUNT = 3; +static constexpr int AI_WARRUSH_BARRACKS_WORKER_COUNT = 3; + +// --------------------------------------------------------------------------- +// Guard-area brush radius applied at each enemy-building tile in +// placeGuardAreas(). +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_GUARD_BRUSH_SIZE = 6; + +// --------------------------------------------------------------------------- +// Gradient propagation cap: AIWarrush stores Uint8 gradients with 255 as the +// "source" marker (water tiles, resource tiles) and 0/1 elsewhere before +// calling Map::updateGlobalGradient(). +// +// AI_WARRUSH_WATER_NEAR_OFFSET is the slack below the cap that still counts +// as "near water" when picking wood/wheat plant locations: +// water_gradient(x, y) > (AI_WARRUSH_GRADIENT_MAX - AI_WARRUSH_WATER_NEAR_OFFSET) +// i.e. > 240 in the original code. Both constants are kept separate so the +// 255-15 arithmetic stays visible at the call site. +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_GRADIENT_MAX = 255; +static constexpr int AI_WARRUSH_WATER_NEAR_OFFSET = 15; + +// --------------------------------------------------------------------------- +// "Heavily worked" swarm/inn fudge: a swarm or inn whose stored CORN exceeds +// (wished * 2/3) is counted as fully-worked even if its worker slot is empty. +// Numerator and denominator kept separate to preserve the literal `*2/3`. +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_HEAVILY_WORKED_RATIO_NUM = 2; +static constexpr int AI_WARRUSH_HEAVILY_WORKED_RATIO_DEN = 3; + +// --------------------------------------------------------------------------- +// Hunger predicate divisor. Bug L10: the function is named +// `isAnyUnitWithLessThanOneThirdFood` but the divisor is 2, so it actually +// fires at the half-food mark. The function name is intentionally NOT +// changed here -- only the literal is named, the bug is preserved. +// See bugs_surfaced_during_magic_number_audit.md L10. +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_HUNGRY_THRESHOLD_DIVISOR = 2; + +// --------------------------------------------------------------------------- +// Building placement geometry used in buildBuildingOfType(): +// isHardSpaceForBuilding(x - bt->width/2, +// y - bt->width/2, // (sic; original uses width here too) +// bt->width * 2, +// bt->height * 2) +// Splits the divisor and the clearance multiplier so reviewers can see the +// "look one half-width up-and-left, then check a 2x clearance box" intent. +// --------------------------------------------------------------------------- +static constexpr int AI_WARRUSH_BUILDING_CENTER_DIVISOR = 2; +static constexpr int AI_WARRUSH_BUILDING_CLEARANCE_MULT = 2; diff --git a/src/ai/castor/Control.cpp b/src/ai/castor/Control.cpp new file mode 100644 index 000000000..5a449f987 --- /dev/null +++ b/src/ai/castor/Control.cpp @@ -0,0 +1,531 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "AICastor.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "Unit.h" +#include "Utilities.h" + +#define AI_FILE_MIN_VERSION 1 +#define AI_FILE_VERSION 2 + +using std::shared_ptr; + +std::shared_ptrAICastor::controlSwarms() +{ + Sint32 warriorGoal=warLevel; + + int unitSum[NB_UNIT_TYPE]; + for (int i=0; imyUnits; + for (int i=0; itypeNum]++; + } + int foodSum=0; + Building **myBuildings=team->myBuildings; + for (int i=0; imaxUnitWorking && b->type->canFeedUnit) + foodSum+=b->type->maxUnitInside; + } + + int unitSumAll=unitSum[0]+unitSum[1]+unitSum[2]; + + foodWarning=((unitSumAll+AI_CASTOR_FOODWARN_OFFSET)>=(foodSum<<1)); + foodLock=((unitSumAll+AI_CASTOR_FOODLOCK_OFFSET)>=(foodSum<<1)); + foodLockStats[foodLock]++; + + foodSurplus=(unitSumAll+AI_CASTOR_FOODSURPLUS_OFFSET>AI_CASTOR_STARVING_RATIO_SHIFT)+AI_CASTOR_STARVING_OFFSET)stats.getStarvingUnits()); + starvingWarningStats[starvingWarning]++; + + bool realFoodLock; + + if (warriorGoal>1) + realFoodLock=((unitSumAll)>=(foodSum*AI_CASTOR_REAL_FOODLOCK_MULT_WAR)); + else + realFoodLock=((unitSumAll)>=(foodSum*AI_CASTOR_REAL_FOODLOCK_MULT_PEACE)); + + if ((timer>AI_CASTOR_FOODLOCK_GRACE_TICKS) && (realFoodLock || starvingWarning || starvingWarningStats[1]>starvingWarningStats[0])) + { + // Stop making any units! + Building **myBuildings=team->myBuildings; + for (int bi=0; bitype->unitProductionTime) + for (int ri=0; riratio[ri]!=0) + { + for (int ri=0; riratio[ri]=0; + b->ratioLocal[ri]=0; + } + b->update(); + return shared_ptr(new OrderModifySwarm(b->gid, b->ratioLocal)); + } + } + + return shared_ptr(); + } + + size_t size=map->w*map->h; + int discovered=0; + int seeable=0; + Uint32 *mapDiscovered=&(map->mapDiscovered[0]); + Uint32 *fogOfWar=&map->fogOfWar[0]; + Uint32 me=team->me; + for (size_t i=0; itype->unitProductionTime) + { + if (b->ratio[EXPLORER]!=explorerGoal + || b->ratio[WORKER]!=workerGoal + || b->ratio[WARRIOR]!=warriorGoal) + { + b->ratio[EXPLORER]=explorerGoal; + b->ratioLocal[EXPLORER]=explorerGoal; + b->ratio[WORKER]=workerGoal; + b->ratioLocal[WORKER]=workerGoal; + b->ratio[WARRIOR]=warriorGoal; + b->ratioLocal[WARRIOR]=warriorGoal; + b->update(); + return shared_ptr(new OrderModifySwarm(b->gid, b->ratioLocal)); + } + } + } + + return shared_ptr(); +} + +std::shared_ptrAICastor::expandFood() +{ + if (foodSurplus + || (!foodWarning && !enoughFreeWorkers()) + || buildingSum[IntBuildingType::FOOD_BUILDING][1]>buildingSum[IntBuildingType::FOOD_BUILDING][0]+1) + return shared_ptr(); + + Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum("inn", 0, true); + int bw=globalContainer->buildingsTypes.get(typeNum)->width; + int bh=globalContainer->buildingsTypes.get(typeNum)->height; + assert(bw==bh); + + computeCanSwim(); + computeObstacleBuildingMap(); + computeSpaceForBuildingMap(bw); + computeBuildingNeighbourMap(bw, bh); + computeObstacleUnitMap(); + computeWheatGrowthMap(); + computeObstacleUnitMap(); + computeWorkPowerMap(); + computeWorkRangeMap(); + computeWorkAbilityMap(); + + return findGoodBuilding(typeNum, true, false, false); +} + +std::shared_ptrAICastor::controlFood() +{ + //int w=map->w; + //int h=map->h; + int wMask=map->wMask; + int hMask=map->hMask; + //int hDec=map->hDec; + int wDec=map->wDec; + //size_t size=w*h; + + int bi=(controlFoodTimer++)&(Building::MAX_COUNT-1); + Building **myBuildings=team->myBuildings; + Building *b=myBuildings[bi]; + for (int i=0; i(); + if (b->type->shortTypeNum!=IntBuildingType::FOOD_BUILDING && b->type->shortTypeNum!=IntBuildingType::SWARM_BUILDING) + return shared_ptr(); + + int bx=b->posX; + int by=b->posY; + int bw=b->type->width; + int bh=b->type->height; + + Uint8 worstCare=0; + for (int xi=bx-1; xiAI_CASTOR_WHEATCARE_STOP_THRESHOLD) + { + if (b->maxUnitWorking!=0) + { + b->maxUnitWorking=0; + b->update(); + if (verbose) + printf("controlFood(), worstCare=%d\n", worstCare); + return shared_ptr(new OrderModifyBuilding(b->gid, 0)); + } + } + else if (worstCare>AI_CASTOR_WHEATCARE_LIMIT_THRESHOLD) + { + if (b->maxUnitWorking>1) + { + b->maxUnitWorking=1; + b->update(); + if (verbose) + printf("controlFood(), beta, worstCare=%d\n", worstCare); + return shared_ptr(new OrderModifyBuilding(b->gid, 1)); + } + } + else + { + if (b->type->shortTypeNum==IntBuildingType::FOOD_BUILDING) + { + Sint32 workers; + if (foodWarning && b->type->isBuildingSite) + workers=AI_CASTOR_FOODWARN_INN_SITE_WORKERS+b->type->level; //TODO: random 2 or 3 + else + workers=AI_CASTOR_INN_WORKERS_BASE+b->type->level; + b->maxUnitWorking=workers; + b->update(); + return shared_ptr(new OrderModifyBuilding(b->gid, workers)); + } + else if (b->type->shortTypeNum==IntBuildingType::SWARM_BUILDING) + { + Sint32 workers; + if (foodWarning) + workers=AI_CASTOR_SWARM_WORKERS_FOODWARN; + else + workers=AI_CASTOR_SWARM_WORKERS_NORMAL; + b->maxUnitWorking=workers; + b->update(); + return shared_ptr(new OrderModifyBuilding(b->gid, workers)); + } + else + assert(false); + } + return shared_ptr(); +} + +std::shared_ptrAICastor::controlUpgrades() +{ + //printf("controlUpgrades(), controlUpgradeTimer=%d, controlUpgradeDelay=%d, buildsAmount=%d\n", + // controlUpgradeTimer, controlUpgradeDelay, buildsAmount); + if (controlUpgradeDelay!=0) + { + controlUpgradeDelay--; + return shared_ptr(); + } + if (buildsAmount<1 || !enoughFreeWorkers()) + return shared_ptr(); + int bi=((controlUpgradeTimer++)&(Building::MAX_COUNT-1)); + Building **myBuildings=team->myBuildings; + Building *b=myBuildings[bi]; + if (b==NULL) + return shared_ptr(); + if (b->type->isVirtual) + return shared_ptr(); + if (b->maxUnitWorking<1) + return shared_ptr(new OrderModifyBuilding(b->gid, 1)); + int numberOfFreeWorkers = team->stats.getLatestStat()->isFree[WORKER]; + int numberOfAbleWorkers = team->stats.getLatestStat()->upgradeState[BUILD][b->type->level]; + if (numberOfAbleWorkers <= AI_CASTOR_UPGRADE_MIN_ABLE_WORKERS + || numberOfFreeWorkers <= AI_CASTOR_UPGRADE_MIN_FREE_WORKERS + || numberOfAbleWorkers <= (numberOfFreeWorkers/AI_CASTOR_UPGRADE_ABLE_FREE_RATIO_DIV)) + return shared_ptr(); + // Is it any repair: + if (!b->type->isBuildingSite) + { + if (b->type->type == "defencetower") + { + if (b->hp*AI_CASTOR_REPAIR_HP_RATIO_DIVtype->hpMax*AI_CASTOR_REPAIR_HP_RATIO_DEFENCE_NUM) + return shared_ptr(new OrderConstruction(b->gid, AI_CASTOR_CONSTRUCTION_ORDER_UNITS, AI_CASTOR_CONSTRUCTION_ORDER_UNITS)); + } + else if (b->type->maxUnitInside) + { + if (b->hp*AI_CASTOR_REPAIR_HP_RATIO_DIVtype->hpMax*AI_CASTOR_REPAIR_HP_RATIO_INSIDE_NUM) + return shared_ptr(new OrderConstruction(b->gid, AI_CASTOR_CONSTRUCTION_ORDER_UNITS, AI_CASTOR_CONSTRUCTION_ORDER_UNITS)); + } + else + { + if (b->hp*AI_CASTOR_REPAIR_HP_RATIO_DIVtype->hpMax*AI_CASTOR_REPAIR_HP_RATIO_OTHER_NUM) + return shared_ptr(new OrderConstruction(b->gid, AI_CASTOR_CONSTRUCTION_ORDER_UNITS, AI_CASTOR_CONSTRUCTION_ORDER_UNITS)); + } + } + // Do we want to upgrade it: + // We compute the number of buildings satifying the strategy: + int shortTypeNum=b->type->shortTypeNum; + if (shortTypeNum>=NB_HARD_BUILDING) + return shared_ptr(); + int level=b->type->level; + int upgradeLevelGoal=((buildsAmount+AI_CASTOR_UPGRADE_LEVEL_FORMULA_BIAS)>>AI_CASTOR_UPGRADE_LEVEL_FORMULA_SHIFT); + if (upgradeLevelGoal>AI_CASTOR_UPGRADE_LEVEL_MAX) + upgradeLevelGoal=AI_CASTOR_UPGRADE_LEVEL_MAX; + if (level>=upgradeLevelGoal) + return shared_ptr(); + int sumOver=0; + for (int li=(level+1); li=upgradeAmountGoal) + return shared_ptr(); + + if (shortTypeNum==IntBuildingType::SCIENCE_BUILDING) + { + int buildBase=team->stats.getWorkersLevel(0); + int buildSum=0; + for (int i=0; istats.getWorkersLevel(i); + if (buildBase>buildSum) + return shared_ptr(); + int sumEqual=0; + for (int li=level; li(); + } + } + controlUpgradeDelay=AI_CASTOR_UPGRADE_DELAY_TICKS; + return shared_ptr(new OrderConstruction(b->gid, AI_CASTOR_CONSTRUCTION_ORDER_UNITS, AI_CASTOR_CONSTRUCTION_ORDER_UNITS)); +} + + +// WARNING : Using wasEvent is *NOT* safe, and will *NOT* work through the network +/*std::shared_ptrAICastor::controlBaseDefense() +{ + int freeWarriors = team->stats.getFreeUnits(WARRIOR); + if (team->wasEvent(Team::BUILDING_UNDER_ATTACK_EVENT) && (freeWarriors>0)) + { + int x, y; + team->getEventPos(&x, &y); + Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum(IntBuildingType::WAR_FLAG, 0, false); + onStrike = true; + return shared_ptr(new OrderCreate(team->teamNumber, x, y, typeNum)); + } + return NULL; +}*/ + + +std::shared_ptrAICastor::controlStrikes() +{ + controlStrikesTimer=timer+AI_CASTOR_CONTROL_STRIKES_INTERVAL; + + if (!onStrike) + return shared_ptr(); + + int warriors=team->stats.getTotalUnits(WARRIOR); + int warFlagsGoal=(warriors+AI_CASTOR_WARFLAG_FORMULA_BIAS)/AI_CASTOR_WARRIORS_PER_WARFLAG; + int warFlagsReal=buildingSum[IntBuildingType::WAR_FLAG][0]; + + if (!strikeTeamSelected) + { + int bestLevel=AI_CASTOR_LEVEL_NONE; + for (int ti=0; timapHeader.getNumberOfTeams(); ti++) + { + Team *enemyTeam=game->teams[ti]; + Uint32 me=team->me; + if ((team->enemies&enemyTeam->me)==0) + continue; + Building **enemyBuildings=enemyTeam->myBuildings; + for (int bi=0; biseenByMask&me)==0) || b->locked[canSwim]) + continue; + int level=b->type->level; + if (bestLevelmapHeader.getNumberOfTeams(); ti++) + { + int score=0; + Team *enemyTeam=game->teams[ti]; + Uint32 me=team->me; + if ((team->enemies&enemyTeam->me)==0) + continue; + Building **enemyBuildings=enemyTeam->myBuildings; + for (int bi=0; biseenByMask&me)==0) || b->locked[canSwim] || b->type->leveltype->shortTypeNum; + if (shortTypeNum==IntBuildingType::ATTACK_BUILDING + || shortTypeNum==IntBuildingType::SCIENCE_BUILDING) + score+=AI_CASTOR_STRIKE_TEAM_SCORE_HIGH; + else + score+=AI_CASTOR_STRIKE_TEAM_SCORE_LOW; + } + if (bestScorew; + //int h=map->h; + int wMask=map->wMask; + int hMask=map->hMask; + //int hDec=map->hDec; + int wDec=map->wDec; + + Uint32 bestScore=0; + Building *bestBuilding=NULL; + Team *enemyTeam=game->teams[strikeTeam]; + Uint32 me=team->me; + Building **enemyBuildings=enemyTeam->myBuildings; + for (int bi=0; biseenByMask&me)==0) || b->locked[canSwim]) + continue; + int x=b->posX; + int y=b->posY; + size_t index=(x&wMask)+((y&hMask)<type->level; + Uint32 score=(AI_CASTOR_STRIKE_BUILDING_SCORE_BIAS+workRange)*(AI_CASTOR_STRIKE_BUILDING_SCORE_BIAS+level); + if (b->type->isBuildingSite) + score=(score>>AI_CASTOR_STRIKE_BUILDING_SITE_SHIFT); + int shortTypeNum=b->type->shortTypeNum; + if (shortTypeNum==IntBuildingType::ATTACK_BUILDING + ||shortTypeNum==IntBuildingType::SCIENCE_BUILDING) + score=(score< *virtualBuildings=&team->virtualBuildings; + if (bestBuilding!=NULL) + { + Sint32 x=bestBuilding->posX+1; + Sint32 y=bestBuilding->posY+1; + + if (warFlagsRealbuildingsTypes.getTypeNum("warflag", 0, false); + return shared_ptr(new OrderCreate(team->teamNumber, x, y, typeNum, 1, 1)); + } + else + { + Sint32 maxSqDist=0; + Building *maxFlag=NULL; + for (std::list::iterator it=virtualBuildings->begin(); it!=virtualBuildings->end(); ++it) + if ((*it)->type->shortTypeNum==IntBuildingType::WAR_FLAG) + { + Sint32 dx=x-(*it)->posX; + Sint32 dy=y-(*it)->posY; + Sint32 sqDist=dx*dx+dy*dy; + if (maxSqDistAI_CASTOR_FLAG_MOVE_SQ_DIST && maxFlag!=NULL) + { + return shared_ptr(new OrderMoveFlag(maxFlag->gid, x, y, true)); + } + for (std::list::iterator it=virtualBuildings->begin(); it!=virtualBuildings->end(); ++it) + if ((*it)->type->shortTypeNum==IntBuildingType::WAR_FLAG + && (*it)->maxUnitWorking(new OrderModifyBuilding((*it)->gid, AI_CASTOR_WARFLAG_WORKER_GOAL)); + } + } + } + else + { + for (std::list::iterator it=virtualBuildings->begin(); it!=virtualBuildings->end(); ++it) + if ((*it)->type->shortTypeNum==IntBuildingType::WAR_FLAG) + { + return shared_ptr(new OrderDelete((*it)->gid)); + } + strikeTeamSelected=false; + onStrike=false; + } + + return shared_ptr(); +} + + + diff --git a/src/ai/castor/GetOrder.cpp b/src/ai/castor/GetOrder.cpp new file mode 100644 index 000000000..f6b98100d --- /dev/null +++ b/src/ai/castor/GetOrder.cpp @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "AICastor.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "Unit.h" +#include "Utilities.h" + +#define AI_FILE_MIN_VERSION 1 +#define AI_FILE_VERSION 2 + +using std::shared_ptr; + + +std::shared_ptrAICastor::getOrder() +{ + timer++; + + if (!strategy.defined) + defineStrategy(); + + if (computeBoot(new NullOrder()); + } + else if (computeBootw*map->h; + Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; + for (int i=0; i<4; i++) + memcpy(oldWheatGradient[i], wheatGradient, size); + for (int i=0; i<2; i++) + memset(wheatCareMap[i], 1, size); + } + break; + case 13: + computeWheatGrowthMap(); + break; + case 14: + computeEnemyPowerMap(); + break; + case 15: + computeEnemyRangeMap(); + break; + case 16: + computeEnemyWarriorsMap(); + break; + default: + assert(false); + } + computeBoot++; + return shared_ptr(new NullOrder()); + } + + if ((timer&AI_CASTOR_WHEAT_HISTORY_INTERVAL_MASK)==0) + { + Uint8 *temp=oldWheatGradient[3]; + for (int i=3; i>0; i--) + oldWheatGradient[i]=oldWheatGradient[i-1]; + oldWheatGradient[0]=temp; + Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; + memcpy(oldWheatGradient[0], wheatGradient, map->w*map->h); + computeObstacleUnitMap(); + computeWheatCareMap(); + } + + /*// Defense, we check it first, because it will only return true if there is an attack and free warriors + { + std::shared_ptrorder = controlBaseDefense(); + if (order) + return order; + }*/ + + //printf("getOrder(), %d projects\n", projects.size()); + for (std::list::iterator pi=projects.begin(); pi!=projects.end();) + if ((*pi)->finished) + { + //printf("deleting project (%s)\n", (*pi)->debugName); + delete *pi; + pi=projects.erase(pi); + } + else + pi++; + bool blocking=false; + for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) + if ((*pi)->blocking) + blocking=true; + + computeBuildingSum(); + + if (!blocking)// No blocking project, we can start a new one: + addProjects(); + Sint32 priority=AICastor::AI_CASTOR_PRIORITY_NONE; + for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) + if (priority>(*pi)->priority && (*pi)->critical) + priority=(*pi)->priority; + + if (timer>controlSwarmsTimer) + { + computeWarLevel(); + controlSwarmsTimer=timer+AI_CASTOR_CONTROL_SWARMS_INTERVAL; // each 10s + std::shared_ptrorder=controlSwarms(); + if (order) + return order; + } + + //bool critical=false; + //for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) + // if ((*pi)->critical) + // critical=true; + + int minReal=Building::MAX_COUNT; + for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) + if ((*pi)->priority<=priority) + { + int real=buildingSum[(*pi)->shortTypeNum][0]; + if (minReal>real) + minReal=real; + } + for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) + if ((*pi)->priority<=priority) + { + int real=buildingSum[(*pi)->shortTypeNum][0]; + if (real<=minReal) + { + std::shared_ptrorder=continueProject(*pi); + if (order) + return order; + } + } + for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) + if ((*pi)->priority<=priority) + { + int real=buildingSum[(*pi)->shortTypeNum][0]; + if (real>minReal) + { + std::shared_ptrorder=continueProject(*pi); + if (order) + return order; + } + } + + if (priority>0 && timer>expandFoodTimer) + { + expandFoodTimer=timer+AI_CASTOR_EXPAND_FOOD_INTERVAL; // each 10s + std::shared_ptrorder=expandFood(); + if (order) + return order; + } + + if (timer>lastEnemyRangeMapComputed+AI_CASTOR_ENEMY_RANGE_REFRESH) // each 41s + { + computeEnemyRangeMap(); + } + if (timer>lastEnemyWarriorsMapComputed+AI_CASTOR_ENEMY_WARRIORS_REFRESH) // each 41s + { + computeEnemyWarriorsMap(); + } + + /*if (onStrike) + { + if (timer>lastEnemyPowerMapComputed+AI_CASTOR_ENEMY_POWER_STRIKE_REFRESH) // each 5s + computeEnemyPowerMap(); + } + else + { + if (timer>lastEnemyPowerMapComputed+AI_CASTOR_ENEMY_POWER_IDLE_REFRESH) // each 2min44s + computeEnemyPowerMap(); + }*/ + + if (priority>0) + { + std::shared_ptrorder=controlFood(); + if (order) + return order; + } + + if (priority>0) + { + std::shared_ptrorder=controlUpgrades(); + if (order) + return order; + } + + if (timer>controlStrikesTimer) + { + std::shared_ptrorder=controlStrikes(); + if (order) + return order; + } + + return shared_ptr(new NullOrder()); +} + +// Default build-policy table for AICastor::defineStrategy(). +// +// One row per hard building, indexed by IntBuildingType::Number 0..7 +// (SWARM, FOOD, HEAL, WALKSPEED, SWIMSPEED, ATTACK, SCIENCE, DEFENSE). +// Field names mirror Strategy::Build exactly so the table copies into +// strategy.build[i] field-for-field. +// +// finalWorkers is -1 for HEAL / WALKSPEED / SWIMSPEED / ATTACK / SCIENCE +// because the original defineStrategy() left those slots at the -1 set +// by the pre-fill loop (only SWARM, FOOD, DEFENSE were re-assigned). +// Encoding -1 explicitly here preserves identical post-init state. +// +// Behavior is byte-for-byte preserved vs the previous column-by-column +// init in GetOrder.cpp:257-333. Network checksums and replay output +// are unaffected. +namespace +{ + struct CastorStrategyDefaults + { + Sint32 successWait; + Sint32 isFreePart; + Sint32 warLevelTrigger; + Uint32 warTimeTrigger; + Sint32 warAmountTrigger; + Sint32 strikeWarPowerTriggerUp; + Sint32 strikeWarPowerTriggerDown; + Uint32 strikeTimeTrigger; + Sint32 maxAmountGoal; + }; + + // Per-hard-building base/new policy table. + // Column order matches Strategy::Build field order. + // Row order matches IntBuildingType::Number 0..7. + static constexpr AICastor::Strategy::Build DEFAULT_BUILD_POLICIES[AICastor::NB_HARD_BUILDING] = + { + // baseOrder, base, baseWorkers, baseUpgrade, finalWorkers, newOrder, news, newWorkers, newUpgrade + /* 0 SWARM_BUILDING */ { 1, 2, 2, 0, 2, 1, 1, 3, 0 }, + /* 1 FOOD_BUILDING */ { 4, 4, 3, 2, 1, 2, 7, 2, 3 }, + /* 2 HEAL_BUILDING */ { 5, 2, 1, 2, -1, 5, 5, 2, 5 }, + /* 3 WALKSPEED_BUILDING */ { 7, 1, 5, 0, -1, 6, 1, 4, 0 }, + /* 4 SWIMSPEED_BUILDING */ { 6, 1, 3, 0, -1, 7, 1, 4, 0 }, + /* 5 ATTACK_BUILDING */ { 2, 2, 2, 2, -1, 4, 2, 5, 2 }, + /* 6 SCIENCE_BUILDING */ { 0, 2, 5, 2, -1, 3, 2, 7, 2 }, + /* 7 DEFENSE_BUILDING */ { 3, 2, 2, 1, 2, 0, 10, 4, 10 }, + }; + + // Scalar strategy defaults set once per game by defineStrategy(). + // strikeTimeTrigger = 32768 ticks ≈ 21 min 51 s. + // isFreePart = 10 (denominator for "1/N of pop = excess"; "good in [3..20]" per source comment). + static constexpr CastorStrategyDefaults DEFAULTS = + { + /* successWait */ 0, // TODO: use a "lowDiscovered" flag instead + /* isFreePart */ 10, // good in [3..20] + /* warLevelTrigger */ 1, + /* warTimeTrigger */ 8192, + /* warAmountTrigger */ 3, + /* strikeWarPowerTriggerUp */ 4096, + /* strikeWarPowerTriggerDown */ 2048, + /* strikeTimeTrigger */ 32768, // 21 min 51 s + /* maxAmountGoal */ 10, + }; +} + +void AICastor::defineStrategy() +{ + strategy.defined=true; + + // Pre-fill all NB_BUILDING (=13) slots, including the non-hard + // EXPLORATION_FLAG / WAR_FLAG / CLEARING_FLAG / STONE_WALL / + // MARKET_BUILDING entries, with the "unset" sentinel for the three + // fields the original code touched in this pre-pass. The remaining + // six Build fields stay uninitialized for slots 8..12, matching the + // pre-refactor behavior (Strategy::Build has no default ctor). + for (int bi=0; bi +#include +#include +#include + +#include "AICastor.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "Unit.h" +#include "Utilities.h" + +#define AI_FILE_MIN_VERSION 1 +#define AI_FILE_VERSION 2 + +using std::shared_ptr; + +// AICastor::Project part: + +AICastor::Project::Project(IntBuildingType::Number shortTypeNum, const char *suffix) +{ + this->shortTypeNum=shortTypeNum; + init(suffix); +} +AICastor::Project::Project(IntBuildingType::Number shortTypeNum, int amount, Sint32 mainWorkers, const char *suffix) +{ + this->shortTypeNum=shortTypeNum; + init(suffix); + this->amount=amount; + this->mainWorkers=mainWorkers; +} +void AICastor::Project::init(const char *suffix) +{ + amount=AI_CASTOR_PROJECT_DEFAULT_AMOUNT; + food=(this->shortTypeNum==IntBuildingType::SWARM_BUILDING + || this->shortTypeNum==IntBuildingType::FOOD_BUILDING); + defense=(this->shortTypeNum==IntBuildingType::DEFENSE_BUILDING); + + debugStdName += IntBuildingType::typeFromShortNumber(this->shortTypeNum); + debugStdName += "-"; + debugStdName += suffix; + this->debugName=debugStdName.c_str(); + + //printf("new project(%s)\n", debugName); + + subPhase=AI_CASTOR_SUBPHASE_BOOT; + + successWait=0; + blocking=true; + critical=false; + priority=AI_CASTOR_PROJECT_DEFAULT_PRIORITY; + triesLeft=AI_CASTOR_PROJECT_TRIES_LEFT; + + mainWorkers=AI_CASTOR_WORKERS_UNSET; + foodWorkers=AI_CASTOR_WORKERS_UNSET; + otherWorkers=AI_CASTOR_WORKERS_UNSET; + + multipleStart=false; + waitFinished=false; + finalWorkers=AI_CASTOR_WORKERS_UNSET; + + finished=false; + + timer=AI_CASTOR_TIMER_NEVER; +} + + +// AICastor::Strategy part: + +AICastor::Strategy::Strategy() +{ + defined=false; + + successWait=0; + + warLevelTrigger=0; + warTimeTrigger=0; + maxAmountGoal=0; +}; + +// AICastor main class part: + +void AICastor::firstInit() +{ + obstacleUnitMap=NULL; + obstacleBuildingMap=NULL; + spaceForBuildingMap=NULL; + buildingNeighbourMap=NULL; + + workPowerMap=NULL; + workRangeMap=NULL; + workAbilityMap=NULL; + hydratationMap=NULL; + notGrassMap=NULL; + wheatGrowthMap=NULL; + for (int i=0; i<4; i++) + oldWheatGradient[i]=NULL; + for (int i=0; i<2; i++) + wheatCareMap[i]=NULL; + + goodBuildingMap=NULL; + + enemyWarriorsMap=NULL; + enemyPowerMap=NULL; + enemyRangeMap=NULL; + + ressourcesCluster=NULL; +} + +AICastor::AICastor(Player *player) +{ + firstInit(); + init(player); +} + +AICastor::AICastor(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + firstInit(); + bool goodLoad=load(stream, player, versionMinor); + assert(goodLoad); +} + +void AICastor::init(Player *player) +{ + assert(player); + + // Logical : + timer=0; + canSwim=false; + needSwim=false; + lastFreeWorkersComputed=AI_CASTOR_TIMER_NEVER; + lastWheatGrowthMapComputed=AI_CASTOR_TIMER_NEVER; + lastEnemyRangeMapComputed=AI_CASTOR_TIMER_NEVER; + lastEnemyPowerMapComputed=AI_CASTOR_TIMER_NEVER; + lastEnemyWarriorsMapComputed=AI_CASTOR_TIMER_NEVER; + computeNeedSwimTimer=0; + controlSwarmsTimer=0; + expandFoodTimer=0; + controlFoodTimer=0; + controlUpgradeTimer=0; + controlUpgradeDelay=AI_CASTOR_UPGRADE_DELAY_TICKS; + controlStrikesTimer=0; + + warLevel=0; + warTimeTriggerLevel=0; + warLevelTriggerLevel=0; + warAmountTriggerLevel=0; + + onStrike=false; + strikeTimeTrigger=0; + strikeTeamSelected=false; + strikeTeam=0; + + foodWarning=false; + foodLock=false; + foodSurplus=false; + foodLockStats[0]=0; + foodLockStats[1]=0; + overWorkers=false; + starvingWarning=false; + starvingWarningStats[0]=0; + starvingWarningStats[1]=0; + buildsAmount=0; + + + for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) + delete *pi; + projects.clear(); + + // Structural: + this->player=player; + this->team=player->team; + this->game=player->game; + this->map=player->map; + + assert(this->team); + assert(this->game); + assert(this->map); + + size_t size=map->w*map->h; + assert(size>0); + + computeBoot=0; + + if (obstacleUnitMap!=NULL) + delete[] obstacleUnitMap; + obstacleUnitMap=new Uint8[size]; + + if (obstacleBuildingMap!=NULL) + delete[] obstacleBuildingMap; + obstacleBuildingMap=new Uint8[size]; + + if (spaceForBuildingMap!=NULL) + delete[] spaceForBuildingMap; + spaceForBuildingMap=new Uint8[size]; + + if (buildingNeighbourMap!=NULL) + delete[] buildingNeighbourMap; + buildingNeighbourMap=new Uint8[size]; + + + if (workPowerMap!=NULL) + delete[] workPowerMap; + workPowerMap=new Uint8[size]; + + if (workRangeMap!=NULL) + delete[] workRangeMap; + workRangeMap=new Uint8[size]; + + if (workAbilityMap!=NULL) + delete[] workAbilityMap; + workAbilityMap=new Uint8[size]; + + if (hydratationMap!=NULL) + delete[] hydratationMap; + hydratationMap=new Uint8[size]; + + if (notGrassMap!=NULL) + delete[] notGrassMap; + notGrassMap=new Uint8[size]; + + if (wheatGrowthMap!=NULL) + delete[] wheatGrowthMap; + wheatGrowthMap=new Uint8[size]; + + for (int i=0; i<4; i++) + { + if (oldWheatGradient[i]!=NULL) + delete[] oldWheatGradient[i]; + oldWheatGradient[i]=new Uint8[size]; + } + + for (int i=0; i<2; i++) + { + if (wheatCareMap[i]!=NULL) + delete[] wheatCareMap[i]; + wheatCareMap[i]=new Uint8[size]; + } + + if (goodBuildingMap!=NULL) + delete[] goodBuildingMap; + goodBuildingMap=new Uint8[size]; + + if (enemyPowerMap!=NULL) + delete[] enemyPowerMap; + enemyPowerMap=new Uint8[size]; + + if (enemyRangeMap!=NULL) + delete[] enemyRangeMap; + enemyRangeMap=new Uint8[size]; + + if (enemyWarriorsMap!=NULL) + delete[] enemyWarriorsMap; + enemyWarriorsMap=new Uint8[size]; + + if (ressourcesCluster!=NULL) + delete[] ressourcesCluster; + ressourcesCluster=new Uint16[size]; +} + +AICastor::~AICastor() +{ + if (obstacleUnitMap!=NULL) + delete[] obstacleUnitMap; + + if (obstacleBuildingMap!=NULL) + delete[] obstacleBuildingMap; + + if (spaceForBuildingMap!=NULL) + delete[] spaceForBuildingMap; + + if (buildingNeighbourMap!=NULL) + delete[] buildingNeighbourMap; + + + if (workPowerMap!=NULL) + delete[] workPowerMap; + + if (workRangeMap!=NULL) + delete[] workRangeMap; + + if (workAbilityMap!=NULL) + delete[] workAbilityMap; + + if (hydratationMap!=NULL) + delete[] hydratationMap; + + if (notGrassMap!=NULL) + delete[] notGrassMap; + + if (wheatGrowthMap!=NULL) + delete[] wheatGrowthMap; + + for (int i=0; i<4; i++) + if (oldWheatGradient[i]!=NULL) + delete[] oldWheatGradient[i]; + + for (int i=0; i<2; i++) + if (wheatCareMap[i]!=NULL) + delete[] wheatCareMap[i]; + + if (goodBuildingMap!=NULL) + delete[] goodBuildingMap; + + if (enemyPowerMap!=NULL) + delete[] enemyPowerMap; + + if (enemyRangeMap!=NULL) + delete[] enemyRangeMap; + + if (enemyWarriorsMap!=NULL) + delete[] enemyWarriorsMap; + + if (ressourcesCluster!=NULL) + delete[] ressourcesCluster; + + for(std::list::iterator i=projects.begin(); i!=projects.end(); ++i) + { + delete *i; + } + +} + +bool AICastor::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + init(player); + assert(game); + + stream->readEnterSection("AICastor"); + Sint32 aiFileVersion = stream->readSint32("aiFileVersion"); + if (aiFileVersionreadLeaveSection(); + return false; + } + if (aiFileVersion>=1) + timer = stream->readUint32("timer"); + else + timer=0; + + stream->readLeaveSection(); + return true; +} + +void AICastor::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("AICastor"); + stream->writeSint32(AI_FILE_VERSION, "aiFileVersion"); + stream->writeUint32(timer, "timer"); + stream->writeLeaveSection(); +} diff --git a/src/ai/castor/Maps.cpp b/src/ai/castor/Maps.cpp new file mode 100644 index 000000000..32285370e --- /dev/null +++ b/src/ai/castor/Maps.cpp @@ -0,0 +1,751 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "AICastor.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapInternal.h" +#include "Order.h" +#include "Player.h" +#include "Unit.h" +#include "Utilities.h" + +#define AI_FILE_MIN_VERSION 1 +#define AI_FILE_VERSION 2 + +using std::shared_ptr; + +void AICastor::computeObstacleUnitMap() +{ + //printf("computeObstacleUnitMap()...\n"); + int w=map->w; + int h=map->h; + //int wMask=map->wMask; + //int hMask=map->hMask; + size_t size=w*h; + const auto& cases=map->cases; + Uint32 teamMask=team->me; + for (size_t i=0; i=AI_CASTOR_TERRAIN_WATER_FIRST) && (c.terrainw; + int h=map->h; + //int wMask=map->wMask; + //int hMask=map->hMask; + //int hDec=map->hDec; + //int wDec=map->wDec; + size_t size=w*h; + const auto& cases=map->cases; + for (size_t i=0; i=AI_CASTOR_TERRAIN_GRASS_COUNT) // if (!isGrass) + obstacleBuildingMap[i]=0; + else if (c.ressource.type!=NO_RES_TYPE) + obstacleBuildingMap[i]=0; + else + obstacleBuildingMap[i]=1; + } + //printf("...computeObstacleBuildingMap() done\n"); +} + +void AICastor::computeSpaceForBuildingMap(int max) +{ + //printf("computeSpaceForBuildingMap()...\n"); + int w=map->w; + int h=map->h; + int wMask=map->wMask; + int hMask=map->hMask; + //int hDec=map->hDec; + //int wDec=map->wDec; + size_t size=w*h; + + memcpy(spaceForBuildingMap, obstacleBuildingMap, size); + + for (int i=1; iobs[i]) + min=obs[i]; + if (min!=0) + spaceForBuildingMap[wyx[0]]=min+1; + } + } + } + //printf("...computeSpaceForBuildingMap() done\n"); +} + +void AICastor::computeBuildingNeighbourMapOfBuilding(int bx, int by, int bw, int bh, int dw, int dh) +{ + //int w=map->w; + //int h=map->h; + int wMask=map->wMask; + int hMask=map->hMask; + //int hDec=map->hDec; + int wDec=map->wDec; + + //size_t size=w*h; + Uint8 *gradient=buildingNeighbourMap; + const auto& cases=map->cases; + + //Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; + + /*int bx=b->posX; + int by=b->posY; + int bw=b->type->width; + int bh=b->type->height;*/ + + // we skip building with already a neighbour: + bool neighbour=false; + //bool wheat=false; + for (int xi=bx-1; xi<=bx+bw; xi++) + { + int index; + index=(xi&wMask)+(((by-1 )&hMask)<w; + int h=map->h; + //size_t size=w*h; + + //int hDec=map->hDec; + int wDec=map->wDec; + + int wMask=map->wMask; + int hMask=map->hMask; + + Uint8 *gradient=buildingNeighbourMap; + //memset(gradient, 0, size); + Uint32 visionMask=team->me; + for (int y=0; ymapDiscovered[index]&visionMask)) + goto doubleBreak; + } + gradient[(y<game; + for (Sint32 ti=0; timapHeader.getNumberOfTeams(); ti++) + { + Team *team=game->teams[ti]; + assert(team); + if (!team) + continue; + Building **myBuildings=team->myBuildings; + for (int i=0; itype->isVirtual) + { + int bx=b->posX; + int by=b->posY; + int bw=b->type->width; + int bh=b->type->height; + computeBuildingNeighbourMapOfBuilding(bx, by, bw, bh, dw, dh); + } + } + } + + for (std::list::iterator bpi=game->buildProjects.begin(); bpi!=game->buildProjects.end(); bpi++) + { + int bx=bpi->posX&map->getMaskW(); + int by=bpi->posY&map->getMaskH(); + //int teamNumber=bpi->teamNumber; + Sint32 typeNum=(bpi->typeNum); + BuildingType *bt=globalContainer->buildingsTypes.get(typeNum); + int bw=bt->width; + int bh=bt->height; + computeBuildingNeighbourMapOfBuilding(bx, by, bw, bh, dw, dh); + } +} + +void AICastor::computeWorkPowerMap() +{ + int w=map->w; + int h=map->h; + int wMask=map->wMask; + int hMask=map->hMask; + //int hDec=map->hDec; + int wDec=map->wDec; + size_t size=w*h; + Uint8 *gradient=workPowerMap; + Uint8 maxRange=AI_CASTOR_WORK_POWER_MAX_RANGE; + if (maxRange>w/AI_CASTOR_HALFMAP_DIV) + maxRange=w/AI_CASTOR_HALFMAP_DIV; + if (maxRange>h/AI_CASTOR_HALFMAP_DIV) + maxRange=h/AI_CASTOR_HALFMAP_DIV; + + memset(gradient, 0, size); + + Unit **myUnits=team->myUnits; + for (int i=0; itypeNum==WORKER && u->medical==0 && u->activity!=Unit::ACT_UPGRADING) + { + int range=((u->hungry-u->trigHungry)>>AI_CASTOR_HUNGER_RANGE_SHIFT)/u->race->hungryness; + if (range<0) + continue; + //printf(" range=%d\n", range); + if (range>maxRange) + range=maxRange; + int ux=u->posX; + int uy=u->posY; + static const int reducer=AI_CASTOR_POWER_STAMP_REDUCER; + { + Uint8 *gp=&gradient[(ux&wMask)+((uy&hMask)<>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + for (int r=1; r>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + for (int dx=-r; dx<=r; dx++) + { + Uint8 *gp=&gradient[((ux+dx)&wMask)+(((uy +r)&hMask)<>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + for (int dy=(1-r); dy>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + for (int dy=(1-r); dy>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + } + } + } +} + + +void AICastor::computeWorkRangeMap() +{ + int w=map->w; + int h=map->h; + int wMask=map->wMask; + int hMask=map->hMask; + //int hDec=map->hDec; + int wDec=map->wDec; + size_t size=w*h; + Uint8 *gradient=workRangeMap; + + memcpy(gradient, obstacleUnitMap, size); + + Unit **myUnits=team->myUnits; + for (int i=0; itypeNum==WORKER && u->medical==0 && u->activity!=Unit::ACT_UPGRADING) + { + int range=((u->hungry-u->trigHungry)>>AI_CASTOR_HUNGER_RANGE_SHIFT)/u->race->hungryness; + if (range<0) + continue; + //printf(" range=%d\n", range); + if (range>GRADIENT_AT_GOAL) + range=GRADIENT_AT_GOAL; + int index=(u->posX&wMask)+((u->posY&hMask)<w; + int h=map->h; + //int wMask=map->wMask; + //int hMask=map->hMask; + //int hDec=map->hDec; + //int wDec=map->wDec; + size_t size=w*h; + + for (size_t i=0; i>AI_CASTOR_WORK_ABILITY_NORM_SHIFT); + if (workAbility>GRADIENT_AT_GOAL) + workAbility=GRADIENT_AT_GOAL; + + workAbilityMap[i]=(Uint8)workAbility; + } +} + +void AICastor::computeHydratationMap() +{ + int w=map->w; + int h=map->h; + int wMask=map->wMask; + int hMask=map->hMask; + //int hDec=map->hDec; + int wDec=map->wDec; + size_t size=w*h; + + Uint16 *gradient=(Uint16 *)malloc(2*size); + memset(gradient, 0, 2*size); + const auto& cases=map->cases; + static const int range=AI_CASTOR_HYDRATATION_RANGE; + for (int y=0; y=AI_CASTOR_TERRAIN_SAND_FIRST)&&(t>AI_CASTOR_HYDRATATION_NORM_SHIFT; + if (valuew; + int h=map->h; + //int wMask=map->wMask; + //int hMask=map->hMask; + size_t size=w*h; + + memset(notGrassMap, 0, size); + + const auto& cases=map->cases; + for (size_t i=0; i16 (not >=16 like obstacleBuildingMap above) — see bug M6. + if (t>AI_CASTOR_TERRAIN_GRASS_COUNT)// if !GRASS + notGrassMap[i]=AI_CASTOR_GRADIENT_OBSTACLE_NO_OBSTACLE; + } + + updateGlobalGradientNoObstacle(notGrassMap); +} + +void AICastor::computeWheatCareMap() +{ + int w=map->w; + int h=map->h; + //int wMask=map->wMask; + //int hMask=map->hMask; + //int hDec=map->hDec; + //int wDec=map->wDec; + size_t size=w*h; + size_t sizeMask=(size-1); + //Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; + //Case *cases=map->cases; + //Uint32 teamMask=team->me; + + Uint8 *temp=wheatCareMap[1]; + wheatCareMap[1]=wheatCareMap[0]; + wheatCareMap[0]=temp; + + memcpy(wheatCareMap[0], obstacleUnitMap, size); + for (size_t i=0; i<=sizeMask; i++) + if (wheatCareMap[0][i]!=0 && notGrassMap[i]==AI_CASTOR_NOTGRASS_NEIGHBOUR_VAL && hydratationMap[i]>0 + && ((wheatCareMap[1][i]>AI_CASTOR_WHEATCARE_PREV_HIGH_THRESHOLD) + || ((oldWheatGradient[3][i]==AI_CASTOR_WHEAT_GRADIENT_PEAK || oldWheatGradient[2][i]==AI_CASTOR_WHEAT_GRADIENT_PEAK) && (oldWheatGradient[1][i]updateGlobalGradient(wheatCareMap[0]); +} + +void AICastor::computeWheatGrowthMap() +{ + if (lastWheatGrowthMapComputed==timer) + return; + + int w=map->w; + int h=map->h; + //int wMask=map->wMask; + //int hMask=map->hMask; + //int hDec=map->hDec; + //int wDec=map->wDec; + size_t size=w*h; + Uint8 *wheatGradient=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; + + memcpy(wheatGrowthMap, obstacleBuildingMap, size); + + for (size_t i=0; i>AI_CASTOR_WHEAT_GROWTH_HYDRATATION_SHIFT); + + map->updateGlobalGradient(wheatGrowthMap); + + for (size_t i=0; iAI_CASTOR_WHEAT_CARE_SUBTRACT_THRESHOLD) + { + Uint8 *p=&wheatGrowthMap[i]; + Uint8 growth=*p; + if (growth>care) + (*p)=growth-care; + else + (*p)=AI_CASTOR_WHEAT_GROWTH_MIN; + } + } + lastWheatGrowthMapComputed=timer; +} + +void AICastor::computeEnemyPowerMap() +{ + if (lastEnemyPowerMapComputed==timer) + return; + lastEnemyPowerMapComputed=timer; + + int w=map->w; + int h=map->h; + int wMask=map->wMask; + int hMask=map->hMask; + //int hDec=map->hDec; + int wDec=map->wDec; + size_t size=w*h; + Uint8 *gradient=enemyPowerMap; + + memset(gradient, 0, size); + + for (int ti=0; timapHeader.getNumberOfTeams(); ti++) + { + Team *enemyTeam=game->teams[ti]; + Uint32 me=team->me; + if ((team->enemies&enemyTeam->me)==0) + continue; + Building **enemyBuildings=enemyTeam->myBuildings; + for (int bi=0; biseenByMask&me)==0)) + continue; + int bx=b->posX; + int by=b->posY; + static const int reducer=AI_CASTOR_POWER_STAMP_REDUCER; + static const int range=AI_CASTOR_ENEMY_POWER_RANGE; // max 32 + { + Uint8 *gp=&gradient[(bx&wMask)+((by&hMask)<>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + for (int r=1; r>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + for (int dx=-r; dx<=r; dx++) + { + Uint8 *gp=&gradient[((bx+dx)&wMask)+(((by +r)&hMask)<>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + for (int dy=(1-r); dy>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + for (int dy=(1-r); dy>reducer); + if (sum>AI_CASTOR_UINT8_MAX_VALUE) + sum=AI_CASTOR_UINT8_MAX_VALUE; + *gp=sum; + } + } + } + } +} + +void AICastor::computeEnemyRangeMap() +{ + if (lastEnemyRangeMapComputed==timer) + return; + lastEnemyRangeMapComputed=timer; + + int w=map->w; + int h=map->h; + int wMask=map->wMask; + int hMask=map->hMask; + //int hDec=map->hDec; + int wDec=map->wDec; + size_t size=w*h; + Uint8 *gradient=enemyRangeMap; + + memcpy(gradient, obstacleUnitMap, size); + + for (int ti=0; timapHeader.getNumberOfTeams(); ti++) + { + Team *enemyTeam=game->teams[ti]; + Uint32 me=team->me; + + if ((team->enemies & enemyTeam->me)==0) + continue; + Building **enemyBuildings=enemyTeam->myBuildings; + for (int bi=0; biseenByMask&me)==0) || b->type->isBuildingSite) + continue; + int bx=b->posX; + int by=b->posY; + int bw=b->type->width; + int bh=b->type->height; + for (int dy=by; dyupdateGlobalGradient(gradient); +} + +void AICastor::computeEnemyWarriorsMap() +{ + if (lastEnemyWarriorsMapComputed==timer) + return; + lastEnemyWarriorsMapComputed=timer; + if (verbose) + printf("computeEnemyWarriorsMap()\n"); + + int w=map->w; + int h=map->h; + //int wMask=map->wMask; + //int hMask=map->hMask; + //int hDec=map->hDec; + //int wDec=map->wDec; + size_t size=w*h; + Uint8 *gradient=enemyWarriorsMap; + + memcpy(gradient, obstacleUnitMap, size); + for (size_t i=0; ifogOfWar[i]&team->me)==0) + continue; + Uint16 guid=map->cases[i].groundUnit; + if (guid==NOGUID) + continue; + Uint32 teamMask=(1<<(guid>>AI_CASTOR_GUID_TEAM_SHIFT)); + if ((teamMask&team->enemies)==0) + continue; + gradient[i]=AI_CASTOR_ENEMY_WARRIOR_GRADIENT_SEED; + } + map->updateGlobalGradient(gradient); +} + diff --git a/src/ai/castor/Placement.cpp b/src/ai/castor/Placement.cpp new file mode 100644 index 000000000..7759592e1 --- /dev/null +++ b/src/ai/castor/Placement.cpp @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "AICastor.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "Unit.h" +#include "Utilities.h" + +#define AI_FILE_MIN_VERSION 1 +#define AI_FILE_VERSION 2 + +using std::shared_ptr; + +std::shared_ptrAICastor::findGoodBuilding(Sint32 typeNum, bool food, bool defense, bool critical) +{ + int w=map->w; + int h=map->h; + int bw=globalContainer->buildingsTypes.get(typeNum)->width; + int bh=globalContainer->buildingsTypes.get(typeNum)->height; + assert(bw==bh); + //int hDec=map->hDec; + int wDec=map->wDec; + int wMask=map->wMask; + int hMask=map->hMask; + size_t size=w*h; + Uint32 *mapDiscovered=&(map->mapDiscovered[0]); + Uint32 me=team->me; + + // minWork computation: + Sint32 bestWorkScore=AI_CASTOR_BEST_WORK_SCORE_FLOOR; + for (size_t i=0; iAI_CASTOR_MINWORK_CRITICAL_CAP_PER_CORNER*AI_CASTOR_CORNERS) + minWork=AI_CASTOR_MINWORK_CRITICAL_CAP_PER_CORNER*AI_CASTOR_CORNERS; + } + else + { + if (minWork>AI_CASTOR_MINWORK_NORMAL_CAP_PER_CORNER*AI_CASTOR_CORNERS) + minWork=AI_CASTOR_MINWORK_NORMAL_CAP_PER_CORNER*AI_CASTOR_CORNERS; + } + + // wheatGradientLimit computation: + Uint32 wheatGradientLimit; + if (food) + { + if (critical) + wheatGradientLimit=(AI_CASTOR_WHEAT_GRADIENT_PEAK-AI_CASTOR_WHEAT_GRADIENT_CRITICAL_FOOD_OFFSET)*AI_CASTOR_CORNERS; + else + wheatGradientLimit=(AI_CASTOR_WHEAT_GRADIENT_PEAK-AI_CASTOR_WHEAT_GRADIENT_NORMAL_FOOD_OFFSET)*AI_CASTOR_CORNERS; + } + else + { + if (critical) + wheatGradientLimit=(AI_CASTOR_WHEAT_GRADIENT_PEAK-AI_CASTOR_WHEAT_GRADIENT_CRITICAL_OTHER_OFFSET)*AI_CASTOR_CORNERS; + else + wheatGradientLimit=(AI_CASTOR_WHEAT_GRADIENT_PEAK-AI_CASTOR_WHEAT_GRADIENT_NORMAL_OTHER_OFFSET)*AI_CASTOR_CORNERS; + } + + // we find the best place possible: + size_t bestIndex=0; + Sint32 bestScore=0; + + //wheatLimit=(wheatLimit<<2); + //printf(" (scaled) minWork=%d, wheatLimit=%d\n", minWork, wheatLimit); + + Uint8 *wheatGradientMap=map->ressourcesGradient[team->teamNumber][CORN][canSwim]; + memset(goodBuildingMap, 0, size); + + for (int y=0; ywheatGradientLimit) + continue; + //if (wheatGrowth>wheatGrowthLimit) + // continue; + } + } + //goodBuildingMap[corner0]=4; + + Uint32 enemyRange=enemyRangeMap[corner0]+enemyRangeMap[corner1]+enemyRangeMap[corner2]+enemyRangeMap[corner3]; + if (enemyRange>AI_CASTOR_CORNERS*(AI_CASTOR_WHEAT_GRADIENT_PEAK-AI_CASTOR_ENEMY_RANGE_REJECT_OFFSET)) + continue; + //goodBuildingMap[corner0]=5; + + Sint32 wheatGrowth=wheatGrowthMap[corner0]+wheatGrowthMap[corner1]+wheatGrowthMap[corner2]+wheatGrowthMap[corner3]; + + Uint8 neighbour=buildingNeighbourMap[corner0]; + Uint8 directNeighboursCount=(neighbour>>AI_CASTOR_NEIGHBOUR_DIRECT_SHIFT)&AI_CASTOR_NEIGHBOUR_MASK; // [0, 7] + Uint8 farNeighboursCount=(neighbour>>AI_CASTOR_NEIGHBOUR_FAR_SHIFT)&AI_CASTOR_NEIGHBOUR_MASK; // [0, 7] + if ((neighbour&AI_CASTOR_NEIGHBOUR_DIRTY_BIT)||(directNeighboursCount>AI_CASTOR_NEIGHBOUR_MAX_DIRECT)) + continue; + + //goodBuildingMap[corner0]=6; + + Sint32 score; + if (defense) + score=((work<>AI_CASTOR_SCORE_FOOD_GRADIENT_SHIFT)-enemyRange)*(AI_CASTOR_SCORE_FOOD_NEIGHBOUR_BIAS+(directNeighboursCount<>AI_CASTOR_DEFENSE_SCORE_NORMALISE_SHIFT)>=AI_CASTOR_DEFENSE_SCORE_CAP) + goodBuildingMap[corner0]=AI_CASTOR_DEFENSE_SCORE_CAP; + else + goodBuildingMap[corner0]=(score>>AI_CASTOR_DEFENSE_SCORE_NORMALISE_SHIFT); + } + + if (bestScore0) + { + Sint32 x=(bestIndex&map->wMask); + Sint32 y=((bestIndex>>map->wDec)&map->hMask); + return shared_ptr(new OrderCreate(team->teamNumber, x, y, typeNum, 1, 1)); + } + + return shared_ptr(); +} + +void AICastor::computeRessourcesCluster() +{ + int w=map->w; + int h=map->h; + //int wMask=map->wMask; + int hMask=map->hMask; + size_t size=w*h; + + memset(ressourcesCluster, 0, size*2); + + //int i=0; + Uint8 old=NO_RES_TYPE; + Uint16 id=0; + bool usedid[AI_CASTOR_CLUSTER_ID_SPACE]; + memset(usedid, 0, AI_CASTOR_CLUSTER_ID_SPACE*sizeof(bool)); + for (int y=0; ycases[map->coordToIndex(x, y)]; // case + const auto& r=c.ressource; // ressource + Uint8 rt=r.type; // ressources type + + int rci=x+y*w; // ressource cluster index + Uint16 *rcp=&ressourcesCluster[rci]; // ressource cluster pointer + Uint16 rc=*rcp; // ressource cluster + + if (rt==NO_RES_TYPE) + { + *rcp=0; + old=NO_RES_TYPE; + } + else + { + if (rt!=old) + { + id=AI_CASTOR_CLUSTER_FIRST_ID; + while (usedid[id]) + id++; + if (id) + usedid[id]=true; + old=rt; + } + if (rc!=id) + { + if (rc==0) + { + *rcp=id; + } + else + { + Uint16 oldid=id; + usedid[oldid]=false; + id=rc; // newid + // We have to correct last ressourcesCluster values: + *rcp=id; + while (*rcp==oldid) + { + *rcp=id; + rcp--; + } + } + } + } + } + memcpy(ressourcesCluster+((y+1)&hMask)*w, ressourcesCluster+y*w, w*2); + } + +} + +void AICastor::updateGlobalGradientNoObstacle(Uint8 *gradient) +{ + //In this algotithm, "l" stands for one case at Left, "r" for one case at Right, "u" for Up, and "d" for Down. + // Warning, this is *nearly* a copy-past, 4 times, once for each direction. + int w=map->w; + int h=map->h; + int hMask=map->hMask; + int wMask=map->wMask; + //int hDec=map->hDec; + int wDec=map->wDec; + + for (int yi=0; yimax) + max=side[i]; + if (max==0) + gradient[wy+x]=0; + else + gradient[wy+x]=max-1; + } + } + } + + for (int y=hMask; y>=0; y--) + { + int wy=(y<max) + max=side[i]; + if (max==0) + gradient[wy+x]=0; + else + gradient[wy+x]=max-1; + } + } + } + + for (int x=0; xmax) + max=side[i]; + if (max==0) + gradient[wy+x]=0; + else + gradient[wy+x]=max-1; + } + } + } + + for (int x=wMask; x>=0; x--) + { + int xr=(x+1)&wMask; + for (int yi=x; yi<(x+h); yi++) + { + int wy=((yi&hMask)<max) + max=side[i]; + if (max==0) + gradient[wy+x]=0; + else + gradient[wy+x]=max-1; + } + } + } +} + +void AICastor::updateGlobalGradient(Uint8 *gradient) +{ + //In this algotithm, "l" stands for one case at Left, "r" for one case at Right, "u" for Up, and "d" for Down. + // Warning, this is *nearly* a copy-past, 4 times, once for each direction. + + int w=map->w; + int h=map->h; + int hMask=map->hMask; + int wMask=map->wMask; + //int hDec=map->hDec; + int wDec=map->wDec; + + for (int yi=0; yimax) + max=side[i]; + if (max==1) + gradient[wy+x]=1; + else + gradient[wy+x]=max-1; + } + } + } + + for (int y=hMask; y>=0; y--) + { + int wy=(y<max) + max=side[i]; + if (max==1) + gradient[wy+x]=1; + else + gradient[wy+x]=max-1; + } + } + } + + for (int x=0; xmax) + max=side[i]; + if (max==1) + gradient[wy+x]=1; + else + gradient[wy+x]=max-1; + } + } + } + + for (int x=wMask; x>=0; x--) + { + int xr=(x+1)&wMask; + for (int yi=x; yi<(x+h); yi++) + { + int wy=((yi&hMask)<max) + max=side[i]; + if (max==1) + gradient[wy+x]=1; + else + gradient[wy+x]=max-1; + } + } + } +} diff --git a/src/ai/castor/Projects.cpp b/src/ai/castor/Projects.cpp new file mode 100644 index 000000000..43109c3d3 --- /dev/null +++ b/src/ai/castor/Projects.cpp @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "AICastor.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "Unit.h" +#include "Utilities.h" + +#define AI_FILE_MIN_VERSION 1 +#define AI_FILE_VERSION 2 + +using std::shared_ptr; + +bool AICastor::addProject(Project *project) +{ + if (buildingSum[project->shortTypeNum][0]>=project->amount) + { + delete project; + return false; + } + for (std::list::iterator pi=projects.begin(); pi!=projects.end(); pi++) + if (project->shortTypeNum==(*pi)->shortTypeNum) + { + if (project->amount<=(*pi)->amount) + { + (*pi)->timer=timer; + delete project; + return false; + } + else + { + delete (*pi); + projects.erase(pi); + projects.push_back(project); + return true; + } + } + projects.push_back(project); + return true; +} + +void AICastor::addProjects() +{ + //printf(" canFeedUnit=%d, swarms=%d, pool=%d+%d, attaque=%d+%d, speed=%d+%d\n", + // canFeedUnit, swarms, pool, poolSite, attaque, attaqueSite, speed, speedSite); + + buildsAmount=-1; + + if (buildingSum[IntBuildingType::FOOD_BUILDING][0]==0) + { + Project *project=new Project(IntBuildingType::FOOD_BUILDING, "boot"); + + project->successWait=strategy.successWait; + project->critical=true; + project->priority=AI_CASTOR_PROJECT_PRIORITY_CRITICAL; + project->food=true; + + project->mainWorkers=AI_CASTOR_BOOT_FOOD_MAIN_WORKERS; + project->foodWorkers=AI_CASTOR_BOOT_FOOD_FOOD_WORKERS; + project->otherWorkers=AI_CASTOR_BOOT_OTHER_WORKERS_OFF; + + project->multipleStart=true; + project->waitFinished=true; + project->finalWorkers=AI_CASTOR_BOOT_FOOD_FINAL_WORKERS; + + if (addProject(project)) + return; + } + if (buildingSum[IntBuildingType::SWARM_BUILDING][0]+buildingSum[IntBuildingType::SWARM_BUILDING][1]==0) + { + Project *project=new Project(IntBuildingType::SWARM_BUILDING, "boot"); + + project->successWait=strategy.successWait; + project->critical=true; + project->priority=AI_CASTOR_PROJECT_PRIORITY_CRITICAL; + project->food=true; + + project->mainWorkers=AI_CASTOR_BOOT_SWARM_MAIN_WORKERS; + project->foodWorkers=AI_CASTOR_BOOT_SWARM_FOOD_WORKERS; + project->otherWorkers=AI_CASTOR_BOOT_OTHER_WORKERS_OFF; + + project->multipleStart=false; + project->waitFinished=true; + project->finalWorkers=AI_CASTOR_BOOT_SWARM_FINAL_WORKERS; + + if (addProject(project)) + return; + } + if (buildingSum[IntBuildingType::SWIMSPEED_BUILDING][0]+buildingSum[IntBuildingType::SWIMSPEED_BUILDING][1]==0) + { + if (timer>computeNeedSwimTimer) + { + computeNeedSwimTimer=timer+AI_CASTOR_NEED_SWIM_REFRESH;// every 41s + computeNeedSwim(); + } + if (needSwim) + { + Project *project=new Project(IntBuildingType::SWIMSPEED_BUILDING, AI_CASTOR_BOOT_SWIM_AMOUNT, AI_CASTOR_BOOT_SWIM_MAIN_WORKERS, "boot"); + project->successWait=strategy.successWait; + project->critical=true; + project->priority=AI_CASTOR_PROJECT_PRIORITY_CRITICAL; + if (addProject(project)) + return; + } + } + if (buildingSum[IntBuildingType::ATTACK_BUILDING][0]+buildingSum[IntBuildingType::ATTACK_BUILDING][1]==0) + { + Project *project=new Project(IntBuildingType::ATTACK_BUILDING, AI_CASTOR_BOOT_ATTACK_AMOUNT, AI_CASTOR_BOOT_ATTACK_MAIN_WORKERS, "boot"); + project->successWait=strategy.successWait; + project->critical=true; + if (addProject(project)) + return; + } + /*if (buildingSum[IntBuildingType::WALKSPEED_BUILDING][0]+buildingSum[IntBuildingType::WALKSPEED_BUILDING][1]==0) + { + Project *project=new Project(IntBuildingType::WALKSPEED_BUILDING, 1, 7, "boot"); + project->successWait=strategy.successWait; + project->critical=true; + if (addProject(project)) + return; + } + if (buildingSum[IntBuildingType::HEAL_BUILDING][0]+buildingSum[IntBuildingType::HEAL_BUILDING][1]==0) + { + Project *project=new Project(IntBuildingType::HEAL_BUILDING, 1, 3, "boot"); + project->successWait=strategy.successWait; + project->critical=true; + project->multipleStart=true; + if (addProject(project)) + return; + } + if (buildingSum[IntBuildingType::SCIENCE_BUILDING][0]+buildingSum[IntBuildingType::SCIENCE_BUILDING][1]==0) + { + Project *project=new Project(IntBuildingType::SCIENCE_BUILDING, 1, 5, "boot"); + project->successWait=strategy.successWait; + project->critical=true; + if (addProject(project)) + return; + }*/ + // all critical projects succeded. + + // enough workers + //Strategy::Builds buildsCurrent=strategy.buildsBase; + buildsAmount=0; + if (!enoughFreeWorkers()) + return; + + for (int bpi=0; bpifoodLockStats[0] + || starvingWarning + || starvingWarningStats[1]>starvingWarningStats[0])) + continue; + Project *project=new Project((IntBuildingType::Number)bi, + strategy.build[bi].base, strategy.build[bi].baseWorkers, "base"); + project->successWait=strategy.successWait; + project->finalWorkers=strategy.build[bi].finalWorkers; + if (addProject(project)) + return; + } + buildsAmount=1; + + for (int bi=0; bifoodLockStats[0] + || starvingWarning + || starvingWarningStats[1]>starvingWarningStats[0])) + continue; + Project *project=new Project((IntBuildingType::Number)bi, + amountGoal[bi], strategy.build[bi].newWorkers+(agi-AI_CASTOR_TIER_WORKERS_SCALE_BIAS), "loop"); + project->successWait=strategy.successWait; + project->finalWorkers=strategy.build[bi].finalWorkers; + if (addProject(project)) + return; + } + buildsAmount=AI_CASTOR_BUILDS_TIER_BASE_MID+(agi<AICastor::continueProject(Project *project) +{ + // Phase alpha will make a new Food Building at any price. + //printf("(%s)(stn=%d, f=%d, w=[%d, %d, %d], ms=%d, wf=%d), sp=%d\n", + // project->debugName, + // project->shortTypeNum, project->food, + // project->mainWorkers, project->foodWorkers, project->otherWorkers, + // project->multipleStart, project->waitFinished, project->subPhase); + + if (timertimer+AI_CASTOR_PROJECT_STEP_INTERVAL) + return shared_ptr(); + + if (foodLock && !project->critical && project->shortTypeNum==IntBuildingType::SWARM_BUILDING) + { + if (starvingWarning) + project->timer=timer+AI_CASTOR_SWARM_STARVE_BACKOFF; // 5min28s + else + project->timer=timer+AI_CASTOR_SWARM_FOODLOCK_BACKOFF; // 1min22s + project->blocking=false; + project->critical=false; + } + + if (project->subPhase==AICastor::AI_CASTOR_SUBPHASE_BOOT) + { + // boot phase + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_CHECK_SITES; + } + else if (project->subPhase==AICastor::AI_CASTOR_SUBPHASE_FIND_PLACE) + { + if (!project->critical && !enoughFreeWorkers()) + { + project->timer=timer; + return shared_ptr(); + } + // find any good building place + + Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum(IntBuildingType::typeFromShortNumber(project->shortTypeNum), 0, true); + int bw=globalContainer->buildingsTypes.get(typeNum)->width; + int bh=globalContainer->buildingsTypes.get(typeNum)->height; + assert(bw==bh); + + computeCanSwim(); + computeObstacleBuildingMap(); + computeSpaceForBuildingMap(bw); + computeBuildingNeighbourMap(bw, bh); + computeObstacleUnitMap(); + computeWheatGrowthMap(); + computeWorkPowerMap(); + computeWorkRangeMap(); + computeWorkAbilityMap(); + + std::shared_ptrgfbm=findGoodBuilding(typeNum, project->food, project->defense, project->critical); + project->timer=timer; + if (gfbm) + { + if (project->successWait>0) + { + project->successWait--; + } + else + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_CHECK_SITES; + return gfbm; + } + } + else if (project->triesLeft>0) + { + project->triesLeft--; + } + else + { + project->timer=timer+AI_CASTOR_PROJECT_ABORT_BACKOFF; // 5min27s + project->blocking=false; + project->critical=false; + } + } + else if (project->subPhase==AICastor::AI_CASTOR_SUBPHASE_CHECK_SITES) + { + // do we have enough building sites ? + + int real=buildingSum[project->shortTypeNum][0]; + int site=buildingSum[project->shortTypeNum][1]; + int sum=real+site; + + if (real>=project->amount) + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_BALANCE_FINAL; + if (!project->waitFinished) + { + project->blocking=false; + project->critical=false; + } + } + else if (sumamount) + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_FIND_PLACE; + } + else + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_BALANCE_MAIN; + if (!project->waitFinished) + { + project->blocking=false; + project->critical=false; + } + } + } + else if (project->subPhase==AICastor::AI_CASTOR_SUBPHASE_BALANCE_MAIN) + { + // balance workers: + + int isFree=team->stats.getWorkersBalance(); + Sint32 mainWorkers=project->mainWorkers; + Sint32 finalWorkers=project->finalWorkers; + if (isFree<=AI_CASTOR_FREE_WORKERS_LOW) + { + if (mainWorkers>AI_CASTOR_FREE_WORKERS_LOW) + mainWorkers=((AI_CASTOR_FREE_WORKERS_LOW+mainWorkers)>>1); + //if (finalWorkers>AI_CASTOR_FREE_WORKERS_LOW) + // finalWorkers=AI_CASTOR_FREE_WORKERS_LOW; + } + else + { + if (mainWorkers>isFree) + mainWorkers=((isFree+mainWorkers)>>1); + //if (finalWorkers>isFree) + // finalWorkers=isFree; + } + + Building **myBuildings=team->myBuildings; + for (int i=0; itype->shortTypeNum==project->shortTypeNum) + { + if (b->type->isBuildingSite) + { + // a main building site + if (mainWorkers>=0 && b->maxUnitWorking!=mainWorkers) + { + b->maxUnitWorking=mainWorkers; + b->update(); + project->timer=timer; + return shared_ptr(new OrderModifyBuilding(b->gid, mainWorkers)); + } + } + else + { + // a main building + if (finalWorkers>=0 && b->maxUnitWorking!=finalWorkers) + { + b->maxUnitWorking=finalWorkers; + b->update(); + project->timer=timer; + return shared_ptr(new OrderModifyBuilding(b->gid, finalWorkers)); + } + } + } + else if (b->type->shortTypeNum==IntBuildingType::SWARM_BUILDING + || b->type->shortTypeNum==IntBuildingType::FOOD_BUILDING) + { + // food buildings + if (project->foodWorkers>=0 && b->maxUnitWorking!=project->foodWorkers) + { + b->maxUnitWorking=project->foodWorkers; + b->update(); + project->timer=timer; + return shared_ptr(new OrderModifyBuilding(b->gid, project->foodWorkers)); + } + } + else if (b->type->maxUnitWorking!=0) + { + // others buildings: + if (project->otherWorkers>=0 && b->maxUnitWorking!=project->otherWorkers) + { + b->maxUnitWorking=project->otherWorkers; + b->update(); + project->timer=timer; + return shared_ptr(new OrderModifyBuilding(b->gid, project->otherWorkers)); + } + } + } + } + + int real=buildingSum[project->shortTypeNum][0]; + int site=buildingSum[project->shortTypeNum][1]; + int sum=real+site; + + //printf("(%s) (all maxUnitWorking set)\n", project->debugName); + + if (real>=project->amount) + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_BALANCE_FINAL; + } + else if (sumamount) + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_FIND_PLACE; + } + else if (project->multipleStart) + { + if (isFree>AI_CASTOR_FREE_WORKERS_SPARE) + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_FIND_PLACE; + } + else + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_WAIT_FINISHED; + } + } + else + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_WAIT_FINISHED; + } + } + else if (project->subPhase==AICastor::AI_CASTOR_SUBPHASE_WAIT_FINISHED) + { + // We simply wait for the building to be finished, + // and add free workers if available and project.waitFinished: + + if ((project->waitFinished || overWorkers) && enoughFreeWorkers()) + { + Building **myBuildings=team->myBuildings; + for (int i=0; itype->shortTypeNum==project->shortTypeNum && b->maxUnitWorkingmainWorkers) + { + //printf("(%s) (incrementing workers) isFree=%d, current=%d\n", + // project->debugName, isFree, b->maxUnitWorking); + b->maxUnitWorking++; + b->update(); + project->timer=timer; + return shared_ptr(new OrderModifyBuilding(b->gid, b->maxUnitWorking)); + } + } + } + + int real=buildingSum[project->shortTypeNum][0]; + int site=buildingSum[project->shortTypeNum][1]; + int sum=real+site; + + if (real>=project->amount) + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_BALANCE_FINAL; + } + else if (sumamount) + { + project->subPhase=AICastor::AI_CASTOR_SUBPHASE_CHECK_SITES; + } + } + else if (project->subPhase==AICastor::AI_CASTOR_SUBPHASE_BALANCE_FINAL) + { + // balance final workers: + + if (project->blocking) + { + project->blocking=false; + project->critical=false; + } + + if (project->finalWorkers>=0) + { + Sint32 finalWorkers=project->finalWorkers; + + Building **myBuildings=team->myBuildings; + for (int i=0; itype->shortTypeNum==project->shortTypeNum && b->maxUnitWorking!=finalWorkers) + { + assert(b->type->maxUnitWorking!=0); + b->maxUnitWorking=finalWorkers; + b->update(); + project->timer=timer; + return shared_ptr(new OrderModifyBuilding(b->gid, finalWorkers)); + } + } + } + if (buildingSum[project->shortTypeNum][1]==0) + { + project->finished=true; + } + } + else + assert(false); + + return shared_ptr(); +} + diff --git a/src/ai/castor/State.cpp b/src/ai/castor/State.cpp new file mode 100644 index 000000000..2b68c478a --- /dev/null +++ b/src/ai/castor/State.cpp @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "AICastor.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "Unit.h" +#include "Utilities.h" + +#define AI_FILE_MIN_VERSION 1 +#define AI_FILE_VERSION 2 + +using std::shared_ptr; + +bool AICastor::enoughFreeWorkers() +{ + int totalWorkers=team->stats.getTotalUnits(WORKER); + int workersBalance=team->stats.getWorkersBalance(); + int partFree=(totalWorkers/strategy.isFreePart); + int minBalance; + if (buildsAmount<=0) + minBalance=-partFree; + else if (buildsAmount<=AI_CASTOR_BUILDS_LOW) + minBalance=0; + else if (buildsAmount<=AI_CASTOR_BUILDS_MID) + minBalance=partFree; + else + minBalance=(partFree<minBalance); + overWorkers=(workersBalance>minOverWorkers); + + assert(buildsAmountmyUnits; + int sumCanSwim=0; + int sumCantSwim=0; + for (int i=0; itypeNum==WORKER && u->medical==0) + { + if (u->performance[SWIM]>0) + sumCanSwim++; + else + sumCantSwim++; + } + } + + canSwim=(sumCanSwim>sumCantSwim); + //printf("...computeCanSwim() done\n"); +} + +void AICastor::computeNeedSwim() +{ + int w=map->w; + int h=map->h; + size_t size=w*h; + + canSwim=false; + computeObstacleUnitMap(); + computeWorkRangeMap(); + + Sint32 baseCount=0; + for (size_t i=0; i(AI_CASTOR_SWIM_GAIN_DENOM*extendedCount)); + + computeCanSwim(); +} + +void AICastor::computeBuildingSum() +{ + for (int bi=0; bimyBuildings; + for (int i=0; ibuildingState==Building::WAITING_FOR_CONSTRUCTION && b->constructionResultState==Building::UPGRADE) + buildingLevels[b->type->shortTypeNum][1][b->type->level+1]++; + else + buildingLevels[b->type->shortTypeNum][b->type->isBuildingSite][b->type->level]++; + } + } + for (int bi=0; bi0) + if ((timer&AI_CASTOR_VERBOSE_LOG_INTERVAL_MASK)==0) + if (verbose) + printf("buildingLevels[%d][%d][%d]=%d\n", bi, si, li, buildingLevels[bi][si][li]); +} + +void AICastor::computeWarLevel() +{ + if (timer>strategy.warTimeTrigger) + { + warTimeTriggerLevel++; + strategy.warTimeTrigger=strategy.warTimeTrigger+((AI_CASTOR_WARTIME_TRIGGER_GROWTH_BIAS+strategy.warTimeTrigger)>>AI_CASTOR_WARTIME_TRIGGER_GROWTH_SHIFT); + } + int warTimeTriggerLevelUse=warTimeTriggerLevel; + if (warTimeTriggerLevelUse>AI_CASTOR_WARTIME_LEVEL_CAP) + warTimeTriggerLevelUse=AI_CASTOR_WARTIME_LEVEL_CAP; + + int sum=0; + for (int si=0; si<2; si++) + for (int li=strategy.warLevelTrigger; liAI_CASTOR_WARLEVEL_BUILDINGS_HIGH) + warLevelTriggerLevel=AI_CASTOR_WAR_LEVEL_HIGH; + else if (sum>0) + warLevelTriggerLevel=AI_CASTOR_WAR_LEVEL_MID; + else + warLevelTriggerLevel=0; + + if (buildsAmount>strategy.warAmountTrigger) + warAmountTriggerLevel=AI_CASTOR_WAR_LEVEL_HIGH; + else if (buildsAmount>=strategy.warAmountTrigger) + warAmountTriggerLevel=AI_CASTOR_WAR_LEVEL_MID; + else + warAmountTriggerLevel=0; + warLevel=warTimeTriggerLevelUse+warLevelTriggerLevel+warAmountTriggerLevel; + + static int oldWarLevel=AI_CASTOR_WAR_LEVEL_UNSET; + if (oldWarLevel!=warLevel) + { + oldWarLevel=warLevel; + } + + if (warLevel==0) + return; + + int warPowerSum=0; + Unit **myUnits=team->myUnits; + for (int i=0; imedical==Unit::MED_FREE && u->typeNum==WARRIOR) + warPowerSum+=u->performance[ATTACK_SPEED]*u->performance[ATTACK_STRENGTH]; + } + static int oldWarPowerSum=AI_CASTOR_WAR_POWER_UNSET; + if (oldWarPowerSum!=warPowerSum) + { + oldWarPowerSum=warPowerSum; + } + + if (warPowerSumstrikeTimeTrigger || warPowerSum>strategy.strikeWarPowerTriggerUp) + { + onStrike=true; + } +} + diff --git a/src/ai/echo/BuildingOrder.cpp b/src/ai/echo/BuildingOrder.cpp new file mode 100644 index 000000000..98ab517f7 --- /dev/null +++ b/src/ai/echo/BuildingOrder.cpp @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include +#include "BuildingType.h" +#include "IntBuildingType.h" +#include "GlobalContainer.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Conditions; +using namespace boost::logic; + + +BuildingOrder::BuildingOrder(int building_type, int number_of_workers) : building_type(building_type), number_of_workers(number_of_workers) +{ + +} + + + +bool BuildingOrder::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("BuildingOrder"); + + building_type=stream->readUint32("building_type"); + number_of_workers=stream->readUint32("number_of_workers"); + + stream->readEnterSection("constraints"); + Uint32 size = stream->readUint32("size"); + constraints.resize(size); + for(unsigned x=0; xreadEnterSection(x); + constraints[x] = std::shared_ptr(Constraint::load_constraint(stream, player, versionMinor)); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + + stream->readEnterSection("conditions"); + size = stream->readUint32("size"); + conditions.resize(size); + for(unsigned x=0; xreadEnterSection(x); + conditions[x] = std::shared_ptr(Condition::load_condition(stream, player, versionMinor)); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + stream->readLeaveSection(); + return true; +} + + + +void BuildingOrder::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("BuildingOrder"); + + stream->writeUint32(building_type, "building_type"); + stream->writeUint32(number_of_workers, "number_of_workers"); + + stream->writeEnterSection("constraints"); + stream->writeUint32(constraints.size(), "size"); + for(unsigned x=0; xwriteEnterSection(x); + Constraint::save_constraint(constraints[x].get(), stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->writeEnterSection("conditions"); + stream->writeUint32(conditions.size(), "size"); + for(unsigned x=0; xwriteEnterSection(x); + Condition::save_condition(conditions[x].get(), stream); + stream->writeLeaveSection(); + } + + stream->writeLeaveSection(); + stream->writeLeaveSection(); +} + + + +void BuildingOrder::add_constraint(Constraint* constraint) +{ + constraints.push_back(std::shared_ptr(constraint)); +} + + +void BuildingOrder::add_condition(Condition* condition) +{ + conditions.push_back(std::shared_ptr(condition)); +} + + + +position BuildingOrder::find_location(Echo& echo, Map* map, GradientManager& manager) +{ + position best(0,0); + Player* player=echo.player; + int best_score=std::numeric_limits::min(); + BuildingType* type=globalContainer->buildingsTypes.getByType(IntBuildingType::typeFromShortNumber(building_type), 0, true); + bool check_flag=false; + //If theres no type for a construction zone, then this is a flag + if(type==NULL) + { + type=globalContainer->buildingsTypes.getByType(IntBuildingType::typeFromShortNumber(building_type), 0, false); + check_flag=true; + } + + for(int x=0; xgetW(); ++x) + { + for(int y=0; ygetH(); ++y) + { + if(!check_flag && !map->isHardSpaceForBuilding(x, y, type->width, type->height)) + continue; + + if(check_flag && echo.get_flag_map().get_flag(x, y)!=NOGBID) + continue; + int score=0; + bool passes=true; + for(std::vector >::iterator i=constraints.begin(); i!=constraints.end(); ++i) + { + for(int x2=0; x2width && passes; ++x2) + for(int y2=0; y2height && passes; ++y2) + if((x2==0 || y2==0 || x2==type->width-1 || y2==type->height-1)) + { + if(!(*i)->passes_constraint(echo, map->normalizeX(x+x2), map->normalizeY(y+y2))) + { + passes=false; + } + } + if(!passes) + { + break; + } + + if(!check_flag && (!map->isMapDiscovered(x, y, player->team->allies) || + !map->isMapDiscovered(x+type->width-1, y+type->height-1, player->team->allies)) + ) + { + passes=false; + break; + } + score+=(*i)->calculate_constraint(echo, map->normalizeX(x), map->normalizeY(y)); + score+=(*i)->calculate_constraint(echo, map->normalizeX(x+type->width-1), map->normalizeY(y+type->height-1)); + score+=(*i)->calculate_constraint(echo, map->normalizeX(x), map->normalizeY(y+type->height-1)); + score+=(*i)->calculate_constraint(echo, map->normalizeX(x+type->width-1), map->normalizeY(y)); + } + if(!passes) + continue; + if(score>best_score) + { + best=position(x, y); + best_score=score; + } + } + } + + return best; +} + + + +boost::logic::tribool BuildingOrder::passes_conditions(Echo& echo) +{ + for(unsigned int i=0; ipasses(echo); + if(passes) + continue; + else if(!passes) + return false; + else + return indeterminate; + + } + + for(unsigned n=0; nget_gradient_info()) + { + bool is_updated=echo.get_gradient_manager().is_updated(*constraints[n]->get_gradient_info()); + if(!is_updated) + return false; + } + } + + return true; +} + + + +void BuildingOrder::queue_gradients(Gradients::GradientManager& manager) +{ + for(unsigned n=0; nget_gradient_info()) + { + manager.queue_gradient(*constraints[n]->get_gradient_info()); + } + } +} diff --git a/src/ai/echo/BuildingRegister.cpp b/src/ai/echo/BuildingRegister.cpp new file mode 100644 index 000000000..b971226e9 --- /dev/null +++ b/src/ai/echo/BuildingRegister.cpp @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Building.h" +#include "BuildingType.h" +#include "IntBuildingType.h" +#include + +using namespace AIEcho; +using namespace AIEcho::Construction; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + +FlagMap::FlagMap(Echo& echo) : flagmap(echo.player->map->getW()*echo.player->map->getH(), NOGBID), width(echo.player->map->getW()), echo(echo) +{ +} + + + +int FlagMap::get_flag(int x, int y) +{ + return flagmap[y*width+x]; +} + + + +void FlagMap::set_flag(int x, int y, int gid) +{ + flagmap[y*width+x]=gid; +} + + + +bool FlagMap::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("FlagMap"); + stream->readEnterSection("flagmap"); + Uint32 size=stream->readUint32("size"); + flagmap.resize(size); + for (Uint32 flagmap_index = 0; flagmap_index < size; flagmap_index++) + { + stream->readEnterSection(flagmap_index); + flagmap[flagmap_index]=stream->readUint32("gid"); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + width=stream->readUint32("width"); + stream->readLeaveSection(); + return true; +} + + + +void FlagMap::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("FlagMap"); + stream->writeEnterSection("flagmap"); + stream->writeUint32(flagmap.size(), "size"); + for (Uint32 flagmap_index = 0; flagmap_index < flagmap.size(); flagmap_index++) + { + stream->writeEnterSection(flagmap_index); + stream->writeUint32(flagmap[flagmap_index], "gid"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + stream->writeUint32(width, "width"); + stream->writeLeaveSection(); +} + + + +BuildingRegister::BuildingRegister(Player* player, Echo& echo) : building_id(0), player(player), echo(echo) +{ + +} + + + +void BuildingRegister::initiate() +{ + for(int i=0; iteam->myBuildings[i]; + if(b!=NULL) + { + found_buildings[building_id++]=std::make_tuple(b->posX, b->posY, b->type->shortTypeNum, b->gid, false); + } + } +} + + + +unsigned int BuildingRegister::register_building() +{ + pending_buildings[building_id]=std::make_tuple(-1, -1, -1, AI_ECHO_PENDING_NOT_ISSUED); + return building_id++; +} + + + +void BuildingRegister::issue_order(int id, int x, int y, int building_type) +{ + pending_buildings[id]=std::make_tuple(x, y, building_type, 0); +} + + + +void BuildingRegister::remove_building(int id) +{ + pending_buildings.erase(id); +} + + + +bool BuildingRegister::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("BuildingRegister"); + + stream->readEnterSection("pending_buildings"); + Uint32 pending_size=stream->readUint32("size"); + for(Uint32 pending_index=0; pending_indexreadEnterSection(pending_index); + Uint32 id=stream->readSint32("echo_building_id"); + Uint32 x=stream->readSint32("xpos"); + Uint32 y=stream->readSint32("ypos"); + Uint32 type=stream->readSint32("building_type"); + Uint32 ticks=stream->readSint32("ticks_since_registered"); + pending_buildings[id]=std::make_tuple(x, y, type, ticks); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + stream->readEnterSection("found_buildings"); + Uint32 found_size=stream->readUint32("size"); + for(Uint32 found_index=0; found_indexreadEnterSection(found_index); + Uint32 id=stream->readUint32("echo_building_id"); + Uint32 xpos=stream->readUint32("xpos"); + Uint32 ypos=stream->readUint32("ypos"); + Uint32 building_type=stream->readUint32("building_type"); + Uint32 gid=stream->readUint32("gid"); + Uint8 upgrade_status=stream->readUint8("upgrade_status"); + boost::logic::tribool t; + if(upgrade_status==AI_ECHO_TRIBOOL_FALSE) + t=false; + else if(upgrade_status==AI_ECHO_TRIBOOL_TRUE) + t=true; + else + t=indeterminate; + found_buildings[id]=std::make_tuple(xpos, ypos, building_type, gid, t); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + building_id=stream->readUint32("building_id"); + stream->readLeaveSection(); + return true; +} + + + +void BuildingRegister::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("BuildingRegister"); + + stream->writeEnterSection("pending_buildings"); + unsigned int pending_size=0; + stream->writeUint32(pending_buildings.size(), "size"); + for(pending_iterator i=pending_buildings.begin(); i!=pending_buildings.end(); ++i) + { + stream->writeEnterSection(pending_size); + stream->writeSint32(i->first, "echo_building_id"); + stream->writeSint32(std::get<0>(i->second), "xpos"); + stream->writeSint32(std::get<1>(i->second), "ypos"); + stream->writeSint32(std::get<2>(i->second), "building_type"); + stream->writeSint32(std::get<3>(i->second), "ticks_since_registered"); + stream->writeLeaveSection(); + pending_size++; + } + stream->writeLeaveSection(); + + stream->writeEnterSection("found_buildings"); + unsigned int found_size=0; + stream->writeUint32(found_buildings.size(), "size"); + for(found_iterator i=found_buildings.begin(); i!=found_buildings.end(); ++i) + { + stream->writeEnterSection(found_size); + stream->writeUint32(i->first, "echo_building_id"); + stream->writeUint32(std::get<0>(i->second), "xpos"); + stream->writeUint32(std::get<1>(i->second), "ypos"); + stream->writeUint32(std::get<2>(i->second), "building_type"); + stream->writeUint32(std::get<3>(i->second), "gid"); + if(std::get<4>(i->second)) + stream->writeUint8(AI_ECHO_TRIBOOL_TRUE, "upgrade_status"); + else if(!std::get<4>(i->second)) + stream->writeUint8(AI_ECHO_TRIBOOL_FALSE, "upgrade_status"); + else + stream->writeUint8(AI_ECHO_TRIBOOL_INDETERMINATE, "upgrade_status"); + stream->writeLeaveSection(); + found_size++; + } + stream->writeLeaveSection(); + + stream->writeUint32(building_id, "building_id"); + stream->writeLeaveSection(); +} + + + +void BuildingRegister::set_upgrading(unsigned int id) +{ + std::get<4>(found_buildings[id])=indeterminate; +} + + + + +void BuildingRegister::tick() +{ + for(pending_iterator i=pending_buildings.begin(); i!=pending_buildings.end();) + { + //When get<3>() is AI_ECHO_PENDING_NOT_ISSUED, it means that the building order + //hasen't been sent to the glob2 engine yet. This is used when the building is + //registered, but awaiting conditions to be satisfied. + if(std::get<3>(i->second)!=AI_ECHO_PENDING_NOT_ISSUED) + { + std::get<3>(i->second)++; + if(std::get<3>(i->second) > AI_ECHO_PENDING_BUILDING_TIMEOUT_TICKS) + { + pending_iterator current=i; + ++i; + pending_buildings.erase(current); + continue; + } + int gbid=NOGBID; + if(std::get<2>(i->second) > IntBuildingType::DEFENSE_BUILDING && std::get<2>(i->second) < IntBuildingType::STONE_WALL) + { + gbid=is_flag(echo, std::get<0>(i->second), std::get<1>(i->second)); + } + else + { + gbid=player->map->getBuilding(std::get<0>(i->second), std::get<1>(i->second)); + } + if(gbid!=NOGBID) + { + if(std::get<2>(i->second) > IntBuildingType::DEFENSE_BUILDING && std::get<2>(i->second) < IntBuildingType::STONE_WALL) + { + echo.get_flag_map().set_flag(std::get<0>(i->second), std::get<1>(i->second), gbid); + } + found_buildings[i->first]=std::make_tuple(std::get<0>(i->second), std::get<1>(i->second), std::get<2>(i->second), gbid, false); + pending_iterator current=i; + ++i; + pending_buildings.erase(current); + continue; + } + } + ++i; + } + for(found_iterator i = found_buildings.begin(); i!=found_buildings.end();) + { + if(std::get<2>(i->second) > IntBuildingType::DEFENSE_BUILDING && std::get<2>(i->second) < IntBuildingType::STONE_WALL) + { + if(echo.get_flag_map().get_flag(std::get<0>(i->second), std::get<1>(i->second))==NOGBID) + { + found_iterator current=i; + ++i; + found_buildings.erase(current); + continue; + } + if(player->team->myBuildings[::Building::GIDtoID(std::get<3>(i->second))]==NULL) + { + echo.get_flag_map().set_flag(std::get<0>(i->second), std::get<1>(i->second), NOGBID); + found_iterator current=i; + ++i; + found_buildings.erase(current); + continue; + } + } + else + { + const int gbid=player->map->getBuilding(std::get<0>(i->second), std::get<1>(i->second)); + if(gbid==NOGBID || gbid != std::get<3>(i->second)) + { + found_iterator current=i; + ++i; + found_buildings.erase(current); + continue; + } + Building* b=player->team->myBuildings[::Building::GIDtoID(gbid)]; + if(b==NULL) + { + found_iterator current=i; + ++i; + found_buildings.erase(current); + continue; + } + //True + if(std::get<4>(i->second)) + { + std::get<0>(i->second)=b->posX; + std::get<1>(i->second)=b->posY; + if(b->constructionResultState==::Building::NO_CONSTRUCTION) + { + std::get<4>(i->second)=false; + } + } + //False + else if(!std::get<4>(i->second)) + { + + } + //Indeterminate + else + { + if(b->constructionResultState!=::Building::NO_CONSTRUCTION) + { + std::get<4>(i->second)=true; + } + } + } + ++i; + } +} + +bool BuildingRegister::is_building_pending(unsigned int id) +{ + if(pending_buildings.find(id)!=pending_buildings.end()) + { + return true; + } + return false; +} + + + +bool BuildingRegister::is_building_found(unsigned int id) +{ + if(found_buildings.find(id)!=found_buildings.end()) + { + return true; + } + return false; +} + + + + +bool BuildingRegister::is_building_upgrading(unsigned int id) +{ + if(found_buildings.find(id)==found_buildings.end()) + { + return false; + } + + tribool v=std::get<4>(found_buildings[id]); + if(v) + return true; + else if(!v) + return false; + return true; +} + + + +Building* BuildingRegister::get_building(unsigned int id) +{ + if(found_buildings.find(id)==found_buildings.end()) + { + return NULL; + } + return player->team->myBuildings[::Building::GIDtoID(std::get<3>(found_buildings[id]))]; +} + + + +BuildingType* BuildingRegister::get_building_type(unsigned int id) +{ + if(found_buildings.find(id)==found_buildings.end()) + { + return NULL; + } + return player->team->myBuildings[::Building::GIDtoID(std::get<3>(found_buildings[id]))]->type; +} + + + +int BuildingRegister::get_type(unsigned int id) +{ + if(found_buildings.find(id)==found_buildings.end()) + { + return 0; + } + return std::get<2>(found_buildings[id]); +} + + + +int BuildingRegister::get_level(unsigned int id) +{ + if(found_buildings.find(id)==found_buildings.end()) + { + return 0; + } + return get_building(id)->type->level+1; +} + + + +int BuildingRegister::get_assigned(unsigned int id) +{ + if(found_buildings.find(id)==found_buildings.end()) + { + return 0; + } + return get_building(id)->maxUnitWorking; +} diff --git a/src/ai/echo/Conditions.cpp b/src/ai/echo/Conditions.cpp new file mode 100644 index 000000000..7901303f9 --- /dev/null +++ b/src/ai/echo/Conditions.cpp @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Building.h" +#include "IntBuildingType.h" + +using namespace AIEcho; +using namespace AIEcho::Conditions; + +// Helper for the load_condition switches: each case constructs a new T, +// calls its load(), and breaks. T's protected/private members are accessible +// because this macro expands inside Condition::load_condition (or +// BuildingCondition::load_condition), which is a friend of every derived class. +#define LOAD_CASE(EnumVal, Type) \ + case EnumVal: \ + condition = new Type; \ + condition->load(stream, player, versionMinor); \ + break; + + +Condition* Condition::load_condition(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("Condition"); + ConditionType type=static_cast(stream->readUint32("type")); + Condition* condition=NULL; + switch(type) + { + LOAD_CASE(CParticularBuilding, ParticularBuilding) + LOAD_CASE(CBuildingDestroyed, BuildingDestroyed) + LOAD_CASE(CEnemyBuildingDestroyed, EnemyBuildingDestroyed) + LOAD_CASE(CEitherCondition, EitherCondition) + LOAD_CASE(CPopulation, Population) + } + stream->readLeaveSection(); + return condition; +} + + + +void Condition::save_condition(Condition* condition, GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Condition"); + stream->writeUint32(condition->get_type(), "type"); + condition->save(stream); + stream->writeLeaveSection(); +} + + + +BuildingCondition* BuildingCondition::load_condition(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("BuildingCondition"); + BuildingConditionType type=static_cast(stream->readUint32("type")); + BuildingCondition* condition=NULL; + switch(type) + { + LOAD_CASE(CNotUnderConstruction, NotUnderConstruction) + LOAD_CASE(CUnderConstruction, UnderConstruction) + LOAD_CASE(CBeingUpgraded, BeingUpgraded) + LOAD_CASE(CBeingUpgradedTo, BeingUpgradedTo) + LOAD_CASE(CSpecificBuildingType, SpecificBuildingType) + LOAD_CASE(CNotSpecificBuildingType, NotSpecificBuildingType) + LOAD_CASE(CBuildingLevel, BuildingLevel) + LOAD_CASE(CUpgradable, Upgradable) + LOAD_CASE(CRessourceTrackerAmount, RessourceTrackerAmount) + LOAD_CASE(CRessourceTrackerAge, RessourceTrackerAge) + } + stream->readLeaveSection(); + return condition; +} + +#undef LOAD_CASE + + + +void BuildingCondition::save_condition(BuildingCondition* condition, GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("BuildingCondition"); + stream->writeUint32(condition->get_type(), "type"); + condition->save(stream); + stream->writeLeaveSection(); +} + + + +bool NotUnderConstruction::passes(Echo& echo, int id) +{ + Building* building = echo.get_building_register().get_building(id); + bool result=building->constructionResultState==::Building::NO_CONSTRUCTION && !echo.get_building_register().is_building_upgrading(id); + return result; +} + + + +bool UnderConstruction::passes(Echo& echo, int id) +{ + Building* building = echo.get_building_register().get_building(id); + return building->constructionResultState!=::Building::NO_CONSTRUCTION && building->buildingState==Building::ALIVE; +} + + + +bool BeingUpgraded::passes(Echo& echo, int id) +{ + return echo.get_building_register().is_building_upgrading(id); +} + + + + +bool Upgradable::passes(Echo& echo, int id) +{ + Building* building = echo.get_building_register().get_building(id); + if((building->type->shortTypeNum==IntBuildingType::FOOD_BUILDING || + building->type->shortTypeNum==IntBuildingType::HEAL_BUILDING || + building->type->shortTypeNum==IntBuildingType::SWIMSPEED_BUILDING || + building->type->shortTypeNum==IntBuildingType::WALKSPEED_BUILDING || + building->type->shortTypeNum==IntBuildingType::ATTACK_BUILDING || + building->type->shortTypeNum==IntBuildingType::SCIENCE_BUILDING || + building->type->shortTypeNum==IntBuildingType::DEFENSE_BUILDING) && + building->constructionResultState==Building::NO_CONSTRUCTION && + building->type->level!=AI_ECHO_MAX_BUILDING_LEVEL_INDEX && + building->isHardSpaceForBuildingSite(Building::UPGRADE) && + building->hp == building->type->hpMax + ) + return true; + return false; +} diff --git a/src/ai/echo/Conditions.h b/src/ai/echo/Conditions.h new file mode 100644 index 000000000..1519f9f6d --- /dev/null +++ b/src/ai/echo/Conditions.h @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#pragma once + +#include "echo/Position.h" +#include "Player.h" +#include "Stream.h" + +#include + +namespace AIEcho +{ + class Echo; + + namespace Construction + { + class BuildingOrder; + } + + namespace Management + { + class ManagementOrder; + } + + namespace SearchTools + { + class BuildingSearch; + } + + ///These are all conditions on a particular Building. They are used in several places, such as when counting numbers of buildings, or + ///for setting a condition on an order to change the number of units assigned, making them very usefull. Its important to note that + ///none of the conditions work on enemies buildings, they only work on buildings on you're own team. + namespace Conditions + { + ///This is used for loading and saving purposes only. + ///Values are part of the on-disk save format — never renumber. + enum ConditionType + { + CParticularBuilding = 0, + CBuildingDestroyed = 1, + CEnemyBuildingDestroyed = 2, + CEitherCondition = 3, + // value 4 reserved (was CAllConditions, removed — never instantiated by any AI) + CPopulation = 5, + }; + + class BuildingCondition; + + ///This is a generic condition. It can be attached to many parts of the code + class Condition + { + public: + virtual ~Condition() {} + protected: + friend class Management::ManagementOrder; + friend class Construction::BuildingOrder; + friend class EitherCondition; + ///This function checks if the condition passes. The third state, indeterminate, means that the condition + ///is impossible to fullfill. For example, a condition on a particular building could never pass if that + ///building is destroyed. + virtual boost::logic::tribool passes(Echo& echo)=0; + virtual ConditionType get_type()=0; + virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; + virtual void save(GAGCore::OutputStream *stream)=0; + static Condition* load_condition(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + static void save_condition(Condition* condition, GAGCore::OutputStream *stream); + }; + + ///This converts a BuildingCondition into a standard condition simply by supplying the id of the building + ///to be checked. + class ParticularBuilding : public Condition + { + public: + friend class Condition; + ParticularBuilding(BuildingCondition* condition, int id); + ~ParticularBuilding(); + boost::logic::tribool passes(Echo& echo); + ConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + ParticularBuilding() = default; + BuildingCondition* condition = nullptr; + int id = -1; + }; + + ///This condition matches when one of your own buildings are destroyed. It also matches when the building + ///is timed out and removed. + class BuildingDestroyed : public Condition + { + public: + BuildingDestroyed(int id); + protected: + friend class Condition; + BuildingDestroyed() = default; + boost::logic::tribool passes(Echo& echo); + ConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int id = 0; + }; + + ///This condition matches when the provided gid of the enemy building, obtained from an enemy_building_iterator, + ///is destroyed. It's meant for use with war flags or exploration flags. + class EnemyBuildingDestroyed : public Condition + { + public: + EnemyBuildingDestroyed(Echo& echo, int gbid); + protected: + friend class Condition; + EnemyBuildingDestroyed() = default; + boost::logic::tribool passes(Echo& echo); + ConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int gbid = 0; + int type = 0; + int level = 0; + position location; + }; + + ///Matches if either condition is true, does not require both of them + class EitherCondition : public Condition + { + public: + EitherCondition(Condition* condition1, Condition* condition2); + protected: + friend class Condition; + ~EitherCondition(); + boost::logic::tribool passes(Echo& echo); + ConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + EitherCondition() = default; + Condition* condition1 = nullptr; + Condition* condition2 = nullptr; + }; + + ///Matches when the population of the specified group of units is reached in the given method + class Population : public Condition + { + public: + enum PopulationMethod + { + Greater, + Lesser, + }; + + Population(bool workers, bool explorers, bool warriors, int num, PopulationMethod method); + protected: + friend class Condition; + boost::logic::tribool passes(Echo& echo); + ConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + Population() = default; + bool workers = false; + bool explorers = false; + bool warriors = false; + int num = 0; + PopulationMethod method = Greater; + }; + + ///This is used for loading and saving purposes only. + ///Values are part of the on-disk save format — never renumber. + enum BuildingConditionType + { + CNotUnderConstruction = 0, + CUnderConstruction = 1, + CBeingUpgraded = 2, + CBeingUpgradedTo = 3, + CSpecificBuildingType = 4, + CNotSpecificBuildingType = 5, + CBuildingLevel = 6, + CUpgradable = 7, + CRessourceTrackerAmount = 8, + CRessourceTrackerAge = 9, + // value 10 reserved (was CTicksPassed, removed — debug-only, never instantiated by any AI) + }; + + ///A generic building condition has one important function, one that checks whether the condition is satisfied + class BuildingCondition + { + public: + virtual ~BuildingCondition() {} + friend class AIEcho::Management::ManagementOrder; + friend class AIEcho::Construction::BuildingOrder; + friend class AIEcho::SearchTools::BuildingSearch; + friend class ParticularBuilding; + protected: + virtual bool passes(Echo& echo, int id)=0; + virtual BuildingConditionType get_type()=0; + virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; + virtual void save(GAGCore::OutputStream *stream)=0; + static BuildingCondition* load_condition(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + static void save_condition(BuildingCondition* condition, GAGCore::OutputStream *stream); + }; + + ///This condition waits for a building not to be under construction. + class NotUnderConstruction : public BuildingCondition + { + protected: + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *s, Player *, Sint32) + { s->readEnterSection("NotUnderConstruction"); s->readLeaveSection(); return true; } + void save(GAGCore::OutputStream *s) + { s->writeEnterSection("NotUnderConstruction"); s->writeLeaveSection(); } + }; + + ///This condition waits for a building to be under construction + class UnderConstruction : public BuildingCondition + { + protected: + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *s, Player *, Sint32) + { s->readEnterSection("UnderConstruction"); s->readLeaveSection(); return true; } + void save(GAGCore::OutputStream *s) + { s->writeEnterSection("UnderConstruction"); s->writeLeaveSection(); } + }; + + ///This condition tells whether a building is being upgraded + class BeingUpgraded : public BuildingCondition + { + protected: + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *s, Player *, Sint32) + { s->readEnterSection("BeingUpgraded"); s->readLeaveSection(); return true; } + void save(GAGCore::OutputStream *s) + { s->writeEnterSection("BeingUpgraded"); s->writeLeaveSection(); } + }; + + ///Similair to BeingUpgraded, but this also takes a level, in which the building is being upgraded + ///to a particular level. When possible, use this instead od combining BeingUpgraded and BuildingLevel + class BeingUpgradedTo : public BuildingCondition + { + public: + BeingUpgradedTo() : level(0) {} + explicit BeingUpgradedTo(int level); + protected: + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int level; + }; + + ///This condition tells whether a building is a particular type, as defined in IntBuildingType.h + class SpecificBuildingType : public BuildingCondition + { + public: + SpecificBuildingType() : building_type(0) {} + explicit SpecificBuildingType(int building_type); + protected: + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int building_type; + }; + + ///This condition matches any building that isn't of a particular type + class NotSpecificBuildingType : public BuildingCondition + { + public: + NotSpecificBuildingType() : building_type(0) {} + explicit NotSpecificBuildingType(int building_type); + protected: + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int building_type; + }; + + ///This building matches buildings of a particular level + class BuildingLevel : public BuildingCondition + { + public: + BuildingLevel() : building_level(0) {} + explicit BuildingLevel(int building_level); + protected: + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int building_level; + }; + + ///This condition matches a building that can be upgraded + class Upgradable : public BuildingCondition + { + protected: + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *s, Player *, Sint32) + { s->readEnterSection("Upgradable"); s->readLeaveSection(); return true; } + void save(GAGCore::OutputStream *s) + { s->writeEnterSection("Upgradable"); s->writeLeaveSection(); } + }; + + ///This class compares the total amount of ressources recorded by a ressource tracker. + class RessourceTrackerAmount : public BuildingCondition + { + public: + enum TrackerMethod + { + Greater, + Lesser, + }; + + explicit RessourceTrackerAmount(int amount, TrackerMethod tracker_method); + private: + friend class BuildingCondition; + RessourceTrackerAmount() = default; + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + int amount = 0; + int tracker_method = 0; + }; + + ///This class compares the age provided by a ressource tracker + class RessourceTrackerAge : public BuildingCondition + { + public: + enum TrackerMethod + { + Greater, + Lesser, + }; + + explicit RessourceTrackerAge(int age, TrackerMethod tracker_method); + private: + friend class BuildingCondition; + RessourceTrackerAge() = default; + bool passes(Echo& echo, int id); + BuildingConditionType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + int age = 0; + int tracker_method = 0; + }; + + }; +} + + + +inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::Upgradable::get_type() +{ + return CUpgradable; +} + + + +inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::NotUnderConstruction::get_type() +{ + return CNotUnderConstruction; +} + + + +inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::UnderConstruction::get_type() +{ + return CUnderConstruction; +} + + + +inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::BeingUpgraded::get_type() +{ + return CBeingUpgraded; +} + + + +inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::BeingUpgradedTo::get_type() +{ + return CBeingUpgradedTo; +} + + + +inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::SpecificBuildingType::get_type() +{ + return CSpecificBuildingType; +} + + + +inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::NotSpecificBuildingType::get_type() +{ + return CNotSpecificBuildingType; +} + + + +inline AIEcho::Conditions::BuildingConditionType AIEcho::Conditions::BuildingLevel::get_type() +{ + return CBuildingLevel; +} + + + +inline AIEcho::Conditions::ConditionType AIEcho::Conditions::EnemyBuildingDestroyed::get_type() +{ + return CEnemyBuildingDestroyed; +} diff --git a/src/ai/echo/ConditionsBuilding.cpp b/src/ai/echo/ConditionsBuilding.cpp new file mode 100644 index 000000000..df9a3a83a --- /dev/null +++ b/src/ai/echo/ConditionsBuilding.cpp @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Building.h" +#include "Game.h" + +using namespace AIEcho; +using namespace AIEcho::Conditions; +using namespace boost::logic; + + +ParticularBuilding::ParticularBuilding(BuildingCondition* condition, int id) : condition(condition), id(id) +{ + +} + + + +ParticularBuilding::~ParticularBuilding() +{ + delete condition; +} + + + +boost::logic::tribool ParticularBuilding::passes(Echo& echo) +{ + if(!echo.get_building_register().is_building_found(id) && !echo.get_building_register().is_building_pending(id)) + { + return indeterminate; + } + if(echo.get_building_register().is_building_found(id)) + { + bool passes=condition->passes(echo, id); + return passes; + } + return false; +} + + + +ConditionType ParticularBuilding::get_type() +{ + return CParticularBuilding; +} + + + +bool ParticularBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("ParticularBuilding"); + id=stream->readSint32("id"); + condition=BuildingCondition::load_condition(stream, player, versionMinor); + stream->readLeaveSection(); + return true; +} + + + +void ParticularBuilding::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("ParticularBuilding"); + stream->writeSint32(id, "id"); + BuildingCondition::save_condition(condition, stream); + stream->writeLeaveSection(); +} + + +BuildingDestroyed::BuildingDestroyed(int id) : id(id) +{ + +} + + + +boost::logic::tribool BuildingDestroyed::passes(Echo& echo) +{ + if(!echo.get_building_register().is_building_found(id) && !echo.get_building_register().is_building_pending(id)) + { + return true; + } + return false; +} + + + +ConditionType BuildingDestroyed::get_type() +{ + return CBuildingDestroyed; +} + + + +bool BuildingDestroyed::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("BuildingDestroyed"); + id=stream->readSint32("id"); + stream->readLeaveSection(); + return true; +} + + + +void BuildingDestroyed::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("BuildingDestroyed"); + stream->writeSint32(id, "id"); + stream->writeLeaveSection(); +} + + + +EnemyBuildingDestroyed::EnemyBuildingDestroyed(Echo& echo, int gbid) : gbid(gbid) +{ + Building* b=echo.player->game->teams[Building::GIDtoTeam(gbid)]->myBuildings[Building::GIDtoID(gbid)]; + type=b->type->shortTypeNum; + level=b->type->level; + location=position(b->posX, b->posY); +} + + + +boost::logic::tribool EnemyBuildingDestroyed::passes(Echo& echo) +{ + Building* b=echo.player->game->teams[Building::GIDtoTeam(gbid)]->myBuildings[Building::GIDtoID(gbid)]; + if(b==NULL) + { + return true; + } + if(b->posX != location.x || b->posY != location.y) + { + return true; + } + if(b->type->shortTypeNum != type) + { + return true; + } + return false; +} + + + +bool EnemyBuildingDestroyed::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("EnemyBuildingDestroyed"); + gbid=stream->readUint32("gbid"); + type=stream->readUint32("type"); + level=stream->readUint32("level"); + int posx=stream->readUint32("posx"); + int posy=stream->readUint32("posy"); + location=position(posx, posy); + stream->readLeaveSection(); + return true; +} + + + +void EnemyBuildingDestroyed::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("EnemyBuildingDestroyed"); + stream->writeUint32(gbid, "gbid"); + stream->writeUint32(type, "type"); + stream->writeUint32(level, "level"); + stream->writeUint32(location.x, "posx"); + stream->writeUint32(location.y, "posy"); + stream->writeLeaveSection(); +} + + +SpecificBuildingType::SpecificBuildingType(int building_type) : building_type(building_type) +{ + +} + + + +bool SpecificBuildingType::passes(Echo& echo, int id) +{ + if(echo.get_building_register().get_type(id)==building_type) + return true; + return false; +} + +bool SpecificBuildingType::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("SpecificBuildingType"); + building_type=stream->readUint32("building_type"); + stream->readLeaveSection(); + return true; +} + + + +void SpecificBuildingType::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("SpecificBuildingType"); + stream->writeUint32(building_type, "building_type"); + stream->writeLeaveSection(); +} + + + + + +NotSpecificBuildingType::NotSpecificBuildingType(int building_type) : building_type(building_type) +{ + +} + + + +bool NotSpecificBuildingType::passes(Echo& echo, int id) +{ + if(echo.get_building_register().get_type(id)!=building_type) + return true; + return false; +} + +bool NotSpecificBuildingType::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("NotSpecificBuildingType"); + building_type=stream->readUint32("building_type"); + stream->readLeaveSection(); + return true; +} + + + +void NotSpecificBuildingType::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("NotSpecificBuildingType"); + stream->writeUint32(building_type, "building_type"); + stream->writeLeaveSection(); +} + + + + + +BeingUpgradedTo::BeingUpgradedTo(int level) : level(level) +{ + +} + + + +bool BeingUpgradedTo::passes(Echo& echo, int id) +{ + Building* b= echo.get_building_register().get_building(id); + if(!echo.get_building_register().is_building_upgrading(id)) + return false; + if(b->type->isBuildingSite) + { + if(b->type->level==(level-AI_ECHO_LEVEL_OFFSET_USER_TO_ENGINE)) + { + return true; + } + } + else if(b->type->level==(level-AI_ECHO_LEVEL_OFFSET_FINISHED_TO_TARGET)) + { + return true; + } + return false; +} + + +bool BeingUpgradedTo::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("BeingUpgradedTo"); + level=stream->readUint32("level"); + stream->readLeaveSection(); + return true; +} + + + +void BeingUpgradedTo::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("BeingUpgradedTo"); + stream->writeUint32(level, "level"); + stream->writeLeaveSection(); +} + + + + +BuildingLevel::BuildingLevel(int building_level) : building_level(building_level) +{ + +} + + + +bool BuildingLevel::passes(Echo& echo, int id) +{ + Building* building = echo.get_building_register().get_building(id); + if(building->type->level==building_level-AI_ECHO_LEVEL_OFFSET_USER_TO_ENGINE) + return true; + return false; +} + + +bool BuildingLevel::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("BuildingLevel"); + building_level=stream->readUint32("building_level"); + stream->readLeaveSection(); + return true; +} + + + +void BuildingLevel::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("BuildingLevel"); + stream->writeUint32(building_level, "building_level"); + stream->writeLeaveSection(); +} diff --git a/src/ai/echo/ConditionsPopulation.cpp b/src/ai/echo/ConditionsPopulation.cpp new file mode 100644 index 000000000..980c368ed --- /dev/null +++ b/src/ai/echo/ConditionsPopulation.cpp @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" + +using namespace AIEcho; +using namespace AIEcho::Conditions; +using namespace boost::logic; + + +EitherCondition::EitherCondition(Condition* condition1, Condition* condition2) : condition1(condition1), condition2(condition2) +{ + +} + + + +EitherCondition::~EitherCondition() +{ + delete condition1; + delete condition2; +} + + + +boost::logic::tribool EitherCondition::passes(Echo& echo) +{ + tribool p1=condition1->passes(echo); + tribool p2=condition2->passes(echo); + if(p1 || p2) + return true; + else if(!p1 || !p2) + return false; + else + return indeterminate; +} + + + +ConditionType EitherCondition::get_type() +{ + return CEitherCondition; +} + + + +bool EitherCondition::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("EitherCondition"); + condition1=Condition::load_condition(stream, player, versionMinor); + condition2=Condition::load_condition(stream, player, versionMinor); + stream->readLeaveSection(); + return true; +} + + + +void EitherCondition::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("EitherCondition"); + Condition::save_condition(condition1, stream); + Condition::save_condition(condition2, stream); + stream->writeLeaveSection(); +} + + + +Population::Population(bool workers, bool explorers, bool warriors, int num, PopulationMethod method) : workers(workers), explorers(explorers), warriors(warriors), num(num), method(method) +{ + +} + + + +boost::logic::tribool Population::passes(Echo& echo) +{ + int amount=0; + if(workers) + amount+=echo.player->team->stats.getLatestStat()->numberUnitPerType[WORKER]; + if(explorers) + amount+=echo.player->team->stats.getLatestStat()->numberUnitPerType[EXPLORER]; + if(warriors) + amount+=echo.player->team->stats.getLatestStat()->numberUnitPerType[WARRIOR]; + if(method==Greater) + { + return (amount >= num); + } + else if(method==Lesser) + { + return (amount <= num); + } + return false; +} + + + +ConditionType Population::get_type() +{ + return CPopulation; +} + + + +bool Population::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("Population"); + workers=stream->readUint8("workers"); + explorers=stream->readUint8("explorers"); + warriors=stream->readUint8("warriors"); + num=stream->readSint32("num"); + method=static_cast(stream->readUint32("method")); + stream->readLeaveSection(); + return true; +} + + + +void Population::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Population"); + stream->writeUint8(workers, "workers"); + stream->writeUint8(explorers, "explorers"); + stream->writeUint8(warriors, "warriors"); + stream->writeSint32(num, "num"); + stream->writeUint32(static_cast(method), "method"); + stream->writeLeaveSection(); +} diff --git a/src/ai/echo/ConditionsTracker.cpp b/src/ai/echo/ConditionsTracker.cpp new file mode 100644 index 000000000..e55e52de3 --- /dev/null +++ b/src/ai/echo/ConditionsTracker.cpp @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" + +using namespace AIEcho; +using namespace AIEcho::Conditions; + + +RessourceTrackerAmount::RessourceTrackerAmount(int amount, TrackerMethod tracker_method) : amount(amount), tracker_method(tracker_method) +{ + +} + + + +bool RessourceTrackerAmount::passes(Echo& echo, int id) +{ + if(tracker_method==Greater) + { + return echo.get_ressource_tracker(id)->get_total_level() > amount; + } + else if(tracker_method==Lesser) + { + return echo.get_ressource_tracker(id)->get_total_level() < amount; + } + return false; +} + + + +BuildingConditionType RessourceTrackerAmount::get_type() +{ + return CRessourceTrackerAmount; +} + + + +bool RessourceTrackerAmount::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("RessourceTrackerAmount"); + amount=stream->readUint32("amount"); + tracker_method=static_cast(stream->readUint32("tracker_method")); + stream->readLeaveSection(); + return true; +} + + + +void RessourceTrackerAmount::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("RessourceTrackerAmount"); + stream->writeUint32(amount, "amount"); + stream->writeUint32(static_cast(tracker_method), "tracker_method"); + stream->writeLeaveSection(); +} + + + +RessourceTrackerAge::RessourceTrackerAge(int age, TrackerMethod tracker_method) : age(age), tracker_method(tracker_method) +{ + +} + + + +bool RessourceTrackerAge::passes(Echo& echo, int id) +{ + if(tracker_method==Greater) + { + return echo.get_ressource_tracker(id)->get_age() > age; + } + else if(tracker_method==Lesser) + { + return echo.get_ressource_tracker(id)->get_age() < age; + } + return false; +} + + + +BuildingConditionType RessourceTrackerAge::get_type() +{ + return CRessourceTrackerAge; +} + + + +bool RessourceTrackerAge::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("RessourceTrackerAge"); + age=stream->readUint32("age"); + tracker_method=static_cast(stream->readUint32("tracker_method")); + stream->readLeaveSection(); + return true; +} + + + +void RessourceTrackerAge::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("RessourceTrackerAge"); + stream->writeUint32(age, "age"); + stream->writeUint32(static_cast(tracker_method), "tracker_method"); + stream->writeLeaveSection(); +} diff --git a/src/ai/echo/Construction.cpp b/src/ai/echo/Construction.cpp new file mode 100644 index 000000000..1e5caee64 --- /dev/null +++ b/src/ai/echo/Construction.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" + +using namespace AIEcho; +using namespace AIEcho::Construction; + + +Constraint* Constraint::load_constraint(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("Constraint"); + ConstraintType type=static_cast(stream->readUint32("type")); + Constraint* constraint=NULL; + switch(type) + { + case CTMinimumDistance: + constraint=new MinimumDistance; + constraint->load(stream, player, versionMinor); + break; + case CTMaximumDistance: + constraint=new MaximumDistance; + constraint->load(stream, player, versionMinor); + break; + case CTMinimizedDistance: + constraint=new MinimizedDistance; + constraint->load(stream, player, versionMinor); + break; + case CTMaximizedDistance: + constraint=new MaximizedDistance; + constraint->load(stream, player, versionMinor); + break; + case CTCenterOfBuilding: + constraint=new CenterOfBuilding; + constraint->load(stream, player, versionMinor); + break; + case CTSinglePosition: + constraint=new SinglePosition; + constraint->load(stream, player, versionMinor); + break; + } + stream->readLeaveSection(); + return constraint; +} + + + +void Constraint::save_constraint(Constraint* constraint, GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Constraint"); + stream->writeUint32(constraint->get_type(), "type"); + constraint->save(stream); + stream->writeLeaveSection(); +} diff --git a/src/ai/echo/Construction.h b/src/ai/echo/Construction.h new file mode 100644 index 000000000..da1b46648 --- /dev/null +++ b/src/ai/echo/Construction.h @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#pragma once + +#include "echo/Gradients.h" +#include "echo/Position.h" +#include "Player.h" +#include "BuildingType.h" + +#include +#include +#include +#include +#include + +namespace AIEcho +{ + class Echo; + + namespace Conditions + { + class Condition; + class BuildingCondition; + class NotUnderConstruction; + class UnderConstruction; + class BeingUpgraded; + class BeingUpgradedTo; + class SpecificBuildingType; + class NotSpecificBuildingType; + class BuildingLevel; + class Upgradable; + class EnemyBuildingDestroyed; + } + + namespace Management + { + class ManagementOrder; + class AssignWorkers; + class ChangeSwarm; + class DestroyBuilding; + class RessourceTracker; + class AddRessourceTracker; + class PauseRessourceTracker; + class UnPauseRessourceTracker; + class ChangeFlagSize; + class ChangeFlagMinimumLevel; + class GlobalManagementOrder; + class AddArea; + class RemoveArea; + class ChangeAlliances; + class UpgradeRepair; + } + + namespace SearchTools + { + class building_search_iterator; + class BuildingSearch; + } + + ///This namespace stores all things related to the construction of new buildings. + namespace Construction + { + class BuildingOrder; + class FlagMap; + class BuildingRegister; + + enum ConstraintType + { + CTMinimumDistance, + CTMaximumDistance, + CTMinimizedDistance, + CTMaximizedDistance, + CTCenterOfBuilding, + CTSinglePosition, + }; + + ///A generic constraint serves two purposes, one, to compute a score for a particular position, and two, + ///to verify that a particular position matches the requirements of the constraint. Most constraints + ///are passed a GradientInfo, as they use the distances on various gradients to do their work. + ///Keep in mind that the verifications that the position satisfies the constraint must be satisfied + ///for all points on a newly placed building, not just one (with the exception of points that aren't + ///touching the outside of the building) + class Constraint + { + public: + virtual ~Constraint(){} + protected: + friend class AIEcho::Construction::BuildingOrder; + virtual int calculate_constraint(Echo& echo, int x, int y)=0; + virtual bool passes_constraint(Echo& echo, int x, int y)=0; + ///This function is meant for the registering of GradientInfo, return NULL if the Constraint doesn't use a gradient + virtual Gradients::GradientInfo* get_gradient_info()=0; + virtual ConstraintType get_type()=0; + virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; + virtual void save(GAGCore::OutputStream *stream)=0; + static Constraint* load_constraint(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + static void save_constraint(Constraint* constraint, GAGCore::OutputStream *stream); + }; + + ///This constraint keeps buildings from being placed too close to a particular source + class MinimumDistance : public Constraint + { + public: + MinimumDistance(const Gradients::GradientInfo& gi, int distance); + protected: + MinimumDistance() :gradient_cache(NULL), distance(0) {} + friend class Constraint; + int calculate_constraint(Echo& echo, int x, int y); + bool passes_constraint(Echo& echo, int x, int y); + Gradients::GradientInfo* get_gradient_info() { return &gi; } + ConstraintType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + Gradients::GradientInfo gi; + Gradients::Gradient* gradient_cache; + int distance; + }; + + ///This constraint keeps buildings from being placed to far from a particular source + class MaximumDistance: public Constraint + { + public: + MaximumDistance(const Gradients::GradientInfo& gi, int distance); + protected: + MaximumDistance() :gradient_cache(NULL), distance(0) {} + friend class Constraint; + int calculate_constraint(Echo& echo, int x, int y); + bool passes_constraint(Echo& echo, int x, int y); + Gradients::GradientInfo* get_gradient_info() { return &gi; } + ConstraintType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + Gradients::GradientInfo gi; + Gradients::Gradient* gradient_cache; + int distance; + }; + + ///This constraint tries to make buildings closer to a particular source. It can be given a weight, + ///changing the effect the constraint has on the final position of the building + class MinimizedDistance : public Constraint + { + public: + MinimizedDistance(const Gradients::GradientInfo& gi, int weight); + protected: + MinimizedDistance() :gradient_cache(NULL), weight(0) {} + friend class Constraint; + int calculate_constraint(Echo& echo, int x, int y); + bool passes_constraint(Echo& echo, int x, int y); + Gradients::GradientInfo* get_gradient_info() { return &gi; } + ConstraintType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + Gradients::GradientInfo gi; + Gradients::Gradient* gradient_cache; + int weight; + }; + + ///This constraint tries to make buildings farther from a particular source. It can be given a weight, + ///changing the effect the constraint has on the final position of the building + class MaximizedDistance : public Constraint + { + public: + MaximizedDistance(const Gradients::GradientInfo& gi, int weight); + protected: + MaximizedDistance() :gradient_cache(NULL), weight(0) {} + friend class Constraint; + int calculate_constraint(Echo& echo, int x, int y); + bool passes_constraint(Echo& echo, int x, int y); + Gradients::GradientInfo* get_gradient_info() { return &gi; } + ConstraintType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + Gradients::GradientInfo gi; + Gradients::Gradient* gradient_cache; + int weight; + }; + + ///This constraint doesn't use gradients, unlike the other ones. In particular, it only allows one + ///position to be allowed, the center of the building with the provided GBID. Notice this is not + ///like other building ID's, it can only be obtained with enemy_building_iterator or a similair + ///method. + class CenterOfBuilding : public Constraint + { + public: + explicit CenterOfBuilding(int gbid); + protected: + CenterOfBuilding() : gbid(0) {} + friend class Constraint; + int calculate_constraint(Echo& echo, int x, int y); + bool passes_constraint(Echo& echo, int x, int y); + Gradients::GradientInfo* get_gradient_info() { return NULL; } + ConstraintType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int gbid; + }; + + + ///This constraint, againt unlike the others, does not use gradients. It only allows the given + ///position to be allowed. The resulting building will *not* be centered on it except if it is + ///a 1x1 building + class SinglePosition : public Constraint + { + public: + SinglePosition(int posx, int posy); + protected: + SinglePosition() : posx(0), posy(0) {} + friend class Constraint; + int calculate_constraint(Echo& echo, int x, int y); + bool passes_constraint(Echo& echo, int x, int y); + Gradients::GradientInfo* get_gradient_info() { return NULL; } + ConstraintType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int posx; + int posy; + }; + + + ///An order for new buildings to be constructed. It takes the type of building from IntBuildingType.h, + ///and the number of workers that should be used to construct it. + class BuildingOrder + { + public: + BuildingOrder(int building_type, int number_of_workers); + ///Adds a constraint to be used in finding a location of the building. This class takes ownership of the constraint. + void add_constraint(Constraint* constraint); + ///Adds a new condition to the building order. This assumes ownership of the condition. + void add_condition(Conditions::Condition* condition); + private: + friend class AIEcho::Echo; + BuildingOrder() {} + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + ///An internal function used to find the location to place the building + position find_location(Echo& echo, Map* map, Gradients::GradientManager& manager); + boost::logic::tribool passes_conditions(Echo& echo); + ///An internal function that has all of the constraints register their respective Gradients with the GradientManager + void queue_gradients(Gradients::GradientManager& manager); + int get_building_type() const { return building_type; } + int get_number_of_workers() const { return number_of_workers; } + int building_type; + int number_of_workers; + int id; + std::vector > constraints; + std::vector > conditions; + }; + + ///This class is used for quick lookup of flags, which aren't stored in Map like other buildings. + class FlagMap + { + public: + explicit FlagMap(Echo& echo); + int get_flag(int x, int y); + private: + friend class AIEcho::Construction::BuildingRegister; + friend class AIEcho::Echo; + void set_flag(int x, int y, int gid); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + std::vector flagmap; + int width; + Echo& echo; + }; + + ///The building register is a very important sub system of Echo. It keeps track of buildings. + ///A seemingly simple process, but very, very important. Buildings you construct are looked for, + ///found, recorded, etc. Allot of seemingly odd code is found here, meant to work arround some + ///of the difficulties of other parts of glob2, so that the AI programmer can have a seemless, + ///comfortable interface. Nothing here is directly important to an AI programmer. + ///The system puts buildings through three stages. The first is where the building order has been + ///issued by the ai, but it hasn't satisfied its conditions, and thus hasn't been sent to the glob2 + ///engine. The second is where the building conditions are satisfied and the building order + ///has been sent, but the engine is awaiting the pertimiter of the building to be cleared before + ///it sets the building in place. The third stage is where the building has been set in place, + ///and was detected on the map. In this stage, an engine gid has been found and a pointer to + ///the building in memory secured. The fourth stage is where the building is being upgraded. + ///This is to solve a very minor bug where a building is destroyed, then a different one + ///rebuilt in the same spot fast enough that the building register couldn't detect the change. + ///If the register knows when a building is being upgraded, it knows when the building is + ///expected to change in size and to what size, and this bug is solved. + ///Another unmentioned part is that during the second stage, the building can be timed out if + ///it was unable to be set for various reasons (ressources grew into its area) + class BuildingRegister + { + public: + BuildingRegister(Player* player, Echo& echo); + bool is_building_pending(unsigned int id); + bool is_building_found(unsigned int id); + bool is_building_upgrading(unsigned int id); + int get_type(unsigned int id); + int get_level(unsigned int id); + int get_assigned(unsigned int id); + Building* get_building(unsigned int id); + BuildingType* get_building_type(unsigned int id); + private: + friend class AIEcho::SearchTools::building_search_iterator; + friend class AIEcho::SearchTools::BuildingSearch; + friend class AIEcho::Construction::BuildingOrder; + friend class AIEcho::Echo; + + friend class AIEcho::Conditions::NotUnderConstruction; + friend class AIEcho::Conditions::UnderConstruction; + friend class AIEcho::Conditions::BeingUpgraded; + friend class AIEcho::Conditions::BeingUpgradedTo; + friend class AIEcho::Conditions::SpecificBuildingType; + friend class AIEcho::Conditions::NotSpecificBuildingType; + friend class AIEcho::Conditions::BuildingLevel; + friend class AIEcho::Conditions::Upgradable; + friend class AIEcho::Conditions::EnemyBuildingDestroyed; + + friend class AIEcho::Management::AssignWorkers; + friend class AIEcho::Management::ChangeSwarm; + friend class AIEcho::Management::DestroyBuilding; + friend class AIEcho::Management::RessourceTracker; + friend class AIEcho::Management::AddRessourceTracker; + friend class AIEcho::Management::PauseRessourceTracker; + friend class AIEcho::Management::UnPauseRessourceTracker; + friend class AIEcho::Management::ChangeFlagSize; + friend class AIEcho::Management::ChangeFlagMinimumLevel; + friend class AIEcho::Management::GlobalManagementOrder; + friend class AIEcho::Management::AddArea; + friend class AIEcho::Management::RemoveArea; + friend class AIEcho::Management::ChangeAlliances; + friend class AIEcho::Management::UpgradeRepair; + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + + ///This function initiates the BuildingRegister with any buildings that already exist on the map. + void initiate(); + ///This function registers a new building. When the building orders conditions are satisfied and the order + ///for the construction is sent to the game engine, call issue_order. + unsigned int register_building(); + ///After registering a building, this tells the register that an order for the construction has commenced + void issue_order(int id, int x, int y, int building_type); + ///Removes the building from the list of pending buildings. This may been to be done in the event that the + ///conditions for the buildings constructed can never be satisfied. + void remove_building(int id); + void set_upgrading(unsigned int id); + void tick(); + + typedef std::map >::iterator pending_iterator; + typedef std::map >::iterator found_iterator; + + found_iterator begin() { return found_buildings.begin(); } + found_iterator end() { return found_buildings.end(); } + ///The last variables in both of these is simply a "this exists" variable. Its used to combat the fact + ///that pending_buildings[id] may create a new object, and the system can't tell the difference between it and something + ///real. So bassically, the last variable is set to true when the object is supposed to be there, false is + ///the default value if its accidentilly created. + std::map > pending_buildings; + std::map > found_buildings; + unsigned int building_id; + Player* player; + Echo& echo; + }; + + }; +} diff --git a/src/ai/echo/ConstructionConstraints.cpp b/src/ai/echo/ConstructionConstraints.cpp new file mode 100644 index 000000000..62db2825b --- /dev/null +++ b/src/ai/echo/ConstructionConstraints.cpp @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Building.h" +#include "Game.h" + +using namespace AIEcho; +using namespace AIEcho::Construction; + + +MinimumDistance::MinimumDistance(const Gradients::GradientInfo& gi, int distance) : gi(gi), gradient_cache(NULL), distance(distance) +{ + +} + + +int MinimumDistance::calculate_constraint(Echo& echo, int x, int y) +{ + return 0; +} + + +bool MinimumDistance::passes_constraint(Echo& echo, int x, int y) +{ + if(gradient_cache==NULL) + gradient_cache=&echo.get_gradient_manager().get_gradient(gi); + int height=gradient_cache->get_height(x, y); + if(height==AI_ECHO_GRADIENT_HEIGHT_UNREACHED) + return false; + if(height>=distance) + return true; + return false; +} + + +ConstraintType MinimumDistance::get_type() +{ + return CTMinimumDistance; +} + + + +bool MinimumDistance::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("MinimumDistance"); + distance = stream->readSint32("distance"); + gi.load(stream, player, versionMinor); + stream->readLeaveSection(); + return true; +} + + + +void MinimumDistance::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("MinimumDistance"); + stream->writeSint32(distance, "distance"); + gi.save(stream); + stream->writeLeaveSection(); +} + + + +MaximumDistance::MaximumDistance(const Gradients::GradientInfo& gi, int distance) : gi(gi), gradient_cache(NULL), distance(distance) +{ + +} + + +int MaximumDistance::calculate_constraint(Echo& echo, int x, int y) +{ + return 0; +} + + +bool MaximumDistance::passes_constraint(Echo& echo, int x, int y) +{ + if(gradient_cache==NULL) + gradient_cache=&echo.get_gradient_manager().get_gradient(gi); + int height=gradient_cache->get_height(x, y); + if(height==AI_ECHO_GRADIENT_HEIGHT_UNREACHED) + return false; + if(height<=distance) + return true; + return false; +} + + +ConstraintType MaximumDistance::get_type() +{ + return CTMaximumDistance; +} + + + +bool MaximumDistance::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("MaximumDistance"); + distance = stream->readSint32("distance"); + gi.load(stream, player, versionMinor); + stream->readLeaveSection(); + return true; +} + + + +void MaximumDistance::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("MaximumDistance"); + stream->writeSint32(distance, "distance"); + gi.save(stream); + stream->writeLeaveSection(); +} + + + +MinimizedDistance::MinimizedDistance(const Gradients::GradientInfo& gi, int weight) : gi(gi), gradient_cache(NULL), weight(weight) +{ + +} + + +int MinimizedDistance::calculate_constraint(Echo& echo, int x, int y) +{ + if(gradient_cache==NULL) + gradient_cache=&echo.get_gradient_manager().get_gradient(gi); + return -(gradient_cache->get_height(x, y) * weight); +} + + +bool MinimizedDistance::passes_constraint(Echo& echo, int x, int y) +{ + if(gradient_cache==NULL) + gradient_cache=&echo.get_gradient_manager().get_gradient(gi); + return gradient_cache->get_height(x, y)!=AI_ECHO_GRADIENT_HEIGHT_UNREACHED; +} + + +ConstraintType MinimizedDistance::get_type() +{ + return CTMinimizedDistance; +} + + + +bool MinimizedDistance::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("MinimizedDistance"); + weight = stream->readSint32("weight"); + gi.load(stream, player, versionMinor); + stream->readLeaveSection(); + return true; +} + + + +void MinimizedDistance::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("MinimizedDistance"); + stream->writeSint32(weight, "weight"); + gi.save(stream); + stream->writeLeaveSection(); +} + + + +MaximizedDistance::MaximizedDistance(const Gradients::GradientInfo& gi, int weight) : gi(gi), gradient_cache(NULL), weight(weight) +{ + +} + + +int MaximizedDistance::calculate_constraint(Echo& echo, int x, int y) +{ + if(gradient_cache==NULL) + gradient_cache=&echo.get_gradient_manager().get_gradient(gi); + return gradient_cache->get_height(x, y) * weight; +} + + +bool MaximizedDistance::passes_constraint(Echo& echo, int x, int y) +{ + if(gradient_cache==NULL) + gradient_cache=&echo.get_gradient_manager().get_gradient(gi); + return gradient_cache->get_height(x, y)!=AI_ECHO_GRADIENT_HEIGHT_UNREACHED; +} + + +ConstraintType MaximizedDistance::get_type() +{ + return CTMaximizedDistance; +} + + + +bool MaximizedDistance::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("MaximizedDistance"); + weight = stream->readSint32("weight"); + gi.load(stream, player, versionMinor); + stream->readLeaveSection(); + return true; +} + + + +void MaximizedDistance::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("MaximizedDistance"); + stream->writeSint32(weight, "weight"); + gi.save(stream); + stream->writeLeaveSection(); +} + + + +CenterOfBuilding::CenterOfBuilding(int gbid) : gbid(gbid) +{ + +} + + + +int CenterOfBuilding::calculate_constraint(Echo& echo, int x, int y) +{ + return 0; +} + + + +bool CenterOfBuilding::passes_constraint(Echo& echo, int x, int y) +{ + Building* b=echo.player->game->teams[Building::GIDtoTeam(gbid)]->myBuildings[Building::GIDtoID(gbid)]; + if(b) + { + if((b->posX+b->type->width/2)==x && (b->posY+b->type->height/2)==y) + { + return true; + } + } + return false; +} + + +ConstraintType CenterOfBuilding::get_type() +{ + return CTCenterOfBuilding; +} + + + +bool CenterOfBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("CenterOfBuilding"); + gbid = stream->readSint32("gbid"); + stream->readLeaveSection(); + return true; +} + + + +void CenterOfBuilding::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("CenterOfBuilding"); + stream->writeSint32(gbid, "gbid"); + stream->writeLeaveSection(); +} + + + +SinglePosition::SinglePosition(int posx, int posy) : posx(posx), posy(posy) +{ + +} + + + +int SinglePosition::calculate_constraint(Echo& echo, int x, int y) +{ + return 0; +} + + + +bool SinglePosition::passes_constraint(Echo& echo, int x, int y) +{ + if(posx==x && posy==y) + return true; + return false; +} + + + +ConstraintType SinglePosition::get_type() +{ + return CTSinglePosition; +} + + + +bool SinglePosition::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("SinglePosition"); + posx = stream->readSint32("posx"); + posy = stream->readSint32("posy"); + stream->readLeaveSection(); + return true; +} + + + +void SinglePosition::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("SinglePosition"); + stream->writeSint32(posx, "posx"); + stream->writeSint32(posy, "posy"); + stream->writeLeaveSection(); +} diff --git a/src/ai/echo/Echo.cpp b/src/ai/echo/Echo.cpp new file mode 100644 index 000000000..b26b7c1ef --- /dev/null +++ b/src/ai/echo/Echo.cpp @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Building.h" +#include +#include "IntBuildingType.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Order.h" +#include + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using std::shared_ptr; + + + +void AIEcho::signature_write(GAGCore::OutputStream *stream) +{ + stream->write("EchoSig", AI_ECHO_SIGNATURE_LENGTH, "signature"); +} + + + +void AIEcho::signature_check(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + char signature[AI_ECHO_SIGNATURE_LENGTH]; + stream->read(signature, AI_ECHO_SIGNATURE_LENGTH, "signature"); + if (memcmp(signature,"EchoSig", AI_ECHO_SIGNATURE_LENGTH)!=0) + { + + std::cerr<<"Signature match failed. Expected \"EchoSig\", recieved \""<(bo)); + bo->queue_gradients(get_gradient_manager()); + unsigned int id=br.register_building(); + bo->id=id; + return id; +} + + +void Echo::add_management_order(Management::ManagementOrder* mo) +{ + management_orders.push_back(std::shared_ptr(mo)); +} + + +void Echo::update_management_orders() +{ + for(std::vector >::iterator i=management_orders.begin(); i!=management_orders.end();) + { + boost::logic::tribool passes=(*i)->passes_conditions(*this); + if(passes) + { + size_t pos = i - management_orders.begin(); + (*i)->modify(*this); + management_orders.erase(management_orders.begin() + pos); + i = management_orders.begin() + pos; + continue; + } + else if(!passes) + { + } + else + { + size_t pos = i - management_orders.begin(); + management_orders.erase(i); + i = management_orders.begin() + pos; + continue; + } + ++i; + } +} + + + +void Echo::add_ressource_tracker(Management::RessourceTracker* rt, int building_id) +{ + ressource_trackers[building_id]=std::make_tuple(std::shared_ptr(rt), true); +} + + + +std::shared_ptr Echo::get_ressource_tracker(int building_id) +{ + if(ressource_trackers.find(building_id)==ressource_trackers.end()) + return std::shared_ptr(); + return std::get<0>(ressource_trackers[building_id]); +} + + + +void Echo::pause_ressource_tracker(int building_id) +{ + std::get<1>(ressource_trackers[building_id])=false; +} + + + +void Echo::unpause_ressource_tracker(int building_id) +{ + std::get<1>(ressource_trackers[building_id])=true; +} + + + +void Echo::update_ressource_trackers() +{ + for(std::map, bool> >::iterator i = ressource_trackers.begin(); i!=ressource_trackers.end();) + { + if(!br.is_building_found(i->first) && !br.is_building_pending(i->first)) + { + std::map, bool> >::iterator current=i; + ++i; + ressource_trackers.erase(current); + continue; + } + else if(br.is_building_found(i->first)) + { + if(std::get<1>(i->second)) + std::get<0>(i->second)->tick(); + } + ++i; + } +} + + + +void Echo::update_building_orders() +{ + for(std::vector >::iterator i=building_orders.begin(); i!=building_orders.end();) + { + boost::logic::tribool passes=(*i)->passes_conditions(*this); + if(passes) + { + if(!(previous_building_id==-1 || br.is_building_found(previous_building_id) || !br.is_building_pending(previous_building_id))) + break; + position p=(*i)->find_location(*this, player->map, *gm); + if(p.x != 0 || p.y != 0) + { + br.issue_order((*i)->id, p.x, p.y, (*i)->get_building_type()); + Sint32 type=-1; + if((*i)->get_building_type()>IntBuildingType::DEFENSE_BUILDING && (*i)->get_building_type() buildingsTypes.getTypeNum(IntBuildingType::reverseConversionMap[(*i)->get_building_type()], 0, false); + ManagementOrder* mo_flag=new AssignWorkers((*i)->get_number_of_workers(), (*i)->id); + add_management_order(mo_flag); + } + else + { + type=globalContainer->buildingsTypes.getTypeNum(IntBuildingType::reverseConversionMap[(*i)->get_building_type()], 0, true); + ManagementOrder* mo_during_construction=new AssignWorkers((*i)->get_number_of_workers(), (*i)->id); + mo_during_construction->add_condition(new ParticularBuilding(new UnderConstruction, (*i)->id)); + add_management_order(mo_during_construction); + } + orders.push_back(shared_ptr(new OrderCreate(player->team->teamNumber, p.x, p.y, type, 1, 1))); + previous_building_id=(*i)->id; + i=building_orders.erase(i); + break; + } + else + { + br.remove_building((*i)->id); + i=building_orders.erase(i); + continue; + } + } + else if(!passes) + { + } + else + { + br.remove_building((*i)->id); + i=building_orders.erase(i); + continue; + } + ++i; + } +} + + + +void Echo::init_starting_buildings() +{ + for(int t=0; tgame->teams[t]) + { + for(int bu=0; bugame->teams[t]->myBuildings[bu]; + if(b) + { + starting_buildings.insert(b->gid); + } + } + } + } +} + +void Echo::check_fruit() +{ + MapInfo mi(*this); + for(int x=0; x Echo::getOrder(void) +{ +// for(int x=0; xmap->getW(); ++x) +// { +// for(int y=0; ymap->getH(); ++y) +// { +// player->map->setMapDiscovered(x, y, player->team->me); +// } +// } +/* + if(timer%128==0) + { + OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend("glob2.world-desynchronization.dump.txt")); + player->game->save(stream, false, "glob2.world-desynchronization.dump.txt"); + delete stream; + } +*/ + if(!gm) + { + gm.reset(new GradientManager(player->map)); + update_gm=true; + for(int x=0; xteam->game->gameHeader.getNumberOfPlayers(); ++x) + { + if(player->team->game->players[x]!=NULL) + { + if(player->team->game->players[x]->type>=BasePlayer::P_AI) + { + Echo* other=dynamic_cast(player->team->game->players[x]->ai->aiImplementation); + if(other) + { + if(!other->gm) + { + other->gm=gm; + other->update_gm=false; +// std::cout<<"Linked with another AI, number "<team->allies; + enemies=player->team->enemies; + market_view=player->team->sharedVisionExchange; + inn_view=player->team->sharedVisionFood; + other_view=player->team->sharedVisionOther; + } + + if(!orders.empty()) + { + std::shared_ptr order=orders.front(); + orders.erase(orders.begin()); + return order; + } + if(update_gm) + gm->update(); + br.tick(); + update_ressource_trackers(); + update_management_orders(); + echoai->tick(*this); + update_management_orders(); + update_building_orders(); + timer++; + from_load_timer++; + return std::shared_ptr(new NullOrder()); +} diff --git a/src/ai/echo/Echo.h b/src/ai/echo/Echo.h new file mode 100644 index 000000000..5abbdb066 --- /dev/null +++ b/src/ai/echo/Echo.h @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#pragma once + +#include "echo/Position.h" +#include "echo/Gradients.h" +#include "echo/Construction.h" +#include "echo/Conditions.h" +#include "echo/Management.h" +#include "echo/SearchTools.h" + +#include "AIEchoTuning.h" +#include "AIImplementation.h" +#include "Order.h" +#include "Player.h" +#include "TeamStat.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace AIEcho +{ + ///This is a base class for all EchoAI's + class EchoAI + { + public: + virtual ~EchoAI(){} + ///Your AI must implement the load function that loads all of its data from a stream + virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; + ///Your AI must implement a save function that saves all of its data to a stream + virtual void save(GAGCore::OutputStream *stream)=0; + ///This function is called every tick, about 25 times per second. This is where you put + ///all of you AI's logic + virtual void tick(Echo& echo)=0; + ///Handles a message sent from the AI to itself if certain conditions are satisfied. + virtual void handle_message(Echo& echo, const std::string& message)=0; + }; + + ///Reach To Infinity is a simple economic test AI for Echo. + class ReachToInfinity : public EchoAI + { + public: + ReachToInfinity(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + void tick(Echo& echo); + void handle_message(Echo& echo, const std::string& message); + private: + // Tick helpers — each guards on its own timer condition and is invoked + // unconditionally from tick(). Implementations are split across + // ReachToInfinity.cpp, ReachToInfinityBuilding.cpp, and + // ReachToInfinityFlags.cpp; the call order in tick() matches the + // original sequence of if-blocks. + void tick_initial_setup(Echo& echo); + void tick_explorer_flags_fruit(Echo& echo); + void tick_explorer_flags_enemies(Echo& echo); + void tick_inns_near_wheat(Echo& echo); + void tick_swarms_near_wheat(Echo& echo); + void tick_racetrack_near_stone_wood(Echo& echo); + void tick_swimmingpool_near_wheat_wood(Echo& echo); + void tick_school_inland(Echo& echo); + void tick_upgrade_l1_to_l2(Echo& echo); + void tick_upgrade_l2_to_l3(Echo& echo); + void tick_delete_old_inns_swarms(Echo& echo); + void tick_farming_areas(Echo& echo); + + int timer; + bool flag_on_cherry; + bool flag_on_orange; + bool flag_on_prune; + std::set flags_on_enemy; + }; + + ///This is the part that ties everything together. This bridges the interface between the game and the AI system. + ///This is where you send all of you're orders. + class Echo : public AIImplementation + { + public: + Echo(EchoAI* echoai, Player* player); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + + std::shared_ptr getOrder(void); + + unsigned int add_building_order(Construction::BuildingOrder* bo); + void add_management_order(Management::ManagementOrder* mo); + void add_ressource_tracker(Management::RessourceTracker* rt, int building_id); + std::shared_ptr get_ressource_tracker(int building_id); + + TeamStat& get_team_stats(); + void flare(int x, int y); + Construction::BuildingRegister& get_building_register(); + Construction::FlagMap& get_flag_map(); + void push_order(std::shared_ptr order); + Gradients::GradientManager& get_gradient_manager(); + std::set& get_starting_buildings(); + + bool is_fruit_on_map() { return is_fruit; } + + Player* player; + private: + + friend class AIEcho::Management::AddRessourceTracker; + friend class AIEcho::Management::PauseRessourceTracker; + friend class AIEcho::Management::UnPauseRessourceTracker; + friend class AIEcho::Management::ChangeAlliances; + friend class AIEcho::Management::SendMessage; + + + Uint32 allies; + Uint32 enemies; + Uint32 inn_view; + Uint32 market_view; + Uint32 other_view; + + void update_management_orders(); + void pause_ressource_tracker(int building_id); + void unpause_ressource_tracker(int building_id); + void init_starting_buildings(); + void update_ressource_trackers(); + void update_building_orders(); + void check_fruit(); + + std::list > orders; + std::shared_ptr echoai; + std::shared_ptr gm; + Construction::BuildingRegister br; + Construction::FlagMap fm; + std::vector > building_orders; + std::vector > management_orders; + std::map, bool> > ressource_trackers; + typedef std::map, bool> >::iterator tracker_iterator; + std::set starting_buildings; + int timer; + ///This to keep multiuple buildings from being constructed on the same tick. + ///Before the next building is constructed, the previous building must be + ///found on the BuildingRegister + int previous_building_id; + bool update_gm; + bool is_fruit; + + int from_load_timer; + }; + + const unsigned int INVALID_BUILDING=65535; + + // --- AI Echo per-slice magic-number renames (Phase 3b) --- + // Sentinels — distinct meanings; do not collapse into a single constant. + + /// BuildingRegister: pending-building tuple's "ticks since registered" slot + /// is set to this value to signal "engine order not yet sent — still waiting + /// on conditions" (Construction.cpp:667, 798). + static constexpr int AI_ECHO_PENDING_NOT_ISSUED = -1; + + /// SearchTools iterator initial state — "iteration has not yet started; the + /// first set_to_next() call will seed the cursor". Distinct from the + /// wildcard sentinels below (SearchTools.cpp building_search_iterator, + /// enemy_team_iterator, enemy_building_iterator). + static constexpr int AI_ECHO_ITER_NOT_STARTED = -1; + + /// enemy_building_iterator: building_type == this means "match any building + /// type" (SearchTools.h:113-116, SearchTools.cpp:313). + static constexpr int AI_ECHO_WILDCARD_TYPE = -1; + + /// enemy_building_iterator: level == this means "match any level" + /// (SearchTools.h:113-116, SearchTools.cpp:314). + static constexpr int AI_ECHO_WILDCARD_LEVEL = -1; + + // On-disk encoding of boost::logic::tribool inside AI Echo save streams. + // Used by BuildingRegister and ChangeAlliances. NOT a wire-format enum — + // these bytes only appear in saved-game/AI snapshots. + static constexpr int AI_ECHO_TRIBOOL_FALSE = 0; + static constexpr int AI_ECHO_TRIBOOL_TRUE = 1; + static constexpr int AI_ECHO_TRIBOOL_INDETERMINATE = 2; + + /// Convert the AI Echo "user-facing" 1-based building level (the level + /// number a script writer types) to the engine's 0-based BuildingType::level + /// (Conditions.cpp:481, 524, Management.cpp:624, SearchTools.cpp:314). + static constexpr int AI_ECHO_LEVEL_OFFSET_USER_TO_ENGINE = 1; + + /// In BeingUpgradedTo::passes, a finished (non-site) building's current + /// engine level is target-2: the user level is 1-based AND the building + /// hasn't yet stepped up. Distinct from the offset above (Conditions.cpp:486). + static constexpr int AI_ECHO_LEVEL_OFFSET_FINISHED_TO_TARGET = 2; + + // AI Echo's internal Gradient encoding (in echo/Gradient.cpp). This is + // SEPARATE from the engine's Map gradient sentinels (GRADIENT_FORBIDDEN + // etc. in MapInternal.h) — Echo BFS uses a tiny 3-value encoding offset by + // 2 so that Gradient::get_height() returns 0 at sources, -1 on obstacles, + // and -2 on cells the BFS never reached. + + /// Returned by Gradient::get_height() for tiles that BFS never reached + /// (Construction.cpp:96, 149, 203, 253; corresponds to internal value 0). + static constexpr int AI_ECHO_GRADIENT_HEIGHT_UNREACHED = -2; + + /// Internal seed value written for source tiles before BFS expansion; the + /// +2 offset is reversed by Gradient::get_height() (Gradient.cpp:227, 240). + static constexpr int AI_ECHO_GRADIENT_SOURCE_SEED = 2; + + /// Internal value written for obstacle tiles; never expanded by BFS (which + /// only fills cells == 0). get_height() returns -1 on these (Gradient.cpp:231). + static constexpr int AI_ECHO_GRADIENT_OBSTACLE_MARKER = 1; + + void signature_write(GAGCore::OutputStream *stream); + void signature_check(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); +}; + + + +inline TeamStat& AIEcho::Echo::get_team_stats() +{ + return *player->team->stats.getLatestStat(); +} + + + +inline void AIEcho::Echo::flare(int x, int y) +{ + orders.push_back(std::shared_ptr(new MapMarkOrder(player->team->teamNumber, x, y))); +} + + + +inline AIEcho::Construction::BuildingRegister& AIEcho::Echo::get_building_register() +{ + return br; +} + + + +inline AIEcho::Construction::FlagMap& AIEcho::Echo::get_flag_map() +{ + return fm; +} + + + +inline void AIEcho::Echo::push_order(std::shared_ptr order) +{ + orders.push_back(order); +} + + + +inline AIEcho::Gradients::GradientManager& AIEcho::Echo::get_gradient_manager() +{ + return *gm; +} + + + +inline std::set& AIEcho::Echo::get_starting_buildings() +{ + return starting_buildings; +} diff --git a/src/ai/echo/EchoSerialization.cpp b/src/ai/echo/EchoSerialization.cpp new file mode 100644 index 000000000..b2b31e075 --- /dev/null +++ b/src/ai/echo/EchoSerialization.cpp @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Order.h" +#include + +using namespace AIEcho; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; + +bool Echo::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("EchoAI"); + signature_check(stream, player, versionMinor); + + stream->readEnterSection("orders"); + Uint32 ordersSize = stream->readUint32("size"); + for (Uint32 ordersIndex = 0; ordersIndex < ordersSize; ordersIndex++) + { + stream->readEnterSection(ordersIndex); + size_t size=stream->readUint32("size"); + Uint8* buffer = new Uint8[size+1]; + stream->read(buffer, size+1, "data"); + orders.push_back(Order::getOrder(buffer, size+1, versionMinor)); + // FIXME : clear the container before load + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + signature_check(stream, player, versionMinor); + + br.load(stream, player, versionMinor); + + signature_check(stream, player, versionMinor); + + fm.load(stream, player, versionMinor); + + signature_check(stream, player, versionMinor); + + + stream->readEnterSection("management_orders"); + Uint32 managementSize=stream->readUint32("size"); + for(Uint32 managementIndex = 0; managementIndex < managementSize; ++managementIndex) + { + stream->readEnterSection(managementIndex); + signature_check(stream, player, versionMinor); + signature_check(stream, player, versionMinor); + std::shared_ptr mo=std::shared_ptr(ManagementOrder::load_order(stream, player, versionMinor)); + management_orders.push_back(mo); + signature_check(stream, player, versionMinor); + signature_check(stream, player, versionMinor); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + signature_check(stream, player, versionMinor); + + stream->readEnterSection("building_orders"); + Uint32 buildingSize=stream->readUint32("size"); + building_orders.resize(buildingSize); + for(Uint32 buildingIndex = 0; buildingIndex < buildingSize; ++buildingIndex) + { + stream->readEnterSection(buildingIndex); + building_orders[buildingIndex]=std::shared_ptr(new BuildingOrder); + building_orders[buildingIndex]->load(stream, player, versionMinor); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + + signature_check(stream, player, versionMinor); + + stream->readEnterSection("ressource_trackers"); + Uint32 ressourceTrackerSize=stream->readUint32("size"); + for(Uint32 ressourceTrackerIndex=0; ressourceTrackerIndexreadEnterSection(ressourceTrackerIndex); + int id=stream->readUint32("echo_building_id"); + std::shared_ptr rt(new RessourceTracker(*this, stream, player, versionMinor)); + bool activated=stream->readUint8("active"); + ressource_trackers[id]=std::make_tuple(rt, activated); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + signature_check(stream, player, versionMinor); + + stream->readEnterSection("starting_buildings"); + Uint32 startingBuildingSize=stream->readUint32("size"); + for(Uint32 startingBuildingIndex=0; startingBuildingIndexreadEnterSection(startingBuildingIndex); + starting_buildings.insert(stream->readUint32("gid")); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + + signature_check(stream, player, versionMinor); + + timer=stream->readUint32("timer"); + update_gm=stream->readUint8("update_gm"); + + allies=stream->readUint32("allies"); + enemies=stream->readUint32("enemies"); + inn_view=stream->readUint32("inn_view"); + market_view=stream->readUint32("market_view"); + other_view=stream->readUint32("other_view"); + + signature_check(stream, player, versionMinor); + + echoai->load(stream, player, versionMinor); + + + signature_check(stream, player, versionMinor); + + stream->readLeaveSection(); + signature_check(stream, player, versionMinor); + + + return true; +} + + + +void Echo::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("EchoAI"); + + signature_write(stream); + + stream->writeEnterSection("orders"); + stream->writeUint32((Uint32)orders.size(), "size"); + Uint32 ordersIndex = 0; + for (std::list >::iterator i = orders.begin(); i!=orders.end(); ++i) + { + stream->writeEnterSection(ordersIndex); + stream->writeUint32((*i)->getDataLength(), "size"); + ///one byte indicating the type is required to be written for order. + stream->writeUint8((*i)->getOrderType(), "type"); + stream->write((*i)->getData(), (*i)->getDataLength(), "data"); + stream->writeLeaveSection(); + ordersIndex++; + } + stream->writeLeaveSection(); + + signature_write(stream); + + br.save(stream); + + signature_write(stream); + + fm.save(stream); + + signature_write(stream); + + + stream->writeEnterSection("management_orders"); + stream->writeUint32(management_orders.size(), "size"); + for(Uint32 managementIndex = 0; managementIndex < management_orders.size(); ++managementIndex) + { + stream->writeEnterSection(managementIndex); + signature_write(stream); + signature_write(stream); + Management::ManagementOrder::save_order(management_orders[managementIndex].get(), stream); + signature_write(stream); + signature_write(stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + signature_write(stream); + + stream->writeEnterSection("building_orders"); + stream->writeUint32(building_orders.size(), "size"); + for(Uint32 buildingIndex = 0; buildingIndex < building_orders.size(); ++buildingIndex) + { + stream->writeEnterSection(buildingIndex); + building_orders[buildingIndex]->save(stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + signature_write(stream); + + stream->writeEnterSection("ressource_trackers"); + stream->writeUint32(ressource_trackers.size(), "size"); + Uint32 ressourceTrackerIndex=0; + for(tracker_iterator i=ressource_trackers.begin(); i!=ressource_trackers.end(); ++ressourceTrackerIndex, ++i) + { + stream->writeEnterSection(ressourceTrackerIndex); + stream->writeUint32(i->first, "echo_building_id"); + std::get<0>(i->second)->save(stream); + stream->writeUint8(std::get<1>(i->second), "active"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + signature_write(stream); + + stream->writeEnterSection("starting_buildings"); + Uint32 startingBuildingIndex=0; + stream->writeUint32(starting_buildings.size(), "size"); + for(std::set::iterator i=starting_buildings.begin(); i!=starting_buildings.end(); ++i, ++startingBuildingIndex) + { + stream->writeEnterSection(startingBuildingIndex); + stream->writeUint32(*i, "gid"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + signature_write(stream); + + stream->writeUint32(timer, "timer"); + stream->writeUint8(update_gm, "update_gm"); + + stream->writeUint32(allies, "allies"); + stream->writeUint32(enemies, "enemies"); + stream->writeUint32(inn_view, "inn_view"); + stream->writeUint32(market_view, "market_view"); + stream->writeUint32(other_view, "other_view"); + + signature_write(stream); + + echoai->save(stream); + + + signature_write(stream); + + stream->writeLeaveSection(); + signature_write(stream); +} diff --git a/src/ai/echo/Entities.cpp b/src/ai/echo/Entities.cpp new file mode 100644 index 000000000..4a6ac195c --- /dev/null +++ b/src/ai/echo/Entities.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; + + +Entities::Entity* Entities::Entity::load_entity(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("Entity"); + EntityType type = static_cast(stream->readUint32("type")); + Entity* entity = NULL; + switch(type) + { + case Entities::EBuilding: + entity = new Entities::Building; + entity->load(stream, player, versionMinor); + break; + case Entities::EAnyTeamBuilding: + entity = new Entities::AnyTeamBuilding; + entity->load(stream, player, versionMinor); + break; + case Entities::EAnyBuilding: + entity = new Entities::AnyBuilding; + entity->load(stream, player, versionMinor); + break; + case Entities::ERessource: + entity = new Entities::Ressource; + entity->load(stream, player, versionMinor); + break; + case Entities::EAnyRessource: + entity = new Entities::AnyRessource; + entity->load(stream, player, versionMinor); + break; + case Entities::EWater: + entity = new Entities::Water; + entity->load(stream, player, versionMinor); + break; + case Entities::EPosition: + entity = new Entities::Position; + entity->load(stream, player, versionMinor); + break; + case Entities::ESand: + entity = new Entities::Sand; + entity->load(stream, player, versionMinor); + break; + }; + stream->readLeaveSection(); + return entity; +} + + + +void Entities::Entity::save_entity(Entity* entity, GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Entity"); + stream->writeUint32(entity->get_type(), "type"); + entity->save(stream); + stream->writeLeaveSection(); +} + diff --git a/src/ai/echo/EntitiesBuilding.cpp b/src/ai/echo/EntitiesBuilding.cpp new file mode 100644 index 000000000..24406002d --- /dev/null +++ b/src/ai/echo/EntitiesBuilding.cpp @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Building.h" +#include "Game.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; + + +Entities::Building::Building(int building_type, int team, bool under_construction) : building_type(building_type), team(team), under_construction(under_construction) +{ + +} + + +bool Entities::Building::is_entity(Map* map, int posx, int posy) +{ + int building_id=map->getBuilding(posx, posy); + if(building_id!=NOGBID) + { + int team_id=::Building::GIDtoTeam(building_id); + if(team_id==team && + map->game->teams[team_id]->myBuildings[::Building::GIDtoID(building_id)]->typeNum==building_type && + (map->game->teams[team_id]->myBuildings[::Building::GIDtoID(building_id)]->constructionResultState==::Building::NO_CONSTRUCTION || under_construction) + ) + { + return true; + } + } + return false; +} + +bool Entities::Building::operator==(const Entity& rhs) const +{ + if(typeid(rhs)==typeid(Entities::Building) && + static_cast(rhs).building_type==building_type && + static_cast(rhs).team==team && + static_cast(rhs).under_construction==under_construction + ) + return true; + return false; +} + + + +bool Entities::Building::can_change() +{ + return true; +} + + + +Entities::EntityType Entities::Building::get_type() +{ + return Entities::EBuilding; +} + + + +bool Entities::Building::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("Building"); + building_type = stream->readSint32("building_type"); + team = stream->readSint32("team"); + under_construction = stream->readUint8("under_construction"); + stream->readLeaveSection(); + return true; +} + + + +void Entities::Building::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Building"); + stream->writeSint32(building_type, "building_type"); + stream->writeSint32(team, "team"); + stream->writeUint8(under_construction, "under_construction"); + stream->writeLeaveSection(); +} + + + +Entities::AnyTeamBuilding::AnyTeamBuilding(int team, bool under_construction) : team(team), under_construction(under_construction) +{ + +} + + + +bool Entities::AnyTeamBuilding::is_entity(Map* map, int posx, int posy) +{ + int building_id=map->getBuilding(posx, posy); + if(building_id!=NOGBID) + { + int team_id=::Building::GIDtoTeam(building_id); + if(team_id==team && + (map->game->teams[team_id]->myBuildings[::Building::GIDtoID(building_id)]->constructionResultState==::Building::NO_CONSTRUCTION || under_construction) + ) + { + return true; + } + } + return false; +} + + + +bool Entities::AnyTeamBuilding::operator==(const Entity& rhs) const +{ + if(typeid(rhs)==typeid(Entities::AnyTeamBuilding) && + static_cast(rhs).team==team && + static_cast(rhs).under_construction==under_construction + ) + return true; + return false; +} + + + +bool Entities::AnyTeamBuilding::can_change() +{ + return true; +} + + + +Entities::EntityType Entities::AnyTeamBuilding::get_type() +{ + return Entities::EAnyTeamBuilding; +} + + + +bool Entities::AnyTeamBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("AnyTeamBuilding"); + team = stream->readSint32("team"); + under_construction = stream->readUint8("under_construction"); + stream->readLeaveSection(); + return true; +} + + + +void Entities::AnyTeamBuilding::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("AnyTeamBuilding"); + stream->writeSint32(team, "team"); + stream->writeUint8(under_construction, "under_construction"); + stream->writeLeaveSection(); +} + + + +Entities::AnyBuilding::AnyBuilding(bool under_construction) : under_construction(under_construction) +{ +} + + + +bool Entities::AnyBuilding::is_entity(Map* map, int posx, int posy) +{ + int building_id=map->getBuilding(posx, posy); + if(building_id!=NOGBID) + { + int team_id=::Building::GIDtoTeam(building_id); + if(map->game->teams[team_id]->myBuildings[::Building::GIDtoID(building_id)]->constructionResultState==::Building::NO_CONSTRUCTION || under_construction) + return true; + } + return false; +} + + + +bool Entities::AnyBuilding::operator==(const Entity& rhs) const +{ + if(typeid(rhs)==typeid(Entities::AnyBuilding) && + static_cast(rhs).under_construction==under_construction + ) + return true; + return false; +} + + + +bool Entities::AnyBuilding::can_change() +{ + return true; +} + + + +Entities::EntityType Entities::AnyBuilding::get_type() +{ + return Entities::EAnyBuilding; +} + + + +bool Entities::AnyBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("AnyBuilding"); + under_construction = stream->readUint8("under_construction"); + stream->readLeaveSection(); + return true; +} + + + +void Entities::AnyBuilding::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("AnyBuilding"); + stream->writeUint8(under_construction, "under_construction"); + stream->writeLeaveSection(); +} + diff --git a/src/ai/echo/EntitiesResource.cpp b/src/ai/echo/EntitiesResource.cpp new file mode 100644 index 000000000..8b78d45c2 --- /dev/null +++ b/src/ai/echo/EntitiesResource.cpp @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; + + +Entities::Ressource::Ressource(int ressource_type) : ressource_type(ressource_type) +{ + +} + + + +bool Entities::Ressource::is_entity(Map* map, int posx, int posy) +{ + if(map->isRessourceTakeable(posx, posy, ressource_type)) + { + return true; + } + return false; +} + + + +bool Entities::Ressource::operator==(const Entity& rhs) const +{ + if(typeid(rhs)==typeid(Entities::Ressource) && + static_cast(rhs).ressource_type==ressource_type + ) + return true; + return false; +} + + + +bool Entities::Ressource::can_change() +{ + if(ressource_type==WOOD || ressource_type==CORN || ressource_type==ALGA) + return true; + return false; +} + + + +Entities::EntityType Entities::Ressource::get_type() +{ + return Entities::ERessource; +} + + + +bool Entities::Ressource::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("Ressource"); + ressource_type = stream->readSint32("ressource_type"); + stream->readLeaveSection(); + return true; +} + + + +void Entities::Ressource::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Ressource"); + stream->writeSint32(ressource_type, "ressource_type"); + stream->writeLeaveSection(); +} + + + +Entities::AnyRessource:: AnyRessource() +{ + +} + + + +bool Entities::AnyRessource:: is_entity(Map* map, int posx, int posy) +{ + if(map->isRessource(posx, posy)) + { + return true; + } + return false; +} + + + +bool Entities::AnyRessource::operator==(const Entity& rhs) const +{ + if(typeid(rhs)==typeid(Entities::AnyRessource)) + return true; + return false; +} + + + +bool Entities::AnyRessource::can_change() +{ + return true; +} + + + +Entities::EntityType Entities::AnyRessource::get_type() +{ + return Entities::EAnyRessource; +} + + + +bool Entities::AnyRessource::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("AnyRessource"); + stream->readLeaveSection(); + return true; +} + + + +void Entities::AnyRessource::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("AnyRessource"); + stream->writeLeaveSection(); +} + diff --git a/src/ai/echo/EntitiesTerrain.cpp b/src/ai/echo/EntitiesTerrain.cpp new file mode 100644 index 000000000..9457d3009 --- /dev/null +++ b/src/ai/echo/EntitiesTerrain.cpp @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; + + +Entities::Water::Water() +{ + +} + + + +bool Entities::Water::is_entity(Map* map, int posx, int posy) +{ + if(map->isWater(posx, posy)) + { + return true; + } + return false; +} + + + +bool Entities::Water::operator==(const Entity& rhs) const +{ + if(typeid(rhs)==typeid(Entities::Water)) + return true; + return false; +} + + + +bool Entities::Water::can_change() +{ + return false; +} + + + +Entities::EntityType Entities::Water::get_type() +{ + return Entities::EWater; +} + + + +bool Entities::Water::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("Water"); + stream->readLeaveSection(); + return true; +} + + + +void Entities::Water::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Water"); + stream->writeLeaveSection(); +} + + +Entities::Position::Position(int x, int y) : x(x), y(y) +{ + +} + + +bool Entities::Position::is_entity(Map* map, int posx, int posy) +{ + if(x==posx && y==posy) + { + return true; + } + return false; +} + + +bool Entities::Position::operator==(const Entity& rhs) const +{ + if(typeid(rhs)==typeid(Entities::Position) && + static_cast(rhs).x==x && + static_cast(rhs).y==y) + return true; + return false; +} + + +bool Entities::Position::can_change() +{ + return false; +} + + +Entities::EntityType Entities::Position::get_type() +{ + return Entities::EPosition; +} + + +bool Entities::Position::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("Position"); + x=stream->readSint32("posX"); + y=stream->readSint32("posY"); + stream->readLeaveSection(); + return false; +} + + +void Entities::Position::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Position"); + stream->writeSint32(x, "posX"); + stream->writeSint32(y, "posy"); + stream->writeLeaveSection(); +} + + + +Entities::Sand::Sand() +{ + +} + + + +bool Entities::Sand::is_entity(Map* map, int posx, int posy) +{ + if(map->hasSand(posx, posy)) + { + return true; + } + return false; +} + + + +bool Entities::Sand::operator==(const Entity& rhs) const +{ + if(typeid(rhs)==typeid(Entities::Sand)) + return true; + return false; +} + + + +bool Entities::Sand::can_change() +{ + return false; +} + + + +Entities::EntityType Entities::Sand::get_type() +{ + return Entities::ESand; +} + + + +bool Entities::Sand::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("Sand"); + stream->readLeaveSection(); + return true; +} + + + +void Entities::Sand::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Sand"); + stream->writeLeaveSection(); +} + diff --git a/src/ai/echo/Gradient.cpp b/src/ai/echo/Gradient.cpp new file mode 100644 index 000000000..c07107b85 --- /dev/null +++ b/src/ai/echo/Gradient.cpp @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Building.h" +#include +#include +#include +#include +#include +#include "BuildingType.h" +#include "IntBuildingType.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Order.h" +#include +#include "Utilities.h" +#include +#include "Brush.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; +using std::shared_ptr; + + +void GradientInfo::add_source(Entities::Entity* source) +{ + sources.push_back(std::shared_ptr(source)); +} + + +void GradientInfo::add_obstacle(Entities::Entity* obstacle) +{ + obstacles.push_back(std::shared_ptr(obstacle)); +} + + +bool GradientInfo::match_source(Map* map, int posx, int posy) +{ + for(unsigned int x=0; xis_entity(map, posx, posy)) + return true; + return false; +} + + +bool GradientInfo::match_obstacle(Map* map, int posx, int posy) +{ + for(unsigned int x=0; xis_entity(map, posx, posy)) + return true; + return false; +} + + +bool GradientInfo::operator==(const GradientInfo& rhs) const +{ + if(sources.size()!=rhs.sources.size() || obstacles.size() != rhs.obstacles.size()) + return false; + for(unsigned int i=0; ican_change()) + { + needs_updated=true; + return true; + } + } + + for(unsigned int i=0; ican_change()) + { + needs_updated=true; + return true; + } + } + } + return false; +} + + + +bool GradientInfo::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("GradientInfo"); + + stream->readEnterSection("sources"); + int size=stream->readUint32("size"); + sources.resize(size); + for(int n=0; nreadEnterSection(n); + sources[n]=std::shared_ptr(Entities::Entity::load_entity(stream, player, versionMinor)); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + stream->readEnterSection("obstacles"); + size=stream->readUint32("size"); + obstacles.resize(size); + for(int n=0; nreadEnterSection(n); + obstacles[n]=std::shared_ptr(Entities::Entity::load_entity(stream, player, versionMinor)); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + stream->readLeaveSection(); + return true; +} + + + +void GradientInfo::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("GradientInfo"); + + stream->writeEnterSection("sources"); + stream->writeUint32(sources.size(), "size"); + for(unsigned n=0; nwriteEnterSection(n); + Entities::Entity::save_entity(sources[n].get(), stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->writeEnterSection("obstacles"); + stream->writeUint32(obstacles.size(), "size"); + for(unsigned n=0; nwriteEnterSection(n); + Entities::Entity::save_entity(obstacles[n].get(), stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->writeLeaveSection(); +} + + + +GradientInfo make_gradient_info(Entities::Entity* source) +{ + GradientInfo gi; + gi.add_source(source); + return gi; +} + + + +GradientInfo make_gradient_info_obstacle(Entities::Entity* source, Entities::Entity* obstacle) +{ + GradientInfo gi; + gi.add_source(source); + gi.add_obstacle(obstacle); + return gi; +} + + + +GradientInfo make_gradient_info(Entities::Entity* source1, Entities::Entity* source2) +{ + GradientInfo gi; + gi.add_source(source1); + gi.add_source(source2); + return gi; +} + + + +GradientInfo make_gradient_info_obstacle(Entities::Entity* source1, Entities::Entity* source2, Entities::Entity* obstacle) +{ + GradientInfo gi; + gi.add_source(source1); + gi.add_source(source2); + gi.add_obstacle(obstacle); + return gi; +} + + + +void Gradient::recalculate(Map* map) +{ + width=map->getW(); + gradient.resize(map->getW()*map->getH()); + std::fill(gradient.begin(), gradient.end(),0); + + std::queue positions; + for(int x=0; xgetW(); ++x) + { + for(int y=0; ygetH(); ++y) + { + if(gradient_info.match_source(map, x, y)) + { + gradient[get_pos(x, y)]=AI_ECHO_GRADIENT_SOURCE_SEED; + positions.push(position(x, y)); + } + else if(gradient_info.match_obstacle(map, x, y)) + gradient[get_pos(x, y)]=AI_ECHO_GRADIENT_OBSTACLE_MARKER; + } + } + expand_bfs(positions); +} + + +int Gradient::get_height(int posx, int posy) const +{ + // Reverses the +SOURCE_SEED offset applied at recalculate(): source tiles + // (internal value 2) → height 0; obstacles (1) → -1; unreached (0) → -2. + return gradient[get_pos(posx, posy)]-AI_ECHO_GRADIENT_SOURCE_SEED; +} + + +bool Gradient::within_dist(int posx, int posy, int max_dist) const +{ + int h = get_height(posx, posy); + return h >= 0 && h < max_dist; +} + + + +GradientManager::GradientManager(Map* map) : map(map), cur_update(0), timer(0) +{ +} + + +Gradient& GradientManager::get_gradient(const GradientInfo& gi) +{ + for(std::vector >::iterator i=gradients.begin(); i!=gradients.end(); ++i) + { + if((*i)->get_gradient_info() == gi) + { + if(ticks_since_update[i-gradients.begin()]>AI_ECHO_GRADIENT_STALE_TICKS) + { + ticks_since_update[i-gradients.begin()]=0; + (*i)->recalculate(map); + } + return **i; + } + } + + //Did not find a matching gradient + gradients.push_back(std::shared_ptr(new Gradient(gi))); + (*(gradients.end()-1))->recalculate(map); + ticks_since_update.push_back(0); + return **(gradients.end()-1); +} + + +void GradientManager::queue_gradient(const GradientInfo& gi) +{ + for(unsigned i=0; iget_gradient_info() == gi) + { + if(gi.needs_updating()) + { + queuedGradients.push(i); + } + return; + } + } + //Did not find a matching gradient + gradients.push_back(std::shared_ptr(new Gradient(gi))); + ticks_since_update.push_back(AI_ECHO_GRADIENT_INITIAL_AGE_TICKS); + queuedGradients.push(gradients.size()-1); +} + + +bool GradientManager::is_updated(const GradientInfo& gi) +{ + for(std::vector >::iterator i=gradients.begin(); i!=gradients.end(); ++i) + { + if((*i)->get_gradient_info() == gi) + { + if(ticks_since_update[i-gradients.begin()]>AI_ECHO_GRADIENT_STALE_TICKS && (*i)->get_gradient_info().needs_updating()) + { + return false; + } + return true; + } + } + //If the gradient hasn't been queued to be updated, consider it updated, + //and it will be calculated on request + return true; +} + + +void GradientManager::update() +{ + timer++; + std::transform(ticks_since_update.begin(), ticks_since_update.end(), ticks_since_update.begin(), increment); + + // (timer%1)==0 is a tautology — preserved verbatim per audit note L8 + // (bugs_surfaced_during_magic_number_audit.md). Looks like a disabled + // throttle; do NOT name as a constant or restore an intended period. + if((timer%1)==0 && !queuedGradients.empty()) + { + int g=queuedGradients.front(); + if(ticks_since_update[g]>AI_ECHO_GRADIENT_QUEUE_MIN_AGE_TICKS) + { + gradients[g]->recalculate(map); + ticks_since_update[g]=0; + } + queuedGradients.pop(); + return; + } +} + + diff --git a/src/ai/echo/GradientBFS.cpp b/src/ai/echo/GradientBFS.cpp new file mode 100644 index 000000000..a69cb472b --- /dev/null +++ b/src/ai/echo/GradientBFS.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2026 glob2 contributors + +// This file holds the small, Map-free pieces of the AIEcho gradient system, +// kept separate so that determinism tests for `Gradient::expand_bfs` can link +// without dragging in the full game (Map, Game, GlobalContainer, ...). + +#include "echo/Echo.h" + +#include + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace boost::logic; + + +GradientInfo::GradientInfo() +{ + needs_updated=indeterminate; +} + + +GradientInfo::~GradientInfo() +{ + +} + + +Gradient::Gradient(const GradientInfo& gi) +{ + gradient_info=gi; + width=0; +} + + +void Gradient::expand_bfs(std::queue& positions) +{ + const int height = static_cast(gradient.size()) / width; + while(!positions.empty()) + { + position p=positions.front(); + positions.pop(); + + int left=p.x-1; + if(left<0) + left+=width; + int right=p.x+1; + if(right>=width) + right-=width; + int up=p.y-1; + if(up<0) + up+=height; + int down=p.y+1; + if(down>=height) + down-=height; + const int center_h=p.x; + const int center_y=p.y; + const Sint16 n=gradient[get_pos(center_h, center_y)]; + + // 8-neighbor BFS step. Push order is fixed for deterministic networking + // (lockstep desyncs if any client sees the queue in a different order); + // do not reorder. + const position neighbors[8] = { + position(left, up), + position(center_h, up), + position(right, up), + position(left, center_y), + position(right, center_y), + position(left, down), + position(center_h, down), + position(right, down), + }; + for (const position& nb : neighbors) + { + const int idx = get_pos(nb.x, nb.y); + if (gradient[idx] == 0) + { + gradient[idx] = n + 1; + positions.push(nb); + } + } + } +} diff --git a/src/ai/echo/Gradients.h b/src/ai/echo/Gradients.h new file mode 100644 index 000000000..a0c8c5f35 --- /dev/null +++ b/src/ai/echo/Gradients.h @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#pragma once + +#include "echo/Position.h" +#include "Map.h" + +#include +#include +#include +#include + +class GradientBFSTest; +class Player; + +namespace AIEcho +{ + class Echo; + + namespace Construction + { + class MinimumDistance; + class MaximumDistance; + class MinimizedDistance; + class MaximizedDistance; + } + + ///The gradients namespace stores anything related to Echo's gradient system. + namespace Gradients + { + class GradientInfo; + class Gradient; + class GradientManager; + + ///Stores classes related to objects that determine the sources and obstacles on a gradient + namespace Entities + { + ///This is an enum of the types of entities, used for saving and loading + enum EntityType + { + EBuilding, + EAnyTeamBuilding, + EAnyBuilding, + ERessource, + EAnyRessource, + EWater, + EPosition, + ESand, + }; + + ///An entity is any observable object on the map. Its entirely generic, not specific to a certain team + class Entity + { + public: + virtual ~Entity(){} + friend class AIEcho::Gradients::GradientInfo; + protected: + virtual bool is_entity(Map* map, int posx, int posy)=0; + ///The comparison operator is used to reference gradients by the entities and sources that was use to compute them + virtual bool operator==(const Entity& rhs) const=0; + + ///This function says whether the entity can change during runtime. For example, water never changes during + ///the coarse of the game, however the layout of buildings can. + virtual bool can_change()=0; + + virtual EntityType get_type()=0; + virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor)=0; + virtual void save(GAGCore::OutputStream *stream)=0; + static Entity* load_entity(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + static void save_entity(Entity* entity, GAGCore::OutputStream *stream); + }; + + ///Matches any building of a particular type, team, and construction state + class Building : public Entity + { + public: + Building(int building_type, int team, bool under_construction); + protected: + Building() : building_type(-1), team(-1), under_construction(false) {} + friend class Entity; + bool is_entity(Map* map, int posx, int posy); + bool operator==(const Entity& rhs) const; + bool can_change(); + EntityType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int building_type; + int team; + bool under_construction; + }; + + ///Matches any building of a particular team and consruction state + class AnyTeamBuilding : public Entity + { + public: + AnyTeamBuilding(int team, bool under_construction); + protected: + AnyTeamBuilding() : team(-1), under_construction(false) {} + friend class Entity; + bool is_entity(Map* map, int posx, int posy); + bool operator==(const Entity& rhs) const; + bool can_change(); + EntityType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int team; + bool under_construction; + }; + + ///Matches any building from any team, as long as it matches the construction state + class AnyBuilding : public Entity + { + public: + explicit AnyBuilding(bool under_construction); + protected: + AnyBuilding() : under_construction(false) {} + friend class Entity; + bool is_entity(Map* map, int posx, int posy); + bool operator==(const Entity& rhs) const; + bool can_change(); + EntityType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + bool under_construction; + }; + + ///Matches a particular ressource type + class Ressource : public Entity + { + public: + explicit Ressource(int ressource_type); + protected: + Ressource() : ressource_type(-1) {} + friend class Entity; + bool is_entity(Map* map, int posx, int posy); + bool operator==(const Entity& rhs) const; + bool can_change(); + EntityType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int ressource_type; + }; + + ///Matches any ressource type + class AnyRessource : public Entity + { + public: + AnyRessource(); + protected: + friend class Entity; + bool is_entity(Map* map, int posx, int posy); + bool operator==(const Entity& rhs) const; + bool can_change(); + EntityType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + }; + + ///Matches water + class Water : public Entity + { + public: + Water(); + protected: + friend class Entity; + bool is_entity(Map* map, int posx, int posy); + bool operator==(const Entity& rhs) const; + bool can_change(); + EntityType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + }; + + ///Matches the provided position + class Position : public Entity + { + public: + Position(int x, int y); + protected: + Position() : x(-1), y(-1) {} + friend class Entity; + bool is_entity(Map* map, int posx, int posy); + bool operator==(const Entity& rhs) const; + bool can_change(); + EntityType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + int x; + int y; + }; + + ///Matches sand + class Sand : public Entity + { + public: + Sand(); + protected: + friend class Entity; + bool is_entity(Map* map, int posx, int posy); + bool operator==(const Entity& rhs) const; + bool can_change(); + EntityType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + }; + }; + + ///The gradient info class is used to hold the information about sources and obstacles taht are used to compute a gradient + class GradientInfo + { + public: + GradientInfo(); + ~GradientInfo(); + ///Adds a provided source to the gradient. Ownership for the source is taken. + void add_source(Entities::Entity* source); + ///Adds a provided obstacle to the gradient. Ownership for the obstacle is taken. + void add_obstacle(Entities::Entity* obstacle); + private: + friend class AIEcho::Gradients::Gradient; + friend class AIEcho::Gradients::GradientManager; + friend class AIEcho::Construction::MinimumDistance; + friend class AIEcho::Construction::MaximumDistance; + friend class AIEcho::Construction::MinimizedDistance; + friend class AIEcho::Construction::MaximizedDistance; + + + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + + ///Returns true if the provided position matches any of the sources that where added + bool match_source(Map* map, int posx, int posy); + ///Returns true if the provided position matches any of the obstacles that where added + bool match_obstacle(Map* map, int posx, int posy); + ///Returns true if this GradientInfo has any entities that can change, causing it to need to be updated. + ///This is an optmization, as many gradients don't need to be update + bool needs_updating() const; + + bool operator==(const GradientInfo& rhs) const; + std::vector > sources; + std::vector > obstacles; + mutable boost::logic::tribool needs_updated; + }; + + ///Heres a few convience functions for creating a Gradient Info + ///@{ + GradientInfo make_gradient_info(Entities::Entity* source); + GradientInfo make_gradient_info_obstacle(Entities::Entity* source, Entities::Entity* obstacle); + GradientInfo make_gradient_info(Entities::Entity* source1, Entities::Entity* source2); + GradientInfo make_gradient_info_obstacle(Entities::Entity* source1, Entities::Entity* source2, Entities::Entity* obstacle); + ///@} + + + + ///A generic, all purpose gradient. The gradient is referenced by its GradientInfo, which it uses continually in its computation. + ///Echo gradients are probably the slowest gradients in the game. However, they have one key difference compared to other gradinents, + ///they can be shared, and they are generic, even more so than Nicowar gradients (which where decently generic, but not entirely). + class Gradient + { + public: + explicit Gradient(const GradientInfo& gi); + ///Gets the distance of the provided position from the nearest source + int get_height(int posx, int posy) const; + ///Returns true if the tile is reachable and within max_dist of the + ///nearest source. Excludes obstacle (-1) and BFS-unreached (-2) tiles, + ///which a naive `get_height < max_dist` would silently include. + bool within_dist(int posx, int posy, int max_dist) const; + private: + friend class AIEcho::Gradients::GradientManager; + friend class ::GradientBFSTest; + + ///Causes the gradient to be updated + void recalculate(Map* map); + ///Toroidal 8-connected BFS expansion from sources already seeded in `gradient`. + ///Push order is fixed for deterministic networking; do not change without + ///verifying lockstep behavior. Drains `positions`. + void expand_bfs(std::queue& positions); + ///Returns the gradient info for comparison + const GradientInfo& get_gradient_info() const { return gradient_info; } + int width; + int get_pos(int x, int y) const { return y*width + x; } + GradientInfo gradient_info; + std::vector gradient; +// Sint16* gradient; + }; + + ///The gradient manager is a very important part of the system, just like the gradient itself is. The gradient manager takes upon the task + ///of managing and updating various gradients in the game. It returns a matching gradient when provided a GradientInfo. + ///This object is shared among all Echo AI's, which means gradients that aren't specific to a particular team (such as most Ressource + ///gradients) don't have to be recalculated for every Echo AI seperately. This saves allot of cpu time when their are multiple Echo AI's. + class GradientManager + { + public: + explicit GradientManager(Map* map); + ///A simple function, returns the Gradient that matches the GradientInfo. Its garunteed to be up to date within the last 150 ticks. + ///If a matching gradient isn't found, a new one is created. 150 ticks may sound like a large amount of leeway, however, most + ///gradients are updated sooner than that. As well, at normal game speed, 150 ticks is only 6 seconds, and you can count it yourself, + ///not much changes in the game in six seconds. + Gradient& get_gradient(const GradientInfo& gi); + ///Queues up a gradient with GradientInfo to be updated. This gradient will be updated once and then never again. + void queue_gradient(const GradientInfo& gi); + ///Returns true if the gradient GradientInfo has been updated recently. + bool is_updated(const GradientInfo& gi); + private: + friend class AIEcho::Echo; + void update(); + static int increment(const int x) { return x+1; } + std::vector > gradients; + std::queue queuedGradients; + std::vector ticks_since_update; + Map* map; + unsigned int cur_update; + int timer; + }; + }; +} diff --git a/src/ai/echo/Management.cpp b/src/ai/echo/Management.cpp new file mode 100644 index 000000000..aef68ec49 --- /dev/null +++ b/src/ai/echo/Management.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" + +using namespace AIEcho; +using namespace AIEcho::Management; + + +ManagementOrder* ManagementOrder::load_order(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("ManagementOrder"); + ManagementOrderType mot=static_cast(stream->readUint32("type")); + ManagementOrder* mo=NULL; + switch(mot) + { + case MAssignWorkers: + mo=new AssignWorkers; + mo->load(stream, player, versionMinor); + break; + case MChangeSwarm: + mo=new ChangeSwarm; + mo->load(stream, player, versionMinor); + break; + case MDestroyBuilding: + mo=new DestroyBuilding; + mo->load(stream, player, versionMinor); + break; + case MAddRessourceTracker: + mo=new AddRessourceTracker; + mo->load(stream, player, versionMinor); + break; + case MPauseRessourceTracker: + mo=new PauseRessourceTracker; + mo->load(stream, player, versionMinor); + break; + case MUnPauseRessourceTracker: + mo=new UnPauseRessourceTracker; + mo->load(stream, player, versionMinor); + break; + case MChangeFlagSize: + mo=new ChangeFlagSize; + mo->load(stream, player, versionMinor); + break; + case MChangeFlagMinimumLevel: + mo=new ChangeFlagMinimumLevel; + mo->load(stream, player, versionMinor); + break; + case MAddArea: + mo=new AddArea; + mo->load(stream, player, versionMinor); + break; + case MRemoveArea: + mo=new RemoveArea; + mo->load(stream, player, versionMinor); + break; + case MChangeAlliances: + mo=new ChangeAlliances; + mo->load(stream, player, versionMinor); + break; + case MUpgradeRepair: + mo=new UpgradeRepair; + mo->load(stream, player, versionMinor); + break; + case MSendMessage: + mo=new SendMessage; + mo->load(stream, player, versionMinor); + break; + case MChangeFlagPosition: + mo=new ChangeFlagPosition; + mo->load(stream, player, versionMinor); + break; + case MAdjustPriority: + mo=new AdjustPriority; + mo->load(stream, player, versionMinor); + break; + } + stream->readLeaveSection(); + return mo; +} + + + +void ManagementOrder::save_order(ManagementOrder* mo, GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("ManagementOrder"); + stream->writeUint32(mo->get_type(), "type"); + mo->save(stream); + stream->writeLeaveSection(); +} + + + +boost::logic::tribool ManagementOrder::wait_for_building(Echo& echo, int building_id) +{ + if(echo.get_building_register().is_building_found(building_id)) + return true; + if(echo.get_building_register().is_building_pending(building_id)) + return false; + return boost::logic::indeterminate; +} + diff --git a/src/ai/echo/Management.h b/src/ai/echo/Management.h new file mode 100644 index 000000000..f24abf88c --- /dev/null +++ b/src/ai/echo/Management.h @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#pragma once + +#include "echo/Position.h" +#include "Map.h" +#include "Player.h" + +#include +#include +#include +#include + +namespace AIEcho +{ + class Echo; + + namespace Conditions + { + class Condition; + } + + ///This namespace stores anything related to managing you're buildings, flags and areas. + namespace Management + { + enum ManagementOrderType + { + MAssignWorkers, + MChangeSwarm, + MDestroyBuilding, + MAddRessourceTracker, + MPauseRessourceTracker, + MUnPauseRessourceTracker, + MChangeFlagSize, + MChangeFlagMinimumLevel, + MAddArea, + MRemoveArea, + MChangeAlliances, + MUpgradeRepair, + MSendMessage, + MChangeFlagPosition, + MAdjustPriority, + }; + + + ///A generic management order can have conditions attached to it. This makes management orders + ///both convinient and usefull. They will wait for the conditions to be satisfied before + ///performing their change. + class ManagementOrder + { + public: + virtual ~ManagementOrder() {} + ///Adds a new condition to the management order. This assumes ownership of the condition. + void add_condition(Conditions::Condition* condition); + protected: + virtual void modify(Echo& echo)=0; + ///This acts somewhat like a condition tester of its own. Like passes_conditions, this one + ///checks for the conditions for the management order to execute at all. indeterminate means + ///that its impossible to execute, false means wait some more and true means ready to execute + ///For example, the ChangeFlagSize order requires that the building be in existance, and + ///that its a flag. + virtual boost::logic::tribool wait(Echo& echo)=0; + + virtual bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + virtual void save(GAGCore::OutputStream *stream); + virtual ManagementOrderType get_type()=0; + + ///Shared wait() implementation for orders that target a single building: + ///true once the building is constructed, false while it's pending, + ///indeterminate once it has gone away (so the order is dropped). + static boost::logic::tribool wait_for_building(Echo& echo, int building_id); + + private: + friend class AIEcho::Echo; + boost::logic::tribool passes_conditions(Echo& echo); + static ManagementOrder* load_order(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + static void save_order(ManagementOrder* mo, GAGCore::OutputStream *stream); + + std::vector > conditions; + }; + + ///Assigns a particular number of workers to a building + class AssignWorkers : public ManagementOrder + { + public: + AssignWorkers() : number_of_workers(0), building_id(0) {} + explicit AssignWorkers(int number_of_workers, int building_id); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int number_of_workers; + int building_id; + }; + + ///Changes the ratios on a swarm + class ChangeSwarm : public ManagementOrder + { + public: + ChangeSwarm() : worker_ratio(0), explorer_ratio(0), warrior_ratio(0), building_id(0) {} + ChangeSwarm(int worker_ratio, int explorer_ratio, int warrior_ratio, int building_id); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int worker_ratio; + int explorer_ratio; + int warrior_ratio; + int building_id; + }; + + ///Orders the destruction of a building + class DestroyBuilding : public ManagementOrder + { + public: + DestroyBuilding() : building_id(0) {} + DestroyBuilding(int building_id); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + int building_id; + }; + + + ///A ressource tracker is generally used for management, like most other things. A ressource trackers job is to keep + ///track of the number of ressources in a particular building, and returning averages over a small period of time. + ///Its better to use a ressource tracker than getting the ressource amounts directly, because a ressource tracker + ///returns trends, and small anomalies like an Inn running out of food for only a second don't impact its result greatly. + class RessourceTracker + { + public: + RessourceTracker(Echo& echo, GAGCore::InputStream* stream, Player* player, Sint32 versionMinor) : echo(echo) + { load(stream, player, versionMinor); } + RessourceTracker(Echo& echo, int building_id, int length, int ressource); + ///Returns the total ressources the building possessed within the time frame + int get_total_level(); + ///Returns the number of ticks the ressource tracker has been tracking. + int get_age(); + private: + friend class AIEcho::Echo; + void tick(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + std::vector record; + unsigned int position; + int timer; + int length; + Echo& echo; + int building_id; + int ressource; + }; + + ///This adds a ressource tracker to a building + class AddRessourceTracker : public ManagementOrder + { + public: + AddRessourceTracker(int length, int ressource, int building_id); + AddRessourceTracker() : length(0), building_id(0), ressource(0) {} + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + int length; + int building_id; + int ressource; + }; + + ///This pauses a ressource tracker. This is mainly done when a building is about to be upgraded. + class PauseRessourceTracker : public ManagementOrder + { + public: + PauseRessourceTracker() : building_id(0) {} + PauseRessourceTracker(int building_id); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + int building_id; + }; + + ///This unpauses a ressource tracker. This should be done when a building is done being upgraded. + class UnPauseRessourceTracker : public ManagementOrder + { + public: + UnPauseRessourceTracker() : building_id(0) {} + UnPauseRessourceTracker(int building_id); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + int building_id; + }; + + ///This changes the radius of a flag. + class ChangeFlagSize : public ManagementOrder + { + public: + ChangeFlagSize() : size(0), building_id(0) {} + explicit ChangeFlagSize(int size, int building_id); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int size; + int building_id; + }; + + ///This changes the minimum_level required to attend a flag. Used mainly for War Flags, but this + ///can be used to control whether ground attack explorers come to a particular flag. To have only + ///ground attack explorers come, use level 4. Levels 2 and 3 can only be set by the map editor. + class ChangeFlagMinimumLevel : public ManagementOrder + { + public: + ChangeFlagMinimumLevel() : minimum_level(0), building_id(0) {} + explicit ChangeFlagMinimumLevel(int minimum_level, int building_id); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int minimum_level; + int building_id; + }; + + ///This changes a flags position + class ChangeFlagPosition : public ManagementOrder + { + public: + ChangeFlagPosition() : x(0), y(0), building_id(0) {} + explicit ChangeFlagPosition(int x, int y, int building_id); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int x; + int y; + int building_id; + }; + + ///This order adjusts the priority on a building + class AdjustPriority : public ManagementOrder + { + public: + enum BuildingPriority + { + Low, + Medium, + High, + }; + + AdjustPriority() : building_id(0) {} + AdjustPriority(int building_id, BuildingPriority priority); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int building_id; + BuildingPriority priority; + }; + + ///This management order adds a particular type of "area" to the ground. + ///The three types of areas are in the AreaType enum, and are passed to + ///the constructor. To have this change multiple areas, its nesseccary + ///to call the add_location function multiple times. + class AddArea : public ManagementOrder + { + public: + AddArea() {} + explicit AddArea(AreaType areatype); + void add_location(int x, int y); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + AreaType areatype; + std::vector locations; + }; + + ///This management order removes an area from the ground. Its exactly + ///the same as AddArea, with the exception that it removes areas, + ///instead of adding them. + class RemoveArea : public ManagementOrder + { + public: + RemoveArea() {} + explicit RemoveArea(AreaType areatype); + void add_location(int x, int y); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + AreaType areatype; + std::vector locations; + }; + + ///This class allows you to adjust alliances with other teams. + class ChangeAlliances : public ManagementOrder + { + public: + ChangeAlliances() {} + ///You pass in a team number, that can be retrieved from enemy_team_iterator or a similar method. Then you pass in modifiers + ///on each of the possible alliances. If you pass in true, that alliance mode is set. If you pass in false, that alliance + ///mode is unset. If you pass in undeterminate, that alliance mode is not changed, keeping whatever value it had before. + ChangeAlliances(int team, boost::logic::tribool is_allied, boost::logic::tribool is_enemy, boost::logic::tribool view_market, boost::logic::tribool view_inn, boost::logic::tribool view_other); + protected: + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int team; + boost::logic::tribool is_allied; + boost::logic::tribool is_enemy; + boost::logic::tribool view_market; + boost::logic::tribool view_inn; + boost::logic::tribool view_other; + }; + + ///This order calls for a particular building to be upgraded or repaired with the provided number of workers. + class UpgradeRepair : public ManagementOrder + { + public: + UpgradeRepair(int id); + protected: + friend class ManagementOrder; + UpgradeRepair() {} + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + private: + int id; + }; + + #ifdef SendMessage + #undef SendMessage + #endif + + ///This sends a message to the AI's handle_message function. + class SendMessage : public ManagementOrder + { + public: + SendMessage(const std::string& message); + protected: + friend class ManagementOrder; + SendMessage() {} + void modify(Echo& echo); + boost::logic::tribool wait(Echo& echo); + ManagementOrderType get_type(); + bool load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); + std::string message; + }; + }; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::AssignWorkers::get_type() +{ + return MAssignWorkers; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::ChangeSwarm::get_type() +{ + return MChangeSwarm; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::DestroyBuilding::get_type() +{ + return MDestroyBuilding; +} + + +inline int AIEcho::Management::RessourceTracker::get_age() +{ + return timer; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::AddRessourceTracker::get_type() +{ + return MAddRessourceTracker; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::PauseRessourceTracker::get_type() +{ + return MPauseRessourceTracker; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::UnPauseRessourceTracker::get_type() +{ + return MUnPauseRessourceTracker; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::ChangeFlagSize::get_type() +{ + return MChangeFlagSize; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::ChangeFlagMinimumLevel::get_type() +{ + return MChangeFlagMinimumLevel; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::AddArea::get_type() +{ + return MAddArea; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::RemoveArea::get_type() +{ + return MRemoveArea; +} + + + +inline AIEcho::Management::ManagementOrderType AIEcho::Management::ChangeAlliances::get_type() +{ + return MChangeAlliances; +} diff --git a/src/ai/echo/ManagementFlag.cpp b/src/ai/echo/ManagementFlag.cpp new file mode 100644 index 000000000..81e04c9ec --- /dev/null +++ b/src/ai/echo/ManagementFlag.cpp @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Order.h" +#include "Brush.h" + +using namespace AIEcho; +using namespace AIEcho::Management; +using namespace boost::logic; +using std::shared_ptr; + + +namespace +{ + int priority_to_int(AdjustPriority::BuildingPriority priority) + { + switch(priority) + { + case AdjustPriority::Low: return AI_ECHO_PRIORITY_LOW; + case AdjustPriority::Medium: return AI_ECHO_PRIORITY_MEDIUM; + case AdjustPriority::High: return AI_ECHO_PRIORITY_HIGH; + } + return AI_ECHO_PRIORITY_MEDIUM; + } + + AdjustPriority::BuildingPriority int_to_priority(int p) + { + if(p == AI_ECHO_PRIORITY_LOW) return AdjustPriority::Low; + if(p == AI_ECHO_PRIORITY_HIGH) return AdjustPriority::High; + return AdjustPriority::Medium; + } + + void apply_area_modification(Echo& echo, AreaType areatype, + const std::vector& locations, + Uint8 mode) + { + BrushAccumulator acc; + for(std::vector::const_iterator i=locations.begin(); i!=locations.end(); ++i) + { + acc.applyBrush(BrushApplication(echo.player->map->normalizeX(i->x), echo.player->map->normalizeY(i->y), 0), echo.player->map); + } + if(acc.getApplicationCount()==0) + return; + Uint8 team = echo.player->team->teamNumber; + const Map* map = echo.player->map; + switch(areatype) + { + case ClearingArea: + echo.push_order(shared_ptr(new OrderAlterateClearArea(team, mode, &acc, map))); + break; + case ForbiddenArea: + echo.push_order(shared_ptr(new OrderAlterateForbidden(team, mode, &acc, map))); + break; + case GuardArea: + echo.push_order(shared_ptr(new OrderAlterateGuardArea(team, mode, &acc, map))); + break; + } + } +} + + +ChangeFlagSize::ChangeFlagSize(int size, int building_id) : size(size), building_id(building_id) +{ + +} + + + +void ChangeFlagSize::modify(Echo& echo) +{ + echo.push_order(shared_ptr(new OrderModifyFlag(echo.get_building_register().get_building(building_id)->gid, size))); +} + + + +boost::logic::tribool ChangeFlagSize::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +bool ChangeFlagSize::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("ChangeFlagSize"); + ManagementOrder::load(stream, player, versionMinor); + size=stream->readUint32("size"); + building_id=stream->readUint32("building_id"); + stream->readLeaveSection(); + return true; +} + + + +void ChangeFlagSize::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("ChangeFlagSize"); + ManagementOrder::save(stream); + stream->writeUint32(size, "size"); + stream->writeUint32(building_id, "building_id"); + stream->writeLeaveSection(); +} + + + +ChangeFlagMinimumLevel::ChangeFlagMinimumLevel(int minimum_level, int building_id) : minimum_level(minimum_level), building_id(building_id) +{ + +} + + + +void ChangeFlagMinimumLevel::modify(Echo& echo) +{ + echo.push_order(shared_ptr(new OrderModifyMinLevelToFlag(echo.get_building_register().get_building(building_id)->gid, minimum_level-AI_ECHO_LEVEL_OFFSET_USER_TO_ENGINE))); +} + + + +boost::logic::tribool ChangeFlagMinimumLevel::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +bool ChangeFlagMinimumLevel::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("ChangeFlagMinimumLevel"); + ManagementOrder::load(stream, player, versionMinor); + minimum_level=stream->readUint32("minimum_level"); + building_id=stream->readUint32("building_id"); + stream->readLeaveSection(); + return true; +} + + + +void ChangeFlagMinimumLevel::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("ChangeFlagMinimumLevel"); + ManagementOrder::save(stream); + stream->writeUint32(minimum_level, "minimum_level"); + stream->writeUint32(building_id, "building_id"); + stream->writeLeaveSection(); +} + + + +ChangeFlagPosition::ChangeFlagPosition(int x, int y, int building_id) + : x(x), y(y), building_id(building_id) +{ + +} + + +void ChangeFlagPosition::modify(Echo& echo) +{ + echo.push_order(shared_ptr(new OrderMoveFlag(echo.get_building_register().get_building(building_id)->gid, x, y, true))); +} + + + +boost::logic::tribool ChangeFlagPosition::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +ManagementOrderType ChangeFlagPosition::get_type() +{ + return MChangeFlagPosition; +} + + + +bool ChangeFlagPosition::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("ChangeFlagPosition"); + ManagementOrder::load(stream, player, versionMinor); + building_id=stream->readUint32("building_id"); + x=stream->readUint32("x"); + y=stream->readUint32("y"); + stream->readLeaveSection(); + return true; +} + + + +void ChangeFlagPosition::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("ChangeFlagPosition"); + ManagementOrder::save(stream); + stream->writeUint32(building_id, "building_id"); + stream->writeUint32(x, "x"); + stream->writeUint32(y, "y"); + stream->writeLeaveSection(); +} + + + +AdjustPriority::AdjustPriority(int building_id, AdjustPriority::BuildingPriority priority) + : building_id(building_id), priority(priority) +{ + +} + + +void AdjustPriority::modify(Echo& echo) +{ + echo.push_order(shared_ptr(new OrderChangePriority(echo.get_building_register().get_building(building_id)->gid, priority_to_int(priority)))); +} + + + +boost::logic::tribool AdjustPriority::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +ManagementOrderType AdjustPriority::get_type() +{ + return MAdjustPriority; +} + + + +bool AdjustPriority::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("AdjustPriority"); + ManagementOrder::load(stream, player, versionMinor); + building_id=stream->readUint32("building_id"); + priority = int_to_priority(stream->readSint32("priority")); + stream->readLeaveSection(); + return true; +} + + + +void AdjustPriority::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("AdjustPriority"); + ManagementOrder::save(stream); + stream->writeUint32(building_id, "building_id"); + stream->writeSint32(priority_to_int(priority), "priority"); + stream->writeLeaveSection(); +} + + + + +AddArea::AddArea(AreaType areatype) : areatype(areatype) +{ + +} + + + +void AddArea::add_location(int x, int y) +{ + locations.push_back(position(x, y)); +} + + + +void AddArea::modify(Echo& echo) +{ + apply_area_modification(echo, areatype, locations, BrushTool::MODE_ADD); +} + + + +boost::logic::tribool AddArea::wait(Echo& echo) +{ + return true; +} + + + +bool AddArea::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("AddArea"); + ManagementOrder::load(stream, player, versionMinor); + areatype=static_cast(stream->readUint32("area_type")); + stream->readEnterSection("locations"); + Uint32 size=stream->readUint32("size"); + locations.resize(size); + for(Uint32 location_index=0; location_indexreadEnterSection(location_index); + locations[location_index]=position(stream->readUint32("posx"), stream->readUint32("posy")); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + stream->readLeaveSection(); + return true; +} + + + +void AddArea::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("AddArea"); + ManagementOrder::save(stream); + stream->writeUint32(areatype, "area_type"); + stream->writeEnterSection("locations"); + stream->writeUint32(locations.size(), "size"); + for(Uint32 location_index=0; location_indexwriteEnterSection(location_index); + stream->writeUint32(locations[location_index].x, "posx"); + stream->writeUint32(locations[location_index].y, "posy"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + stream->writeLeaveSection(); +} + + + +RemoveArea::RemoveArea(AreaType areatype) : areatype(areatype) +{ + +} + + + +void RemoveArea::add_location(int x, int y) +{ + locations.push_back(position(x, y)); +} + + + +void RemoveArea::modify(Echo& echo) +{ + apply_area_modification(echo, areatype, locations, BrushTool::MODE_DEL); +} + + + +boost::logic::tribool RemoveArea::wait(Echo& echo) +{ + return true; +} + + + +bool RemoveArea::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("RemoveArea"); + ManagementOrder::load(stream, player, versionMinor); + areatype=static_cast(stream->readUint32("area_type")); + stream->readEnterSection("locations"); + Uint32 size=stream->readUint32("size"); + locations.resize(size); + for(Uint32 location_index=0; location_indexreadEnterSection(location_index); + locations[location_index]=position(stream->readUint32("posx"), stream->readUint32("posy")); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + stream->readLeaveSection(); + return true; +} + + + +void RemoveArea::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("RemoveArea"); + ManagementOrder::save(stream); + stream->writeUint32(areatype, "area_type"); + stream->writeEnterSection("locations"); + stream->writeUint32(locations.size(), "size"); + for(Uint32 location_index=0; location_indexwriteEnterSection(location_index); + stream->writeUint32(locations[location_index].x, "posx"); + stream->writeUint32(locations[location_index].y, "posy"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + stream->writeLeaveSection(); +} + diff --git a/src/ai/echo/ManagementMisc.cpp b/src/ai/echo/ManagementMisc.cpp new file mode 100644 index 000000000..df375fb7d --- /dev/null +++ b/src/ai/echo/ManagementMisc.cpp @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Game.h" +#include "Order.h" + +using namespace AIEcho; +using namespace AIEcho::Management; +using namespace boost::logic; +using std::shared_ptr; + + +ChangeAlliances::ChangeAlliances(int team, boost::logic::tribool is_allied, boost::logic::tribool is_enemy, boost::logic::tribool view_market, boost::logic::tribool view_inn, boost::logic::tribool view_other) : team(team), is_allied(is_allied), is_enemy(is_enemy), view_market(view_market), view_inn(view_inn), view_other(view_other) +{ + +} + + + +void ChangeAlliances::modify(Echo& echo) +{ + Uint32 alliedmask=echo.allies; + Uint32 enemymask=echo.enemies; + Uint32 market_mask=echo.market_view; + Uint32 inn_mask=echo.inn_view; + Uint32 other_mask=echo.other_view; + Team* t=echo.player->game->teams[team]; + // t->me is always a single bit (Team::teamNumberToMask = 1 << teamNumber), + // so &= ~t->me clears it cleanly; the legacy `if(mask&t->me) mask^=t->me;` + // pattern was equivalent but obscured the intent. + if(is_allied) + alliedmask|=t->me; + else if(!is_allied) + alliedmask&=~t->me; + + if(is_enemy) + enemymask|=t->me; + else if(!is_enemy) + enemymask&=~t->me; + + if(view_market) + market_mask|=t->me; + else if(!view_market) + market_mask&=~t->me; + + if(view_inn) + inn_mask|=t->me; + else if(!view_inn) + inn_mask&=~t->me; + + if(view_other) + other_mask|=t->me; + else if(!view_other) + other_mask&=~t->me; + + echo.allies=alliedmask; + echo.enemies=enemymask; + echo.market_view=market_mask; + echo.inn_view=inn_mask; + echo.other_view=other_mask; + + echo.push_order(shared_ptr(new SetAllianceOrder(echo.player->team->teamNumber, alliedmask, enemymask, market_mask, inn_mask, other_mask))); +} + + + +boost::logic::tribool ChangeAlliances::wait(Echo& echo) +{ + return true; +} + + + +bool ChangeAlliances::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("ChangeAlliances"); + ManagementOrder::load(stream, player, versionMinor); + team=stream->readUint32("team"); + + Uint8 tmp=stream->readUint8("is_allied"); + if(tmp==AI_ECHO_TRIBOOL_TRUE) + is_allied=true; + else if(tmp==AI_ECHO_TRIBOOL_FALSE) + is_allied=false; + else if(tmp==AI_ECHO_TRIBOOL_INDETERMINATE) + is_allied=indeterminate; + + tmp=stream->readUint8("is_enemy"); + if(tmp==AI_ECHO_TRIBOOL_TRUE) + is_enemy=true; + else if(tmp==AI_ECHO_TRIBOOL_FALSE) + is_enemy=false; + else if(tmp==AI_ECHO_TRIBOOL_INDETERMINATE) + is_enemy=indeterminate; + + tmp=stream->readUint8("view_market"); + if(tmp==AI_ECHO_TRIBOOL_TRUE) + view_market=true; + else if(tmp==AI_ECHO_TRIBOOL_FALSE) + view_market=false; + else if(tmp==AI_ECHO_TRIBOOL_INDETERMINATE) + view_market=indeterminate; + + tmp=stream->readUint8("view_inn"); + if(tmp==AI_ECHO_TRIBOOL_TRUE) + view_inn=true; + else if(tmp==AI_ECHO_TRIBOOL_FALSE) + view_inn=false; + else if(tmp==AI_ECHO_TRIBOOL_INDETERMINATE) + view_inn=indeterminate; + + tmp=stream->readUint8("view_other"); + if(tmp==AI_ECHO_TRIBOOL_TRUE) + view_other=true; + else if(tmp==AI_ECHO_TRIBOOL_FALSE) + view_other=false; + else if(tmp==AI_ECHO_TRIBOOL_INDETERMINATE) + view_other=indeterminate; + + stream->readLeaveSection(); + return true; +} + + + +void ChangeAlliances::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("ChangeAlliances"); + ManagementOrder::save(stream); + stream->writeUint32(team, "team"); + + if(is_allied) + stream->writeUint8(AI_ECHO_TRIBOOL_TRUE, "is_allied"); + else if(!is_allied) + stream->writeUint8(AI_ECHO_TRIBOOL_FALSE, "is_allied"); + else + stream->writeUint8(AI_ECHO_TRIBOOL_INDETERMINATE, "is_allied"); + + if(is_enemy) + stream->writeUint8(AI_ECHO_TRIBOOL_TRUE, "is_enemy"); + else if(!is_enemy) + stream->writeUint8(AI_ECHO_TRIBOOL_FALSE, "is_enemy"); + else + stream->writeUint8(AI_ECHO_TRIBOOL_INDETERMINATE, "is_enemy"); + + if(view_market) + stream->writeUint8(AI_ECHO_TRIBOOL_TRUE, "view_market"); + else if(!view_market) + stream->writeUint8(AI_ECHO_TRIBOOL_FALSE, "view_market"); + else + stream->writeUint8(AI_ECHO_TRIBOOL_INDETERMINATE, "view_market"); + + if(view_inn) + stream->writeUint8(AI_ECHO_TRIBOOL_TRUE, "view_inn"); + else if(!view_inn) + stream->writeUint8(AI_ECHO_TRIBOOL_FALSE, "view_inn"); + else + stream->writeUint8(AI_ECHO_TRIBOOL_INDETERMINATE, "view_inn"); + + if(view_other) + stream->writeUint8(AI_ECHO_TRIBOOL_TRUE, "view_other"); + else if(!view_other) + stream->writeUint8(AI_ECHO_TRIBOOL_FALSE, "view_other"); + else + stream->writeUint8(AI_ECHO_TRIBOOL_INDETERMINATE, "view_other"); + + stream->writeLeaveSection(); +} + +UpgradeRepair::UpgradeRepair(int id) : id(id) +{ + +} + + + +void UpgradeRepair::modify(Echo& echo) +{ + echo.push_order(shared_ptr(new OrderConstruction(echo.get_building_register().get_building(id)->gid,1,1))); + echo.get_building_register().set_upgrading(id); +} + + + +boost::logic::tribool UpgradeRepair::wait(Echo& echo) +{ + return wait_for_building(echo, id); +} + + + +ManagementOrderType UpgradeRepair::get_type() +{ + return MUpgradeRepair; +} + + + +bool UpgradeRepair::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("UpgradeRepair"); + ManagementOrder::load(stream, player, versionMinor); + id=stream->readUint32("id"); + stream->readLeaveSection(); + return true; +} + + + +void UpgradeRepair::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("UpgradeRepair"); + ManagementOrder::save(stream); + stream->writeUint32(id, "id"); + stream->writeLeaveSection(); +} + + +SendMessage::SendMessage(const std::string& message) : message(message) +{ + +} + + + +void SendMessage::modify(Echo& echo) +{ + echo.echoai->handle_message(echo, message); +} + + + +boost::logic::tribool SendMessage::wait(Echo& echo) +{ + return true; +} + + + +ManagementOrderType SendMessage::get_type() +{ + return MSendMessage; +} + + + +bool SendMessage::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("SendMessage"); + ManagementOrder::load(stream, player, versionMinor); + message=stream->readText("message"); + stream->readLeaveSection(); + return true; +} + + + +void SendMessage::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("SendMessage"); + ManagementOrder::save(stream); + stream->writeText(message, "message"); + stream->writeLeaveSection(); +} + diff --git a/src/ai/echo/ManagementOrderBase.cpp b/src/ai/echo/ManagementOrderBase.cpp new file mode 100644 index 000000000..4877b0a07 --- /dev/null +++ b/src/ai/echo/ManagementOrderBase.cpp @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Order.h" + +using namespace AIEcho; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace boost::logic; +using std::shared_ptr; + + +bool ManagementOrder::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("ManagementOrder"); + stream->readEnterSection("conditions"); + Uint32 size = stream->readUint32("size"); + conditions.resize(size); + for(unsigned x=0; xreadEnterSection(x); + conditions[x] = std::shared_ptr(Condition::load_condition(stream, player, versionMinor)); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + stream->readLeaveSection(); + return true; +} + + + +void ManagementOrder::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("ManagementOrder"); + stream->writeEnterSection("conditions"); + stream->writeUint32(conditions.size(), "size"); + for(unsigned x=0; xwriteEnterSection(x); + Condition::save_condition(conditions[x].get(), stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + stream->writeLeaveSection(); +} + + + +void ManagementOrder::add_condition(Condition* condition) +{ + conditions.push_back(std::shared_ptr(condition)); +} + + + +boost::logic::tribool ManagementOrder::passes_conditions(Echo& echo) +{ + for(unsigned int i=0; ipasses(echo); + if(passes) + continue; + else if(!passes) + return false; + else + return indeterminate; + + } + + boost::logic::tribool passes=wait(echo); + if(passes) + return true; + if(!passes) + return false; + return indeterminate; +} + + + +AssignWorkers::AssignWorkers(int number_of_workers, int building_id) : number_of_workers(number_of_workers), building_id(building_id) +{ + +} + + +void AssignWorkers::modify(Echo& echo) +{ + echo.push_order(shared_ptr(new OrderModifyBuilding(echo.get_building_register().get_building(building_id)->gid, number_of_workers))); +} + + + +boost::logic::tribool AssignWorkers::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +bool AssignWorkers::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("AssignWorkers"); + ManagementOrder::load(stream, player, versionMinor); + number_of_workers=stream->readUint32("number_of_workers"); + building_id=stream->readUint32("building_id"); + stream->readLeaveSection(); + return true; +} + + + +void AssignWorkers::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("AssignWorkers"); + ManagementOrder::save(stream); + stream->writeUint32(number_of_workers, "number_of_workers"); + stream->writeUint32(building_id, "building_id"); + stream->writeLeaveSection(); +} + + + +ChangeSwarm::ChangeSwarm(int worker_ratio, int explorer_ratio, int warrior_ratio, int building_id) : worker_ratio(worker_ratio), explorer_ratio(explorer_ratio), warrior_ratio(warrior_ratio), building_id(building_id) +{ + +} + + +void ChangeSwarm::modify(Echo& echo) +{ + Sint32 ratio[NB_UNIT_TYPE]; + ratio[0]=worker_ratio; + ratio[1]=explorer_ratio; + ratio[2]=warrior_ratio; + echo.push_order(shared_ptr(new OrderModifySwarm(echo.get_building_register().get_building(building_id)->gid, ratio))); +} + + + +boost::logic::tribool ChangeSwarm::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +bool ChangeSwarm::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("ChangeSwarm"); + ManagementOrder::load(stream, player, versionMinor); + worker_ratio=stream->readUint32("worker_ratio"); + explorer_ratio=stream->readUint32("explorer_ratio"); + warrior_ratio=stream->readUint32("warrior_ratio"); + building_id=stream->readUint32("building_id"); + stream->readLeaveSection(); + return true; + +} + + + +void ChangeSwarm::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("ChangeSwarm"); + ManagementOrder::save(stream); + stream->writeUint32(worker_ratio, "worker_ratio"); + stream->writeUint32(explorer_ratio, "explorer_ratio"); + stream->writeUint32(warrior_ratio, "warrior_ratio"); + stream->writeUint32(building_id, "building_id"); + stream->writeLeaveSection(); +} + + + +DestroyBuilding::DestroyBuilding(int building_id) : building_id(building_id) +{ + +} + + + +void DestroyBuilding::modify(Echo& echo) +{ + echo.push_order(shared_ptr(new OrderDelete(echo.get_building_register().get_building(building_id)->gid))); +} + + + +boost::logic::tribool DestroyBuilding::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +bool DestroyBuilding::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("DestroyBuilding"); + ManagementOrder::load(stream, player, versionMinor); + building_id=stream->readUint32("building_id"); + stream->readLeaveSection(); + return true; +} + + + +void DestroyBuilding::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("DestroyBuilding"); + ManagementOrder::save(stream); + stream->writeUint32(building_id, "building_id"); + stream->writeLeaveSection(); +} + + diff --git a/src/ai/echo/ManagementTracker.cpp b/src/ai/echo/ManagementTracker.cpp new file mode 100644 index 000000000..a55bbd425 --- /dev/null +++ b/src/ai/echo/ManagementTracker.cpp @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Building.h" + +using namespace AIEcho; +using namespace AIEcho::Management; +using namespace boost::logic; + + +RessourceTracker::RessourceTracker(Echo& echo, int building_id, int length, int ressource) : record(length, 0), position(0), timer(0), length(length), echo(echo), building_id(building_id), ressource(ressource) +{ + +} + + + +void RessourceTracker::tick() +{ + timer++; + if((timer%AI_ECHO_TRACKER_SAMPLE_INTERVAL_TICKS)==0) + { + Building* b = echo.get_building_register().get_building(building_id); + record[position]=b->ressources[ressource]; + position++; + if(position>=record.size()) + position=0; + } +} + + +int RessourceTracker::get_total_level() +{ + int sum=0; + for(unsigned int n=0; nreadEnterSection("RessourceTracker"); + stream->readEnterSection("record"); + Uint32 recordsize=stream->readUint32("size"); + record.resize(recordsize); + for(unsigned int record_index=0; record_indexreadEnterSection(record_index); + record[record_index]=stream->readUint32("quantity_of_ressources"); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + position=stream->readUint32("position"); + timer=stream->readUint32("timer"); + building_id=stream->readUint32("building_id"); + length=stream->readUint32("length"); + ressource=stream->readUint32("ressource"); + stream->readLeaveSection(); + return true; +} + + + +void RessourceTracker::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("RessourceTracker"); + stream->writeEnterSection("record"); + stream->writeUint32(record.size(), "size"); + for(unsigned int record_index=0; record_indexwriteEnterSection(record_index); + stream->writeUint32(record[record_index], "quantity_of_ressources"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + stream->writeUint32(position, "position"); + stream->writeUint32(timer, "timer"); + stream->writeUint32(building_id, "building_id"); + stream->writeUint32(length, "length"); + stream->writeUint32(ressource, "ressource"); + stream->writeLeaveSection(); +} + + + +AddRessourceTracker::AddRessourceTracker(int length, int ressource, int building_id) : length(length), building_id(building_id), ressource(ressource) +{ + +} + + + +void AddRessourceTracker::modify(Echo& echo) +{ + echo.add_ressource_tracker(new RessourceTracker(echo, building_id, length, ressource), building_id); +} + + + +boost::logic::tribool AddRessourceTracker::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +bool AddRessourceTracker::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("AddRessourceTracker"); + ManagementOrder::load(stream, player, versionMinor); + length=stream->readUint32("length"); + building_id=stream->readUint32("building_id"); + ressource=stream->readUint32("ressource"); + stream->readLeaveSection(); + return true; +} + + + +void AddRessourceTracker::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("AddRessourceTracker"); + ManagementOrder::save(stream); + stream->writeUint32(length, "length"); + stream->writeUint32(building_id, "building_id"); + stream->writeUint32(ressource, "ressource"); + stream->writeLeaveSection(); +} + + + +PauseRessourceTracker::PauseRessourceTracker(int building_id) : building_id(building_id) +{ + +} + + + +void PauseRessourceTracker::modify(Echo& echo) +{ + echo.pause_ressource_tracker(building_id); +} + + + +boost::logic::tribool PauseRessourceTracker::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +bool PauseRessourceTracker::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("PauseRessourceTracker"); + ManagementOrder::load(stream, player, versionMinor); + building_id=stream->readUint32("building_id"); + stream->readLeaveSection(); + return true; +} + + + +void PauseRessourceTracker::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("PauseRessourceTracker"); + ManagementOrder::save(stream); + stream->writeUint32(building_id, "building_id"); + stream->writeLeaveSection(); +} + + + +UnPauseRessourceTracker::UnPauseRessourceTracker(int building_id) : building_id(building_id) +{ + +} + + + +void UnPauseRessourceTracker::modify(Echo& echo) +{ + echo.unpause_ressource_tracker(building_id); +} + + + +boost::logic::tribool UnPauseRessourceTracker::wait(Echo& echo) +{ + return wait_for_building(echo, building_id); +} + + + +bool UnPauseRessourceTracker::load(GAGCore::InputStream *stream, Player *player, Sint32 versionMinor) +{ + stream->readEnterSection("UnPauseRessourceTracker"); + ManagementOrder::load(stream, player, versionMinor); + building_id=stream->readUint32("building_id"); + stream->readLeaveSection(); + return true; +} + + + +void UnPauseRessourceTracker::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("UnPauseRessourceTracker"); + ManagementOrder::save(stream); + stream->writeUint32(building_id, "building_id"); + stream->writeLeaveSection(); +} + + diff --git a/src/ai/echo/MapInfo.cpp b/src/ai/echo/MapInfo.cpp new file mode 100644 index 000000000..145fbe847 --- /dev/null +++ b/src/ai/echo/MapInfo.cpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" + +using namespace AIEcho; +using namespace AIEcho::SearchTools; + +MapInfo::MapInfo(Echo& echo) : echo(echo) +{ + +} + + + +int MapInfo::get_width() +{ + return echo.player->map->getW(); +} + + + +int MapInfo::get_height() +{ + return echo.player->map->getH(); +} + + + +bool MapInfo::is_forbidden_area(int x, int y) +{ + return echo.player->map->isForbidden(x, y, echo.player->team->me); +} + + + +bool MapInfo::is_guard_area(int x, int y) +{ + return echo.player->map->isGuardArea(x, y, echo.player->team->me); +} + + + +bool MapInfo::is_clearing_area(int x, int y) +{ + return echo.player->map->isClearArea(x, y, echo.player->team->me); +} + + + +bool MapInfo::is_discovered(int x, int y) +{ + return echo.player->map->isMapDiscovered(x, y, echo.player->team->me); +} + + + +bool MapInfo::is_ressource(int x, int y, int type) +{ + return echo.player->map->isRessourceTakeable(x, y, type); +} + + + +bool MapInfo::is_ressource(int x, int y) +{ + return echo.player->map->isRessource(x, y); +} + + + +bool MapInfo::is_water(int x, int y) +{ + return echo.player->map->isWater(x, y); +} + + + +bool MapInfo::is_sand(int x, int y) +{ + return echo.player->map->isSand(x, y); +} + + + +bool MapInfo::is_grass(int x, int y) +{ + return echo.player->map->isGrass(x, y); +} + + + +bool MapInfo::backs_onto_sand(int x, int y) +{ + if(echo.player->map->hasSand(x-1, y)) + return true; + if(echo.player->map->hasSand(x+1, y)) + return true; + if(echo.player->map->hasSand(x-1, y-1)) + return true; + if(echo.player->map->hasSand(x, y-1)) + return true; + if(echo.player->map->hasSand(x+1, y-1)) + return true; + if(echo.player->map->hasSand(x-1, y+1)) + return true; + if(echo.player->map->hasSand(x, y+1)) + return true; + if(echo.player->map->hasSand(x+1, y+1)) + return true; + return false; +} + + + +int MapInfo::get_ammount_ressource(int x, int y) +{ + return echo.player->map->getRessource(x, y).amount; +} diff --git a/src/ai/echo/Position.h b/src/ai/echo/Position.h new file mode 100644 index 000000000..45b6e1bc8 --- /dev/null +++ b/src/ai/echo/Position.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#pragma once + +namespace AIEcho +{ + ///A position on a map. Simple x and y cordinates, and a comparison operator for stoarge and maps and sets + class position + { + public: + position() : x(0), y(0) {} + position(int x, int y) : x(x), y(y) {} + int x; + int y; + bool operator<(const position& rhs) const + { + if(x!=rhs.x) + return xreadEnterSection("ReachToInfinity"); + timer=stream->readUint32("timer"); + flag_on_cherry=stream->readUint32("flag_on_cherry"); + flag_on_orange=stream->readUint32("flag_on_orange"); + flag_on_prune=stream->readUint32("flag_on_prune"); + + stream->readEnterSection("flags_on_enemy"); + Uint32 flagsOnEnemySize=stream->readUint32("size"); + for(Uint32 flagsOnEnemyIndex=0; flagsOnEnemyIndexreadEnterSection(flagsOnEnemyIndex); + flags_on_enemy.insert(stream->readUint32("gid")); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + stream->readLeaveSection(); + return true; +} + + +void ReachToInfinity::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("ReachToInfinity"); + stream->writeUint32(timer, "timer"); + stream->writeUint32(flag_on_cherry, "flag_on_cherry"); + stream->writeUint32(flag_on_orange, "flag_on_orange"); + stream->writeUint32(flag_on_prune, "flag_on_prune"); + + stream->writeEnterSection("flags_on_enemy"); + Uint32 flagsOnEnemyIndex=0; + stream->writeUint32(flags_on_enemy.size(), "size"); + for(std::set::iterator i=flags_on_enemy.begin(); i!=flags_on_enemy.end(); ++i, ++flagsOnEnemyIndex) + { + stream->writeEnterSection(flagsOnEnemyIndex); + stream->writeUint32(*i, "gid"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->writeLeaveSection(); +} + + +void ReachToInfinity::tick(Echo& echo) +{ + timer++; + + tick_initial_setup(echo); + // Demonstration of the Conditions/Constraints API previously inlined + // here as a /* ... */ block has been moved to doc/aiEchoExamples.txt. + + tick_explorer_flags_fruit(echo); + tick_explorer_flags_enemies(echo); + tick_inns_near_wheat(echo); + tick_swarms_near_wheat(echo); + tick_racetrack_near_stone_wood(echo); + tick_swimmingpool_near_wheat_wood(echo); + tick_school_inland(echo); + tick_upgrade_l1_to_l2(echo); + tick_upgrade_l2_to_l3(echo); + tick_delete_old_inns_swarms(echo); + tick_farming_areas(echo); +} + + +void ReachToInfinity::tick_initial_setup(Echo& echo) +{ + if(timer==1) + { + BuildingSearch bs(echo); + for(building_search_iterator i = bs.begin(); i!=bs.end(); ++i) + { + if(echo.get_building_register().get_type(*i)==IntBuildingType::SWARM_BUILDING) + { + ManagementOrder* mo_completion=new AssignWorkers(AI_ECHO_RTI_INITIAL_SWARM_WORKERS, *i); + echo.add_management_order(mo_completion); + + ManagementOrder* mo_ratios=new ChangeSwarm(AI_ECHO_RTI_SWARM_RATIO_WORKER, AI_ECHO_RTI_SWARM_RATIO_EXPLORER, AI_ECHO_RTI_SWARM_RATIO_WARRIOR, *i); + mo_ratios->add_condition(new ParticularBuilding(new NotUnderConstruction, *i)); + echo.add_management_order(mo_ratios); + + ManagementOrder* mo_tracker=new AddRessourceTracker(AI_ECHO_RTI_TRACKER_LENGTH, CORN, *i); + echo.add_management_order(mo_tracker); + } + if(echo.get_building_register().get_type(*i)==IntBuildingType::FOOD_BUILDING) + { + ManagementOrder* mo_tracker=new AddRessourceTracker(AI_ECHO_RTI_TRACKER_LENGTH, CORN, *i); + echo.add_management_order(mo_tracker); + } + } + } +} + + +void ReachToInfinity::handle_message(Echo& echo, const std::string& message) +{ + if(message=="construct inn") + { + //The main order for the inn + BuildingOrder* bo = new BuildingOrder(IntBuildingType::FOOD_BUILDING, 2); + + //Constraints around the location of wheat + AIEcho::Gradients::GradientInfo gi_wheat; + gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); + //You want to be close to wheat + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, AI_ECHO_RTI_INN_WHEAT_WEIGHT)); + //You can't be farther than 10 units from wheat + bo->add_constraint(new AIEcho::Construction::MaximumDistance(gi_wheat, AI_ECHO_RTI_INN_WHEAT_MAX_DIST)); + + //Constraints around nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_ECHO_RTI_BUILD_CLUSTER_WEIGHT)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_ECHO_RTI_INN_CONSTRUCTION_MIN_DIST)); + + //Constraints around the location of fruit + if(echo.is_fruit_on_map()) + { + AIEcho::Gradients::GradientInfo gi_fruit; + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); + //You want to be reasnobly close to fruit, closer if possible + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_fruit, AI_ECHO_RTI_INN_FRUIT_WEIGHT)); + } + + //Add the building order to the list of orders + unsigned int id=echo.add_building_order(bo); + +// std::cout<<"inn ordered, id="<add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_completion); + + ManagementOrder* mo_tracker=new AddRessourceTracker(AI_ECHO_RTI_TRACKER_LENGTH, CORN, id); + mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_tracker); + } +} diff --git a/src/ai/echo/ReachToInfinityBuilding.cpp b/src/ai/echo/ReachToInfinityBuilding.cpp new file mode 100644 index 000000000..db67cf89c --- /dev/null +++ b/src/ai/echo/ReachToInfinityBuilding.cpp @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include +#include "IntBuildingType.h" +#include +#include "Utilities.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; + + +//Standard Inns near wheat +void ReachToInfinity::tick_inns_near_wheat(Echo& echo) +{ + if((timer%AI_ECHO_RTI_INN_INTERVAL_TICKS)==0 && (timer%AI_ECHO_RTI_BIG_CYCLE_TICKS)!=0) + { + BuildingSearch bs_level1(echo); + bs_level1.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); + bs_level1.add_condition(new BuildingLevel(1)); + const int number1=bs_level1.count_buildings(); + + BuildingSearch bs_level2(echo); + bs_level2.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); + bs_level2.add_condition(new BuildingLevel(2)); + const int number2=bs_level2.count_buildings(); + + BuildingSearch bs_level3(echo); + bs_level3.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); + bs_level3.add_condition(new BuildingLevel(3)); + const int number3=bs_level3.count_buildings(); + + if((echo.player->team->stats.getLatestStat()->totalUnit)>=(number1*AI_ECHO_RTI_INN_POP_PER_L1 + number2*AI_ECHO_RTI_INN_POP_PER_L2 + number3*AI_ECHO_RTI_INN_POP_PER_L3)) + { + //The main order for the inn + BuildingOrder* bo = new BuildingOrder(IntBuildingType::FOOD_BUILDING, 2); + + //Constraints arround the location of wheat + AIEcho::Gradients::GradientInfo gi_wheat; + gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); + //You want to be close to wheat + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, AI_ECHO_RTI_INN_WHEAT_WEIGHT)); + //You can't be farther than 10 units from wheat + bo->add_constraint(new AIEcho::Construction::MaximumDistance(gi_wheat, AI_ECHO_RTI_INN_WHEAT_MAX_DIST)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_ECHO_RTI_BUILD_CLUSTER_WEIGHT)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_ECHO_RTI_INN_CONSTRUCTION_MIN_DIST)); + + if(echo.is_fruit_on_map()) + { + //Constraints arround the location of fruit + AIEcho::Gradients::GradientInfo gi_fruit; + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); + //You want to be reasnobly close to fruit, closer if possible + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_fruit, AI_ECHO_RTI_INN_FRUIT_WEIGHT)); + } + + //Add the building order to the list of orders + unsigned int id=echo.add_building_order(bo); + +// std::cout<<"inn ordered, id="<add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_completion); + + ManagementOrder* mo_tracker=new AddRessourceTracker(AI_ECHO_RTI_TRACKER_LENGTH, CORN, id); + mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_tracker); + } + } +} + +//Standard swarms near wheat. Uses special mechanism, builds more swarms early on. +void ReachToInfinity::tick_swarms_near_wheat(Echo& echo) +{ + if((timer%AI_ECHO_RTI_BIG_CYCLE_TICKS)==AI_ECHO_RTI_SWARM_OFFSET_TICKS) + { + BuildingSearch bs(echo); + bs.add_condition(new SpecificBuildingType(IntBuildingType::SWARM_BUILDING)); + const int number=bs.count_buildings(); + if((number<=AI_ECHO_RTI_SWARM_EARLY_LIMIT && (echo.player->team->stats.getLatestStat()->totalUnit/AI_ECHO_RTI_SWARM_EARLY_RATIO)>=number) || + (echo.player->team->stats.getLatestStat()->totalUnit/AI_ECHO_RTI_SWARM_LATE_RATIO)>=number) + { +// std::cout<<"Constructing swarm"<add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, AI_ECHO_RTI_INN_WHEAT_WEIGHT)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_ECHO_RTI_SWARM_CLUSTER_WEIGHT)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_ECHO_RTI_INN_CONSTRUCTION_MIN_DIST)); + + //Add the building order to the list of orders + unsigned int id=echo.add_building_order(bo); + +// std::cout<<"Swarm ordered, id="<add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_completion); + + //Change the ratio of the swarm when its finished + ManagementOrder* mo_ratios=new ChangeSwarm(AI_ECHO_RTI_SWARM_RATIO_WORKER, AI_ECHO_RTI_SWARM_RATIO_EXPLORER, AI_ECHO_RTI_SWARM_RATIO_WARRIOR, id); + mo_ratios->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_ratios); + + //Add a tracker + ManagementOrder* mo_tracker=new AddRessourceTracker(AI_ECHO_RTI_TRACKER_LENGTH, CORN, id); + mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_tracker); + + } + } +} + +//Standard racetrack near stone and wood +void ReachToInfinity::tick_racetrack_near_stone_wood(Echo& echo) +{ + if((timer%AI_ECHO_RTI_BIG_CYCLE_TICKS)==AI_ECHO_RTI_RACETRACK_OFFSET_TICKS) + { + BuildingSearch bs(echo); + bs.add_condition(new SpecificBuildingType(IntBuildingType::WALKSPEED_BUILDING)); + const int number=bs.count_buildings(); + if((echo.player->team->stats.getLatestStat()->totalUnit/AI_ECHO_RTI_SECONDARY_BLDG_RATIO)>=number && numberadd_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, AI_ECHO_RTI_RACETRACK_WOOD_WEIGHT)); + + //Constraints arround the location of stone + AIEcho::Gradients::GradientInfo gi_stone; + gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); + //You want to be close to stone + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_stone, AI_ECHO_RTI_RACETRACK_STONE_WEIGHT)); + //But not to close, so you have room to upgrade + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_stone, AI_ECHO_RTI_RACETRACK_STONE_MIN_DIST)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_ECHO_RTI_BUILD_CLUSTER_WEIGHT)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_ECHO_RTI_RACETRACK_CONSTR_MIN_DIST)); + + //Add the building order to the list of orders + echo.add_building_order(bo); + } + } +} + +//Standard swimming pool near wheat and wood +void ReachToInfinity::tick_swimmingpool_near_wheat_wood(Echo& echo) +{ + if((timer%AI_ECHO_RTI_BIG_CYCLE_TICKS)==AI_ECHO_RTI_SWIMMINGPOOL_OFFSET_TICKS) + { + BuildingSearch bs(echo); + bs.add_condition(new SpecificBuildingType(IntBuildingType::SWIMSPEED_BUILDING)); + const int number=bs.count_buildings(); + if((echo.player->team->stats.getLatestStat()->totalUnit/AI_ECHO_RTI_SECONDARY_BLDG_RATIO)>=number && numberadd_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, AI_ECHO_RTI_SWIMMINGPOOL_WOOD_WEIGHT)); + + //Constraints arround the location of wheat + AIEcho::Gradients::GradientInfo gi_wheat; + gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); + //You want to be close to wheat + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, AI_ECHO_RTI_SWIMMINGPOOL_WHEAT_WEIGHT)); + + //Constraints arround the location of stone + AIEcho::Gradients::GradientInfo gi_stone; + gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); + //You don't want to be too close, so you have room to upgrade + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_stone, AI_ECHO_RTI_SWIMMINGPOOL_STONE_MIN_DIST)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_ECHO_RTI_BUILD_CLUSTER_WEIGHT)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_ECHO_RTI_SWIMMINGPOOL_CONSTR_MIN_DIST)); + + //Add the building order to the list of orders + echo.add_building_order(bo); + } + } +} + + +//Standard school inland away from the enemies +void ReachToInfinity::tick_school_inland(Echo& echo) +{ + if((timer%AI_ECHO_RTI_BIG_CYCLE_TICKS)==AI_ECHO_RTI_SCHOOL_OFFSET_TICKS) + { + BuildingSearch bs(echo); + bs.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + const int number=bs.count_buildings(); + if((echo.player->team->stats.getLatestStat()->totalUnit/AI_ECHO_RTI_SECONDARY_BLDG_RATIO)>=number && numberteam->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_ECHO_RTI_BUILD_CLUSTER_WEIGHT)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_ECHO_RTI_SCHOOL_CONSTR_MIN_DIST)); + + //Constraints arround the enemy + AIEcho::Gradients::GradientInfo gi_enemy; + for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) + { + gi_enemy.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(*i, false)); + } + gi_enemy.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + bo->add_constraint(new AIEcho::Construction::MaximizedDistance(gi_enemy, AI_ECHO_RTI_SCHOOL_ENEMY_DIST_WEIGHT)); + + //Add the building order to the list of orders + echo.add_building_order(bo); + } + } +} + + +//Level 1 to level 2 upgrades +void ReachToInfinity::tick_upgrade_l1_to_l2(Echo& echo) +{ + if((timer%AI_ECHO_RTI_UPGRADE_INTERVAL_TICKS)==0) + { + BuildingSearch level_twos(echo); + level_twos.add_condition(new BeingUpgradedTo(AI_ECHO_RTI_UPGRADE_TARGET_LEVEL_2)); + const int level_two_counts=level_twos.count_buildings(); + + BuildingSearch schools(echo); + schools.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools.add_condition(new NotUnderConstruction); + const int school_counts=schools.count_buildings(); + + BuildingSearch buildings(echo); + buildings.add_condition(new BuildingLevel(1)); + const int total_buildings=buildings.count_buildings(); + if(level_two_counts<=(total_buildings/AI_ECHO_RTI_CONCURRENT_UPGRADE_FRACTION) && school_counts>0) + { + BuildingSearch bs(echo); + bs.add_condition(new Upgradable); + bs.add_condition(new BuildingLevel(1)); + if(school_counts buildings; + std::copy(bs.begin(), bs.end(), std::back_insert_iterator >(buildings)); + + if(buildings.size()!=0) + { + int chosen=syncRand()%buildings.size(); + ManagementOrder* uro = new UpgradeRepair(buildings[chosen]); + echo.add_management_order(uro); + + int assigned=echo.get_building_register().get_assigned(buildings[chosen]); + + ManagementOrder* mo_assign=new AssignWorkers(AI_ECHO_RTI_UPGRADE_WORKERS_DURING, buildings[chosen]); + mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, buildings[chosen])); + echo.add_management_order(mo_assign); + + if(echo.get_building_register().get_type(buildings[chosen])==IntBuildingType::FOOD_BUILDING) + { + ManagementOrder* mo_tracker_pause=new PauseRessourceTracker(buildings[chosen]); + mo_tracker_pause->add_condition(new ParticularBuilding(new UnderConstruction, buildings[chosen])); + echo.add_management_order(mo_tracker_pause); + + ManagementOrder* mo_tracker_unpause=new UnPauseRessourceTracker(buildings[chosen]); + mo_tracker_unpause->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); + echo.add_management_order(mo_tracker_unpause); + + ManagementOrder* mo_completion=new AssignWorkers(AI_ECHO_RTI_INN_L2_WORKERS_FINISHED, buildings[chosen]); + mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); + echo.add_management_order(mo_completion); + } + else + { + ManagementOrder* mo_assign=new AssignWorkers(assigned, buildings[chosen]); + mo_assign->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); + echo.add_management_order(mo_assign); + } + } + } + } +} + +//Level 2 to level 3 upgrades +void ReachToInfinity::tick_upgrade_l2_to_l3(Echo& echo) +{ + if((timer%AI_ECHO_RTI_UPGRADE_INTERVAL_TICKS)==0) + { + BuildingSearch level_threes(echo); + level_threes.add_condition(new BeingUpgradedTo(AI_ECHO_RTI_UPGRADE_TARGET_LEVEL_3)); + const int level_three_counts=level_threes.count_buildings(); + + BuildingSearch schools(echo); + schools.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools.add_condition(new NotUnderConstruction); + schools.add_condition(new BuildingLevel(2)); + int school_counts=schools.count_buildings(); + + BuildingSearch schools2(echo); + schools2.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools2.add_condition(new NotUnderConstruction); + schools2.add_condition(new BuildingLevel(3)); + school_counts+=schools2.count_buildings(); + + BuildingSearch buildings(echo); + buildings.add_condition(new BuildingLevel(2)); + const int total_buildings=buildings.count_buildings(); + if(level_three_counts<=(total_buildings/AI_ECHO_RTI_CONCURRENT_UPGRADE_FRACTION) && school_counts>0) + { + BuildingSearch bs(echo); + bs.add_condition(new Upgradable); + bs.add_condition(new BuildingLevel(2)); + if(school_counts buildings; + std::copy(bs.begin(), bs.end(), std::back_insert_iterator >(buildings)); + + if(buildings.size()!=0) + { + int chosen=syncRand()%buildings.size(); + ManagementOrder* uro = new UpgradeRepair(buildings[chosen]); + echo.add_management_order(uro); + + int assigned=echo.get_building_register().get_assigned(buildings[chosen]); + + ManagementOrder* mo_assign=new AssignWorkers(AI_ECHO_RTI_UPGRADE_WORKERS_DURING, buildings[chosen]); + mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, buildings[chosen])); + echo.add_management_order(mo_assign); + + if(echo.get_building_register().get_type(buildings[chosen])==IntBuildingType::FOOD_BUILDING) + { + ManagementOrder* mo_tracker_pause=new PauseRessourceTracker(buildings[chosen]); + mo_tracker_pause->add_condition(new ParticularBuilding(new UnderConstruction, buildings[chosen])); + echo.add_management_order(mo_tracker_pause); + + ManagementOrder* mo_tracker_unpause=new UnPauseRessourceTracker(buildings[chosen]); + mo_tracker_unpause->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); + echo.add_management_order(mo_tracker_unpause); + + ManagementOrder* mo_completion=new AssignWorkers(AI_ECHO_RTI_INN_L3_WORKERS_FINISHED, buildings[chosen]); + mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); + echo.add_management_order(mo_completion); + } + else + { + ManagementOrder* mo_assign=new AssignWorkers(assigned, buildings[chosen]); + mo_assign->add_condition(new ParticularBuilding(new NotUnderConstruction, buildings[chosen])); + echo.add_management_order(mo_assign); + } + } + } + } +} + + + +//Delete old inns and swarms that are hard to keep full of wheat +void ReachToInfinity::tick_delete_old_inns_swarms(Echo& echo) +{ + if((timer%AI_ECHO_RTI_DELETE_SCAN_INTERVAL_TICKS)==0) + { + BuildingSearch inns(echo); + inns.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); + inns.add_condition(new NotUnderConstruction); + for(building_search_iterator i=inns.begin(); i!=inns.end(); ++i) + { + std::shared_ptr rt=echo.get_ressource_tracker(*i); + if(rt) + { + if(rt->get_age()>AI_ECHO_RTI_INN_DELETE_AGE_TICKS) + { + if(rt->get_total_level() < AI_ECHO_RTI_INN_DELETE_FOOD_PER_LEVEL*echo.get_building_register().get_level(*i)) + { + ManagementOrder* mo_destroy=new DestroyBuilding(*i); + echo.add_management_order(mo_destroy); + } + } + } + } + + + BuildingSearch swarms(echo); + swarms.add_condition(new SpecificBuildingType(IntBuildingType::SWARM_BUILDING)); + swarms.add_condition(new NotUnderConstruction); + for(building_search_iterator i=swarms.begin(); i!=swarms.end(); ++i) + { + std::shared_ptr rt=echo.get_ressource_tracker(*i); + if(rt) + { + if(rt->get_age()>AI_ECHO_RTI_SWARM_DELETE_AGE_TICKS) + { + if(rt->get_total_level() < AI_ECHO_RTI_SWARM_DELETE_FOOD) + { + ManagementOrder* mo_destroy=new DestroyBuilding(*i); + echo.add_management_order(mo_destroy); + } + } + } + } + } +} diff --git a/src/ai/echo/ReachToInfinityFlags.cpp b/src/ai/echo/ReachToInfinityFlags.cpp new file mode 100644 index 000000000..6dc96cf39 --- /dev/null +++ b/src/ai/echo/ReachToInfinityFlags.cpp @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "IntBuildingType.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + +//Explorer flags on the three nearest fruit trees +void ReachToInfinity::tick_explorer_flags_fruit(Echo& echo) +{ + if((timer%AI_ECHO_RTI_FRUIT_FLAG_INTERVAL_TICKS)==0) + { + if(echo.is_fruit_on_map()) + { + if(echo.get_team_stats().numberUnitPerType[EXPLORER]>=AI_ECHO_RTI_FRUIT_FLAG_EXPLORER_MIN && !flag_on_cherry && !flag_on_orange && !flag_on_prune) + { + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + + if(!flag_on_cherry) + { + //The main order for the exploration flag + BuildingOrder* bo_cherry = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 2); + + //You want the closest fruit to your settlement possible + bo_cherry->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); + + //Constraint arround the location of fruit + AIEcho::Gradients::GradientInfo gi_cherry; + gi_cherry.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); + //You want to be ontop of the cherry trees + bo_cherry->add_constraint(new AIEcho::Construction::MaximumDistance(gi_cherry, 0)); + + //Add the building order to the list of orders + unsigned int id_cherry=echo.add_building_order(bo_cherry); + + if(id_cherry!=INVALID_BUILDING) + { + ManagementOrder* mo_completion=new ChangeFlagSize(AI_ECHO_RTI_FRUIT_FLAG_RADIUS, id_cherry); + echo.add_management_order(mo_completion); + flag_on_cherry=true; + + for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) + { + ManagementOrder* mo_alliance=new ChangeAlliances(*i, indeterminate, indeterminate, indeterminate, true, indeterminate); + echo.add_management_order(mo_alliance); + } + } + } + + if(!flag_on_orange) + { + //The main order for the exploration flag + BuildingOrder* bo_orange = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 2); + + //You want the closest fruit to your settlement possible + bo_orange->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); + + //Constraints arround the location of fruit + AIEcho::Gradients::GradientInfo gi_orange; + gi_orange.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); + //You want to be ontop of the orange trees + bo_orange->add_constraint(new AIEcho::Construction::MaximumDistance(gi_orange, 0)); + + unsigned int id_orange=echo.add_building_order(bo_orange); + + if(id_orange!=INVALID_BUILDING) + { + ManagementOrder* mo_completion=new ChangeFlagSize(AI_ECHO_RTI_FRUIT_FLAG_RADIUS, id_orange); + echo.add_management_order(mo_completion); + flag_on_orange=true; + + for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) + { + ManagementOrder* mo_alliance=new ChangeAlliances(*i, indeterminate, indeterminate, indeterminate, true, indeterminate); + echo.add_management_order(mo_alliance); + } + } + } + + if(!flag_on_prune) + { + //The main order for the exploration flag + BuildingOrder* bo_prune = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 2); + + //You want the closest fruit to your settlement possible + bo_prune->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, 1)); + + AIEcho::Gradients::GradientInfo gi_prune; + gi_prune.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); + //You want to be ontop of the prune trees + bo_prune->add_constraint(new AIEcho::Construction::MaximumDistance(gi_prune, 0)); + + //Add the building order to the list of orders + unsigned int id_prune=echo.add_building_order(bo_prune); + + if(id_prune!=INVALID_BUILDING) + { + ManagementOrder* mo_completion=new ChangeFlagSize(AI_ECHO_RTI_FRUIT_FLAG_RADIUS, id_prune); + echo.add_management_order(mo_completion); + flag_on_prune=true; + + for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) + { + ManagementOrder* mo_alliance=new ChangeAlliances(*i, indeterminate, indeterminate, indeterminate, true, indeterminate); + echo.add_management_order(mo_alliance); + } + } + } + } + } + } +} + +//Place exploration flags on the enemy swarms +void ReachToInfinity::tick_explorer_flags_enemies(Echo& echo) +{ + if((timer%AI_ECHO_RTI_ENEMY_SCAN_INTERVAL_TICKS)==0) + { + if(echo.get_team_stats().numberUnitPerType[EXPLORER]>=AI_ECHO_RTI_ENEMY_FLAG_EXPLORER_MIN) + { + for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) + { + for(enemy_building_iterator ebi(echo, *i, IntBuildingType::SWARM_BUILDING, AI_ECHO_WILDCARD_LEVEL, false); ebi!=enemy_building_iterator(); ++ebi) + { + if(flags_on_enemy.find(*i)!=flags_on_enemy.end()) + continue; + + BuildingOrder* bo = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 1); + bo->add_constraint(new CenterOfBuilding(*ebi)); + unsigned int id=echo.add_building_order(bo); + + if(id!=INVALID_BUILDING) + { + ManagementOrder* mo_completion=new ChangeFlagSize(AI_ECHO_RTI_ENEMY_FLAG_RADIUS, id); + echo.add_management_order(mo_completion); + + ManagementOrder* mo_destroyed=new DestroyBuilding(id); + mo_destroyed->add_condition(new EnemyBuildingDestroyed(echo, *ebi)); + echo.add_management_order(mo_destroyed); + + flags_on_enemy.insert(*i); + } + } + } + } + } +} + +//Farming wheat and wood near water +void ReachToInfinity::tick_farming_areas(Echo& echo) +{ + if((timer%AI_ECHO_RTI_FARMING_INTERVAL_TICKS)==0) + { + AddArea* mo_farming=new AddArea(ForbiddenArea); + RemoveArea* mo_non_farming=new RemoveArea(ForbiddenArea); + AIEcho::Gradients::GradientInfo gi_water; + gi_water.add_source(new Entities::Water); + Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_water); + MapInfo mi(echo); + for(int x=0; xadd_location(x, y); + } + else + { + if((mi.is_ressource(x, y, WOOD) || + mi.is_ressource(x, y, CORN)) && + mi.is_discovered(x, y) && + !mi.is_forbidden_area(x, y) && + gradient.within_dist(x, y, AI_ECHO_RTI_FARMING_WATER_MAX_DIST)) + { + mo_farming->add_location(x, y); + } + } + } + } + } + echo.add_management_order(mo_farming); + echo.add_management_order(mo_non_farming); + } +} diff --git a/src/ai/echo/SearchTools.cpp b/src/ai/echo/SearchTools.cpp new file mode 100644 index 000000000..e20e8432f --- /dev/null +++ b/src/ai/echo/SearchTools.cpp @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "echo/Echo.h" +#include "Building.h" +#include +#include +#include +#include +#include +#include "BuildingType.h" +#include "IntBuildingType.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Order.h" +#include +#include "Utilities.h" +#include +#include "Brush.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; +using std::shared_ptr; + + + +building_search_iterator::building_search_iterator() : found_id(AI_ECHO_ITER_NOT_STARTED), is_end(true), search(NULL) +{ + +} + + +building_search_iterator::building_search_iterator(BuildingSearch& search) : found_id(AI_ECHO_ITER_NOT_STARTED), is_end(false), search(&search) +{ + set_to_next(); +} + + + +const unsigned int building_search_iterator::operator*() +{ + return found_id; +} + + + +building_search_iterator& building_search_iterator::operator++() +{ + set_to_next(); + return *this; +} + + + +building_search_iterator building_search_iterator::operator++(int) +{ + building_search_iterator copy(*this); + set_to_next(); + return copy; +} + + + +bool building_search_iterator::operator!=(const building_search_iterator& rhs) const +{ + if(is_end==rhs.is_end) + return false; + return is_end!=rhs.is_end || position!=rhs.position || found_id!=rhs.found_id ; +} + + + +void building_search_iterator::set_to_next() +{ + Construction::BuildingRegister::found_iterator positionSaved = position; + if(is_end) + return; + if(found_id==AI_ECHO_ITER_NOT_STARTED) + { + position=search->echo.get_building_register().begin(); + } + else + position++; + for(; position!=search->echo.get_building_register().end() && !search->passes_conditions(position->first); position++) + { + } + if(position==search->echo.get_building_register().end()) + { + is_end=true; + return; + } + if(position->first==AI_ECHO_ITER_NOT_STARTED && positionSaved==position) + { // This fixes an infinit loop. + is_end=true; // In some special cases the program Logic + return; // must have been wrong. + } + found_id=position->first; +} + + + +BuildingSearch::BuildingSearch(Echo& echo) : echo(echo) +{ + +} + + + +void BuildingSearch::add_condition(Conditions::BuildingCondition* condition) +{ + conditions.push_back(std::shared_ptr(condition)); +} + + + +int BuildingSearch::count_buildings() +{ + int count=0; + for(Construction::BuildingRegister::found_iterator i=echo.get_building_register().begin(); i!=echo.get_building_register().end(); ++i) + { + if(passes_conditions(i->first)) + { + count++; + } + } + return count; +} + + + +building_search_iterator BuildingSearch::begin() +{ + return building_search_iterator(*this); +} + + + +building_search_iterator BuildingSearch::end() +{ + return building_search_iterator(); +} + + + +bool BuildingSearch::passes_conditions(int b) +{ + for(std::vector >::iterator i = conditions.begin(); i!=conditions.end(); ++i) + { + if(!(*i)->passes(echo, b)) + return false; + } + return true; +} + + +enemy_team_iterator::enemy_team_iterator(Echo& echo) : team_number(AI_ECHO_ITER_NOT_STARTED), is_end(false), echo(&echo) +{ + set_to_next(); +} + + +enemy_team_iterator::enemy_team_iterator() : team_number(AI_ECHO_ITER_NOT_STARTED), is_end(true), echo(NULL) +{ + +} + + +const unsigned int enemy_team_iterator::operator*() +{ + return team_number; +} + + +enemy_team_iterator& enemy_team_iterator::operator++() +{ + set_to_next(); + return *this; +} + + +enemy_team_iterator enemy_team_iterator::operator++(int) +{ + enemy_team_iterator copy(*this); + set_to_next(); + return copy; +} + + +bool enemy_team_iterator::operator!=(const enemy_team_iterator& rhs) const +{ + if(rhs.is_end && is_end) + return false; + return rhs.is_end != is_end || rhs.team_number!=team_number; +} + + +void enemy_team_iterator::set_to_next() +{ + if(is_end) + return; + if(team_number==AI_ECHO_ITER_NOT_STARTED) + { + team_number=0; + } + else + team_number++; + for(; echo->player->team->game->teams[team_number]!=NULL && !(echo->player->team->enemies & echo->player->team->game->teams[team_number]->me); team_number++) + { + } + + if(echo->player->team->game->teams[team_number]==NULL) + { + is_end=true; + return; + } + +} + + +int SearchTools::is_flag(Echo& echo, int x, int y) +{ + Building** buildings=echo.player->team->myBuildings; + for(int n=0; nposX==x && b->posY==y) + { + if(b->type->shortTypeNum > (int)(IntBuildingType::DEFENSE_BUILDING) && b->type->shortTypeNum < (int)(IntBuildingType::STONE_WALL)) + { + return b->gid; + } + } + } + } + return NOGBID; +} + + + + +enemy_building_iterator::enemy_building_iterator() : is_end(true) +{ + +} + + + +enemy_building_iterator::enemy_building_iterator(Echo& echo, int team, int building_type, int level, boost::logic::tribool construction_site) : current_gid(AI_ECHO_ITER_NOT_STARTED), team(team), building_type(building_type), level(level), construction_site(construction_site), is_end(false), echo(&echo) +{ + set_to_next(); +} + + + +const unsigned int enemy_building_iterator::operator*() +{ + return current_gid; +} + + + +enemy_building_iterator& enemy_building_iterator::operator++() +{ + set_to_next(); + return *this; +} + + + +enemy_building_iterator enemy_building_iterator::operator++(int) +{ + enemy_building_iterator copy; + set_to_next(); + return copy; +} + + + +bool enemy_building_iterator::operator!=(const enemy_building_iterator& rhs) const +{ + if(is_end && rhs.is_end) + return false; + return is_end!=rhs.is_end || team!=rhs.team || building_type!=rhs.building_type || level!=rhs.level || bool(construction_site!=rhs.construction_site); +} + + + +void enemy_building_iterator::set_to_next() +{ + if(current_gid==AI_ECHO_ITER_NOT_STARTED) + { + current_index=0; + } + else + current_index++; + + while(current_indexplayer->game->teams[team]->myBuildings[current_index]; + if(b) + { + if( (b->seenByMask&echo->player->team->me + // Don't allow AIs to cheat!!!!!! + // || echo->get_starting_buildings().find(b->gid)!=echo->get_starting_buildings().end() + ) && + (building_type==AI_ECHO_WILDCARD_TYPE || b->type->shortTypeNum==building_type) && + (level==AI_ECHO_WILDCARD_LEVEL || b->type->level==(level-AI_ECHO_LEVEL_OFFSET_USER_TO_ENGINE))) + { + if(construction_site) + { + if(b->type->isBuildingSite) + { + current_gid=b->gid; + break; + } + } + else if(!construction_site) + { + if(!b->type->isBuildingSite) + { + current_gid=b->gid; + break; + } + } + else + { + current_gid=b->gid; + break; + } + } + } + current_index++; + } + + if(current_index==Building::MAX_COUNT) + is_end=true; +} + + diff --git a/src/ai/echo/SearchTools.h b/src/ai/echo/SearchTools.h new file mode 100644 index 000000000..a731aa0bd --- /dev/null +++ b/src/ai/echo/SearchTools.h @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#pragma once + +#include "echo/Construction.h" + +#include +#include +#include +#include + +namespace AIEcho +{ + class Echo; + + namespace Conditions + { + class BuildingCondition; + } + + ///This namespace stores anything related to search and iterating through buildings or teams that satisfy particular conditions. + namespace SearchTools + { + class BuildingSearch; + + ///This is a standards complying iterator that iterates over buildings that satisfy conditions. Can only be + ///obtained from a BuildingSearch object. + class building_search_iterator + { + public: + const unsigned int operator*(); + building_search_iterator& operator++(); + building_search_iterator operator++(int); + bool operator!=(const building_search_iterator& rhs) const; + + typedef std::forward_iterator_tag iterator_category; + typedef unsigned int value_type; + typedef size_t difference_type; + typedef unsigned int* pointer; + typedef unsigned int& reference; + private: + friend class AIEcho::SearchTools::BuildingSearch; + building_search_iterator(); + explicit building_search_iterator(BuildingSearch& search); + void set_to_next(); + int found_id; + Construction::BuildingRegister::found_iterator position; + bool is_end; + BuildingSearch* search; + }; + + ///This class holds all of the conditions for a search of buildings. Its much preferred to use this building search system + ///than to manually go over the buildings yourself, or record building ID's in your AI for future use. It has a begin() and + ///end() function like standard containers + class BuildingSearch + { + public: + explicit BuildingSearch(Echo& echo); + ///This adds a condition that the building has to pass in order to be examined. + void add_condition(Conditions::BuildingCondition* condition); + ///This counts up all the buildings that satisfy the conditions + int count_buildings(); + ///Returns the begininng iterator + building_search_iterator begin(); + ///Returns the one-past-the-end iterator + building_search_iterator end(); + private: + friend class AIEcho::SearchTools::building_search_iterator; + Echo& echo; + bool passes_conditions(int b); + std::vector > conditions; + }; + + ///This class is a standard iterator that is used to iterate over teams that qualify as "enemies". + ///It returns an integer corrosponding to the teams id. + class enemy_team_iterator + { + public: + explicit enemy_team_iterator(Echo& echo); + enemy_team_iterator(); + const unsigned int operator*(); + enemy_team_iterator& operator++(); + enemy_team_iterator operator++(int); + bool operator!=(const enemy_team_iterator& rhs) const; + + typedef std::forward_iterator_tag iterator_category; + typedef unsigned int value_type; + typedef size_t difference_type; + typedef unsigned int* pointer; + typedef unsigned int& reference; + private: + void set_to_next(); + int team_number; + bool is_end; + Echo* echo; + }; + + ///This function returns whether there is a flag at the given position, and if so, its GID, if not, NOGBID + int is_flag(Echo& echo, int x, int y); + + ///This is an iterator that is used to iterate over enemy buildings. You only get so much information + ///about enemy buildings, which is why you can't use the standard Conditions. It returns standard GBIDs, + ///which are different from the building ID's you get in other portions of the system. If a class or function + ///requires a GBID instead of a standard building id, it has to come from here. You also don't get information + ///on buildings you can't see, with one exception, you can get information about buildings you don't see, + ///as long as those buildings existed when the game started (this simulates a human looking at the map before + ///a game) + class enemy_building_iterator + { + public: + enemy_building_iterator(); + ///These are the three pieces of information you are provided with. If building_type or level are -1, + ///they are considered a wildcard, any building will match. If construction_site is indeterminate, + ///the same thing applies, its a wildcard, any building will match. + enemy_building_iterator(Echo& echo, int team, int building_type, int level, boost::logic::tribool construction_site); + + const unsigned int operator*(); + enemy_building_iterator& operator++(); + enemy_building_iterator operator++(int); + bool operator!=(const enemy_building_iterator& rhs) const; + + typedef std::forward_iterator_tag iterator_category; + typedef unsigned int value_type; + typedef size_t difference_type; + typedef unsigned int* pointer; + typedef unsigned int& reference; + + private: + void set_to_next(); + int current_gid; + int current_index; + int team; + int building_type; + int level; + boost::logic::tribool construction_site; + bool is_end; + Echo* echo; + }; + + + ///This class is used to get information about the map. + class MapInfo + { + public: + MapInfo(Echo& echo); + int get_width(); + int get_height(); + bool is_forbidden_area(int x, int y); + bool is_guard_area(int x, int y); + bool is_clearing_area(int x, int y); + bool is_discovered(int x, int y); + bool is_ressource(int x, int y, int type); + bool is_ressource(int x, int y); + bool is_water(int x, int y); + bool is_sand(int x, int y); + bool is_grass(int x, int y); + bool backs_onto_sand(int x, int y); + int get_ammount_ressource(int x, int y); + private: + Echo& echo; + }; + }; +} diff --git a/src/ai/nicowar/Attack.cpp b/src/ai/nicowar/Attack.cpp new file mode 100644 index 000000000..d7479f364 --- /dev/null +++ b/src/ai/nicowar/Attack.cpp @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "AINicowar.h" +#include "GlobalContainer.h" +#include "FormatableString.h" +#include +#include "Utilities.h" +#include "Game.h" +#include "Unit.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + + +int NewNicowar::choose_building_to_attack(Echo& echo) +{ + std::vector buildings_to_attack; + buildings_to_attack.reserve(100); + + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new Entities::AnyRessource); + Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_building); + + for(enemy_building_iterator ebi(echo, target, -1, -1, indeterminate); ebi!=enemy_building_iterator(); ++ebi) + { + Building* b=echo.player->game->teams[target]->myBuildings[Building::GIDtoID(*ebi)]; + if(gradient.get_height(b->posX, b->posY) != AI_NICOWAR_GRADIENT_UNREACHABLE) + buildings_to_attack.push_back(*ebi); + } + + if(buildings_to_attack.size() == 0) + return -1; + + int num=syncRand() % buildings_to_attack.size(); + return buildings_to_attack[num]; +} + + +void NewNicowar::attack_building(Echo& echo) +{ + int building=choose_building_to_attack(echo); + if(building==-1) + { + if(!is_digging_out) + if(!dig_out_enemy(echo)) + { + target = AI_NICOWAR_NO_TARGET; + } + return; + } + BuildingOrder* bo = new BuildingOrder(IntBuildingType::WAR_FLAG, strategy.war_phase_war_flag_units_assigned); + bo->add_constraint(new CenterOfBuilding(building)); + unsigned int id=echo.add_building_order(bo); + + ManagementOrder* mo_minimum=new ChangeFlagMinimumLevel(AI_NICOWAR_WAR_FLAG_MIN_LEVEL,id); + echo.add_management_order(mo_minimum); + + ManagementOrder* mo_destroyed_1=new DestroyBuilding(id); + mo_destroyed_1->add_condition(new EnemyBuildingDestroyed(echo, building)); + echo.add_management_order(mo_destroyed_1); + + ManagementOrder* mo_destroyed_2=new SendMessage("attack finished "+std::to_string(id)); + mo_destroyed_2->add_condition(new BuildingDestroyed(id)); + echo.add_management_order(mo_destroyed_2); + + attack_flags.push_back(id); +} + + +void NewNicowar::control_attacks(Echo& echo) +{ + choose_enemy_target(echo); + + if(target!=AI_NICOWAR_NO_TARGET) + { + unsigned number_attacks=0; + if(war) + { + number_attacks=strategy.war_phase_num_attack_flags; + } + + if(attack_flags.size() < number_attacks) + { + attack_building(echo); + } + } + + BuildingSearch bs_pool(echo); + bs_pool.add_condition(new SpecificBuildingType(IntBuildingType::SWIMSPEED_BUILDING)); + int num_pool=bs_pool.count_buildings(); + + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new Entities::AnyRessource); + if(num_pool == 0) + gi_building.add_obstacle(new Entities::Water); + Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_building); + + for(unsigned i=0; iposX, b->posY) == AI_NICOWAR_GRADIENT_UNREACHABLE) + { + ManagementOrder* mo_destroy=new DestroyBuilding(attack_flags[i]); + echo.add_management_order(mo_destroy); + } + } + } +} + + + +void NewNicowar::choose_enemy_target(Echo& echo) +{ + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new Entities::AnyRessource); + Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_building); + + if(target==AI_NICOWAR_NO_TARGET || !echo.player->game->teams[target]->isAlive) + { + std::vector available_reachable_targets; + std::vector available_targets; + for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) + { + if(echo.player->game->teams[*i]->isAlive) + { + available_targets.push_back(*i); + enemy_building_iterator ebi(echo, *i, -1, -1, indeterminate); + /* Make sure we know of at least one + building that we can directly attack + before committing to a particular enemy. + It used to be that we did not (normally) + need to test this, because all starting + buildings were known. But that was + cheating and has been fixed. */ + for(; ebi != enemy_building_iterator(); ++ebi) + { + Building* b=echo.player->game->teams[*i]->myBuildings[Building::GIDtoID(*ebi)]; + if(gradient.get_height(b->posX, b->posY) != AI_NICOWAR_GRADIENT_UNREACHABLE) + { + available_reachable_targets.push_back(*i); + break; + } + } + } + } + if(available_reachable_targets.size()!=0) + target=available_reachable_targets[syncRand() % available_reachable_targets.size()]; + else if(available_targets.size()!=0) + target=available_targets[syncRand() % available_targets.size()]; + else + target=AI_NICOWAR_NO_TARGET; + } +} + + + +bool NewNicowar::dig_out_enemy(Echo& echo) +{ + ///First choose an enemy building to dig out + std::vector buildings_to_attack; + buildings_to_attack.reserve(100); + + MapInfo mi(echo); + + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new Entities::AnyRessource); + Gradient& gradient=echo.get_gradient_manager().get_gradient(gi_building); + + for(enemy_building_iterator ebi(echo, target, -1, -1, indeterminate); ebi!=enemy_building_iterator(); ++ebi) + { + Building* b=echo.player->game->teams[target]->myBuildings[Building::GIDtoID(*ebi)]; + int bx = (b->posX + mi.get_width()) % mi.get_width(); + int by = (b->posY + mi.get_height()) % mi.get_height(); + if(gradient.get_height(bx, by) == AI_NICOWAR_GRADIENT_UNREACHABLE) + buildings_to_attack.push_back(*ebi); + } + + if(buildings_to_attack.size() == 0) + return false; + + int num=syncRand() % buildings_to_attack.size(); + + + int building=buildings_to_attack[num]; + const int bx=(echo.player->game->teams[target]->myBuildings[Building::GIDtoID(building)]->posX) % mi.get_width(); + const int by=(echo.player->game->teams[target]->myBuildings[Building::GIDtoID(building)]->posY) % mi.get_height(); + + AIEcho::Gradients::GradientInfo gi_pathfind; + gi_pathfind.add_source(new Entities::Position(bx, by)); + gi_pathfind.add_obstacle(new Entities::Ressource(STONE)); + Gradient& gradient_pathfind=echo.get_gradient_manager().get_gradient(gi_pathfind); + + ///Next, find the closest point manhattan distance wise, to the building that is accessible + int closest_x=0; + int closest_y=0; + int closest_distance=AI_NICOWAR_DIG_OUT_INIT_DIST; + for(int x=0; x= 0) + { + int dist=gradient_pathfind.get_height(x, y); + if(dist < closest_distance) + { + closest_x=x; + closest_y=y; + closest_distance=dist; + } + } + } + } + + ///Next, follow a path arround stone between the closest point and the buildings position, + ///placing Clearing flags as you go + + int xpos=closest_x; + int ypos=closest_y; + + int flag_dist_count=AI_NICOWAR_DIG_FLAG_INIT_COUNTER; + + int w=mi.get_width(); + int h=mi.get_height(); + + while(xpos != bx || ypos!=by) + { + int nxpos = xpos; + int nypos = ypos; + int rx=(xpos+1+w) % w; + int lx=(xpos-1+w) % w; + int dy=(ypos+1+h) % h; + int uy=(ypos-1+h) % h; + int lowest_entity=gradient_pathfind.get_height(xpos, ypos)+AI_NICOWAR_PATHFIND_TOLERANCE; + + if(lowest_entity == 0) + break; + + //Test diagnols first, then the horizontals and verticals. + if(gradient_pathfind.get_height(lx, uy) < lowest_entity && gradient_pathfind.get_height(lx, uy)>=0) + { + lowest_entity=gradient_pathfind.get_height(lx, uy); + nxpos=lx; + nypos=uy; + } + if(gradient_pathfind.get_height(rx, uy) < lowest_entity && gradient_pathfind.get_height(rx, uy)>=0) + { + lowest_entity=gradient_pathfind.get_height(rx, uy); + nxpos=rx; + nypos=uy; + } + if(gradient_pathfind.get_height(lx, dy) < lowest_entity && gradient_pathfind.get_height(lx, dy)>=0) + { + lowest_entity=gradient_pathfind.get_height(lx, dy); + nxpos=lx; + nypos=dy; + } + if(gradient_pathfind.get_height(rx, dy) < lowest_entity && gradient_pathfind.get_height(rx, dy)>=0) + { + lowest_entity=gradient_pathfind.get_height(rx, dy); + nxpos=rx; + nypos=dy; + } + + if(gradient_pathfind.get_height(xpos, uy) < lowest_entity && gradient_pathfind.get_height(xpos, uy)>=0) + { + lowest_entity=gradient_pathfind.get_height(xpos, uy); + nxpos=xpos; + nypos=uy; + } + if(gradient_pathfind.get_height(lx, ypos) < lowest_entity && gradient_pathfind.get_height(lx, ypos)>=0) + { + lowest_entity=gradient_pathfind.get_height(lx, ypos); + nxpos=lx; + nypos=ypos; + } + if(gradient_pathfind.get_height(rx, ypos) < lowest_entity && gradient_pathfind.get_height(rx, ypos)>=0) + { + lowest_entity=gradient_pathfind.get_height(rx, ypos); + nxpos=rx; + nypos=ypos; + } + if(gradient_pathfind.get_height(xpos, dy) < lowest_entity && gradient_pathfind.get_height(xpos, dy)>=0) + { + lowest_entity=gradient_pathfind.get_height(xpos, dy); + nxpos=xpos; + nypos=dy; + } + + + flag_dist_count+=1; + + + if(flag_dist_count>AI_NICOWAR_DIG_FLAG_INTERVAL) + { + flag_dist_count=0; + //The main order for the clearing flag + BuildingOrder* bo_flag = new BuildingOrder(IntBuildingType::CLEARING_FLAG, AI_NICOWAR_DIG_CLEARING_WORKERS); + //Place it on the current point + bo_flag->add_constraint(new Construction::SinglePosition(xpos, ypos)); + //Add the building order to the list of orders + unsigned int id_flag=echo.add_building_order(bo_flag); + + ManagementOrder* mo_destroyed=new DestroyBuilding(id_flag); + mo_destroyed->add_condition(new EnemyBuildingDestroyed(echo, building)); + echo.add_management_order(mo_destroyed); + + + ManagementOrder* mo_completion=new ChangeFlagSize(AI_NICOWAR_DIG_FLAG_SIZE, id_flag); + echo.add_management_order(mo_completion); + } + xpos = nxpos; + ypos = nypos; + + } + + ManagementOrder* mo_destroyed=new SendMessage("finished digging out"); + mo_destroyed->add_condition(new EnemyBuildingDestroyed(echo, building)); + echo.add_management_order(mo_destroyed); + + is_digging_out=true; + + return true; +} + + diff --git a/src/ai/nicowar/Buildings.cpp b/src/ai/nicowar/Buildings.cpp new file mode 100644 index 000000000..b00e155a6 --- /dev/null +++ b/src/ai/nicowar/Buildings.cpp @@ -0,0 +1,826 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "AINicowar.h" +#include "GlobalContainer.h" +#include "FormatableString.h" +#include +#include "Utilities.h" +#include "Game.h" +#include "Unit.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + + +void NewNicowar::queue_buildings(Echo& echo) +{ + queue_racetracks(echo); + queue_swimmingpools(echo); + queue_schools(echo); + queue_barracks(echo); + queue_hospitals(echo); + queue_inns(echo); + queue_swarms(echo); +} + + +void NewNicowar::queue_inns(Echo& echo) +{ + //Get some statistics + TeamStat* stat=echo.player->team->stats.getLatestStat(); + int total_workers=stat->numberUnitPerType[WORKER]; + int total_explorers=stat->numberUnitPerType[EXPLORER]; + int total_warriors=stat->numberUnitPerType[WARRIOR]; + + //Count the number of inns there are at each level + BuildingSearch bs_level1(echo); + bs_level1.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); + bs_level1.add_condition(new BuildingLevel(1)); + bs_level1.add_condition(new NotUnderConstruction); + const int number1=bs_level1.count_buildings() + buildings_under_construction_per_type[RegularInn]; + + BuildingSearch bs_level2(echo); + bs_level2.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); + bs_level2.add_condition(new BuildingLevel(2)); + const int number2=bs_level2.count_buildings(); + + BuildingSearch bs_level3(echo); + bs_level3.add_condition(new SpecificBuildingType(IntBuildingType::FOOD_BUILDING)); + bs_level3.add_condition(new BuildingLevel(3)); + const int number3=bs_level3.count_buildings(); + + const int score = + number1*strategy.level_1_inn_units_can_feed + + number2*strategy.level_2_inn_units_can_feed + + number3*strategy.level_3_inn_units_can_feed; + + ///(by default), A level 1 Inn can handle 8 units, a level 2 can handle 12 and a level 3 can handle 16 + if((total_workers+total_explorers+total_warriors)>=score) + { + placement_queue.push_back(RegularInn); + } + + //Place for starving recovery inns + if(starving_recovery) + { + int total_starving = stat->needFoodNoInns; + int required_inns = total_starving / strategy.starving_recovery_phase_unfed_per_new_inn; + if(starving_recovery_inns < required_inns) + { + starving_recovery_inns += 1; + placement_queue.push_back(StarvingRecoveryInn); + } + } +} + + +void NewNicowar::queue_swarms(Echo& echo) +{ + BuildingSearch bs(echo); + bs.add_condition(new SpecificBuildingType(IntBuildingType::SWARM_BUILDING)); + bs.add_condition(new NotUnderConstruction); + const int swarm_count = bs.count_buildings() + buildings_under_construction_per_type[RegularSwarm]; + const int total_unit = echo.player->team->stats.getLatestStat()->totalUnit; + int demand=0; + if(growth_phase) + { + demand = std::min(strategy.growth_phase_maximum_swarms, 1 + total_unit/strategy.growth_phase_units_per_swarm); + } + else + { + demand = (total_unit/strategy.non_growth_phase_units_per_swarm); + } + + if(demand > swarm_count) + { + placement_queue.push_back(RegularSwarm); + } +} + + +void NewNicowar::queue_racetracks(Echo& echo) +{ + BuildingSearch bs_finished(echo); + bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::WALKSPEED_BUILDING)); + bs_finished.add_condition(new NotUnderConstruction); + + BuildingSearch bs_upgrading(echo); + bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::WALKSPEED_BUILDING)); + bs_upgrading.add_condition(new BeingUpgraded); + + const int racetrack_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularRacetrack]; + //const int total_unit = echo.player->team->stats.getLatestStat()->totalUnit; + int demand=0; + if(skilled_work_phase) + { + demand=strategy.skilled_work_phase_number_of_racetracks; + } + + if(demand > racetrack_count) + { + placement_queue.push_back(RegularRacetrack); + } +} + + +void NewNicowar::queue_swimmingpools(Echo& echo) +{ + BuildingSearch bs_finished(echo); + bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::SWIMSPEED_BUILDING)); + bs_finished.add_condition(new NotUnderConstruction); + + BuildingSearch bs_upgrading(echo); + bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::SWIMSPEED_BUILDING)); + bs_upgrading.add_condition(new BeingUpgraded); + + const int swimmingpool_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularSwimmingpool]; + //const int total_unit = echo.player->team->stats.getLatestStat()->totalUnit; + int demand=0; + if(skilled_work_phase) + { + demand=strategy.skilled_work_phase_number_of_swimmingpools; + } + + if(demand > swimmingpool_count) + { + placement_queue.push_back(RegularSwimmingpool); + } +} + + +void NewNicowar::queue_schools(Echo& echo) +{ + BuildingSearch bs_finished(echo); + bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + bs_finished.add_condition(new NotUnderConstruction); + + BuildingSearch bs_upgrading(echo); + bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + bs_upgrading.add_condition(new BeingUpgraded); + + const int school_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularSchool]; + //const int total_unit = echo.player->team->stats.getLatestStat()->totalUnit; + int demand=0; + if(skilled_work_phase) + { + demand=strategy.skilled_work_phase_number_of_schools; + } + + if(demand > school_count) + { + placement_queue.push_back(RegularSchool); + } +} + + +void NewNicowar::queue_barracks(Echo& echo) +{ + BuildingSearch bs_finished(echo); + bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::ATTACK_BUILDING)); + bs_finished.add_condition(new NotUnderConstruction); + + BuildingSearch bs_upgrading(echo); + bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::ATTACK_BUILDING)); + bs_upgrading.add_condition(new BeingUpgraded); + + const int barracks_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularBarracks]; + + int demand=0; + if(war_preperation) + { + demand=strategy.war_preparation_phase_number_of_barracks; + ///This only kicks in right at the start, so that it doesn't build barracks when it doesn't need to + demand = std::min(demand, echo.player->team->stats.getLatestStat()->isFree[WARRIOR] / AI_NICOWAR_BARRACKS_FREE_WARRIOR_DIVISOR); + } + + if(demand > barracks_count) + { + placement_queue.push_back(RegularBarracks); + } +} + + +void NewNicowar::queue_hospitals(Echo& echo) +{ + BuildingSearch bs_finished(echo); + bs_finished.add_condition(new SpecificBuildingType(IntBuildingType::HEAL_BUILDING)); + bs_finished.add_condition(new NotUnderConstruction); + + BuildingSearch bs_upgrading(echo); + bs_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::HEAL_BUILDING)); + bs_upgrading.add_condition(new BeingUpgraded); + + const int hospital_count=bs_finished.count_buildings() + bs_upgrading.count_buildings() + buildings_under_construction_per_type[RegularHospital]; + const int total_warrior = echo.player->team->stats.getLatestStat()->numberUnitPerType[WARRIOR]; + + int demand=0; + if(echo.player->team->stats.getLatestStat()->needHeal > 0) + demand += strategy.base_number_of_hospitals; + if(war_preperation || war) + { + demand+=total_warrior/strategy.war_preperation_phase_warriors_per_hospital; + } + + if(demand > hospital_count) + { + placement_queue.push_back(RegularHospital); + } +} + + + +void NewNicowar::order_buildings(Echo& echo) +{ + while(!placement_queue.empty()) + { + BuildingPlacement b=placement_queue.front(); + placement_queue.erase(placement_queue.begin()); + construction_queue.push_back(b); + buildings_under_construction_per_type[int(b)]+=1; + } + ///Increase the maximum number of buildings under construction when starving recovery is active + int maximum_under_construction = strategy.base_number_of_construction_sites; + if(starving_recovery) + maximum_under_construction += strategy.starving_recovery_phase_number_of_extra_construction_sites; + + while(!construction_queue.empty() && buildings_under_construction < maximum_under_construction) + { + int id=-1; + BuildingPlacement b=construction_queue.front(); + construction_queue.erase(construction_queue.begin()); + if(b==RegularInn) + { + id=order_regular_inn(echo); + } + if(b==StarvingRecoveryInn) + { + id=order_regular_inn(echo); + ManagementOrder* mo_completion_message=new SendMessage("finished starving recovery inn"); + mo_completion_message->add_condition(new EitherCondition( + new ParticularBuilding(new NotUnderConstruction, id), + new BuildingDestroyed(id))); + echo.add_management_order(mo_completion_message); + } + if(b==RegularSwarm) + { + id=order_regular_swarm(echo); + } + if(b==RegularRacetrack) + { + id=order_regular_racetrack(echo); + } + if(b==RegularSwimmingpool) + { + id=order_regular_swimmingpool(echo); + } + if(b==RegularSchool) + { + id=order_regular_school(echo); + } + if(b==RegularBarracks) + { + id=order_regular_barracks(echo); + } + if(b==RegularHospital) + { + id=order_regular_hospital(echo); + } + + ///This code keeps track of the number of buildings that are under construction at any one point + buildings_under_construction+=1; + ManagementOrder* mo_completion_message=new SendMessage("building completed "+std::to_string(int(b))); + mo_completion_message->add_condition(new EitherCondition( + new ParticularBuilding(new NotUnderConstruction, id), + new BuildingDestroyed(id))); + echo.add_management_order(mo_completion_message); + if(b == RegularInn || b==RegularSwarm) + { + ManagementOrder* mo_construction_completion_message=new SendMessage("update clearing zone1 "+std::to_string(int(id))); + mo_construction_completion_message->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_construction_completion_message); + } + else + { + ManagementOrder* mo_construction_completion_message=new SendMessage("update clearing zone2 "+std::to_string(int(id))); + mo_construction_completion_message->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_construction_completion_message); + } + } +} + + +int NewNicowar::order_regular_inn(Echo& echo) +{ + //The main order for the inn + BuildingOrder* bo = new BuildingOrder(IntBuildingType::FOOD_BUILDING, AI_NICOWAR_INN_ORDER_WORKERS); + + //Constraints arround the location of wheat + AIEcho::Gradients::GradientInfo gi_wheat; + gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); + //You want to be close to wheat + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, AI_NICOWAR_INN_WHEAT_MIN_DIST)); + //You can't be farther than 10 units from wheat + bo->add_constraint(new AIEcho::Construction::MaximumDistance(gi_wheat, AI_NICOWAR_INN_WHEAT_MAX_DIST)); + + //Constraints about the distance to water. + AIEcho::Gradients::GradientInfo gi_water; + gi_water.add_source(new AIEcho::Gradients::Entities::Water); + //You dont want to be too close to water, so that farm can develop between it and water + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, AI_NICOWAR_INN_WATER_MIN_DIST)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_INN_BUILDING_PREF)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_NICOWAR_INN_CONSTRUCTION_MIN)); + + ///Add constraints for all enemy teams to keep distance + AIEcho::Gradients::GradientInfo gi_enemy; + for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) + { + gi_enemy.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(*i, false)); + } + bo->add_constraint(new AIEcho::Construction::MaximizedDistance(gi_enemy, AI_NICOWAR_INN_ENEMY_MAX_DIST)); + + if(echo.is_fruit_on_map()) + { + //Constraints arround the location of fruit + AIEcho::Gradients::GradientInfo gi_fruit; + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); + gi_fruit.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); + //You want to be reasnobly close to fruit, closer if possible + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_fruit, AI_NICOWAR_INN_FRUIT_PREF)); + } + + //Add the building order to the list of orders + unsigned int id=echo.add_building_order(bo); + + //Change the number of workers assigned when the building is finished + ManagementOrder* mo_completion=new SendMessage(FormatableString("update inn %0").arg(id)); + mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_completion); + + ManagementOrder* mo_tracker=new AddRessourceTracker(AI_NICOWAR_RESSOURCE_TRACKER_DEPTH, CORN, id); + mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_tracker); + + return id; +} + + +int NewNicowar::order_regular_swarm(Echo& echo) +{ + //The main order for the swarm + BuildingOrder* bo = new BuildingOrder(IntBuildingType::SWARM_BUILDING, AI_NICOWAR_SWARM_ORDER_WORKERS); + + //Constraints arround the location of wheat + AIEcho::Gradients::GradientInfo gi_wheat; + gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); + //You want to be close to wheat + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, AI_NICOWAR_SWARM_WHEAT_PREF)); + + //Constraints about the distance to water. + AIEcho::Gradients::GradientInfo gi_water; + gi_water.add_source(new AIEcho::Gradients::Entities::Water); + //You dont want to be too close to water, so that farm can develop between it and water + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, AI_NICOWAR_SWARM_WATER_MIN_DIST)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_SWARM_BUILDING_PREF)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_NICOWAR_SWARM_CONSTRUCTION_MIN)); + + //Add the building order to the list of orders + unsigned int id=echo.add_building_order(bo); + + //Change the number of workers assigned when the building is finished + ManagementOrder* mo_completion=new SendMessage(FormatableString("update swarm %0").arg(id)); + mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_completion); + + ManagementOrder* mo_tracker=new AddRessourceTracker(AI_NICOWAR_RESSOURCE_TRACKER_DEPTH, CORN, id); + mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_tracker); + + return id; +} + + +int NewNicowar::order_regular_racetrack(Echo& echo) +{ + //The main order for the racetrack + BuildingOrder* bo = new BuildingOrder(IntBuildingType::WALKSPEED_BUILDING, AI_NICOWAR_RACETRACK_ORDER_WORKERS); + + //Constraints arround the location of wood + AIEcho::Gradients::GradientInfo gi_wood; + gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); + //You want to be close to wood + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, AI_NICOWAR_RACETRACK_WOOD_PREF)); + + //Constraints about the distance to water. + AIEcho::Gradients::GradientInfo gi_water; + gi_water.add_source(new AIEcho::Gradients::Entities::Water); + //You dont want to be too close to water. allows farms to develop + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, AI_NICOWAR_RACETRACK_WATER_MIN_DIST)); + + //Constraints arround the location of stone + AIEcho::Gradients::GradientInfo gi_stone; + gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); + //You want to be close to stone + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_stone, AI_NICOWAR_RACETRACK_STONE_PREF)); + //But not to close, so you have room to upgrade + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_stone, AI_NICOWAR_RACETRACK_STONE_MIN)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_RACETRACK_BUILDING_PREF)); + + //Constraints arround water. Can't be too close to sand. + AIEcho::Gradients::GradientInfo gi_sand; + gi_sand.add_source(new AIEcho::Gradients::Entities::Sand); + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_sand, AI_NICOWAR_RACETRACK_SAND_MIN)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_NICOWAR_RACETRACK_CONSTRUCTION_MIN)); + + //Add the building order to the list of orders + int id = echo.add_building_order(bo); + + return id; +} + + +int NewNicowar::order_regular_swimmingpool(Echo& echo) +{ + //The main order for the swimmingpool + BuildingOrder* bo = new BuildingOrder(IntBuildingType::SWIMSPEED_BUILDING, AI_NICOWAR_SWIMMINGPOOL_ORDER_WORKERS); + + //Constraints arround the location of wood + AIEcho::Gradients::GradientInfo gi_wood; + gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); + //You want to be close to wood + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, AI_NICOWAR_SWIMMINGPOOL_WOOD_PREF)); + + //Constraints about the distance to water. + AIEcho::Gradients::GradientInfo gi_water; + gi_water.add_source(new AIEcho::Gradients::Entities::Water); + //You dont want to be too close to water. allows farms to develop + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, AI_NICOWAR_SWIMMINGPOOL_WATER_MIN_DIST)); + + //Constraints arround the location of wheat + AIEcho::Gradients::GradientInfo gi_wheat; + gi_wheat.add_source(new AIEcho::Gradients::Entities::Ressource(CORN)); + //You want to be close to wheat + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wheat, AI_NICOWAR_SWIMMINGPOOL_WHEAT_PREF)); + + //Constraints arround the location of stone + AIEcho::Gradients::GradientInfo gi_stone; + gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); + //You don't want to be too close, so you have room to upgrade + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_stone, AI_NICOWAR_SWIMMINGPOOL_STONE_MIN)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You want to be close to other buildings, but wheat is more important + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_SWIMMINGPOOL_BUILDING_PREF)); + + //Constraints arround water. Can't be too close to sand. + AIEcho::Gradients::GradientInfo gi_sand; + gi_sand.add_source(new AIEcho::Gradients::Entities::Sand); + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_sand, AI_NICOWAR_SWIMMINGPOOL_SAND_MIN)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_NICOWAR_SWIMMINGPOOL_CONSTRUCTION_MIN)); + + //Add the building order to the list of orders + int id = echo.add_building_order(bo); + + return id; +} + + +int NewNicowar::order_regular_school(Echo& echo) +{ + //The main order for the school + BuildingOrder* bo = new BuildingOrder(IntBuildingType::SCIENCE_BUILDING, AI_NICOWAR_SCHOOL_ORDER_WORKERS); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You want to be close to other buildings + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_SCHOOL_BUILDING_PREF)); + + //Constraints about the distance to water. + AIEcho::Gradients::GradientInfo gi_water; + gi_water.add_source(new AIEcho::Gradients::Entities::Water); + //You dont want to be too close to water. allows farms to develop + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, AI_NICOWAR_SCHOOL_WATER_MIN_DIST)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_NICOWAR_SCHOOL_CONSTRUCTION_MIN)); + + //Constraints arround the enemy + AIEcho::Gradients::GradientInfo gi_enemy; + for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) + { + gi_enemy.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(*i, false)); + } +// gi_enemy.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + bo->add_constraint(new AIEcho::Construction::MaximizedDistance(gi_enemy, AI_NICOWAR_SCHOOL_ENEMY_MAX_DIST)); + + //Add the building order to the list of orders + int id = echo.add_building_order(bo); + + return id; +} + + +int NewNicowar::order_regular_barracks(Echo& echo) +{ + //The main order for the barracks + BuildingOrder* bo = new BuildingOrder(IntBuildingType::ATTACK_BUILDING, AI_NICOWAR_BARRACKS_ORDER_WORKERS); + + //Constraints about the distance to water. + AIEcho::Gradients::GradientInfo gi_water; + gi_water.add_source(new AIEcho::Gradients::Entities::Water); + //You dont want to be too close to water. allows farms to develop + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, AI_NICOWAR_BARRACKS_WATER_MIN_DIST)); + + //Constraints arround the location of stone + AIEcho::Gradients::GradientInfo gi_stone; + gi_stone.add_source(new AIEcho::Gradients::Entities::Ressource(STONE)); + //You want to be close to stone + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_stone, AI_NICOWAR_BARRACKS_STONE_PREF)); + + //Constraints arround the location of wood + AIEcho::Gradients::GradientInfo gi_wood; + gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); + //You want to be close to wood + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, AI_NICOWAR_BARRACKS_WOOD_PREF)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You want to be close to other buildings + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_BARRACKS_BUILDING_PREF)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_NICOWAR_BARRACKS_CONSTRUCTION_MIN)); + + //Add the building order to the list of orders + int id = echo.add_building_order(bo); + + return id; +} + + +int NewNicowar::order_regular_hospital(Echo& echo) +{ + //The main order for the hospital + BuildingOrder* bo = new BuildingOrder(IntBuildingType::HEAL_BUILDING, AI_NICOWAR_HOSPITAL_ORDER_WORKERS); + + //Constraints arround the location of wood + AIEcho::Gradients::GradientInfo gi_wood; + gi_wood.add_source(new AIEcho::Gradients::Entities::Ressource(WOOD)); + //You want to be close to wood + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_wood, AI_NICOWAR_HOSPITAL_WOOD_PREF)); + + //Constraints about the distance to water. + AIEcho::Gradients::GradientInfo gi_water; + gi_water.add_source(new AIEcho::Gradients::Entities::Water); + //You dont want to be too close to water. allows farms to develop + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_water, AI_NICOWAR_HOSPITAL_WATER_MIN_DIST)); + + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + gi_building.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You want to be close to other buildings + bo->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_HOSPITAL_BUILDING_PREF)); + + AIEcho::Gradients::GradientInfo gi_building_construction; + gi_building_construction.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, true)); + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::AnyRessource); + if(!can_swim) + gi_building_construction.add_obstacle(new AIEcho::Gradients::Entities::Water); + //You don't want to be too close + bo->add_constraint(new AIEcho::Construction::MinimumDistance(gi_building_construction, AI_NICOWAR_HOSPITAL_CONSTRUCTION_MIN)); + + //Add the building order to the list of orders + int id = echo.add_building_order(bo); + + return id; + +} + + +void NewNicowar::manage_buildings(Echo& echo) +{ + BuildingSearch bs(echo); + bs.add_condition(new NotUnderConstruction); + for(building_search_iterator i = bs.begin(); i!=bs.end(); ++i) + { + if(echo.get_building_register().get_type(*i)==IntBuildingType::SWARM_BUILDING) + { + manage_swarm(echo, *i); + } + if(echo.get_building_register().get_type(*i)==IntBuildingType::FOOD_BUILDING) + { + manage_inn(echo, *i); + } + } +} + + +void NewNicowar::manage_inn(Echo& echo, int id) +{ + int level=echo.get_building_register().get_level(id); + int assigned=echo.get_building_register().get_assigned(id); + + //Do nothing if the ressource_tracker order hasn't been processed yet + if(! echo.get_ressource_tracker(id)) + return; + int total_ressource_level = echo.get_ressource_tracker(id)->get_total_level(); + + int to_assign = 0; + if(level==1 && total_ressource_level>(strategy.level_1_inn_low_wheat_trigger_ammount*AI_NICOWAR_RESSOURCE_TRACKER_DEPTH)) + to_assign=strategy.level_1_inn_units_assigned_normal_wheat; + else if(level==1 && total_ressource_level<=(strategy.level_1_inn_low_wheat_trigger_ammount*AI_NICOWAR_RESSOURCE_TRACKER_DEPTH)) + to_assign=strategy.level_1_inn_units_assigned_low_wheat; + + if(level==2 && total_ressource_level>(strategy.level_2_inn_low_wheat_trigger_ammount*AI_NICOWAR_RESSOURCE_TRACKER_DEPTH)) + to_assign=strategy.level_2_inn_units_assigned_normal_wheat; + else if(level==2 && total_ressource_level<=(strategy.level_2_inn_low_wheat_trigger_ammount*AI_NICOWAR_RESSOURCE_TRACKER_DEPTH)) + to_assign=strategy.level_2_inn_units_assigned_low_wheat; + + if(level==3 && total_ressource_level>(strategy.level_3_inn_low_wheat_trigger_ammount*AI_NICOWAR_RESSOURCE_TRACKER_DEPTH)) + to_assign=strategy.level_3_inn_units_assigned_normal_wheat; + else if(level==3 && total_ressource_level<=(strategy.level_3_inn_low_wheat_trigger_ammount*AI_NICOWAR_RESSOURCE_TRACKER_DEPTH)) + to_assign=strategy.level_3_inn_units_assigned_low_wheat; + + ///The number of units assigned to an Inn depends entirely on its level + if(to_assign != assigned) + { + ManagementOrder* mo_assign=new AssignWorkers(to_assign, id); + echo.add_management_order(mo_assign); + } +} + + +void NewNicowar::manage_swarm(Echo& echo, int id) +{ + //Get some statistics + TeamStat* stat=echo.player->team->stats.getLatestStat(); + int total_explorers=stat->numberUnitPerType[EXPLORER]; + if(stat->totalUnit == 0) + return; + int total_starving_percent = stat->needFoodCritical * 100 / stat->totalUnit; + int total_hungry_percent = stat->needFood * 100 / stat->totalUnit; + + int assigned=echo.get_building_register().get_assigned(id); + int to_assign=0; + + //Do nothing if the ressource_tracker order hasn't been processed yet + if(! echo.get_ressource_tracker(id)) + return; + int total_ressource_level = echo.get_ressource_tracker(id)->get_total_level(); + + int worker_ratio=0; + int explorer_ratio=0; + int warrior_ratio=0; + + + to_assign=strategy.base_swarm_units_assigned; + + ///Double units when ressource level is low + if(total_ressource_level <= (strategy.base_swarm_low_wheat_trigger_ammount * AI_NICOWAR_RESSOURCE_TRACKER_DEPTH)) + to_assign*=2; + + ///Half units if world is hungry + if((total_starving_percent + total_hungry_percent) > strategy.base_swarm_hungry_reduce_trigger_percent) + to_assign/=2; + + ///No units when the world is starving + if(starving_recovery) + to_assign=0; + + + ///The ratio of workers during the growth phase is different, due to the fact + ///that most explorers are made during the growth phase + if(growth_phase) + { + worker_ratio=strategy.growth_phase_swarm_worker_ratio; + + } + else + { + if(no_workers_phase) + worker_ratio=0; + else + worker_ratio=strategy.non_growth_phase_swarm_worker_ratio; + } + + //Base needed explorers never exceed 1/10 of population + int needed_explorers=std::min(strategy.base_number_of_explorers, stat->totalUnit/AI_NICOWAR_EXPLORER_POP_DIVISOR+AI_NICOWAR_EXPLORER_MIN); + if(fruit_phase) + needed_explorers+=strategy.fruit_phase_extra_number_of_explorers; + if(defend_explorers) + needed_explorers+=(stat->totalUnit * strategy.defense_explorer_population_percent) / 100; + if(explorer_attack_preperation_phase) + needed_explorers+=strategy.offense_explorer_number; + + if(total_explorers +#include "Utilities.h" +#include "Game.h" +#include "Unit.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + + +void NewNicowar::update_farming(Echo& echo) +{ + //Farming wheat and wood in areas near water + AddArea* mo_farming=new AddArea(ForbiddenArea); + RemoveArea* mo_non_farming=new RemoveArea(ForbiddenArea); + AIEcho::Gradients::GradientInfo gi_water; + gi_water.add_source(new Entities::Water); + Gradient& water_gradient=echo.get_gradient_manager().get_gradient(gi_water); + + MapInfo mi(echo); + for(int x=0; xadd_location(x, y); + } + else if(!farm_spot && mi.is_forbidden_area(x, y)) + { + mo_non_farming->add_location(x, y); + } + } + } + } + echo.add_management_order(mo_farming); + echo.add_management_order(mo_non_farming); +} + + +void NewNicowar::update_fruit_flags(AIEcho::Echo& echo) +{ + if(fruit_phase && !exploration_on_fruit) + { + //Constraints arround nearby settlement + AIEcho::Gradients::GradientInfo gi_building; + gi_building.add_source(new AIEcho::Gradients::Entities::AnyTeamBuilding(echo.player->team->teamNumber, false)); + + + //The main order for the exploration flag on cherry + BuildingOrder* bo_cherry = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, AI_NICOWAR_FRUIT_FLAG_WORKERS); + //You want the closest fruit to your settlement possible + bo_cherry->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_FRUIT_FLAG_BUILDING_PREF)); + //Constraint arround the location of fruit + AIEcho::Gradients::GradientInfo gi_cherry; + gi_cherry.add_source(new AIEcho::Gradients::Entities::Ressource(CHERRY)); + //You want to be ontop of the cherry trees + bo_cherry->add_constraint(new AIEcho::Construction::MaximumDistance(gi_cherry, AI_NICOWAR_FRUIT_FLAG_ON_FRUIT_DIST)); + //Add the building order to the list of orders + unsigned int id_cherry=echo.add_building_order(bo_cherry); + + ManagementOrder* mo_completion_cherry=new ChangeFlagSize(AI_NICOWAR_FRUIT_FLAG_SIZE, id_cherry); + echo.add_management_order(mo_completion_cherry); + + + + //The main order for the exploration flag in orange + BuildingOrder* bo_orange = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, AI_NICOWAR_FRUIT_FLAG_WORKERS); + //You want the closest fruit to your settlement possible + bo_orange->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_FRUIT_FLAG_BUILDING_PREF)); + //Constraints arround the location of fruit + AIEcho::Gradients::GradientInfo gi_orange; + gi_orange.add_source(new AIEcho::Gradients::Entities::Ressource(ORANGE)); + //You want to be ontop of the orange trees + bo_orange->add_constraint(new AIEcho::Construction::MaximumDistance(gi_orange, AI_NICOWAR_FRUIT_FLAG_ON_FRUIT_DIST)); + unsigned int id_orange=echo.add_building_order(bo_orange); + + ManagementOrder* mo_completion_orange=new ChangeFlagSize(AI_NICOWAR_FRUIT_FLAG_SIZE, id_orange); + echo.add_management_order(mo_completion_orange); + + //The main order for the exploration flag on prunes + BuildingOrder* bo_prune = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, AI_NICOWAR_FRUIT_FLAG_WORKERS); + //You want the closest fruit to your settlement possible + bo_prune->add_constraint(new AIEcho::Construction::MinimizedDistance(gi_building, AI_NICOWAR_FRUIT_FLAG_BUILDING_PREF)); + AIEcho::Gradients::GradientInfo gi_prune; + gi_prune.add_source(new AIEcho::Gradients::Entities::Ressource(PRUNE)); + //You want to be ontop of the prune trees + bo_prune->add_constraint(new AIEcho::Construction::MaximumDistance(gi_prune, AI_NICOWAR_FRUIT_FLAG_ON_FRUIT_DIST)); + //Add the building order to the list of orders + unsigned int id_prune=echo.add_building_order(bo_prune); + + ManagementOrder* mo_completion_prune=new ChangeFlagSize(AI_NICOWAR_FRUIT_FLAG_SIZE, id_prune); + echo.add_management_order(mo_completion_prune); + + + + exploration_on_fruit=true; + } + update_fruit_alliances(echo); +} + + +void NewNicowar::update_fruit_alliances(AIEcho::Echo& echo) +{ + bool activated=fruit_phase; + + for(enemy_team_iterator i(echo); i!=enemy_team_iterator(); ++i) + { + ManagementOrder* mo_alliance=new ChangeAlliances(*i, indeterminate, indeterminate, indeterminate, activated, indeterminate); + echo.add_management_order(mo_alliance); + } +} + diff --git a/src/ai/nicowar/Flags.cpp b/src/ai/nicowar/Flags.cpp new file mode 100644 index 000000000..4ea153002 --- /dev/null +++ b/src/ai/nicowar/Flags.cpp @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "AINicowar.h" +#include "GlobalContainer.h" +#include "FormatableString.h" +#include +#include "Utilities.h" +#include "Game.h" +#include "Unit.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + + +void NewNicowar::compute_defense_flag_positioning(AIEcho::Echo& echo) +{ + //This algorithm works by finding all units and buildings under attack, and creating a potential + //field by adding 1 to all squares within range of the units or buildings under attack. The result + //will be that the highest square will have the largest number of buildings or units that need + //defending within range. A flag is put onto the highest square, and the same concept is repeated, + //except that all under-attack units or buildings that are within range of a placed defense flag + //are ignored. + + //This algorithm does that, except optimized. A list is maintained to keep track of squares + //that have a value other than 0 as these are the only ones we want to place a flag on, and + //when a defense flag position is chosen, all units or buildings within range of the flag + //have all squares within their range -1, effectivly doing the same as recalculating all + //squares excluding those units now covered by a defense flag + MapInfo mi(echo); + const int w = mi.get_width(); + const int h = mi.get_height(); + const int RADIUS = AI_NICOWAR_DEFENSE_FLAG_RADIUS; + + Uint16* counts = new Uint16[w * h]; + Uint16* buildingGID = new Uint16[w * h]; + Uint16* unitGID = new Uint16[w * h]; + memset(counts, 0, sizeof(Uint16) * w * h); + memset(buildingGID, NOGBID, sizeof(Uint16) * w * h); + memset(unitGID, NOGUID, sizeof(Uint16) * w * h); + std::list locations; + + //For every unit thats under attack, increment in the squares surrounding it. + //Use the 'locations' list to keep track of non-zero squares + for(int i=0; iteam->myUnits[i]; + if(unit && unit->underAttackTimer && unit->movement != Unit::MOV_ATTACKING_TARGET && unit->typeNum != EXPLORER && unitGID[(unit->posX+w)%w * h + (unit->posY+h)%h] == NOGUID) + { + unitGID[(unit->posX+w)%w * h + (unit->posY+h)%h] = unit->gid; + modify_points(counts, w, h, (unit->posX+w)%w, (unit->posY+h)%h, RADIUS, 1, locations); + } + } + for(int i=0; iteam->myBuildings[i]; + if(building && building->underAttackTimer && buildingGID[building->posX * h + building->posY] == NOGBID) + { + int nx = (building->posX - building->type->decLeft + w) %w; + int ny = (building->posY - building->type->decTop + h) %h; + buildingGID[building->posX * h + building->posY] = building->gid; + modify_points(counts, w, h, nx, ny, RADIUS, 1, locations); + } + } + + ///Choose the highest location, remove all units and buildings within a flags radius of that location, + ///and add that location to the list + std::vector flagLocations; + std::vector enemyUnits; + while(!locations.empty()) + { + //Find the square with the highest value, a flag is put here + int max = 0; + int maxPos = 0; + for(std::list::iterator i = locations.begin(); i!=locations.end(); ++i) + { + int pos = *i; + int n = counts[pos]; + if(n > max) + { + maxPos = pos; + max = n; + } + } + + // Inserting twice the same flag is a bug and may lead to an + // infinite loop. The most probable cause is an insufficient + // margin in the loop on all units and buildings below. + for (std::vector::const_iterator i = flagLocations.begin(); + i != flagLocations.end(); + ++i) + assert (*i != maxPos); + flagLocations.push_back(maxPos); + + int max_x = maxPos / h; + int max_y = maxPos % h; + + //test(echo, counts, w, h, squareProtected, locations); + + //For all units and buildings that are under attack and within the radius of the flag, + //decrement the values surrounding them. At the same time, count the number of enemy + //warriors in this zone + int enemy_count = 0; + // We need to loop over an area slightly bigger than RADIUS + // because buildings are taken into account in an offset + // location + for(int px = -RADIUS-AI_NICOWAR_DEFENSE_BUILDING_OFFSET_MARGIN; px <= RADIUS+AI_NICOWAR_DEFENSE_BUILDING_OFFSET_MARGIN; ++px) + { + int nx = (max_x + px + w)%w; + for(int py = -RADIUS-AI_NICOWAR_DEFENSE_BUILDING_OFFSET_MARGIN; py<=RADIUS+AI_NICOWAR_DEFENSE_BUILDING_OFFSET_MARGIN; ++py) + { + int ny = (max_y + py + h)%h; + if(unitGID[nx * h + ny] != NOGUID) + { + Unit* unit = echo.player->team->myUnits[Unit::GIDtoID(unitGID[nx * h + ny])]; + modify_points(counts, w, h, (unit->posX+w)%w, (unit->posY+h)%h, RADIUS, -1, locations); + unitGID[nx * h + ny] = NOGUID; + } + if(buildingGID[nx * h + ny] != NOGBID) + { + Building* building = echo.player->team->myBuildings[Building::GIDtoID(buildingGID[nx * h + ny])]; + int nx2 = (building->posX - building->type->decLeft + w) %w; + int ny2 = (building->posY - building->type->decTop + h) %h; + modify_points(counts, w, h, nx2, ny2, RADIUS, -1, locations); + buildingGID[nx * h + ny] = NOGBID; + } + + // Take enemy units into account only if they are + // within RADIUS of the flag (remember that we loop + // over a bigger area). + if ((px >= -RADIUS) && (px <= RADIUS) && (py >= -RADIUS) && (py <= RADIUS)) { + Uint16 guid = echo.player->map->getGroundUnit(nx, ny); + if(guid != NOGUID && (1<team->enemies) + { + Unit* unit = echo.player->game->teams[Unit::GIDtoTeam(guid)]->myUnits[Unit::GIDtoID(guid)]; + if(unit->typeNum == WARRIOR) + { + enemy_count += 1; + } + } + } + } + } + enemyUnits.push_back(std::min(AI_NICOWAR_MAX_DEFENSE_FLAG_WORKERS, enemy_count)); + } + + //Remove all flags with an enemy_count of 0 + for(std::vector::iterator i=flagLocations.begin(); i!=flagLocations.end();) + { + int n = i-flagLocations.begin(); + if(enemyUnits[n] == 0) + { + i = flagLocations.erase(i); + enemyUnits.erase(enemyUnits.begin() + n); + } + else + { + ++i; + } + } + + //Take all existing defense flags, and move them to the nearest new flag position + std::vector existing_defense_flags(defense_flags); + while(!existing_defense_flags.empty()) + { + int min_dist = INT_MAX; + int min_flag = 0; + int min_pos = 0; + int min_pos_x = 0; + int min_pos_y = 0; + int min_enemy = 0; + ///Choose the flag <-> flag location combination that has the lowest distance, start from it + for(std::vector::iterator i = existing_defense_flags.begin(); i!=existing_defense_flags.end(); ++i) + { + if(echo.get_building_register().is_building_found(*i)) + { + Building* b = echo.get_building_register().get_building(*i); + for(std::vector::iterator j = flagLocations.begin(); j!=flagLocations.end(); ++j) + { + int flag_x = (*j) / h; + int flag_y = (*j) % h; + int d = echo.player->map->warpDistSquare(flag_x, flag_y, b->posX, b->posY); + if(d < min_dist) + { + min_dist = d; + min_flag = i - existing_defense_flags.begin(); + min_pos = j - flagLocations.begin(); + min_pos_x = flag_x; + min_pos_y = flag_y; + min_enemy = enemyUnits[j - flagLocations.begin()]; + } + } + } + } + //Don't move flags more than 8 squares + if(min_dist < (AI_NICOWAR_DEFENSE_FLAG_MAX_MOVE_TILES * AI_NICOWAR_DEFENSE_FLAG_MAX_MOVE_TILES)) + { + int id_flag = existing_defense_flags[min_flag]; + existing_defense_flags.erase(existing_defense_flags.begin() + min_flag); + flagLocations.erase(flagLocations.begin() + min_pos); + enemyUnits.erase(enemyUnits.begin() + min_pos); + + if(min_dist>0) + { + ManagementOrder* mo_move=new ChangeFlagPosition(min_pos_x, min_pos_y, id_flag); + echo.add_management_order(mo_move); + } + if(min_enemy != echo.get_building_register().get_assigned(id_flag)) + { + ManagementOrder* mo_assign=new AssignWorkers(min_enemy, id_flag); + echo.add_management_order(mo_assign); + } + } + else + { + break; + } + } + //If there are remaining flags, its because these flags don't have a new position + //on the map to go to, so delete them + for(std::vector::iterator i = existing_defense_flags.begin(); i!=existing_defense_flags.end(); ++i) + { + if(echo.get_building_register().is_building_found(*i)) + { + Building* b = echo.get_building_register().get_building(*i); + int enemy_count = 0; + for(int px = -AI_NICOWAR_DEFENSE_REASSIGN_RADIUS; px <= AI_NICOWAR_DEFENSE_REASSIGN_RADIUS; ++px) + { + int nx = (b->posX + px + w)%w; + for(int py = -AI_NICOWAR_DEFENSE_REASSIGN_RADIUS; py<=AI_NICOWAR_DEFENSE_REASSIGN_RADIUS; ++py) + { + int ny = (b->posY + py + h)%h; + Uint16 guid = echo.player->map->getGroundUnit(nx, ny); + if(guid != NOGUID && (1<team->enemies) + { + Unit* unit = echo.player->game->teams[Unit::GIDtoTeam(guid)]->myUnits[Unit::GIDtoID(guid)]; + if(unit->typeNum == WARRIOR) + { + enemy_count += 1; + } + } + } + } + if(enemy_count == 0) + { + ManagementOrder* mo_destroyed=new DestroyBuilding(*i); + echo.add_management_order(mo_destroyed); + } + else + { + if(enemy_count != echo.get_building_register().get_assigned(*i)) + { + ManagementOrder* mo_assign=new AssignWorkers(std::min(AI_NICOWAR_MAX_DEFENSE_FLAG_WORKERS, enemy_count), *i); + echo.add_management_order(mo_assign); + } + } + } + } + + //If there are remaining positions on the map, it is because we didn't have enough existing + //flags to cover them, so create new ones + for(std::vector::iterator i = flagLocations.begin(); i!=flagLocations.end(); ++i) + { + int enemy = enemyUnits[i - flagLocations.begin()]; + int flag_x = *i / h; + int flag_y = *i % h; + + //The main order for the war flag + BuildingOrder* bo_flag = new BuildingOrder(IntBuildingType::WAR_FLAG, enemy); + bo_flag->add_constraint(new Construction::SinglePosition(flag_x, flag_y)); + unsigned int id_flag=echo.add_building_order(bo_flag); + defense_flags.push_back(id_flag); + + ManagementOrder* mo_completion=new ChangeFlagSize(AI_NICOWAR_DEFENSE_FLAG_SIZE, id_flag); + echo.add_management_order(mo_completion); + + ManagementOrder* mo_destroyed=new SendMessage("guard flag deleted " + std::to_string(id_flag)); + mo_destroyed->add_condition(new BuildingDestroyed(id_flag)); + echo.add_management_order(mo_destroyed); + } + + delete[] counts; + delete[] unitGID; + delete[] buildingGID; +} + + + +void NewNicowar::modify_points(Uint16* counts, int w, int h, int x, int y, int dist, int value, std::list& locations) +{ + for(int px = -dist; px <= dist; ++px) + { + int nx = (x + px + w)%w; + for(int py = -dist; py <= dist; ++py) + { + int ny = (y + py + h)%h; + if(px * px + py * py <= dist * dist) + { + if(value>0) + { + if(counts[nx * h + ny] == 0) + locations.push_back(nx * h + ny); + counts[nx * h + ny] += value; + } + else if(value<0) + { + counts[nx * h + ny] += value; + if(counts[nx * h + ny] == 0) + locations.remove(nx * h + ny); + } + } + } + } +} + + + +void NewNicowar::compute_explorer_flag_attack_positioning(AIEcho::Echo& echo) +{ + //The algorithm here is interesting. Bassically, an enemy unit is selected. Every enemy unit within 4 squares of this unit + //is counted as part of the larger group, and every unit 4 squares from those and so on, as long as it doesn't go past + //6 squares from the average. Flags are put on the average x and y of largest groups + MapInfo mi(echo); + const int w = mi.get_width(); + const int h = mi.get_height(); + + std::vector > groups; + + if(explorer_attack_phase && target!=-1) + { + Unit** units = new Unit*[Unit::MAX_COUNT]; + Unit* first = NULL; + for(int i=0; igame->teams[target]->myUnits[i]; + if(unit && mi.is_discovered(unit->posX, unit->posY) && unit->typeNum != EXPLORER && unit->activity != Unit::ACT_UPGRADING) + { + if(!first) + first = unit; + units[i] = unit; + } + else + { + units[i] = NULL; + } + } + + while(true) + { + int group_x = 0; + int group_y = 0; + int group_size = 0; + + std::queue proccess; + std::queue xposs; + std::queue yposs; + for(int i=0; iposX; + group_y += units[i]->posY; + proccess.push(units[i]); + xposs.push(units[i]->posX); + yposs.push(units[i]->posY); + units[i] = NULL; + group_size+=1; + break; + } + } + + if(group_size == 0) + break; + + while(!proccess.empty()) + { + Unit* top = proccess.front(); + int ix = xposs.front(); + int iy = yposs.front(); + proccess.pop(); + xposs.pop(); + yposs.pop(); + for(int dx = -AI_NICOWAR_EXPLORER_GROUP_SEARCH_RADIUS; dx<=AI_NICOWAR_EXPLORER_GROUP_SEARCH_RADIUS; ++dx) + { + int nx = (top->posX + dx + w) % w; + for(int dy = -AI_NICOWAR_EXPLORER_GROUP_SEARCH_RADIUS; dy<=AI_NICOWAR_EXPLORER_GROUP_SEARCH_RADIUS; ++dy) + { + int ny = (top->posY + dy + h) % h; + if(echo.player->map->warpDistSquare(group_x / group_size, group_y / group_size, nx, ny) < (AI_NICOWAR_EXPLORER_GROUP_COHESION_TILES * AI_NICOWAR_EXPLORER_GROUP_COHESION_TILES)) + { + Uint16 guid = echo.player->map->getGroundUnit(nx, ny); + if(guid != NOGUID && Unit::GIDtoTeam(guid) == target) + { + int id = Unit::GIDtoID(guid); + if(units[id]) + { + group_x += ix + dx; + group_y += iy + dy; + proccess.push(units[id]); + xposs.push(ix + dx); + yposs.push(iy + dy); + units[id] = NULL; + group_size+=1; + } + } + } + } + } + } + group_x = (group_x / group_size + w)%w; + group_y = (group_y / group_size + h)%h; + + groups.push_back(std::make_tuple(group_size, group_x, group_y)); + } + } + + std::sort(groups.begin(), groups.end(), std::greater >()); + int total_attacks = strategy.offense_explorer_flag_number; + if(!explorer_attack_phase) + total_attacks = 0; + + //Go through existing flags and see if they can be moved to be on top of new groups + std::vector existing_explorer_attack_flags(explorer_attack_flags); + while(total_attacks && !existing_explorer_attack_flags.empty()) + { + int min_dist = INT_MAX; + int min_flag = 0; + int min_pos = 0; + int min_pos_x = 0; + int min_pos_y = 0; + ///Choose the flag <-> flag location combination that has the lowest distance, start from it + for(std::vector::iterator i = existing_explorer_attack_flags.begin(); i!=existing_explorer_attack_flags.end(); ++i) + { + if(echo.get_building_register().is_building_found(*i)) + { + Building* b = echo.get_building_register().get_building(*i); + for(std::vector >::iterator j = groups.begin(); j!=groups.end(); ++j) + { + int flag_x = std::get<1>(*j); + int flag_y = std::get<2>(*j); + int d = echo.player->map->warpDistSquare(flag_x, flag_y, b->posX, b->posY); + if(d < min_dist) + { + min_dist = d; + min_flag = i - existing_explorer_attack_flags.begin(); + min_pos = j - groups.begin(); + min_pos_x = flag_x; + min_pos_y = flag_y; + } + } + } + } + + if(min_dist != INT_MAX) + { + total_attacks-=1; + int id_flag = existing_explorer_attack_flags[min_flag]; + + existing_explorer_attack_flags.erase(existing_explorer_attack_flags.begin() + min_flag); + groups.erase(groups.begin() + min_pos); + + if(min_dist != 0) + { + ManagementOrder* mo_move=new ChangeFlagPosition(min_pos_x, min_pos_y, id_flag); + echo.add_management_order(mo_move); + } + } + else + { + break; + } + } + + //If there are remaining flags, its because these flags don't have a new position + //on the map to go to, so delete them + for(std::vector::iterator i = existing_explorer_attack_flags.begin(); i!=existing_explorer_attack_flags.end(); ++i) + { + if(echo.get_building_register().is_building_found(*i)) + { + ManagementOrder* mo_destroyed=new DestroyBuilding(*i); + echo.add_management_order(mo_destroyed); + } + } + + while(total_attacks && !groups.empty()) + { + std::tuple groupInfo = *groups.begin(); + groups.erase(groups.begin()); + total_attacks -= 1; + + BuildingOrder* bo_flag = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, strategy.offense_explorer_flag_assigned); + bo_flag->add_constraint(new Construction::SinglePosition(std::get<1>(groupInfo), std::get<2>(groupInfo))); + unsigned int id_flag=echo.add_building_order(bo_flag); + + ManagementOrder* mo_completion=new ChangeFlagSize(AI_NICOWAR_EXPLORER_ATTACK_FLAG_SIZE, id_flag); + echo.add_management_order(mo_completion); + + // [POSSIBLE BUG / preserved] Skill levels run 0..3; passing 4 here + // either locks the flag entirely or is silently capped at 3 by the + // engine. See bugs_surfaced_during_magic_number_audit.md M8. + ManagementOrder* mo_level=new ChangeFlagMinimumLevel(AI_NICOWAR_EXPLORER_ATTACK_MIN_LEVEL, id_flag); + echo.add_management_order(mo_level); + + explorer_attack_flags.push_back(id_flag); + + ManagementOrder* mo_destroyed=new SendMessage("explorer attack flag deleted " + std::to_string(id_flag)); + mo_destroyed->add_condition(new BuildingDestroyed(id_flag)); + echo.add_management_order(mo_destroyed); + } +} + + diff --git a/src/ai/nicowar/Lifecycle.cpp b/src/ai/nicowar/Lifecycle.cpp new file mode 100644 index 000000000..3602f846c --- /dev/null +++ b/src/ai/nicowar/Lifecycle.cpp @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "AINicowar.h" +#include "GlobalContainer.h" +#include "FormatableString.h" +#include +#include "Utilities.h" +#include "Game.h" +#include "Unit.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + + +NewNicowar::NewNicowar() +{ + timer=0; + buildings_under_construction=0; + growth_phase=false; + skilled_work_phase=0; + upgrading_phase_1=false; + upgrading_phase_2=false; + war_preperation=false; + war=false; + fruit_phase=false; + starving_recovery=false; + no_workers_phase=false; + can_swim=false; + defend_explorers=false; + explorer_attack_preperation_phase=false; + explorer_attack_phase=false; + starving_recovery_inns = 0; + exploration_on_fruit=false; + for(int n=0; nreadEnterSection("NewNicowar"); + timer=stream->readUint32("timer"); + if(versionMinor >= AI_NICOWAR_SAVE_FORMAT_V59) + { + if(versionMinor >= AI_NICOWAR_SAVE_FORMAT_V60) + { + std::string strategyName = stream->readText("strategy_name"); + NicowarStrategyLoader loader; + strategy = loader.getParticularStrategy(strategyName); + } + else + { + NicowarStrategyLoader loader; + strategy = loader.getParticularStrategy("default"); + } + growth_phase=stream->readUint8("growth_phase"); + skilled_work_phase=stream->readUint8("skilled_work_phase"); + upgrading_phase_1=stream->readUint8("upgrading_phase_1"); + upgrading_phase_2=stream->readUint8("upgrading_phase_2"); + war_preperation=stream->readUint8("war_preperation"); + war=stream->readUint8("war"); + fruit_phase=stream->readUint8("fruit_phase"); + starving_recovery=stream->readUint8("starving_recovery"); + no_workers_phase=stream->readUint8("no_workers_phase"); + if(versionMinor >= AI_NICOWAR_SAVE_FORMAT_V60) + can_swim=stream->readUint8("can_swim"); + + starving_recovery_inns=stream->readUint8("starving_recovery_inns"); + buildings_under_construction=stream->readUint32("buildings_under_construction"); + for(int n=0; nreadUint8(FormatableString("buildings_under_construction_per_type[%0]").arg(n).c_str()); + } + + stream->readEnterSection("placement_queue"); + size_t size = stream->readUint16("size"); + for(size_t n = 0; nreadEnterSection(n); + BuildingPlacement bp = static_cast(stream->readUint8("placement")); + placement_queue.push_back(bp); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + stream->readEnterSection("construction_queue"); + size = stream->readUint16("size"); + for(size_t n = 0; nreadEnterSection(n); + BuildingPlacement bp = static_cast(stream->readUint8("placement")); + construction_queue.push_back(bp); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + target = stream->readSint8("target"); + is_digging_out = stream->readUint8("is_digging_out"); + + stream->readEnterSection("attack_flags"); + size = stream->readUint16("size"); + for(size_t n = 0; nreadEnterSection(n); + int flag = stream->readUint32("flag"); + attack_flags.push_back(flag); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + if(versionMinor >= AI_NICOWAR_SAVE_FORMAT_V66) + { + stream->readEnterSection("defense_flags"); + size = stream->readUint16("size"); + for(size_t n = 0; nreadEnterSection(n); + int flag = stream->readUint32("flag"); + defense_flags.push_back(flag); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + stream->readEnterSection("explorer_attack_flags"); + size = stream->readUint16("size"); + for(size_t n = 0; nreadEnterSection(n); + int flag = stream->readUint32("flag"); + explorer_attack_flags.push_back(flag); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + } + + exploration_on_fruit=stream->readUint8("exploration_on_fruit"); + stream->readLeaveSection(); + } + return true; +} + + +void NewNicowar::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("NewNicowar"); + stream->writeUint32(timer, "timer"); + stream->writeText(strategy.getStrategyName(), "strategy_name"); + stream->writeUint8(growth_phase, "growth_phase"); + stream->writeUint8(skilled_work_phase, "skilled_work_phase"); + stream->writeUint8(upgrading_phase_1, "upgrading_phase_1"); + stream->writeUint8(upgrading_phase_2, "upgrading_phase_2"); + stream->writeUint8(war_preperation, "war_preperation"); + stream->writeUint8(war, "war"); + stream->writeUint8(fruit_phase, "fruit_phase"); + stream->writeUint8(starving_recovery, "starving_recovery"); + stream->writeUint8(no_workers_phase, "no_workers_phase"); + stream->writeUint8(can_swim, "can_swim"); + stream->writeUint8(starving_recovery_inns, "starving_recovery_inns"); + stream->writeUint32(buildings_under_construction, "buildings_under_construction"); + for(int n=0; nwriteUint8(buildings_under_construction_per_type[n], FormatableString("buildings_under_construction_per_type[%0]").arg(n).c_str()); + } + + stream->writeEnterSection("placement_queue"); + stream->writeUint16(placement_queue.size(), "size"); + size_t n = 0; + for(std::list::iterator i = placement_queue.begin(); i!=placement_queue.end(); ++i) + { + stream->writeEnterSection(n); + stream->writeUint8(static_cast(*i), "placement"); + stream->writeLeaveSection(); + n+=1; + } + stream->writeLeaveSection(); + + stream->writeEnterSection("construction_queue"); + stream->writeUint16(construction_queue.size(), "size"); + n = 0; + for(std::list::iterator i = construction_queue.begin(); i!=construction_queue.end(); ++i) + { + stream->writeEnterSection(n); + stream->writeUint8(static_cast(*i), "placement"); + stream->writeLeaveSection(); + n+=1; + } + stream->writeLeaveSection(); + + stream->writeUint8(target, "target"); + stream->writeUint8(is_digging_out, "is_digging_out"); + + stream->writeEnterSection("attack_flags"); + stream->writeUint16(attack_flags.size(), "size"); + for(n = 0; nwriteEnterSection(n); + stream->writeUint32(attack_flags[n], "flag"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->writeEnterSection("defense_flags"); + stream->writeUint16(defense_flags.size(), "size"); + for(n = 0; nwriteEnterSection(n); + stream->writeUint32(defense_flags[n], "flag"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->writeEnterSection("explorer_attack_flags"); + stream->writeUint16(explorer_attack_flags.size(), "size"); + for(n = 0; nwriteEnterSection(n); + stream->writeUint32(explorer_attack_flags[n], "flag"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->writeUint8(exploration_on_fruit, "exploration_on_fruit"); + stream->writeLeaveSection(); +} + + +void NewNicowar::tick(Echo& echo) +{ + timer++; + if(timer==AI_NICOWAR_INIT_TICK) + { + selectStrategy(); + check_phases(echo); + initialize(echo); + } + if(timer%AI_NICOWAR_DECISION_CYCLE_TICKS == AI_NICOWAR_QUEUE_BUILDINGS_PHASE) + { + queue_buildings(echo); + } + if(timer%AI_NICOWAR_DECISION_CYCLE_TICKS == AI_NICOWAR_CHECK_PHASES_PHASE) + { + check_phases(echo); + } + if(timer%AI_NICOWAR_DECISION_CYCLE_TICKS == AI_NICOWAR_MANAGE_BUILDINGS_PHASE) + { + manage_buildings(echo); + } + if(timer%AI_NICOWAR_DECISION_CYCLE_TICKS == AI_NICOWAR_UPGRADE_PHASE) + { + upgrade_buildings(echo); + } + if(timer%AI_NICOWAR_DECISION_CYCLE_TICKS == AI_NICOWAR_CONTROL_ATTACKS_PHASE) + { + control_attacks(echo); + } + if(timer%AI_NICOWAR_DECISION_CYCLE_TICKS == AI_NICOWAR_DEFENSE_FLAG_PHASE) + { + compute_defense_flag_positioning(echo); + } + if(timer%AI_NICOWAR_FARMING_INTERVAL_TICKS == 0) + { + update_farming(echo); + } + if(timer%AI_NICOWAR_FARMING_INTERVAL_TICKS == AI_NICOWAR_FRUIT_PHASE_OFFSET) + { + update_fruit_flags(echo); + } + if(timer%AI_NICOWAR_EXPLORER_ATTACK_INTERVAL_TICKS == AI_NICOWAR_EXPLORER_ATTACK_OFFSET) + { + compute_explorer_flag_attack_positioning(echo); + } + + order_buildings(echo); +} + + +void NewNicowar::handle_message(Echo& echo, const std::string& message) +{ + if(message.substr(0,19) == "building completed ") + { + int placement_num=std::stoi(message.substr(19, message.size()-1)); + buildings_under_construction-=1; + buildings_under_construction_per_type[placement_num]-=1; + } + if(message.substr(0,22) == "update clearing zone1 ") + { + MapInfo mi(echo); + int id=std::stoi(message.substr(22, message.size()-1)); + Building* b = echo.get_building_register().get_building(id); + AddArea* mo_clearing=new AddArea(ClearingArea); + RemoveArea* mo_remove_clearing=new RemoveArea(ClearingArea); + mo_remove_clearing->add_condition(new BuildingDestroyed(id)); + for(int nx=-1; nxtype->width+1; ++nx) + { + for(int ny=-1; nytype->height+1; ++ny) + { + if(!mi.is_forbidden_area(b->posX+nx, b->posY+ny)) + { + mo_clearing->add_location(b->posX+nx, b->posY+ny); + mo_remove_clearing->add_location(b->posX+nx, b->posY+ny); + } + } + } + echo.add_management_order(mo_clearing); + echo.add_management_order(mo_remove_clearing); + } + if(message.substr(0,22) == "update clearing zone2 ") + { + MapInfo mi(echo); + int id=std::stoi(message.substr(22, message.size()-1)); + Building* b = echo.get_building_register().get_building(id); + AddArea* mo_clearing=new AddArea(ClearingArea); + RemoveArea* mo_remove_clearing=new RemoveArea(ClearingArea); + mo_remove_clearing->add_condition(new BuildingDestroyed(id)); + for(int nx=-1; nxtype->width+1; ++nx) + { + for(int ny=-1; nytype->height+1; ++ny) + { + mo_clearing->add_location(b->posX+nx, b->posY+ny); + mo_remove_clearing->add_location(b->posX+nx, b->posY+ny); + } + } + echo.add_management_order(mo_clearing); + echo.add_management_order(mo_remove_clearing); + } + if(message.substr(0,13) == "update swarm ") + { + int id=std::stoi(message.substr(13, message.size()-1)); + manage_swarm(echo, id); + } + if(message.substr(0,11) == "update inn ") + { + int id=std::stoi(message.substr(11, message.size()-1)); + manage_inn(echo, id); + } + if(message.substr(0,16) == "attack finished ") + { + int id=std::stoi(message.substr(16, message.size()-1)); + attack_flags.erase(std::find(attack_flags.begin(), attack_flags.end(), id)); + } + if(message.substr(0,19) == "guard flag deleted ") + { + int id=std::stoi(message.substr(19, message.size()-1)); + defense_flags.erase(std::find(defense_flags.begin(), defense_flags.end(), id)); + } + if(message.substr(0,29) == "explorer attack flag deleted ") + { + int id=std::stoi(message.substr(29, message.size()-1)); + explorer_attack_flags.erase(std::find(explorer_attack_flags.begin(), explorer_attack_flags.end(), id)); + } + if(message == "finished digging out") + { + is_digging_out=false; + } + if(message == "finished starving recovery inn") + { + starving_recovery_inns-=1; + } +} + + + +void NewNicowar::selectStrategy() +{ + NicowarStrategyLoader loader; + strategy = loader.chooseRandomStrategy(); + //strategy = loader.getParticularStrategy("default"); +} + + + +void NewNicowar::initialize(Echo& echo) +{ + BuildingSearch bs(echo); + for(building_search_iterator i = bs.begin(); i!=bs.end(); ++i) + { + if(echo.get_building_register().get_type(*i)==IntBuildingType::SWARM_BUILDING) + { + ManagementOrder* mo_tracker=new AddRessourceTracker(AI_NICOWAR_RESSOURCE_TRACKER_DEPTH, CORN, *i); + mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, *i)); + echo.add_management_order(mo_tracker); + } + if(echo.get_building_register().get_type(*i)==IntBuildingType::FOOD_BUILDING) + { + ManagementOrder* mo_tracker=new AddRessourceTracker(AI_NICOWAR_RESSOURCE_TRACKER_DEPTH, CORN, *i); + mo_tracker->add_condition(new ParticularBuilding(new NotUnderConstruction, *i)); + echo.add_management_order(mo_tracker); + } + } + + manage_buildings(echo); +} + diff --git a/src/ai/nicowar/Phases.cpp b/src/ai/nicowar/Phases.cpp new file mode 100644 index 000000000..597ea084f --- /dev/null +++ b/src/ai/nicowar/Phases.cpp @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "AINicowar.h" +#include "GlobalContainer.h" +#include "FormatableString.h" +#include +#include "Utilities.h" +#include "Game.h" +#include "Unit.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + + +void NewNicowar::check_phases(Echo& echo) +{ + TeamStat* stat=echo.player->team->stats.getLatestStat(); + + ///Qualifications for the growth phase: + ///1) Less than strategy.growth_phase_unit_max units + if(stat->totalUnittotalUnit>=strategy.skilled_work_phase_unit_min) + { + skilled_work_phase=true; + } + else + { + skilled_work_phase=false; + } + + ///Qualifications for the upgrading phase 1: + ///1) Atleast strategy.upgrading_phase_1_school_min schools + ///2) Atleast strategy.upgrading_phase_1_unit_min units + ///3) Atleast strategy.upgrading_phase_1_trained_worker_min of them are trained for upgrading to level 2 + BuildingSearch schools(echo); + schools.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools.add_condition(new NotUnderConstruction); + const int school_counts=schools.count_buildings(); + const int trained_count=stat->upgradeState[BUILD][1] + stat->upgradeState[BUILD][2] + stat->upgradeState[BUILD][3]; + + if(stat->totalUnit>=strategy.upgrading_phase_1_unit_min && school_counts>=strategy.upgrading_phase_1_school_min && trained_count>strategy.upgrading_phase_1_trained_worker_min) + { + upgrading_phase_1=true; + } + else + { + upgrading_phase_1=false; + } + + ///Qualifications for the upgrading phase 2: + ///1) Atleast strategy.upgrading_phase_2_school_min level 2 or level 3 schools + ///2) Atleast strategy.upgrading_phase_2_unit_min units + ///3) Atleast strategy.upgrading_phase_2_trained_worker_min of them are trained for upgrading to level 3 + BuildingSearch schools_2(echo); + schools_2.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools_2.add_condition(new NotUnderConstruction); + schools_2.add_condition(new BuildingLevel(2)); + BuildingSearch schools_3(echo); + schools_3.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools_3.add_condition(new NotUnderConstruction); + schools_3.add_condition(new BuildingLevel(3)); + const int school_counts_2=schools_2.count_buildings() + schools_3.count_buildings(); + const int trained_count_2=echo.get_team_stats().upgradeState[BUILD][2] + stat->upgradeState[BUILD][3]; + + if(stat->totalUnit>=strategy.upgrading_phase_2_unit_min && school_counts_2>=strategy.upgrading_phase_2_school_min && trained_count_2>strategy.upgrading_phase_2_trained_worker_min) + { + upgrading_phase_2=true; + } + else + { + upgrading_phase_2=false; + } + + ///Qualifications for the war preperation phase: + ///1) Atleast strategy.war_preperation_phase_unit_min units + ///2) Less than strategy.war_preperation_phase_barracks_max barracks OR + ///3) Less than strategy.war_preperation_phase_trained_warrior_max trained warriors + BuildingSearch barracks(echo); + barracks.add_condition(new SpecificBuildingType(IntBuildingType::ATTACK_BUILDING)); + int barracks_count=barracks.count_buildings(); + + int warrior_count=0; + for(int i=strategy.minimum_warrior_level_for_trained; i<=AI_NICOWAR_MAX_UPGRADE_LEVEL; ++i) + { + warrior_count += stat->upgradeState[ATTACK_SPEED][i]; + } + + if(stat->totalUnit>=strategy.war_preperation_phase_unit_min && (warrior_count < strategy.war_preperation_phase_trained_warrior_max || barracks_count= strategy.war_phase_trained_warrior_min) + { + war=true; + } + else + { + war=false; + } + + ///Qualifcations for the fruit phase: + ///Atleast strategy.fruit_phase_unit_min units, and fruits on the map + if(echo.is_fruit_on_map() && stat->totalUnit >= strategy.fruit_phase_unit_min) + { + fruit_phase=true; + } + else + { + fruit_phase=false; + } + + ///Qualifications for the starving recovery phase: + ///1) More than strategy.starvation_recovery_phase_starving_no_inn_min_percent % units hungry but not able to eat + ///2) Atleast one unit (because of devision by 0) + if(stat->totalUnit > AI_NICOWAR_STARVATION_MIN_UNITS) + { + int total_starving_percent = stat->needFoodNoInns * 100 / stat->totalUnit; + if(total_starving_percent >= strategy.starvation_recovery_phase_starving_no_inn_min_percent) + { + starving_recovery=true; + } + else + { + starving_recovery=false; + } + } + else + { + starving_recovery=false; + } + + ///Qualifications for the no worker phase: + ///1) More than strategy.no_workers_phase_free_worker_minimum_percen % workers free + ///2) No needed jobs + ///3) Atleast one worker (because of devision by 0) + if(stat->numberUnitPerType[WORKER] > 0) + { + const int workers_free = (stat->isFree[WORKER] - stat->totalNeeded) * 100 / stat->numberUnitPerType[WORKER]; + if(workers_free > strategy.no_workers_phase_free_worker_minimum_percent) + { + no_workers_phase=true; + } + else + { + no_workers_phase=false; + } + } + else + { + no_workers_phase=false; + } + + ///Qualifications for the can swim phase: + ///1) Atleast one worker that can swim + int total_can_swim=0; + for(int i=0; iupgradeStatePerType[WORKER][SWIM][i]; + if(total_can_swim>0) + { + can_swim=true; + } + else + { + can_swim=false; + } + + ///Qualifications for the defend explorers phase + ///1) Prestige, not counting this teams prestige, is more than 0, indicating that ground attacking explorers are being created + if(echo.player->game->totalPrestige - echo.player->team->prestige > 0) + { + defend_explorers=true; + } + else + { + defend_explorers=false; + } + + ///Qualifications for the explorer attack preperation phase + //1) This teams prestige greater than 0 + if(echo.player->team->prestige > 0) + { + explorer_attack_preperation_phase = true; + } + else + { + explorer_attack_preperation_phase = false; + } + + ///Qualifications for the explorer attack phase + //1) The minimum number of trained explorers is greater than offense_explorer_minimum + if(stat->upgradeStatePerType[EXPLORER][MAGIC_ATTACK_GROUND][AI_NICOWAR_EXPLORER_MAX_LEVEL] > strategy.offense_explorer_minimum) + { + explorer_attack_phase = true; + } + else + { + explorer_attack_phase = false; + } +} + diff --git a/src/ai/nicowar/Strategy.cpp b/src/ai/nicowar/Strategy.cpp new file mode 100644 index 000000000..3a5a78f23 --- /dev/null +++ b/src/ai/nicowar/Strategy.cpp @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "AINicowar.h" +#include "GlobalContainer.h" +#include "FormatableString.h" +#include +#include "Utilities.h" +#include "Game.h" +#include "Unit.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + +NicowarStrategy::NicowarStrategy() +{ + +} + + + +void NicowarStrategy::loadFromConfigFile(const ConfigBlock *configBlock) +{ + configBlock->load(growth_phase_unit_max, "growth_phase_unit_max"); + configBlock->load(skilled_work_phase_unit_min, "skilled_work_phase_unit_min"); + configBlock->load(upgrading_phase_1_school_min, "upgrading_phase_1_school_min"); + configBlock->load(upgrading_phase_1_unit_min, "upgrading_phase_1_unit_min"); + configBlock->load(upgrading_phase_1_trained_worker_min, "upgrading_phase_1_trained_worker_min"); + configBlock->load(upgrading_phase_2_school_min, "upgrading_phase_2_school_min"); + configBlock->load(upgrading_phase_2_unit_min, "upgrading_phase_2_unit_min"); + configBlock->load(upgrading_phase_2_trained_worker_min, "upgrading_phase_2_trained_worker_min"); + configBlock->load(minimum_warrior_level_for_trained, "minimum_warrior_level_for_trained"); + configBlock->load(war_preperation_phase_unit_min, "war_preperation_phase_unit_min"); + configBlock->load(war_preperation_phase_barracks_max, "war_preperation_phase_barracks_max"); + configBlock->load(war_preperation_phase_trained_warrior_max, "war_preperation_phase_trained_warrior_max"); + configBlock->load(war_phase_trained_warrior_min, "war_phase_trained_warrior_min"); + configBlock->load(fruit_phase_unit_min, "fruit_phase_unit_min"); + configBlock->load(starvation_recovery_phase_starving_no_inn_min_percent, "starvation_recovery_phase_starving_no_inn_min_percent"); + configBlock->load(starving_recovery_phase_unfed_per_new_inn, "starving_recovery_phase_unfed_per_new_inn"); + configBlock->load(no_workers_phase_free_worker_minimum_percent, "no_workers_phase_free_worker_minimum_percent"); + configBlock->load(level_1_inn_units_can_feed, "level_1_inn_units_can_feed"); + configBlock->load(level_2_inn_units_can_feed, "level_2_inn_units_can_feed"); + configBlock->load(level_3_inn_units_can_feed, "level_3_inn_units_can_feed"); + configBlock->load(growth_phase_units_per_swarm, "growth_phase_units_per_swarm"); + configBlock->load(non_growth_phase_units_per_swarm, "non_growth_phase_units_per_swarm"); + configBlock->load(growth_phase_maximum_swarms, "growth_phase_maximum_swarms"); + configBlock->load(skilled_work_phase_number_of_racetracks, "skilled_work_phase_number_of_racetracks"); + configBlock->load(skilled_work_phase_number_of_swimmingpools, "skilled_work_phase_number_of_swimmingpools"); + configBlock->load(skilled_work_phase_number_of_schools, "skilled_work_phase_number_of_schools"); + configBlock->load(war_preparation_phase_number_of_barracks, "war_preparation_phase_number_of_barracks"); + configBlock->load(base_number_of_hospitals, "base_number_of_hospitals"); + configBlock->load(war_preperation_phase_warriors_per_hospital, "war_preperation_phase_warriors_per_hospital"); + configBlock->load(base_number_of_construction_sites, "base_number_of_construction_sites"); + configBlock->load(starving_recovery_phase_number_of_extra_construction_sites, "starving_recovery_phase_number_of_extra_construction_sites"); + configBlock->load(level_1_inn_low_wheat_trigger_ammount, "level_1_inn_low_wheat_trigger_ammount"); + configBlock->load(level_2_inn_low_wheat_trigger_ammount, "level_2_inn_low_wheat_trigger_ammount"); + configBlock->load(level_3_inn_low_wheat_trigger_ammount, "level_3_inn_low_wheat_trigger_ammount"); + configBlock->load(level_1_inn_units_assigned_normal_wheat, "level_1_inn_units_assigned_normal_wheat"); + configBlock->load(level_2_inn_units_assigned_normal_wheat, "level_2_inn_units_assigned_normal_wheat"); + configBlock->load(level_3_inn_units_assigned_normal_wheat, "level_3_inn_units_assigned_normal_wheat"); + configBlock->load(level_1_inn_units_assigned_low_wheat, "level_1_inn_units_assigned_low_wheat"); + configBlock->load(level_2_inn_units_assigned_low_wheat, "level_2_inn_units_assigned_low_wheat"); + configBlock->load(level_3_inn_units_assigned_low_wheat, "level_3_inn_units_assigned_low_wheat"); + configBlock->load(base_swarm_units_assigned, "base_swarm_units_assigned"); + configBlock->load(base_swarm_low_wheat_trigger_ammount, "base_swarm_low_wheat_trigger_ammount"); + configBlock->load(base_swarm_hungry_reduce_trigger_percent, "base_swarm_hungry_reduce_trigger_percent"); + configBlock->load(growth_phase_swarm_worker_ratio, "growth_phase_swarm_worker_ratio"); + configBlock->load(non_growth_phase_swarm_worker_ratio, "non_growth_phase_swarm_worker_ratio"); + configBlock->load(base_number_of_explorers, "base_number_of_explorers"); + configBlock->load(fruit_phase_extra_number_of_explorers, "fruit_phase_extra_number_of_explorers"); + configBlock->load(base_swarm_explorer_ratio, "base_swarm_explorer_ratio"); + configBlock->load(war_preperation_swarm_warrior_ratio, "war_preperation_swarm_warrior_ratio"); + configBlock->load(defense_explorer_population_percent, "defense_explorer_population_percent"); + configBlock->load(offense_explorer_number, "offense_explorer_number"); + configBlock->load(offense_explorer_minimum, "offense_explorer_minimum"); + configBlock->load(offense_explorer_flag_number, "offense_explorer_flag_number"); + configBlock->load(offense_explorer_flag_assigned, "offense_explorer_flag_assigned"); + configBlock->load(upgrading_phase_1_inn_chance, "upgrading_phase_1_inn_chance"); + configBlock->load(upgrading_phase_1_hospital_chance, "upgrading_phase_1_hospital_chance"); + configBlock->load(upgrading_phase_1_racetrack_chance, "upgrading_phase_1_racetrack_chance"); + configBlock->load(upgrading_phase_1_swimmingpool_chance, "upgrading_phase_1_swimmingpool_chance"); + configBlock->load(upgrading_phase_1_barracks_chance, "upgrading_phase_1_barracks_chance"); + configBlock->load(upgrading_phase_1_school_chance, "upgrading_phase_1_school_chance"); + configBlock->load(upgrading_phase_1_tower_chance, "upgrading_phase_1_tower_chance"); + configBlock->load(upgrading_phase_2_inn_chance, "upgrading_phase_2_inn_chance"); + configBlock->load(upgrading_phase_2_hospital_chance, "upgrading_phase_2_hospital_chance"); + configBlock->load(upgrading_phase_2_racetrack_chance, "upgrading_phase_2_racetrack_chance"); + configBlock->load(upgrading_phase_2_swimmingpool_chance, "upgrading_phase_2_swimmingpool_chance"); + configBlock->load(upgrading_phase_2_barracks_chance, "upgrading_phase_2_barracks_chance"); + configBlock->load(upgrading_phase_2_school_chance, "upgrading_phase_2_school_chance"); + configBlock->load(upgrading_phase_2_tower_chance, "upgrading_phase_2_tower_chance"); + configBlock->load(upgrading_phase_1_units_assigned, "upgrading_phase_1_units_assigned"); + configBlock->load(upgrading_phase_2_units_assigned, "upgrading_phase_2_units_assigned"); + configBlock->load(upgrading_phase_1_num_units, "upgrading_phase_1_num_units"); + configBlock->load(upgrading_phase_2_num_units, "upgrading_phase_2_num_units"); + configBlock->load(war_phase_war_flag_units_assigned, "war_phase_war_flag_units_assigned"); + configBlock->load(war_phase_num_attack_flags, "war_phase_num_attack_flags"); + +} + + +std::string NicowarStrategy::getStrategyName() +{ + return name; +} + + +void NicowarStrategy::setStrategyName(const std::string& name) +{ + this->name=name; +} + + +NicowarStrategyLoader::NicowarStrategyLoader() +{ + ConfigVector::load("data/nicowar.default.txt", true); + ConfigVector::load("data/nicowar.txt"); +} + + + +NicowarStrategy NicowarStrategyLoader::chooseRandomStrategy() +{ + int chosen = syncRand() % entries.size(); + entries[chosen]->setStrategyName(entriesToName[chosen]); + return *entries[chosen]; +} + + + +NicowarStrategy NicowarStrategyLoader::getParticularStrategy(const std::string& name) +{ + entries[nameToEntries[name]]->setStrategyName(name); + return *entries[nameToEntries[name]]; +} + + diff --git a/src/ai/nicowar/Upgrade.cpp b/src/ai/nicowar/Upgrade.cpp new file mode 100644 index 000000000..61263ce94 --- /dev/null +++ b/src/ai/nicowar/Upgrade.cpp @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Bradley Arsenault + +#include "AINicowar.h" +#include "GlobalContainer.h" +#include "FormatableString.h" +#include +#include "Utilities.h" +#include "Game.h" +#include "Unit.h" + +using namespace AIEcho; +using namespace AIEcho::Gradients; +using namespace AIEcho::Construction; +using namespace AIEcho::Management; +using namespace AIEcho::Conditions; +using namespace AIEcho::SearchTools; +using namespace boost::logic; + + + +int NewNicowar::choose_building_upgrade_type_level1(Echo& echo) +{ + BuildingSearch schools(echo); + schools.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools.add_condition(new BeingUpgradedTo(2)); + const int school_counts=schools.count_buildings(); + + ///Schools are only upgraded one at a time + int school_chance = strategy.upgrading_phase_1_school_chance; + if(school_counts>0) + school_chance=0; + + return choose_building_upgrade_type(echo, 1, strategy.upgrading_phase_1_inn_chance, strategy.upgrading_phase_1_hospital_chance, strategy.upgrading_phase_1_racetrack_chance, strategy.upgrading_phase_1_swimmingpool_chance, strategy.upgrading_phase_1_barracks_chance, school_chance, strategy.upgrading_phase_1_tower_chance); +} + + + +int NewNicowar::choose_building_upgrade_type_level2(Echo& echo) +{ + BuildingSearch schools_upgrading(echo); + schools_upgrading.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools_upgrading.add_condition(new BeingUpgradedTo(3)); + const int school_counts_upgrading=schools_upgrading.count_buildings(); + + BuildingSearch schools_lvl2(echo); + schools_lvl2.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools_lvl2.add_condition(new BuildingLevel(2)); + schools_lvl2.add_condition(new NotUnderConstruction); + const int school_counts_level2=schools_lvl2.count_buildings(); + + BuildingSearch schools_lvl3(echo); + schools_lvl3.add_condition(new SpecificBuildingType(IntBuildingType::SCIENCE_BUILDING)); + schools_lvl3.add_condition(new BuildingLevel(3)); + schools_lvl3.add_condition(new NotUnderConstruction); + const int school_counts_level3=schools_lvl3.count_buildings(); + + ///Schools are only upgraded one at a time + int school_chance = strategy.upgrading_phase_2_school_chance; + if(school_counts_upgrading>0 || (school_counts_level2 + school_counts_level3) buildings; + buildings.reserve(100); + if(building_count[IntBuildingType::FOOD_BUILDING] > 0) + { + for(int n=0; n 0) + { + for(int n=0; n 0) + { + for(int n=0; n 0) + { + for(int n=0; n 0) + { + for(int n=0; n 0) + { + for(int n=0; n 0) + { + for(int n=0; n buildings; + std::copy(bs.begin(), bs.end(), std::back_insert_iterator >(buildings)); + int random=syncRand() % buildings.size(); + int id=buildings[random]; + + return id; +} + + +void NewNicowar::upgrade_buildings(Echo& echo) +{ + TeamStat* stat=echo.player->team->stats.getLatestStat(); + int can_upgrade_level1 = stat->upgradeState[BUILD][1] + stat->upgradeState[BUILD][2] + stat->upgradeState[BUILD][3]; + int can_upgrade_level2 = stat->upgradeState[BUILD][2] + stat->upgradeState[BUILD][3]; + + int num_to_upgrade_level1=0; + int num_to_upgrade_level2=0; + if(upgrading_phase_1) + { + //rounded up + num_to_upgrade_level1=(can_upgrade_level1 + strategy.upgrading_phase_1_num_units/2) / (strategy.upgrading_phase_1_num_units); + } + else + { + num_to_upgrade_level1=0; + } + + if(upgrading_phase_2) + { + //rounded up + num_to_upgrade_level2=(can_upgrade_level2 + strategy.upgrading_phase_2_num_units/2) / (strategy.upgrading_phase_2_num_units); + } + else + { + num_to_upgrade_level2=0; + } + + BuildingSearch bs_lvl1(echo); + bs_lvl1.add_condition(new BeingUpgradedTo(2)); + int num_upgrading_level1=bs_lvl1.count_buildings(); + + BuildingSearch bs_lvl2(echo); + bs_lvl2.add_condition(new BeingUpgradedTo(3)); + int num_upgrading_level2=bs_lvl2.count_buildings(); + + ///Level one upgrades + if(num_upgrading_level1 < num_to_upgrade_level1) + { + int building_type=choose_building_upgrade_type_level1(echo); + if(building_type!=AI_NICOWAR_NO_BUILDING_TYPE) + { + std::string type=IntBuildingType::typeFromShortNumber(building_type); + + int id=choose_building_for_upgrade(echo, building_type, 1); + + ManagementOrder* uro = new UpgradeRepair(id); + echo.add_management_order(uro); + + ManagementOrder* mo_assign=new AssignWorkers(strategy.upgrading_phase_1_units_assigned, id); + mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, id)); + echo.add_management_order(mo_assign); + + //Cause the building to be updated after its completion. Not all buildings need + //to be updated, in which case the order will simply be ignored + ManagementOrder* mo_completion=new SendMessage(FormatableString("update %0 %1").arg(type).arg(id)); + mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_completion); + } + } + + ///Level two upgrades + if(num_upgrading_level2 < num_to_upgrade_level2) + { + int building_type=choose_building_upgrade_type_level2(echo); + if(building_type!=AI_NICOWAR_NO_BUILDING_TYPE) + { + std::string type=IntBuildingType::typeFromShortNumber(building_type); + + int id=choose_building_for_upgrade(echo, building_type, 2); + ManagementOrder* uro = new UpgradeRepair(id); + echo.add_management_order(uro); + + ManagementOrder* mo_assign=new AssignWorkers(strategy.upgrading_phase_2_units_assigned, id); + mo_assign->add_condition(new ParticularBuilding(new UnderConstruction, id)); + echo.add_management_order(mo_assign); + + //Cause the building to be updated after its completion. Not all buildings need + //to be updated, in which case the order will simply be ignored + ManagementOrder* mo_completion=new SendMessage(FormatableString("update %0 %1").arg(type).arg(id)); + mo_completion->add_condition(new ParticularBuilding(new NotUnderConstruction, id)); + echo.add_management_order(mo_completion); + } + } +} + diff --git a/src/Building.h b/src/building/Building.h similarity index 63% rename from src/Building.h rename to src/building/Building.h index 8ef22a4d1..1a63e6c49 100644 --- a/src/Building.h +++ b/src/building/Building.h @@ -1,29 +1,14 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __BUILDING_H -#define __BUILDING_H +#pragma once +#include #include #include #include "BuildingUtils.h" +#include "MapInternal.h" #include "Ressource.h" #include "UnitConsts.h" @@ -36,14 +21,75 @@ namespace GAGCore class Unit; class Team; -class BuildingType; +struct BuildingType; class BuildingsTypes; class Order; +/// Sentinel for `BuildingType::prevLevel` / `nextLevel` meaning +/// "no upgrade/downgrade exists in this chain" (signed int). +/// Distinct from the wire-format `NB_BUILDING` size sentinel. +static constexpr int BUILDING_LEVEL_NONE = -1; + +/// Sentinel for "no resource type chosen yet" on signed integers +/// (e.g. `Building::neededRessource()` return, `bestResource` in +/// scoring loops). Distinct from `NO_RES_TYPE` (Uint8 0xFF) used +/// on the `Ressource` value-type field. +static constexpr int RESSOURCE_TYPE_NONE = -1; + +/// Length of the per-building "no-swim variant" / "can-swim variant" +/// pair. Every per-building gradient/lock/resource array is indexed +/// `[canSwim]` where `canSwim == 0` means the no-swim variant and +/// `canSwim == 1` means the can-swim variant. Used both as the array +/// dimension and as the loop bound in `for (canSwim=0; canSwim<...; …)`. +static constexpr int SWIM_VARIANT_COUNT = 2; + +/// Index into a `[SWIM_VARIANT_COUNT]` array selecting the variant +/// reachable by units that have the SWIM ability. Used at sites that +/// hard-pick the swim variant (e.g. swarms only emit swimming workers +/// once the can-swim path is reachable). +static constexpr int SWIM_VARIANT_CAN_SWIM = 1; + class Building : public BuildingUtils { public: static const int MAX_COUNT=1024; + + /// `lastShootStep = LAST_SHOOT_STEP_NEVER` means this turret has + /// not fired yet this game; the field is `Uint32` step counter. + static constexpr Uint32 LAST_SHOOT_STEP_NEVER = static_cast(-1); + + /// Initial value for proportion-finding loops in `neededRessource` + /// and `swarmStep`: every real proportion compares less. Same as + /// `INT32_MAX`; named for clarity at the call site. + static constexpr Sint32 MIN_PROPORTION_INIT = INT32_MAX; + + /// Wished-resources scaling factor: `wishedResources = (NUM/DEN) * + /// missing` ≈ 1.33×, so workers can be subscribed before resources + /// are actually depleted. + static constexpr int WISHED_RESOURCE_NUM = 4; + static constexpr int WISHED_RESOURCE_DEN = 3; + + /// `findGroundExit` quality scoring (per-tile bonuses): + /// - +1 when the candidate exit is next to a ressource + /// - +2 when the candidate exit is on open ground + /// Search aborts once any side reaches `EXIT_QUALITY_GOOD_ENOUGH`. + static constexpr int EXIT_QUALITY_NEAR_RESSOURCE = 1; + static constexpr int EXIT_QUALITY_OPEN_GROUND = 2; + static constexpr int EXIT_QUALITY_GOOD_ENOUGH = 4; + + /// Sanity bound on `maxUnitInside` reads from old saves; the value + /// is logically a `Uint16` so anything ≥ 65536 is corrupt data. + static constexpr Sint32 MAX_UNIT_INSIDE_LIMIT = 65536; + + /// Turret rotating-shoot sprite has this many animation frames; + /// `shootingStep` cycles `0..SHOOTING_ANIMATION_FRAMES-1`. + static constexpr Uint32 SHOOTING_ANIMATION_FRAMES = 8; + + /// Turret types are required to be `TURRET_SIZE × TURRET_SIZE` + /// tiles. Bullet-spawn math (e.g. `<<4` half-tile offsets) bakes + /// in this assumption. + static constexpr int TURRET_SIZE = 2; + ///This is the buildings basic state of existence. enum BuildingState { @@ -72,11 +118,16 @@ class Building : public BuildingUtils LS_OUT=2 }; -public: + // ─── Public methods ───────────────────────────────────────────── + Building(GAGCore::InputStream *stream, BuildingsTypes *types, Team *owner, Sint32 versionMinor); Building(int x, int y, Uint16 gid, Sint32 typeNum, Team *team, BuildingsTypes *types, Sint32 unitWorking, Sint32 unitWorkingFuture); virtual ~Building(void); void freeGradients(); + // Drop both pathfinding buffers (call after the building moves or its range changes). + void resetPathfindGradients(); + // Drop only the local-ressources buffer (call when a tile inside the footprint changes). + void resetLocalRessources(); void load(GAGCore::InputStream *stream, BuildingsTypes *types, Team *owner, Sint32 versionMinor); void save(GAGCore::OutputStream *stream); @@ -90,7 +141,6 @@ class Building : public BuildingUtils * @param array of needs that will be filled by this function */ void neededRessources(int needs[MAX_NB_RESSOURCES]); - void wishedRessources(int needs[MAX_NB_RESSOURCES]); /** * @param res The resource type * @return count of resources needed of type res. In case of higher multiplicity @@ -99,7 +149,8 @@ class Building : public BuildingUtils */ int neededRessource(int res); ///Wished ressources are any ressources that are needed, and not being carried by a unit already. - void computeWishedRessources(); + ///Fills `needs[]` with the result; pass `wishedResources` to refresh the cached member. + void computeWishedRessources(int needs[MAX_NB_RESSOURCES]); int totalWishedRessource(); ///Launches construction. Provided with the number of units that should be working during the construction, @@ -128,50 +179,28 @@ class Building : public BuildingUtils ///fires some. void updateUnitsWorking(void); - /// This function updates the units harvesting at this building. In - /// particular, it unsubscribes them when the building is being destroyed or - /// turns invisible when for example the other teams switches the view for - /// its markets. -private:void updateUnitsHarvesting(void); - ///This function is called after important events in order to update the building -public:void update(void); + void update(void); ///Sets the area around the building to be discovered, and visible by the building void setMapDiscovered(void); ///Gets the amount of ressources for each type of ressource that are needed to repair the building. -public:void getRessourceCountToRepair(int ressources[BASIC_COUNT]); + void getRessourceCountToRepair(int ressources[BASIC_COUNT]); ///Attempts to find room for a building site. If room is found, the building site is established, ///and it returns true. bool tryToBuildingSiteRoom(void); - ///This function puts hidden forbidden area around a new building site. This dispereses units so that - ///the building isn't waiting for space when there are lots of units. -private:void addForbiddenZoneToUpgradeArea(void); - ///This function removes the hidden forbidden area placed by addForbiddenToUpgradeArea - ///It must be done before any type or position state is changed. - void removeForbiddenZoneFromUpgradeArea(void); - - ///Checks if there is hard space for a building. Non hard space is any space occupied by something that - ///won't move. Units will move, so they are ignored. If there is space for the building site, then this - ///returns true. - bool isHardSpaceForBuildingSite(void); -public:bool isHardSpaceForBuildingSite(ConstructionResultState constructionResultState); - - ///Designates whether we are full inside. For Inns, takes into account how much wheat is left - ///and whether there is enough wheat for more units. -private:bool fullInside(void); - - ///This function tells the number of workers that should be working at this building. - ///If, for example, the building doesn't need any ressources, then this function will - ///return 0, because if its already full, it doesn't need any units. - int desiredNumberOfWorkers(void); + ///Checks if there is hard space for a building site under the given construction result state. + ///Non hard space is any space occupied by something that won't move. Units will move, so they + ///are ignored. If there is space for the building site, then this returns true. The no-arg + ///private overload defers to this one using the building's current `constructionResultState`. + bool isHardSpaceForBuildingSite(ConstructionResultState requestedState); ///This is called every step. The building updates the desiredMaxUnitWorking variable using ///the function desiredNumberOfWorkers -public:void step(void); + void step(void); ///This function subscribes any building that needs ressources carried to it with units. ///It is considered greedy, hiring as many units as it needs in order of its preference ///Returns true if a unit was hired @@ -193,19 +222,14 @@ public:void step(void); /// changing the state and adding it to the list of buildings to be deleted void kill(void); - /// Tells whether a particular unit can work at this building. Takes into account this buildings level, - /// the units type and level, and whether this building is a flag, because flags get a couple of special - /// rules. -private:bool canUnitWorkHere(Unit* unit); - /// This function removes the unit from the list of units working on the building. Units will remove themselves /// when they run out of food, for example. This does not handle units state, just the buildings. -public:void removeUnitFromWorking(Unit* unit); - + void removeUnitFromWorking(Unit* unit); + /// Insert into the harvesting unit, when the unit has decided to do so. /// This does not handle units state, just the buildings. void insertUnitToHarvesting(Unit* unit); - + /// This function removes the unit from the list of units harvesting from the building. Units will remove themselves /// when they run out of food, for example. This does not handle units state, just the buildings. /// It is safe to call this function even if the unit is not harvesting at the building. @@ -215,13 +239,9 @@ public:void removeUnitFromWorking(Unit* unit); /// it does not update the units state. void removeUnitFromInside(Unit* unit); - /// This function updates the ressources pointer. The variable ressources can either point to local ressources - /// or team resources, depending on the BuildingType. -private:void updateRessourcesPointer(); - /// This function is called when a Unit places a ressource into the building. -public:void addRessourceIntoBuilding(int ressourceType); - + void addRessourceIntoBuilding(int ressourceType); + /// This function is called when a Unit takes a ressource from a building, such as a market void removeRessourceFromBuilding(int ressourceType); @@ -238,24 +258,10 @@ public:void addRessourceIntoBuilding(int ressourceType); /// and provides the x and y coordinates, along with the direction the unit should be travelling /// when it leaves. bool findAirExit(int *posX, int *posY, int *dx, int *dy); -private: - /// checkstyle found this block of 26 lines being repeated 4 times. - void checkGroundExitQuality( - const int testX, - const int testY, - const int extraTestX, - const int extraTestY, - int & exitX, - int & exitY, - int & exitQuality, - int & oldQuality, - bool canSwim); + /// Returns the script level number. Construction sites are odd numbers and completed buildings /// even, from 0 to 5 -public:int getLongLevel(void); - - /// get flag from units attached to flag. - void computeFlagStatLocal(int *goingTo, int *onSpot); + int getLongLevel(void); /// Eats one wheat and one of each of the available fruit from the building. /// Return the number of different fruits in this building. If mask is non-null, @@ -272,11 +278,82 @@ public:int getLongLevel(void); bool integrity(); Uint32 checkSum(std::vector *checkSumsVector); -int verbose; -private:std::list orderQueue; + +private: + // ─── Private helper methods ───────────────────────────────────── + + /// This function updates the units harvesting at this building. In + /// particular, it unsubscribes them when the building is being destroyed or + /// turns invisible when for example the other teams switches the view for + /// its markets. + void updateUnitsHarvesting(void); + + ///This function puts hidden forbidden area around a new building site. This dispereses units so that + ///the building isn't waiting for space when there are lots of units. + void addForbiddenZoneToUpgradeArea(void); + ///This function removes the hidden forbidden area placed by addForbiddenToUpgradeArea + ///It must be done before any type or position state is changed. + void removeForbiddenZoneFromUpgradeArea(void); + ///Shared body for add/remove. `add=true` adds the zone, `add=false` removes it. + void modifyForbiddenZoneForUpgradeArea(bool add); + + ///No-arg overload: defers to the public `isHardSpaceForBuildingSite(requestedState)` using the + ///building's current `constructionResultState`. + bool isHardSpaceForBuildingSite(void); + + ///Designates whether we are full inside. For Inns, takes into account how much wheat is left + ///and whether there is enough wheat for more units. + bool fullInside(void); + + ///This function tells the number of workers that should be working at this building. + ///If, for example, the building doesn't need any ressources, then this function will + ///return 0, because if its already full, it doesn't need any units. + int desiredNumberOfWorkers(void); + + /// Tells whether a particular unit can work at this building. Takes into account this buildings level, + /// the units type and level, and whether this building is a flag, because flags get a couple of special + /// rules. + bool canUnitWorkHere(Unit* unit); + + /// Per-zonable candidate-selection helpers for subscribeForFlagingStep. + /// Each tests one unit against the per-flag-type requirements (activity, + /// level, distance reachability) and either populates *dist with the + /// distance metric used for scoring, or increments + /// unitsFailingRequirements with the rejection reason. + /// Returns true iff the unit is accepted as a candidate. + /// + /// Distance metrics differ by flag type and are NOT interchangeable: + /// - Explorer flag: squared Euclidean distance from Map::warpDistSquare, + /// compared against timeLeft^2. + /// - Worker / Warrior flag: linear gradient distance from + /// Map::buildingAvailable (range 0..~254), compared against timeLeft. + bool considerUnitForExplorerFlag(Unit* unit, int* dist); + bool considerUnitForWorkerFlag(Unit* unit, int* dist); + bool considerUnitForWarriorFlag(Unit* unit, int* dist); + + /// This function updates the ressources pointer. The variable ressources can either point to local ressources + /// or team resources, depending on the BuildingType. + void updateRessourcesPointer(); + + /// checkstyle found this block of 26 lines being repeated 4 times. + void checkGroundExitQuality( + const int testX, + const int testY, + const int extraTestX, + const int extraTestY, + int & exitX, + int & exitY, + int & exitQuality, + int & oldQuality, + bool canSwim); static std::string getBuildingName(int type); + public: + // ─── Public data ──────────────────────────────────────────────── + + int verbose; + // type Sint32 typeNum; // number in BuildingTypes ///This is the typenum from IntBuildingType @@ -288,49 +365,26 @@ private:std::list orderQueue; ConstructionResultState constructionResultState; // units - Sint32 maxUnitWorkingLocal; Sint32 maxUnitWorking; // (Uint16) -private:Sint32 maxUnitWorkingFuture; -public:Sint32 maxUnitWorkingPreferred; -private:Sint32 maxUnitWorkingPrevious; + Sint32 maxUnitWorkingPreferred; ///This is a constantly updated number that indicates the buildings desired number of units, ///say for example that the building is full, it needs no units, so this is 0 -public:Sint32 desiredMaxUnitWorking; + Sint32 desiredMaxUnitWorking; ///This is the list of units actively working on the building. std::list unitsWorking; - ///The subscribeToBringRessourcesStep and subscribeForFlagingStep operate every 32 ticks -private:Sint32 subscriptionWorkingTimer; -public:Sint32 maxUnitInside; + Sint32 maxUnitInside; ///This counts the number of units that failed the requirements for the building, but where free std::list unitsInside; ///This stores the priority of the building, 0 is normal, -1 is low, +1 is high Sint32 priority; Sint32 priorityLocal; - ///This stores the old priority, so that if the priority changes, this building will be updated in Teams -private:Sint32 oldPriority; - ///This is the list of units harvesting from the building (if it is a market for instance) -private:std::list unitsHarvesting; - -private: - // optimisation and consistency - InListState inCanFeedUnit; - Uint8 canNotConvertUnitTimer; //counts down 150 frames after the building was last unable to feed a unit - InListState inCanHealUnit; - InListState inUpgrade[NB_ABILITY]; - /// This variable indicates whether this building is already in the team call list - /// to receive units. A 1 indicates its already in the call list, and 0 indicates - /// that it is not. - Uint8 callListState; - -public: // identity Uint16 gid; // for reservation see GIDtoID() and GIDtoTeam(). Team *owner; // position Sint32 posX, posY; // (Uint16) - Sint32 posXLocal, posYLocal; // Counts down 240 frames from when a unit was attacked Uint8 underAttackTimer; @@ -338,7 +392,6 @@ private:std::list unitsHarvesting; // Flag usefull : Sint32 unitStayRange; // (Uint8) - Sint32 unitStayRangeLocal; bool clearingRessources[BASIC_COUNT]; // true if the ressource has to be cleared. bool clearingRessourcesLocal[BASIC_COUNT]; Sint32 minLevelToFlag; @@ -350,41 +403,39 @@ private:std::list unitsHarvesting; /// will be changed to point to the global ressources Team::teamRessources instead of localRessources. Sint32* ressources; Sint32 wishedResources[MAX_NB_RESSOURCES]; -private:Sint32 localRessource[MAX_NB_RESSOURCES]; // quality parameters -public:Sint32 hp; // (Uint16) + Sint32 hp; // (Uint16) // swarm building parameters Sint32 productionTimeout; -private:Sint32 totalRatio; -public:Sint32 ratio[NB_UNIT_TYPE]; + Sint32 ratio[NB_UNIT_TYPE]; Sint32 ratioLocal[NB_UNIT_TYPE]; -private:Sint32 percentUsed[NB_UNIT_TYPE]; // exchange building parameters -public:Uint32 receiveRessourceMask; + Uint32 receiveRessourceMask; Uint32 sendRessourceMask; Uint32 receiveRessourceMaskLocal; Uint32 sendRessourceMaskLocal; // turrets building parameters -private:Uint32 shootingStep; -private:Sint32 shootingCooldown; -public:Sint32 bullets; + Sint32 bullets; // A true bit meant that the corresponding team can see this building, under FOW or not. Uint32 seenByMask; - bool dirtyLocalGradient[2]; - Uint8 localGradient[2][1024]; - Uint8 *globalGradient[2]; - bool locked[2]; //True if the building is not reachable. - Uint32 lastGlobalGradientUpdateStepCounter[2]; + bool dirtyLocalGradient[SWIM_VARIANT_COUNT]; + Uint8 localGradient[SWIM_VARIANT_COUNT][LOCAL_GRID_AREA]; + Uint8 *globalGradient[SWIM_VARIANT_COUNT]; + bool locked[SWIM_VARIANT_COUNT]; //True if the building is not reachable. + Uint32 lastGlobalGradientUpdateStepCounter[SWIM_VARIANT_COUNT]; - Uint8 *localRessources[2]; - int localRessourcesCleanTime[2]; // The time since the localRessources[x] has not been updated. - int anyRessourceToClear[2]; // Which localRessources[x] gradient has any ressource. {0: unknow, 1:true, 2:false} + Uint8 *localRessources[SWIM_VARIANT_COUNT]; + int localRessourcesCleanTime[SWIM_VARIANT_COUNT]; // The time since the localRessources[x] has not been updated. + // Per-swim-variant tri-state cache of whether `localRessources[canSwim]` + // currently has any ressource. Stored value at each slot: 0 = unknown + // (not yet computed), 1 = true (has at least one), 2 = false (none). + int anyRessourceToClear[SWIM_VARIANT_COUNT]; // shooting eye-candy data, not net synchronised Uint32 lastShootStep; @@ -407,9 +458,41 @@ public:Sint32 bullets; Uint32 unitsFailingRequirements[UnitCantWorkReasonSize]; -protected: - FILE *logFile; -}; +private: + // ─── Private data ─────────────────────────────────────────────── -#endif + // pending player orders (consumed by step()) + std::list orderQueue; + // units: scratch counters for subscription / priority diff + Sint32 maxUnitWorkingFuture; + Sint32 maxUnitWorkingPrevious; + ///The subscribeToBringRessourcesStep and subscribeForFlagingStep operate every 32 ticks + Sint32 subscriptionWorkingTimer; + ///This stores the old priority, so that if the priority changes, this building will be updated in Teams + Sint32 oldPriority; + + ///This is the list of units harvesting from the building (if it is a market for instance) + std::list unitsHarvesting; + + // optimisation and consistency + InListState inCanFeedUnit; + Uint8 canNotConvertUnitTimer; //counts down 150 frames after the building was last unable to feed a unit + InListState inCanHealUnit; + InListState inUpgrade[NB_ABILITY]; + /// This variable indicates whether this building is already in the team call list + /// to receive units. A 1 indicates its already in the call list, and 0 indicates + /// that it is not. + Uint8 callListState; + + // Building specific (private): + Sint32 localRessource[MAX_NB_RESSOURCES]; + + // swarm building parameters (private): + Sint32 totalRatio; + Sint32 percentUsed[NB_UNIT_TYPE]; + + // turrets building parameters (private): + Uint32 shootingStep; + Sint32 shootingCooldown; +}; diff --git a/src/building/BuildingUtils.cpp b/src/building/BuildingUtils.cpp new file mode 100644 index 000000000..ad601940e --- /dev/null +++ b/src/building/BuildingUtils.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "BuildingUtils.h" +#include "Team.h" + + +Sint32 BuildingUtils::GIDtoID(Uint16 gid) +{ + assert(gid < BuildingUtils::MAX_COUNT * Team::MAX_COUNT); + return gid % BuildingUtils::MAX_COUNT; +} + +Sint32 BuildingUtils::GIDtoTeam(Uint16 gid) +{ + assert(gid < BuildingUtils::MAX_COUNT * Team::MAX_COUNT); + return gid / BuildingUtils::MAX_COUNT; +} + +Uint16 BuildingUtils::GIDfrom(Sint32 id, Sint32 team) +{ + assert(id < BuildingUtils::MAX_COUNT); + assert(team < Team::MAX_COUNT); + return id + team * BuildingUtils::MAX_COUNT; +} + diff --git a/src/building/BuildingUtils.h b/src/building/BuildingUtils.h new file mode 100644 index 000000000..7bf74ece7 --- /dev/null +++ b/src/building/BuildingUtils.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include + +class BuildingUtils +{ + public: + static Sint32 GIDtoID(Uint16 gid); + static Sint32 GIDtoTeam(Uint16 gid); + static Uint16 GIDfrom(Sint32 id, Sint32 team); + + static const int MAX_COUNT = 1024; +}; + + diff --git a/src/building/Construction.cpp b/src/building/Construction.cpp new file mode 100644 index 000000000..1337cae05 --- /dev/null +++ b/src/building/Construction.cpp @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include +#include + +#include "Building.h" +#include "BuildingType.h" +#include "EngineTiming.h" +#include "FixedPoint.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Team.h" +#include "Unit.h" +#include "Utilities.h" +#include "Order.h" +#include "Bullet.h" +#include "Integrity.h" + +bool Building::isRessourceFull(void) +{ + for (int i=0; imultiplierRessource[i]<=type->maxRessource[i]) + return false; + } + return true; +} + +int Building::neededRessource(void) +{ + Sint32 minProportion = MIN_PROPORTION_INIT; + int minType = RESSOURCE_TYPE_NONE; + int deci=syncRand()%MAX_RESSOURCES; + for (int ib=0; ibmaxRessource[i]; + if (maxr) + { + Sint32 proportion=(ressources[i]<maxRessource[ri] - ressources[ri])) / (type->multiplierRessource[ri] * WISHED_RESOURCE_DEN); + for (std::list::iterator ui = unitsWorking.begin(); ui != unitsWorking.end(); ++ui) + if ((*ui)->destinationPurpose >= 0) + { + assert((*ui)->destinationPurpose < MAX_NB_RESSOURCES); + needs[(*ui)->destinationPurpose]--; + } +} + +int Building::neededRessource(int r) +{ + assert(r >= 0); + int need = type->maxRessource[r] - ressources[r] + 1 - type->multiplierRessource[r]; + return std::max(need,0); +} + + +int Building::totalWishedRessource() +{ + int sum=0; + for (int ri = 0; ri < MAX_NB_RESSOURCES; ri++) + sum += wishedResources[ri]; + return sum; +} + + + +void Building::launchConstruction(Sint32 unitWorking, Sint32 unitWorkingFuture) +{ + if ((buildingState==ALIVE) && (!type->isBuildingSite)) + { + if (hphpMax) + { + if ((type->prevLevel==BUILDING_LEVEL_NONE) || !isHardSpaceForBuildingSite(REPAIR)) + return; + constructionResultState=REPAIR; + } + else + { + if ((type->nextLevel==BUILDING_LEVEL_NONE) || !isHardSpaceForBuildingSite(UPGRADE)) + return; + constructionResultState=UPGRADE; + } + + owner->removeFromAbilitiesLists(this); + + // We remove all units who are going to the building: + // Notice that the algotithm is not fast but clean. + std::list unitsToRemove; + for (std::list::iterator it=unitsInside.begin(); it!=unitsInside.end(); ++it) + { + Unit *u=*it; + assert(u); + int d=u->displacement; + if ((d!=Unit::DIS_INSIDE)&&(d!=Unit::DIS_ENTERING_BUILDING)&&(d!=Unit::DIS_EXITING_BUILDING)) + { + u->standardRandomActivity(); + unitsToRemove.push_front(u); + } + } + + for (std::list::iterator it=unitsToRemove.begin(); it!=unitsToRemove.end(); ++it) + { + Unit *u=*it; + assert(u); + unitsInside.remove(u); + } + + maxUnitWorkingPrevious = maxUnitWorking; + buildingState=WAITING_FOR_CONSTRUCTION; + maxUnitWorking=0; + maxUnitInside=0; + updateCallLists(); + updateUnitsWorking(); // To remove all units working. + updateUnitsHarvesting(); // To remove all units working. + //following reassigns units to work on upgrade, certain buildings will + //glitch if units are not unassigned and then reassigned like this + maxUnitWorking = unitWorking; + maxUnitWorkingPreferred = maxUnitWorking; + maxUnitWorkingFuture = unitWorkingFuture; + updateConstructionState(); // To switch to a real building site, if all units have been freed from building. + } +} + +void Building::cancelConstruction(Sint32 unitWorking) +{ + Sint32 recoverTypeNum=typeNum; + BuildingType *recoverType=type; + + if (type->isBuildingSite) + { + assert(buildingState==ALIVE); + int targetLevelTypeNum=BUILDING_LEVEL_NONE; + + if (constructionResultState==UPGRADE) + targetLevelTypeNum=type->prevLevel; + else if (constructionResultState==REPAIR) + targetLevelTypeNum=type->nextLevel; + else + assert(false); + + if (targetLevelTypeNum!=BUILDING_LEVEL_NONE) + { + recoverTypeNum=targetLevelTypeNum; + recoverType=globalContainer->buildingsTypes.get(targetLevelTypeNum); + } + else + assert(false); + } + else if (buildingState==WAITING_FOR_CONSTRUCTION_ROOM) + { + if(constructionResultState == UPGRADE) + removeForbiddenZoneFromUpgradeArea(); + + owner->buildingsTryToBuildingSiteRoom.remove(this); + buildingState=ALIVE; + } + else if (buildingState==WAITING_FOR_CONSTRUCTION) + { + buildingState=ALIVE; + } + else + { + // Congratulation, you have managed to click "cancel upgrade" + // when the building upgrade" was already canceled. + return; + } + + constructionResultState=NO_CONSTRUCTION; + + if (!type->isVirtual) + owner->map->setBuilding(posX, posY, type->width, type->height, NOGBID); + int midPosX=posX-type->decLeft; + int midPosY=posY-type->decTop; + owner->removeFromAbilitiesLists(this); + owner->prestige-=type->prestige; + typeNum=recoverTypeNum; + type=recoverType; + owner->prestige+=type->prestige; + owner->addToStaticAbilitiesLists(this); + + //Update the pointer ressources to the newly changed type + updateRessourcesPointer(); + + posX=midPosX+type->decLeft; + posY=midPosY+type->decTop; + + if (!type->isVirtual) + owner->map->setBuilding(posX, posY, type->width, type->height, gid); + + maxUnitWorking=maxUnitWorkingPrevious; + maxUnitInside=type->maxUnitInside; + updateCallLists(); + updateUnitsWorking(); + // no unit harvesting at that point + + if (hp>=type->hpInit) + hp=type->hpInit; + + productionTimeout=type->unitProductionTime; + + if (type->unitProductionTime) + owner->swarms.push_back(this); + if (type->shootingRange) + owner->turrets.push_back(this); + if (type->canExchange) + owner->canExchange.push_back(this); + if (type->isVirtual) + owner->virtualBuildings.push_back(this); + if (type->zonable[WORKER]) + owner->clearingFlags.push_back(this); + + totalRatio=0; + + for (int i=0; ibuildingsWaitingForDestruction.push_front(this); + } +} + +void Building::cancelDelete(void) +{ + buildingState=ALIVE; + maxUnitWorking=maxUnitWorkingPrevious; + maxUnitInside=type->maxUnitInside; + updateCallLists(); + updateUnitsWorking(); + // we do not update units harvesting because there is none at this point + // we do not update owner->buildingsWaitingForDestruction because Team::syncStep will remove this building from the list +} + + +void Building::updateCallLists(void) +{ + if (buildingState==DEAD) + return; + desiredMaxUnitWorking = desiredNumberOfWorkers(); + bool ressourceFull=isRessourceFull(); + if (ressourceFull && !(type->canExchange && owner->openMarket())) + { + // Then we don't need anyone more to fill me, if I'm still in the call list for units, + // remove me + if(callListState != 0) + { + owner->remove_building_needing_work(this, oldPriority); + callListState=0; + oldPriority = priority; + } + } + + if (unitsWorking.size()<(unsigned)desiredMaxUnitWorking) + { + if (buildingState==ALIVE) + { + // I need units, if I am not in the call lists, add me + if(callListState != 1) + { + owner->add_building_needing_work(this, priority); + callListState = 1; + oldPriority = priority; + } + // I am in the call list. Re-register at current priority. This + // handles both BH-230 (priority changed -> move to new bucket) + // and the original same-priority re-sort, which is observable + // because Team::updateAllBuildingTasks calls subscribe* on each + // building in the bucket, and subscribe* -> updateCallLists + // mutates the bucket mid-iteration. + else + { + owner->remove_building_needing_work(this, oldPriority); + owner->add_building_needing_work(this, priority); + oldPriority = priority; + } + } + } + else + { + if(callListState != 0) + { + owner->remove_building_needing_work(this, oldPriority); + callListState=0; + oldPriority = priority; + } + } + + if ((signed)unitsInside.size()upgrade[i]) + { + owner->upgrade[i].push_front(this); + inUpgrade[i]=LS_IN; + } + + // this is for food handling + if (type->canFeedUnit) + { + if (ressources[CORN]>(int)unitsInside.size()) + { + if (inCanFeedUnit!=LS_IN) + { + owner->canFeedUnit.push_front(this); + //A Building newly getting available to feed is locked to conversion for CANNOT_CONVERT_TIMER_INIT frames + canNotConvertUnitTimer=CANNOT_CONVERT_TIMER_INIT; + inCanFeedUnit=LS_IN; + } + } + else + { + if (inCanFeedUnit!=LS_OUT) + { + owner->canFeedUnit.remove(this); + inCanFeedUnit=LS_OUT; + } + } + } + + // this is for Unit healing + if (type->canHealUnit && inCanHealUnit!=LS_IN) + { + owner->canHealUnit.push_front(this); + inCanHealUnit=LS_IN; + } + } + else + { + // delete itself from all Call lists + for (int i=0; iupgrade[i]) + { + owner->upgrade[i].remove(this); + inUpgrade[i]=LS_OUT; + } + + if (type->canFeedUnit && inCanFeedUnit!=LS_OUT) + { + owner->canFeedUnit.remove(this); + inCanFeedUnit=LS_OUT; + } + if (type->canHealUnit && inCanHealUnit!=LS_OUT) + { + owner->canHealUnit.remove(this); + inCanHealUnit=LS_OUT; + } + } +} + +void Building::updateConstructionState(void) +{ + if (buildingState==DEAD) + return; + + if ((buildingState==WAITING_FOR_CONSTRUCTION) || (buildingState==WAITING_FOR_CONSTRUCTION_ROOM)) + { + if (!isHardSpaceForBuildingSite()) + { + //this is semi-faulty code and needs to be fixed later + //anytime a building is upgraded but unable to do so it reverts to + //one worker working instead of previous value + cancelConstruction(1); + } + else if ((unitsWorking.size()==0) && (unitsInside.size()==0)) + { + if (buildingState!=WAITING_FOR_CONSTRUCTION_ROOM) + { + buildingState=WAITING_FOR_CONSTRUCTION_ROOM; + owner->buildingsTryToBuildingSiteRoom.push_front(this); + if(constructionResultState == UPGRADE) + addForbiddenZoneToUpgradeArea(); + if (verbose) + printf("bgid=%d, inserted in buildingsTryToBuildingSiteRoom\n", gid); + } + } + else if (verbose) + printf("bgid=%d, Building wait for upgrade, uws=%lu, uis=%lu.\n", gid, (unsigned long)unitsWorking.size(), (unsigned long)unitsInside.size()); + } +} diff --git a/src/IntBuildingType.cpp b/src/building/IntBuildingType.cpp similarity index 72% rename from src/IntBuildingType.cpp rename to src/building/IntBuildingType.cpp index 03655be21..751efce15 100644 --- a/src/IntBuildingType.cpp +++ b/src/building/IntBuildingType.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "IntBuildingType.h" #include diff --git a/src/building/IntBuildingType.h b/src/building/IntBuildingType.h new file mode 100644 index 000000000..261ef6597 --- /dev/null +++ b/src/building/IntBuildingType.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include +#include +#include + +struct IntBuildingType +{ + enum Number + { + SWARM_BUILDING=0, + FOOD_BUILDING=1, + HEAL_BUILDING=2, + + WALKSPEED_BUILDING=3, + SWIMSPEED_BUILDING=4, + ATTACK_BUILDING=5, + SCIENCE_BUILDING=6, + + DEFENSE_BUILDING=7, + + EXPLORATION_FLAG=8, + WAR_FLAG=9, + CLEARING_FLAG=10, + + STONE_WALL=11, + + MARKET_BUILDING=12, + + NB_BUILDING + }; + + static std::map conversionMap; + static std::vector reverseConversionMap; + static std::string null; + + static int shortNumberFromType(const std::string &type); + static const std::string & typeFromShortNumber(int number); + + static void init(void); +}; + diff --git a/src/building/Lifecycle.cpp b/src/building/Lifecycle.cpp new file mode 100644 index 000000000..7e3fd6369 --- /dev/null +++ b/src/building/Lifecycle.cpp @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include +#include +#include + +#include "Building.h" +#include "BuildingType.h" +#include "EngineTiming.h" +#include "FileFormatVersions.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Team.h" +#include "Unit.h" +#include "Utilities.h" +#include "Order.h" +#include "Bullet.h" +#include "Integrity.h" + +Building::Building(GAGCore::InputStream *stream, BuildingsTypes *types, Team *owner, Sint32 versionMinor) +{ + for (int i=0; igid=gid; + owner=team; + + // type + this->typeNum=typeNum; + type=types->get(typeNum); + owner->prestige+=type->prestige; + + // construction state + buildingState=ALIVE; + // We can only push on map level 0 building-sites ! + // If you want to add higher level building-sites, you have to change the "constructionResultState" to UPGRADE, + // and set the "buildingState" correctly. + if (type->isBuildingSite) + constructionResultState=NEW_BUILDING; + else + constructionResultState=NO_CONSTRUCTION; + + + // units + shortTypeNum = type->shortTypeNum; + maxUnitInside = type->maxUnitInside; + maxUnitWorking = unitWorking; + maxUnitWorkingPreferred = maxUnitWorking; + maxUnitWorkingFuture = unitWorkingFuture; + maxUnitWorkingPrevious = 0; + desiredMaxUnitWorking = maxUnitWorking; + subscriptionWorkingTimer = 0; + priority = 0; + priorityLocal = 0; + oldPriority = 0; + + // position + posX=x; + posY=y; + + underAttackTimer=0; + canNotConvertUnitTimer=0; + + // flag usefull : + unitStayRange=type->defaultUnitStayRange; + for(int i=0; ihpInit; // (Uint16) + + // prefered parameters + + productionTimeout=type->unitProductionTime; + + totalRatio=0; + ratioLocal[0]=ratio[0]=1; + totalRatio++; + percentUsed[0]=0; + for (int i=1; ireadEnterSection("Building"); + + // construction state + buildingState = (BuildingState)stream->readUint32("buildingState"); + constructionResultState = (ConstructionResultState)stream->readUint32("constructionResultState"); + + // identity + gid = stream->readUint16("gid"); + this->owner = owner; + + // position + posX = stream->readSint32("posX"); + posY = stream->readSint32("posY"); + + if(versionMinor>=FILE_FORMAT_VERSION_UNDER_ATTACK_TIMER) + underAttackTimer = stream->readUint8("underAttackTimer"); + else + underAttackTimer = 0; + if(versionMinor>=FILE_FORMAT_VERSION_CANNOT_CONVERT_TIMER) + canNotConvertUnitTimer = stream->readUint8("canNotConvertUnitTimer"); + else + canNotConvertUnitTimer = CANNOT_CONVERT_TIMER_INIT; + + // priority + if(versionMinor>=FILE_FORMAT_VERSION_BUILDING_PRIORITY_FIELD) + { + priority = stream->readSint32("priority"); + priorityLocal = stream->readSint32("priorityLocal"); + oldPriority = priority; + } + else + { + priority = 0; + priorityLocal = 0; + oldPriority = 0; + } + + // Flag specific + unitStayRange = stream->readUint32("unitStayRange"); + + for (int i=0; ireadSint32(oss.str().c_str()); + } + assert(clearingRessources[STONE] == false); + + memcpy(clearingRessourcesLocal, clearingRessources, sizeof(bool)*BASIC_COUNT); + + minLevelToFlag = stream->readSint32("minLevelToFlag"); + minLevelToFlagLocal = minLevelToFlag; + + // Building Specific + for (int i=0; ireadSint32(oss.str().c_str()); + } + + // quality parameters + hp = stream->readSint32("hp"); + + // prefered parameters + productionTimeout = stream->readSint32("productionTimeout"); + totalRatio = stream->readSint32("totalRatio"); + for (int i=0; ireadSint32(oss.str().c_str()); + } + { + std::ostringstream oss; + oss << "percentUsed[" << i << "]"; + percentUsed[i] = stream->readSint32(oss.str().c_str()); + } + } + + receiveRessourceMask = stream->readUint32("receiveRessourceMask"); + sendRessourceMask = stream->readUint32("sendRessourceMask"); + receiveRessourceMaskLocal = receiveRessourceMask; + sendRessourceMaskLocal = sendRessourceMask; + + shootingStep = stream->readUint32("shootingStep"); + shootingCooldown = stream->readSint32("shootingCooldown"); + bullets = stream->readSint32("bullets"); + + // type + // FIXME : do not save typenum but name/isBuildingSite/level + typeNum = stream->readSint32("typeNum"); + type = types->get(typeNum); + assert(type); + updateRessourcesPointer(); + + // reload data from type + shortTypeNum = type->shortTypeNum; + maxUnitInside = type->maxUnitInside; + maxUnitWorking = type->maxUnitWorking; + + // init data not loaded + maxUnitWorkingPreferred = 1; + maxUnitWorkingFuture = 1; + desiredMaxUnitWorking = maxUnitWorking; + subscriptionWorkingTimer = 0; + + owner->prestige += type->prestige; + + seenByMask = stream->readUint32("seenByMask"); + + inCanFeedUnit=LS_UNKNOWN; + inCanHealUnit=LS_UNKNOWN; + callListState = 0; + + for (int i=0; ireadLeaveSection(); + + lastShootStep = LAST_SHOOT_STEP_NEVER; + lastShootSpeedX = 0; + lastShootSpeedY = 0; + + + for(int i=0; iwriteEnterSection("Building"); + + // construction state + stream->writeUint32((Uint32)buildingState, "buildingState"); + stream->writeUint32((Uint32)constructionResultState, "constructionResultState"); + + // identity + stream->writeUint16(gid, "gid"); + // we drop team + + // position + stream->writeSint32(posX, "posX"); + stream->writeSint32(posY, "posY"); + + stream->writeUint8(underAttackTimer, "underAttackTimer"); + stream->writeUint8(canNotConvertUnitTimer, "canNotConvertUnitTimer"); + + // priority + stream->writeSint32(priority, "priority"); + stream->writeSint32(priorityLocal, "priorityLocal"); + + // Flag specific + stream->writeUint32(unitStayRange, "unitStayRange"); + for(int i=0; iwriteSint32(clearingRessources[i], oss.str().c_str()); + } + stream->writeSint32(minLevelToFlag, "minLevelToFlag"); + + // Building Specific + for (int i=0; iwriteSint32(localRessource[i], oss.str().c_str()); + } + + // quality parameters + stream->writeSint32(hp, "hp"); + + // prefered parameters + stream->writeSint32(productionTimeout, "productionTimeout"); + stream->writeSint32(totalRatio, "totalRatio"); + for (int i=0; iwriteSint32(ratio[i], oss.str().c_str()); + } + { + std::ostringstream oss; + oss << "percentUsed[" << i << "]"; + stream->writeSint32(percentUsed[i], oss.str().c_str()); + } + } + + stream->writeUint32(receiveRessourceMask, "receiveRessourceMask"); + stream->writeUint32(sendRessourceMask, "sendRessourceMask"); + + stream->writeUint32(shootingStep, "shootingStep"); + stream->writeSint32(shootingCooldown, "shootingCooldown"); + stream->writeSint32(bullets, "bullets"); + + // type + stream->writeUint32(typeNum, "typeNum"); + // we drop type + + stream->writeUint32(seenByMask, "seenByMask"); + + stream->writeLeaveSection(); +} + +void Building::loadCrossRef(GAGCore::InputStream *stream, BuildingsTypes *types, Team *owner, Sint32 versionMinor) +{ + stream->readEnterSection("Building"); + + // units + maxUnitInside = stream->readSint32("maxUnitInside"); + assert(maxUnitInside < MAX_UNIT_INSIDE_LIMIT); + + unsigned nbWorking = stream->readUint32("nbWorking"); + unitsWorking.clear(); + for (unsigned i=0; imyUnits[Unit::GIDtoID(stream->readUint16(oss.str().c_str()))]; + assert(unit); + unitsWorking.push_front(unit); + } + + subscriptionWorkingTimer = stream->readSint32("subscriptionWorkingTimer"); + maxUnitWorking = stream->readSint32("maxUnitWorking"); + maxUnitWorkingPreferred = stream->readSint32("maxUnitWorkingPreferred"); + if(versionMinor>=FILE_FORMAT_VERSION_MAX_UNIT_WORKING_PREVIOUS) + maxUnitWorkingPrevious = stream->readSint32("maxUnitWorkingPrevious"); + else + maxUnitWorkingPrevious = maxUnitWorkingPreferred; + if(versionMinor>=FILE_FORMAT_VERSION_MAX_UNIT_WORKING_FUTURE) + maxUnitWorkingFuture = stream->readSint32("maxUnitWorkingFuture"); + desiredMaxUnitWorking = maxUnitWorking; + + if(versionMinor>=FILE_FORMAT_VERSION_UNITS_FAILING_REQUIREMENTS_INT && versionMinorreadSint32("unitsFailingRequirements"); + } + else if(versionMinor>=FILE_FORMAT_VERSION_UNITS_FAILING_REQUIREMENTS_ARRAY) + { + stream->readEnterSection("unitsFailingRequirements"); + for(int i=0; ireadEnterSection(i); + unitsFailingRequirements[i]=stream->readUint32("unitsFailingRequirements"); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + } + + unsigned nbInside = stream->readUint32("nbInside"); + unitsInside.clear(); + for (unsigned i=0; imyUnits[Unit::GIDtoID(stream->readUint16(oss.str().c_str()))]; + assert(unit); + unitsInside.push_front(unit); + } + + if (versionMinor>=FILE_FORMAT_VERSION_UNITS_HARVESTING_LIST) + { + unsigned nbHarvesting = stream->readUint32("nbHarvesting"); + unitsHarvesting.clear(); + for (unsigned i=0; imyUnits[Unit::GIDtoID(stream->readUint16(oss.str().c_str()))]; + assert(unit); + unitsHarvesting.push_front(unit); + } + } + + stream->readLeaveSection(); +} + +void Building::saveCrossRef(GAGCore::OutputStream *stream) +{ + unsigned i; + + stream->writeEnterSection("Building"); + + // units + stream->writeSint32(maxUnitInside, "maxUnitInside"); + //TODO: std::list::size() is O(n). We should investigate + //if our intense use of this has an impact on overall performance. + //steph and nuage suggested to store and update size in a variable + //what is faster but also more error prone. + stream->writeUint32(unitsWorking.size(), "nbWorking"); + i = 0; + for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) + { + assert(*it); + assert(owner->myUnits[Unit::GIDtoID((*it)->gid)]); + std::ostringstream oss; + oss << "unitsWorking[" << i++ << "]"; + stream->writeUint16((*it)->gid, oss.str().c_str()); + } + + stream->writeSint32(subscriptionWorkingTimer, "subscriptionWorkingTimer"); + stream->writeSint32(maxUnitWorking, "maxUnitWorking"); + stream->writeSint32(maxUnitWorkingPreferred, "maxUnitWorkingPreferred"); + stream->writeSint32(maxUnitWorkingPrevious, "maxUnitWorkingPrevious"); + stream->writeSint32(maxUnitWorkingFuture, "maxUnitWorkingFuture"); + + stream->writeEnterSection("unitsFailingRequirements"); + for(int i=0; iwriteEnterSection(i); + stream->writeUint32(unitsFailingRequirements[i], "unitsFailingRequirements"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + + stream->writeUint32(unitsInside.size(), "nbInside"); + i = 0; + for (std::list::iterator it=unitsInside.begin(); it!=unitsInside.end(); ++it) + { + assert(*it); + assert(owner->myUnits[Unit::GIDtoID((*it)->gid)]); + std::ostringstream oss; + oss << "unitsInside[" << i++ << "]"; + stream->writeUint16((*it)->gid, oss.str().c_str()); + } + + stream->writeUint32(unitsHarvesting.size(), "nbHarvesting"); + i = 0; + for (std::list::iterator it=unitsHarvesting.begin(); it!=unitsHarvesting.end(); ++it) + { + assert(*it); + std::ostringstream oss; + oss << "unitsHarvesting[" << i++ << "]"; + stream->writeUint16((*it)->gid, oss.str().c_str()); + } + + stream->writeLeaveSection(); +} + diff --git a/src/building/Misc.cpp b/src/building/Misc.cpp new file mode 100644 index 000000000..9d5093179 --- /dev/null +++ b/src/building/Misc.cpp @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include +#include + +#include "Building.h" +#include "BuildingType.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Team.h" +#include "Unit.h" +#include "Utilities.h" +#include "Order.h" +#include "Bullet.h" +#include "Integrity.h" + +void Building::kill(void) +{ + if (buildingState==DEAD) + return; + + + for (std::list::iterator it=unitsInside.begin(); it!=unitsInside.end(); ++it) + { + //TODO: We should somehow try to save their lives. In training buildings they should just drop out untrained etc. + Unit *u=*it; + if (u->displacement==Unit::DIS_INSIDE) + u->isDead=true; + + if (u->displacement==Unit::DIS_ENTERING_BUILDING) + { + if (u->performance[FLY]) + owner->map->setAirUnit(u->posX-u->dx, u->posY-u->dy, NOGUID); + else + owner->map->setGroundUnit(u->posX-u->dx, u->posY-u->dy, NOGUID); + //printf("(%x)Building:: Unit(uid%d)(id%d) killed while entering. dis=%d, mov=%d, ab=%x, ito=%d \n",this, u->gid, Unit::UIDtoID(u->gid), u->displacement, u->movement, (int)u->attachedBuilding, u->insideTimeout); + u->isDead=true; + } + u->standardRandomActivity(); + } + unitsInside.clear(); + + for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) + { + assert(*it); + (*it)->standardRandomActivity(); + } + unitsWorking.clear(); + + maxUnitWorking=0; + maxUnitInside=0; + desiredMaxUnitWorking = 0; + updateCallLists(); + + + if (!type->isVirtual) + { + owner->map->setBuilding(posX, posY, type->width, type->height, NOGBID); + owner->dirtyGlobalGradient(); + owner->map->updateForbiddenGradient(owner->teamNumber); + owner->map->updateGuardAreasGradient(owner->teamNumber); + owner->map->updateClearAreasGradient(owner->teamNumber); + if (type->isBuildingSite && type->level==0) + { + bool good=false; + for (int r=0; r0) + { + good=true; + break; + } + if (!good) + owner->noMoreBuildingSitesCountdown=Team::noMoreBuildingSitesCountdownMax; + } + + } + + buildingState=DEAD; + + updateUnitsHarvesting(); + + owner->prestige-=type->prestige; + + owner->buildingsToBeDestroyed.push_front(this); +} + + +bool Building::canUnitWorkHere(Unit* unit) +{ + if(type->isVirtual) + { + if(type->zonable[unit->typeNum]) + { + if (unit->typeNum == WARRIOR) + { + int level=std::min(unit->level[ATTACK_SPEED], unit->level[ATTACK_STRENGTH]); + if(minLevelToFlag<=level) + return true; + } + else if (unit->typeNum == EXPLORER) + { + if(minLevelToFlag && !unit->level[MAGIC_ATTACK_GROUND]) + return false; + else + return true; + } + else if (unit->typeNum == WORKER) + { + return true; + } + + } + } + else if(unit->typeNum == WORKER) + { + int actLevel=unit->level[HARVEST]; + if(type->level <= actLevel) + return true; + } + return false; + +} + + + +void Building::removeUnitFromWorking(Unit* unit) +{ + unitsWorking.remove(unit); + updateCallLists(); +} + +void Building::insertUnitToHarvesting(Unit* unit) +{ + unitsHarvesting.push_front(unit); +} + + +void Building::removeUnitFromHarvesting(Unit* unit) +{ + unitsHarvesting.remove(unit); +} + + +void Building::removeUnitFromInside(Unit* unit) +{ + unitsInside.remove(unit); + updateCallLists(); +} + + + +void Building::updateRessourcesPointer() +{ + if(!type->useTeamRessources) + { + ressources=localRessource; + } + else + { + ressources=owner->teamRessources; + } +} + + + +void Building::addRessourceIntoBuilding(int ressourceType) +{ + ressources[ressourceType]+=type->multiplierRessource[ressourceType]; + //You can not exceed the maximum amount + ressources[ressourceType] = std::min(ressources[ressourceType], type->maxRessource[ressourceType]); + switch (constructionResultState) + { + case NO_CONSTRUCTION: + break; + case NEW_BUILDING: + case UPGRADE: + { + hp+=type->hpInc; + hp = std::min(hp, type->hpMax); + } + break; + + case REPAIR: + { + int totRessources=0; + for (unsigned i=0; imaxRessource[i]; + if (totRessources>0) + { + hp += type->hpMax/totRessources; + hp = std::min(hp, type->hpMax); + } + } + break; + + default: + assert(false); + } + update(); +} + + + +void Building::removeRessourceFromBuilding(int ressourceType) +{ + ressources[ressourceType]-=type->multiplierRessource[ressourceType]; + ressources[ressourceType]= std::max(ressources[ressourceType], 0); + updateCallLists(); +} + + + +int Building::getMidX(void) +{ + return ((posX-type->decLeft)&owner->map->getMaskW()); +} + +int Building::getMidY(void) +{ + return ((posY-type->decTop)&owner->map->getMaskH()); +} + +bool Building::findGroundExit(int *posX, int *posY, int *dx, int *dy, bool canSwim) +{ + int testX, testY; + int exitQuality=0; + int oldQuality; + int exitX=0, exitY=0; + + // TODO: Introduce a border iterator for rectangles + + // if (exitQualityposY-1; + oldQuality=0; + for (testX=this->posX-1; testX<=this->posX+type->width ; testX++) + checkGroundExitQuality(testX,testY,testX,testY-1,exitX,exitY,exitQuality,oldQuality,canSwim); + } + if (exitQualityposY+type->height; + oldQuality=0; + for (testX=this->posX-1; (testX<=this->posX+type->width) ; testX++) + checkGroundExitQuality(testX,testY,testX,testY+1,exitX,exitY,exitQuality,oldQuality,canSwim); + } + if (exitQualityposX-1; + for (testY=this->posY-1; (testY<=this->posY+type->height) ; testY++) + checkGroundExitQuality(testX,testY,testX-1,testY,exitX,exitY,exitQuality,oldQuality,canSwim); + } + if (exitQualityposX+type->width; + for (testY=this->posY-1; (testY<=this->posY+type->height) ; testY++) + checkGroundExitQuality(testX,testY,testX+1,testY,exitX,exitY,exitQuality,oldQuality,canSwim); + } + if (exitQuality>0) + { + auto off = owner->map->doesPosTouchBuilding(exitX, exitY, gid); + assert(off); + *dx=-off->dx; + *dy=-off->dy; + *posX=exitX & owner->map->getMaskW(); + *posY=exitY & owner->map->getMaskH(); + return true; + } + return false; +} + +void Building::checkGroundExitQuality( + const int testX, + const int testY, + const int extraTestX, + const int extraTestY, + int & exitX, + int & exitY, + int & exitQuality, + int & oldQuality, + bool canSwim) +{ + Uint32 me=owner->me; + if (owner->map->isFreeForGroundUnit(testX, testY, canSwim, me)) + { + if (owner->map->isFreeForGroundUnit(extraTestX, extraTestY, canSwim, me)) + oldQuality++; + if (owner->map->isRessource(testX, testY-1)) + { + if (exitQualityposX; xiposX+type->width; xi++) + for (int yi=this->posY; yiposY+type->height; yi++) + if (owner->map->isFreeForAirUnit(xi, yi)) + { + *posX=xi; + *posY=yi; + int tdx=xi-getMidX(); + int tdy=yi-getMidY(); + if (tdx<0) + *dx=-1; + else if (tdx==0) + *dx=0; + else + *dx=1; + + if (tdy<0) + *dy=-1; + else if (tdy==0) + *dy=0; + else + *dy=1; + return true; + } + return false; +} + +int Building::getLongLevel(void) +{ + return ((type->level)<<1)+1-type->isBuildingSite; +} + +Uint32 Building::eatOnce(Uint32 *mask) +{ + ressources[CORN]--; + assert(ressources[CORN]>=0); + Uint32 fruitMask=0; + Uint32 fruitCount=0; + for (int i=0; iinside) + happyness++; + return happyness; +} + +bool Building::canConvertUnit(void) +{ + assert(type->canFeedUnit); + return + canNotConvertUnitTimer<=0 && + ((int)unitsInside.size()::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) + { + checkInvariant(*it); + checkInvariant(owner->myUnits[Unit::GIDtoID((*it)->gid)]); + checkInvariant((*it)->attachedBuilding==this); + } + + checkInvariant((int)unitsInside.size()<=Unit::MAX_COUNT); + for (std::list::iterator it=unitsInside.begin(); it!=unitsInside.end(); ++it) + { + checkInvariant(*it); + checkInvariant(owner->myUnits[Unit::GIDtoID((*it)->gid)]); + checkInvariant((*it)->attachedBuilding==this); + } + for (std::list::iterator it=unitsHarvesting.begin(); it!=unitsHarvesting.end(); ++it) + { + checkInvariant(*it); + checkInvariant((*it)->targetBuilding==this); + } + return true; +} + +Uint32 Building::checkSum(std::vector *checkSumsVector) +{ + // `cs` is signed `int` so the open-coded `(cs<<31)|(cs>>1)` rotates + // use arithmetic right-shift (sign-extending). Do NOT replace these + // with the unsigned `rotr1(Uint32)` helper in Utilities.h: when XOR + // mixing leaves bit 31 set, signed `>>1` and unsigned `>>1` produce + // different bit patterns, and the network checksum diverges. The + // Rust port should preserve the arithmetic-shift behavior — i.e. + // `((cs as i32) >> 1) as u32` — not `cs.rotate_right(1)`. + int cs=0; + + cs^=typeNum; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [0] + + cs^=buildingState; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [1] + cs=(cs<<31)|(cs>>1); + + cs^=constructionResultState; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [2] + cs=(cs<<31)|(cs>>1); + + cs^=maxUnitWorking; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [3] + + cs^=maxUnitWorkingFuture; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [4] + + cs^=maxUnitWorkingPreferred; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [5] + + cs^=maxUnitWorkingPrevious; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [6] + + cs^=desiredMaxUnitWorking; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [7] + + cs^=unitsWorking.size(); + if (checkSumsVector) + checkSumsVector->push_back(cs);// [8] + + cs^=subscriptionWorkingTimer; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [9] + + cs^=unitsInside.size(); + if (checkSumsVector) + checkSumsVector->push_back(cs);// [10] + cs=(cs<<31)|(cs>>1); + + cs^=posX; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [11] + + cs^=posY; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [12] + cs=(cs<<31)|(cs>>1); + + cs^=unitStayRange; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [13] + + for (int i=0; ipush_back(cs);// [14] + cs=(cs<<31)|(cs>>1); + + cs^=hp; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [15] + + cs^=productionTimeout; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [16] + + + cs^=totalRatio; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [17] + + + for (int i=0; i>1); + } + if (checkSumsVector) + checkSumsVector->push_back(cs);// [18] + + cs^=shootingStep; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [19] + + + cs^=shootingCooldown; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [20] + + + cs^=bullets; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [21] + cs=(cs<<31)|(cs>>1); + + cs^=seenByMask; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [22] + + cs^=gid; + if (checkSumsVector) + checkSumsVector->push_back(cs);// [23] + + + cs^=unitsHarvesting.size(); + if (checkSumsVector) + checkSumsVector->push_back(cs);// [24] + + return cs; +} diff --git a/src/building/Step.cpp b/src/building/Step.cpp new file mode 100644 index 000000000..8dd1136c9 --- /dev/null +++ b/src/building/Step.cpp @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include +#include + +#include "Building.h" +#include "BuildingType.h" +#include "FixedPoint.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Team.h" +#include "Unit.h" +#include "Utilities.h" +#include "Order.h" +#include "Bullet.h" +#include "Integrity.h" + +void Building::step(void) +{ + computeWishedRessources(wishedResources); + + updateCallLists(); + if(underAttackTimer>0) + underAttackTimer--; + if(canNotConvertUnitTimer>0) + canNotConvertUnitTimer--; + // NOTE : Unit needs to update itself when it is in a building +} + + +bool Building::subscribeToBringRessourcesStep() +{ + for(int i=0; imap; + for(int i=0; i>2 - (d+dr))*500+100/harvest + */ + /* + int maxValue=-INT_MAX; + for(int n=0; nmyUnits[n]; + if(unit==NULL + || unit->activity != Unit::ACT_RANDOM + || unit->medical != Unit::MED_FREE + || !unit->performance[HARVEST]) + continue; + if(!canUnitWorkHere(unit)) + continue; + + int r=unit->carriedRessource; + int dist; + if(!map->buildingAvailable(this, unit->performance[SWIM], unit->posX, unit->posY, &dist)) + { + //std::cout << ":" << std::flush; + continue; //also to fill dist + } + int distUnitRessource; + int nr; + for (nr=0; nr0) + { + if(map->ressourceAvailable(owner->teamNumber, nr, unit->performance[SWIM], unit->posX, unit->posY, &distUnitRessource)) //to fill distUnitRessource + break; + else + continue; + } + } + if (neededRessource(nr)<=0) + { + //std::cout << "," << std::flush; + continue; + } + int rightRes=(((r>=0) && neededRessource(r))?1:0); + if(rightRes==1 && (unit->hungry-unit->trigHungry)/unit->race->hungryness/2hungry-unit->trigHungry)/unit->race->hungryness/2<(dist+distUnitRessource)) + continue; + int noRes=(r<0?1:0); + int wrongRes=(((r>=0) && !neededRessource(r))?1:0); + int value = ( + rightRes*10*(512-dist)+ + noRes*8*(512-dist-distUnitRessource)+ + wrongRes*2*(512-dist-distUnitRessource) + )*(unit->level[WALK]+1)+ + //enoughTimeLeft*5000+ + 50*(unit->level[HARVEST]+1)+ + (unit->level[SWIM]>0?-200:0);//swimmer's penalty to keep them free for swimmer tasks + //std::cout << "d" << dist << " dr" << distUnitRessource << " rr" << rightRes << " nr" << noRes << " wr" << wrongRes << " wa" << unit->level[WALK] << " ha" << unit->level[HARVEST] << " va" << value << std::endl << std::flush; + unit->destinationPurpose=(rightRes>0?r:nr); + if (value>maxValue) + { + maxValue=value; + choosen=unit; + } + } +*/ + // Compute the list of candidate units + Unit* possibleUnits[Unit::MAX_COUNT]; + int distances[Unit::MAX_COUNT]; + int resource[Unit::MAX_COUNT]; + int teamNumber=owner->teamNumber; + for(int n=0; nmyUnits[n]; + if(unit) + { + if(!unit->performance[HARVEST]) + { + continue; + } + else if(unit->attachedBuilding == this && unit->activity == Unit::ACT_FILLING) + { + continue; + } + else if(unit->activity != Unit::ACT_RANDOM || unit->medical != Unit::MED_FREE) + { + unitsFailingRequirements[UnitNotAvailable] += 1; + } + else if(!canUnitWorkHere(unit)) + { + unitsFailingRequirements[UnitTooLowLevel] += 1; + } + else + { + int distBuilding=0; + int timeLeft=(unit->hungry-unit->trigHungry)/unit->race->hungryness; + bool canSwim=unit->performance[SWIM]; + if(!map->buildingAvailable(this, canSwim, unit->posX, unit->posY, &distBuilding)) + { + unitsFailingRequirements[UnitCantAccessBuilding] += 1; + } + else if(distBuilding >= timeLeft) + { + unitsFailingRequirements[UnitTooFarFromBuilding] += 1; + } + else + { + int unitr = unit->carriedRessource; + if((unitr>=0) && neededRessource(unitr)) + { + possibleUnits[n] = unit; + distances[n] = distBuilding; + resource[n] = unitr; + } + else + { + int bestDist = 100000; + int bestResource = RESSOURCE_TYPE_NONE; + bool regularFound=false; + bool fruitFound=false; + bool regularFoundTooFar=false; + bool fruitFoundTooFar=false; + int x=unit->posX; + int y=unit->posY; + for(int r=0; r0) + { + if(rressourceAvailable(teamNumber, r, canSwim, x, y, &distResource)) + { + if(distResourcecarriedRessource; + int timeLeft=(unit->hungry-unit->trigHungry)/unit->race->hungryness; + if ((r>=0) && neededRessource(r)) + { + int dist = distances[n]; + int value=dist-(timeLeft>>1); + int level = unit->level[HARVEST]*10 + unit->level[WALK]; + unit->destinationPurpose=r; + if ((level>maxLevel) || (level==maxLevel && valuecarriedRessource<0) + { + int r = resource[n]; + int value=distances[n]; + int level = unit->level[HARVEST]*10 + unit->level[WALK]; + if ((level>maxLevel) || (level==maxLevel && valuedestinationPurpose=r; + } + } + } + } + + //Third: we look for an unit who is carrying an unwanted resource: + if (choosen==NULL) + { + for(int n=0; ncarriedRessource; + if ((r2>=0) && !neededRessource(r2)) + { + int r = resource[n]; + int value=distances[n]; + int level = unit->level[HARVEST]*10 + unit->level[WALK]; + if ((level>maxLevel) || (level==maxLevel && valuedestinationPurpose=r; + } + } + } + } + if (choosen) + { + unitsWorking.push_back(choosen); + choosen->subscriptionSuccess(this, false); + hired=true; + } + } + + updateCallLists(); + + if (verbose) + printf(" ...done\n"); + return hired; +} + +bool Building::considerUnitForExplorerFlag(Unit* unit, int* dist) +{ + if (unit->activity != Unit::ACT_RANDOM || unit->medical != Unit::MED_FREE) + { + unitsFailingRequirements[UnitNotAvailable] += 1; + return false; + } + if (!canUnitWorkHere(unit)) + { + unitsFailingRequirements[UnitTooLowLevel] += 1; + return false; + } + int timeLeft = (unit->hungry - unit->trigHungry) / unit->race->hungryness; + // warpDistSquare returns squared Euclidean distance, so timeLeft is + // squared here to keep the comparison in the same units. Worker/warrior + // flags compare against Map::buildingAvailable (linear gradient + // distance) and must NOT square — see considerUnitForWorkerFlag. + int timeLeftSquared = timeLeft * timeLeft; + int directdist = owner->map->warpDistSquare(unit->posX, unit->posY, posX, posY); + if (timeLeftSquared < directdist) + { + unitsFailingRequirements[UnitTooFarFromBuilding] += 1; + return false; + } + *dist = directdist; + return true; +} + +bool Building::considerUnitForWorkerFlag(Unit* unit, int* dist) +{ + if (unit->activity != Unit::ACT_RANDOM || unit->medical != Unit::MED_FREE) + { + unitsFailingRequirements[UnitNotAvailable] += 1; + return false; + } + if (!canUnitWorkHere(unit)) + { + unitsFailingRequirements[UnitTooLowLevel] += 1; + return false; + } + int distBuilding = 0; + // timeLeft and distBuilding are both linear (in ticks-remaining and + // linear gradient steps respectively); compare as-is. The corresponding + // check in subscribeToBringRessourcesStep uses the same pairing. + int timeLeft = (unit->hungry - unit->trigHungry) / unit->race->hungryness; + bool canSwim = unit->performance[SWIM]; + if (!owner->map->buildingAvailable(this, canSwim, unit->posX, unit->posY, &distBuilding)) + { + unitsFailingRequirements[UnitCantAccessBuilding] += 1; + return false; + } + if (distBuilding >= timeLeft) + { + unitsFailingRequirements[UnitTooFarFromBuilding] += 1; + return false; + } + if (anyRessourceToClear[canSwim] == 2) + { + unitsFailingRequirements[UnitCantAccessResource] += 1; + return false; + } + *dist = distBuilding; + return true; +} + +bool Building::considerUnitForWarriorFlag(Unit* unit, int* dist) +{ + if (unit->activity != Unit::ACT_RANDOM || unit->medical != Unit::MED_FREE) + { + unitsFailingRequirements[UnitNotAvailable] += 1; + return false; + } + if (!canUnitWorkHere(unit)) + { + unitsFailingRequirements[UnitTooLowLevel] += 1; + return false; + } + if (unit->movement == Unit::MOV_ATTACKING_TARGET) + { + unitsFailingRequirements[UnitNotAvailable] += 1; + return false; + } + int distBuilding = 0; + // timeLeft and distBuilding are both linear (in ticks-remaining and + // linear gradient steps respectively); compare as-is. The corresponding + // check in subscribeToBringRessourcesStep uses the same pairing. + int timeLeft = (unit->hungry - unit->trigHungry) / unit->race->hungryness; + bool canSwim = unit->performance[SWIM]; + if (!owner->map->buildingAvailable(this, canSwim, unit->posX, unit->posY, &distBuilding)) + { + unitsFailingRequirements[UnitCantAccessBuilding] += 1; + return false; + } + if (distBuilding >= timeLeft) + { + unitsFailingRequirements[UnitTooFarFromBuilding] += 1; + return false; + } + *dist = distBuilding; + return true; +} + +bool Building::subscribeForFlagingStep() +{ + if (buildingState==DEAD) + { + for(int i=0; i32) + { + // Reset stale failure counts for the case where the while loop below + // doesn't run (building already fully staffed). When the loop does run, + // this is overwritten by the per-iteration reset on iteration 1. + for(int i=0; imyUnits[n]; + if(!unit) + continue; + if(unit->attachedBuilding == this) + continue; + if(type->zonable[EXPLORER]) + { + if(unit->typeNum != EXPLORER) + continue; + if(considerUnitForExplorerFlag(unit, &distances[n])) + possibleUnits[n]=unit; + } + else if(type->zonable[WORKER]) + { + if(unit->typeNum != WORKER) + continue; + if(considerUnitForWorkerFlag(unit, &distances[n])) + possibleUnits[n]=unit; + } + else if(type->zonable[WARRIOR]) + { + if(unit->typeNum != WARRIOR) + continue; + if(considerUnitForWarriorFlag(unit, &distances[n])) + possibleUnits[n]=unit; + } + } + + int minValue=INT_MAX; + int minLevel=INT_MAX; + int maxLevel=-INT_MAX; + Unit *choosen=NULL; + + /* To choose a good unit, we get a composition of things: + 1-the closer the unit is, the better it is. + 2-the less the unit is hungry, the better it is. + 3-the more hp the unit has, the better it is. + */ + if (type->zonable[EXPLORER]) + { + for(int n=0; nhungry/unit->race->hungryness; + int hp=(unit->hp<<4)/unit->race->unitTypes[0][0].performance[HP]; + timeLeft*=timeLeft; + hp*=hp; + int dist=distances[n]; + //Use explorers without ground attack first before ones with, so that ground attacking explorers + //are available for more important jobs + int value=dist-2*timeLeft-2*hp; + int level = unit->level[MAGIC_ATTACK_GROUND]; + if ((level < minLevel) || (level==minLevel && valuezonable[WARRIOR]) + { + for(int n=0; nhungry/unit->race->hungryness; + int hp=(unit->hp<<4)/unit->race->unitTypes[0][0].performance[HP]; + int dist = distances[n]; + int value=dist-2*timeLeft-2*hp; + //We want to maximize the attack level, use higher level soldeirs first + int level=unit->performance[ATTACK_SPEED]*unit->getRealAttackStrength(); + if ((level > maxLevel) || (level==maxLevel && valuezonable[WORKER]) + { + for(int n=0; nhungry-unit->trigHungry)/unit->race->hungryness; + int hp=(unit->hp<<4)/unit->race->unitTypes[0][0].performance[HP]; + int dist = distances[n]; + int value=dist-timeLeft-hp; + int level = unit->level[HARVEST]; + //We want to minimize the level of harvesting units, so that the higher level + //units are available for more important work. + if ((level < minLevel) || (level==minLevel && valuesubscriptionSuccess(this, false); + hired=true; + } + else + break; + } + + updateCallLists(); + + subscriptionWorkingTimer=0; + } + return hired; +} + + +void Building::subscribeUnitForInside(Unit* unit) +{ + unitsInside.push_back(unit); + unit->subscriptionSuccess(this, true); + updateCallLists(); +} + + diff --git a/src/building/TypeSteps.cpp b/src/building/TypeSteps.cpp new file mode 100644 index 000000000..33de9a7f1 --- /dev/null +++ b/src/building/TypeSteps.cpp @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include +#include + +#include "Building.h" +#include "BuildingType.h" +#include "EngineTiming.h" +#include "FixedPoint.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Map.h" +#include "Team.h" +#include "Unit.h" +#include "Utilities.h" +#include "Order.h" +#include "Bullet.h" +#include "Integrity.h" + +void Building::swarmStep(void) +{ + // increase HP + if (hphpMax) + hp++; + assert(NB_UNIT_TYPE==3); + if ((ressources[CORN]>=type->ressourceForOneUnit)&&(ratio[0]|ratio[1]|ratio[2])) + productionTimeout--; + + if (productionTimeout<0) + { + // We find the kind of unit we have to create: + Sint32 fProportion; + Sint32 fMinProportion = MIN_PROPORTION_INIT; + int minType=-1; + for (int i=0; i=0); + assert(minType=NB_UNIT_TYPE) + minType=0; + + // We get the unit UnitType: + int posX, posY, dx, dy; + UnitType *ut=owner->race.getUnitType(minType, 0); + + // Is there a place to exit ? + bool exitFound; + if (ut->performance[FLY]) + exitFound=findAirExit(&posX, &posY, &dx, &dy); + else + exitFound=findGroundExit(&posX, &posY, &dx, &dy, ut->performance[SWIM]); + if (exitFound) + { + Unit * u=owner->game->addUnit(posX, posY, owner->teamNumber, minType, 0, 0, dx, dy); + if (u) + { + ressources[CORN]-=type->ressourceForOneUnit; + updateCallLists(); + + u->activity=Unit::ACT_RANDOM; + u->displacement=Unit::DIS_RANDOM; + u->movement=Unit::MOV_EXITING_BUILDING; + u->speed=u->performance[u->action]; + + productionTimeout=type->unitProductionTime; + + // We update percentUsed[] + percentUsed[minType]++; + + bool allDone=true; + for (int i=0; iteamNumber); + } + } +} + + +void Building::turretStep(Uint32 stepCounter) +{ + // create bullet from stones in stock + if (ressources[STONE]>0 && (bullets<=(type->maxBullets-type->multiplierStoneToBullets))) + { + ressources[STONE]--; + bullets += type->multiplierStoneToBullets; + + // we need to be stone-feeded + updateCallLists(); + } + + // compute cooldown + if (shootingCooldown > 0) + { + shootingCooldown -= type->shootRythme; + return; + } + + // if we have no bullet, don't try to shoot + if (bullets <= 0) + return; + + //for some reason, any turret that is not 2x2 makes no sense at all to the game + assert(type->width == TURRET_SIZE); + assert(type->height == TURRET_SIZE); + + int range = type->shootingRange; + shootingStep = (shootingStep+1) & (SHOOTING_ANIMATION_FRAMES - 1); + + Uint32 enemies = owner->enemies; + Map *map = owner->map; + assert(map); + + // the type of target we have found + enum TargetType + { + TARGETTYPE_NONE, + TARGETTYPE_BUILDING, + TARGETTYPE_WORKER, + TARGETTYPE_WARRIOR, + TARGETTYPE_EXPLORER, + }; + // The type of the best target we have found up to now + TargetType targetFound = TARGETTYPE_NONE; + // The score of the best target we have found up to now + int bestScore = INT_MIN; + // The number of ticks before the unit may move away + int bestTicks = 0; + // The position of the best target we have found up to now + int bestTargetX = 0, bestTargetY=0; + + for (int i=0; i<=range ; i++) + { + // The number of ticks before the bullet hits the target at range "i". + int ticksToHit = ((i << Map::TILE_PIXEL_SHIFT) + ((type->width) << 4)) / (type->shootSpeed>>Q8_FIXED_POINT_SHIFT); + for (int j=0; j<=i ; j++) + { + for (int k=0; k<8; k++) + { + int targetX, targetY; + switch (k) + { + case 0: + targetX=posX-j; + targetY=posY-i; + break; + case 1: + targetX=posX+j+1; + targetY=posY-i; + break; + case 2: + targetX=posX-j; + targetY=posY+i+1; + break; + case 3: + targetX=posX+j+1; + targetY=posY+i+1; + break; + case 4: + targetX=posX-i; + targetY=posY-j; + break; + case 5: + targetX=posX+i+1; + targetY=posY-j; + break; + case 6: + targetX=posX-i; + targetY=posY+j+1; + break; + case 7: + targetX=posX+i+1; + targetY=posY+j+1; + break; + default: + assert(false); + targetX=0; + targetY=0; + break; + } + int targetGUID = map->getGroundUnit(targetX, targetY); + int airTargetGUID = map->getAirUnit(targetX, targetY); + if (targetGUID != NOGUID) + { + Sint32 otherTeam = Unit::GIDtoTeam(targetGUID); + Sint32 targetID = Unit::GIDtoID(targetGUID); + Uint32 otherTeamMask = 1<game->teams[otherTeam]->myUnits[targetID]; + if ((owner->sharedVisionExchange & otherTeamMask) == 0) + { + int targetTicks = (256 - testUnit->delta) / testUnit->speed; + // skip this unit if it will move away too soon. + if (targetTicks <= ticksToHit) + continue; + // shoot warrior first, then workers if no warrior + if (testUnit->typeNum == WARRIOR) + { + int targetOffense = (testUnit->getRealAttackStrength() * testUnit->performance[ATTACK_SPEED]); // 88 to 1024 + int targetWeakeness = 0; // 0 to 512 + if (testUnit->hp > 0) + { + if (testUnit->hp < type->shootDamage) // hahaha, how mean! + targetWeakeness = 512; + else + targetWeakeness = 256 / testUnit->hp; + } + int targetProximity = 0; // 0 to 512 + if (i <= 0) + targetProximity = 512; + else + targetProximity = (256 / i); + int targetScore = targetOffense + targetWeakeness + targetProximity; + // lower scores are overriden + if (targetScore > bestScore) + { + bestScore = targetScore; + bestTicks = targetTicks; + bestTargetX = targetX; + bestTargetY = targetY; + targetFound = TARGETTYPE_WARRIOR; + } + } + else if ((targetFound != TARGETTYPE_WARRIOR) && (testUnit->typeNum == WORKER)) + { + // adjust score for range + int targetScore = - testUnit->hp; + // lower scores are overriden + if (targetScore > bestScore) + { + bestScore = targetScore; + bestTicks = targetTicks; + bestTargetX = targetX; + bestTargetY = targetY; + targetFound = TARGETTYPE_WORKER; + } + } + } + } + } + //explorers are now priority targets as defined later + + if (airTargetGUID != NOGUID) + { + Sint32 otherTeam = Unit::GIDtoTeam(airTargetGUID); + Sint32 targetID = Unit::GIDtoID(airTargetGUID); + Uint32 otherTeamMask = 1<game->teams[otherTeam]->myUnits[targetID]; + if ((owner->sharedVisionExchange & otherTeamMask) == 0) + { + int targetTicks = (256 - testUnit->delta) / testUnit->speed; + // skip this unit if it will move away too soon. + if (targetTicks <= ticksToHit) + continue; + //Using simple calculation for now (should always shoot ground-attackers first, probably) + // adjust score for range + int targetScore = - testUnit->hp; + // lower scores are overriden + if (targetScore > bestScore) + { + bestScore = targetScore; + bestTicks = targetTicks; + bestTargetX = targetX; + bestTargetY = targetY; + targetFound = TARGETTYPE_EXPLORER; + } + } + } + } + + // shoot building only if no unit is found + if (targetFound == TARGETTYPE_NONE) + { + Uint16 targetGBID = map->getBuilding(targetX, targetY); + if (targetGBID != NOGBID) + { + Sint32 otherTeam = Building::GIDtoTeam(targetGBID); + //int otherID = Building::GIDtoID(targetGBID); + Uint32 otherTeamMask = 1< bestScore) + { + bestScore = targetScore; + bestTicks = 256; + bestTargetX = targetX; + bestTargetY = targetY; + targetFound = TARGETTYPE_BUILDING; + } + } + } + } + } + } + if (targetFound == TARGETTYPE_EXPLORER) + break;//specifying explorers as high priority + } + + if (targetFound != TARGETTYPE_NONE) + { + shootingStep = 0; + + //printf("%d found target found: (%d, %d) \n", gid, targetX, targetY); + Sector *s=owner->map->getSector(getMidX(), getMidY()); + + int px, py; + px=((posX)<width)<<4); + py=((posY)<height)<<4); + + int speedX, speedY, ticksLeft; + + // TODO : shall we really uses shootSpeed ? + // FIXME : is it correct this way ? Is there a function for this ? + int dpx=(bestTargetX*Map::TILE_PX)+Map::HALF_TILE_PX-4-px; // 4 is the half size of the bullet + int dpy=(bestTargetY*Map::TILE_PX)+Map::HALF_TILE_PX-4-py; + //printf("%d insert: dp=(%d, %d).\n", gid, dpx, dpy); + if (dpx>(map->getW()<<4)) + dpx=dpx-(map->getW()<getW()<<4)) + dpx=dpx+(map->getW()<(map->getH()<<4)) + dpy=dpy-(map->getH()<getH()<<4)) + dpy=dpy+(map->getH()<abs(dpy)) //we avoid a square root, since all ditances are squares lengthed. + { + mdp=abs(dpx); + speedX=((dpx*type->shootSpeed)/(mdp<shootSpeed)/(mdp<shootSpeed)/(mdp<shootSpeed)/(mdp<shootDamage, bestTargetX, bestTargetY, posX-1, posY-1, type->width+2, type->height+2); + s->bullets.push_front(b); + bullets--; + shootingCooldown = SHOOTING_COOLDOWN_MAX; + lastShootStep = stepCounter; + lastShootSpeedX = speedX; + lastShootSpeedY = speedY; + } + } + +} + + + +void Building::clearingFlagStep() +{ + // PORT: timer is reset inside Map::updateLocalRessources (MapGradientBuilding.cpp:275), not here. + // PORT: also bumped by +=16 from MapPathfindRessource.cpp:189 when units find resources unreachable. + if (unitsWorking.size()<(unsigned)maxUnitWorking) + for (int canSwim=0; canSwimCLEARING_FLAG_REFRESH_TICKS) // Update every 5[s] + { + if (!owner->map->updateLocalRessources(this, canSwim)) + { + // PORT: verify standardRandomActivity() detaches unit->attachedBuilding and updates call lists. + // PORT: if not, the Rust port should call removeUnitFromWorking(unit) per unit instead of clear(). + for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) + (*it)->standardRandomActivity(); + unitsWorking.clear(); + } + } +} + + + diff --git a/src/building/Update.cpp b/src/building/Update.cpp new file mode 100644 index 000000000..7bdb69740 --- /dev/null +++ b/src/building/Update.cpp @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include +#include + +#include "Building.h" +#include "BuildingType.h" +#include "FixedPoint.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Team.h" +#include "Unit.h" +#include "Utilities.h" +#include "Order.h" +#include "Bullet.h" +#include "Integrity.h" + + +void Building::updateBuildingSite(void) +{ + assert(type->isBuildingSite); + + if (isRessourceFull() && (buildingState!=WAITING_FOR_DESTRUCTION)) + { + // we really uses the resources of the building site: + for(int i=0; imaxRessource[i]; + + owner->prestige-=type->prestige; + typeNum=type->nextLevel; + type=globalContainer->buildingsTypes.get(type->nextLevel); + assert(constructionResultState!=NO_CONSTRUCTION); + constructionResultState=NO_CONSTRUCTION; + owner->prestige+=type->prestige; + + //Update the pointer ressources to the newly changed type + updateRessourcesPointer(); + + + //now that building is complete clear the workers + for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); it++) + (*it)->standardRandomActivity(); + unitsWorking.clear(); + + if (type->maxUnitWorking) + { + maxUnitWorking = maxUnitWorkingFuture; + maxUnitWorkingFuture = 0; + } + else + maxUnitWorking=0; + + // The working units still works for us, but + // we don't have any unit in buildings + assert(unitsInside.size()==0); + maxUnitInside=type->maxUnitInside; + + if (hp>=type->hpInit) + hp=type->hpInit; + + productionTimeout=type->unitProductionTime; + if (type->unitProductionTime) + owner->swarms.push_back(this); + if (type->shootingRange) + owner->turrets.push_back(this); + if (type->canExchange) + owner->canExchange.push_back(this); + if (type->isVirtual) + owner->virtualBuildings.push_back(this); + if (type->zonable[WORKER]) + owner->clearingFlags.push_back(this); + + setMapDiscovered(); + owner->pushGameEvent(GameEvent::buildingCompleted(owner->game->stepCounter, getMidX(), getMidY(), shortTypeNum)); + + // we need to do an update again + updateCallLists(); + updateUnitsWorking(); + // no unit harvesting at that point + } +} + + + +void Building::updateUnitsWorking(void) +{ + if (maxUnitWorking==0) + { + // This is only a special optimization case: + for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) + (*it)->standardRandomActivity(); + unitsWorking.clear(); + } + else + { + while (unitsWorking.size()>(unsigned)desiredMaxUnitWorking) + { + int maxDistSquare=0; + + Unit *fu=NULL; + std::list::iterator ittemp; + + // First choice: free a unit who has a not needed ressource.. + for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end();) + { + int r=(*it)->carriedRessource; + if (r>=0 && !neededRessource(r)) + { + fu=(*it); + fu->standardRandomActivity(); + it=unitsWorking.erase(it); + continue; + } else { + ++it; + } + } + if(fu!=NULL) continue; + // Second choice: free a unit who has no ressource.. + if (fu==NULL) + { + int minDistSquare=INT_MAX; + for (std::list::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) + { + int r=(*it)->carriedRessource; + if (r<0) + { + int tx = posX; + int ty = posY; + if((*it)->targetX != -1) + { + tx = (*it)->targetX; + ty = (*it)->targetY; + } + int newDistSquare=distSquare((*it)->posX, (*it)->posY, tx, ty); + if (newDistSquare::iterator it=unitsWorking.begin(); it!=unitsWorking.end(); ++it) + { + int newDistSquare=distSquare((*it)->posX, (*it)->posY, posX, posY); + if (newDistSquare>maxDistSquare) + { + maxDistSquare=newDistSquare; + fu=(*it); + ittemp=it; + } + } + + if (fu!=NULL) + { + if (verbose) + printf("bgid=%d, we free the unit gid=%d\n", gid, fu->gid); + // We free the unit. + fu->standardRandomActivity(); + unitsWorking.erase(ittemp); + } + else + break; + } + } +} + +void Building::updateUnitsHarvesting(void) +{ + // if we are not alive or has not vision, remove all units harvesting from this building + for (std::list::iterator it=unitsHarvesting.begin(); it!=unitsHarvesting.end();) + { + std::list::iterator tmpIt = it; + Unit* u = *tmpIt; + it++; + + // if the building is not available to fetch from (invisible or broken) + if ((buildingState != ALIVE) || ((owner->sharedVisionExchange & u->owner->me) == 0)) + { + // cancel the task u were just doing + u->attachedBuilding->removeUnitFromWorking(u); + // cancel fetching resources here + removeUnitFromHarvesting(u); + // behave randomly + u->standardRandomActivity(); + // TODO: replacing the remove by an erase should be a lot faster but + // it causes the game to crash when a market gets destroyed. No idea + // why. Actually there's no point bothering about this here as this + // method is not performance critical but still it's weired to me + // why it doesn't work the other way round. + // unitsHarvesting.erase(tmpIt); + } + } +} + +void Building::update(void) +{ + computeWishedRessources(wishedResources); + if (buildingState==DEAD) + return; + desiredMaxUnitWorking = desiredNumberOfWorkers(); + updateCallLists(); + updateUnitsWorking(); + updateUnitsHarvesting(); + updateConstructionState(); + if (type->isBuildingSite) + updateBuildingSite(); +} + +void Building::setMapDiscovered(void) +{ + assert(type); + int vr=type->viewingRange; + if (type->canExchange) + owner->map->setMapDiscovered(posX-vr, posY-vr, type->width+vr*2, type->height+vr*2, owner->sharedVisionExchange); + else if (type->canFeedUnit) + owner->map->setMapDiscovered(posX-vr, posY-vr, type->width+vr*2, type->height+vr*2, owner->sharedVisionFood); + else + owner->map->setMapDiscovered(posX-vr, posY-vr, type->width+vr*2, type->height+vr*2, owner->sharedVisionOther); + owner->map->setMapExploredByBuilding(posX-vr, posY-vr, type->width+vr*2, type->height+vr*2, owner->teamNumber); +} + +void Building::getRessourceCountToRepair(int ressources[BASIC_COUNT]) +{ + assert(!type->isBuildingSite); + int repairLevelTypeNum=type->prevLevel; + BuildingType *repairBt=globalContainer->buildingsTypes.get(repairLevelTypeNum); + assert(repairBt); + Sint32 fDestructionRatio=(hp<hpMax; + Sint32 fTotErr=0; + for (int i=0; imaxRessource[i]; + int iVal=(fVal>>FIXED_POINT_SHIFT_16); + fTotErr+=fVal&(int)FIXED_POINT_FRAC_MASK; + if (fTotErr>=(int)FIXED_POINT_ONE) + { + fTotErr-=(int)FIXED_POINT_ONE; + iVal++; + } + ressources[i]=repairBt->maxRessource[i]-iVal; + } +} + +bool Building::tryToBuildingSiteRoom(void) +{ + int midPosX=posX-type->decLeft; + int midPosY=posY-type->decTop; + + int targetLevelTypeNum=BUILDING_LEVEL_NONE; + if (constructionResultState==UPGRADE) + targetLevelTypeNum=type->nextLevel; + else if (constructionResultState==REPAIR) + targetLevelTypeNum=type->prevLevel; + else + assert(false); + + if (targetLevelTypeNum==BUILDING_LEVEL_NONE) + return false; + + BuildingType *targetBt=globalContainer->buildingsTypes.get(targetLevelTypeNum); + int newPosX=midPosX+targetBt->decLeft; + int newPosY=midPosY+targetBt->decTop; + + int newWidth=targetBt->width; + int newHeight=targetBt->height; + + bool isRoom=owner->map->isFreeForBuilding(newPosX, newPosY, newWidth, newHeight, gid); + if (isRoom) + { + if(constructionResultState == UPGRADE) + removeForbiddenZoneFromUpgradeArea(); + + // OK, we have found enough room to expand our building-site, then we set-up the building-site. + if (constructionResultState==REPAIR) + { + Sint32 fDestructionRatio=(hp<hpMax; + Sint32 fTotErr=0; + for (int i=0; imaxRessource[i]; + int iVal=(fVal>>FIXED_POINT_SHIFT_16); + fTotErr+=fVal&(int)FIXED_POINT_FRAC_MASK; + if (fTotErr>=(int)FIXED_POINT_ONE) + { + fTotErr-=(int)FIXED_POINT_ONE; + iVal++; + } + ressources[i]=iVal; + } + } + + if (!type->isVirtual) + { + owner->map->setBuilding(posX, posY, type->width, type->height, NOGBID); + owner->map->setBuilding(newPosX, newPosY, newWidth, newHeight, gid); + } + + + owner->prestige-=type->prestige; + typeNum=targetLevelTypeNum; + type=targetBt; + owner->prestige+=type->prestige; + + //Update the pointer ressources to the newly changed type + updateRessourcesPointer(); + + buildingState=ALIVE; + owner->addToStaticAbilitiesLists(this); + + // towers may already have some stone! + if (constructionResultState==UPGRADE) + for (int i=0; imaxRessource[i]; + if (res>0 && resMax>0) + { + if (res>resMax) + res=resMax; + if (verbose) + printf("using %d ressources[%d] for fast constr (hp+=%d)\n", res, i, res*type->hpInc); + hp+=res*type->hpInc; + } + } + + // units + if (verbose) + printf("bgid=%d, uses maxUnitWorkingPreferred=%d\n", gid, maxUnitWorkingPreferred); + maxUnitWorking=maxUnitWorkingPreferred; + maxUnitInside=type->maxUnitInside; + updateCallLists(); + updateUnitsWorking(); + // no unit harvesting at that point + + // position + posX=newPosX; + posY=newPosY; + + // flag usefull : + unitStayRange=type->defaultUnitStayRange; + + // quality parameters + // hp=type->hpInit; // (Uint16) + + // prefered parameters + productionTimeout=type->unitProductionTime; + + totalRatio=0; + for (int i=0; idecLeft; + int midPosY=posY-type->decTop; + + BuildingType *targetBt=globalContainer->buildingsTypes.get(type->nextLevel); + int newPosX=midPosX+targetBt->decLeft; + int newPosY=midPosY+targetBt->decTop; + int newWidth=targetBt->width; + int newHeight=targetBt->height; + + for(int x=newPosX; x<(newPosX+newWidth); ++x) + { + for(int y=newPosY; y<(newPosY+newHeight); ++y) + { + if (add) + owner->map->addForbidden(x, y, owner->teamNumber); + else + owner->map->removeForbidden(x, y, owner->teamNumber); + } + } + if(owner->teamNumber == owner->map->getLocalTeam()) + owner->map->computeLocalForbidden(owner->teamNumber); + owner->map->updateForbiddenGradient(owner->teamNumber); +} + +void Building::addForbiddenZoneToUpgradeArea(void) { modifyForbiddenZoneForUpgradeArea(true); } +void Building::removeForbiddenZoneFromUpgradeArea(void) { modifyForbiddenZoneForUpgradeArea(false); } + + + +bool Building::isHardSpaceForBuildingSite(void) +{ + return isHardSpaceForBuildingSite(constructionResultState); +} + +bool Building::isHardSpaceForBuildingSite(ConstructionResultState requestedState) +{ + int tltn=BUILDING_LEVEL_NONE; + if (requestedState==UPGRADE) + tltn=type->nextLevel; + else if (requestedState==REPAIR) + tltn=type->prevLevel; + else + assert(false); + + if (tltn==BUILDING_LEVEL_NONE) + return true; + BuildingType *bt=globalContainer->buildingsTypes.get(tltn); + int x=posX+bt->decLeft-type->decLeft; + int y=posY+bt->decTop -type->decTop ; + int w=bt->width; + int h=bt->height; + + if (bt->isVirtual) + return true; + return owner->map->isHardSpaceForBuilding(x, y, w, h, gid); +} + +bool Building::fullInside(void) +{ + if ((type->canFeedUnit) && (ressources[CORN]<=(int)unitsInside.size())) + return true; + else + return ((signed)unitsInside.size()>=maxUnitInside); +} + + +int Building::desiredNumberOfWorkers(void) +{ + //If It's virtual, then this building is a flag and always gets + //full ressources + if(type->isVirtual) + { + return maxUnitWorking; + } + //Otherwise, this building gets what the user desires, up to a limit of 2 units per 1 needed ressource, + //thus if no ressources are needed, then no units will be working here. + int neededRessourcesSum = 0; + for (size_t ri = 0; ri < MAX_RESSOURCES; ri++) + { + int neededRessources = (type->maxRessource[ri] - ressources[ri]) / type->multiplierRessource[ri]; + if (neededRessources > 0) + neededRessourcesSum += neededRessources; + } + int user_num = maxUnitWorking; + int max_considering_ressources = (WISHED_RESOURCE_NUM * neededRessourcesSum) / WISHED_RESOURCE_DEN; + return std::min(user_num, max_considering_ressources); +} + + diff --git a/src/game/entities/BuildingType.h b/src/game/entities/BuildingType.h new file mode 100644 index 000000000..5afd838ef --- /dev/null +++ b/src/game/entities/BuildingType.h @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include +#include +#include + +#include "Ressource.h" +#include "UnitConsts.h" + +namespace GAGCore { class Sprite; } +using GAGCore::Sprite; + +// BuildingType describes the static configuration of one building variant +// (e.g. "swarm0c" — the level-0 swarm under construction). Historically these +// values were loaded at runtime from data/buildings.default.txt + data/buildings.txt +// via the ConfigVector template; they are now baked into a +// per-variant table in buildings.cpp. The fields remain Sint32 for ABI parity +// with the old loader (booleans were stored as ints). +// +// The default member initializers below mirror data/buildings.default.txt so +// that each entry in the table only has to spell out the fields that differ +// from the defaults — same defaults+overrides shape as the text format used +// to provide. +struct BuildingType +{ + // basic infos + std::string type = "null"; + + // visualisation + std::string gameSprite = "ERROR_NO_GAME_SPRITE_DEFINED"; + Sint32 gameSpriteImage = 0; + Sint32 gameSpriteCount = 1; + std::string miniSprite = "ERROR_NO_MINI_SPRITE_DEFINED"; + Sint32 miniSpriteImage = 0; + + Sint32 hueImage = 0; // bool. The way we show the building's team (false=we draw a flag, true=we hue all the sprite) + Sint32 flagImage = 49; + Sint32 crossConnectMultiImage = 0; // If true, mean we have a wall-like building + + // could be Uint8, if non 0 tell the number of maximum units locked by bulding for: + // by order of priority (top = max) + Sint32 upgrade[NB_ABILITY] = {}; // What kind on units can be upgraded here + Sint32 upgradeTime[NB_ABILITY] = {}; // Time to upgrade an unit, given the upgrade type needed. + Sint32 upgradeInParallel = 0; // if true, can learn all upgardes with one learning time into the building + Sint32 foodable = 0; + Sint32 fillable = 0; + Sint32 zonable[NB_UNIT_TYPE] = {}; // If an unit is required for a presence. + Sint32 zonableForbidden = 0; + + Sint32 canFeedUnit = 0; + Sint32 timeToFeedUnit = 0; + Sint32 canHealUnit = 0; + Sint32 timeToHealUnit = 0; + Sint32 insideSpeed = 12; + Sint32 canExchange = 0; + Sint32 useTeamRessources = 0; + + Sint32 width = 0, height = 0; // Uint8, size in square + Sint32 decLeft = 0, decTop = 0; + Sint32 isVirtual = 0; // bool, doesn't occupy ground occupation map, used for war-flag and exploration-flag. + Sint32 isCloacked = 0; // bool, graphicaly invisible for enemy. + Sint32 shootingRange = 0; // Uint8, if 0 can't shoot + Sint32 shootDamage = 0; // Uint8 + Sint32 shootSpeed = 0; // Uint8, the actual speed at which the shots fly through the air. + Sint32 shootRythme = 0; // Uint8, The frequency with which a tower fires. It fires once every + // SHOOTING_COOLDOWN_MAX/shootRythme ticks. + Sint32 maxBullets = 0; + Sint32 multiplierStoneToBullets = 0; // The tower gets this many bullets every time a worker delivers stone to it. + + Sint32 unitProductionTime = 0; // Uint8, nb tick to produce one unit + Sint32 ressourceForOneUnit = 0; // The amount of wheat consumed in the production of a unit. + + Sint32 maxRessource[MAX_NB_RESSOURCES] = {}; + // multiplierRessource defaults: 1 for the basic 5 (wood/corn/papyrus/stone/algue), 10 for fruits 0..9. + Sint32 multiplierRessource[MAX_NB_RESSOURCES] = { 1, 1, 1, 1, 1, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 }; + Sint32 maxUnitInside = 0; + Sint32 maxUnitWorking = 0; + + Sint32 hpInit = 0; // (Uint16) Initial HP of the building. This is generally equal to hpMax for completed buildings, + // equal to 1 for newly created buildings, and equal to the hpMax of the original building for + // upgrading buildings. + Sint32 hpMax = 0; + Sint32 hpInc = 0; // The amount by which the building's hitpoints are incremented when a resource is added to it, + // for buildings under construction. + Sint32 armor = 0; // (Uint8) Any damage the building takes is reduced by this much, although it has a minumum of 1 + // for most damage, 0 only for Explorers. + Sint32 level = 0; // (Uint8) + Sint32 shortTypeNum = 0; // BuildingTypeShortNumber, Should not be used by the main engine, but only to choose the next level building. + Sint32 isBuildingSite = 0; + + // Flag usefull + Sint32 defaultUnitStayRange = 0; + Sint32 maxUnitStayRange = 0; + + Sint32 viewingRange = 1; + Sint32 regenerationSpeed = 0; + + Sint32 prestige = 0; + + // Regenerated parameters — set by BuildingsTypes::init() at startup, not part of the data table. + Sprite *gameSpritePtr = nullptr; + Sprite *miniSpritePtr = nullptr; + int prevLevel = -1; + int nextLevel = -1; +}; + +// BuildingsTypes is the read-only registry of building variants, indexed by an +// integer ID that is the position in the const table (0=swarm0c, 1=swarm0, +// 2=inn0c, …). Those IDs are persisted in saves, replays and network traffic, +// so reordering is a behavioral change. The class keeps the same external +// surface (.get / .getTypeNum / .getByType) as the old ConfigVector +// subclass so existing callers compile unchanged; it is now backed by a +// static array rather than a parsed text file. +class BuildingsTypes +{ +public: + // Resolve sprite pointers and prev/next-level cross-references, and run + // the same integrity checks the old loader did. Replaces the old + // load("data/buildings.default.txt") + load("data/buildings.txt") chain. + void init(); + + BuildingType *get(std::size_t id); + std::size_t size() const; + + Sint32 getTypeNum(const char *type, int level, bool isBuildingSite); + Sint32 getTypeNum(const std::string &s, int level, bool isBuildingSite); + BuildingType *getByType(const char *type, int level, bool isBuildingSite); + BuildingType *getByType(const std::string &s, int level, bool isBuildingSite); +}; diff --git a/src/game/entities/Buildings.cpp b/src/game/entities/Buildings.cpp new file mode 100644 index 000000000..a11d64f6d --- /dev/null +++ b/src/game/entities/Buildings.cpp @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// Umbrella file for the static building-type table (was data/buildings.txt + +// data/buildings.default.txt parsed at startup). The 51-entry table is split +// across two siblings: +// - buildings_part_a.cpp : entries 0..25 (swarm/inn/hospital/racetrack/swimmingpool) +// - buildings_part_b.cpp : entries 26..50 (barracks/school/defencetower/flags/stonewall/market) +// each declaring a non-static BuildingType[] array; this file stitches them +// together via extern declarations and exposes the BuildingsTypes accessor +// surface. The split is purely a file-size accommodation (each part stays +// well under 500 lines); the resulting table is logically a single flat +// vector indexed 0..50, in the same order data/buildings.txt declared. +// +// The order is the in-game integer ID and is persisted in saves, replays +// and network traffic — reordering is a behavioral change. + +#include +#include +#include + +#include +#include + +#include "BuildingType.h" +#include "GlobalContainer.h" + +using namespace GAGCore; + +// Defined in buildings_part_a.cpp / buildings_part_b.cpp. +extern BuildingType g_buildingsPartA[]; +extern const std::size_t g_buildingsPartACount; +extern BuildingType g_buildingsPartB[]; +extern const std::size_t g_buildingsPartBCount; + +// Resolve table[i] for a flat 0..(N-1) index across the two parts. +static BuildingType *entry(std::size_t i) +{ + if (i < g_buildingsPartACount) + return &g_buildingsPartA[i]; + return &g_buildingsPartB[i - g_buildingsPartACount]; +} + +static std::size_t entryCount() +{ + return g_buildingsPartACount + g_buildingsPartBCount; +} + +// Mirror of the legacy ConfigVector::checkIntegrity assertions. +static void checkIntegrity() +{ + const std::size_t count = entryCount(); + for (std::size_t i = 0; i < count; ++i) + { + BuildingType *bt = entry(i); + + // Need ressource integrity: + bool needRessource = false; + for (unsigned j = 0; j < MAX_RESSOURCES; ++j) + if (bt->maxRessource[j]) + { + needRessource = true; + break; + } + if (needRessource) + assert(bt->fillable || bt->foodable); + + // hpInc integrity: + if (bt->isBuildingSite) + assert(bt->hpInc > 0); + else + assert(bt->hpInc == 0); + + // hpMax/hpInit integrity (warning only, matches legacy std::cerr behavior): + if (bt->isBuildingSite && bt->level) + { + assert(bt->prevLevel != -1); + BuildingType *bt2 = entry(static_cast(bt->prevLevel)); + if (bt->hpInit != bt2->hpMax) + { + std::cerr << "BuildingsTypes::init() : warning : " << bt->type + << " : Building site has hpInit=" << bt->hpInit + << ", but final building (level " << bt2->level + << ") has hpMax=" << bt2->hpMax << std::endl; + } + } + + // hpInit/hpInc integrity (warning only): + if (bt->isBuildingSite) + { + int resSum = 0; + for (int j = 0; j < MAX_RESSOURCES; ++j) + resSum += bt->maxRessource[j]; + int hpSum = bt->hpInit + resSum * bt->hpInc; + if (hpSum < bt->hpMax) + { + std::cerr << "BuildingsTypes::init() : warning : " << bt->type + << " : hpSum(" << hpSum << ") < hpMax(" << bt->hpMax + << ") with hpInit=" << bt->hpInit << ", hpInc=" << bt->hpInc + << ", resSum=" << resSum << ". Make hpInc>=" + << (resSum ? (bt->hpMax - bt->hpInit + resSum - 1) / resSum : 0) + << std::endl; + } + } + + // flag integrity: + if (bt->isVirtual) + { + assert(bt->isCloacked); + assert(bt->defaultUnitStayRange); + } + if (bt->isCloacked) + { + assert(bt->isVirtual); + assert(bt->defaultUnitStayRange); + } + if (bt->defaultUnitStayRange) + { + assert(bt->isCloacked); + assert(bt->isVirtual); + } + if (bt->zonableForbidden) + { + assert(bt->isCloacked); + assert(bt->isVirtual); + assert(bt->defaultUnitStayRange); + } + } +} + +// Walk the table once to set prevLevel/nextLevel, mirroring the loader's +// resolveUpgradeReferences. Bidirectional: building-site entries link +// forward to the completed building of the same type+level, and that +// completed building links forward to the next-level building site. +static void resolveUpgradeReferences() +{ + const std::size_t count = entryCount(); + for (std::size_t i = 0; i < count; ++i) + { + entry(i)->prevLevel = -1; + entry(i)->nextLevel = -1; + } + + for (std::size_t i = 0; i < count; ++i) + { + BuildingType *bt1 = entry(i); + for (std::size_t j = 0; j < count; ++j) + { + BuildingType *bt2 = entry(j); + if (bt1 == bt2) + continue; + + if (bt1->isBuildingSite) + { + if (bt2->level == bt1->level && bt2->type == bt1->type && !bt2->isBuildingSite) + { + bt1->nextLevel = static_cast(j); + bt2->prevLevel = static_cast(i); + break; + } + } + else + { + if (bt2->level == bt1->level + 1 && bt2->type == bt1->type && bt2->isBuildingSite) + { + bt1->nextLevel = static_cast(j); + bt2->prevLevel = static_cast(i); + break; + } + } + } + } +} + +void BuildingsTypes::init() +{ + resolveUpgradeReferences(); + + // Resolve sprite pointers, replacing the lazy load that happened inside + // the old loadFromConfigFile. Skips the "null" default block (not in + // this table) and skips on headless runs, same as the original loader. + if (!globalContainer->runNoX) + { + const std::size_t count = entryCount(); + for (std::size_t i = 0; i < count; ++i) + { + BuildingType *bt = entry(i); + if (bt->type == "null") + continue; + bt->gameSpritePtr = Toolkit::getSprite(bt->gameSprite.c_str()); + if (bt->miniSpriteImage >= 0) + bt->miniSpritePtr = Toolkit::getSprite(bt->miniSprite.c_str()); + } + } + + checkIntegrity(); +} + +BuildingType *BuildingsTypes::get(std::size_t id) +{ + if (id < entryCount()) + return entry(id); + std::cerr << "BuildingsTypes::get(" << static_cast(id) + << ") : warning : id is not valid" << std::endl; + assert(false); + return nullptr; +} + +std::size_t BuildingsTypes::size() const +{ + return entryCount(); +} + +Sint32 BuildingsTypes::getTypeNum(const char *type, int level, bool isBuildingSite) +{ + assert(type); + const std::size_t count = entryCount(); + for (std::size_t i = 0; i < count; ++i) + { + const BuildingType *bt = entry(i); + if (bt->type == type && bt->level == level && (bt->isBuildingSite != 0) == isBuildingSite) + return static_cast(i); + } + // Reachable when the caller asks for a flag (which has only one variant). + return -1; +} + +Sint32 BuildingsTypes::getTypeNum(const std::string &s, int level, bool isBuildingSite) +{ + return getTypeNum(s.c_str(), level, isBuildingSite); +} + +BuildingType *BuildingsTypes::getByType(const char *type, int level, bool isBuildingSite) +{ + assert(type); + const std::size_t count = entryCount(); + for (std::size_t i = 0; i < count; ++i) + { + BuildingType *bt = entry(i); + if (bt->type == type && bt->level == level && (bt->isBuildingSite != 0) == isBuildingSite) + return bt; + } + return nullptr; +} + +BuildingType *BuildingsTypes::getByType(const std::string &s, int level, bool isBuildingSite) +{ + return getByType(s.c_str(), level, isBuildingSite); +} diff --git a/src/game/entities/BuildingsPartA.cpp b/src/game/entities/BuildingsPartA.cpp new file mode 100644 index 000000000..a95884d26 --- /dev/null +++ b/src/game/entities/BuildingsPartA.cpp @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// First half of the static building-type table: swarm, inn, hospital, +// racetrack, swimmingpool (entries 0..25). See buildings.cpp for the +// umbrella file that stitches the two halves together and exposes the +// BuildingsTypes accessor surface, and for an explanation of the +// designated-initializer + defaults transcription. + +#include "BuildingType.h" + +BuildingType g_buildingsPartA[] = { + // 0: swarm0c (level 0, under construction) + { .type = "swarm", + .gameSprite = "data/gfx/swarm0c", .miniSprite = "data/gfx/miniswarm0c", + .hueImage = 1, + .fillable = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxRessource = { /*wood*/0, /*corn*/35 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 700, .hpInc = 20, + .level = 0, .shortTypeNum = 0, .isBuildingSite = 1 }, + + // 1: swarm0 (level 0, completed) + { .type = "swarm", + .gameSprite = "data/gfx/swarm0b", .miniSprite = "data/gfx/miniswarm0b", + .hueImage = 1, + .fillable = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .unitProductionTime = 150, .ressourceForOneUnit = 5, + .maxRessource = { /*wood*/0, /*corn*/20 }, + .maxUnitWorking = 1, + .hpInit = 700, .hpMax = 700, + .level = 0, .shortTypeNum = 0, + .viewingRange = 4, .regenerationSpeed = 3 }, + + // 2: inn0c (level 0, under construction) + { .type = "inn", + .gameSprite = "data/gfx/inn0c", .miniSprite = "data/gfx/miniinn0c", + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/3 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 200, .hpInc = 67, + .level = 0, .shortTypeNum = 1, .isBuildingSite = 1 }, + + // 3: inn0 (level 0, completed) + { .type = "inn", + .gameSprite = "data/gfx/inn0b", .gameSpriteCount = 2, .miniSprite = "data/gfx/miniinn0b", + .foodable = 1, + .canFeedUnit = 1, .timeToFeedUnit = 24, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/0, /*corn*/10, /*papyrus*/0, /*stone*/0, /*algue*/0, + /*fruit0*/40, /*fruit1*/40, /*fruit2*/40 }, + .maxUnitInside = 4, + .maxUnitWorking = 1, + .hpInit = 200, .hpMax = 200, + .level = 0, .shortTypeNum = 1 }, + + // 4: inn1c (level 1, under construction) + { .type = "inn", + .gameSprite = "data/gfx/inn1c", .miniSprite = "data/gfx/miniinn1c", + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/8 }, + .maxUnitWorking = 1, + .hpInit = 200, .hpMax = 500, .hpInc = 38, + .level = 1, .shortTypeNum = 1, .isBuildingSite = 1 }, + + // 5: inn1 (level 1, completed) + { .type = "inn", + .gameSprite = "data/gfx/inn1b", .gameSpriteCount = 2, .miniSprite = "data/gfx/miniinn1b", + .foodable = 1, + .canFeedUnit = 1, .timeToFeedUnit = 15, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/0, /*corn*/30, /*papyrus*/0, /*stone*/0, /*algue*/0, + /*fruit0*/80, /*fruit1*/80, /*fruit2*/80 }, + .maxUnitInside = 7, + .maxUnitWorking = 1, + .hpInit = 500, .hpMax = 500, + .armor = 5, + .level = 1, .shortTypeNum = 1 }, + + // 6: inn2c (level 2, under construction) + { .type = "inn", + .gameSprite = "data/gfx/inn2c", .miniSprite = "data/gfx/miniinn2c", + .fillable = 1, + .width = 3, .height = 3, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/7, /*corn*/0, /*papyrus*/0, /*stone*/5 }, + .maxUnitWorking = 1, + .hpInit = 500, .hpMax = 700, .hpInc = 17, + .armor = 5, + .level = 2, .shortTypeNum = 1, .isBuildingSite = 1 }, + + // 7: inn2 (level 2, completed) + { .type = "inn", + .gameSprite = "data/gfx/inn2b", .miniSprite = "data/gfx/miniinn2b", + .foodable = 1, + .canFeedUnit = 1, .timeToFeedUnit = 9, + .width = 3, .height = 3, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/0, /*corn*/50, /*papyrus*/0, /*stone*/0, /*algue*/0, + /*fruit0*/200, /*fruit1*/200, /*fruit2*/200 }, + .maxUnitInside = 17, + .maxUnitWorking = 1, + .hpInit = 700, .hpMax = 700, + .armor = 10, + .level = 2, .shortTypeNum = 1 }, + + // 8: hospital0c (level 0, under construction) + { .type = "hospital", + .gameSprite = "data/gfx/hosp0c", .miniSprite = "data/gfx/minihosp0c", + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/3 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 260, .hpInc = 87, + .level = 0, .shortTypeNum = 2, .isBuildingSite = 1 }, + + // 9: hospital0 (level 0, completed) + { .type = "hospital", + .gameSprite = "data/gfx/hosp0b", .gameSpriteCount = 2, .miniSprite = "data/gfx/minihosp0b", + .canHealUnit = 1, .timeToHealUnit = 30, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxUnitInside = 2, + .hpInit = 260, .hpMax = 260, + .armor = 5, + .level = 0, .shortTypeNum = 2 }, + + // 10: hospital1c (level 1, under construction) + { .type = "hospital", + .gameSprite = "data/gfx/hosp1c", .miniSprite = "data/gfx/minihosp1c", + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/8 }, + .maxUnitWorking = 1, + .hpInit = 260, .hpMax = 500, .hpInc = 30, + .level = 1, .shortTypeNum = 2, .isBuildingSite = 1 }, + + // 11: hospital1 (level 1, completed) + { .type = "hospital", + .gameSprite = "data/gfx/hosp1b", .miniSprite = "data/gfx/minihosp1b", + .canHealUnit = 1, .timeToHealUnit = 18, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxUnitInside = 5, + .hpInit = 500, .hpMax = 500, + .armor = 5, + .level = 1, .shortTypeNum = 2 }, + + // 12: hospital2c (level 2, under construction) + { .type = "hospital", + .gameSprite = "data/gfx/hosp2c", .miniSprite = "data/gfx/minihosp2c", + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/3, /*corn*/0, /*papyrus*/0, /*stone*/5 }, + .maxUnitWorking = 1, + .hpInit = 500, .hpMax = 700, .hpInc = 25, + .armor = 5, + .level = 2, .shortTypeNum = 2, .isBuildingSite = 1 }, + + // 13: hospital2 (level 2, completed) + { .type = "hospital", + .gameSprite = "data/gfx/hosp2b", .miniSprite = "data/gfx/minihosp2b", + .canHealUnit = 1, .timeToHealUnit = 6, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxUnitInside = 7, + .hpInit = 700, .hpMax = 700, + .armor = 10, + .level = 2, .shortTypeNum = 2 }, + + // 14: racetrack0c (level 0, under construction) + { .type = "racetrack", + .gameSprite = "data/gfx/racetrack0c", .miniSprite = "data/gfx/miniracetrack0c", + .fillable = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxRessource = { /*wood*/6, /*corn*/0, /*papyrus*/0, /*stone*/1 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 675, .hpInc = 97, + .armor = 0, + .level = 0, .shortTypeNum = 3, .isBuildingSite = 1 }, + + // 15: racetrack0 (level 0, completed) — upgrade[3]=Walk, upgradeTime[3]=21 + { .type = "racetrack", + .gameSprite = "data/gfx/racetrack0b", .gameSpriteCount = 3, .miniSprite = "data/gfx/miniracetrack0b", + .upgrade = { 0, 0, 0, 1 }, + .upgradeTime = { 0, 0, 0, 21 }, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxUnitInside = 2, + .hpInit = 675, .hpMax = 675, + .armor = 5, + .level = 0, .shortTypeNum = 3 }, + + // 16: racetrack1c (level 1, under construction) — generic buildingsite sprite + { .type = "racetrack", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 5, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 5, + .fillable = 1, + .width = 6, .height = 6, .decLeft = -3, .decTop = -3, + .maxRessource = { /*wood*/10, /*corn*/0, /*papyrus*/0, /*stone*/5 }, + .maxUnitWorking = 1, + .hpInit = 675, .hpMax = 1000, .hpInc = 22, + .armor = 5, + .level = 1, .shortTypeNum = 3, .isBuildingSite = 1 }, + + // 17: racetrack1 (level 1, completed) + { .type = "racetrack", + .gameSprite = "data/gfx/racetrack1b", .gameSpriteCount = 3, .miniSprite = "data/gfx/miniracetrack1b", + .upgrade = { 0, 0, 0, 1 }, + .upgradeTime = { 0, 0, 0, 21 }, + .width = 6, .height = 6, .decLeft = -3, .decTop = -3, + .maxUnitInside = 4, + .hpInit = 1000, .hpMax = 1000, + .armor = 10, + .level = 1, .shortTypeNum = 3 }, + + // 18: racetrack2c (level 2, under construction) + { .type = "racetrack", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 5, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 5, + .fillable = 1, + .width = 6, .height = 6, .decLeft = -3, .decTop = -3, + .maxRessource = { /*wood*/15, /*corn*/0, /*papyrus*/0, /*stone*/5 }, + .maxUnitWorking = 1, + .hpInit = 1000, .hpMax = 1500, .hpInc = 25, + .armor = 10, + .level = 2, .shortTypeNum = 3, .isBuildingSite = 1 }, + + // 19: racetrack2 (level 2, completed) + { .type = "racetrack", + .gameSprite = "data/gfx/racetrack2b", .miniSprite = "data/gfx/miniracetrack2b", + .upgrade = { 0, 0, 0, 1 }, + .upgradeTime = { 0, 0, 0, 24 }, + .width = 6, .height = 6, .decLeft = -3, .decTop = -3, + .maxUnitInside = 6, + .hpInit = 1500, .hpMax = 1500, + .armor = 12, + .level = 2, .shortTypeNum = 3 }, + + // 20: swimmingpool0c (level 0, under construction) + { .type = "swimmingpool", + .gameSprite = "data/gfx/pool0c", .miniSprite = "data/gfx/minipool0c", + .fillable = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxRessource = { /*wood*/8 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 675, .hpInc = 97, + .armor = 0, + .level = 0, .shortTypeNum = 4, .isBuildingSite = 1 }, + + // 21: swimmingpool0 (level 0, completed) — upgrade[4]=Swim + { .type = "swimmingpool", + .gameSprite = "data/gfx/pool0b", .gameSpriteCount = 2, .miniSprite = "data/gfx/minipool0b", + .upgrade = { 0, 0, 0, 0, 1 }, + .upgradeTime = { 0, 0, 0, 0, 21 }, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxUnitInside = 2, + .hpInit = 675, .hpMax = 675, + .armor = 5, + .level = 0, .shortTypeNum = 4 }, + + // 22: swimmingpool1c (level 1, under construction) + { .type = "swimmingpool", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 5, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 5, + .fillable = 1, + .width = 6, .height = 6, .decLeft = -3, .decTop = -3, + .maxRessource = { /*wood*/12, /*corn*/6 }, + .maxUnitWorking = 1, + .hpInit = 675, .hpMax = 1000, .hpInc = 19, + .armor = 5, + .level = 1, .shortTypeNum = 4, .isBuildingSite = 1 }, + + // 23: swimmingpool1 (level 1, completed) + { .type = "swimmingpool", + .gameSprite = "data/gfx/pool1b", .miniSprite = "data/gfx/minipool1b", + .upgrade = { 0, 0, 0, 0, 1 }, + .upgradeTime = { 0, 0, 0, 0, 21 }, + .width = 6, .height = 6, .decLeft = -3, .decTop = -3, + .maxUnitInside = 4, + .hpInit = 1000, .hpMax = 1000, + .armor = 8, + .level = 1, .shortTypeNum = 4 }, + + // 24: swimmingpool2c (level 2, under construction) + { .type = "swimmingpool", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 5, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 5, + .fillable = 1, + .width = 6, .height = 6, .decLeft = -3, .decTop = -3, + .maxRessource = { /*wood*/8, /*corn*/4, /*papyrus*/0, /*stone*/6, /*algue*/8 }, + .maxUnitWorking = 1, + .hpInit = 1000, .hpMax = 1500, .hpInc = 20, + .armor = 8, + .level = 2, .shortTypeNum = 4, .isBuildingSite = 1 }, + + // 25: swimmingpool2 (level 2, completed) + { .type = "swimmingpool", + .gameSprite = "data/gfx/pool2b", .miniSprite = "data/gfx/minipool2b", + .upgrade = { 0, 0, 0, 0, 1 }, + .upgradeTime = { 0, 0, 0, 0, 24 }, + .width = 6, .height = 6, .decLeft = -3, .decTop = -3, + .maxUnitInside = 6, + .hpInit = 1500, .hpMax = 1500, + .armor = 12, + .level = 2, .shortTypeNum = 4 }, +}; + +extern const std::size_t g_buildingsPartACount = + sizeof(g_buildingsPartA) / sizeof(g_buildingsPartA[0]); diff --git a/src/game/entities/BuildingsPartB.cpp b/src/game/entities/BuildingsPartB.cpp new file mode 100644 index 000000000..90301212b --- /dev/null +++ b/src/game/entities/BuildingsPartB.cpp @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// Second half of the static building-type table: barracks, school, +// defencetower, flags (exploration/war/clearing), stonewall, market +// (entries 26..50). See buildings.cpp for the umbrella file that +// stitches the two halves together. + +#include "BuildingType.h" + +BuildingType g_buildingsPartB[] = { + // 26: barracks0c (level 0, under construction) + { .type = "barracks", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 3, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 3, + .fillable = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxRessource = { /*wood*/7 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 440, .hpInc = 63, + .armor = 0, + .level = 0, .shortTypeNum = 5, .isBuildingSite = 1 }, + + // 27: barracks0 (level 0, completed) — upgrade[8]=AttackSpeed, upgrade[9]=AttackStrength + { .type = "barracks", + .gameSprite = "data/gfx/barracks0b", .miniSprite = "data/gfx/minibarracks0b", + .upgrade = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + .upgradeTime = { 0, 0, 0, 0, 0, 0, 0, 0, 21, 21 }, + .upgradeInParallel = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxUnitInside = 2, + .hpInit = 440, .hpMax = 440, + .armor = 5, + .level = 0, .shortTypeNum = 5 }, + + // 28: barracks1c (level 1, under construction) + { .type = "barracks", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 3, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 3, + .fillable = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxRessource = { /*wood*/3, /*corn*/0, /*papyrus*/0, /*stone*/10 }, + .maxUnitWorking = 1, + .hpInit = 440, .hpMax = 800, .hpInc = 28, + .armor = 5, + .level = 1, .shortTypeNum = 5, .isBuildingSite = 1 }, + + // 29: barracks1 (level 1, completed) + { .type = "barracks", + .gameSprite = "data/gfx/barracks1b", .miniSprite = "data/gfx/minibarracks1b", + .upgrade = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + .upgradeTime = { 0, 0, 0, 0, 0, 0, 0, 0, 30, 30 }, + .upgradeInParallel = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxUnitInside = 4, + .hpInit = 800, .hpMax = 800, + .armor = 10, + .level = 1, .shortTypeNum = 5 }, + + // 30: barracks2c (level 2, under construction) + { .type = "barracks", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 3, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 3, + .fillable = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxRessource = { /*wood*/10, /*corn*/0, /*papyrus*/0, /*stone*/10 }, + .maxUnitWorking = 1, + .hpInit = 800, .hpMax = 1300, .hpInc = 25, + .armor = 10, + .level = 2, .shortTypeNum = 5, .isBuildingSite = 1 }, + + // 31: barracks2 (level 2, completed) + { .type = "barracks", + .gameSprite = "data/gfx/barracks2b", .miniSprite = "data/gfx/minibarracks2b", + .upgrade = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + .upgradeTime = { 0, 0, 0, 0, 0, 0, 0, 0, 42, 42 }, + .upgradeInParallel = 1, + .width = 4, .height = 4, .decLeft = -2, .decTop = -2, + .maxUnitInside = 5, + .hpInit = 1300, .hpMax = 1300, + .armor = 12, + .level = 2, .shortTypeNum = 5 }, + + // 32: school0c (level 0, under construction) + { .type = "school", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 1, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 1, + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/7, /*corn*/0, /*papyrus*/0, /*stone*/0, /*algue*/2 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 360, .hpInc = 40, + .armor = 0, + .level = 0, .shortTypeNum = 6, .isBuildingSite = 1 }, + + // 33: school0 (level 0, completed) — upgrade[6]=Build, upgrade[7]=Harvest + { .type = "school", + .gameSprite = "data/gfx/school0b", .gameSpriteCount = 2, .miniSprite = "data/gfx/minischool0b", + .upgrade = { 0, 0, 0, 0, 0, 0, 1, 1 }, + .upgradeTime = { 0, 0, 0, 0, 0, 0, 21, 21 }, + .upgradeInParallel = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxUnitInside = 4, + .hpInit = 360, .hpMax = 360, + .armor = 3, + .level = 0, .shortTypeNum = 6 }, + + // 34: school1c (level 1, under construction) + { .type = "school", + .gameSprite = "data/gfx/school1c", .miniSprite = "data/gfx/minischool1c", + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/5, /*corn*/0, /*papyrus*/0, /*stone*/5, /*algue*/12 }, + .maxUnitWorking = 1, + .hpInit = 360, .hpMax = 520, .hpInc = 8, + .armor = 3, + .level = 1, .shortTypeNum = 6, .isBuildingSite = 1 }, + + // 35: school1 (level 1, completed) + { .type = "school", + .gameSprite = "data/gfx/school1b", .gameSpriteCount = 3, .miniSprite = "data/gfx/minischool1b", + .upgrade = { 0, 0, 0, 0, 0, 0, 1, 1 }, + .upgradeTime = { 0, 0, 0, 0, 0, 0, 33, 33 }, + .upgradeInParallel = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxUnitInside = 7, + .hpInit = 520, .hpMax = 520, + .armor = 8, + .level = 1, .shortTypeNum = 6 }, + + // 36: school2c (level 2, under construction) + { .type = "school", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 1, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 1, + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/7, /*corn*/4, /*papyrus*/0, /*stone*/12, /*algue*/10 }, + .maxUnitWorking = 1, + .hpInit = 520, .hpMax = 700, .hpInc = 6, + .armor = 8, + .level = 2, .shortTypeNum = 6, .isBuildingSite = 1 }, + + // 37: school2 (level 2, completed) — adds upgrade[11]=MagicAttackGround + { .type = "school", + .gameSprite = "data/gfx/school2b", .miniSprite = "data/gfx/minischool2b", + .upgrade = { 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1 }, + .upgradeTime = { 0, 0, 0, 0, 0, 0, 42, 42, 0, 0, 0, 42 }, + .upgradeInParallel = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxUnitInside = 9, + .hpInit = 700, .hpMax = 700, + .armor = 12, + .level = 2, .shortTypeNum = 6, + .prestige = 50 }, + + // 38: defencetower0c (level 0, under construction) + { .type = "defencetower", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 1, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 1, + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/6 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 480, .hpInc = 80, + .armor = 0, + .level = 0, .shortTypeNum = 7, .isBuildingSite = 1 }, + + // 39: defencetower0 (level 0, completed) — short-range stone tower + { .type = "defencetower", + .gameSprite = "data/gfx/defencetower0b", .miniSprite = "data/gfx/minidefencetower0b", + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .shootingRange = 5, .shootDamage = 30, .shootSpeed = 5000, .shootRythme = 1700, + .maxBullets = 12, .multiplierStoneToBullets = 3, + .maxRessource = { /*wood*/0, /*corn*/0, /*papyrus*/0, /*stone*/4 }, + .maxUnitWorking = 1, + .hpInit = 480, .hpMax = 480, + .armor = 8, + .level = 0, .shortTypeNum = 7, + .viewingRange = 6 }, + + // 40: defencetower1c (level 1, under construction) + { .type = "defencetower", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 1, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 1, + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/10, /*corn*/0, /*papyrus*/0, /*stone*/14 }, + .maxUnitWorking = 1, + .hpInit = 480, .hpMax = 1440, .hpInc = 40, + .armor = 8, + .level = 1, .shortTypeNum = 7, .isBuildingSite = 1, + .viewingRange = 5 }, + + // 41: defencetower1 (level 1, completed) + { .type = "defencetower", + .gameSprite = "data/gfx/defencetower1b", .gameSpriteCount = 3, .miniSprite = "data/gfx/minidefencetower1b", + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .shootingRange = 7, .shootDamage = 40, .shootSpeed = 5700, .shootRythme = 1800, + .maxBullets = 16, .multiplierStoneToBullets = 4, + .maxRessource = { /*wood*/0, /*corn*/0, /*papyrus*/0, /*stone*/4 }, + .maxUnitWorking = 1, + .hpInit = 1440, .hpMax = 1440, + .armor = 12, + .level = 1, .shortTypeNum = 7, + .viewingRange = 7 }, + + // 42: defencetower2c (level 2, under construction) + { .type = "defencetower", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 1, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 1, + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/8, /*corn*/0, /*papyrus*/0, /*stone*/14, /*algue*/2 }, + .maxUnitWorking = 1, + .hpInit = 1440, .hpMax = 2000, .hpInc = 24, + .armor = 12, + .level = 2, .shortTypeNum = 7, .isBuildingSite = 1, + .viewingRange = 6 }, + + // 43: defencetower2 (level 2, completed) — long-range stone tower + { .type = "defencetower", + .gameSprite = "data/gfx/defencetower2b", .miniSprite = "data/gfx/minidefencetower2b", + .fillable = 1, + .width = 2, .height = 2, .decLeft = -1, .decTop = -1, + .shootingRange = 9, .shootDamage = 50, .shootSpeed = 7000, .shootRythme = 1900, + .maxBullets = 20, .multiplierStoneToBullets = 7, + .maxRessource = { /*wood*/0, /*corn*/0, /*papyrus*/0, /*stone*/4 }, + .maxUnitWorking = 1, + .hpInit = 2000, .hpMax = 2000, + .armor = 15, + .level = 2, .shortTypeNum = 7, + .viewingRange = 8 }, + + // 44: explorationflag0 + // Virtual flag — gameSprite override only (no miniSprite override). The data + // file says "miniSpriteImage -1", which leaves miniSprite at the default + // "ERROR_NO_MINI_SPRITE_DEFINED"; the -1 image suppresses miniSpritePtr load. + { .type = "explorationflag", + .gameSprite = "data/gfx/explorationflag", + .miniSpriteImage = -1, + .hueImage = 1, + .zonable = { /*worker*/0, /*explorer*/1 }, + .width = 1, .height = 1, + .isVirtual = 1, .isCloacked = 1, + .maxUnitWorking = 1, + .shortTypeNum = 8, + .defaultUnitStayRange = 10, .maxUnitStayRange = 20 }, + + // 45: warflag0 + { .type = "warflag", + .gameSprite = "data/gfx/warflag", + .miniSpriteImage = -1, + .hueImage = 1, + .zonable = { /*worker*/0, /*explorer*/0, /*warrior*/1 }, + .width = 1, .height = 1, + .isVirtual = 1, .isCloacked = 1, + .maxUnitWorking = 1, + .shortTypeNum = 9, + .defaultUnitStayRange = 4, .maxUnitStayRange = 8 }, + + // 46: clearingflag0 + { .type = "clearingflag", + .gameSprite = "data/gfx/clearingflag", + .miniSpriteImage = -1, + .hueImage = 1, + .zonable = { /*worker*/1 }, + .width = 1, .height = 1, + .isVirtual = 1, .isCloacked = 1, + .maxUnitWorking = 1, + .shortTypeNum = 10, + .defaultUnitStayRange = 3, .maxUnitStayRange = 14 }, + + // 47: stonewall0c + // In the data file the parser sees "hueImage 1;" — the trailing semicolon + // is silently dropped by std::istringstream when reading int, so this is + // equivalent to "hueImage 1". The duplicate "miniSpriteImage" line + // overwrites the earlier value (final = -1). + { .type = "stonewall", + .gameSprite = "data/gfx/wallc", + .miniSpriteImage = -1, + .hueImage = 1, + .fillable = 1, + .width = 1, .height = 1, + .maxRessource = { /*wood*/0, /*corn*/0, /*papyrus*/0, /*stone*/1 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 180, .hpInc = 180, + .level = 0, .shortTypeNum = 11, .isBuildingSite = 1 }, + + // 48: stonewall0 + // Same trailing-semicolon and duplicate-key behavior as stonewall0c. + { .type = "stonewall", + .gameSprite = "data/gfx/wall", + .miniSpriteImage = -1, + .hueImage = 1, + .crossConnectMultiImage = 1, + .width = 1, .height = 1, + .hpInit = 180, .hpMax = 180, + .armor = 10, + .level = 0, .shortTypeNum = 11 }, + + // 49: market0c (level 0, under construction) + { .type = "market", + .gameSprite = "data/gfx/buildingsite", .gameSpriteImage = 2, + .miniSprite = "data/gfx/minibuildingsite", .miniSpriteImage = 2, + .fillable = 1, + .width = 3, .height = 3, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/4, /*corn*/0, /*papyrus*/0, /*stone*/4 }, + .maxUnitWorking = 1, + .hpInit = 1, .hpMax = 400, .hpInc = 50, + .level = 0, .shortTypeNum = 12, .isBuildingSite = 1 }, + + // 50: market0 (level 0, completed) — exchanges fruit between teams + { .type = "market", + .gameSprite = "data/gfx/market0b", .miniSprite = "data/gfx/minimarket0b", + .fillable = 1, + .canExchange = 1, .useTeamRessources = 1, + .width = 3, .height = 3, .decLeft = -1, .decTop = -1, + .maxRessource = { /*wood*/0, /*corn*/0, /*papyrus*/0, /*stone*/0, /*algue*/0, + /*fruit0*/200, /*fruit1*/200, /*fruit2*/200 }, + .maxUnitWorking = 1, + .hpInit = 400, .hpMax = 400, + .armor = 6, + .level = 0, .shortTypeNum = 12 }, +}; + +extern const std::size_t g_buildingsPartBCount = + sizeof(g_buildingsPartB) / sizeof(g_buildingsPartB[0]); diff --git a/src/game/entities/Race.cpp b/src/game/entities/Race.cpp new file mode 100644 index 000000000..80c5cb336 --- /dev/null +++ b/src/game/entities/Race.cpp @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include + +#include "Race.h" + +UnitType Race::unitTypes[NB_UNIT_TYPE][NB_UNIT_LEVELS]; +Sint32 Race::hungryness; + +namespace +{ + // Compile-time defaults transcribed from the legacy data/units.txt. Index + // is [unit_type][level] — outer dim is WORKER/EXPLORER/WARRIOR, inner is + // upgrade level 0..3. Values are positional in the array fields: + // startImage[NB_MOVE=9] = { stopWalk, stopSwim, stopFly, + // walk, swim, fly, build, harvest, attack } + // performance[NB_ABILITY=17] = { stopWalk, stopSwim, stopFly, + // walk, swim, fly, build, harvest, + // attackSpeed, attackForce, + // magicAttackAir, magicAttackGround, + // magicCreateWood, magicCreateCorn, + // magicCreateAlga, armor, hpMax } + const UnitType kDefaultUnitTypes[NB_UNIT_TYPE][NB_UNIT_LEVELS] = { + // WORKER (baseWorker) + { + // level 0 + { .startImage = {64, 128, 0, 64, 128, 0, 192, 192, 0}, + .hungryness = 350, + .performance = {8, 8, 0, 16, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 200}, + .harvestDamage = 10, + .armorReductionPerHappyness = 0, + .experiencePerLevel = 0, + .magicActionCooldown = 0 }, + // level 1 + { .startImage = {64, 128, 0, 64, 128, 0, 192, 192, 0}, + .hungryness = 350, + .performance = {8, 8, 0, 21, 10, 0, 12, 9, 0, 0, 0, 0, 0, 0, 0, 0, 200}, + .harvestDamage = 10, + .armorReductionPerHappyness = 0, + .experiencePerLevel = 0, + .magicActionCooldown = 0 }, + // level 2 + { .startImage = {64, 128, 0, 64, 128, 0, 192, 192, 0}, + .hungryness = 350, + .performance = {8, 8, 0, 26, 20, 0, 16, 10, 0, 0, 0, 0, 0, 0, 0, 0, 200}, + .harvestDamage = 10, + .armorReductionPerHappyness = 0, + .experiencePerLevel = 0, + .magicActionCooldown = 0 }, + // level 3 + { .startImage = {64, 128, 0, 64, 128, 0, 192, 192, 0}, + .hungryness = 350, + .performance = {8, 8, 0, 30, 30, 0, 20, 11, 0, 0, 0, 0, 0, 0, 0, 0, 200}, + .harvestDamage = 10, + .armorReductionPerHappyness = 0, + .experiencePerLevel = 0, + .magicActionCooldown = 0 }, + }, + // EXPLORER (baseExplorer) + { + // level 0 + { .startImage = {0, 0, 0, 0, 0, 0, 0, 0, 0}, + .hungryness = 350, + .performance = {8, 8, 0, 0, 0, 28, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 38}, + .harvestDamage = 0, + .armorReductionPerHappyness = 1, + .experiencePerLevel = 50, + .magicActionCooldown = 3 }, + // level 1 (editor-only) + { .startImage = {0, 0, 0, 0, 0, 0, 0, 0, 0}, + .hungryness = 350, + .performance = {8, 8, 0, 0, 0, 28, 0, 0, 0, 0, 6, 0, 4, 4, 4, 0, 38}, + .harvestDamage = 0, + .armorReductionPerHappyness = 1, + .experiencePerLevel = 50, + .magicActionCooldown = 3 }, + // level 2 (editor-only) + { .startImage = {0, 0, 0, 0, 0, 0, 0, 0, 0}, + .hungryness = 350, + .performance = {8, 8, 0, 0, 0, 28, 0, 0, 0, 0, 6, 0, 3, 3, 3, 0, 38}, + .harvestDamage = 0, + .armorReductionPerHappyness = 1, + .experiencePerLevel = 50, + .magicActionCooldown = 3 }, + // level 3 + { .startImage = {0, 0, 0, 0, 0, 0, 0, 0, 0}, + .hungryness = 350, + .performance = {8, 8, 0, 0, 0, 28, 0, 0, 0, 0, 6, 8, 2, 2, 2, 0, 38}, + .harvestDamage = 0, + .armorReductionPerHappyness = 1, + .experiencePerLevel = 50, + .magicActionCooldown = 3 }, + }, + // WARRIOR (baseWarrior) + { + // level 0 + { .startImage = {256, 320, 0, 256, 320, 0, 0, 0, 384}, + .hungryness = 350, + .performance = {8, 8, 0, 16, 0, 0, 0, 0, 12, 13, 0, 0, 0, 0, 0, 10, 250}, + .harvestDamage = 0, + .armorReductionPerHappyness = 10, + .experiencePerLevel = 20, + .magicActionCooldown = 0 }, + // level 1 + { .startImage = {256, 320, 0, 256, 320, 0, 0, 0, 384}, + .hungryness = 350, + .performance = {8, 8, 0, 21, 8, 0, 0, 0, 16, 14, 0, 0, 0, 0, 0, 10, 250}, + .harvestDamage = 0, + .armorReductionPerHappyness = 10, + .experiencePerLevel = 20, + .magicActionCooldown = 0 }, + // level 2 + { .startImage = {256, 320, 0, 256, 320, 0, 0, 0, 384}, + .hungryness = 350, + .performance = {8, 8, 0, 26, 16, 0, 0, 0, 22, 15, 0, 0, 0, 0, 0, 10, 250}, + .harvestDamage = 0, + .armorReductionPerHappyness = 10, + .experiencePerLevel = 20, + .magicActionCooldown = 0 }, + // level 3 + { .startImage = {256, 320, 0, 256, 320, 0, 0, 0, 384}, + .hungryness = 350, + .performance = {8, 8, 0, 30, 24, 0, 0, 0, 28, 16, 0, 0, 0, 0, 0, 10, 250}, + .harvestDamage = 0, + .armorReductionPerHappyness = 10, + .experiencePerLevel = 20, + .magicActionCooldown = 0 }, + }, + }; + + const Sint32 kDefaultRaceHungryness = 425; +} + +Race::Race() +{ +} + +Race::~Race() +{ +} + +void Race::loadDefault() +{ + hungryness = kDefaultRaceHungryness; + for (int t = 0; t < NB_UNIT_TYPE; ++t) + for (int l = 0; l < NB_UNIT_LEVELS; ++l) + unitTypes[t][l] = kDefaultUnitTypes[t][l]; +} + +void Race::load() +{ +} + +UnitType *Race::getUnitType(int type, int level) +{ + assert (level>=0); + assert (level=0); + assert (typewriteSint32(hungryness, "hungryness"); +} + +bool Race::load(GAGCore::InputStream *stream, Sint32 versionMinor) +{ + for (int i=0; ireadSint32("hungryness"); + + return true; +} diff --git a/src/game/entities/Race.h b/src/game/entities/Race.h new file mode 100644 index 000000000..dc8cc0511 --- /dev/null +++ b/src/game/entities/Race.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include "UnitType.h" + +namespace GAGCore +{ + class InputStream; + class OutputStream; +} + +class Race +{ +public: + static UnitType unitTypes[NB_UNIT_TYPE][NB_UNIT_LEVELS]; + static Sint32 hungryness; + +public: + Race(); + virtual ~Race(); + + void load(); + // Installs the compile-time default unit-type table from race.cpp into + // Race::unitTypes (and seeds Race::hungryness). Replaces the previous + // runtime parser of data/units.txt. + static void loadDefault(); + + UnitType *getUnitType(int type, int level); + + void save(GAGCore::OutputStream *stream); + bool load(GAGCore::InputStream *stream, Sint32 versionMinor); +}; diff --git a/src/game/entities/Resources.cpp b/src/game/entities/Resources.cpp new file mode 100644 index 000000000..638078e93 --- /dev/null +++ b/src/game/entities/Resources.cpp @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "RessourceType.h" + +// Compile-time const table of resource types. The order MUST match the +// integer IDs declared in Ressource.h (WOOD=0, CORN=1, PAPYRUS=2, STONE=3, +// ALGA=4, CHERRY=5, ORANGE=6, PRUNE=7) — those IDs are persisted in saves, +// replays and network traffic, so reordering is a behavioral change. +// +// Values are transcribed from data/ressources.txt (which used a defaults + +// per-section overrides format); each entry below spells out every field +// explicitly. The 'clearable' field replaces a hard-coded predicate that +// previously listed WOOD/CORN/PAPYRUS/ALGA at the call sites in +// UnitMovement.cpp and MapGradientArea.cpp. +static constexpr RessourceType kRessourceTypes[] = { + // WOOD + { /*terrain*/ 2, /*gfxId*/ 0, /*sizesCount*/ 5, /*varietiesCount*/ 2, + /*shrinkable*/ 1, /*expendable*/ 1, /*eternal*/ 0, /*granular*/ 0, /*visibleToBeCollected*/ 0, + /*minimapR*/ 0, /*minimapG*/ 60, /*minimapB*/ 0, /*clearable*/ 1 }, + // CORN + { /*terrain*/ 2, /*gfxId*/ 10, /*sizesCount*/ 5, /*varietiesCount*/ 2, + /*shrinkable*/ 1, /*expendable*/ 1, /*eternal*/ 0, /*granular*/ 1, /*visibleToBeCollected*/ 0, + /*minimapR*/ 211, /*minimapG*/ 207, /*minimapB*/ 167, /*clearable*/ 1 }, + // PAPYRUS + { /*terrain*/ 2, /*gfxId*/ 20, /*sizesCount*/ 5, /*varietiesCount*/ 1, + /*shrinkable*/ 1, /*expendable*/ 0, /*eternal*/ 0, /*granular*/ 1, /*visibleToBeCollected*/ 0, + /*minimapR*/ 0, /*minimapG*/ 0, /*minimapB*/ 0, /*clearable*/ 1 }, + // STONE + { /*terrain*/ 2, /*gfxId*/ 30, /*sizesCount*/ 5, /*varietiesCount*/ 2, + /*shrinkable*/ 0, /*expendable*/ 0, /*eternal*/ 1, /*granular*/ 1, /*visibleToBeCollected*/ 0, + /*minimapR*/ 104, /*minimapG*/ 112, /*minimapB*/ 124, /*clearable*/ 0 }, + // ALGA + { /*terrain*/ 0, /*gfxId*/ 40, /*sizesCount*/ 5, /*varietiesCount*/ 2, + /*shrinkable*/ 1, /*expendable*/ 1, /*eternal*/ 0, /*granular*/ 1, /*visibleToBeCollected*/ 0, + /*minimapR*/ 41, /*minimapG*/ 157, /*minimapB*/ 165, /*clearable*/ 1 }, + // CHERRY + { /*terrain*/ 2, /*gfxId*/ 50, /*sizesCount*/ 4, /*varietiesCount*/ 1, + /*shrinkable*/ 1, /*expendable*/ 0, /*eternal*/ 1, /*granular*/ 1, /*visibleToBeCollected*/ 1, + /*minimapR*/ 255, /*minimapG*/ 127, /*minimapB*/ 0, /*clearable*/ 0 }, + // ORANGE + { /*terrain*/ 2, /*gfxId*/ 55, /*sizesCount*/ 4, /*varietiesCount*/ 1, + /*shrinkable*/ 1, /*expendable*/ 0, /*eternal*/ 1, /*granular*/ 1, /*visibleToBeCollected*/ 1, + /*minimapR*/ 255, /*minimapG*/ 127, /*minimapB*/ 0, /*clearable*/ 0 }, + // PRUNE + { /*terrain*/ 2, /*gfxId*/ 60, /*sizesCount*/ 4, /*varietiesCount*/ 1, + /*shrinkable*/ 1, /*expendable*/ 0, /*eternal*/ 1, /*granular*/ 1, /*visibleToBeCollected*/ 1, + /*minimapR*/ 255, /*minimapG*/ 127, /*minimapB*/ 0, /*clearable*/ 0 }, +}; + +const RessourceType* RessourcesTypes::get(unsigned int num) const +{ + const std::size_t count = sizeof(kRessourceTypes) / sizeof(kRessourceTypes[0]); + if (num < count) + return &kRessourceTypes[num]; + assert(false); + return nullptr; +} + +std::size_t RessourcesTypes::size() const +{ + return sizeof(kRessourceTypes) / sizeof(kRessourceTypes[0]); +} diff --git a/src/game/entities/RessourceType.h b/src/game/entities/RessourceType.h new file mode 100644 index 000000000..eee0323e1 --- /dev/null +++ b/src/game/entities/RessourceType.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include +#include + +#include "Ressource.h" + +// RessourceType describes the static configuration of a resource kind +// (Wood, Corn, Papyrus, Stone, Alga, Cherry, Orange, Prune). Historically +// these values were loaded at runtime from data/ressources.txt via the +// EntitiesTypes template; they are now baked into a compile-time const +// table in resources.cpp. The fields remain Sint32 for ABI parity with the +// old loader (booleans were stored as ints). +struct RessourceType +{ + Sint32 terrain; + Sint32 gfxId; + Sint32 sizesCount; + Sint32 varietiesCount; + // The following values are integers, but are used like booleans. + Sint32 shrinkable; // whether the resource is depleted when it is collected. + Sint32 expendable; // probably a misspelling of 'extendable'. What it actually determines is whether + // the resource multiplies itself to adjacent squares over time. + Sint32 eternal; // whether the resource cannot be destroyed or completely consumed. + Sint32 granular; // whether the resource is decremented, rather than removed, when it is harvested/cleared. + Sint32 visibleToBeCollected; // whether the resource can only be collected if the fog of war is cleared on its location. + Sint32 minimapR, minimapG, minimapB; + // Whether a worker's clearArea action will remove this resource. Stone, Cherry, + // Orange and Prune are non-clearable; the rest (Wood, Corn, Papyrus, Alga) are + // clearable. Previously hard-coded as a type==X || type==Y predicate at the call sites. + Sint32 clearable; +}; + +// RessourcesTypes is the read-only registry of resource types, indexed by the +// in-game RessourceType integer ID (WOOD=0, CORN=1, ..., PRUNE=7). The class +// keeps the same accessor surface (.get / .size) as the old EntitiesTypes +// subclass so existing callers compile unchanged; it is now backed by a +// compile-time const array rather than a parsed text file. +class RessourcesTypes +{ +public: + const RessourceType* get(unsigned int num) const; + std::size_t size() const; +}; diff --git a/src/UnitType.cpp b/src/game/entities/UnitType.cpp similarity index 80% rename from src/UnitType.cpp rename to src/game/entities/UnitType.cpp index 71071435c..b40f9c650 100644 --- a/src/UnitType.cpp +++ b/src/game/entities/UnitType.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "UnitType.h" #include @@ -24,7 +8,7 @@ UnitType& UnitType::operator+=(const UnitType &a) { for (int i=0; iperformance[i]; - + return r; } @@ -92,10 +76,10 @@ void UnitType::copyIf(const UnitType a, const UnitType b) { for (int i=0; ireadUint32("startImageBuild"); startImage[HARVEST] = stream->readUint32("startImageHarvest"); startImage[ATTACK_SPEED] = stream->readUint32("startImageAttack"); - + hungryness = stream->readSint32("hungryness"); - + performance[STOP_WALK] = stream->readSint32("stopWalkSpeed"); performance[STOP_SWIM] = stream->readSint32("stopSwimSpeed"); performance[STOP_FLY] = stream->readSint32("stopFlySpeed"); @@ -146,7 +130,7 @@ void UnitType::load(GAGCore::InputStream *stream, Sint32 versionMinor) performance[MAGIC_CREATE_ALGA] = stream->readSint32("magicCreateAlga"); performance[ARMOR] = stream->readSint32("armor"); performance[HP] = stream->readSint32("hpMax"); - + harvestDamage = stream->readSint32("harvestDamage"); armorReductionPerHappyness = stream->readSint32("armorReductionPerHappyness"); experiencePerLevel = stream->readSint32("experiencePerLevel"); @@ -164,9 +148,9 @@ void UnitType::save(GAGCore::OutputStream *stream) stream->writeUint32(startImage[BUILD], "startImageBuild"); stream->writeUint32(startImage[HARVEST], "startImageHarvest"); stream->writeUint32(startImage[ATTACK_SPEED], "startImageAttack"); - + stream->writeSint32(hungryness, "hungryness"); - + stream->writeSint32(performance[STOP_WALK], "stopWalkSpeed"); stream->writeSint32(performance[STOP_SWIM], "stopSwimSpeed"); stream->writeSint32(performance[STOP_FLY], "stopFlySpeed"); @@ -184,30 +168,9 @@ void UnitType::save(GAGCore::OutputStream *stream) stream->writeSint32(performance[MAGIC_CREATE_ALGA], "magicCreateAlga"); stream->writeSint32(performance[ARMOR], "armor"); stream->writeSint32(performance[HP], "hpMax"); - + stream->writeSint32(harvestDamage, "harvestDamage"); stream->writeSint32(armorReductionPerHappyness, "armorReductionPerHappyness"); stream->writeSint32(experiencePerLevel, "experiencePerLevel"); stream->writeSint32(magicActionCooldown, "magicActionCooldown"); } - -Uint32 UnitType::checkSum(void) -{ - Uint32 cs = 0; - cs ^= hungryness; - cs = (cs<<1) | (cs>>31); - for (int i=STOP_WALK; i>31); - } - cs ^= harvestDamage; - cs = (cs<<1) | (cs>>31); - cs ^= armorReductionPerHappyness; - cs = (cs<<1) | (cs>>31); - cs ^= experiencePerLevel; - cs = (cs<<1) | (cs>>31); - cs ^= magicActionCooldown; - cs = (cs<<1) | (cs>>31); - return cs; -} diff --git a/src/game/entities/UnitType.h b/src/game/entities/UnitType.h new file mode 100644 index 000000000..e84af02c0 --- /dev/null +++ b/src/game/entities/UnitType.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include +#include "UnitConsts.h" + +namespace GAGCore +{ + class InputStream; + class OutputStream; +} + +// UnitType is an aggregate so that the per-type defaults in race.cpp can use +// C++20 designated initializers ({ .startImage = {...}, .hungryness = N, ... }). +// Aggregate-ness requires no user-declared constructors and no virtual +// functions; the previous virtual ~UnitType() and the unused +// UnitType(InputStream*) constructor were dropped accordingly. No callers +// derive from UnitType (only Race::unitTypes ever holds instances), so +// removing the virtual destructor is behavior-preserving. +struct UnitType +{ + // caracteristic modulated by player choice, if 0, feature disabled + // display infos + Uint32 startImage[NB_MOVE]; + + Sint32 hungryness; + + Sint32 performance[NB_ABILITY]; + + Sint32 harvestDamage; + Sint32 armorReductionPerHappyness; + Sint32 experiencePerLevel; + + Sint32 magicActionCooldown; + + UnitType& operator+=(const UnitType &a); + UnitType operator+(const UnitType &a); + UnitType& operator/=(int a); + UnitType operator/(int a); + UnitType& operator*=(int a); + UnitType operator*(int a); + int operator*(const UnitType &a); + + void copyIf(const UnitType a, const UnitType b); + void copyIfNot(const UnitType a, const UnitType b); + + // Used by save-file serialization in Race::save() / Race::load(stream). + // Note: the text-stream "data/units.txt" load path is gone — the default + // table is now baked into race.cpp at compile time. + void load(GAGCore::InputStream *stream, Sint32 versionMinor); + void save(GAGCore::OutputStream *stream); +}; diff --git a/src/gui/BuildingGuiState.cpp b/src/gui/BuildingGuiState.cpp new file mode 100644 index 000000000..d2c98f2a1 --- /dev/null +++ b/src/gui/BuildingGuiState.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "BuildingGuiState.h" + +#include "building/Building.h" +#include "map/Map.h" +#include "team/Team.h" +#include "unit/Unit.h" + +namespace { +const BuildingGuiState* lookup(const BuildingGuiStateMap& m, const Building& b) +{ + auto it = m.find(b.gid); + return it == m.end() ? nullptr : &it->second; +} +} + +Sint32 displayedPosX(const BuildingGuiStateMap& m, const Building& b) +{ + const BuildingGuiState* s = lookup(m, b); + return (s && s->pendingPosX) ? *s->pendingPosX : b.posX; +} + +Sint32 displayedPosY(const BuildingGuiStateMap& m, const Building& b) +{ + const BuildingGuiState* s = lookup(m, b); + return (s && s->pendingPosY) ? *s->pendingPosY : b.posY; +} + +Sint32 displayedMaxUnitWorking(const BuildingGuiStateMap& m, const Building& b) +{ + const BuildingGuiState* s = lookup(m, b); + return (s && s->pendingMaxUnitWorking) ? *s->pendingMaxUnitWorking : b.maxUnitWorking; +} + +Sint32 displayedUnitStayRange(const BuildingGuiStateMap& m, const Building& b) +{ + const BuildingGuiState* s = lookup(m, b); + return (s && s->pendingUnitStayRange) ? *s->pendingUnitStayRange : b.unitStayRange; +} + +void computeFlagStatDisplayed(const Building& b, Sint32 posX, Sint32 posY, + Sint32 stayRange, int* goingTo, int* onSpot) +{ + *goingTo = 0; + *onSpot = 0; + + const Sint32 stayRangeSquare = (1 + stayRange) * (1 + stayRange); + for (auto* unit : b.unitsWorking) + { + const Sint32 distSquare = b.owner->map->warpDistSquare(posX, posY, unit->posX, unit->posY); + if (distSquare < stayRangeSquare) + (*onSpot)++; + else + (*goingTo)++; + } +} diff --git a/src/gui/BuildingGuiState.h b/src/gui/BuildingGuiState.h new file mode 100644 index 000000000..b399e89b3 --- /dev/null +++ b/src/gui/BuildingGuiState.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include + +class Building; + +/// Per-building GUI-side optimistic shadow of pending orders. +/// +/// When the local player drags a flag or scrolls a building's worker count, +/// the change is queued as an Order and won't take effect on the simulation +/// until the network round-trip completes. To make the UI feel responsive, +/// the GUI stores the intended value here and renders it in preference to +/// the building's authoritative state until the order executes. +/// +/// Reconciliation happens in GameGUI::executeOrder when an order matching +/// this building arrives — see GameGUI::reconcileBuildingGuiState. +/// +/// Each field is nullopt when there is no pending change, in which case +/// the displayed value falls back to the authoritative Building field. +/// Keyed by Building::gid (stable for the building's lifetime). +/// +/// Never read or written by simulation, AI, or scripts — strictly GUI state. +struct BuildingGuiState +{ + std::optional pendingPosX; + std::optional pendingPosY; + std::optional pendingMaxUnitWorking; + std::optional pendingUnitStayRange; +}; + +/// Map from Building::gid to its pending GUI state. +using BuildingGuiStateMap = std::unordered_map; + +// Display accessors: return pending value if set, else the authoritative +// value from `b`. Defined in BuildingGuiState.cpp because Building's header +// is heavy and we want this header light enough to forward-declare through. +Sint32 displayedPosX(const BuildingGuiStateMap& m, const Building& b); +Sint32 displayedPosY(const BuildingGuiStateMap& m, const Building& b); +Sint32 displayedMaxUnitWorking(const BuildingGuiStateMap& m, const Building& b); +Sint32 displayedUnitStayRange(const BuildingGuiStateMap& m, const Building& b); + +/// Count workers currently inside vs. en route to the flag's pending area. +/// `posX`, `posY`, `stayRange` should be the displayed values (caller already +/// resolved any pending state). Was Building::computeFlagStatLocal — moved off +/// the sim object because it only consumes GUI-side state. +void computeFlagStatDisplayed(const Building& b, Sint32 posX, Sint32 posY, + Sint32 stayRange, int* goingTo, int* onSpot); diff --git a/src/gui/GameGUI.cpp b/src/gui/GameGUI.cpp new file mode 100644 index 000000000..d46c70ddc --- /dev/null +++ b/src/gui/GameGUI.cpp @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Unit.h" +#include "Utilities.h" +#include "IRC.h" +#include "SoundMixer.h" +#include "VoiceRecorder.h" +#include "GameGUIKeyActions.h" +#include "Player.h" +#include "ReplayReader.h" +#include "ReplayWriter.h" +#include "config.h" +#include "Order.h" + +#include + +using std::shared_ptr; +using std::static_pointer_cast; + +InGameTextInput::InGameTextInput(GraphicContext *parentCtx) +:OverlayScreen(parentCtx, 492, 34) +{ + textInput=new TextInput(5, 5, 482, 24, ALIGN_LEFT, ALIGN_LEFT, "standard", "", true, 256); + addWidget(textInput); + dispatchInit(); +} + +void InGameTextInput::onAction(Widget *source, Action action, int par1, int par2) +{ + if (action==TEXT_VALIDATED) + { + endValue=0; + } +} + +GameGUI::GameGUI() + : keyboardManager(GameGUIShortcuts), game(this), toolManager(game, brush, defaultAssign, ghostManager), + minimap(globalContainer->runNoX, + RIGHT_MENU_WIDTH, // width of the menu + (globalContainer->runNoX ? 0 : globalContainer->gfx->getW()), // width of the screen + 20, // x offset + 10, // y offset + 128, // width + 128, //height + Minimap::ShowFOW), // minimap mode + + ghostManager(game) +{ +} + +GameGUI::~GameGUI() +{ + for (ParticleSet::iterator it = particles.begin(); it != particles.end(); ++it) + delete *it; +} + +Sint32 GameGUI::displayedPosX(const Building& b) const { return ::displayedPosX(buildingGuiState, b); } +Sint32 GameGUI::displayedPosY(const Building& b) const { return ::displayedPosY(buildingGuiState, b); } +Sint32 GameGUI::displayedMaxUnitWorking(const Building& b) const { return ::displayedMaxUnitWorking(buildingGuiState, b); } +Sint32 GameGUI::displayedUnitStayRange(const Building& b) const { return ::displayedUnitStayRange(buildingGuiState, b); } + +void GameGUI::init() +{ + notmenu = false; + isRunning=true; + gamePaused=false; + hardPause=false; + exitGlobCompletely=false; + flushOutgoingAndExit=false; + drawHealthFoodBar=true; + drawPathLines=false; + drawAccessibilityAids=false; + viewportX=0; + viewportY=0; + mouseX=0; + mouseY=0; + displayMode=CONSTRUCTION_VIEW; + replayDisplayMode=RDM_REPLAY_VIEW; + selectionMode=NO_SELECTION; + selectionPushed=false; + selection.building = NULL; + selection.unit = NULL; + miniMapPushed=false; + putMark=false; + showUnitWorkingToBuilding=true; + chatMask=0xFFFFFFFF; + hasSpaceBeenClicked=false; + swallowSpaceKey=false; + scriptTextUpdated = false; + + viewportSpeedX=0; + viewportSpeedY=0; + + showStarvingMap=false; + showDamagedMap=false; + showDefenseMap=false; + showFertilityMap=false; + + inGameMenu=IGM_NONE; + gameMenuScreen=NULL; + typingInputScreen=NULL; + scrollableText=NULL; + typingInputScreenPos=0; + + eventGoTypeIterator = 0; + localTeam=NULL; + teamStats=NULL; + + hasEndOfGameDialogBeenShown=false; + panPushed=false; + + buildingsChoiceName.clear(); + buildingsChoiceName.push_back("swarm"); + buildingsChoiceName.push_back("inn"); + buildingsChoiceName.push_back("hospital"); + buildingsChoiceName.push_back("racetrack"); + buildingsChoiceName.push_back("swimmingpool"); + buildingsChoiceName.push_back("barracks"); + buildingsChoiceName.push_back("school"); + buildingsChoiceName.push_back("defencetower"); + buildingsChoiceName.push_back("stonewall"); + buildingsChoiceName.push_back("market"); + + buildingsChoiceState.resize(buildingsChoiceName.size(), true); + + flagsChoiceName.clear(); + flagsChoiceName.push_back("explorationflag"); + flagsChoiceName.push_back("warflag"); + flagsChoiceName.push_back("clearingflag"); + flagsChoiceState.resize(flagsChoiceName.size(), true); + + hiddenGUIElements=0; + + for (size_t i=0; i=0); + assert(localTeamNo0); + assert(game.gameHeader.getNumberOfPlayers()<=Team::MAX_COUNT); + assert(localTeamNostats; + + // Mirror the local-team identity onto Map so sim code can consult it without + // reaching into the GUI layer. + game.map.setLocalTeam(localTeamNo); + + // recompute local forbidden and guard areas + game.map.computeLocalForbidden(localTeamNo); + game.map.computeLocalGuardArea(localTeamNo); + game.map.computeLocalClearArea(localTeamNo); + + // set default event position + eventGoPosX = localTeam->startPosX; + eventGoPosY = localTeam->startPosY; + eventGoType = 0; +} + +void GameGUI::adjustInitialViewport() +{ + assert(localTeam); + viewportX=localTeam->startPosX-((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); + viewportY=localTeam->startPosY-(globalContainer->gfx->getH()>>6); + viewportX&=game.map.getMaskW(); + viewportY&=game.map.getMaskH(); +} + +std::shared_ptr GameGUI::getOrder(void) +{ + std::shared_ptr order; + if (orderQueue.size()==0) + order=shared_ptr(new NullOrder()); + else + { + order=orderQueue.front(); + orderQueue.pop_front(); + } + return order; +} + +void GameGUI::setMultiLine(const std::string &input, std::vector *output, std::string indent) +{ + unsigned pos = 0; + int length = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64; + + std::string lastWord; + std::string lastLine; + std::string ninput=input; + if(!ninput.empty() && ninput.back() != ' ') + ninput += " "; + + while (posstandardFont->getStringWidth(lastLine.c_str()); + int actWordLength = globalContainer->standardFont->getStringWidth(lastWord.c_str()); + int spaceLength = globalContainer->standardFont->getStringWidth(" "); + if (actWordLength+actLineLength+spaceLength < length) + { + if (lastLine.length()) + lastLine += " "; + lastLine += lastWord; + lastWord.clear(); + } + else + { + output->push_back(lastLine); + lastLine = indent+lastWord; + lastWord.clear(); + } + } + else + { + lastWord += ninput[pos]; + } + pos++; + } + if (lastLine.length()) + lastLine += " "; + lastLine += lastWord; + if (lastLine.length()) + output->push_back(lastLine); +} + +void GameGUI::addMessage(const GAGCore::Color& color, const std::string &msgText, bool chat) +{ + //Split into one per line + std::vector messages; + globalContainer->standardFont->pushStyle(Font::Style(Font::STYLE_BOLD, 255, 255, 255)); + setMultiLine(msgText, &messages); + globalContainer->standardFont->popStyle(); + + ///Add each line as a seperate message to the message manager. + ///Must be done backwards to appear in the right order + for (int i=messages.size()-1; i>=0; i--) + { + if(!chat) + messageManager.addGameMessage(InGameMessage(messages[i], color)); + else + messageManager.addChatMessage(InGameMessage(messages[i], color, 16000)); + } +} + +void GameGUI::addMark(shared_ptrmmo) +{ + markManager.addMark(Mark(mmo->x, mmo->y, game.teams[mmo->teamNumber]->color)); +} diff --git a/src/GameGUI.h b/src/gui/GameGUI.h similarity index 63% rename from src/GameGUI.h rename to src/gui/GameGUI.h index b43db896e..438aef37b 100644 --- a/src/GameGUI.h +++ b/src/gui/GameGUI.h @@ -1,28 +1,12 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GAME_GUI_H -#define __GAME_GUI_H +#pragma once +#include #include +#include #include #include "Game.h" @@ -32,11 +16,13 @@ #include "KeyboardManager.h" #include "MarkManager.h" #include "GameGUIMessageManager.h" -#include "Minimap.h" +#include "render/Minimap.h" #include "OverlayAreas.h" #include "GameGUIToolManager.h" #include "GameGUIDefaultAssignManager.h" #include "GameGUIGhostBuildingManager.h" +#include "BuildingGuiState.h" +#include "GameMusicController.h" namespace GAGCore { @@ -81,14 +67,14 @@ class GameGUI //! Handle mouse, keyboard and window resize inputs, and stats void step(void); //! Get order from gui, return NullOrder if - boost::shared_ptr getOrder(void); + std::shared_ptr getOrder(void); //! Return position on x int getViewportX() { return viewportX; } //! Return position on y int getViewportY() { return viewportY; } void drawAll(int team); - void executeOrder(boost::shared_ptr order); + void executeOrder(std::shared_ptr order); /// If setGameHeader is true, then the given gameHeader will replace the one loaded with /// the map, otherwise it will be ignored @@ -104,6 +90,17 @@ class GameGUI //! return the local team of the player who is running glob2 Team *getLocalTeam(void) { return localTeam; } + // Sim → GUI lifecycle hooks. The simulation path (Team::syncStep) + // calls these when a unit dies or a building is demolished, so the + // sim itself never reads GameGUI-owned selection state. The hook + // runs entirely on the local client's GUI state; checkSelection() + // picks up the resulting NULL on the next draw and tears down the + // rest of the panel. In the Rust port, do not duplicate selection + // between sim and GUI — keep it solely on per-viewer GUI state and + // drop these hooks entirely. + void onUnitDestroyed(Unit *u); + void onBuildingDestroyed(Building *b); + // Script interface void enableBuildingsChoice(const std::string &name); void disableBuildingsChoice(const std::string &name); @@ -221,9 +218,14 @@ class GameGUI void handleKey(SDL_Keysym key, bool pressed); void handleKeyAlways(void); void handleKeyDump(SDL_KeyboardEvent key); + void handleKeySwitchToAreaBrush(int figure); + void handleKeySelectConstruct(const char *buildingName); + void handleKeySelectPlaceFlag(const char *flagName); + void handleKeySelectPlaceArea(GameGUIToolManager::ZoneType zone); void handleMouseMotion(int mx, int my, int button); void handleMapClick(int mx, int my, int button); void handleMenuClick(int mx, int my, int button); + void handleMenuClickBuildingSelection(int mx, int my, int button); void handleReplayProgressBarClick(int mx, int my, int button); void handleActivation(Uint8 state, Uint8 gain); @@ -231,7 +233,7 @@ class GameGUI void minimapMouseToPos(int mx, int my, int *cx, int *cy, bool forScreenViewport); // Drawing support functions - void drawScrollBox(int x, int y, int value, int valueLocal, int act, int max); + void drawScrollBox(int x, int y, int valueLocal, int act, int max); void drawXPProgressBar(int x, int y, int act, int max); void drawButton(int x, int y, std::string caption, int r=128, int g=128, int b=128, bool doLanguageLookup=true); void drawBlueButton(int x, int y, std::string caption, bool doLanguageLookup=true); @@ -257,14 +259,82 @@ class GameGUI void drawPanelButtons(int y); //! Draw a single button of the panel void drawPanelButton(int y, int pos, int numButtons, int sprite); - //! Draw a choice of buildings or flags - void drawChoice(int pos, std::vector &types, std::vector &states, unsigned numberPerLine = 2); + //! Draw a choice of buildings or flags. Thin coordinator over the four helpers below. + //! `panelTopY` is the Y anchor used for mouse hit-testing; note that the sprite grid is + //! drawn at YPOS_BASE_BUILDING (panelTopY + 5), producing a 5-pixel offset — see BH-290. + void drawChoice(int panelTopY, std::vector &types, std::vector &states, unsigned numberPerLine = 2); + //! Paint the icon grid for the choice panel and queue any tutorial-hilight arrows. + void drawChoiceSprites(const std::vector& types, const std::vector& states, unsigned numberPerLine); + //! Paint the selection-highlight sprite over cell `selIdx`. + void drawChoiceHighlight(size_t selIdx, unsigned numberPerLine); + //! Return the cell index the mouse is currently over, or nullopt if not over any cell. + //! `panelTopY` is the Y origin of the hit grid (caller's `pos`). + std::optional pickChoiceUnderMouse(int panelTopY, size_t count, unsigned numberPerLine) const; + //! Paint the resource/info text block at the bottom of the right panel for the given type. + void drawChoiceInfoPanel(const std::string& type); //! Draw a choice of flags void drawFlagView(void); //! Draw the infos from a unit void drawUnitInfos(void); - //! Draw the infos and actions from a building + //! Draw the infos and actions from a building. Thin coordinator that calls + //! the per-section helpers below in vertical order. void drawBuildingInfos(void); + //! Draw the centered title row (" ()") and the + //! subtitle ("level N — (building site) — Prestige"). Advances ypos past + //! the title block. + void drawBuildingHeader(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw the building's mini-sprite icon framed by the panel icon backing, + //! at the current ypos. Does not advance ypos. + void drawBuildingIcon(Building* selBuild, BuildingType* buildingType, int ypos); + //! Draw the HP label and current/max value (red below 1/5th max). No + //! ypos advance — sits in the icon row next to the icon. + void drawBuildingHP(Building* selBuild, BuildingType* buildingType, int ypos); + //! Draw the units-inside count ("N/maxUnitInside" when ALIVE, otherwise + //! the "still N units" message). Ally-gated. No ypos advance. + void drawBuildingInsideStats(Building* selBuild, BuildingType* buildingType, int ypos); + //! Draw a flag building's "in way" / "on the spot" unit counts using the + //! displayed (optimistic) flag position/range so the numbers track a drag + //! or scroll-resize. Ally-gated. No ypos advance. + void drawBuildingFlagInfo(Building* selBuild, BuildingType* buildingType, int ypos); + //! Draw the "working" label, count, and the maxUnitWorking scrollbox. + //! Queues the tutorial hilight arrow when active. Ally-gated. Advances + //! ypos past the working bar when present. + void drawBuildingWorkingControls(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw the three priority radio buttons (low / medium / high) for + //! buildings with maxUnitWorking>0. Ally-gated. Advances ypos. + void drawBuildingPriorityControls(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw the flag's stay-range scrollbox. Ally-gated. Advances ypos. + void drawBuildingRangeControls(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw the time-to-leave progress bar showing units' insideTimeout (extracted from drawBuildingInfos) + void drawBuildingTimeToLeaveBar(Building* selBuild, BuildingType* buildingType, int& ypos, unsigned& unitInsideBarYDec); + //! Draw the flag-type-specific controls for clearing/war/exploration flags (extracted from drawBuildingInfos) + void drawBuildingFlagControls(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw armor / shoot damage / shoot range text rows for combat buildings. + //! Advances ypos. + void drawBuildingCombatStats(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw the market exchange panel (per-happyness ressource readouts) for + //! buildings that can exchange and that the local team has shared-vision + //! exchange visibility on. Advances ypos. + void drawBuildingExchange(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw non-exchange resource readouts ("name: cur/max") and the bullets + //! row for shooters. Ally-gated; skipped for exchange buildings. Advances + //! ypos. + void drawBuildingResources(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw the swarm production progress bar plus the per-unit-type ratio + //! scrollboxes (worker / explorer / warrior). Queues the ratio-bar + //! tutorial hilight arrow when active. Ally-gated. Advances ypos. + void drawBuildingSwarmRatios(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw any "X units can't access resource"-style explanations of why the + //! building isn't filling its assigned worker slots. Ally-gated. Advances + //! ypos. + void drawBuildingFailureReasons(Building* selBuild, BuildingType* buildingType, int& ypos); + //! Draw the repair / upgrade / destroy / cancel action buttons at the + //! bottom of the panel, plus the upgrade-preview tooltip on hover. Only + //! shown when the local team owns the building. Uses absolute + //! bottom-of-screen Y; does not consume ypos. + void drawBuildingActionButtons(Building* selBuild, BuildingType* buildingType, unsigned unitInsideBarYDec); + //! Draw the upgrade preview tooltip (cost + new abilities) shown on hover over the upgrade button (extracted from drawBuildingInfos) + void drawBuildingUpgradePreview(Building* selBuild, BuildingType* buildingType, unsigned unitInsideBarYDec); //! Draw the infos about a ressource on map (type and number left) void drawRessourceInfos(void); //! Draw the replay panel @@ -285,9 +355,10 @@ class GameGUI //! on each step, check if we have won or lost void checkWonConditions(void); - //! given the game state, change the music - void musicStep(void); - + //! Owns the in-game music state machine. Reset by init() at the start of + //! every loaded game; advanced once per simulation tick from stepGameLogic. + GameMusicController musicController; + friend class InGameAllianceScreen; //! Display mode @@ -396,7 +467,7 @@ class GameGUI Uint32 chatMask; - std::list > orderQueue; + std::list > orderQueue; Minimap minimap; @@ -435,7 +506,10 @@ class GameGUI int eventGoType; //!< type of last event int eventGoTypeIterator; //!< iterator to iter on ctrl + space press - //! Transform a text to multi line according to screen width + //! Word-wrap \a input into \a output, breaking at spaces so each line fits the + //! message-panel pixel width (screen width minus right menu and side padding), + //! measured via globalContainer->standardFont. Continuation lines are prefixed + //! with \a indent. Empty input yields an empty output (no lines pushed). void setMultiLine(const std::string &input, std::vector *output, std::string indent=""); // Typing stuff : @@ -447,7 +521,7 @@ class GameGUI MarkManager markManager; //! add a minimap mark - void addMark(boost::shared_ptr mmo); + void addMark(std::shared_ptr mmo); // records CPU usage percentages static const unsigned SMOOTHED_CPU_SIZE=32; @@ -468,6 +542,22 @@ class GameGUI ///This function flushes orders from the scrollWheel at the end of every frame void flushScrollWheelOrders(); + + ///Per-building GUI-side pending order state (optimistic shadow). + ///See BuildingGuiState.h. Public so render code can read pending positions. + BuildingGuiStateMap buildingGuiState; + + ///Accessor: pending value if set, else authoritative from `b`. + Sint32 displayedPosX(const Building& b) const; + Sint32 displayedPosY(const Building& b) const; + Sint32 displayedMaxUnitWorking(const Building& b) const; + Sint32 displayedUnitStayRange(const Building& b) const; + + ///Get-or-create the pending state for a building (used by GUI mutators). + BuildingGuiState& pendingFor(Uint16 gid) { return buildingGuiState[gid]; } + + ///Called from executeOrder: clear pending fields the order has now made authoritative. + void reconcileBuildingGuiState(const std::shared_ptr& order); //! A particle is cute and only for eye candy struct Particle @@ -494,5 +584,4 @@ class GameGUI void moveParticles(int oldViewportX, int viewportX, int oldViewportY, int viewportY); }; -#endif diff --git a/src/GameGUIDefaultAssignManager.cpp b/src/gui/GameGUIDefaultAssignManager.cpp similarity index 70% rename from src/GameGUIDefaultAssignManager.cpp rename to src/gui/GameGUIDefaultAssignManager.cpp index b8209f485..0c603895f 100644 --- a/src/GameGUIDefaultAssignManager.cpp +++ b/src/gui/GameGUIDefaultAssignManager.cpp @@ -1,26 +1,9 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "GameGUIDefaultAssignManager.h" -#include "BuildingsTypes.h" +#include "BuildingType.h" #include "IntBuildingType.h" #include "GlobalContainer.h" #include "Stream.h" diff --git a/src/gui/GameGUIDefaultAssignManager.h b/src/gui/GameGUIDefaultAssignManager.h new file mode 100644 index 000000000..5715f0302 --- /dev/null +++ b/src/gui/GameGUIDefaultAssignManager.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include +#include "Types.h" + +namespace GAGCore +{ + class OutputStream; + class InputStream; +}; + + +///This class manages the default number of units to be assigned when constructing a new buildings +class GameGUIDefaultAssignManager +{ +public: + ///Constructs a GameGUIDefaultAssignManager + GameGUIDefaultAssignManager(); + + ///Retrive the default assigned units for a given building typenum (note, not the + ///ntBuildingType typenum, the BuildingTypes typenum) + int getDefaultAssignedUnits(int typenum); + + ///Sets the default assigned units for a given building typenum + void setDefaultAssignedUnits(int typenum, int value); + + ////Saves the default assign information + void save(GAGCore::OutputStream* stream) const; + + ///Loads the default assign information + void load(GAGCore::InputStream* stream, Sint32 versionMinor); + +private: + std::map unitCount; +}; + + diff --git a/src/GameGUIDialog.cpp b/src/gui/GameGUIDialog.cpp similarity index 95% rename from src/GameGUIDialog.cpp rename to src/gui/GameGUIDialog.cpp index 6e2ccc155..85b37559c 100644 --- a/src/GameGUIDialog.cpp +++ b/src/gui/GameGUIDialog.cpp @@ -1,23 +1,8 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "boost/lexical_cast.hpp" +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include #include "GameGUIDialog.h" #include "GameGUI.h" #include "GlobalContainer.h" @@ -545,7 +530,7 @@ InGameObjectivesScreen::InGameObjectivesScreen(GameGUI* gui, bool showBriefing) text = gui->game.gameHints.getGameHintText(i); if(Toolkit::getStringTable()->doesStringExist(text.c_str())) text = Toolkit::getStringTable()->getString(text.c_str()); - text = boost::lexical_cast(n+1) + ") " + text; + text = std::to_string(n+1) + ") " + text; hintsWidgets.push_back(new Text(50, 70 + 25*n, ALIGN_LEFT, ALIGN_TOP, "standard", text.c_str())); n+=1; } diff --git a/src/GameGUIDialog.h b/src/gui/GameGUIDialog.h similarity index 74% rename from src/GameGUIDialog.h rename to src/gui/GameGUIDialog.h index ddcd95e98..6b9357b86 100644 --- a/src/GameGUIDialog.h +++ b/src/gui/GameGUIDialog.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GAME_GUI_DIALOG_H -#define __GAME_GUI_DIALOG_H +#pragma once #include @@ -155,4 +138,3 @@ class InGameObjectivesScreen:public OverlayScreen }; -#endif diff --git a/src/gui/GameGUIDraw.cpp b/src/gui/GameGUIDraw.cpp new file mode 100644 index 000000000..7e7715a41 --- /dev/null +++ b/src/gui/GameGUIDraw.cpp @@ -0,0 +1,758 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIInternal.h" +#include "GlobalContainer.h" +#include "Player.h" +#include "ReplayReader.h" +#include "SoundMixer.h" +#include "TeamDisplay.h" +#include "Unit.h" +#include "VoiceRecorder.h" + +void GameGUI::drawPanelButtons(int y) +{ + if (!globalContainer->replaying) + { + if (!(hiddenGUIElements & HIDABLE_BUILDINGS_LIST)) + { + if (((selectionMode==NO_SELECTION) || (selectionMode==TOOL_SELECTION)) && (displayMode==CONSTRUCTION_VIEW)) + drawPanelButton(y, 0, NB_VIEWS, 1); + else + drawPanelButton(y, 0, NB_VIEWS, 0); + } + + if (!(hiddenGUIElements & HIDABLE_FLAGS_LIST)) + { + if (((selectionMode==NO_SELECTION) || (selectionMode==TOOL_SELECTION) || (selectionMode==BRUSH_SELECTION)) && (displayMode==FLAG_VIEW)) + drawPanelButton(y, 1, NB_VIEWS, 29); + else + drawPanelButton(y, 1, NB_VIEWS, 28); + } + + if (!(hiddenGUIElements & HIDABLE_TEXT_STAT)) + { + if ((selectionMode==NO_SELECTION) && (displayMode==STAT_TEXT_VIEW)) + drawPanelButton(y, 2, NB_VIEWS, 3); + else + drawPanelButton(y, 2, NB_VIEWS, 2); + } + + if (!(hiddenGUIElements & HIDABLE_GFX_STAT)) + { + if ((selectionMode==NO_SELECTION) && (displayMode==STAT_GRAPH_VIEW)) + drawPanelButton(y, 3, NB_VIEWS, 5); + else + drawPanelButton(y, 3, NB_VIEWS, 4); + } + } + else + { + if (replayDisplayMode==RDM_REPLAY_VIEW) + drawPanelButton(y, 0, RDM_NB_VIEWS, 48); + else + drawPanelButton(y, 0, RDM_NB_VIEWS, 49); + + if (replayDisplayMode==RDM_STAT_TEXT_VIEW) + drawPanelButton(y, 1, RDM_NB_VIEWS, 3); + else + drawPanelButton(y, 1, RDM_NB_VIEWS, 2); + + if (replayDisplayMode==RDM_STAT_GRAPH_VIEW) + drawPanelButton(y, 2, RDM_NB_VIEWS, 5); + else + drawPanelButton(y, 2, RDM_NB_VIEWS, 4); + } + + if(hilights.find(HilightUnderMinimapIcon) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36, y, 38)); + } +} + +void GameGUI::drawPanelButton(int y, int pos, int numButtons, int sprite) +{ + int dec = (RIGHT_MENU_WIDTH - numButtons*32)/2; + + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + dec + pos*32, y, globalContainer->gamegui, sprite); +} + +void GameGUI::drawValueAlignedRight(int y, int v) +{ + FormatableString s("%0"); + s.arg(v); + int len = globalContainer->littleFont->getStringWidth(s.c_str()); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-len-2, y, globalContainer->littleFont, s.c_str()); +} + +void GameGUI::drawCosts(int ressources[BASIC_COUNT], Font *font) +{ + for (int i=0; i>1; + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+4+(i&0x1)*64, 256+172-42+y*12, + font, + FormatableString("%0: %1").arg(getRessourceName(i)).arg(ressources[i]).c_str()); + } +} + +void GameGUI::drawCheckButton(int x, int y, std::string caption, bool isSet) +{ + globalContainer->gfx->drawRect(x, y, 16, 16, Color::white); + if(isSet) + { + globalContainer->gfx->drawLine(x+4, y+4, x+12, y+12, Color::white); + globalContainer->gfx->drawLine(x+12, y+4, x+4, y+12, Color::white); + } + globalContainer->gfx->drawString(x+20, y, globalContainer->littleFont, caption); +} + + +void GameGUI::drawRadioButton(int x, int y, bool isSet) +{ + if(isSet) + { + globalContainer->gfx->drawSprite(x, y, globalContainer->gamegui, 20); + } + else + { + globalContainer->gfx->drawSprite(x, y, globalContainer->gamegui, 19); + } +} + +void GameGUI::drawPanel(void) +{ + // ensure we have a valid selection and associate pointers + checkSelection(); + + // set the clipping rectangle + globalContainer->gfx->setClipRect(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 128, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128); + + // draw menu background, black if low speed graphics, transparent otherwise + if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) + globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 133, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128, 0, 0, 0); + else + globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 133, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128, 0, 0, 40, 180); + + if(hilights.find(HilightRightSidePanel) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36, globalContainer->gfx->getH()/2, 38)); + } + + // draw the panel selection buttons + drawPanelButtons(YPOS_BASE_DEFAULT-32); + + switch(selectionMode) + { + case BUILDING_SELECTION: + drawBuildingInfos(); + break; + case UNIT_SELECTION: + drawUnitInfos(); + break; + case RESSOURCE_SELECTION: + drawRessourceInfos(); + break; + default: + if (!globalContainer->replaying) + { + switch(displayMode) + { + case CONSTRUCTION_VIEW: + drawChoice(YPOS_BASE_CONSTRUCTION, buildingsChoiceName, buildingsChoiceState); + break; + case FLAG_VIEW: + drawFlagView(); + break; + case STAT_TEXT_VIEW: + teamStats->drawText(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET, YPOS_BASE_STAT); + break; + case STAT_GRAPH_VIEW: + teamStats->drawStat(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET, YPOS_BASE_STAT); + drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+140+64, Toolkit::getStringTable()->getString("[Starving Map]"), showStarvingMap); + drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+140+88, Toolkit::getStringTable()->getString("[Damaged Map]"), showDamagedMap); + drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+140+112, Toolkit::getStringTable()->getString("[Defense Map]"), showDefenseMap); + drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+140+136, Toolkit::getStringTable()->getString("[Fertility Map]"), showFertilityMap); + break; + default: + std::cout << "Was not expecting displayMode" << displayMode; + assert(false); + } + } + else + { + switch(replayDisplayMode) + { + case RDM_REPLAY_VIEW: + drawReplayPanel(); + break; + case RDM_STAT_TEXT_VIEW: + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+15, YPOS_BASE_STAT+5, globalContainer->littleFont, FormatableString("%0 %1").arg(Toolkit::getStringTable()->getString("[watching:]")).arg(displayPlayerName(*localTeam)).c_str()); + teamStats->drawText(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET, YPOS_BASE_STAT+15); + break; + case RDM_STAT_GRAPH_VIEW: + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+15, YPOS_BASE_STAT+5, globalContainer->littleFont, FormatableString("%0 %1").arg(Toolkit::getStringTable()->getString("[watching:]")).arg(displayPlayerName(*localTeam)).c_str()); + teamStats->drawStat(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET, YPOS_BASE_STAT+15); + drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+155+64, Toolkit::getStringTable()->getString("[Starving Map]"), showStarvingMap); + drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+155+88, Toolkit::getStringTable()->getString("[Damaged Map]"), showDamagedMap); + drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+155+112, Toolkit::getStringTable()->getString("[Defense Map]"), showDefenseMap); + drawCheckButton(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8, YPOS_BASE_STAT+155+136, Toolkit::getStringTable()->getString("[Fertility Map]"), showFertilityMap); + break; + default: + std::cout << "Was not expecting replayDisplayMode" << replayDisplayMode; + assert(false); + } + } + } +} + +void GameGUI::drawTopScreenBar(void) +{ + // bar background + if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) + globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 16, 0, 0, 0); + else + globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 16, 0, 0, 40, 180); + + // draw unit stats + Uint8 redC[]={200, 0, 0}; + Uint8 greenC[]={0, 200, 0}; + Uint8 whiteC[]={200, 200, 200}; + Uint8 yellowC[]={200, 200, 0}; + Uint8 actC[3]; + int free, tot; + + int dec = (globalContainer->gfx->getW()-640)>>2; + dec += 10; + + globalContainer->unitmini->setBaseColor(localTeam->color); + for (int i=0; i<3; i++) + { + free = teamStats->getFreeUnits(i); + // worker is a special case + if (i==0) + free -= teamStats->getWorkersNeeded(); + tot = teamStats->getTotalUnits(i); + if (free<0) + memcpy(actC, redC, sizeof(redC)); + else if (free>0) + memcpy(actC, greenC, sizeof(greenC)); + else + memcpy(actC, whiteC, sizeof(whiteC)); + + globalContainer->gfx->drawSprite(dec+2, -1, globalContainer->unitmini, i); + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, actC[0], actC[1], actC[2])); + globalContainer->gfx->drawString(dec+22, 0, globalContainer->littleFont, FormatableString("%0 / %1").arg(free).arg(tot).c_str()); + globalContainer->littleFont->popStyle(); + + if(i==WORKER && hilights.find(HilightWorkersWorkingFreeStat) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(dec+22, 32, 39)); + } + + else if(i==WARRIOR && hilights.find(HilightExplorersWorkingFreeStat) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(dec+22, 32, 39)); + } + + else if(i==EXPLORER && hilights.find(HilightWarriorsWorkingFreeStat) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(dec+22, 32, 39)); + } + + dec += 70; + } + + // draw prestige stats + globalContainer->gfx->drawString(dec+0, 0, globalContainer->littleFont, FormatableString("%0 / %1 / %2").arg(localTeam->prestige).arg(game.totalPrestige).arg(game.prestigeToReach).c_str()); + + dec += 90; + + // draw unit conversion stats + globalContainer->gfx->drawString(dec, 0, globalContainer->littleFont, FormatableString("+%0 / -%1").arg(localTeam->unitConversionGained).arg(localTeam->unitConversionLost).c_str()); + + // draw CPU load + dec += 70; + int cpuLoad=0; + for (unsigned i=0; igfx->drawFilledRect(dec, 4, cpuLength, 8, actC[0], actC[1], actC[2]); + globalContainer->gfx->drawVertLine(dec, 2, 12, 200, 200, 200); + globalContainer->gfx->drawVertLine(dec+40, 2, 12, 200, 200, 200); + + // draw window bar + int pos=globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-16; + for (int i=0; igfx->drawSprite(i, 16, globalContainer->gamegui, 16); + } + for (int i=16; igfx->getH(); i+=32) + { + globalContainer->gfx->drawSprite(pos+12, i, globalContainer->gamegui, 17); + } + + + int index; + // draw main menu button + if (inGameMenu == IGM_MAIN) + index = 7; + else + index = 6; + globalContainer->gfx->drawSprite(pos, IGM_MAIN_MENU_ICON_Y, globalContainer->gamegui, index); + + // draw alliance button + if ( !(hiddenGUIElements & HIDABLE_ALLIANCE) ) + { + if (inGameMenu == IGM_ALLIANCE) + index = 44; + else + index = 45; + globalContainer->gfx->drawSprite(pos, IGM_ALLIANCE_ICON_Y, globalContainer->gamegui, index); + } + + // draw objectives button + if (inGameMenu == IGM_OBJECTIVES) + index = 46; + else + index = 47; + globalContainer->gfx->drawSprite(pos, IGM_OBJECTIVES_ICON_Y, globalContainer->gamegui, index); + + if(hilights.find(HilightMainMenuIcon) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(pos-32, 32, 43)); + } +} + +void GameGUI::drawOverlayInfos(void) +{ + if (selectionMode==TOOL_SELECTION) + { + globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); + toolManager.drawTool(mouseX, mouseY, localTeamNo, viewportX, viewportY); + } + else if (selectionMode==BRUSH_SELECTION) + { + globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); + toolManager.drawTool(mouseX, mouseY, localTeamNo, viewportX, viewportY); + } + else if (selectionMode==BUILDING_SELECTION) + { + Building* selBuild=selection.building; + globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); + int centerX, centerY; + game.map.buildingPosToCursor(displayedPosX(*selBuild), displayedPosY(*selBuild), selBuild->type->width, selBuild->type->height, ¢erX, ¢erY, viewportX, viewportY); + if (selBuild->owner->teamNumber==localTeamNo) + globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 0, 0, 190); + else if ((localTeam->allies) & (selBuild->owner->me)) + globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 255, 196, 0); + else if (!selBuild->type->isVirtual) + globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 190, 0, 0); + + // draw a white circle around units that are working at building + if ((showUnitWorkingToBuilding) + && ((selBuild->owner->allies) &(1<::iterator unitsWorkingIt=selBuild->unitsWorking.begin(); unitsWorkingIt!=selBuild->unitsWorking.end(); ++unitsWorkingIt) + { + Unit *unit=*unitsWorkingIt; + int px, py; + game.map.mapCaseToDisplayable(unit->posX, unit->posY, &px, &py, viewportX, viewportY); + int deltaLeft=255-unit->delta; + if (unit->actiondx*deltaLeft)>>3; + py-=(unit->dy*deltaLeft)>>3; + } + globalContainer->gfx->drawCircle(px+16, py+16, 16, 255, 255, 255, 180); + } + } + } + else if (selectionMode==RESSOURCE_SELECTION) + { + int rx = selection.ressource & game.map.getMaskW(); + int ry = selection.ressource >> game.map.getShiftW(); + int px, py; + game.map.mapCaseToDisplayable(rx, ry, &px, &py, viewportX, viewportY); + globalContainer->gfx->drawCircle(px+16, py+16, 16, 0, 0, 190); + } + + // draw message List + if (game.anyPlayerWaited && game.maskAwayPlayer && game.anyPlayerWaitedTimeFor>2) + { + int nbap=0; // Number of away players + Uint32 pm=1; + Uint32 apm=game.maskAwayPlayer; + for(int pi=0; pigfx->drawFilledRect(32, 32, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64, 22+nbap*20, 0, 0, 140, 127); + globalContainer->gfx->drawRect(32, 32, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64, 22+nbap*20, 255, 255, 255); + pm=1; + int pnb=0; + for(int pi2=0; pi2gfx->drawString(44, 44+pnb*20, globalContainer->standardFont, FormatableString(Toolkit::getStringTable()->getString("[waiting for %0]")).arg(game.players[pi2]->name).c_str()); + pnb++; + } + pm=pm<<1; + } + } + else + { + int ymesg = 32; + int yinc = 0; + + // TODO: die with SGSL + // show script text + if (game.sgslScript.isTextShown) + { + std::vector lines; + setMultiLine(game.sgslScript.textShown, &lines); + globalContainer->gfx->drawFilledRect(24, ymesg-8, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64+16, lines.size()*20+16, 0,0,0,128); + for (unsigned i=0; igfx->drawString(32, ymesg+yinc, globalContainer->standardFont, lines[i].c_str()); + yinc += 20; + } + + if (swallowSpaceKey) + { + globalContainer->gfx->drawFilledRect(24, ymesg+yinc+8, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64+16, 20, 0,0,0,128); + globalContainer->gfx->drawString(32, ymesg+yinc, globalContainer->standardFont, Toolkit::getStringTable()->getString("[press space]")); + yinc += 20; + } + yinc += 8; + } + + // show script text + if (!scriptText.empty()) + { + std::vector lines; + setMultiLine(scriptText, &lines); + globalContainer->gfx->drawFilledRect(24, ymesg-8, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-64+16, lines.size()*20+16, 0,0,0,128); + for (unsigned i=0; igfx->drawString(32, ymesg+yinc, globalContainer->standardFont, lines[i].c_str()); + yinc += 20; + } + } + + // show script counter + if (game.sgslScript.getMainTimer()) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-165, ymesg, globalContainer->standardFont, FormatableString("%0").arg(game.sgslScript.getMainTimer()).c_str()); + yinc = std::max(yinc, 32); + } + + ymesg += yinc+2; + + messageManager.drawAllGameMessages(32, ymesg); + } + + // display map mark + globalContainer->gfx->setClipRect(); + markManager.drawAll(localTeamNo, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+20, 10, 128, viewportX, viewportY, game); + + // display text if placing a building + if(selectionMode == TOOL_SELECTION && toolManager.getBuildingName() != "") + { + globalContainer->standardFont->pushStyle(Font::Style(Font::STYLE_NORMAL, Color(255,255,255))); + globalContainer->gfx->drawString(10, globalContainer->gfx->getH()-100, globalContainer->standardFont, Toolkit::getStringTable()->getString("[Building Tool Line Explanation]"), 0, 75); + globalContainer->gfx->drawString(10, globalContainer->gfx->getH()-100+12, globalContainer->standardFont, Toolkit::getStringTable()->getString("[Building Tool Box Explanation]"), 0, 75); + globalContainer->standardFont->popStyle(); + } + + // Draw icon if trasmitting + if (globalContainer->voiceRecorder->recordingNow) + globalContainer->gfx->drawSprite(5, globalContainer->gfx->getH()-50, globalContainer->gamegui, 24); + + // Draw which players are transmitting voice + int xinc = 42; + for(int p=0; pmix->isPlayerTransmittingVoice(p)) + { + if(xinc==42) + { + globalContainer->gamegui->setBaseColor(game.teams[game.players[p]->teamNumber]->color); + globalContainer->gfx->drawSprite(42, globalContainer->gfx->getH()-55, globalContainer->gamegui, 30); + xinc += 47; + } + int height = globalContainer->standardFont->getStringHeight(game.players[p]->name.c_str()); + + globalContainer->standardFont->pushStyle(Font::Style(Font::STYLE_NORMAL, game.teams[game.players[p]->teamNumber]->color)); + globalContainer->gfx->drawString(xinc, globalContainer->gfx->getH()-35-height/2, globalContainer->standardFont, game.players[p]->name); + xinc += globalContainer->standardFont->getStringWidth(game.players[p]->name.c_str()) + 5; + globalContainer->standardFont->popStyle(); + } + } + + if(!scrollableText) + messageManager.drawAllChatMessages(32, globalContainer->gfx->getH() - 165); + + // Draw the bar contining number of units, CPU load, etc... + drawTopScreenBar(); +} + +void GameGUI::drawInGameMenu(void) +{ + gameMenuScreen->dispatchPaint(); + globalContainer->gfx->drawSurface((int)gameMenuScreen->decX, (int)gameMenuScreen->decY, gameMenuScreen->getSurface()); + + // Draw a-la-aqua drop shadows + if ((globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) == 0) + { + int x = gameMenuScreen->decX; + int y = gameMenuScreen->decY; + int w = gameMenuScreen->getSurface()->getW(); + int h = gameMenuScreen->getSurface()->getH(); + + globalContainer->gfx->drawSprite(x-8, y+h, globalContainer->terrainShader, 17); + globalContainer->gfx->drawSprite(x+w, y+h, globalContainer->terrainShader, 18); + globalContainer->gfx->setClipRect(x, y+h, w, 16); + for (int i=0; igfx->drawSprite(x+i, y+h, globalContainer->terrainShader, 16); + } + globalContainer->gfx->setClipRect(x-8, y, w+16, h); + for (int i=0; igfx->drawSprite(x-8, y+i, globalContainer->terrainShader, 19); + globalContainer->gfx->drawSprite(x+w, y+i, globalContainer->terrainShader, 20); + } + } +} + +void GameGUI::drawInGameTextInput(void) +{ + typingInputScreen->decX=(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-492)/2; + typingInputScreen->decY=globalContainer->gfx->getH()-typingInputScreenPos; + typingInputScreen->dispatchPaint(); + globalContainer->gfx->drawSurface((int)typingInputScreen->decX, (int)typingInputScreen->decY, typingInputScreen->getSurface()); + if (typingInputScreenInc>0) + { + if (typingInputScreenPosTYPING_INPUT_BASE_INC) + typingInputScreenPos+=typingInputScreenInc; + else + { + typingInputScreenInc=0; + delete typingInputScreen; + typingInputScreen=NULL; + } + } +} + +void GameGUI::drawInGameScrollableText(void) +{ + scrollableText->decX=28; + scrollableText->decY=globalContainer->gfx->getH() - 165; + scrollableText->dispatchPaint(); + globalContainer->gfx->drawSurface(scrollableText->decX, scrollableText->decY, scrollableText->getSurface()); +} + +void GameGUI::drawAll(int team) +{ + // draw the map + Uint32 drawOptions = (drawHealthFoodBar ? Game::DRAW_HEALTH_FOOD_BAR : 0) | + (drawPathLines ? Game::DRAW_PATH_LINE : 0) | + (drawAccessibilityAids ? Game::DRAW_ACCESSIBILITY : 0 ) | + ((selectionMode==TOOL_SELECTION) ? Game::DRAW_BUILDING_RECT : 0) | + ((showStarvingMap) ? Game::DRAW_OVERLAY : 0) | + ((showDamagedMap) ? Game::DRAW_OVERLAY : 0) | + ((showDefenseMap) ? Game::DRAW_OVERLAY : 0) | + ((showFertilityMap) ? Game::DRAW_OVERLAY : 0) | + ((globalContainer->replaying && !globalContainer->replayShowFog) ? Game::DRAW_WHOLE_MAP : 0) | + Game::DRAW_AREA; + + updateHilightInGame(); + arrowPositions.clear(); + if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) + { + globalContainer->gfx->setClipRect(0, 16, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-16); + game.drawMap(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH(), 0, 16, viewportX, viewportY, localTeamNo, drawOptions, nullptr, &buildingGuiState); + } + else + { + std::set visibleBuildings; + + globalContainer->gfx->setClipRect(); + + game.drawMap(0, 0, globalContainer->gfx->getW(), globalContainer->gfx->getH(), RIGHT_MENU_WIDTH, 16, viewportX, viewportY, localTeamNo, drawOptions, &visibleBuildings, &buildingGuiState); + + // generate and draw particles + generateNewParticles(&visibleBuildings); + drawParticles(); + } + + ///Draw ghost buildings + if (!globalContainer->replaying) ghostManager.drawAll(viewportX, viewportY, localTeamNo); + + // if paused, tint the game area + if (gamePaused) + { + std::string s; + + if (globalContainer->replaying && globalContainer->replayReader->isFinished()) + { + s = Toolkit::getStringTable()->getString("[replay ended]"); + } + else + { + globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH(), 0, 0, 0, 20); + s = Toolkit::getStringTable()->getString("[Paused]"); + } + + int x = (globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-globalContainer->menuFont->getStringWidth(s))/2; + globalContainer->gfx->drawString(x, globalContainer->gfx->getH()-80, globalContainer->menuFont, s); + } + + // draw the panel + globalContainer->gfx->setClipRect(); + drawPanel(); + + // draw the minimap + drawOptions = 0; + //globalContainer->gfx->setClipRect(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 0, 128, 128); + //game.drawMiniMap(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 0, 128, 128, viewportX, viewportY, team, drawOptions); + + globalContainer->gfx->setClipRect(); + minimap.draw(localTeamNo, viewportX, viewportY, (globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)/32, globalContainer->gfx->getH()/32 ); + + // draw the progress bar if this is a replay + if (globalContainer->replaying) drawReplayProgressBar(); + + // draw the top bar and other infos + globalContainer->gfx->setClipRect(); + drawOverlayInfos(); + + // draw menu if any + if (inGameMenu) + { + globalContainer->gfx->setClipRect(); + drawInGameMenu(); + } + + // draw input box if any + if (typingInputScreen) + { + globalContainer->gfx->setClipRect(); + drawInGameTextInput(); + } + if (scrollableText) + drawInGameScrollableText(); + + // draw the hilight arrows + for(int i=0; i<(int)arrowPositions.size(); ++i) + { + globalContainer->gfx->drawSprite(arrowPositions[i].x, arrowPositions[i].y, globalContainer->gamegui, arrowPositions[i].sprite); + + } +} + +void GameGUI::drawButton(int x, int y, std::string caption, int r, int g, int b, bool doLanguageLookup) +{ + globalContainer->gfx->drawSprite(x+8, y, globalContainer->gamegui, 12); + globalContainer->gfx->drawFilledRect(x+17, y+3, 94, 10, r, g, b); + + std::string textToDraw; + if (doLanguageLookup) + textToDraw=Toolkit::getStringTable()->getString(caption); + else + textToDraw=caption; + int len=globalContainer->littleFont->getStringWidth(textToDraw); + int h=globalContainer->littleFont->getStringHeight(textToDraw); + globalContainer->gfx->drawString(x+17+((94-len)>>1), y+((16-h)>>1), globalContainer->littleFont, textToDraw); +} + +void GameGUI::drawBlueButton(int x, int y, std::string caption, bool doLanguageLookup) +{ + drawButton(x,y,caption,128,128,192,doLanguageLookup); +} + +void GameGUI::drawRedButton(int x, int y, std::string caption, bool doLanguageLookup) +{ + drawButton(x,y,caption,192,128,128,doLanguageLookup); +} + +void GameGUI::drawTextCenter(int x, int y, std::string caption) +{ + std::string text; + + text=Toolkit::getStringTable()->getString(caption); + int dec=(RIGHT_MENU_WIDTH-globalContainer->littleFont->getStringWidth(text))>>1; + globalContainer->gfx->drawString(x+dec, y, globalContainer->littleFont, text); +} + +// Draws a two-channel scrollbox bar. `valueLocal` is the local/pending value the +// user has dialed in (drawn as the lighter localBar); `act` is the simulation- +// confirmed value (drawn as the darker actualBar on top). When the two agree +// the actualBar fully overlays the localBar; while an order is in flight they +// differ briefly. `max` is the divisor for both channels. +void GameGUI::drawScrollBox(int x, int y, int valueLocal, int act, int max) +{ + //scrollbar borders + globalContainer->gfx->setClipRect(x+8, y, 112, 16); + globalContainer->gfx->drawSprite(x+8, y, globalContainer->gamegui, 9); + + //localBar + int size=(valueLocal*92)/max; + globalContainer->gfx->setClipRect(x+18, y, size, 16); + globalContainer->gfx->drawSprite(x+18, y+3, globalContainer->gamegui, 10); + + //actualBar + size=(act*92)/max; + globalContainer->gfx->setClipRect(x+18, y, size, 16); + globalContainer->gfx->drawSprite(x+18, y+4, globalContainer->gamegui, 11); + + globalContainer->gfx->setClipRect(); +} + +void GameGUI::drawXPProgressBar(int x, int y, int act, int max) +{ + globalContainer->gfx->setClipRect(x+8, y, 112, 16); + + globalContainer->gfx->setClipRect(x+18, y, 92, 16); + globalContainer->gfx->drawSprite(x+18, y+3, globalContainer->gamegui, 10); + + globalContainer->gfx->setClipRect(x+18, y, (act*92)/max, 16); + globalContainer->gfx->drawSprite(x+18, y+4, globalContainer->gamegui, 11); + + globalContainer->gfx->setClipRect(); +} diff --git a/src/gui/GameGUIDrawBuildingHelpers.cpp b/src/gui/GameGUIDrawBuildingHelpers.cpp new file mode 100644 index 000000000..aa51a7f8e --- /dev/null +++ b/src/gui/GameGUIDrawBuildingHelpers.cpp @@ -0,0 +1,700 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIInternal.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "TeamDisplay.h" +#include "Unit.h" +#include "UnitDisplayNames.h" + +void GameGUI::drawBuildingHeader(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + Uint8 r, g, b; + + // draw "building" of "player" + std::string title; + std::string key = "[" + buildingType->type + "]"; + title += Toolkit::getStringTable()->getString(key.c_str()); + { + title += " ("; + title += displayPlayerName(*selBuild->owner); + title += ")"; + } + + if (localTeam->teamNumber == selBuild->owner->teamNumber) + { r=160; g=160; b=255; } + else if (localTeam->allies & selBuild->owner->me) + { r=255; g=210; b=20; } + else + { r=255; g=50; b=50; } + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); + int titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); + int titlePos = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+((RIGHT_MENU_WIDTH-titleLen)>>1); + globalContainer->gfx->drawString(titlePos, ypos, globalContainer->littleFont, title.c_str()); + globalContainer->littleFont->popStyle(); + + // building text + title = ""; + if ((buildingType->nextLevel>=0) || (buildingType->prevLevel>=0)) + { + const std::string textT = Toolkit::getStringTable()->getString("[level]"); + title += FormatableString("%0 %1").arg(textT).arg(buildingType->level+1); + } + if (buildingType->isBuildingSite) + { + title += " ("; + title += Toolkit::getStringTable()->getString("[building site]"); + title += ")"; + } + if (buildingType->prestige) + { + title += " - "; + title += Toolkit::getStringTable()->getString("[Prestige]"); + } + titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); + titlePos = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+((RIGHT_MENU_WIDTH-titleLen)>>1); + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 200, 200, 200)); + globalContainer->gfx->drawString(titlePos, ypos+YOFFSET_TEXT_PARA-1, globalContainer->littleFont, title.c_str()); + globalContainer->littleFont->popStyle(); + + ypos += YOFFSET_NAME; +} + +void GameGUI::drawBuildingIcon(Building* selBuild, BuildingType* buildingType, int ypos) +{ + Sprite *miniSprite; + int imgid; + if (buildingType->miniSpriteImage >= 0) + { + miniSprite = buildingType->miniSpritePtr; + imgid = buildingType->miniSpriteImage; + } + else + { + miniSprite = buildingType->gameSpritePtr; + imgid = buildingType->gameSpriteImage; + } + int dx = (56-miniSprite->getW(imgid))>>1; + int dy = (46-miniSprite->getH(imgid))>>1; + int ddx = (RIGHT_MENU_HALF_WIDTH - 56) / 2 + 2; + miniSprite->setBaseColor(selBuild->owner->color); + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+ddx+dx, ypos+4+dy, miniSprite, imgid); + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+ddx, ypos+4, globalContainer->gamegui, 18); + globalContainer->gfx->finishDrawingSprite(miniSprite, 255); +} + +void GameGUI::drawBuildingHP(Building* selBuild, BuildingType* buildingType, int ypos) +{ + if (!buildingType->hpMax) + return; + + Uint8 r, g, b; + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos, globalContainer->littleFont, Toolkit::getStringTable()->getString("[hp]")); + globalContainer->littleFont->popStyle(); + + if (selBuild->hp <= buildingType->hpMax/5) + { r=255; g=0; b=0; } + else + { r=0; g=255; b=0; } + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0/%1").arg(selBuild->hp).arg(buildingType->hpMax).c_str()); + globalContainer->littleFont->popStyle(); +} + +void GameGUI::drawBuildingInsideStats(Building* selBuild, BuildingType* buildingType, int ypos) +{ + if (!buildingType->maxUnitInside) + return; + if (!((selBuild->owner->allies) & (1<littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+YOFFSET_TEXT_LINE, globalContainer->littleFont, Toolkit::getStringTable()->getString("[inside]")); + globalContainer->littleFont->popStyle(); + if (selBuild->buildingState==Building::ALIVE) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0/%1").arg(selBuild->unitsInside.size()).arg(buildingType->maxUnitInside).c_str()); + } + else + { + if (selBuild->unitsInside.size()>1) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0%1").arg(Toolkit::getStringTable()->getString("[Still (i)]")).arg(selBuild->unitsInside.size()).c_str()); + } + else if (selBuild->unitsInside.size()==1) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, globalContainer->littleFont, + Toolkit::getStringTable()->getString("[Still one]") ); + } + } +} + +void GameGUI::drawBuildingFlagInfo(Building* selBuild, BuildingType* buildingType, int ypos) +{ + if (!buildingType->defaultUnitStayRange) + return; + if (!((selBuild->owner->allies) & (1<littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos, globalContainer->littleFont, FormatableString("%0").arg(Toolkit::getStringTable()->getString("[In way]")).c_str()); + globalContainer->littleFont->popStyle(); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0").arg(goingTo).c_str()); + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+YOFFSET_TEXT_LINE, + globalContainer->littleFont, FormatableString(Toolkit::getStringTable()->getString("[On the spot]")).c_str()); + globalContainer->littleFont->popStyle(); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-+RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0").arg(onSpot).c_str()); +} + +void GameGUI::drawBuildingWorkingControls(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + if (!buildingType->maxUnitWorking) + return; + + if ((selBuild->owner->allies)&(1<buildingState==Building::ALIVE) + { + // If we're replaying, display the actual number, not the locally cached one (changable by the gui user) + const int maxUnitsWorking = (globalContainer->replaying?selBuild->maxUnitWorking:displayedMaxUnitWorking(*selBuild)); + + std::string working = Toolkit::getStringTable()->getString("[working]"); + const int len = globalContainer->littleFont->getStringWidth(working)+4; + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, working); + globalContainer->littleFont->popStyle(); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4+len, ypos, globalContainer->littleFont, FormatableString("%0/%1").arg((int)selBuild->unitsWorking.size()).arg(maxUnitsWorking).c_str()); + drawScrollBox(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos+YOFFSET_TEXT_BAR, maxUnitsWorking, selBuild->unitsWorking.size(), MAX_UNIT_WORKING); + } + else + { + if (selBuild->unitsWorking.size()>1) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0%1%2").arg(Toolkit::getStringTable()->getString("[still (w)]")).arg(selBuild->unitsWorking.size()).arg(Toolkit::getStringTable()->getString("[units working]")).c_str()); + } + else if (selBuild->unitsWorking.size()==1) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, + Toolkit::getStringTable()->getString("[still one unit working]") ); + } + } + } + if(hilights.find(HilightUnitsAssignedBar) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36, ypos+6, 38)); + } + ypos += YOFFSET_BAR+YOFFSET_B_SEP; +} + +void GameGUI::drawBuildingPriorityControls(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + if (!buildingType->maxUnitWorking) + return; + if (!((selBuild->owner->allies)&(1<buildingState != Building::ALIVE) + return; + + // If we're replaying, display the actual number, not the locally cached one (changable by the gui user) + const int priority = (globalContainer->replaying?selBuild->priority:selBuild->priorityLocal); + + ypos += YOFFSET_B_SEP; + + int width = 128/3; + std::string prioritystr = Toolkit::getStringTable()->getString("[priority]"); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, prioritystr); + + std::string lowstr = Toolkit::getStringTable()->getString("[low priority]"); + std::string medstr = Toolkit::getStringTable()->getString("[medium priority]"); + std::string highstr = Toolkit::getStringTable()->getString("[high priority]"); + + drawRadioButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos+12+4, (priority==-1)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+14, ypos+12+2, globalContainer->littleFont, lowstr); + + drawRadioButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+width, ypos+12+4, (priority==0)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+14+width, ypos+12+2, globalContainer->littleFont, medstr); + + drawRadioButton(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+width*2, ypos+12+4, (priority==1)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+14+width*2, ypos+12+2, globalContainer->littleFont, highstr); + + ypos += YOFFSET_BAR+YOFFSET_B_SEP; +} + +void GameGUI::drawBuildingRangeControls(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + if (!buildingType->defaultUnitStayRange) + return; + + if ((selBuild->owner->allies)&(1<replaying?selBuild->unitStayRange:displayedUnitStayRange(*selBuild)); + + std::string range = Toolkit::getStringTable()->getString("[range]"); + const int len = globalContainer->littleFont->getStringWidth(range)+4; + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, range); + globalContainer->littleFont->popStyle(); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4+len, ypos, globalContainer->littleFont, FormatableString("%0").arg(selBuild->unitStayRange).c_str()); + drawScrollBox(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos+YOFFSET_TEXT_BAR, unitStayRange, 0, selBuild->type->maxUnitStayRange); + } + ypos += YOFFSET_BAR+YOFFSET_B_SEP; +} + +void GameGUI::drawBuildingCombatStats(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + (void)selBuild; + if (buildingType->armor) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[armor]")).arg(buildingType->armor).c_str()); + ypos+=YOFFSET_TEXT_LINE; + } + if (buildingType->maxUnitInside) + ypos += YOFFSET_INFOS; + if (buildingType->shootDamage) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos+1, globalContainer->littleFont, FormatableString("%0 : %1").arg(Toolkit::getStringTable()->getString("[damage]")).arg(buildingType->shootDamage).c_str()); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos+12, globalContainer->littleFont, FormatableString("%0 : %1").arg(Toolkit::getStringTable()->getString("[range]")).arg(buildingType->shootingRange).c_str()); + ypos += YOFFSET_TOWER; + } +} + +void GameGUI::drawBuildingExchange(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + if (!buildingType->canExchange) + return; + if (!((selBuild->owner->sharedVisionExchange)&(1<littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 185, 195, 21)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, Toolkit::getStringTable()->getString("[market]")); + globalContainer->littleFont->popStyle(); + //globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-36-3, ypos+1, globalContainer->gamegui, EXCHANGE_BUILDING_ICONS); + ypos += YOFFSET_TEXT_PARA; + for (unsigned i=0; igfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1/%2)").arg(getRessourceName(i+HAPPYNESS_BASE)).arg(selBuild->ressources[i+HAPPYNESS_BASE]).arg(buildingType->maxRessource[i+HAPPYNESS_BASE]).c_str()); + + /* + int inId, outId; + if (selBuild->receiveRessourceMaskLocal & (1<sendRessourceMaskLocal & (1<gfx->drawSprite(globalContainer->gfx->getW()-36, ypos+2, globalContainer->gamegui, inId); + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-18, ypos+2, globalContainer->gamegui, outId); + */ + + ypos += YOFFSET_TEXT_PARA; + } +} + +void GameGUI::drawBuildingResources(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + if (!((selBuild->owner->allies) & (1<canExchange) + return; + + // ressources in + for (unsigned i=0; iressourcesTypes.size(); i++) + { + if (buildingType->maxRessource[i]) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 : %1/%2").arg(getRessourceName(i)).arg(selBuild->ressources[i]).arg(buildingType->maxRessource[i]).c_str()); + ypos += YOFFSET_RESSOURCE_LINE; + } + } + if (buildingType->maxBullets) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 : %1/%2").arg(Toolkit::getStringTable()->getString("[Bullets]")).arg(selBuild->bullets).arg(buildingType->maxBullets).c_str()); + ypos += YOFFSET_RESSOURCE_LINE; + } + ypos += YOFFSET_RESSOURCE_SECTION_PAD; +} + +// Draws the swarm-building production-timeout progress bar followed by one +// scrollbox per unit type for the local-vs-actual unit ratios. The progress +// bar is split into an "elapsed" (blue) and "remaining" (gray) segment scaled +// to SWARM_PROGRESS_BAR_WIDTH. Each ratio scrollbox shows two channels: +// ratioLocal[i] (the user's pending input, drawn as the lighter bar) and +// ratio[i] (the simulation-confirmed value, drawn as the darker overlay). +// During replay both channels equal ratio[i] and overlay exactly; during +// normal play they differ briefly while OrderModifySwarm is in flight. +void GameGUI::drawBuildingSwarmRatios(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + if (!((selBuild->owner->allies) & (1<unitProductionTime) + return; + + int left=(selBuild->productionTimeout*SWARM_PROGRESS_BAR_WIDTH)/buildingType->unitProductionTime; + int elapsed=SWARM_PROGRESS_BAR_WIDTH-left; + globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos, elapsed, SWARM_PROGRESS_BAR_HEIGHT, 100, 100, 255); + globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+elapsed, ypos, left, SWARM_PROGRESS_BAR_HEIGHT, 128, 128, 128); + + ypos += YOFFSET_SWARM_PROGRESS_BAR; + for (int i=0; ireplaying?selBuild->ratio[i]:selBuild->ratioLocal[i]); + + drawScrollBox(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos, ratio, selBuild->ratio[i], MAX_RATIO_RANGE); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+24, ypos, globalContainer->littleFont, getUnitName(i)); + + if(i==1 && hilights.find(HilightRatioBar) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET-36, ypos-8, 38)); + } + + ypos += YOFFSET_SWARM_RATIO_LINE; + } +} + +// Returns the string-table key for a unit-can't-work reason. The two +// access/too-far-from-building rows reword "building" → "flag" when the +// selected building type is virtual (a flag), so isVirtual is consulted +// only for those two reasons. Table is indexed by Building::UnitCantWorkReason; +// the static_assert keeps it locked to the enum size so future additions +// to UnitCantWorkReason can't silently fall off the end. +static const char* failureReasonKey(Building::UnitCantWorkReason reason, bool isVirtual) +{ + static constexpr const char* kReasonKey[Building::UnitCantWorkReasonSize] = { + /* UnitNotAvailable */ "[%0 units not available]", + /* UnitTooLowLevel */ "[%0 units too low level]", + /* UnitCantAccessBuilding */ "[%0 units can't access building]", + /* UnitTooFarFromBuilding */ "[%0 units too far from building]", + /* UnitCantAccessResource */ "[%0 units can't access resource]", + /* UnitCantAccessFruit */ "[%0 units can't access fruit]", + /* UnitTooFarFromResource */ "[%0 units too far from resource]", + /* UnitTooFarFromFruit */ "[%0 units too far from fruit]", + }; + static_assert(Building::UnitCantWorkReasonSize == 8, + "failureReasonKey table must stay in sync with Building::UnitCantWorkReason"); + + if (isVirtual) + { + if (reason == Building::UnitCantAccessBuilding) + return "[%0 units can't access flag]"; + if (reason == Building::UnitTooFarFromBuilding) + return "[%0 units too far from flag]"; + } + return kReasonKey[reason]; +} + +void GameGUI::drawBuildingFailureReasons(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + if (!((selBuild->owner->allies) & (1<unitsFailingRequirements[j]; + if(j!=0 && n>0) + otherFailure=true; + } + if(!otherFailure) + return; + + for(unsigned j=0; junitsFailingRequirements[j]; + if(n>0 && (int)selBuild->unitsWorking.size() < selBuild->desiredMaxUnitWorking) + { + const char* key = failureReasonKey(static_cast(j), buildingType->isVirtual); + std::string s = FormatableString(Toolkit::getStringTable()->getString(key)).arg(n); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+10, ypos, globalContainer->littleFont, s.c_str()); + ypos += YOFFSET_RESSOURCE_LINE; + } + } +} + +void GameGUI::drawBuildingActionButtons(Building* selBuild, BuildingType* buildingType, unsigned unitInsideBarYDec) +{ + if (!((selBuild->owner->allies) & (1<owner != localTeam) + return; + + const int btnX = globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET; + const int primaryY = globalContainer->gfx->getH()-BOTTOM_BUTTON_PRIMARY_YOFFSET; + const int secondaryY = globalContainer->gfx->getH()-BOTTOM_BUTTON_SECONDARY_YOFFSET; + + if (selBuild->constructionResultState==Building::REPAIR) + { + if (buildingType->isBuildingSite) + assert(buildingType->nextLevel!=-1); + drawBlueButton(btnX, primaryY, "[cancel repair]"); + } + else if (selBuild->constructionResultState==Building::UPGRADE) + { + assert(buildingType->nextLevel!=-1); + if (buildingType->isBuildingSite) + assert(buildingType->prevLevel!=-1); + drawBlueButton(btnX, primaryY, "[cancel upgrade]"); + } + else if ((selBuild->constructionResultState==Building::NO_CONSTRUCTION) && (selBuild->buildingState==Building::ALIVE) && !buildingType->isBuildingSite) + { + if (selBuild->hphpMax) + { + // repair + if (selBuild->type->regenerationSpeed==0 && selBuild->isHardSpaceForBuildingSite(Building::REPAIR) && localTeam->maxBuildLevel()>=buildingType->level) + { + drawBlueButton(btnX, primaryY, "[repair]"); + if ( mouseX>btnX+12 && mouseXgfx->getW()-12 + && mouseY>primaryY && mouseYlittleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 200, 200, 255)); + int ressources[BASIC_COUNT]; + selBuild->getRessourceCountToRepair(ressources); + drawCosts(ressources, globalContainer->littleFont); + globalContainer->littleFont->popStyle(); + } + } + } + else if (buildingType->nextLevel!=-1) + { + // upgrade + if (selBuild->isHardSpaceForBuildingSite(Building::UPGRADE) && (localTeam->maxBuildLevel()>buildingType->level)) + { + drawBlueButton(btnX, primaryY, "[upgrade]"); + if ( mouseX>btnX+12 && mouseXgfx->getW()-12 + && mouseY>primaryY && mouseYlittleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 200, 200, 255)); + drawBuildingUpgradePreview(selBuild, buildingType, unitInsideBarYDec); + globalContainer->littleFont->popStyle(); + } + } + } + } + + // building destruction + if (selBuild->buildingState==Building::WAITING_FOR_DESTRUCTION) + { + drawRedButton(btnX, secondaryY, "[cancel destroy]"); + } + else if (selBuild->buildingState==Building::ALIVE) + { + drawRedButton(btnX, secondaryY, "[destroy]"); + } +} + +void GameGUI::drawBuildingTimeToLeaveBar(Building* selBuild, BuildingType* buildingType, int& ypos, unsigned& unitInsideBarYDec) +{ + if (!((selBuild->owner->allies) & (1<timeToFeedUnit) + maxTimeTo=buildingType->timeToFeedUnit; + else if (buildingType->timeToHealUnit) + maxTimeTo=buildingType->timeToHealUnit; + else + for (int i=0; iupgradeTime[i]) + maxTimeTo=std::max(maxTimeTo, buildingType->upgradeTime[i]); + int dec = (RIGHT_MENU_RIGHT_OFFSET-128); + if (maxTimeTo) + { + globalContainer->gfx->drawFilledRect(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos, 128, 7, 168, 150, 90); + for (std::list::iterator it=selBuild->unitsInside.begin(); it!=selBuild->unitsInside.end(); ++it) + { + Unit *u=*it; + assert(u); + if (u->displacement==Unit::DIS_INSIDE) + { + int dividend=-u->insideTimeout*128+128-u->delta/2; + int divisor=1+maxTimeTo; + int left=dividend/divisor; + int alpha=((dividend%divisor)*255)/divisor; + + if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) + { + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-1-dec, ypos, 7, 17, 30, 64); + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-dec, ypos, 7, 63, 111, 149); + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left+1-dec, ypos, 7, 17, 30, 64); + } + else + { + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-2-dec, ypos, 7, 17, 30, 64, alpha); + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-1-dec, ypos, 7, 17, 30, 64); + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-dec, ypos, 7, 17, 30, 64); + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left+1-dec, ypos, 7, 17, 30, 64); + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left+2-dec, ypos, 7, 17, 30, 64, 255-alpha); + + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-1-dec, ypos, 7, 63, 111, 149, alpha); + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left-dec, ypos, 7, 63, 111, 149); + globalContainer->gfx->drawVertLine(globalContainer->gfx->getW()-left+1-dec, ypos, 7, 63, 111, 149, 255-alpha); + } + } + } + + ypos += YOFFSET_PROGRESS_BAR; + unitInsideBarYDec = YOFFSET_PROGRESS_BAR; + } +} + +void GameGUI::drawBuildingFlagControls(Building* selBuild, BuildingType* buildingType, int& ypos) +{ + if (!((selBuild->owner->allies) & (1<type == "clearingflag") + { + ypos += YOFFSET_B_SEP; + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, + Toolkit::getStringTable()->getString("[Clearing:]")); + ypos += YOFFSET_TEXT_PARA; + for (int i=0; igfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+28, ypos, globalContainer->littleFont, + getRessourceName(i)); + int spriteId; + if (globalContainer->replaying?selBuild->clearingRessources[i]:selBuild->clearingRessourcesLocal[i]) + spriteId=20; + else + spriteId=19; + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+10, ypos+2, globalContainer->gamegui, spriteId); + + ypos+=YOFFSET_TEXT_PARA; + } + } + // min war level for war flags: + else if (buildingType->type == "warflag") + { + ypos += YOFFSET_B_SEP; + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, + Toolkit::getStringTable()->getString("[Min required level:]")); + ypos += YOFFSET_TEXT_PARA; + for (int i=0; i<4; i++) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+28, ypos, globalContainer->littleFont, 1+i); + int spriteId; + if (i==(globalContainer->replaying?selBuild->minLevelToFlag:selBuild->minLevelToFlagLocal)) + spriteId=20; + else + spriteId=19; + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+10, ypos+2, globalContainer->gamegui, spriteId); + + ypos+=YOFFSET_TEXT_PARA; + } + } + else if (buildingType->type == "explorationflag") + { + int spriteId; + + ypos += YOFFSET_B_SEP; + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, + Toolkit::getStringTable()->getString("[Min required level:]")); + ypos += YOFFSET_TEXT_PARA; + + // we use minLevelToFlag as an int which says what magic effect at minimum an explorer + // must be able to do to be accepted at this flag + // 0 == any explorer + // 1 == must be able to attack ground + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+28, ypos, globalContainer->littleFont,Toolkit::getStringTable()->getString("[any explorer]")); + if ((globalContainer->replaying?selBuild->minLevelToFlag:selBuild->minLevelToFlagLocal) == 0) + spriteId = 20; + else + spriteId = 19; + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+10, ypos+2, globalContainer->gamegui, spriteId); + + ypos += YOFFSET_TEXT_PARA; + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+28, ypos, globalContainer->littleFont,Toolkit::getStringTable()->getString("[ground attack]")); + if ((globalContainer->replaying?selBuild->minLevelToFlag:selBuild->minLevelToFlagLocal) == 1) + spriteId = 20; + else + spriteId = 19; + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+10, ypos+2, globalContainer->gamegui, spriteId); + ypos += YOFFSET_TEXT_PARA; + } +} + +void GameGUI::drawBuildingUpgradePreview(Building* selBuild, BuildingType* buildingType, unsigned unitInsideBarYDec) +{ + // We draw the ressources cost. + int typeNum=buildingType->nextLevel; + BuildingType *bt=globalContainer->buildingsTypes.get(typeNum); + drawCosts(bt->maxRessource, globalContainer->littleFont); + + // We draw the new abilities: + int blueYpos = YPOS_BASE_BUILDING + YOFFSET_NAME; + + bt=globalContainer->buildingsTypes.get(bt->nextLevel); + + if (bt->hpMax) + drawValueAlignedRight(blueYpos+YOFFSET_TEXT_LINE, bt->hpMax); + if (bt->maxUnitInside) + drawValueAlignedRight(blueYpos+YOFFSET_TEXT_PARA+2*YOFFSET_TEXT_LINE, bt->maxUnitInside); + blueYpos += YOFFSET_ICON+YOFFSET_B_SEP; + + if (buildingType->maxUnitWorking) + blueYpos += YOFFSET_BAR+YOFFSET_B_SEP; + + if (bt->armor) + { + if (!buildingType->armor) + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, blueYpos-1, globalContainer->littleFont, Toolkit::getStringTable()->getString("[armor]")); + drawValueAlignedRight(blueYpos-1, bt->armor); + blueYpos+=YOFFSET_TEXT_LINE; + } + if (buildingType->maxUnitInside) + blueYpos += YOFFSET_INFOS; + if (bt->shootDamage) + { + drawValueAlignedRight(blueYpos+1, bt->shootDamage); + drawValueAlignedRight(blueYpos+12, bt->shootingRange); + blueYpos += YOFFSET_TOWER; + } + blueYpos += unitInsideBarYDec; + blueYpos += YOFFSET_B_SEP; + + unsigned j = 0; + for (unsigned i=0; iressourcesTypes.size(); i++) + { + if (buildingType->maxRessource[i]) + { + drawValueAlignedRight(blueYpos+(j*11), bt->maxRessource[i]); + j++; + } + } + + if (bt->maxBullets) + { + drawValueAlignedRight(blueYpos+(j*11), bt->maxBullets); + j++; + } +} diff --git a/src/gui/GameGUIDrawBuildingInfos.cpp b/src/gui/GameGUIDrawBuildingInfos.cpp new file mode 100644 index 000000000..ee670762d --- /dev/null +++ b/src/gui/GameGUIDrawBuildingInfos.cpp @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIInternal.h" +#include "GlobalContainer.h" + +void GameGUI::drawBuildingInfos(void) +{ + Building* selBuild = selection.building; + assert(selBuild); + BuildingType *buildingType = selBuild->type; + int ypos = YPOS_BASE_BUILDING; + unsigned unitInsideBarYDec = 0; + + // Title row + level/site/prestige subtitle. + drawBuildingHeader(selBuild, buildingType, ypos); + + // Icon row: icon + HP / inside-count / flag stat all share this row. + drawBuildingIcon(selBuild, buildingType, ypos); + drawBuildingHP(selBuild, buildingType, ypos); + drawBuildingInsideStats(selBuild, buildingType, ypos); + drawBuildingFlagInfo(selBuild, buildingType, ypos); + ypos += YOFFSET_ICON+YOFFSET_B_SEP; + + // Worker assignment row, priority radios, flag stay-range. + drawBuildingWorkingControls(selBuild, buildingType, ypos); + drawBuildingPriorityControls(selBuild, buildingType, ypos); + drawBuildingRangeControls(selBuild, buildingType, ypos); + + // flag control of team and allies (clearing/war/exploration) + drawBuildingFlagControls(selBuild, buildingType, ypos); + + globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); + + // armor / shoot damage / shoot range, then time-to-leave progress bar. + drawBuildingCombatStats(selBuild, buildingType, ypos); + drawBuildingTimeToLeaveBar(selBuild, buildingType, ypos, unitInsideBarYDec); + + ypos += YOFFSET_B_SEP; + + // Lower body: market, resources, swarm ratios, failure reasons, action buttons. + drawBuildingExchange(selBuild, buildingType, ypos); + drawBuildingResources(selBuild, buildingType, ypos); + drawBuildingSwarmRatios(selBuild, buildingType, ypos); + drawBuildingFailureReasons(selBuild, buildingType, ypos); + drawBuildingActionButtons(selBuild, buildingType, unitInsideBarYDec); +} diff --git a/src/gui/GameGUIDrawChoice.cpp b/src/gui/GameGUIDrawChoice.cpp new file mode 100644 index 000000000..6ecd6d723 --- /dev/null +++ b/src/gui/GameGUIDrawChoice.cpp @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include + +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIInternal.h" +#include "GlobalContainer.h" +#include "IntBuildingType.h" + +namespace { + +// Layout of the choice panel (file-private; the constants drive both the sprite grid +// and the mouse hit grid, except for the Y origin — see BH-290). +constexpr int CHOICE_ROW_HEIGHT_PX = 46; +// Width of the per-cell clip rect used when blitting the building icon. This is a +// sprite-tile width, not the cell width (which is RIGHT_MENU_WIDTH/numberPerLine). +constexpr int CHOICE_SPRITE_CLIP_W_PX = 64; + +// Selection-highlight sprite IDs in the `gamegui` sheet, and per-orientation Y nudge. +constexpr int CHOICE_HIGHLIGHT_SPRITE_2COL = 8; +constexpr int CHOICE_HIGHLIGHT_SPRITE_3COL = 23; +constexpr int CHOICE_HIGHLIGHT_DECY_2COL = 1; +constexpr int CHOICE_HIGHLIGHT_DECY_3COL = 4; + +// Right-panel clip rect: starts at this Y and runs to the bottom of the screen. +constexpr int CHOICE_PANEL_CLIP_TOP_Y = 128; + +// The info block at the bottom of the right panel is anchored this many pixels above +// the bottom of the screen. +constexpr int CHOICE_INFO_BOTTOM_OFFSET_PX = 50; + +// Find the index of `name` in `types`, or nullopt if absent. +std::optional findChoiceIndex(const std::vector& types, const std::string& name) +{ + auto it = std::find(types.begin(), types.end(), name); + if (it == types.end()) + return std::nullopt; + return static_cast(it - types.begin()); +} + +} // namespace + +void GameGUI::drawChoiceSprites(const std::vector& types, const std::vector& states, unsigned numberPerLine) +{ + const int width = RIGHT_MENU_WIDTH / static_cast(numberPerLine); + const int panelLeftX = globalContainer->gfx->getW() - RIGHT_MENU_WIDTH; + + for (size_t i = 0; i < types.size(); i++) + { + if (!states[i]) + continue; + + const std::string& type = types[i]; + BuildingType *bt = globalContainer->buildingsTypes.getByType(type.c_str(), 0, false); + assert(bt); + int imgid = bt->miniSpriteImage; + + const int x = (static_cast(i % numberPerLine) * width) + panelLeftX; + const int y = (static_cast(i / numberPerLine) * CHOICE_ROW_HEIGHT_PX) + YPOS_BASE_BUILDING; + globalContainer->gfx->setClipRect(x, y, CHOICE_SPRITE_CLIP_W_PX, CHOICE_ROW_HEIGHT_PX); + + Sprite *buildingSprite; + if (imgid >= 0) + { + buildingSprite = bt->miniSpritePtr; + } + else + { + buildingSprite = bt->gameSpritePtr; + imgid = bt->gameSpriteImage; + } + + const int decX = (width - buildingSprite->getW(imgid)) >> 1; + const int decY = (CHOICE_ROW_HEIGHT_PX - buildingSprite->getW(imgid)) >> 1; + + buildingSprite->setBaseColor(localTeam->color); + globalContainer->gfx->drawSprite(x + decX, y + decY, buildingSprite, imgid); + globalContainer->gfx->finishDrawingSprite(buildingSprite, 255); + + globalContainer->gfx->setClipRect(); + if (hilights.find(HilightBuildingOnPanel + IntBuildingType::shortNumberFromType(type)) != hilights.end()) + { + // Note: `y-6+decX` is preserved verbatim — see BH-291 for the X-into-Y typo. + arrowPositions.push_back(HilightArrowPosition(x + decX - 36, y - 6 + decX, 38)); + } + } +} + +void GameGUI::drawChoiceHighlight(size_t selIdx, unsigned numberPerLine) +{ + const int width = RIGHT_MENU_WIDTH / static_cast(numberPerLine); + const int panelLeftX = globalContainer->gfx->getW() - RIGHT_MENU_WIDTH; + + const int spriteId = (numberPerLine == 2) ? CHOICE_HIGHLIGHT_SPRITE_2COL : CHOICE_HIGHLIGHT_SPRITE_3COL; + const int decYNudge = (numberPerLine == 2) ? CHOICE_HIGHLIGHT_DECY_2COL : CHOICE_HIGHLIGHT_DECY_3COL; + const int sw = globalContainer->gamegui->getW(spriteId); + + const int x = (static_cast(selIdx % numberPerLine) * width) + panelLeftX; + const int y = (static_cast(selIdx / numberPerLine) * CHOICE_ROW_HEIGHT_PX) + YPOS_BASE_BUILDING; + const int decX = (width - sw) / 2; + + globalContainer->gfx->drawSprite(x + decX, y + decYNudge, globalContainer->gamegui, spriteId); +} + +std::optional GameGUI::pickChoiceUnderMouse(int panelTopY, size_t count, unsigned numberPerLine) const +{ + const int width = RIGHT_MENU_WIDTH / static_cast(numberPerLine); + const int panelLeftX = globalContainer->gfx->getW() - RIGHT_MENU_WIDTH; + + if (mouseX <= panelLeftX) + return std::nullopt; + if (mouseY <= panelTopY) + return std::nullopt; + + const int xNum = (mouseX - panelLeftX) / width; + const int yNum = (mouseY - panelTopY) / CHOICE_ROW_HEIGHT_PX; + const size_t id = static_cast(yNum) * numberPerLine + static_cast(xNum); + if (id >= count) + return std::nullopt; + return id; +} + +void GameGUI::drawChoiceInfoPanel(const std::string& type) +{ + const int panelLeftX = globalContainer->gfx->getW() - RIGHT_MENU_WIDTH; + const int buildingInfoStart = globalContainer->gfx->getH() - CHOICE_INFO_BOTTOM_OFFSET_PX; + + std::string key = "[" + type + "]"; + drawTextCenter(panelLeftX, buildingInfoStart - 32, key.c_str()); + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 128, 128, 128)); + key = "[" + type + " explanation]"; + drawTextCenter(panelLeftX, buildingInfoStart - 20, key.c_str()); + key = "[" + type + " explanation 2]"; + drawTextCenter(panelLeftX, buildingInfoStart - 8, key.c_str()); + globalContainer->littleFont->popStyle(); + + BuildingType *bt = globalContainer->buildingsTypes.getByType(type, 0, true); + if (!bt) + return; + + const int colLeftX = panelLeftX + 4 + (RIGHT_MENU_WIDTH - 128) / 2; + const int colRightX = colLeftX + 64; + + // maxRessource[] indexes are the engine-wide resource ordering: 0=Wood, 1=Corn, + // 2=Papyrus, 3=Stone, 4=Alga. Don't reorder without auditing every consumer. + globalContainer->gfx->drawString(colLeftX, buildingInfoStart + 6, globalContainer->littleFont, + FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Wood]")).arg(bt->maxRessource[0]).c_str()); + globalContainer->gfx->drawString(colLeftX, buildingInfoStart + 17, globalContainer->littleFont, + FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Stone]")).arg(bt->maxRessource[3]).c_str()); + + globalContainer->gfx->drawString(colRightX, buildingInfoStart + 6, globalContainer->littleFont, + FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Alga]")).arg(bt->maxRessource[4]).c_str()); + globalContainer->gfx->drawString(colRightX, buildingInfoStart + 17, globalContainer->littleFont, + FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Corn]")).arg(bt->maxRessource[1]).c_str()); + + globalContainer->gfx->drawString(colLeftX, buildingInfoStart + 28, globalContainer->littleFont, + FormatableString("%0: %1").arg(Toolkit::getStringTable()->getString("[Papyrus]")).arg(bt->maxRessource[2]).c_str()); +} + +void GameGUI::drawChoice(int panelTopY, std::vector &types, std::vector &states, unsigned numberPerLine) +{ + assert(numberPerLine >= 2); + assert(numberPerLine <= 3); + + // 1. Paint icon grid (and queue tutorial-hilight arrows). + drawChoiceSprites(types, states, numberPerLine); + + // 2. Paint the selection highlight over the active tool's icon, if any. + globalContainer->gfx->setClipRect( + globalContainer->gfx->getW() - RIGHT_MENU_WIDTH, + CHOICE_PANEL_CLIP_TOP_Y, + RIGHT_MENU_WIDTH, + globalContainer->gfx->getH() - CHOICE_PANEL_CLIP_TOP_Y); + + if (selectionMode == TOOL_SELECTION) + { + const auto selIdx = findChoiceIndex(types, toolManager.getBuildingName()); + assert(selIdx); + drawChoiceHighlight(*selIdx, numberPerLine); + } + + // 3. Resolve which icon to show info for: prefer mouse-hover, fall back to the + // currently-selected tool when the mouse is elsewhere. + std::optional infoIdx = pickChoiceUnderMouse(panelTopY, types.size(), numberPerLine); + if (!infoIdx && !toolManager.getBuildingName().empty()) + infoIdx = findChoiceIndex(types, toolManager.getBuildingName()); + + // 4. Paint the info text block, but only when the chosen cell is active. + if (infoIdx && states[*infoIdx]) + drawChoiceInfoPanel(types[*infoIdx]); +} diff --git a/src/gui/GameGUIDrawMiscPanels.cpp b/src/gui/GameGUIDrawMiscPanels.cpp new file mode 100644 index 000000000..4cce714fe --- /dev/null +++ b/src/gui/GameGUIDrawMiscPanels.cpp @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIInternal.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "ReplayReader.h" +#include "Team.h" +#include "TeamDisplay.h" + +void GameGUI::drawRessourceInfos(void) +{ + // Precondition (established by checkSelection() in drawPanel): when we + // reach here the resource selection still references a live resource tile. + // The early-return is defensive — should never trigger. + const Ressource &r = game.map.getRessource(selection.ressource); + if (r.type==NO_RES_TYPE) + return; + + int ypos = YPOS_BASE_RESSOURCE; + + // Draw ressource name + const std::string &ressourceName = getRessourceName(r.type); + int titleLen = globalContainer->littleFont->getStringWidth(ressourceName.c_str()); + int titlePos = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+((RIGHT_MENU_WIDTH-titleLen)>>1); + globalContainer->gfx->drawString(titlePos, ypos+(YOFFSET_TEXT_PARA>>1), globalContainer->littleFont, ressourceName.c_str()); + ypos += 2*YOFFSET_TEXT_PARA; + + // Draw ressource image + const RessourceType* rt = globalContainer->ressourcesTypes.get(r.type); + unsigned resImg = rt->gfxId + r.variety*rt->sizesCount + r.amount; + if (!rt->eternal) + resImg--; + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+16, ypos, globalContainer->ressources, resImg); + + // Draw ressource count + if (rt->granular) + { + int sizesCount=rt->sizesCount; + int amount=r.amount; + const std::string amountS = FormatableString("%0/%1").arg(amount).arg(sizesCount); + int amountSH = globalContainer->littleFont->getStringHeight(amountS.c_str()); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-64, ypos+((32-amountSH)>>1), globalContainer->littleFont, amountS.c_str()); + } +} + +void GameGUI::drawReplayPanel(void) +{ + Font *font=globalContainer->littleFont; + + int x = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + REPLAY_PANEL_XOFFSET; + int y = REPLAY_PANEL_YOFFSET; + int inc = REPLAY_PANEL_SPACE_BETWEEN_OPTIONS; + + globalContainer->gfx->drawString(x, y, font, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[Options]"))); + + drawCheckButton(x, y + 1*inc, Toolkit::getStringTable()->getString("[fog of war]"), globalContainer->replayShowFog); + drawCheckButton(x, y + 2*inc, Toolkit::getStringTable()->getString("[combined vision]"), (globalContainer->replayVisibleTeams == 0xFFFFFFFF)); + drawCheckButton(x, y + 3*inc, Toolkit::getStringTable()->getString("[show areas]"), (globalContainer->replayShowAreas)); + drawCheckButton(x, y + 4*inc, Toolkit::getStringTable()->getString("[show flags]"), (globalContainer->replayShowFlags)); + + globalContainer->gfx->drawString(x, y + REPLAY_PANEL_PLAYERLIST_YOFFSET, font, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[players]"))); + + for (int i = 0; i < game.teamsCount(); i++) + { + // I know this is a matter of taste, but I prefer checkboxes here. Radio buttons are a totally different style + //drawRadioButton(x, y + REPLAY_PANEL_PLAYERLIST_YOFFSET + (i+1)*inc, game.teams[i]->getFirstPlayerName().c_str(), localTeamNo == i); + drawRadioButton(x + 1, y + REPLAY_PANEL_PLAYERLIST_YOFFSET + (i+1)*inc + 1, localTeamNo == i); + globalContainer->gfx->drawString(x + 20, y + REPLAY_PANEL_PLAYERLIST_YOFFSET + (i+1)*inc, font, displayPlayerName(*game.teams[i]).c_str()); + } +} + +void GameGUI::drawReplayProgressBar(bool drawBackground) +{ + assert(globalContainer->replaying); + assert(globalContainer->replayReader); + assert(globalContainer->replayReader->isValid()); + + // set the clipping rectangle + globalContainer->gfx->setClipRect( 0, REPLAY_BAR_Y - 4, REPLAY_BAR_WIDTH, REPLAY_BAR_HEIGHT + 4); + + // draw menu background, black if low speed graphics, transparent otherwise + if (drawBackground) + { + if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) + globalContainer->gfx->drawFilledRect( 0, REPLAY_BAR_Y, REPLAY_BAR_WIDTH, REPLAY_BAR_HEIGHT, 0, 0, 0); + else + globalContainer->gfx->drawFilledRect( 0, REPLAY_BAR_Y, REPLAY_BAR_WIDTH, REPLAY_BAR_HEIGHT, 0, 0, 40, 180); + } + + // Progress bar y + int y = REPLAY_BAR_Y + REPLAY_PROGRESS_BAR_Y_OFFSET; + + // Draw the actual progress bar + Style::style->drawProgressBar(globalContainer->gfx, + REPLAY_PROGRESS_BAR_X_OFFSET + REPLAY_PROGRESS_BAR_CAP_WIDTH - 1, y, + REPLAY_BAR_WIDTH - 2*REPLAY_PROGRESS_BAR_X_OFFSET - REPLAY_PROGRESS_BAR_NUM_BUTTONS * REPLAY_PROGRESS_BAR_BUTTON_WIDTH - 2*REPLAY_PROGRESS_BAR_CAP_WIDTH + 2, + globalContainer->replayReader->getCurrentStep(), + globalContainer->replayReader->getNumStepsTotal()); + + // Draw the round caps + globalContainer->gfx->drawSprite( + REPLAY_PROGRESS_BAR_X_OFFSET, y, + globalContainer->gamegui, + REPLAY_BAR_LEFT_CAP_SPRITE); + globalContainer->gfx->drawSprite( + REPLAY_BAR_WIDTH - REPLAY_PROGRESS_BAR_X_OFFSET - REPLAY_PROGRESS_BAR_CAP_WIDTH, y, + globalContainer->gamegui, + REPLAY_BAR_RIGHT_CAP_SPRITE); + + // Draw the buttons for play, pause and fast-forward + int x = REPLAY_BAR_WIDTH - REPLAY_PROGRESS_BAR_X_OFFSET - REPLAY_PROGRESS_BAR_CAP_WIDTH; + int inc = REPLAY_PROGRESS_BAR_BUTTON_WIDTH; + + globalContainer->gfx->drawSprite( x - inc*3, y, globalContainer->gamegui, (!gamePaused && !globalContainer->replayFastForward ? REPLAY_BAR_PLAY_BUTTON_ACTIVE_SPRITE : REPLAY_BAR_PLAY_BUTTON_SPRITE)); + globalContainer->gfx->drawSprite( x - inc*2, y, globalContainer->gamegui, (gamePaused ? REPLAY_BAR_PAUSE_BUTTON_ACTIVE_SPRITE : REPLAY_BAR_PAUSE_BUTTON_SPRITE)); + globalContainer->gfx->drawSprite( x - inc*1, y, globalContainer->gamegui, (!gamePaused && globalContainer->replayFastForward ? REPLAY_BAR_FAST_FORWARD_BUTTON_ACTIVE_SPRITE : REPLAY_BAR_FAST_FORWARD_BUTTON_SPRITE)); + + // Calculate the time + // This is based on default speed 25 fps, not the actual Engine's speed + // because if we fast-forward we still want to see the old time + unsigned int time1_sec = (globalContainer->replayReader->getCurrentStep()/25)%60; + unsigned int time1_min = (globalContainer->replayReader->getCurrentStep()/(25*60))%60; + unsigned int time1_hour = (globalContainer->replayReader->getCurrentStep()/(25*3600)); + + unsigned int time2_sec = (globalContainer->replayReader->getNumStepsTotal()/25)%60; + unsigned int time2_min = (globalContainer->replayReader->getNumStepsTotal()/(25*60))%60; + unsigned int time2_hour = (globalContainer->replayReader->getNumStepsTotal()/(25*3600)); + + // Draw the time + if (time2_hour <= 99) + { + globalContainer->gfx->drawString(REPLAY_BAR_TIMER_X, y+3, globalContainer->littleFont, + FormatableString("%0:%1:%2 / %3:%4:%5") + .arg(time1_hour) + .arg(time1_min,2,10,'0') + .arg(time1_sec,2,10,'0') + .arg(time2_hour) + .arg(time2_min,2,10,'0') + .arg(time2_sec,2,10,'0') + .c_str()); + } + else + { + // Time did not get saved properly, don't show it + globalContainer->gfx->drawString(REPLAY_BAR_TIMER_X, y+3, globalContainer->littleFont, + FormatableString("%0:%1:%2") + .arg(time1_hour) + .arg(time1_min,2,10,'0') + .arg(time1_sec,2,10,'0') + .c_str()); + } + + // Draw the filename of the replay + std::string replayName = glob2FilenameToName(globalContainer->replayFileName); + int stringWidth = globalContainer->littleFont->getStringWidth(replayName.c_str()); + int pos = (globalContainer->settings.screenWidth-RIGHT_MENU_WIDTH)/2 - stringWidth/2; + globalContainer->gfx->drawString(pos, y+3, globalContainer->littleFont, replayName.c_str()); + + // Draw the border + if (drawBackground) + { + for (int i = 0; i < REPLAY_BAR_WIDTH; i += 32) + { + globalContainer->gfx->drawSprite(i, REPLAY_BAR_Y-4, globalContainer->gamegui, 16); + } + } +} + +void GameGUI::drawFlagView(void) +{ + int dec = (RIGHT_MENU_WIDTH - 128)/2; + // draw flags + drawChoice(YPOS_BASE_FLAG, flagsChoiceName, flagsChoiceState, 3); + + // draw choice of area + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, globalContainer->gamegui, 13); + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+48+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, globalContainer->gamegui, 14); + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+88+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, globalContainer->gamegui, 25); + if (brush.getType() != BrushTool::MODE_NONE) + { + int decX = 8 + ((int)toolManager.getZoneType()) * 40 + dec; + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, YPOS_BASE_FLAG+YOFFSET_BRUSH, globalContainer->gamegui, 22); + } + globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); + if(hilights.find(HilightForbiddenZoneOnPanel) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36+8+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, 38)); + } + if(hilights.find(HilightGuardZoneOnPanel) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36+48+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, 38)); + } + if(hilights.find(HilightClearingZoneOnPanel) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36+88+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH, 38)); + } + + // draw brush + brush.draw(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH+40); + + if(hilights.find(HilightBrushSelector) != hilights.end()) + { + arrowPositions.push_back(HilightArrowPosition(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-36+dec, YPOS_BASE_FLAG+YOFFSET_BRUSH+40+30, 38)); + } + + // draw brush help text + if ((mouseX>globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+dec) && (mouseY>YPOS_BASE_FLAG+YOFFSET_BRUSH)) + { + int buildingInfoStart = globalContainer->gfx->getH()-50; + if (mouseYgfx->getW() + RIGHT_MENU_WIDTH; + if (panelMouseX < 44) + drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[forbidden area]"); + else if (panelMouseX < 84) + drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[guard area]"); + else + drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[clear area]"); + } + else + { + if (toolManager.getZoneType() == GameGUIToolManager::Forbidden) + drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[forbidden area]"); + else if (toolManager.getZoneType() == GameGUIToolManager::Guard) + drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[guard area]"); + else if (toolManager.getZoneType() == GameGUIToolManager::Clearing) + drawTextCenter(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, buildingInfoStart-32, "[clear area]"); + else + assert(false); + } + } +} diff --git a/src/gui/GameGUIDrawUnitInfos.cpp b/src/gui/GameGUIDrawUnitInfos.cpp new file mode 100644 index 000000000..c396998e3 --- /dev/null +++ b/src/gui/GameGUIDrawUnitInfos.cpp @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIInternal.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Player.h" +#include "TeamDisplay.h" +#include "Unit.h" +#include "UnitDisplayNames.h" + +namespace { +// Render one "[label] (displayLevel) : performance" row of the unit-info panel. +// Caller chooses what number to show: WALK/BUILD/HARVEST/ATTACK_SPEED store +// 0-based levels and pass `1 + level[X]` for a 1-based display. SWIM is stored +// 1-based already (`level[SWIM]==0` means "can't swim", which is also the +// guard on whether the row renders at all) and passes the raw value. +void drawAbilityRow(int xpos, int ypos, const char* labelKey, int displayLevel, int performance) +{ + globalContainer->gfx->drawString( + xpos, ypos, globalContainer->littleFont, + FormatableString("%0 (%1) : %2") + .arg(Toolkit::getStringTable()->getString(labelKey)) + .arg(displayLevel) + .arg(performance) + .c_str()); +} +} // namespace + +void GameGUI::drawUnitInfos(void) +{ + Unit* selUnit=selection.unit; + assert(selUnit); + int ypos = YPOS_BASE_UNIT; + Uint8 r, g, b; + + // draw "unit" of "player" + std::string title; + title += getUnitName(selUnit->typeNum); + title += " ("; + + title += displayPlayerName(*selUnit->owner); + title += ")"; + + if (localTeam->teamNumber == selUnit->owner->teamNumber) + { r=160; g=160; b=255; } + else if (localTeam->allies & selUnit->owner->me) + { r=255; g=210; b=20; } + else + { r=255; g=50; b=50; } + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); + int titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); + int titlePos = globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+((RIGHT_MENU_WIDTH-titleLen)>>1); + globalContainer->gfx->drawString(titlePos, ypos+5, globalContainer->littleFont, title.c_str()); + globalContainer->littleFont->popStyle(); + + ypos += YOFFSET_NAME; + + // draw unit's image + Unit* unit=selUnit; + int imgid; + UnitType *ut=unit->race->getUnitType(unit->typeNum, 0); + assert(unit->action>=0); + assert(unit->actionstartImage[unit->action]; + + int dir=unit->direction; + int delta=unit->delta; + assert(dir>=0); + assert(dir<9); + assert(delta>=0); + assert(delta<256); + if (dir==8) + { + imgid+=8*(delta>>5); + } + else + { + imgid+=8*dir; + imgid+=(delta>>5); + } + + Sprite *unitSprite=globalContainer->units; + unitSprite->setBaseColor(unit->owner->color); + int decX = (32-unitSprite->getW(imgid))>>1; + int decY = (32-unitSprite->getH(imgid))>>1; + int ddx = (RIGHT_MENU_HALF_WIDTH - 56) / 2 + 2; + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+ddx+12+decX, ypos+7+4+decY, unitSprite, imgid); + + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+ddx, ypos+4, globalContainer->gamegui, 18); + + // draw HP + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos, globalContainer->littleFont, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[hp]")).c_str()); + + if (selUnit->hp<=selUnit->trigHP) + { r=255; g=0; b=0; } + else + { r=0; g=255; b=0; } + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_LINE, globalContainer->littleFont, FormatableString("%0/%1").arg(selUnit->hp).arg(selUnit->performance[HP]).c_str()); + globalContainer->littleFont->popStyle(); + + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+YOFFSET_TEXT_LINE+YOFFSET_TEXT_PARA, globalContainer->littleFont, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[food]")).c_str()); + + // draw food + if (selUnit->isUnitHungry()) + { r=255; g=0; b=0; } + else + { r=0; g=255; b=0; } + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_HALF_WIDTH, ypos+2*YOFFSET_TEXT_LINE+YOFFSET_TEXT_PARA, globalContainer->littleFont, FormatableString("%0 % (%1)").arg(((float)selUnit->hungry*100.0f)/(float)Unit::HUNGRY_MAX, 0, 0).arg(selUnit->fruitCount).c_str()); + globalContainer->littleFont->popStyle(); + + ypos += YOFFSET_ICON+10; + + int rdec = (RIGHT_MENU_WIDTH-128)/2; + + if (selUnit->performance[HARVEST]) + { + if (selUnit->carriedRessource>=0) + { + const RessourceType* rt = globalContainer->ressourcesTypes.get(selUnit->carriedRessource); + unsigned resImg = rt->gfxId + rt->sizesCount - 1; + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos+8, globalContainer->littleFont, Toolkit::getStringTable()->getString("[carry]")); + globalContainer->gfx->drawSprite(globalContainer->gfx->getW()-32-8-rdec, ypos, globalContainer->ressources, resImg); + } + else + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos+8, globalContainer->littleFont, Toolkit::getStringTable()->getString("[don't carry anything]")); + } + } + ypos += YOFFSET_CARYING+10; + + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 : %1").arg(Toolkit::getStringTable()->getString("[current speed]")).arg(selUnit->speed).c_str()); + ypos += YOFFSET_TEXT_PARA+10; + + if (selUnit->performance[ARMOR]) + { + int armorReductionPerHappyness = selUnit->race->getUnitType(selUnit->typeNum, selUnit->level[ARMOR])->armorReductionPerHappyness; + int realArmor = selUnit->performance[ARMOR] - selUnit->fruitCount * armorReductionPerHappyness; + if (realArmor < 0) + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 255, 0, 0)); + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 : %1 = %2 - %3 * %4").arg(Toolkit::getStringTable()->getString("[armor]")).arg(realArmor).arg(selUnit->performance[ARMOR]).arg(selUnit->fruitCount).arg(armorReductionPerHappyness).c_str()); + if (realArmor < 0) + globalContainer->littleFont->popStyle(); + } + ypos += YOFFSET_TEXT_PARA; + + if (selUnit->typeNum!=EXPLORER) + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0:").arg(Toolkit::getStringTable()->getString("[levels]")).c_str()); + ypos += YOFFSET_TEXT_PARA; + + const int rowX = globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4; + + if (selUnit->performance[WALK]) + drawAbilityRow(rowX, ypos, "[Walk]", 1 + selUnit->level[WALK], selUnit->performance[WALK]); + ypos += YOFFSET_TEXT_LINE; + + // SWIM is stored 1-based (0 = can't swim); pass raw, not 1+. + if (selUnit->performance[SWIM]) + drawAbilityRow(rowX, ypos, "[Swim]", selUnit->level[SWIM], selUnit->performance[SWIM]); + ypos += YOFFSET_TEXT_LINE; + + if (selUnit->performance[BUILD]) + drawAbilityRow(rowX, ypos, "[Build]", 1 + selUnit->level[BUILD], selUnit->performance[BUILD]); + ypos += YOFFSET_TEXT_LINE; + + if (selUnit->performance[HARVEST]) + drawAbilityRow(rowX, ypos, "[Harvest]", 1 + selUnit->level[HARVEST], selUnit->performance[HARVEST]); + ypos += YOFFSET_TEXT_LINE; + + if (selUnit->performance[ATTACK_SPEED]) + drawAbilityRow(rowX, ypos, "[At. speed]", 1 + selUnit->level[ATTACK_SPEED], selUnit->performance[ATTACK_SPEED]); + ypos += YOFFSET_TEXT_LINE; + + if (selUnit->performance[ATTACK_STRENGTH]) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1+%2) : %3+%4").arg(Toolkit::getStringTable()->getString("[At. strength]")).arg(1+selUnit->level[ATTACK_STRENGTH]).arg(selUnit->experienceLevel).arg(selUnit->performance[ATTACK_STRENGTH]).arg(selUnit->experienceLevel).c_str()); + + ypos += YOFFSET_TEXT_PARA + 2; + } + + if (selUnit->performance[MAGIC_ATTACK_AIR]) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1+%2) : %3+%4").arg(Toolkit::getStringTable()->getString("[Magic At. Air]")).arg(1+selUnit->level[MAGIC_ATTACK_AIR]).arg(selUnit->experienceLevel).arg(selUnit->performance[MAGIC_ATTACK_AIR]).arg(selUnit->experienceLevel).c_str()); + + ypos += YOFFSET_TEXT_PARA + 2; + } + + if (selUnit->performance[MAGIC_ATTACK_GROUND]) + { + globalContainer->gfx->drawString(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET+4, ypos, globalContainer->littleFont, FormatableString("%0 (%1+%2) : %3+%4").arg(Toolkit::getStringTable()->getString("[Magic At. Ground]")).arg(1+selUnit->level[MAGIC_ATTACK_GROUND]).arg(selUnit->experienceLevel).arg(selUnit->performance[MAGIC_ATTACK_GROUND]).arg(selUnit->experienceLevel).c_str()); + + ypos += YOFFSET_TEXT_PARA + 2; + } + + if (selUnit->performance[ATTACK_STRENGTH] || selUnit->performance[MAGIC_ATTACK_AIR] || selUnit->performance[MAGIC_ATTACK_GROUND]) + drawXPProgressBar(globalContainer->gfx->getW()-RIGHT_MENU_RIGHT_OFFSET, ypos, selUnit->experience, selUnit->getNextLevelThreshold()); +} diff --git a/src/GameGUIGhostBuildingManager.cpp b/src/gui/GameGUIGhostBuildingManager.cpp similarity index 66% rename from src/GameGUIGhostBuildingManager.cpp rename to src/gui/GameGUIGhostBuildingManager.cpp index 2c2fcd32a..a39f09794 100644 --- a/src/GameGUIGhostBuildingManager.cpp +++ b/src/gui/GameGUIGhostBuildingManager.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "GameGUIGhostBuildingManager.h" @@ -31,7 +16,7 @@ GameGUIGhostBuildingManager::GameGUIGhostBuildingManager(Game& game) void GameGUIGhostBuildingManager::addBuilding(const std::string& type, int x, int y) { - buildings.push_back(boost::make_tuple(type, x, y)); + buildings.push_back(std::make_tuple(type, x, y)); } @@ -46,10 +31,10 @@ bool GameGUIGhostBuildingManager::isGhostBuilding(int x, int y, int w, int h) int ly = (y + py + game.map.getH()) % game.map.getH(); for(unsigned i=0; i(); - int by = buildings[i].get<2>(); + int bx = std::get<1>(buildings[i]); + int by = std::get<2>(buildings[i]); - std::string building = buildings[i].get<0>(); + std::string building = std::get<0>(buildings[i]); int typeNum = globalContainer->buildingsTypes.getTypeNum(building, 0, true); if(typeNum == -1) typeNum = globalContainer->buildingsTypes.getTypeNum(building, 0, false); @@ -79,7 +64,7 @@ void GameGUIGhostBuildingManager::removeBuilding(int x, int y) { for(unsigned i=0; i() == x && buildings[i].get<2>() == y) + if(std::get<1>(buildings[i]) == x && std::get<2>(buildings[i]) == y) { buildings.erase(buildings.begin() + i); } @@ -96,9 +81,9 @@ void GameGUIGhostBuildingManager::drawAll(int viewportX, int viewportY, int loca { for(unsigned i=0; i(); - int px = buildings[i].get<1>(); - int py = buildings[i].get<2>(); + std::string building = std::get<0>(buildings[i]); + int px = std::get<1>(buildings[i]); + int py = std::get<2>(buildings[i]); int typeNum = globalContainer->buildingsTypes.getTypeNum(building, 0, true); if(typeNum == -1) diff --git a/src/GameGUIGhostBuildingManager.h b/src/gui/GameGUIGhostBuildingManager.h similarity index 50% rename from src/GameGUIGhostBuildingManager.h rename to src/gui/GameGUIGhostBuildingManager.h index c06a5b22f..cf581b44f 100644 --- a/src/GameGUIGhostBuildingManager.h +++ b/src/gui/GameGUIGhostBuildingManager.h @@ -1,26 +1,10 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GameGUIGhostBuildingManager_h -#define GameGUIGhostBuildingManager_h +#pragma once #include -#include +#include #include class Game; @@ -48,7 +32,6 @@ class GameGUIGhostBuildingManager void drawAll(int viewportX, int viewportY, int localTeamNo); private: Game& game; - std::vector > buildings; + std::vector > buildings; }; -#endif diff --git a/src/gui/GameGUIInput.cpp b/src/gui/GameGUIInput.cpp new file mode 100644 index 000000000..dad99e3d4 --- /dev/null +++ b/src/gui/GameGUIInput.cpp @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUIKeyActions.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "SoundMixer.h" +#include "Unit.h" +#include "VoiceRecorder.h" + +using std::shared_ptr; +using std::static_pointer_cast; + +namespace { + +struct SlashCommand +{ + std::string name; + std::string body; +}; + +// Parse a chat-input string into a slash-command name and message body. +// "/cmd body words..." → name="cmd", body="body words..." +// "/cmd" → name="cmd", body="" +// "" or non-'/' first char → std::nullopt +// The body is the substring after the first space; if the message is just +// "/" with no space, body is empty and the caller's empty-message guard +// suppresses sending an order. Pivots on a single find(' ') — no past-end +// reads even when the user types "/a" and hits Enter. +std::optional parseSlashCommand(const std::string& message) +{ + if (message.empty() || message[0] != '/') + return std::nullopt; + const std::string::size_type sp = message.find(' '); + if (sp == std::string::npos) + return SlashCommand{message.substr(1), std::string()}; + return SlashCommand{message.substr(1, sp - 1), message.substr(sp + 1)}; +} + +} // namespace + +bool GameGUI::processScrollableWidget(SDL_Event *event) +{ + scrollableText->translateAndProcessEvent(event); + return true; +} + +void GameGUI::processEvent(SDL_Event *event) +{ + // handle typing + if (typingInputScreen) + { + if ((event->type==SDL_KEYDOWN) && (event->key.keysym.sym == SDLK_ESCAPE)) + { + typingInputScreenInc=-TYPING_INPUT_BASE_INC; + typingInputScreen->endValue=1; + } + + typingInputScreen->translateAndProcessEvent(event); + + if (typingInputScreen->endValue==0) + { + //Interpret message + std::string message = typingInputScreen->getText(); + Uint32 nchatMask = chatMask; + if (auto cmd = parseSlashCommand(message)) + { + message = cmd->body; + if (cmd->name == "a") + { + nchatMask = localTeam->allies; + } + else + { + for (int i = 0; i < game.gameHeader.getNumberOfPlayers(); ++i) + { + if (cmd->name == game.gameHeader.getBasePlayer(i).name) + { + nchatMask = game.gameHeader.getBasePlayer(i).teamNumberMask | localTeam->me; + break; + } + } + } + } + + if (!message.empty()) + { + orderQueue.push_back(shared_ptr(new MessageOrder(nchatMask, MessageOrder::NORMAL_MESSAGE_TYPE, message.c_str()))); + typingInputScreen->setText(""); + } + typingInputScreenInc=-TYPING_INPUT_BASE_INC; + typingInputScreen->endValue=1; + return; + } + } + + // the dump (debug) keys are always handled + if (event->type == SDL_KEYDOWN) + handleKeyDump(event->key); + + + if (event->type==SDL_MOUSEBUTTONUP) + { + int button=event->button.button; + if (button==SDL_BUTTON_MIDDLE) + { + panPushed=false; + } + } + + + if (event->type == SDL_MOUSEBUTTONDOWN) + { + int butx = event->button.x; + int buty = event->button.y; + + int leftEdge = globalContainer->gfx->getW() - RIGHT_MENU_WIDTH - IGM_ICON_HEIGHT/2; + int rightEdge = globalContainer->gfx->getW() - RIGHT_MENU_WIDTH + IGM_ICON_HEIGHT/2; + int menu = -1; + + if (event->button.button == SDL_BUTTON_LEFT + && (butx > leftEdge) + && (butx < rightEdge)) + { + if (buty < IGM_MAIN_MENU_ICON_Y + IGM_ICON_HEIGHT) + { + menu = IGM_MAIN; + } + if (!(hiddenGUIElements & HIDABLE_ALLIANCE) + && (buty > IGM_ALLIANCE_ICON_Y) + && (buty < IGM_ALLIANCE_ICON_Y + IGM_ICON_HEIGHT)) + { + menu = IGM_ALLIANCE; + } + if ((buty > IGM_OBJECTIVES_ICON_Y) + && (buty < IGM_OBJECTIVES_ICON_Y + IGM_ICON_HEIGHT)) + { + menu = IGM_OBJECTIVES; + } + + if (menu != -1) + { + if (inGameMenu != IGM_NONE) + { + delete gameMenuScreen; + gameMenuScreen = NULL; + } + if (inGameMenu == menu) + inGameMenu = IGM_NONE; + else + inGameMenu = static_cast(menu); + + switch (menu) + { + case IGM_MAIN: + gameMenuScreen = new InGameMainScreen(globalContainer->replaying); + break; + case IGM_ALLIANCE: + gameMenuScreen = new InGameAllianceScreen(this); + break; + case IGM_OBJECTIVES: + gameMenuScreen = new InGameObjectivesScreen(this, false); + break; + default: + assert(false); + } + } + } + } + + + // if there is a menu he get events first + if (inGameMenu) + { + notmenu=true; + processGameMenu(event); + } + else + { + notmenu=false; + if (scrollableText) + { + processScrollableWidget(event); + } + if (event->type==SDL_KEYDOWN) + { + handleKey(event->key.keysym, true); + } + else if (event->type==SDL_KEYUP) + { + handleKey(event->key.keysym, false); + } + else if (event->type==SDL_MOUSEBUTTONDOWN) + { + int button=event->button.button; + //int state=event->button.state; + + if (button==SDL_BUTTON_RIGHT) + { + handleRightClick(); + } + else if (button==SDL_BUTTON_LEFT) + { + if (event->button.x>globalContainer->gfx->getW()-RIGHT_MENU_WIDTH) + handleMenuClick(event->button.x-globalContainer->gfx->getW()+RIGHT_MENU_WIDTH, event->button.y, event->button.button); + else if (globalContainer->replaying && event->button.y >= REPLAY_BAR_Y) + handleReplayProgressBarClick(event->button.x, event->button.y, event->button.button); + else + handleMapClick(event->button.x, event->button.y, event->button.button); + } + else if (button==SDL_BUTTON_MIDDLE) + { + if ((selectionMode==BUILDING_SELECTION) && (globalContainer->gfx->getW()-event->button.xverbose=(selBuild->verbose+1)%5; +// printf("building gid=(%d)\n", selBuild->gid); +// if (selBuild->verbose==0) +// printf(" verbose off\n"); +// else if (selBuild->verbose==1 || selBuild->verbose==2) +// printf(" verbose global [%d]\n", selBuild->verbose&1); +// else if (selBuild->verbose==3 || selBuild->verbose==4) +// printf(" verbose local [%d]\n", selBuild->verbose&1); +// else +// assert(false); +// printf(" pos=(%d, %d)\n", selBuild->posX, selBuild->posY); +// printf(" dirtyLocalGradient=[%d, %d]\n", selBuild->dirtyLocalGradient[0], selBuild->dirtyLocalGradient[1]); +// printf(" globalGradient=[%p, %p]\n", selBuild->globalGradient[0], selBuild->globalGradient[1]); +// printf(" locked=[%d, %d]\n", selBuild->locked[0], selBuild->locked[1]); + + } + else + { + // Enable panning + panPushed=true; + panMouseX=event->button.x; + panMouseY=event->button.y; + panViewX=viewportX; + panViewY=viewportY; + } + } + else if (button==4) + { + scrollWheelChanges += 1; + + } + else if (button==5) + { + scrollWheelChanges -= 1; + } + } + else if (event->type==SDL_MOUSEBUTTONUP) + { + int button=event->button.button; + if ((button==SDL_BUTTON_LEFT) && (event->button.x < globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)) + { + if ((selectionMode==BUILDING_SELECTION) && selectionPushed && selection.building->type->isVirtual) + { + // update flag + moveFlag(event->button.x, event->button.y, true); + } + // We send the order + else if (selectionMode==BRUSH_SELECTION || selectionMode==TOOL_SELECTION) + { + toolManager.handleMouseUp(event->button.x, event->button.y, localTeamNo, viewportX, viewportY); + } + } + miniMapPushed=false; + selectionPushed=false; + panPushed=false; + // showUnitWorkingToBuilding=false; + } + else if (event->type==SDL_MOUSEWHEEL) + { + int factor = event->wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1; + scrollWheelChanges += event->wheel.y * factor; + } + } + + if (event->type==SDL_MOUSEMOTION) + { + handleMouseMotion(event->motion.x, event->motion.y, event->motion.state); + } + else if (event->type==SDL_WINDOWEVENT) + { + handleActivation(event->window.data1, event->window.data2); + } + else if (event->type==SDL_QUIT) + { + exitGlobCompletely=true; + orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); + flushOutgoingAndExit=true; + } + else if (event->type==SDL_WINDOWEVENT_RESIZED) + { + // FIXME: window resize is broken + /*int newW=event->window.data1; + int newH=event->window.data2; + newW&=(~(0x1F)); + newH&=(~(0x1F)); + if (newW<640) + newW=640; + if (newH<480) + newH=480; + printf("New size : %dx%d\n", newW, newH); + globalContainer->gfx->setRes(newW, newH);*/ + } +} + +void GameGUI::handleKeyDump(SDL_KeyboardEvent key) +{ + if (key.keysym.sym == SDLK_PRINTSCREEN) + { + if ((key.keysym.mod & KMOD_SHIFT) != 0) + { + OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend("glob2.dump.txt")); + if (stream->isEndOfStream()) + { + std::cerr << "Can't dump full game memory to file glob2.dump.txt" << std::endl; + } + else + { + std::cerr << "Dump full game memory" << std::endl; + save(stream, "glob2.dump.txt"); + } + delete stream; + } + else + { + globalContainer->gfx->printScreen("screenshot.bmp"); + } + } +} + +void GameGUI::handleActivation(Uint8 state, Uint8 gain) +{ + if (gain==0) + { + viewportSpeedX=viewportSpeedY=0; + } +} + +void GameGUI::handleRightClick(void) +{ + // We cycle between views: + if (selectionMode==NO_SELECTION) + { + nextDisplayMode(); + } + // We deselect all, we want no tools activated: + else + { + clearSelection(); + } +} + +void GameGUI::nextDisplayMode(void) +{ + if (globalContainer->replaying) + { + replayDisplayMode=ReplayDisplayMode((replayDisplayMode + 1) % RDM_NB_VIEWS); + return; + } + + int t=0; + do + { + displayMode=DisplayMode((displayMode + 1) % NB_VIEWS); + if ((t++)==4) + { + displayMode=NB_VIEWS; + break; + } + } while ((1<<((int)displayMode)) & hiddenGUIElements); +} + +void GameGUI::repairAndUpgradeBuilding(Building *building, bool repair, bool upgrade) +{ + BuildingType *buildingType = building->type; + + // building site can't be repaired nor upgraded + if (buildingType->isBuildingSite) + return; + // we can upgrade or repair only building from our team + if (building->owner->teamNumber != localTeamNo) + return; + int typeNum = building->typeNum + 1; //determines type of updated building + int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum); + int repairUnitWorking = defaultAssign.getDefaultAssignedUnits(building->typeNum - 1); + int unitWorkingFuture = defaultAssign.getDefaultAssignedUnits(typeNum+1); + if ((building->hp < buildingType->hpMax) && repair) + { + // repair + if ((building->type->regenerationSpeed == 0) && + (building->isHardSpaceForBuildingSite(Building::REPAIR)) && + (localTeam->maxBuildLevel() >= buildingType->level)) + orderQueue.push_back(shared_ptr(new OrderConstruction(building->gid, repairUnitWorking, displayedMaxUnitWorking(*building)))); + } + else if (upgrade) + { + // upgrade + if ((buildingType->nextLevel != -1) && + (building->isHardSpaceForBuildingSite(Building::UPGRADE)) && + (localTeam->maxBuildLevel() > buildingType->level)) + orderQueue.push_back(shared_ptr(new OrderConstruction(building->gid, unitWorking, unitWorkingFuture))); + } +} diff --git a/src/gui/GameGUIInputKey.cpp b/src/gui/GameGUIInputKey.cpp new file mode 100644 index 000000000..00e3039a7 --- /dev/null +++ b/src/gui/GameGUIInputKey.cpp @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUIKeyActions.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "SoundMixer.h" +#include "Unit.h" +#include "VoiceRecorder.h" + +using std::shared_ptr; +using std::static_pointer_cast; + +void GameGUI::handleKeySwitchToAreaBrush(int figure) +{ + if (selectionMode != BRUSH_SELECTION) + clearSelection(); + brush.setFigure(figure); + if (brush.getType() == BrushTool::MODE_NONE) + { + brush.setType(BrushTool::MODE_ADD); + } + displayMode = FLAG_VIEW; + setSelection(BRUSH_SELECTION); + toolManager.activateZoneTool(); +} + +void GameGUI::handleKeySelectConstruct(const char *buildingName) +{ + clearSelection(); + if (isBuildingEnabled(std::string(buildingName))) + { + displayMode = CONSTRUCTION_VIEW; + setSelection(TOOL_SELECTION, (void *)buildingName); + } +} + +void GameGUI::handleKeySelectPlaceFlag(const char *flagName) +{ + clearSelection(); + if (isFlagEnabled(std::string(flagName))) + { + displayMode = FLAG_VIEW; + setSelection(TOOL_SELECTION, (void *)flagName); + } +} + +void GameGUI::handleKeySelectPlaceArea(GameGUIToolManager::ZoneType zone) +{ + if (selectionMode != BRUSH_SELECTION) + clearSelection(); + if (brush.getType() == BrushTool::MODE_NONE) + { + brush.setType(BrushTool::MODE_ADD); + } + displayMode = FLAG_VIEW; + setSelection(BRUSH_SELECTION); + toolManager.activateZoneTool(zone); +} + +void GameGUI::handleKey(SDL_Keysym key, bool pressed) +{ + if (typingInputScreen == NULL) + { + if(key.sym == SDLK_SPACE && pressed && swallowSpaceKey) + { + setIsSpaceSet(true); + } + else + { + Uint32 action_t = keyboardManager.getAction(KeyPress(key, pressed)); + switch(action_t) + { + case GameGUIKeyActions::DoNothing: + { + } + break; + case GameGUIKeyActions::ShowMainMenu: + { + if (inGameMenu==IGM_NONE) + { + gameMenuScreen=new InGameMainScreen(globalContainer->replaying); + inGameMenu=IGM_MAIN; + } + } + break; + case GameGUIKeyActions::UpgradeBuilding: + { + if (selectionMode==BUILDING_SELECTION) + { + Building* selBuild = selection.building; + int typeNum = selBuild->typeNum; //determines type of updated building + int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum - 1); + if (selBuild->constructionResultState == Building::UPGRADE) + orderQueue.push_back(shared_ptr(new OrderCancelConstruction(selBuild->gid, unitWorking))); + else if ((selBuild->constructionResultState==Building::NO_CONSTRUCTION) && (selBuild->buildingState==Building::ALIVE)) + repairAndUpgradeBuilding(selBuild, false, true); + } + } + break; + case GameGUIKeyActions::IncreaseUnitsWorking: + { + if (selectionMode==BUILDING_SELECTION) + { + Building* selBuild=selection.building; + const int current = displayedMaxUnitWorking(*selBuild); + if ((selBuild->owner->teamNumber==localTeamNo) && (selBuild->type->maxUnitWorking) && (currentgid).pendingMaxUnitWorking = nbReq; + orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, nbReq))); + defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, nbReq); + } + } + } + break; + case GameGUIKeyActions::DecreaseUnitsWorking: + { + if (selectionMode==BUILDING_SELECTION) + { + Building* selBuild=selection.building; + const int current = displayedMaxUnitWorking(*selBuild); + if ((selBuild->owner->teamNumber==localTeamNo) && (selBuild->type->maxUnitWorking) && (current>0)) + { + int nbReq=std::max(0, current-1); + pendingFor(selBuild->gid).pendingMaxUnitWorking = nbReq; + orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, nbReq))); + defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, nbReq); + } + } + } + break; + case GameGUIKeyActions::OpenChatBox: + { + typingInputScreen=new InGameTextInput(globalContainer->gfx); + typingInputScreenInc=TYPING_INPUT_BASE_INC; + typingInputScreenPos=0; + } + break; + case GameGUIKeyActions::IterateSelection: + { + iterateSelection(); + } + break; + case GameGUIKeyActions::GoToEvent: + { + eventGoTypeIterator = eventGoType; + int evX = eventGoPosX; + int evY = eventGoPosY; + + int oldViewportX = viewportX; + int oldViewportY = viewportY; + + int sw = globalContainer->gfx->getW(); + int sh = globalContainer->gfx->getH(); + viewportX = evX-((sw-RIGHT_MENU_WIDTH)>>6); + viewportY = evY-(sh>>6); + + moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); + } + break; + case GameGUIKeyActions::GoToHome: + { + int evX = localTeam->startPosX; + int evY = localTeam->startPosY; + + int oldViewportX = viewportX; + int oldViewportY = viewportY; + + int sw = globalContainer->gfx->getW(); + int sh = globalContainer->gfx->getH(); + viewportX = evX-((sw-RIGHT_MENU_WIDTH)>>6); + viewportY = evY-(sh>>6); + + moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); + } + break; + case GameGUIKeyActions::PauseGame: + orderQueue.push_back(shared_ptr(new PauseGameOrder(!gamePaused))); + break; + case GameGUIKeyActions::HardPause: + hardPause=!hardPause; + break; + case GameGUIKeyActions::ToggleDrawUnitPaths: + drawPathLines=!drawPathLines; + break; + case GameGUIKeyActions::DestroyBuilding: + { + if (selectionMode==BUILDING_SELECTION) + { + Building* selBuild=selection.building; + if (selBuild->owner->teamNumber==localTeamNo) + { + if (selBuild->buildingState==Building::WAITING_FOR_DESTRUCTION) + { + orderQueue.push_back(shared_ptr(new OrderCancelDelete(selBuild->gid))); + } + else if (selBuild->buildingState==Building::ALIVE) + { + orderQueue.push_back(shared_ptr(new OrderDelete(selBuild->gid))); + } + } + } + } + break; + case GameGUIKeyActions::RepairBuilding: + { + if (selectionMode==BUILDING_SELECTION) + { + Building* selBuild = selection.building; + int typeNum = selBuild->typeNum; //determines type of updated building + int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum); + if (selBuild->constructionResultState == Building::REPAIR) + orderQueue.push_back(shared_ptr(new OrderCancelConstruction(selBuild->gid, unitWorking))); + else if ((selBuild->constructionResultState==Building::NO_CONSTRUCTION) && (selBuild->buildingState==Building::ALIVE)) + repairAndUpgradeBuilding(selBuild, true, false); + } + } + break; + case GameGUIKeyActions::ToggleDrawInformation: + drawHealthFoodBar=!drawHealthFoodBar; + break; + case GameGUIKeyActions::ToggleDrawAccessibilityAids: + drawAccessibilityAids = !drawAccessibilityAids; + break; + case GameGUIKeyActions::MarkMap: + putMark=true; + globalContainer->gfx->cursorManager.setNextType(CursorManager::CURSOR_MARK); + break; + case GameGUIKeyActions::ToggleRecordingVoice: + if (globalContainer->voiceRecorder->recordingNow) + globalContainer->voiceRecorder->stopRecording(); + else + globalContainer->voiceRecorder->startRecording(); + break; + case GameGUIKeyActions::ViewHistory: + { + if ( ! scrollableText) + scrollableText = messageManager.createScrollableHistoryScreen(); + else + { + delete scrollableText; + scrollableText=NULL; + } + } + break; + case GameGUIKeyActions::SelectConstructInn: + handleKeySelectConstruct("inn"); + break; + case GameGUIKeyActions::SelectConstructSwarm: + handleKeySelectConstruct("swarm"); + break; + case GameGUIKeyActions::SelectConstructHospital: + handleKeySelectConstruct("hospital"); + break; + case GameGUIKeyActions::SelectConstructRacetrack: + handleKeySelectConstruct("racetrack"); + break; + case GameGUIKeyActions::SelectConstructSwimmingPool: + handleKeySelectConstruct("swimmingpool"); + break; + case GameGUIKeyActions::SelectConstructBarracks: + handleKeySelectConstruct("barracks"); + break; + case GameGUIKeyActions::SelectConstructSchool: + handleKeySelectConstruct("school"); + break; + case GameGUIKeyActions::SelectConstructDefenceTower: + handleKeySelectConstruct("defencetower"); + break; + case GameGUIKeyActions::SelectConstructStoneWall: + handleKeySelectConstruct("stonewall"); + break; + case GameGUIKeyActions::SelectConstructMarket: + handleKeySelectConstruct("market"); + break; + case GameGUIKeyActions::SelectPlaceExplorationFlag: + handleKeySelectPlaceFlag("explorationflag"); + break; + case GameGUIKeyActions::SelectPlaceWarFlag: + handleKeySelectPlaceFlag("warflag"); + break; + case GameGUIKeyActions::SelectPlaceClearingFlag: + handleKeySelectPlaceFlag("clearingflag"); + break; + case GameGUIKeyActions::SelectPlaceForbiddenArea: + handleKeySelectPlaceArea(GameGUIToolManager::Forbidden); + break; + case GameGUIKeyActions::SelectPlaceGuardArea: + handleKeySelectPlaceArea(GameGUIToolManager::Guard); + break; + case GameGUIKeyActions::SelectPlaceClearingArea: + handleKeySelectPlaceArea(GameGUIToolManager::Clearing); + break; + case GameGUIKeyActions::SwitchToAddingAreas: + { + if(selectionMode != BRUSH_SELECTION) + clearSelection(); + brush.setType(BrushTool::MODE_ADD); + displayMode = FLAG_VIEW; + setSelection(BRUSH_SELECTION); + toolManager.activateZoneTool(); + } + break; + case GameGUIKeyActions::SwitchToRemovingAreas: + { + if(selectionMode != BRUSH_SELECTION) + clearSelection(); + brush.setType(BrushTool::MODE_DEL); + displayMode = FLAG_VIEW; + setSelection(BRUSH_SELECTION); + toolManager.activateZoneTool(); + } + break; + case GameGUIKeyActions::SwitchToAreaBrush1: + handleKeySwitchToAreaBrush(0); + break; + case GameGUIKeyActions::SwitchToAreaBrush2: + handleKeySwitchToAreaBrush(1); + break; + case GameGUIKeyActions::SwitchToAreaBrush3: + handleKeySwitchToAreaBrush(2); + break; + case GameGUIKeyActions::SwitchToAreaBrush4: + handleKeySwitchToAreaBrush(3); + break; + case GameGUIKeyActions::SwitchToAreaBrush5: + handleKeySwitchToAreaBrush(4); + break; + case GameGUIKeyActions::SwitchToAreaBrush6: + handleKeySwitchToAreaBrush(5); + break; + case GameGUIKeyActions::SwitchToAreaBrush7: + handleKeySwitchToAreaBrush(6); + break; + case GameGUIKeyActions::SwitchToAreaBrush8: + handleKeySwitchToAreaBrush(7); + break; + } + } + } +} + +void GameGUI::handleKeyAlways(void) +{ + SDL_PumpEvents(); + const Uint8 *keystate = SDL_GetKeyboardState(NULL); + if (notmenu == false) + { + SDL_Keymod modState = SDL_GetModState(); + int xMotion = 1; + int yMotion = 1; + /* We check that only Control is held to avoid accidentally + matching window manager bindings for switching windows + and/or desktops. */ + if (!(modState & (KMOD_ALT|KMOD_SHIFT))) + { + /* It violates good abstraction principles that I + have to do the calculations in the next two + lines. There should be methods that abstract + these computations. */ + if ((modState & KMOD_CTRL)) + { + /* We move by half screens if Control is held while + the arrow keys are held. So we shift by 6 + instead of 5. (If we shifted by 5, it would be + good to subtract 1 so that there would be a small + overlap between what is viewable both before and + after the motion.) */ + xMotion = ((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); + yMotion = ((globalContainer->gfx->getH())>>6); + } + else + { + /* We move the screen by one square at a time if CTRL key + is not being help */ + xMotion = 1; + yMotion = 1; + } + } + else if (modState) + { + /* Probably some keys held down as part of window + manager operations. */ + xMotion = 0; + yMotion = 0; + } + + if (keystate[SDL_SCANCODE_UP]) + viewportY -= yMotion; + if (keystate[SDL_SCANCODE_KP_8]) + viewportY -= yMotion; + if (keystate[SDL_SCANCODE_DOWN]) + viewportY += yMotion; + if (keystate[SDL_SCANCODE_KP_2]) + viewportY += yMotion; + if ((keystate[SDL_SCANCODE_LEFT]) && (typingInputScreen == NULL)) // we haave a test in handleKeyAlways, that's not very clean, but as every key check based on key states and not key events are here, it is much simpler and thus easier to understand and thus cleaner ;-) + viewportX -= xMotion; + if (keystate[SDL_SCANCODE_KP_4]) + viewportX -= xMotion; + if ((keystate[SDL_SCANCODE_RIGHT]) && (typingInputScreen == NULL)) // we haave a test in handleKeyAlways, that's not very clean, but as every key check based on key states and not key events are here, it is much simpler and thus easier to understand and thus cleaner ;-) + viewportX += xMotion; + if (keystate[SDL_SCANCODE_KP_6]) + viewportX += xMotion; + if (keystate[SDL_SCANCODE_KP_7]) + { + viewportX -= xMotion; + viewportY -= yMotion; + } + if (keystate[SDL_SCANCODE_KP_9]) + { + viewportX += xMotion; + viewportY -= yMotion; + } + if (keystate[SDL_SCANCODE_KP_1]) + { + viewportX -= xMotion; + viewportY += yMotion; + } + if (keystate[SDL_SCANCODE_KP_3]) + { + viewportX += xMotion; + viewportY += yMotion; + } + } +} diff --git a/src/gui/GameGUIInputMenu.cpp b/src/gui/GameGUIInputMenu.cpp new file mode 100644 index 000000000..172b526b8 --- /dev/null +++ b/src/gui/GameGUIInputMenu.cpp @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUIKeyActions.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "SoundMixer.h" +#include "Unit.h" +#include "VoiceRecorder.h" + +using std::shared_ptr; +using std::static_pointer_cast; + +bool GameGUI::processGameMenu(SDL_Event *event) +{ + gameMenuScreen->translateAndProcessEvent(event); + switch (inGameMenu) + { + case IGM_MAIN: + { + switch (gameMenuScreen->endValue) + { + case InGameMainScreen::LOAD_GAME: + { + delete gameMenuScreen; + inGameMenu=IGM_LOAD; + if (globalContainer->replaying) + gameMenuScreen = new LoadSaveScreen("replays", "replay", true, std::string(Toolkit::getStringTable()->getString("[load replay]")), defualtGameSaveName.c_str(), glob2FilenameToName, glob2NameToFilename); + else + gameMenuScreen = new LoadSaveScreen("games", "game", true, false, defualtGameSaveName.c_str(), glob2FilenameToName, glob2NameToFilename); + return true; + } + break; + case InGameMainScreen::SAVE_GAME: + { + delete gameMenuScreen; + inGameMenu=IGM_SAVE; + gameMenuScreen = new LoadSaveScreen("games", "game", false, false, defualtGameSaveName.c_str(), glob2FilenameToName, glob2NameToFilename); + return true; + } + break; + case InGameMainScreen::OPTIONS: + { + delete gameMenuScreen; + inGameMenu=IGM_OPTION; + gameMenuScreen = new InGameOptionScreen(this); + return true; + } + break; + case InGameMainScreen::RETURN_GAME: + { + delete gameMenuScreen; + inGameMenu=IGM_NONE; + gameMenuScreen=NULL; + return true; + } + break; + case InGameMainScreen::QUIT_GAME: + { + delete gameMenuScreen; + inGameMenu=IGM_NONE; + gameMenuScreen=NULL; + orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); + flushOutgoingAndExit=true; + return true; + } + break; + default: + return false; + } + } + + case IGM_ALLIANCE: + { + switch (gameMenuScreen->endValue) + { + case InGameAllianceScreen::OK : + { + Uint32 playerMask[5]; + Uint32 teamMask[5]; + playerMask[0]=((InGameAllianceScreen *)gameMenuScreen)->getAlliedMask(); + playerMask[1]=((InGameAllianceScreen *)gameMenuScreen)->getEnemyMask(); + playerMask[2]=((InGameAllianceScreen *)gameMenuScreen)->getExchangeVisionMask(); + playerMask[3]=((InGameAllianceScreen *)gameMenuScreen)->getFoodVisionMask(); + playerMask[4]=((InGameAllianceScreen *)gameMenuScreen)->getOtherVisionMask(); + teamMask[0]=teamMask[1]=teamMask[2]=teamMask[3]=teamMask[4]=0; + + // mask are for players, we need to convert them to team. + for (int pi=0; piteamNumber; + for (int mi=0; mi<5; mi++) + { + if (playerMask[mi]&(1<playersMask==0) + teamMask[1]|=(1<(new SetAllianceOrder(localTeamNo, + teamMask[0], teamMask[1], teamMask[2], teamMask[3], teamMask[4]))); + chatMask=((InGameAllianceScreen *)gameMenuScreen)->getChatMask(); + inGameMenu=IGM_NONE; + delete gameMenuScreen; + gameMenuScreen=NULL; + } + return true; + + default: + return false; + } + } + + case IGM_OPTION: + { + if (gameMenuScreen->endValue == InGameOptionScreen::OK) + { + inGameMenu=IGM_NONE; + delete gameMenuScreen; + gameMenuScreen=NULL; + return true; + } + else + { + return false; + } + } + + case IGM_OBJECTIVES: + { + if (gameMenuScreen->endValue == InGameObjectivesScreen::OK) + { + inGameMenu=IGM_NONE; + delete gameMenuScreen; + gameMenuScreen=NULL; + return true; + } + else + { + return false; + } + } + + case IGM_LOAD: + case IGM_SAVE: + { + switch (gameMenuScreen->endValue) + { + case LoadSaveScreen::OK: + { + std::string locationName=((LoadSaveScreen *)gameMenuScreen)->getFileName(); + if (inGameMenu==IGM_LOAD) + { + toLoadGameFileName = locationName; + orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); + flushOutgoingAndExit=true; + } + else + { + defualtGameSaveName=((LoadSaveScreen *)gameMenuScreen)->getName(); + OutputStream *stream = new BinaryOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(locationName)); + if (stream->isEndOfStream()) + { + std::cerr << "GGU : Can't save map " << locationName << std::endl; + } + else + { + const std::string name = ((LoadSaveScreen *)gameMenuScreen)->getName(); + assert(name.size()); + save(stream, name); + } + delete stream; + } + } + + case LoadSaveScreen::CANCEL: + inGameMenu=IGM_NONE; + delete gameMenuScreen; + gameMenuScreen=NULL; + return true; + + default: + return false; + } + } + + case IGM_END_OF_GAME: + { + switch (gameMenuScreen->endValue) + { + case InGameEndOfGameScreen::QUIT: + orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); + flushOutgoingAndExit=true; + + case InGameEndOfGameScreen::CONTINUE: + inGameMenu=IGM_NONE; + delete gameMenuScreen; + gameMenuScreen=NULL; + return true; + + case InGameEndOfGameScreen::WATCH_AGAIN: + assert(globalContainer->replaying); + inGameMenu=IGM_NONE; + delete gameMenuScreen; + gameMenuScreen=NULL; + toLoadGameFileName = globalContainer->replayFileName; + orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); + flushOutgoingAndExit=true; + return true; + + default: + return false; + } + } + + default: + return false; + } +} diff --git a/src/gui/GameGUIInputMenuClick.cpp b/src/gui/GameGUIInputMenuClick.cpp new file mode 100644 index 000000000..bc133dd20 --- /dev/null +++ b/src/gui/GameGUIInputMenuClick.cpp @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUIKeyActions.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "SoundMixer.h" +#include "Unit.h" +#include "VoiceRecorder.h" + +using std::shared_ptr; +using std::static_pointer_cast; + +void GameGUI::handleMenuClick(int mx, int my, int button) +{ + // handle minimap + if (my<128 && mx > (RIGHT_MENU_OFFSET) && mx < RIGHT_MENU_WIDTH - RIGHT_MENU_OFFSET) + { + if (putMark) + { + int markx, marky; + minimapMouseToPos(globalContainer->gfx->getW() - RIGHT_MENU_WIDTH + mx, my, &markx, &marky, false); + orderQueue.push_back(shared_ptr(new MapMarkOrder(localTeamNo, markx, marky))); + globalContainer->gfx->cursorManager.setNextType(CursorManager::CURSOR_NORMAL); + putMark = false; + } + else + { + miniMapPushed=true; + int oldViewportX = viewportX; + int oldViewportY = viewportY; + minimapMouseToPos(globalContainer->gfx->getW() - RIGHT_MENU_WIDTH + mx, my, &viewportX, &viewportY, true); + moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); + } + } + // Check if one of the panel buttons has been clicked + else if (myreplaying) + { + int dec = (RIGHT_MENU_WIDTH-128)/2; + int dm=(mx-dec)/32; + if (!((1<replaying)) + { + int xNum=mx/(RIGHT_MENU_WIDTH/2); + int yNum=(my-YPOS_BASE_CONSTRUCTION)/46; + int id=yNum*2+xNum; + if (id<(int)buildingsChoiceName.size()) + if (buildingsChoiceState[id]) + setSelection(TOOL_SELECTION, (void *)buildingsChoiceName[id].c_str()); + } + else if ((displayMode==FLAG_VIEW && !globalContainer->replaying)) + { + int dec = (RIGHT_MENU_WIDTH - 128)/2; + my -= YPOS_BASE_FLAG; + int nmx = mx - dec; + if (my > YOFFSET_BRUSH) + { + // set the selection + setSelection(BRUSH_SELECTION); + // change the brush type (forbidden, guard, clear) if necessary + if (my < YOFFSET_BRUSH+40) + { + if (nmx < 44) + toolManager.activateZoneTool(GameGUIToolManager::Forbidden); + else if (nmx < 84) + toolManager.activateZoneTool(GameGUIToolManager::Guard); + else if(nmx < 124) + toolManager.activateZoneTool(GameGUIToolManager::Clearing); + } + // anyway, update the tool + brush.handleClick(mx-dec, my-YOFFSET_BRUSH-40); + toolManager.activateZoneTool(); + } + else + { + int xNum=mx / (RIGHT_MENU_WIDTH/3); + int yNum=my / 46; + int id=yNum*3+xNum; + if (id<(int)flagsChoiceName.size()) + if (flagsChoiceState[id]) + setSelection(TOOL_SELECTION, (void*)flagsChoiceName[id].c_str()); + } + } + else if ((displayMode==STAT_GRAPH_VIEW && !globalContainer->replaying) || (replayDisplayMode==RDM_STAT_GRAPH_VIEW && globalContainer->replaying)) + { + if(mx > 8 && mx < 24) + { + // In replays, this menu bar is 15 pixels lower than usual to show "Watching: player-name" + int inc; + + if (globalContainer->replaying) inc = 15; + else inc = 0; + + if(my > YPOS_BASE_STAT+140+inc+64 && my < YPOS_BASE_STAT+140+inc+80) + { + showDamagedMap=false; + showDefenseMap=false; + showFertilityMap=false; + showStarvingMap=!showStarvingMap; + overlay.compute(game, OverlayArea::Starving, localTeamNo); + } + + if(my > YPOS_BASE_STAT+140+inc+88 && my < YPOS_BASE_STAT+140+inc+104) + { + showDamagedMap=!showDamagedMap; + showStarvingMap=false; + showDefenseMap=false; + showFertilityMap=false; + overlay.compute(game, OverlayArea::Damage, localTeamNo); + } + + if(my > YPOS_BASE_STAT+140+inc+112 && my < YPOS_BASE_STAT+140+inc+128) + { + showDefenseMap=!showDefenseMap; + showStarvingMap=false; + showDamagedMap=false; + showFertilityMap=false; + overlay.compute(game, OverlayArea::Defence, localTeamNo); + } + + if(my > YPOS_BASE_STAT+140+inc+136 && my < YPOS_BASE_STAT+140+inc+152) + { + showFertilityMap=!showFertilityMap; + showDefenseMap=false; + showStarvingMap=false; + showDamagedMap=false; + overlay.compute(game, OverlayArea::Fertility, localTeamNo); + } + } + } + else if (replayDisplayMode==RDM_REPLAY_VIEW && globalContainer->replaying) + { + int x = REPLAY_PANEL_XOFFSET; + int y = REPLAY_PANEL_YOFFSET; + int inc = REPLAY_PANEL_SPACE_BETWEEN_OPTIONS; + + if (mx > x && mx < x+20 && my > y+1*inc && my < y+1*inc + 20) + { + // Disable/show fog of war + globalContainer->replayShowFog = !globalContainer->replayShowFog; + + if (globalContainer->replayShowFog) minimap.setMinimapMode( Minimap::ShowFOW ); + else minimap.setMinimapMode( Minimap::HideFOW ); + } + if (mx > x && mx < x+20 && my > y+2*inc && my < y+2*inc + 20) + { + // Disable/enable combined vision + if (globalContainer->replayVisibleTeams == 0xFFFFFFFF) + { + globalContainer->replayVisibleTeams = localTeam->me; + } + else + { + globalContainer->replayVisibleTeams = 0xFFFFFFFF; + } + } + if (mx > x && mx < x+20 && my > y+3*inc && my < y+3*inc + 20) + { + // Show/hide player's areas + globalContainer->replayShowAreas = !globalContainer->replayShowAreas; + } + if (mx > x && mx < x+20 && my > y+4*inc && my < y+4*inc + 20) + { + // Show/hide flags + globalContainer->replayShowFlags = !globalContainer->replayShowFlags; + } + + for (int i = 0; i < game.teamsCount(); i++) + { + if (mx > x && mx < x+20 && my > y+REPLAY_PANEL_PLAYERLIST_YOFFSET+(i+1)*inc && my < y+REPLAY_PANEL_PLAYERLIST_YOFFSET+(i+1)*inc + 20) + { + localTeamNo = i; + + // Update everything to match this team number + adjustLocalTeam(); + + // Update localPlayer to the first player of this team + for (int j=0; jteamNumber == localTeamNo) + { + localPlayer = j; + break; + } + } + + // Update the visible players unless all players are visible + if (globalContainer->replayVisibleTeams != 0xFFFFFFFF) + { + globalContainer->replayVisibleTeams = localTeam->me; + } + } + } + } +} diff --git a/src/gui/GameGUIInputMenuClickBuilding.cpp b/src/gui/GameGUIInputMenuClickBuilding.cpp new file mode 100644 index 000000000..a3e7892c0 --- /dev/null +++ b/src/gui/GameGUIInputMenuClickBuilding.cpp @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUIKeyActions.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "SoundMixer.h" +#include "Unit.h" +#include "VoiceRecorder.h" + +using std::shared_ptr; +using std::static_pointer_cast; + +// Interpret a click on a three-zone scrollbox widget. The widget is laid out +// as [<-arrow][===proportional drag track===][->arrow] inside a strip +// SCROLLBOX_BAR_WIDTH wide, with each arrow SCROLLBOX_ARROW_WIDTH wide. +// +// `lmx` is the click x-coordinate relative to the strip's left edge; the +// caller is responsible for having already y-band-gated and confirmed +// lmx is inside [0, SCROLLBOX_BAR_WIDTH). `current` is the value the +// widget currently shows, `max` its upper bound (inclusive; lower bound +// is 0). +// +// Returns the value the user just requested: +// - left arrow click → current - 1 (or nullopt if already at 0) +// - drag-track click → proportional value in [0, max) +// - right arrow click → current + 1 (or nullopt if already at max) +// nullopt means "no order should be issued" — the click was an arrow press +// against an already-clamped value. +static std::optional interpretScrollBoxClick(int lmx, int current, int max) +{ + if (lmx < SCROLLBOX_ARROW_WIDTH) + { + if (current > 0) return current - 1; + return std::nullopt; + } + if (lmx < SCROLLBOX_BAR_WIDTH - SCROLLBOX_ARROW_WIDTH) + { + const int track = SCROLLBOX_BAR_WIDTH - 2 * SCROLLBOX_ARROW_WIDTH; + return ((lmx - SCROLLBOX_ARROW_WIDTH) * max) / track; + } + if (current < max) return current + 1; + return std::nullopt; +} + +void GameGUI::handleMenuClickBuildingSelection(int mx, int my, int button) +{ + Building* selBuild=selection.building; + assert (selBuild); + if (selBuild->owner->teamNumber!=localTeamNo) + return; + int ypos = YPOS_BASE_BUILDING + YOFFSET_NAME + YOFFSET_ICON + YOFFSET_B_SEP; + BuildingType *buildingType = selBuild->type; + int lmx = mx - RIGHT_MENU_OFFSET; // local mx + + // working bar + if (selBuild->type->maxUnitWorking) + { + if (((selBuild->owner->allies)&(1<ypos+YOFFSET_TEXT_BAR + && mybuildingState==Building::ALIVE + && lmx < SCROLLBOX_BAR_WIDTH) + { + const int current = displayedMaxUnitWorking(*selBuild); + if (auto nbReq = interpretScrollBoxClick(lmx, current, MAX_UNIT_WORKING)) + { + pendingFor(selBuild->gid).pendingMaxUnitWorking = *nbReq; + orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, *nbReq))); + defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, *nbReq); + } + } + ypos += YOFFSET_BAR + YOFFSET_B_SEP; + } + + // priorities + if(selBuild->type->maxUnitWorking) + { + ypos += YOFFSET_B_SEP; + if (((selBuild->owner->allies)&(1<ypos+16 + && mybuildingState==Building::ALIVE) + { + int width = (128 - 8)/3; + + if(lmx>=0 && lmx<=12) + { + orderQueue.push_back(shared_ptr(new OrderChangePriority(selBuild->gid, -1))); + selBuild->priorityLocal = -1; + } + else if(lmx>=(width) && lmx<(width+12)) + { + orderQueue.push_back(shared_ptr(new OrderChangePriority(selBuild->gid, 0))); + selBuild->priorityLocal = 0; + } + else if(lmx>=(width*2) && lmx<=(width*2+12)) + { + orderQueue.push_back(shared_ptr(new OrderChangePriority(selBuild->gid, 1))); + selBuild->priorityLocal = 1; + } + } + ypos += YOFFSET_BAR+YOFFSET_B_SEP; + } + + // flag range bar + if (buildingType->defaultUnitStayRange) + { + if (((selBuild->owner->allies)&(1<ypos+YOFFSET_TEXT_BAR) + && (mytype->maxUnitStayRange)) + { + pendingFor(selBuild->gid).pendingUnitStayRange = *nbReq; + orderQueue.push_back(shared_ptr(new OrderModifyFlag(selBuild->gid, *nbReq))); + } + } + ypos += YOFFSET_BAR+YOFFSET_B_SEP; + } + + // flags specific options: + if (((selBuild->owner->allies)&(1<10 + && lmx<22) + { + + // cleared ressources for clearing flags: + if (buildingType->type == "clearingflag") + { + ypos+=YOFFSET_B_SEP+YOFFSET_TEXT_PARA; + for (int i=0; iypos && myclearingRessourcesLocal[i]=!selBuild->clearingRessourcesLocal[i]; + orderQueue.push_back(shared_ptr(new OrderModifyClearingFlag(selBuild->gid, selBuild->clearingRessourcesLocal))); + } + + ypos+=YOFFSET_TEXT_PARA; + } + } + + if (buildingType->type == "warflag") + { + ypos+=YOFFSET_B_SEP+YOFFSET_TEXT_PARA; + for (int i=0; i<4; i++) + { + if (my>ypos && myminLevelToFlagLocal=i; + orderQueue.push_back(shared_ptr(new OrderModifyMinLevelToFlag(selBuild->gid, selBuild->minLevelToFlagLocal))); + } + + ypos+=YOFFSET_TEXT_PARA; + } + + } + + if (buildingType->type == "explorationflag") + { + // we use minLevelToFlag as an int which says what magic effect at minimum an explorer + // must be able to do to be accepted at this flag + // 0 == any explorer + // 1 == must be able to attack ground + ypos+=YOFFSET_B_SEP+YOFFSET_TEXT_PARA; + for (int i=0; i<2; i++) + { + if (my>ypos && myminLevelToFlagLocal=i; + orderQueue.push_back(shared_ptr(new OrderModifyMinLevelToFlag(selBuild->gid, selBuild->minLevelToFlagLocal))); + } + + ypos+=YOFFSET_TEXT_PARA; + } + } + } + + if (buildingType->armor) + ypos+=YOFFSET_TEXT_LINE; + if (buildingType->maxUnitInside) + ypos += YOFFSET_INFOS; + if (buildingType->shootDamage) + ypos += YOFFSET_TOWER; + ypos += YOFFSET_B_SEP; + + //Exchannge building + //Exchanging as a feature is broken + /* + if (selBuild->type->canExchange && ((selBuild->owner->allies)&(1<startY) && (my92) && (lmx<104)) + { + if (selBuild->receiveRessourceMask & (1<receiveRessourceMaskLocal &= ~(1<receiveRessourceMaskLocal |= (1<sendRessourceMaskLocal &= ~(1<(new OrderModifyExchange(selBuild->gid, selBuild->receiveRessourceMaskLocal, selBuild->sendRessourceMaskLocal))); + } + + if ((lmx>110) && (lmx<122)) + { + if (selBuild->sendRessourceMask & (1<sendRessourceMaskLocal &= ~(1<receiveRessourceMaskLocal &= ~(1<sendRessourceMaskLocal |= (1<(new OrderModifyExchange(selBuild->gid, selBuild->receiveRessourceMaskLocal, selBuild->sendRessourceMaskLocal))); + } + } + } + */ + // ressources in + for (unsigned i=0; iressourcesTypes.size(); i++) + { + if (buildingType->maxRessource[i]) + { + ypos += 11; + } + } + if (buildingType->maxBullets) + { + ypos += 11; + } + ypos+=5; + + if (selBuild->type->unitProductionTime) + { + ypos+=15; + for (int i=0; iypos+(i*20))&&(myratioLocal[i], MAX_RATIO_RANGE)) + { + selBuild->ratioLocal[i] = *nbReq; + orderQueue.push_back(shared_ptr(new OrderModifySwarm(selBuild->gid, selBuild->ratioLocal))); + } + } + } + } + + if ((my>globalContainer->gfx->getH()-48) && (mygfx->getH()-32)) + { + if (selBuild->constructionResultState==Building::REPAIR) + { + int typeNum = selBuild->typeNum; //determines type of updated building + int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum); + orderQueue.push_back(shared_ptr(new OrderCancelConstruction(selBuild->gid, unitWorking))); + } + else if (selBuild->constructionResultState==Building::UPGRADE) + { + int typeNum = selBuild->typeNum; //determines type of updated building + int unitWorking = defaultAssign.getDefaultAssignedUnits(typeNum - 1); + orderQueue.push_back(shared_ptr(new OrderCancelConstruction(selBuild->gid, unitWorking))); + } + else if ((selBuild->constructionResultState==Building::NO_CONSTRUCTION) && (selBuild->buildingState==Building::ALIVE)) + { + repairAndUpgradeBuilding(selBuild, true, true); + } + } + + if ((my>globalContainer->gfx->getH()-24) && (mygfx->getH()-8)) + { + if (selBuild->buildingState==Building::WAITING_FOR_DESTRUCTION) + { + orderQueue.push_back(shared_ptr(new OrderCancelDelete(selBuild->gid))); + } + else if (selBuild->buildingState==Building::ALIVE) + { + orderQueue.push_back(shared_ptr(new OrderDelete(selBuild->gid))); + } + } +} diff --git a/src/gui/GameGUIInputMouse.cpp b/src/gui/GameGUIInputMouse.cpp new file mode 100644 index 000000000..c306d28c4 --- /dev/null +++ b/src/gui/GameGUIInputMouse.cpp @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUIKeyActions.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Order.h" +#include "Player.h" +#include "SoundMixer.h" +#include "Unit.h" +#include "VoiceRecorder.h" + +using std::shared_ptr; +using std::static_pointer_cast; + +void GameGUI::minimapMouseToPos(int mx, int my, int *cx, int *cy, bool forScreenViewport) +{ + minimap.convertToMap(mx, my, *cx, *cy); + + ///when for the screen viewport, center + if (forScreenViewport) + { + *cx-=((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); + *cy-=((globalContainer->gfx->getH())>>6); + } + +} + +void GameGUI::handleMouseMotion(int mx, int my, int button) +{ + const int scrollZoneWidth = 10; + game.mouseX=mouseX=mx; + game.mouseY=mouseY=my; + + int oldViewportX = viewportX; + int oldViewportY = viewportY; + + if (miniMapPushed) + { + minimapMouseToPos(mx, my, &viewportX, &viewportY, true); + } + else + { + if (mxglobalContainer->gfx->getW()-scrollZoneWidth) ) + viewportSpeedX=1; + else + viewportSpeedX=0; + + if (myglobalContainer->gfx->getH()-scrollZoneWidth) + viewportSpeedY=1; + else + viewportSpeedY=0; + } + + if (panPushed) + { + // handle paning + int dx = (mx-panMouseX)>>1; + int dy = (my-panMouseY)>>1; + viewportX = (panViewX+dx)&game.map.getMaskW(); + viewportY = (panViewY+dy)&game.map.getMaskH(); + } + + moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); + + dragStep(mx, my, button); +} + +void GameGUI::handleMapClick(int mx, int my, int button) +{ + if (selectionMode==TOOL_SELECTION) + { + toolManager.handleMouseDown(mx, my, localTeamNo, viewportX, viewportY); + + } + else if (selectionMode==BRUSH_SELECTION) + { + toolManager.handleMouseDown(mx, my, localTeamNo, viewportX, viewportY); + } + else if (putMark) + { + int markx, marky; + game.map.displayToMapCaseAligned(mx, my, &markx, &marky, viewportX, viewportY); + orderQueue.push_back(shared_ptr(new MapMarkOrder(localTeamNo, markx, marky))); + globalContainer->gfx->cursorManager.setNextType(CursorManager::CURSOR_NORMAL); + putMark = false; + } + else + { + int mapX, mapY; + game.map.displayToMapCaseAligned(mx, my, &mapX, &mapY, viewportX, viewportY); + selectionPushedPosX=mapX; + selectionPushedPosY=mapY; + // check for flag first + for (std::list::iterator virtualIt=localTeam->virtualBuildings.begin(); + virtualIt!=localTeam->virtualBuildings.end(); ++virtualIt) + { + Building *b=*virtualIt; + if ((displayedPosX(*b)==mapX) && (displayedPosY(*b)==mapY)) + { + setSelection(BUILDING_SELECTION, b); + selectionPushed=true; + return; + } + } + // then for unit + if (game.mouseUnit) + { + // a unit is selected: + setSelection(UNIT_SELECTION, game.mouseUnit); + selectionPushed = true; + // handle dump of unit characteristics + if ((SDL_GetModState() & KMOD_SHIFT) != 0) + { + OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend("unit.dump.txt")); + if (stream->isEndOfStream()) + { + std::cerr << "Can't dump unit to file unit.dump.txt" << std::endl; + } + else + { + std::cerr << "Dump unit " << game.mouseUnit->gid << " memory" << std::endl; + game.mouseUnit->save(stream); + game.mouseUnit->saveCrossRef(stream); + if (game.mouseUnit->attachedBuilding) + { + game.mouseUnit->attachedBuilding->save(stream); + game.mouseUnit->attachedBuilding->saveCrossRef(stream); + } + } + delete stream; + } + } + else + { + // then for building + Uint16 gbid=game.map.getBuilding(mapX, mapY); + if (gbid != NOGBID) + { + int buildingTeam=Building::GIDtoTeam(gbid); + // we can select for view buildings that are in shared vision, or any building in replay mode + if ((buildingTeam==localTeamNo) + || game.map.isFOWDiscovered(mapX, mapY, localTeam->me) + || (game.map.isMapDiscovered(mapX, mapY, localTeam->me) && (game.teams[buildingTeam]->allies&(1<replaying ) + { + setSelection(BUILDING_SELECTION, gbid); + selectionPushed=true; + // showUnitWorkingToBuilding=true; + // handle dump of building characteristics + if ((SDL_GetModState() & KMOD_SHIFT) != 0) + { + OutputStream *stream = new TextOutputStream(Toolkit::getFileManager()->openOutputStreamBackend("building.dump.txt")); + if (stream->isEndOfStream()) + { + std::cerr << "Can't dump unit to file building.dump.txt" << std::endl; + } + else + { + std::cerr << "Dump building " << selection.building->gid << " memory" << std::endl; + selection.building->save(stream); + selection.building->saveCrossRef(stream); + } + delete stream; + } + } + } + else + { + // and ressource + if (game.map.isRessource(mapX, mapY) && game.map.isMapDiscovered(mapX, mapY, localTeam->me)) + { + setSelection(RESSOURCE_SELECTION, mapY*game.map.getW()+mapX); + selectionPushed=true; + } + else + { + if (selectionMode == RESSOURCE_SELECTION) + clearSelection(); + } + } + } + } +} + +void GameGUI::handleReplayProgressBarClick(int mx, int my, int button) +{ + // Check the play, pause and fast-forward buttons + if (globalContainer->replaying) + { + int x = REPLAY_BAR_WIDTH - REPLAY_PROGRESS_BAR_X_OFFSET - REPLAY_PROGRESS_BAR_CAP_WIDTH; + int y = REPLAY_BAR_Y + REPLAY_PROGRESS_BAR_Y_OFFSET; + int inc = REPLAY_PROGRESS_BAR_BUTTON_WIDTH; + + if (my >= y && my <= y+20) + { + if (mx >= x-3*inc && mx <= x-2*inc) + { + // Play + gamePaused = false; + globalContainer->replayFastForward = false; + } + if (mx > x-2*inc && mx <= x-inc) + { + // Pause + gamePaused = true; + } + if (mx > x-inc && mx <= x) + { + // Fast-forward + gamePaused = false; + globalContainer->replayFastForward = true; + } + } + } +} diff --git a/src/gui/GameGUIInternal.h b/src/gui/GameGUIInternal.h new file mode 100644 index 000000000..a30502714 --- /dev/null +++ b/src/gui/GameGUIInternal.h @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// Constants shared between the split GameGUI*.cpp translation units. +// This header is intentionally private to those files; do not include it elsewhere. + +#pragma once + +#include + +#include +#include +#include + +#include "GlobalContainer.h" + +using namespace GAGCore; +using namespace GAGGUI; + +#define TYPING_INPUT_BASE_INC 7 +#define TYPING_INPUT_MAX_POS 46 + +// these values are manually layouted for cuteste perception +#define YPOS_BASE_DEFAULT 180 +#define YPOS_BASE_CONSTRUCTION (YPOS_BASE_DEFAULT + 5) +#define YPOS_BASE_FLAG (YPOS_BASE_DEFAULT + 5) +#define YPOS_BASE_STAT YPOS_BASE_DEFAULT +#define YPOS_BASE_BUILDING (YPOS_BASE_DEFAULT + 10) +#define YPOS_BASE_UNIT (YPOS_BASE_DEFAULT + 10) +#define YPOS_BASE_RESSOURCE YPOS_BASE_DEFAULT + +#define YOFFSET_NAME 28 +#define YOFFSET_ICON 52 +#define YOFFSET_CARYING 34 +#define YOFFSET_BAR 32 +#define YOFFSET_INFOS 12 +#define YOFFSET_TOWER 22 + +#define YOFFSET_B_SEP 6 + +#define YOFFSET_TEXT_BAR 16 +#define YOFFSET_TEXT_PARA 14 +#define YOFFSET_TEXT_LINE 12 + +#define YOFFSET_PROGRESS_BAR 10 + +#define YOFFSET_BRUSH 56 + +// Per-row pitches inside the building info panel resource/swarm sections. +#define YOFFSET_RESSOURCE_LINE 11 +#define YOFFSET_RESSOURCE_SECTION_PAD 5 +#define YOFFSET_SWARM_PROGRESS_BAR 15 +#define YOFFSET_SWARM_RATIO_LINE 20 + +// Dimensions of the production-timeout progress bar that sits above the swarm +// ratio scrollboxes. The width matches the right-menu content area (same +// literal appears in RIGHT_MENU_OFFSET below). +#define SWARM_PROGRESS_BAR_WIDTH 128 +#define SWARM_PROGRESS_BAR_HEIGHT 7 + +// Y-offsets (measured from the bottom of the screen) of the repair/upgrade +// and destroy action buttons in the building info panel, and the button height +// used for hit-testing the upgrade-preview tooltip hover. +#define BOTTOM_BUTTON_PRIMARY_YOFFSET 48 +#define BOTTOM_BUTTON_SECONDARY_YOFFSET 24 +#define BOTTOM_BUTTON_HEIGHT 16 + +// The sidebar on the right +#define RIGHT_MENU_WIDTH 160 +#define RIGHT_MENU_HALF_WIDTH (RIGHT_MENU_WIDTH / 2) +#define RIGHT_MENU_OFFSET ((RIGHT_MENU_WIDTH -128)/2) +#define RIGHT_MENU_RIGHT_OFFSET (RIGHT_MENU_WIDTH - RIGHT_MENU_OFFSET) + +// Geometry of the three-zone scrollbox widget used for worker count, flag +// stay-range and swarm-ratio sliders. The visual strip is laid out as +// [<-arrow][===proportional drag track===][->arrow] +// inside the right-menu's 128px content area. Click x-coordinates are +// interpreted relative to the strip's left edge (see +// interpretScrollBoxClick in GameGUIInputMenuClickBuilding.cpp). +constexpr int SCROLLBOX_BAR_WIDTH = 128; +constexpr int SCROLLBOX_ARROW_WIDTH = 18; + +// Icons for main menu, alliance and objectives buttons. +#define IGM_ICON_HEIGHT 36 +#define IGM_MAIN_MENU_ICON_Y 0 +#define IGM_ALLIANCE_ICON_Y IGM_ICON_HEIGHT +#define IGM_OBJECTIVES_ICON_Y (IGM_ICON_HEIGHT * 2) + +// Settings for the right sidebar in replays +#define REPLAY_PANEL_XOFFSET 25 +#define REPLAY_PANEL_YOFFSET (YPOS_BASE_STAT+10) +#define REPLAY_PANEL_SPACE_BETWEEN_OPTIONS 22 +#define REPLAY_PANEL_PLAYERLIST_YOFFSET (5*REPLAY_PANEL_SPACE_BETWEEN_OPTIONS+5) + +// The actual progress bar (including buttons) +#define REPLAY_PROGRESS_BAR_X_OFFSET 4 +#define REPLAY_PROGRESS_BAR_Y_OFFSET 3 +#define REPLAY_PROGRESS_BAR_BUTTON_WIDTH 15 +#define REPLAY_PROGRESS_BAR_CAP_WIDTH 10 +#define REPLAY_PROGRESS_BAR_NUM_BUTTONS 3 + +// The panel around the actual progress bar +#define REPLAY_BAR_WIDTH (globalContainer->settings.screenWidth - RIGHT_MENU_WIDTH - 4) +#define REPLAY_BAR_HEIGHT (2*REPLAY_PROGRESS_BAR_Y_OFFSET + 20) +#define REPLAY_BAR_Y (globalContainer->settings.screenHeight - REPLAY_BAR_HEIGHT) +#define REPLAY_BAR_TIMER_X (REPLAY_PROGRESS_BAR_X_OFFSET + REPLAY_PROGRESS_BAR_CAP_WIDTH + 5) + +// Sprites for the replay bar +#define REPLAY_BAR_LEFT_CAP_SPRITE 56 +#define REPLAY_BAR_RIGHT_CAP_SPRITE 57 +#define REPLAY_BAR_PLAY_BUTTON_SPRITE 51 +#define REPLAY_BAR_PLAY_BUTTON_ACTIVE_SPRITE 50 +#define REPLAY_BAR_PAUSE_BUTTON_SPRITE 53 +#define REPLAY_BAR_PAUSE_BUTTON_ACTIVE_SPRITE 52 +#define REPLAY_BAR_FAST_FORWARD_BUTTON_SPRITE 55 +#define REPLAY_BAR_FAST_FORWARD_BUTTON_ACTIVE_SPRITE 54 + +enum GameGUIGfxId +{ + EXCHANGE_BUILDING_ICONS = 21 +}; + +//! The screen that contains the text input while typing message in game +class InGameTextInput:public OverlayScreen +{ +protected: + //! the text input widget + TextInput *textInput; + +public: + //! InGameTextInput constructor + InGameTextInput(GraphicContext *parentCtx); + //! InGameTextInput destructor + virtual ~InGameTextInput() { } + //! React on action from any widget (but there is only one anyway) + virtual void onAction(Widget *source, Action action, int par1, int par2); + //! Return the text typed + std::string getText(void) const { return textInput->getText(); } + //! Set the text + void setText(const std::string text) const { textInput->setText(text); } +}; + diff --git a/src/GameGUIKeyActions.cpp b/src/gui/GameGUIKeyActions.cpp similarity index 88% rename from src/GameGUIKeyActions.cpp rename to src/gui/GameGUIKeyActions.cpp index b234759cc..88f24e77f 100644 --- a/src/GameGUIKeyActions.cpp +++ b/src/gui/GameGUIKeyActions.cpp @@ -1,20 +1,5 @@ -/*key - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "GameGUIKeyActions.h" diff --git a/src/GameGUIKeyActions.h b/src/gui/GameGUIKeyActions.h similarity index 69% rename from src/GameGUIKeyActions.h rename to src/gui/GameGUIKeyActions.h index 3bf5d54c6..5f477cde9 100644 --- a/src/GameGUIKeyActions.h +++ b/src/gui/GameGUIKeyActions.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GAMEGUI_KEY_ACTIONS_H -#define __GAMEGUI_KEY_ACTIONS_H +#pragma once #include "SDL.h" #include @@ -98,4 +82,3 @@ namespace GameGUIKeyActions extern std::map keys; }; -#endif diff --git a/src/GameGUILoadSave.cpp b/src/gui/GameGUILoadSave.cpp similarity index 83% rename from src/GameGUILoadSave.cpp rename to src/gui/GameGUILoadSave.cpp index 026e20390..3084285cd 100644 --- a/src/GameGUILoadSave.cpp +++ b/src/gui/GameGUILoadSave.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "GameGUILoadSave.h" #include "GlobalContainer.h" @@ -45,14 +29,17 @@ class FuncFileList: public FileList {} private: - std::string fileToList(const char* fileName) const + // Signatures here must match FileList's virtual base methods exactly, + // otherwise the override silently degenerates to a hidden non-virtual + // method and FileList::generateList() picks up the base default instead. + std::string fileToList(const std::string fileName) const override { - return filenameToNameFunc(fullName(fileName).c_str()); + return filenameToNameFunc(fullName(fileName)); } - - std::string listToFile(const char* listName) const + + std::string listToFile(const std::string listName) const override { - return nameToFilenameFunc(fullDir().c_str(), listName, extension.c_str()); + return nameToFilenameFunc(fullDir(), listName, extension); } private: diff --git a/src/GameGUILoadSave.h b/src/gui/GameGUILoadSave.h similarity index 62% rename from src/GameGUILoadSave.h rename to src/gui/GameGUILoadSave.h index f65195f1e..b2672f2f7 100644 --- a/src/GameGUILoadSave.h +++ b/src/gui/GameGUILoadSave.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GAME_GUI_LOAD_SAVE_H -#define __GAME_GUI_LOAD_SAVE_H +#pragma once #include using namespace GAGGUI; @@ -68,5 +51,3 @@ class LoadSaveScreen:public OverlayScreen const char *getFileName(void); const char *getName(void); }; - -#endif diff --git a/src/GameGUIMessageManager.cpp b/src/gui/GameGUIMessageManager.cpp similarity index 77% rename from src/GameGUIMessageManager.cpp rename to src/gui/GameGUIMessageManager.cpp index 9c65d9bb9..87a7f52aa 100644 --- a/src/GameGUIMessageManager.cpp +++ b/src/gui/GameGUIMessageManager.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "GameGUIMessageManager.h" #include "GlobalContainer.h" diff --git a/src/GameGUIMessageManager.h b/src/gui/GameGUIMessageManager.h similarity index 75% rename from src/GameGUIMessageManager.h rename to src/gui/GameGUIMessageManager.h index 9e3efd0b8..4b05e5356 100644 --- a/src/GameGUIMessageManager.h +++ b/src/gui/GameGUIMessageManager.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GameGUIMessageManager_h -#define GameGUIMessageManager_h +#pragma once #include #include @@ -111,4 +95,3 @@ class InGameScrollableHistory : public OverlayScreen }; -#endif diff --git a/src/gui/GameGUIOrders.cpp b/src/gui/GameGUIOrders.cpp new file mode 100644 index 000000000..7e57fcd92 --- /dev/null +++ b/src/gui/GameGUIOrders.cpp @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Unit.h" +#include "Utilities.h" +#include "IRC.h" +#include "SoundMixer.h" +#include "VoiceRecorder.h" +#include "GameGUIKeyActions.h" +#include "Player.h" +#include "ReplayReader.h" +#include "ReplayWriter.h" +#include "config.h" +#include "Order.h" + +#include + +using std::shared_ptr; +using std::static_pointer_cast; + +void GameGUI::reconcileBuildingGuiState(const std::shared_ptr& order) +{ + // When an order executes that updates the authoritative Building state, + // drop the corresponding pending shadow so the display falls back to + // authoritative. For the LOCAL player's own orders during live play we + // leave pending alone — the user may have already queued a newer change + // past the one that just landed, and we want the display to track the + // latest user intent, not flicker back to the now-stale authoritative + // value. Replays clear pending unconditionally because every order + // represents the authoritative timeline. + const bool replaying = globalContainer->replaying; + switch (order->getOrderType()) + { + case ORDER_MOVE_FLAG: + { + auto omf = std::static_pointer_cast(order); + if (omf->sender != localPlayer || replaying) + { + auto it = buildingGuiState.find(omf->gid); + if (it != buildingGuiState.end()) + { + it->second.pendingPosX.reset(); + it->second.pendingPosY.reset(); + } + } + break; + } + case ORDER_MODIFY_BUILDING: + { + auto omb = std::static_pointer_cast(order); + if (omb->sender != localPlayer || replaying) + { + auto it = buildingGuiState.find(omb->gid); + if (it != buildingGuiState.end()) + it->second.pendingMaxUnitWorking.reset(); + } + break; + } + case ORDER_MODIFY_FLAG: + { + auto omf = std::static_pointer_cast(order); + if (omf->sender != localPlayer || replaying) + { + auto it = buildingGuiState.find(omf->gid); + if (it != buildingGuiState.end()) + it->second.pendingUnitStayRange.reset(); + } + break; + } + default: + break; + } +} + +void GameGUI::executeOrder(std::shared_ptr order) +{ + switch (order->getOrderType()) + { + case ORDER_TEXT_MESSAGE : + { + std::shared_ptr mo=static_pointer_cast(order); + int sp=mo->sender; + Uint32 messageOrderType=mo->messageOrderType; + + if (messageOrderType==MessageOrder::NORMAL_MESSAGE_TYPE) + { + if (mo->recepientsMask &(1<name).arg(mo->getText()), true); + } + else if (messageOrderType==MessageOrder::PRIVATE_MESSAGE_TYPE) + { + if (mo->recepientsMask &(1< %2").arg(Toolkit::getStringTable()->getString("[from:]")).arg(game.players[sp]->name).arg(mo->getText()), true); + else if (sp==localPlayer) + { + Uint32 rm=mo->recepientsMask; + int k; + for (k=0; k %2").arg(Toolkit::getStringTable()->getString("[to:]")).arg(game.players[k]->name).arg(mo->getText()), true); + break; + } + else + rm=rm>>1; + assert(k ov = static_pointer_cast(order); + if (ov->recepientsMask & (1<mix->addVoiceData(ov); + game.executeOrder(order, localPlayer); + } + break; + case ORDER_PLAYER_QUIT_GAME : + { + int qp=order->sender; + if (qp==localPlayer) + isRunning=false; + addMessage(Color(200, 200, 200), FormatableString(Toolkit::getStringTable()->getString("[%0 has left the game]")).arg(game.players[qp]->name), true); + game.executeOrder(order, localPlayer); + } + break; + + case ORDER_MAP_MARK: + { + std::shared_ptr mmo=static_pointer_cast(order); + + assert(game.teams[mmo->teamNumber]->teamNumberteamNumber]->allies & (game.teams[localTeamNo]->me)) + addMark(mmo); + } + break; + case ORDER_PAUSE_GAME: + { + std::shared_ptr pgo=static_pointer_cast(order); + gamePaused=pgo->pause; + } + break; + case ORDER_CREATE: + { + std::shared_ptr pgo=static_pointer_cast(order); + if(pgo->teamNumber == localTeamNo) + ghostManager.removeBuilding(pgo->posX, pgo->posY); + game.executeOrder(order, localPlayer); + } + break; + default: + { + game.executeOrder(order, localPlayer); + } + } + reconcileBuildingGuiState(order); +} diff --git a/src/gui/GameGUIParticles.cpp b/src/gui/GameGUIParticles.cpp new file mode 100644 index 000000000..555f46ece --- /dev/null +++ b/src/gui/GameGUIParticles.cpp @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIInternal.h" +#include "GlobalContainer.h" + +void GameGUI::drawParticles(void) +{ + for (ParticleSet::iterator it = particles.begin(); it != particles.end(); ) + { + Particle* p = *it; + + // delete old particles + if (p->age >= p->lifeSpan) + { + ParticleSet::iterator oldIt = it; + ++it; + + delete *oldIt; + particles.erase(oldIt); + + continue; + } + else + p->age++; + + // do stupid physics + p->x += p->vx; + p->y += p->vy; + p->vx += p->ax; + p->vy += p->ay; + + // get image + float img = (float)p->startImg + (float)((p->endImg - p->startImg) * p->age) / ((float)p->lifeSpan + 1); + Uint8 alpha = (Uint8)(255.f * (img - truncf(img))); + int imgA = (int)img; + + globalContainer->particles->setBaseColor(p->color); + + // first image + int w = globalContainer->particles->getW(imgA); + int h = globalContainer->particles->getH(imgA); + globalContainer->gfx->drawSprite(p->x - 0.5f * w, p->y - 0.5f * h, globalContainer->particles, imgA, 255-alpha); + + // second image + int imgB = imgA + 1; + if (imgB < p->endImg) + { + w = globalContainer->particles->getW(imgA); + h = globalContainer->particles->getH(imgA); + globalContainer->gfx->drawSprite(p->x - 0.5f * w, p->y - 0.5f * h, globalContainer->particles, imgB, alpha); + } + + ++it; + } +} + +void GameGUI::generateNewParticles(std::set *visibleBuildings) +{ + for (std::set::iterator it = visibleBuildings->begin(); it != visibleBuildings->end(); ++it) + { + Building* building = *it; + BuildingType* type = building->type; + int x, y; + game.map.mapCaseToDisplayable(displayedPosX(*building), displayedPosY(*building), &x, &y, viewportX, viewportY); + + if (!type->isBuildingSite) + { + // damaged building smoke + float hpRatio = (float)building->hp / (float)type->hpMax; + if ( + (hpRatio < 0.2 && ((game.stepCounter & 0x1) == 0)) || + (hpRatio < 0.5 && ((game.stepCounter & 0x3) == 0)) + ) + { + Particle* p = new Particle; + p->x = x + type->width * 16; + p->y = y + type->height * 16; + if (hpRatio < 0.2) + { + p->vx = 0.5f - (float)rand() / (float)RAND_MAX; + p->vy = - 3.f * (float)rand() / (float)RAND_MAX; + } + else + { + p->vx = 0.3f - (float)rand() / (float)RAND_MAX; + p->vy = - 1.8f * (float)rand() / (float)RAND_MAX; + } + p->ax = 0.f; + p->ay = -0.01f; + p->age = 0; + p->lifeSpan = 50; + p->startImg = 0; + p->endImg = 2; + p->color = building->owner->color; + particles.insert(p); + } + + // turret firing + if (building->lastShootStep != 0xFFFFFFFF) + { + if ((game.stepCounter - building->lastShootStep < 6) && (game.stepCounter % 2 == 0)) + { + float norm = building->lastShootSpeedX * building->lastShootSpeedX + building->lastShootSpeedY * building->lastShootSpeedY; + float w2 = type->width * 16; + float h2 = type->height * 16; + float dx = (building->lastShootSpeedX * w2) / sqrt(norm); + float dy = (building->lastShootSpeedY * h2) / sqrt(norm); + Particle* p = new Particle; + p->x = x + w2 + dx; + p->y = y + h2 + dy; + p->vx = 0.3f - (float)rand() / (float)RAND_MAX; + p->vy = - 1.2f * (float)rand() / (float)RAND_MAX; + p->ax = 0.f; + p->ay = -0.02f; + p->age = 0; + p->lifeSpan = 30; + p->startImg = 0; + p->endImg = 2; + p->color = building->owner->color; + particles.insert(p); + } + } + } + } +} + +void GameGUI::moveParticles(int oldViewportX, int viewportX, int oldViewportY, int viewportY) +{ + if ((viewportX==oldViewportX) && (viewportY==oldViewportY)) + return; + + int dx = viewportX - oldViewportX; + if (dx > game.map.getW() / 2) + dx -= game.map.getW(); + else if (dx < -game.map.getW() / 2) + dx += game.map.getW(); + + int dy = viewportY - oldViewportY; + if (dy > game.map.getH() / 2) + dy -= game.map.getH(); + else if (dy < -game.map.getH() / 2) + dy += game.map.getH(); + + for (ParticleSet::iterator it = particles.begin(); it != particles.end(); ++it) + { + Particle* p = *it; + p->x -= dx * 32; + p->y -= dy * 32; + } +} diff --git a/src/gui/GameGUIPersistence.cpp b/src/gui/GameGUIPersistence.cpp new file mode 100644 index 000000000..cb6a9b6a4 --- /dev/null +++ b/src/gui/GameGUIPersistence.cpp @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Unit.h" +#include "Utilities.h" +#include "IRC.h" +#include "SoundMixer.h" +#include "VoiceRecorder.h" +#include "GameGUIKeyActions.h" +#include "Player.h" +#include "ReplayReader.h" +#include "ReplayWriter.h" +#include "config.h" +#include "Order.h" + +#include + +bool GameGUI::loadFromHeaders(MapHeader& mapHeader, GameHeader& gameHeader, bool setGameHeader, bool ignoreGUIData, bool saveAI) +{ + init(); + InputStream *stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapHeader.getFileName())); + if (stream->isEndOfStream()) + { + delete stream; + stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapHeader.getFileName(true))); + if(stream->isEndOfStream()) + { + delete stream; + stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapHeader.getFileName(false,true))); + if(stream->isEndOfStream()) + { + std::cerr << "GameGUI::loadFromHeaders() : error, can't open file " << mapHeader.getFileName() << ", " << mapHeader.getFileName(true) << " or " << mapHeader.getFileName(false,true) << std::endl; + delete stream; + return false; + } + } + } + + bool res = load(stream, ignoreGUIData); + delete stream; + if (!res) + return false; + + //Use the map header from the file, the one sent across the network is in the latest format version, where as the actual map + //may be an older file version. + //game.setMapHeader(mapHeader); + if(setGameHeader) + game.setGameHeader(gameHeader, saveAI); + + return true; +} + +bool GameGUI::load(GAGCore::InputStream *stream, bool ignoreGUIData) +{ + init(); + + bool result = game.load(stream); + + if (result == false) + { + std::cerr << "GameGUI::load : can't load game" << std::endl; + return false; + } + defualtGameSaveName = game.mapHeader.getMapName(); + if (game.mapHeader.getIsSavedGame()) + { + // load gui's specific infos + stream->readEnterSection("GameGUI"); + + ///Load the data, but don't store it in local variables + if(ignoreGUIData) + { + stream->readUint32("chatMask"); + stream->readSint32("localPlayer"); + stream->readSint32("localTeamNo"); + stream->readSint32("viewportX"); + stream->readSint32("viewportY"); + stream->readUint32("hiddenGUIElements"); + stream->readUint32("buildingsChoiceMask"); + stream->readUint32("flagsChoiceMask"); + } + else + { + chatMask = stream->readUint32("chatMask"); + + localPlayer = stream->readSint32("localPlayer"); + localTeamNo = stream->readSint32("localTeamNo"); + + viewportX = stream->readSint32("viewportX"); + viewportY = stream->readSint32("viewportY"); + + hiddenGUIElements = stream->readUint32("hiddenGUIElements"); + Uint32 buildingsChoiceMask = stream->readUint32("buildingsChoiceMask"); + Uint32 flagsChoiceMask = stream->readUint32("flagsChoiceMask"); + + // invert value if hidden + for (unsigned i=0; i= 69) + defaultAssign.load(stream, game.mapHeader.getVersionMinor()); + stream->readLeaveSection(); + } + + minimap.setGame(game); + + return true; +} + +void GameGUI::save(GAGCore::OutputStream *stream, const std::string name) +{ + // Game is can't be no more automatically generated + game.save(stream, false, name); + + stream->writeEnterSection("GameGUI"); + stream->writeUint32(chatMask, "chatMask"); + stream->writeSint32(localPlayer, "localPlayer"); + stream->writeSint32(localTeamNo, "localTeamNo"); + stream->writeSint32(viewportX, "viewportX"); + stream->writeSint32(viewportY, "viewportY"); + stream->writeUint32(hiddenGUIElements, "hiddenGUIElements"); + Uint32 buildingsChoiceMask = 0; + Uint32 flagsChoiceMask = 0; + // save one if visible + for (unsigned i=0; iwriteUint32(buildingsChoiceMask, "buildingsChoiceMask"); + stream->writeUint32(flagsChoiceMask, "flagsChoiceMask"); + defaultAssign.save(stream); + stream->writeLeaveSection(); +} diff --git a/src/gui/GameGUIScript.cpp b/src/gui/GameGUIScript.cpp new file mode 100644 index 000000000..7749b198d --- /dev/null +++ b/src/gui/GameGUIScript.cpp @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIInternal.h" +#include "GlobalContainer.h" +#include "IntBuildingType.h" +#include "Unit.h" + +void GameGUI::enableBuildingsChoice(const std::string &name) +{ + for (size_t i=0; ireplaying) return; + + hiddenGUIElements |= (1<settings.language) + showScriptText(text); +} + +void GameGUI::hideScriptText() +{ + scriptText.clear(); +} + +void GameGUI::setCpuLoad(int s) +{ + smoothedCPULoad[smoothedCPUPos]=s; + smoothedCPUPos=(smoothedCPUPos+1) % SMOOTHED_CPU_SIZE; +} + + + +void GameGUI::setCampaignGame(Campaign& campaign, const std::string& missionName) +{ + this->campaign=&campaign; + this->missionName=missionName; +} + + + +void GameGUI::updateHilightInGame() +{ + game.highlightUnitType = 0; + if(hilights.find(HilightWorkers) != hilights.end()) + { + game.highlightUnitType |= 1< + +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIInternal.h" +#include "GlobalContainer.h" +#include "Unit.h" + +void GameGUI::cleanOldSelection(void) +{ + if (selectionMode==BUILDING_SELECTION) + { + game.selectedBuilding=NULL; + } + else if (selectionMode==UNIT_SELECTION) + { + game.selectedUnit=NULL; + } + else if (selectionMode==BRUSH_SELECTION) + { + toolManager.deactivateTool(); + } + else if (selectionMode==TOOL_SELECTION) + { + toolManager.deactivateTool(); + } +} + +void GameGUI::setSelection(SelectionMode newSelMode, unsigned newSelection) +{ + if (selectionMode!=newSelMode) + { + cleanOldSelection(); + selectionMode=newSelMode; + } + + if (selectionMode==BUILDING_SELECTION) + { + int id=Building::GIDtoID(newSelection); + int team=Building::GIDtoTeam(newSelection); + selection.building=game.teams[team]->myBuildings[id]; + game.selectedBuilding=selection.building; + } + else if (selectionMode==UNIT_SELECTION) + { + int id=Unit::GIDtoID(newSelection); + int team=Unit::GIDtoTeam(newSelection); + selection.unit=game.teams[team]->myUnits[id]; + game.selectedUnit=selection.unit; + } + else if (selectionMode==RESSOURCE_SELECTION) + { + selection.ressource=newSelection; + } +} + +void GameGUI::setSelection(SelectionMode newSelMode, void* newSelection) +{ + if (selectionMode!=newSelMode) + { + cleanOldSelection(); + selectionMode=newSelMode; + } + + if (selectionMode==BUILDING_SELECTION) + { + selection.building=(Building*)newSelection; + game.selectedBuilding=selection.building; + } + else if (selectionMode==UNIT_SELECTION) + { + selection.unit=(Unit*)newSelection; + game.selectedUnit=selection.unit; + } + else if (selectionMode==TOOL_SELECTION) + { + toolManager.activateBuildingTool((char*)(newSelection)); + } +} + +// Validate the current selection's referent and clear it if the referent is gone. +// Called from drawPanel() before dispatching to the per-mode draw routines so +// that those routines can assume the selection is still valid. Keep selection +// validation here rather than in draw functions — draws should be pure. +void GameGUI::checkSelection(void) +{ + if ((selectionMode==BUILDING_SELECTION) && (game.selectedBuilding==NULL)) + { + clearSelection(); + } + else if ((selectionMode==UNIT_SELECTION) && (game.selectedUnit==NULL)) + { + clearSelection(); + } + else if ((selectionMode==RESSOURCE_SELECTION) + && (game.map.getRessource(selection.ressource).type==NO_RES_TYPE)) + { + clearSelection(); + } +} + + +void GameGUI::iterateSelection(void) +{ + if (selectionMode==BUILDING_SELECTION) + { + Building* selBuild=selection.building; + Uint16 selectionGBID=selBuild->gid; + assert(selBuild); + assert(selectionGBID!=NOGBID); + int pos=Building::GIDtoID(selectionGBID); + int team=Building::GIDtoTeam(selectionGBID); + int i=pos; + if (team==localTeamNo) + { + while (imyBuildings[i % Building::MAX_COUNT]; + if (b && b->typeNum==selBuild->typeNum) + { + setSelection(BUILDING_SELECTION, b); + centerViewportOnSelection(); + break; + } + } + } + } + else if (selectionMode==TOOL_SELECTION) + { + Sint32 typeNum=globalContainer->buildingsTypes.getTypeNum(toolManager.getBuildingName(), 0, false); + for (int i=0; imyBuildings[i]; + if (b && b->typeNum==typeNum) + { + setSelection(BUILDING_SELECTION, b); + centerViewportOnSelection(); + break; + } + } + } + else if (selectionMode == UNIT_SELECTION) + { + Unit * selUnit = selection.unit; + assert(selUnit); + Uint16 gid = selUnit->gid; + /* to be safe should check if gid is valid here? */ + /* if looking at one of our pieces, continue with the next + one of our pieces of same type, otherwise start at the + beginning of our pieces of that type. */ + Sint32 id = ((Unit::GIDtoTeam(gid) == localTeamNo) ? Unit::GIDtoID(gid) : 0); + id %= Unit::MAX_COUNT; /* just in case! */ + // std::cerr << "starting id: " << id << std::endl; + Sint32 i = id; + while (1) + { + i = ((i + 1) % Unit::MAX_COUNT); + if (i == id) break; + // std::cerr << "trying id: " << i << std::endl; + Unit * u = game.teams[localTeamNo]->myUnits[i]; + if (u && (u->typeNum == selUnit->typeNum)) + { + // std::cerr << "found id: " << i << std::endl; + setSelection(UNIT_SELECTION, u); + centerViewportOnSelection(); + break; + } + } + } +} + +void GameGUI::centerViewportOnSelection(void) +{ + if ((selectionMode==BUILDING_SELECTION) || (selectionMode==UNIT_SELECTION)) + { + // Default-init so a future selectionMode that slips past the outer + // guard can't read uninitialized stack in release builds (where + // the asserts below are stripped). + Sint32 posX = 0, posY = 0; + if (selectionMode==BUILDING_SELECTION) + { + Building* b=selection.building; + assert(b); + posX = b->getMidX(); + posY = b->getMidY(); + } + else if (selectionMode==UNIT_SELECTION) + { + Unit * u = selection.unit; + assert (u); + posX = u->posX; + posY = u->posY; + } + + /* It violates good abstraction principles that we know here + that the size of the right panel is RIGHT_MENU_WIDTH pixels, and that each + map cell is 32 pixels. This information should be + abstracted. */ + + int oldViewportX = viewportX; + int oldViewportY = viewportY; + + viewportX = posX - ((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); + viewportY = posY - ((globalContainer->gfx->getH())>>6); + viewportX = viewportX & game.map.getMaskW(); + viewportY = viewportY & game.map.getMaskH(); + + moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); + } +} + + +// Called from the sim path (Team::syncStep) when a unit is about to be +// deleted. Clears the GUI's selected-unit pointer if it referred to the +// dying unit. The sim never reads game.selectedUnit directly — going +// through this hook keeps the per-client GUI read out of the sim path, +// where a divergent predicate could become a desync if anyone extended +// the branch with sim-touching code. +void GameGUI::onUnitDestroyed(Unit *u) +{ + if (game.selectedUnit == u) + game.selectedUnit = NULL; +} + +// Mirror of onUnitDestroyed for building demolition. See that comment. +void GameGUI::onBuildingDestroyed(Building *b) +{ + if (game.selectedBuilding == b) + game.selectedBuilding = NULL; +} + +void GameGUI::dumpUnitInformation(void) +{ + if(game.selectedUnit != NULL) + { + Unit* unit = game.selectedUnit; + std::cout<<"unit->posx = "<posX<posy = "<posY<gid = "<gid<medical = "<medical<activity = "<activity<displacement = "<displacement<movement = "<movement<action = "<action<targetBuilding) + std::cout<<"unit->targetBuilding->gid = "<targetBuilding->gid< +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Game.h" +#include "GameGUI.h" +#include "GameGUIDialog.h" +#include "GameGUIInternal.h" +#include "GameGUILoadSave.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "Unit.h" +#include "Utilities.h" +#include "IRC.h" +#include "SoundMixer.h" +#include "VoiceRecorder.h" +#include "GameGUIKeyActions.h" +#include "Player.h" +#include "ReplayReader.h" +#include "ReplayWriter.h" +#include "config.h" +#include "Order.h" + +#include + +using std::shared_ptr; +using std::static_pointer_cast; + +void GameGUI::moveFlag(int mx, int my, bool drop) +{ + if (globalContainer->replaying) return; + + int posX, posY; + Building* selBuild=selection.building; + game.map.cursorToBuildingPos(mx, my, selBuild->type->width, selBuild->type->height, &posX, &posY, viewportX, viewportY); + if ((displayedPosX(*selBuild)!=posX) + ||(displayedPosY(*selBuild)!=posY) + ||(drop && (selectionPushedPosX!=posX || selectionPushedPosY!=posY))) + { + Uint16 gid=selBuild->gid; + shared_ptr oms(new OrderMoveFlag(gid, posX, posY, drop)); + // First, we check if anoter move of the same flag is already in the "orderQueue". + bool found=false; + for (std::list >::iterator it=orderQueue.begin(); it!=orderQueue.end(); ++it) + { + if ( ((*it)->getOrderType()==ORDER_MOVE_FLAG)) + { + if(static_pointer_cast(*it)->gid==gid) + { + (*it) = oms; + found=true; + break; + } + } + } + if (!found) + orderQueue.push_back(oms); + BuildingGuiState& s = pendingFor(gid); + s.pendingPosX = posX; + s.pendingPosY = posY; + } +} + +void GameGUI::dragStep(int mx, int my, int button) +{ + /* We used to use SDL_GetMouseState, like the following + commented-out code, but that was buggy and prevented + dragging from correctly going through intermediate cells. + It is vital to use the mouse position and button status as + it was at the time in the middle of the event stream, not + as it is now. So instead we make sure the correct data is + passed to us as a parameter. */ + // int mx, my; + // Uint8 button = SDL_GetMouseState(&mx, &my); + // fprintf (stderr, "enter dragStep: button: %d, mx: %d, selectionMode: %d\n", button, mx, selectionMode); + if ((button&SDL_BUTTON(1)) && (mxgfx->getW()-RIGHT_MENU_WIDTH)) + { + // Update flag + if (selectionMode == BUILDING_SELECTION) + { + Building* selBuild=selection.building; + if (selBuild && selectionPushed && (selBuild->type->isVirtual)) + moveFlag(mx, my, false); + } + // Update tool + else if (selectionMode==BRUSH_SELECTION || selectionMode==TOOL_SELECTION) + { + toolManager.handleMouseDrag(mx, my, localTeamNo, viewportX, viewportY); + } + } + // fprintf (stderr, "exit dragStep\n"); +} + +/* We need to keep track of the last recorded mouse position for use + in drag steps. We can't simply use SDL_GetMouseState to get this + information, because we need the information as it was in the + middle of the event stream. (There may be many later events we + have not yet processed.) */ +int lastMouseX = 0, lastMouseY = 0; // can't make these Uint16 because of SDL_GetMouseState +Uint16 lastMouseButtonState = 0; + +void GameGUI::step(void) +{ + SDL_Event event, mouseMotionEvent, windowEvent; + bool wasMouseMotion=false; + bool wasWindowEvent=false; + int oldMouseMapX = -1, oldMouseMapY = -1; // hopefully the values here will never matter + // we get all pending events but for mousemotion we only keep the last one + while (SDL_PollEvent(&event)) + { + if (event.type==SDL_MOUSEMOTION) + { + lastMouseX = event.motion.x; + lastMouseY = event.motion.y; + lastMouseButtonState = event.motion.state; + int mouseMapX, mouseMapY; + bool onViewport = (lastMouseX < globalContainer->gfx->getW()-RIGHT_MENU_WIDTH); + /* We keep track for each mouse motion event + of which map cell it corresponds to. When + dragging, we will use this to make sure we + process at least one event per map cell, + and only discard multiple events when they + are for the same map cell. This is + necessary to make dragging work correctly + when drawing areas with the brush. */ + if (onViewport) + { + game.map.cursorToBuildingPos (lastMouseX, lastMouseY, 1, 1, &mouseMapX, &mouseMapY, viewportX, viewportY); + } + else + { + /* We interpret all locations outside the + viewport as being equivalent, and + distinct from any map location. */ + mouseMapX = -1; + mouseMapY = -1; + } + // fprintf (stderr, "mouse motion: (lastMouseX,lastMouseY): (%d,%d), (mouseMapX,mouseMapY): (%d,%d), (oldMouseMapX,oldMouseMapY): (%d,%d)\n", lastMouseX, lastMouseY, mouseMapX, mouseMapY, oldMouseMapX, oldMouseMapY); + /* Make sure dragging does not skip over map cells by + processing the old stored event rather than throwing + it away. */ + if (wasMouseMotion + && (lastMouseButtonState & SDL_BUTTON(1)) // are we dragging? (should not be hard-coding this condition but should be abstract somehow) + && ((mouseMapX != oldMouseMapX) + || (mouseMapY != oldMouseMapY)) + ) + { + // fprintf (stderr, "processing old event instead of discarding it\n"); + processEvent(&mouseMotionEvent); + } + oldMouseMapX = mouseMapX; + oldMouseMapY = mouseMapY; + mouseMotionEvent=event; + wasMouseMotion=true; + } +# ifdef USE_OSX + else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_q && SDL_GetModState() & KMOD_GUI) + { + isRunning=false; + exitGlobCompletely=true; + } +# endif +# ifdef USE_WIN32 + else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_F4 && SDL_GetModState() & KMOD_ALT) + { + isRunning=false; + exitGlobCompletely=true; + } +# endif + else if ((event.type == SDL_MOUSEBUTTONDOWN) || (event.type == SDL_MOUSEBUTTONUP)) + { + lastMouseButtonState = SDL_GetMouseState (&lastMouseX, &lastMouseY); + /* We ignore what SDL_GetMouseState does to + lastMouseX and lastMouseY, because that may + reflect many subsequent events that we have not + yet processed. Technically, we shouldn't use + SDL_GetMouseState at all but should calculate the + button state by keeping track of what has + happened. However, I haven't had the programming + energy to do this, so I am cheating in the line + above. */ + lastMouseX = event.button.x; + lastMouseY = event.button.y; + processEvent (&event); + } + else if (event.type==SDL_WINDOWEVENT) + { + windowEvent=event; + wasWindowEvent=true; + } + else + { + processEvent(&event); + } + } + if (wasMouseMotion) + processEvent(&mouseMotionEvent); + if (wasWindowEvent) + processEvent(&windowEvent); + + flushScrollWheelOrders(); + + int oldViewportX = viewportX; + int oldViewportY = viewportY; + + viewportX += game.map.getW(); + viewportY += game.map.getH(); + handleKeyAlways(); + viewportX += viewportSpeedX; + viewportY += viewportSpeedY; + viewportX &= game.map.getMaskW(); + viewportY &= game.map.getMaskH(); + + if ((viewportX!=oldViewportX) || (viewportY!=oldViewportY)) + { + dragStep(lastMouseX, lastMouseY, lastMouseButtonState); + moveParticles(oldViewportX, viewportX, oldViewportY, viewportY); + } + + assert(localTeam); + while(std::optional gevent = localTeam->getEvent()) + { + addMessage(gevent->formatColor(), gevent->formatMessage(game), false); + eventGoPosX = gevent->getX(); + eventGoPosY = gevent->getY(); + eventGoType = gevent->getEventType(); + } + + // voice step + std::shared_ptr orderVoiceData; + while ((orderVoiceData = globalContainer->voiceRecorder->getNextOrder()) != NULL) + { + orderVoiceData->recepientsMask = chatMask ^ (chatMask & (1< messages; + setMultiLine(game.sgslScript.textShown, &messages, " "); + + ///Add each line as a seperate message to the message manager. + ///Must be done backwards to appear in the right order + for (int i=messages.size()-1; i>=0; i--) + { + messageManager.addChatMessage(InGameMessage(messages[i], Color(255, 255, 255), 0)); + } + + previousSGSLText = game.sgslScript.textShown; + } + + // Check if the text being displayed has changed, and if it has, add it to the history box + if (scriptTextUpdated) + { + // Split into one per line + std::vector messages; + setMultiLine(scriptText, &messages, " "); + + // Add each line as a seperate message to the message manager. + // Must be done backwards to appear in the right order + for (int i=messages.size()-1; i>=0; i--) + { + messageManager.addChatMessage(InGameMessage(messages[i], Color(255, 255, 255), 0)); + } + + scriptTextUpdated = false; + } + + // music step + GameMusicEvents musicEvents; + musicEvents.unitUnderAttack = localTeam->wasRecentEvent(GEUnitUnderAttack); + musicEvents.unitLostConversion = localTeam->wasRecentEvent(GEUnitLostConversion); + musicEvents.unitGainedConversion = localTeam->wasRecentEvent(GEUnitGainedConversion); + musicEvents.buildingUnderAttack = localTeam->wasRecentEvent(GEBuildingUnderAttack); + musicEvents.buildingCompleted = localTeam->wasRecentEvent(GEBuildingCompleted); + if (auto nextTrack = musicController.tick(musicEvents)) + globalContainer->mix->setNextTrack(*nextTrack, true); + + std::shared_ptr order = toolManager.getOrder(); + while(order) + { + orderQueue.push_back(order); + order = toolManager.getOrder(); + } + + ///This shows the mission briefing at the begginning of the mission + if(game.stepCounter == 12) + { + if(game.missionBriefing != "") + { + if(gameMenuScreen) + { + delete gameMenuScreen; + gameMenuScreen=NULL; + } + inGameMenu=IGM_OBJECTIVES; + gameMenuScreen = new InGameObjectivesScreen(this, true); + } + } + + if(game.stepCounter % 25 == 1) + { + if(showStarvingMap) + overlay.compute(game, OverlayArea::Starving, localTeamNo); + else if(showDamagedMap) + overlay.compute(game, OverlayArea::Damage, localTeamNo); + else if(showDefenseMap) + overlay.compute(game, OverlayArea::Defence, localTeamNo); + else if(showFertilityMap) + overlay.compute(game, OverlayArea::Fertility, localTeamNo); + } + + // do we have won or lost conditions + checkWonConditions(); + + if (game.anyPlayerWaited) // TODO: warning valgrind + game.anyPlayerWaitedTimeFor++; +} + +void GameGUI::syncStep(void) +{ + assert(localTeam); + assert(teamStats); + + if ((game.stepCounter&255) == 79) + { + const std::string name = Toolkit::getStringTable()->getString("[auto save]"); + std::string fileName = glob2NameToFilename("games", name, "game"); + OutputStream *stream = new BinaryOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(fileName)); + if (stream->isEndOfStream()) + { + std::cerr << "GameGUI::syncStep : can't open autosave file " << name << " for writing" << std::endl; + } + else + { + save(stream, name); + } + delete stream; + } +} + +void GameGUI::checkWonConditions(void) +{ + if (hasEndOfGameDialogBeenShown || globalContainer->replaying) + return; + + if (game.totalPrestigeReached && game.isPrestigeWinCondition()) + { + if (inGameMenu==IGM_NONE) + { + inGameMenu=IGM_END_OF_GAME; + gameMenuScreen=new InGameEndOfGameScreen(Toolkit::getStringTable()->getString("[Total prestige reached]"), true); + hasEndOfGameDialogBeenShown=true; + miniMapPushed=false; + } + } + else if (localTeam->hasLost==true) + { + if (inGameMenu==IGM_NONE) + { + inGameMenu=IGM_END_OF_GAME; + gameMenuScreen=new InGameEndOfGameScreen(Toolkit::getStringTable()->getString("[you have lost]"), true); + hasEndOfGameDialogBeenShown=true; + miniMapPushed=false; + } + } + else if (localTeam->hasWon==true) + { + if (inGameMenu==IGM_NONE) + { + if(campaign!=NULL) + { + campaign->setCompleted(missionName); + } + inGameMenu=IGM_END_OF_GAME; + gameMenuScreen=new InGameEndOfGameScreen(Toolkit::getStringTable()->getString("[you have won]"), true); + hasEndOfGameDialogBeenShown=true; + miniMapPushed=false; + } + } +} + +void GameGUI::showEndOfReplayScreen() +{ + gamePaused = true; + + if (!hasEndOfGameDialogBeenShown) + { + hasEndOfGameDialogBeenShown = true; + + inGameMenu=IGM_END_OF_GAME; + gameMenuScreen=new InGameEndOfGameScreen(Toolkit::getStringTable()->getString("[replay ended]"), true); + miniMapPushed=false; + } +} + +void GameGUI::flushScrollWheelOrders() +{ + SDL_Keymod modState = SDL_GetModState(); + if (scrollWheelChanges!=0 && selectionMode==BUILDING_SELECTION) + { + Building* selBuild=selection.building; + if ((selBuild->owner->teamNumber==localTeamNo) && + (selBuild->buildingState==Building::ALIVE)) + { + if ((selBuild->type->maxUnitWorking) && + (!globalContainer->settings.scrollWheelEnabled ? (modState & KMOD_CTRL) : !(SDL_GetModState()&KMOD_SHIFT))) + { + const int requested = std::min((int)MAX_UNIT_WORKING, std::max(0, displayedMaxUnitWorking(*selBuild) + scrollWheelChanges)); + pendingFor(selBuild->gid).pendingMaxUnitWorking = requested; + orderQueue.push_back(shared_ptr(new OrderModifyBuilding(selBuild->gid, requested))); + defaultAssign.setDefaultAssignedUnits(selBuild->typeNum, requested); + } + else if ((selBuild->type->defaultUnitStayRange) && + (SDL_GetModState()&KMOD_SHIFT)) + { + const int requested = std::min((int)selBuild->type->maxUnitStayRange, std::max(0, displayedUnitStayRange(*selBuild) + scrollWheelChanges)); + pendingFor(selBuild->gid).pendingUnitStayRange = requested; + orderQueue.push_back(shared_ptr(new OrderModifyFlag(selBuild->gid, requested))); + } + } + } + scrollWheelChanges=0; +} diff --git a/src/GameGUIToolManager.cpp b/src/gui/GameGUIToolManager.cpp similarity index 92% rename from src/GameGUIToolManager.cpp rename to src/gui/GameGUIToolManager.cpp index cef8e3e5e..6cea268ba 100644 --- a/src/GameGUIToolManager.cpp +++ b/src/gui/GameGUIToolManager.cpp @@ -1,23 +1,6 @@ -/* -Copyright (C) 2007 Bradley Arsenault - -Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière -for any question or comment contact us at or - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "GameGUIToolManager.h" #include "GlobalContainer.h" @@ -267,15 +250,15 @@ void GameGUIToolManager::handleMouseDrag(int mouseX, int mouseY, int localteam, -boost::shared_ptr GameGUIToolManager::getOrder() +std::shared_ptr GameGUIToolManager::getOrder() { if(!orders.empty()) { - boost::shared_ptr order = orders.front(); + std::shared_ptr order = orders.front(); orders.pop(); return order; } - return boost::shared_ptr(); + return std::shared_ptr(); } @@ -346,15 +329,15 @@ void GameGUIToolManager::flushBrushOrders(int localteam) { if (zoneType == Forbidden) { - orders.push(boost::shared_ptr(new OrderAlterateForbidden(localteam, brush.getType(), &brushAccumulator, &game.map))); + orders.push(std::shared_ptr(new OrderAlterateForbidden(localteam, brush.getType(), &brushAccumulator, &game.map))); } else if (zoneType == Guard) { - orders.push(boost::shared_ptr(new OrderAlterateGuardArea(localteam, brush.getType(), &brushAccumulator, &game.map))); + orders.push(std::shared_ptr(new OrderAlterateGuardArea(localteam, brush.getType(), &brushAccumulator, &game.map))); } else if (zoneType == Clearing) { - orders.push(boost::shared_ptr(new OrderAlterateClearArea(localteam, brush.getType(), &brushAccumulator, &game.map))); + orders.push(std::shared_ptr(new OrderAlterateClearArea(localteam, brush.getType(), &brushAccumulator, &game.map))); } else assert(false); @@ -401,7 +384,7 @@ void GameGUIToolManager::placeBuildingAt(int mapX, int mapY, int localteam) if(bt->isVirtual) r = globalContainer->settings.defaultFlagRadius[bt->shortTypeNum - IntBuildingType::EXPLORATION_FLAG]; ghostManager.addBuilding(building, mapX, mapY); - orders.push(boost::shared_ptr(new OrderCreate(localteam, mapX, mapY, typeNum, unitWorking, unitWorkingFuture, r))); + orders.push(std::shared_ptr(new OrderCreate(localteam, mapX, mapY, typeNum, unitWorking, unitWorkingFuture, r))); } } } diff --git a/src/GameGUIToolManager.h b/src/gui/GameGUIToolManager.h similarity index 74% rename from src/GameGUIToolManager.h rename to src/gui/GameGUIToolManager.h index 498f427e6..a14159251 100644 --- a/src/GameGUIToolManager.h +++ b/src/gui/GameGUIToolManager.h @@ -1,28 +1,10 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +#pragma once - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef GameGUIToolManager_h -#define GameGUIToolManager_h - -#include "boost/shared_ptr.hpp" +#include #include "Brush.h" #include #include @@ -86,7 +68,7 @@ class GameGUIToolManager void handleMouseDrag(int mouseX, int mouseY, int localteam, int viewportX, int viewportY); ///Returns an order, or shared_ptr() if there are none - boost::shared_ptr getOrder(); + std::shared_ptr getOrder(); private: ///Handles placing a zone on the map void handleZonePlacement(int mouseX, int mouseY, int localteam, int viewportX, int viewportY); @@ -122,7 +104,6 @@ class GameGUIToolManager ///Used to indicate the stength of hilight, because it blends during the draw float hilightStrength; ///Queues up orderws for this manager - std::queue > orders; + std::queue > orders; }; -#endif diff --git a/src/gui/GameMusicController.cpp b/src/gui/GameMusicController.cpp new file mode 100644 index 000000000..7eaa9a531 --- /dev/null +++ b/src/gui/GameMusicController.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "GameMusicController.h" + +void GameMusicController::reset() +{ + warTimeoutTicks = 0; + buildingTimeoutTicks = 0; +} + +std::optional GameMusicController::tick(const GameMusicEvents& events) +{ + std::optional nextTrack; + + // Something bad happened. + if (events.unitUnderAttack || events.unitLostConversion || events.buildingUnderAttack) + { + warTimeoutTicks = EVENT_TIMEOUT_TICKS; + nextTrack = MusicTrack::WarEvent; + } + + // Something good happened. + if (events.unitGainedConversion || events.buildingCompleted) + { + buildingTimeoutTicks = EVENT_TIMEOUT_TICKS; + nextTrack = MusicTrack::BuildingEvent; + } + + // Either timer just hit "one tick from zero" — fall back to the in-game + // default. Checked BEFORE the decrement so the transition fires the tick + // the timer would otherwise reach zero on. Matches the original + // musicStep ordering, where the equality check ran ahead of the decay. + if (buildingTimeoutTicks == 1 || warTimeoutTicks == 1) + { + nextTrack = MusicTrack::InGameDefault; + } + + // Decay both timers. + if (warTimeoutTicks > 0) + --warTimeoutTicks; + if (buildingTimeoutTicks > 0) + --buildingTimeoutTicks; + + return nextTrack; +} diff --git a/src/gui/GameMusicController.h b/src/gui/GameMusicController.h new file mode 100644 index 000000000..d020c446a --- /dev/null +++ b/src/gui/GameMusicController.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include + +#include "MusicTrack.h" + +//! Events observed from the local team this tick that may trigger a music +//! transition. The booleans correspond 1:1 to the GameEventType values +//! GameMusicController cares about; bundling them at the call site keeps the +//! controller free of any Team / Game dependency, which is what lets it run +//! in unit tests without SDL or libgag. +struct GameMusicEvents +{ + bool unitUnderAttack = false; + bool unitLostConversion = false; + bool unitGainedConversion = false; + bool buildingUnderAttack = false; + bool buildingCompleted = false; +}; + +//! Pure state machine that selects in-game music based on recent team events. +//! +//! Two timers count down at the simulation tick rate (40 ms). A "bad" event +//! (unit/building under attack, unit lost to conversion) sets the war timer +//! and queues the war track. A "good" event (building completed, unit +//! converted to us) sets the building timer and queues the building track. +//! When either timer is about to expire (reaches 1 before this tick's +//! decrement), the controller queues a return to the in-game default track. +//! +//! All timer state lives on the instance — no function-local statics — so +//! that resetting between games is just constructing a fresh controller (or +//! calling reset()). The previous implementation used file-scope statics in +//! GameGUI::musicStep and leaked timer state from one game into the next. +class GameMusicController +{ +public: + //! Wall-clock duration of the post-event "stay on the event track" + //! window, in 40 ms simulation ticks (220 * 40 ms = 8.8 s). + static constexpr unsigned EVENT_TIMEOUT_TICKS = 220; + + //! Reset both timers to 0. Called by GameGUI::init() at the start of + //! every loaded game so state from a previous game cannot leak in. + void reset(); + + //! Advance one simulation tick. Returns the track that should be + //! requested from SoundMixer this tick, or std::nullopt if nothing + //! changes. When multiple branches fire in the same tick (e.g. an + //! event AND a timer expiring), the later branch wins, mirroring the + //! original musicStep which emitted multiple setNextTrack calls per + //! tick and let the last one stick. + std::optional tick(const GameMusicEvents& events); + + // Accessors for testing. + unsigned getWarTimeoutTicks() const { return warTimeoutTicks; } + unsigned getBuildingTimeoutTicks() const { return buildingTimeoutTicks; } + +private: + unsigned warTimeoutTicks = 0; + unsigned buildingTimeoutTicks = 0; +}; diff --git a/src/gui/TeamDisplay.cpp b/src/gui/TeamDisplay.cpp new file mode 100644 index 000000000..83841aab9 --- /dev/null +++ b/src/gui/TeamDisplay.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "TeamDisplay.h" + +#include "StringTable.h" +#include "Toolkit.h" + +#include "Team.h" + +using namespace GAGCore; + +std::string displayPlayerName(const Team& team) +{ + std::string name = team.getFirstPlayerName(); + if (name.empty()) + return Toolkit::getStringTable()->getString("[Uncontrolled]"); + return name; +} diff --git a/src/gui/TeamDisplay.h b/src/gui/TeamDisplay.h new file mode 100644 index 000000000..560f6dffd --- /dev/null +++ b/src/gui/TeamDisplay.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include + +class Team; + +//! Display name for a team — the first player's name, or a localized +//! "[Uncontrolled]" placeholder when no player owns the team. UI-only; +//! sim code must use Team::getFirstPlayerName (which may return empty). +std::string displayPlayerName(const Team& team); diff --git a/src/gui/UnitDisplayNames.cpp b/src/gui/UnitDisplayNames.cpp new file mode 100644 index 000000000..9e8752fdf --- /dev/null +++ b/src/gui/UnitDisplayNames.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include "Toolkit.h" +#include "StringTable.h" + +#include "UnitConsts.h" +#include "UnitDisplayNames.h" + +using namespace GAGCore; + +std::string getUnitName(int type) +{ + switch(type) + { + case WORKER: + return Toolkit::getStringTable()->getString("[Worker]"); + case WARRIOR: + return Toolkit::getStringTable()->getString("[Warrior]"); + case EXPLORER: + return Toolkit::getStringTable()->getString("[Explorer]"); + default: + assert(false); + return {}; + } +} diff --git a/src/gui/UnitDisplayNames.h b/src/gui/UnitDisplayNames.h new file mode 100644 index 000000000..546f111ec --- /dev/null +++ b/src/gui/UnitDisplayNames.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include + +//! Localized display name for a unit type (WORKER / WARRIOR / EXPLORER). +//! UI/display layer only — sim code must use the UnitConsts enum value. +std::string getUnitName(int type); diff --git a/src/map/Map.cpp b/src/map/Map.cpp new file mode 100644 index 000000000..2d920ff30 --- /dev/null +++ b/src/map/Map.cpp @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "Unit.h" +#include "MapInternal.h" + +#ifndef YOG_SERVER_ONLY +#include "render/GameAnimations.h" +#endif // !YOG_SERVER_ONLY + +#include +#include +#include +#include + + +// Definitions of shared direction tables declared in MapInternal.h. +// All Map*.cpp TUs link against these single definitions. + +const int deltaOne[8][2]={ + { 0, -1}, + { 1, 0}, + { 0, 1}, + {-1, 0}, + {-1, -1}, + { 1, -1}, + { 1, 1}, + {-1, 1}}; + +const int tabClose[8][2]={ + {-1, -1}, + { 0, -1}, + { 1, -1}, + { 1, 0}, + { 1, 1}, + { 0, 1}, + {-1, 1}, + {-1, 0}}; + +const int tabFar[16][2]={ + {-2, -2}, + {-1, -2}, + { 0, -2}, + { 1, -2}, + { 2, -2}, + { 2, -1}, + { 2, 0}, + { 2, 1}, + { 2, 2}, + { 1, 2}, + { 0, 2}, + {-1, 2}, + {-2, 2}, + {-2, 1}, + {-2, 0}, + {-2, -1}}; + +Map::Map() +{ + game=NULL; + + arraysBuilt=false; + + aStarPoints = NULL; + for (int t=0; twDec=wDec; + this->hDec=hDec; + w=1<>Sector::SECTOR_SHIFT; + hSector=h>>Sector::SECTOR_SHIFT; + sizeSector=wSector*hSector; + + if(sectors) + delete[] sectors; + sectors=new Sector[sizeSector]; + + aStarPoints=new AStarAlgorithmPoint[w*h]; + + + immobileUnits = new Uint8[w*h]; + for (int i=0; igame=game; + assert(arraysBuilt); + assert(sectors); + for (int i=0; ianimations->resize(sizeSector); +#endif // !YOG_SERVER_ONLY +} + + diff --git a/src/Map.h b/src/map/Map.h similarity index 77% rename from src/Map.h rename to src/map/Map.h index 6a64e5b47..d2cb34cef 100644 --- a/src/Map.h +++ b/src/map/Map.h @@ -1,28 +1,11 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __MAP_H -#define __MAP_H +#pragma once #include +#include #include #include "Building.h" @@ -46,6 +29,14 @@ class MapGenerationDescriptor; class SessionGame; class MapHeader; +//! 2D grid offset returned by Map's 3x3-neighborhood "doesTouch" queries. +//! dx and dy are each in {-1, 0, +1}. +struct Offset +{ + int dx; + int dy; +}; + // a 1x1 piece of map struct Case { @@ -84,10 +75,27 @@ enum AreaType */ class Map { - static const bool verbose = false; public: //! Type of terrain (used for undermap) + // === Tile geometry (cross-slice) === + //! Bit-shift converting a tile index to its top-left pixel coordinate + //! (i.e. log2 of TILE_PX). Used by mapCaseToPixelCase and friends in + //! MapView.cpp / TypeSteps.cpp. + static constexpr int TILE_PIXEL_SHIFT = 5; + //! Side length of one map tile in screen pixels (1 << TILE_PIXEL_SHIFT). + static constexpr int TILE_PX = 32; + //! Half-tile in pixels — used when centring sprites / bullets on a tile. + static constexpr int HALF_TILE_PX = 16; + + //! Sentinel returned by Map::getTerrainType when the underlying terrain + //! sprite ID does not fall in any of the registered terrain ranges + //! (GRASS / SAND / WATER). Callers test for `< 0` / `== TERRAIN_TYPE_UNKNOWN`. + static constexpr int TERRAIN_TYPE_UNKNOWN = -1; + + //! "Infinity" / "unvisited" sentinel for the A* algorithm's Uint16 cost fields + //! (moveCost, totalCost). All real costs fit within 0..0xFFFE so 0xFFFF is safe. + static constexpr Uint16 ASTAR_COST_INFINITY = static_cast(-1); public: //! Map constructor @@ -96,7 +104,6 @@ class Map virtual ~Map(void); //! Reset map and free arrays void clear(); - void logAtClear(); //! Reset map size to width = 2^wDec and height=2^hDec, and fill background with terrainType void setSize(int wDec, int hDec, TerrainType terrainType=WATER); @@ -244,6 +251,15 @@ class Map void computeLocalGuardArea(int localTeamNo); //! Compute localClearAreaMap from cases array void computeLocalClearArea(int localTeamNo); + + //! Sentinel for "no local team yet" — used before GameGUI::adjustLocalTeam has run. + static constexpr Sint32 NO_LOCAL_TEAM = -1; + //! Register the team whose view is currently displayed. Sim code may consult + //! getLocalTeam() to decide whether to refresh localForbiddenMap / localGuardAreaMap / + //! localClearAreaMap caches. This identity is per-client display state — not in + //! checkSum() — and must be kept in sync with GameGUI's localTeamNo. + void setLocalTeam(Sint32 teamNo) { localTeamNo = teamNo; } + Sint32 getLocalTeam() const { return localTeamNo; } //! Return the case at a given position inline Case &getCase(int x, int y) @@ -280,7 +296,7 @@ class Map else if ((t>=256) && (t<256+16)) return WATER; else - return -1; + return TERRAIN_TYPE_UNKNOWN; } const Ressource& getRessource(int x, int y) const @@ -424,7 +440,25 @@ class Map //! Decrement ressource at position (x,y) if ressource type = ressourceType. Return true on success, false otherwise. void decRessource(int x, int y, int ressourceType); bool incRessource(int x, int y, int ressourceType, int variety); - + +private: + //! Per-tile predicate driver shared by isFree*/isHardSpace*. + //! Each flag toggles whether one occupancy/terrain test contributes to rejection. + struct TileChecks { + bool noRessource : 1; //!< reject if a ressource sits on the tile + bool noUnit : 1; //!< reject if a ground unit sits on the tile + bool waterBlocks : 1; //!< reject water tiles unless canSwim is true + bool requireGrass : 1; //!< reject any tile whose terrain isn't grass + bool checkForbidden : 1; //!< reject if the tile's forbidden mask intersects teamMask + }; + //! Returns true iff (x,y) passes every enabled check. A building whose gid + //! equals ignoreGid is treated as not present (used by the gid-tolerant + //! isFreeForBuilding/isHardSpaceForBuilding overloads); pass NOGBID to make + //! every occupant building reject. + bool checkTile(int x, int y, TileChecks c, bool canSwim, + Uint32 teamMask, Uint16 ignoreGid) const; + +public: //! Return true if unit can go to position (x,y) bool isFreeForGroundUnit(int x, int y, bool canSwim, Uint32 teamMask) const; bool isFreeForGroundUnitNoForbidden(int x, int y, bool canSwim) const; @@ -438,21 +472,19 @@ class Map bool isHardSpaceForBuilding(int x, int y, int w, int h) const; bool isHardSpaceForBuilding(int x, int y, int w, int h, Uint16 gid) const; - //! Return true if unit has contact with building gbid. If true, put contact direction in dx, dy - bool doesUnitTouchBuilding(Unit *unit, Uint16 gbid, int *dx, int *dy) const; - //! Return true if (x,y) has contact with building gbid. - bool doesPosTouchBuilding(int x, int y, Uint16 gbid) const; - //! Return true if (x,y) has contact with building gbid. If true, put contact direction in dx, dy - bool doesPosTouchBuilding(int x, int y, Uint16 gbid, int *dx, int *dy) const; - - //! Return true if unit has contact with ressource of any ressourceType. If true, put contact direction in dx, dy - bool doesUnitTouchRessource(Unit *unit, int *dx, int *dy) const; - //! Return true if unit has contact with ressource of type ressourceType. If true, put contact direction in dx, dy - bool doesUnitTouchRessource(Unit *unit, int ressourceType, int *dx, int *dy) const; - //! Return true if (x,y) has contact with ressource of type ressourceType. If true, put contact direction in dx, dy - bool doesPosTouchRessource(int x, int y, int ressourceType, int *dx, int *dy) const; - //! Return true if unit has contact with enemy. If true, put contact direction in dx, dy - bool doesUnitTouchEnemy(Unit *unit, int *dx, int *dy) const; + //! Return contact direction (dx, dy) if unit touches building gbid; nullopt otherwise. + std::optional doesUnitTouchBuilding(Unit *unit, Uint16 gbid) const; + //! Return contact direction (dx, dy) if (x, y) touches building gbid; nullopt otherwise. + std::optional doesPosTouchBuilding(int x, int y, Uint16 gbid) const; + + //! Return contact direction (dx, dy) if unit touches a ressource of any type; nullopt otherwise. + std::optional doesUnitTouchRessource(Unit *unit) const; + //! Return contact direction (dx, dy) if unit touches a ressource of the given type; nullopt otherwise. + std::optional doesUnitTouchRessource(Unit *unit, int ressourceType) const; + //! Return contact direction (dx, dy) if (x, y) touches a ressource of the given type; nullopt otherwise. + std::optional doesPosTouchRessource(int x, int y, int ressourceType) const; + //! Return contact direction (dx, dy) if unit touches an enemy; nullopt otherwise. + std::optional doesUnitTouchEnemy(Unit *unit) const; //! Sets this particular clearing area location as claimed void setClearingAreaClaimed(int x, int y, int teamNumber, int gid); @@ -484,8 +516,14 @@ class Map cases[coordToIndex(xi, yi)].building = gbid; } + //! Return the sector index of the sector containing tile (x,y). The + //! formula is: y is wrapped to the map height, divided by SECTOR_TILES + //! to get the sector row, then multiplied by sector-grid width and + //! offset by the wrapped/divided x. Used by Map::getSector and by + //! GameAnimations to bucket render effects per sector. + int getSectorIndex(int x, int y) const { return wSector*((y&hMask)>>Sector::SECTOR_SHIFT)+((x&wMask)>>Sector::SECTOR_SHIFT); } //! Return sector at (x,y). - Sector *getSector(int x, int y) { return &(sectors[wSector*((y&hMask)>>4)+((x&wMask)>>4)]); } + Sector *getSector(int x, int y) { return &(sectors[getSectorIndex(x, y)]); } //! Return a sector in the sector array. It is not clean because too high level Sector *getSector(int i) { assert(i>=0); assert(i void updateGlobalGradientSlow(Uint8 *gradient); - - template void updateGlobalGradientVersionSimple( - Uint8 *gradient, Tint *listedAddr, size_t listCountWrite, GradientType gradientType); - template void updateGlobalGradientVersionSimon(Uint8 *gradient, Tint *listedAddr, size_t listCountWrite); - template void updateGlobalGradientVersionKai(Uint8 *gradient, Tint *listedAddr, size_t listCountWrite); - template void updateGlobalGradient( - Uint8 *gradient, Tint *listedAddr, size_t listCountWrite, GradientType gradientType, bool canSwim); - //void updateGlobalGradientSmall(Uint8 *gradient); - //void updateGlobalGradientBig(Uint8 *gradient); - //void updateGlobalGradient(Uint8 *gradient); + // Chamfer distance transform on a pre-seeded gradient buffer. Caller + // fills the buffer (0 = obstacle, 1 = free, any cell >= 3 = source); + // chamfer sweeps it forward and backward until stable. Defined in + // MapGradientGlobal.cpp. + void updateGlobalGradient(Uint8 *gradient); void updateRessourcesGradient(int teamNumber, Uint8 ressourceType, bool canSwim); - template void updateRessourcesGradient(int teamNumber, Uint8 ressourceType, bool canSwim); - bool directionFromMinigrad(Uint8 miniGrad[25], int *dx, int *dy, const bool strict, bool verbose) const; - bool directionByMinigrad(Uint32 teamMask, bool canSwim, int x, int y, int *dx, int *dy, const Uint8 *gradient, bool strict, bool verbose) const; - bool directionByMinigrad(Uint32 teamMask, bool canSwim, int x, int y, int bx, int by, int *dx, int *dy, Uint8 localGradient[1024], bool strict, bool verbose) const; - bool pathfindRessource(int teamNumber, Uint8 ressourceType, bool canSwim, int x, int y, int *dx, int *dy, bool *stopWork, bool verbose); + bool directionFromMinigrad(Uint8 miniGrad[25], int *dx, int *dy, const bool strict) const; + bool directionByMinigrad(Uint32 teamMask, bool canSwim, int x, int y, int *dx, int *dy, const Uint8 *gradient, bool strict) const; + bool directionByMinigrad(Uint32 teamMask, bool canSwim, int x, int y, int bx, int by, int *dx, int *dy, Uint8 localGradient[1024], bool strict) const; + bool pathfindRessource(int teamNumber, Uint8 ressourceType, bool canSwim, int x, int y, int *dx, int *dy, bool *stopWork); #ifndef YOG_SERVER_ONLY - void pathfindRandom(Unit *unit, bool verbose); + void pathfindRandom(Unit *unit); #endif // !YOG_SERVER_ONLY void updateLocalGradient(Building *building, bool canSwim); //The 32*32 gradient void updateGlobalGradient(Building *building, bool canSwim); //The full-sized gradient - template void updateGlobalGradient(Building *building, bool canSwim); //!A special gradient for clearing flags. Returns false if there is nothing to clear. - bool updateLocalRessources(Building *building, bool canSwim); - void expandLocalGradient(Uint8 *gradient); + bool updateLocalRessources(Building *building, bool canSwim); + //! Probe a full-map gradient at (x, y) and its 8 neighbors; sets *dist = GRADIENT_AT_GOAL - g if reachable. + bool probeGlobalGradient(const Uint8 *gradient, int x, int y, int *dist) const; bool buildingAvailable(Building *building, bool canSwim, int x, int y, int *dist); //!requests the next step (dx, dy) to take to get to the building from (x,y) provided the unit canSwim. - bool pathfindBuilding(Building *building, bool canSwim, int x, int y, int *dx, int *dy, bool verbose); + bool pathfindBuilding(Building *building, bool canSwim, int x, int y, int *dx, int *dy); bool pathfindLocalRessource(Building *building, bool canSwim, int x, int y, int *dx, int *dy); // Used for all ressources mixed in clearing flags. //! Make local gradient dirty in the area. Wrap-safe on x,y void dirtyLocalGradient(int x, int y, int wl, int hl, int teamNumber); - bool pathfindForbidden(const Uint8 *optionGradient, int teamNumber, bool canSwim, int x, int y, int *dx, int *dy, bool verbose); - //! Find the best direction toward guard area, return true if one has been found, false otherwise - bool pathfindGuardArea(int teamNumber, bool canSwim, int x, int y, int *dx, int *dy); - //! Find the best direction toward clearing area, return true if one has been found, false otherwise - bool pathfindClearArea(int teamNumber, bool canSwim, int x, int y, int *dx, int *dy); + bool pathfindForbidden(const Uint8 *optionGradient, int teamNumber, bool canSwim, int x, int y, int *dx, int *dy); + enum class AreaKind { Guard, Clear }; + //! Find the best direction toward a guard or clear area; return true if one has been found. + bool pathfindArea(AreaKind kind, int teamNumber, bool canSwim, int x, int y, int *dx, int *dy); //! Update the forbidden gradient, void updateForbiddenGradient(int teamNumber, bool canSwim); - template void updateForbiddenGradient(int teamNumber, bool canSwim); void updateForbiddenGradient(int teamNumber); void updateForbiddenGradient(); //! Update the guard area gradient void updateGuardAreasGradient(int teamNumber, bool canSwim); - template void updateGuardAreasGradient(int teamNumber, bool canSwim); void updateGuardAreasGradient(int teamNumber); void updateGuardAreasGradient(); //! Update the clear area gradient void updateClearAreasGradient(int teamNumber, bool canSwim); - template void updateClearAreasGradient(int teamNumber, bool canSwim); void updateClearAreasGradient(int teamNumber); void updateClearAreasGradient(); @@ -630,90 +656,6 @@ class Map void makeDiscoveredAreasExplored(int teamNumber); void updateExploredArea(int teamNumber); -protected: - // computationals pathfinding statistics: - int ressourceAvailableCount[16][MAX_RESSOURCES]; - int ressourceAvailableCountSuccess[16][MAX_RESSOURCES]; - int ressourceAvailableCountFailure[16][MAX_RESSOURCES]; - - int pathToRessourceCountTot; - int pathToRessourceCountSuccess; - int pathToRessourceCountFailure; - - int localRessourcesUpdateCount; - - int pathfindLocalRessourceCount; - int pathfindLocalRessourceCountWait; - int pathfindLocalRessourceCountSuccessBase; - int pathfindLocalRessourceCountSuccessLocked; - int pathfindLocalRessourceCountSuccessUpdate; - int pathfindLocalRessourceCountSuccessUpdateLocked; - int pathfindLocalRessourceCountFailureUnusable; - int pathfindLocalRessourceCountFailureNone; - int pathfindLocalRessourceCountFailureBad; - - int pathToBuildingCountTot; - - int pathToBuildingCountClose; - int pathToBuildingCountCloseSuccessStand; - int pathToBuildingCountCloseSuccessBase; - int pathToBuildingCountCloseSuccessUpdated; - int pathToBuildingCountCloseFailureLocked; - int pathToBuildingCountCloseFailureEnd; - - int pathToBuildingCountIsFar; - int pathToBuildingCountFar; - int pathToBuildingCountFarIsNew; - int pathToBuildingCountFarOldSuccess; - int pathToBuildingCountFarOldFailureLocked; - int pathToBuildingCountFarOldFailureBad; - int pathToBuildingCountFarOldFailureRepeat; - int pathToBuildingCountFarOldFailureUnusable; - int pathToBuildingCountFarUpdateSuccess; - int pathToBuildingCountFarUpdateFailureLocked; - int pathToBuildingCountFarUpdateFailureVirtual; - int pathToBuildingCountFarUpdateFailureBad; - - int localBuildingGradientUpdate; - int localBuildingGradientUpdateLocked; - int globalBuildingGradientUpdate; - int globalBuildingGradientUpdateLocked; - - int buildingAvailableCountTot; - - int buildingAvailableCountClose; - int buildingAvailableCountCloseSuccessFast; - int buildingAvailableCountCloseSuccessAround; - int buildingAvailableCountCloseSuccessUpdate; - int buildingAvailableCountCloseSuccessUpdateAround; - int buildingAvailableCountCloseFailureLocked; - int buildingAvailableCountCloseFailureEnd; - - int buildingAvailableCountIsFar; - int buildingAvailableCountFar; - int buildingAvailableCountFarNew; - int buildingAvailableCountFarNewSuccessFast; - int buildingAvailableCountFarNewSuccessClosely; - int buildingAvailableCountFarNewFailureLocked; - int buildingAvailableCountFarNewFailureVirtual; - int buildingAvailableCountFarNewFailureEnd; - int buildingAvailableCountFarOld; - int buildingAvailableCountFarOldSuccessFast; - int buildingAvailableCountFarOldSuccessAround; - int buildingAvailableCountFarOldFailureLocked; - int buildingAvailableCountFarOldFailureEnd; - - int pathfindForbiddenCount; - int pathfindForbiddenCountSuccess; - int pathfindForbiddenCountFailure; - - //#define check_disorderable_gradient_error_probability - #ifdef check_disorderable_gradient_error_probability - // stats to check the probability of an error in the updateGlobalGradientVersionDisorderable gradient computation - int *listCountSizeStats[GT_SIZE]; - int listCountSizeStatsOver[GT_SIZE]; - #endif - public: Game *game; public: @@ -743,6 +685,9 @@ class Map Utilities::BitArray localGuardAreaMap; //! true = clear area Utilities::BitArray localClearAreaMap; + //! Team whose view is locally displayed. Mirror of GameGUI::localTeamNo, used by + //! sim code to decide whether to refresh the local*Map caches above. Not in checkSum. + Sint32 localTeamNo = NO_LOCAL_TEAM; ///This is the maximum fertility of any point on the map Uint16 fertilityMaximum; @@ -800,7 +745,7 @@ class Map ///This is a single point in the array used for A* algorithm struct AStarAlgorithmPoint { - AStarAlgorithmPoint() : x(-1), y(-1), dx(-1), dy(-1), moveCost(static_cast(-1)), totalCost(static_cast(-1)), isClosed(false) { } + AStarAlgorithmPoint() : x(-1), y(-1), dx(-1), dy(-1), moveCost(ASTAR_COST_INFINITY), totalCost(ASTAR_COST_INFINITY), isClosed(false) { } AStarAlgorithmPoint(Sint16 x, Sint16 y, Sint16 dx, Sint16 dy, Uint16 moveCost, Uint16 totalCost, bool isClosed) : x(x), y(y), dx(dx), dy(dy), moveCost(moveCost), totalCost(totalCost), isClosed(isClosed) {} //Pos x Sint16 x; @@ -854,9 +799,5 @@ class Map bool oldMakeIslandsMap(MapGenerationDescriptor &descriptor); void oldAddRessourcesIslandsMap(MapGenerationDescriptor &descriptor); -protected: - FILE *logFile; - Uint32 incRessourceLog[16]; }; -#endif diff --git a/src/map/MapInternal.h b/src/map/MapInternal.h new file mode 100644 index 000000000..3eafd8fc0 --- /dev/null +++ b/src/map/MapInternal.h @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +// Private shared definitions for the Map.cpp family of translation units. +// Not intended for inclusion outside Map*.cpp. + +#pragma once + +#include +#include +#include +#include + +#define UPDATE_MAX(max,value) { if (value>(max)) (max)=value; } + +// use deltaOne for first perpendicular direction +extern const int deltaOne[8][2]; +// use tabClose for original circular direction +extern const int tabClose[8][2]; +// use tabMiniFar for all miniGrad far points +extern const int tabFar[16][2]; + +// helper to fill vectors +template +inline void fill(std::vector& vec, const T& value) { + std::fill(vec.begin(), vec.end(), value); +} + +// Local working grid: a building's local gradient and the clearing-flag local-resources +// gradient both live in a 32x32 buffer centered on the building. Index = y << SHIFT | x. +constexpr int LOCAL_GRID_W = 32; +constexpr int LOCAL_GRID_SHIFT = 5; // 1 << SHIFT == W +constexpr int LOCAL_GRID_AREA = LOCAL_GRID_W * LOCAL_GRID_W; // 1024 +constexpr int LOCAL_GRID_CENTER = LOCAL_GRID_W / 2 - 1; // 15 +static_assert(1 << LOCAL_GRID_SHIFT == LOCAL_GRID_W); + +// Helper for updateLocalGradient and the local-gradient pathfinders. +inline int clip_0_31(int x) { return (x < 0) ? 0 : (x > LOCAL_GRID_W - 1) ? LOCAL_GRID_W - 1 : x; } + +// Gradient sentinel values. Gradients propagate from goal cells (set to GRADIENT_AT_GOAL) +// outward, decreasing by 1 per step. A unit at (x, y) walks toward whichever neighbor has +// the highest gradient value. +// GRADIENT_FORBIDDEN (0): obstacle / impassable — never enter. +// GRADIENT_UNREACHABLE (1): reachable cell with no path to any goal yet. +// GRADIENT_FORBIDDEN_BORDER (254): forbidden-zone interior cell that borders a free cell; +// used as a fade-in source for the forbidden gradient so +// the gradient tapers into the forbidden zone. +// GRADIENT_AT_GOAL (255): goal cell itself; distance to goal is GRADIENT_AT_GOAL - g. +constexpr std::uint8_t GRADIENT_FORBIDDEN = 0; +constexpr std::uint8_t GRADIENT_UNREACHABLE = 1; +constexpr std::uint8_t GRADIENT_FORBIDDEN_BORDER = 254; +constexpr std::uint8_t GRADIENT_AT_GOAL = 255; + +// Sentinel for Map::immobileUnits[]: byte stores the team number of the immobile +// unit on the tile, or IMMOBILE_UNIT_NONE if no immobile unit is present. +// Team::MAX_COUNT is well under 255, so the team-number range never collides. +constexpr std::uint8_t IMMOBILE_UNIT_NONE = 255; + +// Map::doesUnitTouchEnemy scoring sentinels. The "bestTime" is in 0..255 for any +// real candidate; 256 acts as a "no candidate yet" sentinel above the valid range. +// ENEMY_TOUCH_BEST_TIME_NONE (256): initial value / "no candidate". +// ENEMY_TOUCH_SCORE_SHOOTER (0) : highest priority — turret/shooter found. +// ENEMY_TOUCH_SCORE_BUILDING_FALLBACK(255): non-shooter enemy building fallback. +constexpr int ENEMY_TOUCH_BEST_TIME_NONE = 256; +constexpr int ENEMY_TOUCH_SCORE_SHOOTER = 0; +constexpr int ENEMY_TOUCH_SCORE_BUILDING_FALLBACK = 255; + +// exploredArea[team][] cell values. The byte counts down each tick (in MapStep); +// EXPLORED_FRESH is the max stamp written when a unit/building reveals a tile, +// EXPLORED_BY_BUILDING_MIN is the floor a stationary building keeps a tile at. +constexpr std::uint8_t EXPLORED_FRESH = 255; +constexpr std::uint8_t EXPLORED_BY_BUILDING_MIN = 2; + +// Initial Ressource::amount when a fresh resource is seeded onto a tile. +constexpr int RESSOURCE_INITIAL_AMOUNT = 1; + +// Corn growth probability denominator: corn grows on 1-in-CORN_GROWTH_DIVISOR +// random rolls. Comment in Map::growRessources says "Growth rate of corn is 1/3". +constexpr int CORN_GROWTH_DIVISOR = 3; + +// Chamfer-dilate a LOCAL_GRID_W * LOCAL_GRID_W gradient buffer in-place. Each free cell is +// raised to max(self, max(neighbor) - 1); 0 (obstacle) and 255 (source) are preserved. +// Used by both Map::updateLocalGradient and Map::updateLocalRessources. +void propagateLocalGradient32(std::uint8_t* gradient); + +// Spiral outward from (startX, startY) for `steps` cells in each of E, S, W, N (in order), +// returning true on the first non-zero gradient cell encountered. The grid stride is +// (1 << wDec) and x/y wrap modulo (wMask + 1) and (hMask + 1) — both must be powers of two. +// Used to test reachability of building footprints in both the toroidal full map and the +// 32x32 local grid (the local grid never wraps in practice, since spirals are short). +inline bool spiralFindNonZero(const std::uint8_t* gradient, int startX, int startY, int steps, + int wMask, int hMask, int wDec) +{ + int x = startX, y = startY; + static constexpr int dxs[4] = { 1, 0, -1, 0 }; + static constexpr int dys[4] = { 0, 1, 0, -1 }; + for (int ai = 0; ai < 4; ai++) { + for (int mi = 0; mi < steps; mi++) { + assert(x >= 0); + assert(y >= 0); + if (gradient[(y << wDec) | x] != 0) + return true; + x = (x + dxs[ai]) & wMask; + y = (y + dys[ai]) & hMask; + } + } + return false; +} + diff --git a/src/map/MapMisc.cpp b/src/map/MapMisc.cpp new file mode 100644 index 000000000..9ece7a379 --- /dev/null +++ b/src/map/MapMisc.cpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Unit.h" +#include "MapInternal.h" + +#include + +#include +#include +#include +#include + + +// Miscellaneous helpers: checkSum, warpDist*, isInLocalGradient, dumpGradient + +Uint32 Map::checkSum(bool heavy) +{ + Uint32 cs=size; + if (heavy) + { + for (const auto& c: cases) + { + cs+= + c.terrain + + c.building + + c.ressource.getUint32() + + c.groundUnit + + c.airUnit + + c.forbidden + + c.scriptAreas; + cs=rotl1(cs); + } + }; + return cs; +} + +Sint32 Map::warpDist1d(int p, int q, int l) +{ + Sint32 d=abs(p-q); + d%=l; + if (d>l/2) + d=l-d; + return d; +} + +Sint32 Map::warpDistSquare(int px, int py, int qx, int qy) +{ + Sint32 dx=warpDist1d(px,qx,w); + Sint32 dy=warpDist1d(py,qy,h); + return ((dx*dx)+(dy*dy)); +} + +Sint32 Map::warpDistMax(int px, int py, int qx, int qy) +{ + Sint32 dx=warpDist1d(px,qx,w); + Sint32 dy=warpDist1d(py,qy,h); + if (dx>dy) + return dx; + else + return dy; +} + +Sint32 Map::warpDistSum(int px, int py, int qx, int qy) +{ + Sint32 dx=warpDist1d(px,qx,w); + Sint32 dy=warpDist1d(py,qy,h); + return dx + dy; +} + + +bool Map::isInLocalGradient(int ux, int uy, int bx, int by) +{ + Sint32 dx=warpDist1d(ux,bx,w); + Sint32 dy=warpDist1d(uy,by,h); + if (dx>dy) + { + if (dxLOCAL_GRID_CENTER) + return false; + + return ((bx+LOCAL_GRID_CENTER) & wMask)==(ux & wMask); + } + else if (dxLOCAL_GRID_CENTER) + return false; + + return ((by+LOCAL_GRID_CENTER) & wMask)==(uy & wMask); + } + else + { + if (dxLOCAL_GRID_CENTER) + return false; + + return (((bx+LOCAL_GRID_CENTER) & wMask)==(ux & wMask)) && (((by+LOCAL_GRID_CENTER) & wMask)==(uy & wMask)); + } +} + +void Map::dumpGradient(Uint8 *gradient, const std::string filename) +{ + FILE *fp = globalContainer->fileManager->openFP(filename, "wb"); + if (fp) + { + fprintf(fp, "P5 %d %d 255\n", w, h); + fwrite(gradient, w, h, fp); + fclose(fp); + } +} + + diff --git a/src/map/MapQuery.cpp b/src/map/MapQuery.cpp new file mode 100644 index 000000000..0bcc70e3d --- /dev/null +++ b/src/map/MapQuery.cpp @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "BuildingType.h" +#include "Unit.h" +#include "MapInternal.h" + +#include +#include +#include +#include + + +// Spatial queries: isFree*, isHardSpace*, doesUnitTouch*, immobile units, clearing area + +// Shared per-tile predicate that drives every isFree*/isHardSpace* overload. +// Each TileChecks flag toggles one occupancy/terrain test; ignoreGid lets the +// gid-tolerant building overloads accept a specific occupant gid. +// C++: Map.cpp historically inlined the same five tests in eight near-duplicate +// bodies — see the original isFree*/isHardSpace* implementations. +bool Map::checkTile(int x, int y, TileChecks c, bool canSwim, + Uint32 teamMask, Uint16 ignoreGid) const +{ + if (c.noRessource && isRessource(x, y)) + return false; + Uint16 buid = getBuilding(x, y); + if (buid != NOGBID && buid != ignoreGid) + return false; + if (c.noUnit && getGroundUnit(x, y) != NOGUID) + return false; + if (c.waterBlocks && !canSwim && isWater(x, y)) + return false; + if (c.requireGrass && !isGrass(x, y)) + return false; + if (c.checkForbidden && (getForbidden(x, y) & teamMask)) + return false; + return true; +} + +bool Map::isFreeForGroundUnit(int x, int y, bool canSwim, Uint32 teamMask) const +{ + return checkTile(x, y, {true, true, true, false, true}, canSwim, teamMask, NOGBID); +} + +bool Map::isFreeForGroundUnitNoForbidden(int x, int y, bool canSwim) const +{ + return checkTile(x, y, {true, true, true, false, false}, canSwim, 0, NOGBID); +} + +bool Map::isFreeForBuilding(int x, int y) const +{ + return checkTile(x, y, {true, true, false, true, false}, false, 0, NOGBID); +} + +bool Map::isFreeForBuilding(int x, int y, int w, int h) const +{ + for (int yi=y; yi Map::doesUnitTouchBuilding(Unit *unit, Uint16 gbid) const +{ + int x=unit->posX; + int y=unit->posY; + + for (int tdx=-1; tdx<=1; tdx++) + for (int tdy=-1; tdy<=1; tdy++) + if (getBuilding(x+tdx, y+tdy)==gbid) + return Offset{tdx, tdy}; + return std::nullopt; +} + +std::optional Map::doesPosTouchBuilding(int x, int y, Uint16 gbid) const +{ + for (int tdx=-1; tdx<=1; tdx++) + for (int tdy=-1; tdy<=1; tdy++) + if (getBuilding(x+tdx, y+tdy)==gbid) + return Offset{tdx, tdy}; + return std::nullopt; +} + +std::optional Map::doesUnitTouchRessource(Unit *unit) const +{ + int x=unit->posX; + int y=unit->posY; + Uint32 me=unit->owner->me; + for (int tdx=-1; tdx<=1; tdx++) + for (int tdy=-1; tdy<=1; tdy++) + if (isRessource(x+tdx, y+tdy) && ((getForbidden(x+tdx, y+tdy)&me)==0)) + return Offset{tdx, tdy}; + return std::nullopt; +} + +std::optional Map::doesUnitTouchRessource(Unit *unit, int ressourceType) const +{ + int x=unit->posX; + int y=unit->posY; + Uint32 me=unit->owner->me; + for (int tdx=-1; tdx<=1; tdx++) + for (int tdy=-1; tdy<=1; tdy++) + if (isRessourceTakeable(x+tdx, y+tdy, ressourceType) && ((getForbidden(x+tdx, y+tdy)&me)==0)) + return Offset{tdx, tdy}; + return std::nullopt; +} + +std::optional Map::doesPosTouchRessource(int x, int y, int ressourceType) const +{ + for (int tdx=-1; tdx<=1; tdx++) + for (int tdy=-1; tdy<=1; tdy++) + if (isRessourceTakeable(x+tdx, y+tdy, ressourceType)) + return Offset{tdx, tdy}; + return std::nullopt; +} + +//! Picks a direction for a warrior to hit. Prefers turrets, then units, then +//! other buildings. Returns nullopt if no enemy is touching. +//! +//! Tie-break rules (preserve carefully — they affect replay determinism): +//! * Shooter (`shootingRange`): unconditional `bestTime=ENEMY_TOUCH_SCORE_SHOOTER` +//! write. So if the 3x3 contains two shooters, the later-iterated one wins. +//! * Non-shooter enemy building: `else if (bestTime>ENEMY_TOUCH_SCORE_BUILDING_FALLBACK)` +//! — only set if no candidate has been seen yet (initial bestTime is +//! ENEMY_TOUCH_BEST_TIME_NONE). Once any candidate exists, subsequent +//! non-shooters are ignored. +//! * Unit: strict `<`, so a unit with score 0 (e.g. delta=255 speed=2) does +//! NOT displace an already-found shooter at the same score. +std::optional Map::doesUnitTouchEnemy(Unit *unit) const +{ + int x=unit->posX; + int y=unit->posY; + int bestTime=ENEMY_TOUCH_BEST_TIME_NONE;//Shorter is better + int bdx=0, bdy=0; + + Uint32 enemies=unit->owner->enemies; + for (int tdx=-1; tdx<=1; tdx++) + for (int tdy=-1; tdy<=1; tdy++) + { + Sint32 gbid=getBuilding(x+tdx, y+tdy); + if (gbid!=NOGBID) + { + int otherTeam=Building::GIDtoTeam(gbid); + Uint32 otherTeamMask=1<teams[otherTeam]); + int otherID=Building::GIDtoID(gbid); + Building *b=game->teams[otherTeam]->myBuildings[otherID]; + if (!b->type->defaultUnitStayRange) + { + if (b->type->shootingRange) + { + // Unconditional write — later shooter wins ties. + bdx=tdx; + bdy=tdy; + bestTime=ENEMY_TOUCH_SCORE_SHOOTER; + } + else if (bestTime>ENEMY_TOUCH_SCORE_BUILDING_FALLBACK) + { + // Only fall back to a non-shooting enemy building + // when no other candidate has been seen yet. + bdx=tdx; + bdy=tdy; + bestTime=ENEMY_TOUCH_SCORE_BUILDING_FALLBACK; + } + } + } + } + Sint32 guid=getGroundUnit(x+tdx, y+tdy); + if (guid!=NOGUID) + { + int otherTeam=Unit::GIDtoTeam(guid); + Uint32 otherTeamMask=1<teams[otherTeam]); + int otherID=Unit::GIDtoID(guid); + Unit *otherUnit=game->teams[otherTeam]->myUnits[otherID]; + if ((unit->owner->sharedVisionExchange & otherTeamMask)==0) + { + int time=(256-otherUnit->delta)/otherUnit->speed; + // Strict `<` — a unit never displaces a tied candidate. + if (time +#include +#include +#include + + +// Resource grid mutations + ressource availability + points/area names + +void Map::decRessource(int x, int y) +{ + Ressource &r = getCase(x, y).ressource; + + if (r.type == NO_RES_TYPE || r.amount == 0) + return; + + const RessourceType *fulltype = globalContainer->ressourcesTypes.get(r.type); + + if (!fulltype->shrinkable) + return; + if (fulltype->eternal) + { + if (r.amount > 0) + r.amount--; + } + else + { + if (!fulltype->granular || r.amount<=1) + r.clear(); + else + r.amount--; + } +} + +void Map::decRessource(int x, int y, int ressourceType) +{ + if (isRessourceTakeable(x, y, ressourceType)) + decRessource(x, y); +} + +bool Map::incRessource(int x, int y, int ressourceType, int variety) +{ + Ressource &r = getCase(x, y).ressource; + const RessourceType *fulltype; + if (r.type == NO_RES_TYPE) + { + if (getBuilding(x, y) != NOGBID) + return false; + if (getGroundUnit(x, y) != NOGUID) + return false; + + fulltype = globalContainer->ressourcesTypes.get(ressourceType); + if (getTerrainType(x, y) == fulltype->terrain) + { + r.type = ressourceType; + r.variety = variety; + r.amount = RESSOURCE_INITIAL_AMOUNT; + r.animation = 0; + return true; + } + else + { + return false; + } + } + else + { + fulltype = globalContainer->ressourcesTypes.get(r.type); + } + + if (r.type != ressourceType) + return false; + if (!fulltype->shrinkable) + return false; + if (r.amount < fulltype->sizesCount) + { + r.amount++; + return true; + } + else + { + r.amount--; + } + return false; +} + + +void Map::setNoRessource(int x, int y, int l) +{ + assert(l>=0); + assert(l>1); dx>1)+1; dx++) + for (int dy=y-(l>>1); dy>1)+1; dy++) + cases[coordToIndex(dx, dy)].ressource.clear(); +} + +void Map::setRessource(int x, int y, int type, int l) +{ + assert(l>=0); + assert(l>1); dx>1)+1; dx++) + for (int dy=y-(l>>1); dy>1)+1; dy++) + if (isRessourceAllowed(dx, dy, type)) + { + Ressource& rp=cases[coordToIndex(dx, dy)].ressource; + rp.type=type; + const RessourceType *rt=globalContainer->ressourcesTypes.get(type); + rp.variety=syncRand()%rt->varietiesCount; + assert(rt->sizesCount>1); + rp.amount=RESSOURCE_INITIAL_AMOUNT+syncRand()%(rt->sizesCount-1); + rp.animation=0; + } +} + +bool Map::isRessourceAllowed(int x, int y, int type) +{ + return (getBuilding(x, y) == NOGBID) && (getGroundUnit(x, y) == NOGUID) && (getTerrainType(x, y)==globalContainer->ressourcesTypes.get(type)->terrain); +} + +bool Map::isPointSet(int n, int x, int y) const +{ + return getCase(x, y).scriptAreas & 1<GRADIENT_UNREACHABLE; //Because 0==obstacle, 1==no obstacle, but you don't know if there is anything around. +} + +bool Map::ressourceAvailable(int teamNumber, int ressourceType, bool canSwim, int x, int y, int *dist) const +{ + Uint8 g = getGradient(teamNumber, ressourceType, canSwim, x, y); + if (g>GRADIENT_UNREACHABLE) + { + *dist = GRADIENT_AT_GOAL-g; + return true; + } + else + return false; +} + +bool Map::ressourceAvailableUpdate(int teamNumber, int ressourceType, bool canSwim, int x, int y, Sint32 *targetX, Sint32 *targetY, int *dist) +{ + // distance and availability + bool result; + if (dist) + result = ressourceAvailable(teamNumber, ressourceType, canSwim, x, y, dist); + else + result = ressourceAvailable(teamNumber, ressourceType, canSwim, x, y); + + // target position + Uint8 *gradient = ressourcesGradient[teamNumber][ressourceType][canSwim]; + getGlobalGradientDestination(gradient, x, y, targetX, targetY); + + return result; +} + +bool Map::getGlobalGradientDestination(Uint8 *gradient, int x, int y, Sint32 *targetX, Sint32 *targetY) const +{ + // we start from our current position + int vx = x & wMask; + int vy = y & hMask; + // max is initialized to gradient value of current position + Uint8 max = gradient[coordToIndex(vx, vy)]; + + bool result = false; + // for up to 255 steps, we follow gradient + for (int count=0; count<255; count++) + { + bool found = false; + int vddx = 0; + int vddy = 0; + + // search all directions + for (int d=0; d<8; d++) + { + int ddx = deltaOne[d][0]; + int ddy = deltaOne[d][1]; + Uint8 g = gradient[coordToIndex(vx + ddx, vy + ddy)]; + if (g>max) + { + max = g; + vddx = ddx; + vddy = ddy; + found = true; + } + } + + // change position + vx = (vx+vddx) & wMask; + vy = (vy+vddy) & hMask; + + // if we have reached destination break + if (max == GRADIENT_AT_GOAL) + { + result = true; + break; + } + // if we haven't found a suitable direction, we break, but we do not have exact destination + else if (!found) + break; + } + + // return best destination and wether it is exact or not + *targetX = vx; + *targetY = vy; + return result; +} + + +/* +This was the old way. I was much more complex but reliable with partially broken gradients. Let's keep it for now in case of such type of gradient reappears +bool Map::ressourceAvailable(int teamNumber, int ressourceType, bool canSwim, int x, int y, Sint32 *targetX, Sint32 *targetY, int *dist) + +commented out version last seen in revision 0ea2652945a0 + +*/ + + diff --git a/src/map/MapStep.cpp b/src/map/MapStep.cpp new file mode 100644 index 000000000..5dd2c0f39 --- /dev/null +++ b/src/map/MapStep.cpp @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Unit.h" +#include "MapInternal.h" +#ifndef YOG_SERVER_ONLY +#include "render/GameAnimations.h" +#endif // !YOG_SERVER_ONLY + +#include +#include +#include +#include + + +// growRessources, syncStep, fog of war, discovery, explored area + +void Map::growRessources(void) +{ + int dy=(syncRand()&0x3); + for (int y=dy; yressourcesTypes.get(r.type)->expendable) + { + // we extand ressource: + int dx, dy; + Unit::dxDyFromDirection((syncRand()&7), &dx, &dy); + int nx=x+dx; + int ny=y+dy; + if(canRessourcesGrow(nx, ny)) + incRessource(nx, ny, r.type, r.variety); + } + } + } + } + } +} + + +#ifndef YOG_SERVER_ONLY +void Map::syncStep(Uint32 stepCounter) +{ + growRessources(); + for (int i=0; ianimations->step(); + + if (stepCounter & 1) + { + int team = (stepCounter >> 1) & 31; + if (team < game->mapHeader.getNumberOfTeams()) + updateExploredArea(team); + } + + // We only update one gradient per step: + bool updated=false; + while (!updated) + { + int numberOfTeam=game->mapHeader.getNumberOfTeams(); + for (int t=0; t=0); + assert(id=0); + assert(teammyBuildings[id]->seenByMask|=sharedVision; + } +} + +void Map::setMapBuildingsDiscovered(int x, int y, int w, int h, Uint32 sharedVision, Team *teams[Team::MAX_COUNT]) +{ + for (int dx=x; dxteams[teamNumber]); + assert(game->teams[teamNumber]->me); + assert(exploredArea[teamNumber]); + for (int x = 0; x < getW(); x++) { + for (int y = 0; y < getH(); y++) { + if (isMapDiscovered (x, y, game->teams[teamNumber]->me)) { + setMapExploredByUnit (x, y, 1, 1, teamNumber); }}} +} + +void Map::updateExploredArea(int teamNumber) +{ + for (size_t i = 0; i < size; i++) + if (exploredArea[teamNumber][i] > 0) + exploredArea[teamNumber][i]--; +} + + diff --git a/src/map/MapTerrain.cpp b/src/map/MapTerrain.cpp new file mode 100644 index 000000000..2bdbd1004 --- /dev/null +++ b/src/map/MapTerrain.cpp @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Unit.h" +#include "MapInternal.h" + +#include +#include +#include +#include + + +// Terrain editing & rendering: setUMatPos, regenerateMap, lookup + +void Map::setUMatPos(int x, int y, TerrainType t, int l) +{ + for (int dx=x-(l>>1); dx>1)+1; dx++) + for (int dy=y-(l>>1); dy>1)+1; dy++) + { + if (t==GRASS) + { + if (getUMTerrain(dx,dy-1)==WATER) + { +// setNoRessource(dx, dy-1, 1); + setUMTerrain(dx,dy-1,SAND); + } + if (getUMTerrain(dx,dy+1)==WATER) + { +// setNoRessource(dx, dy+1, 1); + setUMTerrain(dx,dy+1,SAND); + } + + if (getUMTerrain(dx-1,dy)==WATER) + { +// setNoRessource(dx-1, dy, 1); + setUMTerrain(dx-1,dy,SAND); + } + if (getUMTerrain(dx+1,dy)==WATER) + { +// setNoRessource(dx+1, dy, 1); + setUMTerrain(dx+1,dy,SAND); + } + + if (getUMTerrain(dx-1,dy-1)==WATER) + { +// setNoRessource(dx-1, dy-1, 1); + setUMTerrain(dx-1,dy-1,SAND); + } + if (getUMTerrain(dx+1,dy-1)==WATER) + { +// setNoRessource(dx+1, dy-1, 1); + setUMTerrain(dx+1,dy-1,SAND); + } + + if (getUMTerrain(dx+1,dy+1)==WATER) + { +// setNoRessource(dx+1, dy+1, 1); + setUMTerrain(dx+1,dy+1,SAND); + } + if (getUMTerrain(dx-1,dy+1)==WATER) + { +// setNoRessource(dx-1, dy+1, 1); + setUMTerrain(dx-1,dy+1,SAND); + } + } + else if (t==WATER) + { + if (getUMTerrain(dx,dy-1)==GRASS) + { +// setNoRessource(dx, dy-1, 1); + setUMTerrain(dx,dy-1,SAND); + } + if (getUMTerrain(dx,dy+1)==GRASS) + { +// setNoRessource(dx, dy+1, 1); + setUMTerrain(dx,dy+1,SAND); + } + + if (getUMTerrain(dx-1,dy)==GRASS) + { +// setNoRessource(dx-1, dy, 1); + setUMTerrain(dx-1,dy,SAND); + } + if (getUMTerrain(dx+1,dy)==GRASS) + { +// setNoRessource(dx+1, dy, 1); + setUMTerrain(dx+1,dy,SAND); + } + + if (getUMTerrain(dx-1,dy-1)==GRASS) + { +// setNoRessource(dx-1, dy-1, 1); + setUMTerrain(dx-1,dy-1,SAND); + } + if (getUMTerrain(dx+1,dy-1)==GRASS) + { +// setNoRessource(dx+1, dy-1, 1); + setUMTerrain(dx+1,dy-1,SAND); + } + + if (getUMTerrain(dx+1,dy+1)==GRASS) + { +// setNoRessource(dx+1, dy+1, 1); + setUMTerrain(dx+1,dy+1,SAND); + } + if (getUMTerrain(dx-1,dy+1)==GRASS) + { +// setNoRessource(dx-1, dy+1, 1); + setUMTerrain(dx-1,dy+1,SAND); + } + } + setUMTerrain(dx,dy,t); + } + if (t==SAND) + regenerateMap(x-(l>>1)-1,y-(l>>1)-1,l+1,l+1); + else + regenerateMap(x-(l>>1)-2,y-(l>>1)-2,l+3,l+3); +} + + +void Map::regenerateMap(int x, int y, int w, int h) +{ + for (int dx=x; dx or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault - Copyright (C) 2006 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __GLOB2EDIT_H -#define __GLOB2EDIT_H +#pragma once #include "Brush.h" #include "GameGUILoadSave.h" @@ -29,12 +11,15 @@ #include "KeyboardManager.h" #include #include "MapEditDialog.h" -#include "Minimap.h" +#include "render/Minimap.h" #include "OverlayAreas.h" #include "ScriptEditorScreen.h" #include #include +#define RIGHT_MENU_WIDTH 160 +#define RIGHT_MENU_OFFSET (160-128)/2 + ///A generic rectangle structure used for a variety of purposes, but mainly for the convience of the widget system struct widgetRectangle { @@ -774,8 +759,3 @@ class MapEdit void handleNoRessourceGrowthClick(int mx, int my); }; - - - - -#endif diff --git a/src/map/edit/MapEditAction.cpp b/src/map/edit/MapEditAction.cpp new file mode 100644 index 000000000..3a1dac8af --- /dev/null +++ b/src/map/edit/MapEditAction.cpp @@ -0,0 +1,1095 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +void MapEdit::performAction(const std::string& action, int relMouseX, int relMouseY) +{ +// std::cout<setUnselected(); + areasButton->setUnselected(); + noRessourceGrowthButton->setUnselected(); + isDraggingZone=false; + isDraggingTerrain=false; + isDraggingDelete=false; + isDraggingArea=false; + isDraggingNoRessourceGrowthArea=false; + if(panelMode==UnitEditor) + performAction("switch to building view"); + } + else if(action=="change menu") + { + if(panelMode==AddBuildings) + performAction("switch to flag view"); + else if(panelMode==AddFlagsAndZones) + performAction("switch to terrain view"); + else if(panelMode==Terrain) + performAction("switch to teams view"); + else if(panelMode==Teams) + performAction("switch to building view"); + else + performAction("switch to building view"); + } + else if(action=="minimap drag start") + { + isDraggingMinimap=true; + minimapMouseToPos(mouseX-globalContainer->gfx->getW()+RIGHT_MENU_WIDTH-RIGHT_MENU_OFFSET, mouseY, &viewportX, &viewportY, true); + } + else if(action=="minimap drag motion") + { + minimapMouseToPos(mouseX-globalContainer->gfx->getW()+RIGHT_MENU_WIDTH-RIGHT_MENU_OFFSET, mouseY, &viewportX, &viewportY, true); + } + else if(action=="minimap drag stop") + { + isDraggingMinimap=false; + } + else if(action=="place building") + { + int typeNum=globalContainer->buildingsTypes.getTypeNum(selectionName, buildingLevel, false); + if(!isUpgradable(IntBuildingType::shortNumberFromType(selectionName))) + typeNum = globalContainer->buildingsTypes.getTypeNum(selectionName, 0, false); + BuildingType *bt = globalContainer->buildingsTypes.get(typeNum); + int tempX, tempY, x, y; + game.map.cursorToBuildingPos(mouseX, mouseY, bt->width, bt->height, &tempX, &tempY, viewportX, viewportY); + + if (game.checkRoomForBuilding(tempX, tempY, bt, &x, &y, team, false)) + { + if(bt->maxUnitWorking) + game.addBuilding(x, y, typeNum, team, 1, 0); + else + game.addBuilding(x, y, typeNum, team, 0, 0); + if (selectionName=="swarm") + { + if (game.teams[team]->startPosSet<3) + { + game.teams[team]->startPosX=tempX; + game.teams[team]->startPosY=tempY; + game.teams[team]->startPosSet=3; + } + } + else + { + if (game.teams[team]->startPosSet<2) + { + game.teams[team]->startPosX=tempX; + game.teams[team]->startPosY=tempY; + game.teams[team]->startPosSet=2; + } + } + game.regenerateDiscoveryMap(); + hasMapBeenModified = true; + } + } + else if(action=="switch to building level 1") + { + buildingLevel=0; + } + else if(action=="switch to building level 2") + { + buildingLevel=1; + } + else if(action=="switch to building level 3") + { + buildingLevel=2; + } + else if(action=="open menu screen") + { + performAction("unselect"); + performAction("scroll horizontal stop"); + performAction("scroll vertical stop"); + menuScreen=new MapEditMenuScreen; + showingMenuScreen=true; + } + else if(action=="close menu screen") + { + delete menuScreen; + menuScreen=NULL; + showingMenuScreen=false; + } + else if(action=="open load screen") + { + performAction("unselect"); + performAction("scroll horizontal stop"); + performAction("scroll vertical stop"); + loadSaveScreen=new LoadSaveScreen("maps", "map", true, false, game.mapHeader.getMapName().c_str(), glob2FilenameToName, glob2NameToFilename); + showingLoad=true; + } + else if(action=="close load screen") + { + delete loadSaveScreen; + showingLoad=false; + loadSaveScreen=NULL; + } + else if(action=="open save screen") + { + performAction("unselect"); + performAction("scroll horizontal stop"); + performAction("scroll vertical stop"); + loadSaveScreen=new LoadSaveScreen("maps", "map", false, false, game.mapHeader.getMapName().c_str(), glob2FilenameToName, glob2NameToFilename); + showingSave=true; + } + else if(action=="close save screen") + { + delete loadSaveScreen; + showingSave=false; + loadSaveScreen=NULL; + } + else if(action=="open scenario editor") + { + performAction("unselect"); + performAction("scroll horizontal stop"); + performAction("scroll vertical stop"); + scriptEditor=new ScriptEditorScreen(&game); + showingScriptEditor=true; + hasMapBeenModified=true; + } + else if(action=="close scenario editor") + { + delete scriptEditor; + showingScriptEditor=false; + scriptEditor=NULL; + } + else if(action=="open teams editor") + { + performAction("unselect"); + performAction("scroll horizontal stop"); + performAction("scroll vertical stop"); + + for (int i=0; igetIndex())); + isShowingAreaName=true; + } + else if(action=="close area name") + { + game.map.setAreaName(areaNumber->getIndex(), areaName->getText()); + performAction("update script area number"); + delete areaName; + isShowingAreaName=false; + areaName=NULL; + } + else if(action=="select forbidden zone") + { + performAction("unselect"); + brushType = ForbiddenBrush; + selectionMode=PlaceZone; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select clearing zone") + { + performAction("unselect"); + brushType = ClearAreaBrush; + selectionMode=PlaceZone; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select guard zone") + { + performAction("unselect"); + brushType = GuardAreaBrush; + selectionMode=PlaceZone; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="handle zone click") + { + if(brushType==NoBrush) + { + performAction("unselect"); + performAction("select forbidden zone"); + } + brush.handleClick(relMouseX, relMouseY); + } + else if(action=="zone drag start") + { + isDraggingZone=true; + handleBrushClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="zone drag motion") + { + handleBrushClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="zone drag end") + { + isDraggingZone=false; + lastPlacementX=-1; + lastPlacementY=-1; + firstPlacementX=-1; + firstPlacementY=-1; + } + else if(action=="select grass") + { + performAction("unselect"); + terrainType=TerrainSelector::Grass; + selectionMode=PlaceTerrain; + + brush.defaultSelection(); + brush.setAddRemoveEnabledState(false); + } + else if(action=="select sand") + { + performAction("unselect"); + terrainType=TerrainSelector::Sand; + selectionMode=PlaceTerrain; + + brush.defaultSelection(); + brush.setAddRemoveEnabledState(false); + } + else if(action=="select water") + { + performAction("unselect"); + terrainType=TerrainSelector::Water; + selectionMode=PlaceTerrain; + + brush.defaultSelection(); + brush.setAddRemoveEnabledState(false); + } + else if(action=="select wheat") + { + performAction("unselect"); + terrainType=TerrainSelector::Wheat; + selectionMode=PlaceTerrain; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select trees") + { + performAction("unselect"); + terrainType=TerrainSelector::Trees; + selectionMode=PlaceTerrain; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select stone") + { + performAction("unselect"); + terrainType=TerrainSelector::Stone; + selectionMode=PlaceTerrain; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select algae") + { + performAction("unselect"); + terrainType=TerrainSelector::Algae; + selectionMode=PlaceTerrain; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select papyrus") + { + performAction("unselect"); + terrainType=TerrainSelector::Papyrus; + selectionMode=PlaceTerrain; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select cherry tree") + { + performAction("unselect"); + terrainType=TerrainSelector::CherryTree; + selectionMode=PlaceTerrain; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select orange tree") + { + performAction("unselect"); + terrainType=TerrainSelector::OrangeTree; + selectionMode=PlaceTerrain; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select prune tree") + { + performAction("unselect"); + terrainType=TerrainSelector::PruneTree; + selectionMode=PlaceTerrain; + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="select delete objects") + { + performAction("unselect"); + selectionMode=RemoveObject; + deleteButton->setSelected(); + + brush.defaultSelection(); + brush.setAddRemoveEnabledState(false); + } + else if(action=="select no ressources growth") + { + performAction("unselect"); + selectionMode=ChangeNoRessourceGrowthAreas; + noRessourceGrowthButton->setSelected(); + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="handle terrain click") + { + if(terrainType==TerrainSelector::NoTerrain && selectionMode!=RemoveObject && selectionMode!=ChangeAreas && selectionMode!=ChangeNoRessourceGrowthAreas) + performAction("select grass"); + brush.handleClick(relMouseX, relMouseY); + } + else if(action=="terrain drag start") + { + isDraggingTerrain=true; + handleTerrainClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="terrain drag motion") + { + handleTerrainClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="terrain drag end") + { + isDraggingTerrain=false; + lastPlacementX=-1; + lastPlacementY=-1; + firstPlacementX=-1; + firstPlacementY=-1; + } + else if(action=="delete drag start") + { + isDraggingDelete=true; + handleDeleteClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="delete drag motion") + { + handleDeleteClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="delete drag end") + { + isDraggingDelete=false; + lastPlacementX=-1; + lastPlacementY=-1; + firstPlacementX=-1; + firstPlacementY=-1; + } + else if(action=="update script area number") + { + areaNameLabel->setLabel(game.map.getAreaName(areaNumber->getIndex())); + hasMapBeenModified = true; + } + else if(action=="select change areas") + { + performAction("unselect"); + selectionMode=ChangeAreas; + areasButton->setSelected(); + if (brush.getType() == BrushTool::MODE_NONE) + brush.defaultSelection(); + brush.setAddRemoveEnabledState(true); + } + else if(action=="area drag start") + { + isDraggingArea=true; + handleAreaClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="area drag motion") + { + handleAreaClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="area drag end") + { + isDraggingArea=false; + lastPlacementX=-1; + lastPlacementY=-1; + firstPlacementX=-1; + firstPlacementY=-1; + } + else if(action=="no ressource growth area drag start") + { + isDraggingNoRessourceGrowthArea=true; + handleNoRessourceGrowthClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="no ressource growth area drag motion") + { + handleNoRessourceGrowthClick(mouseX, mouseY); + hasMapBeenModified = true; + } + else if(action=="no ressource growth area drag end") + { + isDraggingNoRessourceGrowthArea=false; + lastPlacementX=-1; + lastPlacementY=-1; + firstPlacementX=-1; + firstPlacementY=-1; + } + else if(action=="add team") + { + if(game.mapHeader.getNumberOfTeams() < 12) + { + game.addTeam(); + regenerateGameHeader(); + } + hasMapBeenModified = true; + } + else if(action=="remove team") + { + if(game.mapHeader.getNumberOfTeams() > 1) + { + if(team==game.mapHeader.getNumberOfTeams()-1) + team-=1; + game.removeTeam(); + regenerateGameHeader(); + } + hasMapBeenModified = true; + } + else if(action=="select active team") + { + int n=relMouseX/16 + (relMouseY/16)*6; + if(game.teams[n]) + { + team=n; + game.map.computeLocalForbidden(team); + game.map.computeLocalClearArea(team); + game.map.computeLocalGuardArea(team); + } + } + else if(action=="select worker") + { + performAction("unselect"); + placingUnit=Worker; + selectionMode=PlaceUnit; + } + else if(action=="select warrior") + { + performAction("unselect"); + placingUnit=Warrior; + selectionMode=PlaceUnit; + } + else if(action=="select explorer") + { + performAction("unselect"); + placingUnit=Explorer; + selectionMode=PlaceUnit; + } + else if(action=="select unit level 1") + { + placingUnitLevel=0; + } + else if(action=="select unit level 2") + { + placingUnitLevel=1; + } + else if(action=="select unit level 3") + { + placingUnitLevel=2; + } + else if(action=="select unit level 4") + { + placingUnitLevel=3; + } + else if(action=="place unit") + { + int type=0; + if(placingUnit==Worker) + type=WORKER; + else if(placingUnit==Warrior) + type=WARRIOR; + else if(placingUnit==Explorer) + type=EXPLORER; + int level=placingUnitLevel; + + int x; + int y; + game.map.displayToMapCaseAligned(mouseX, mouseY, &x, &y, viewportX, viewportY); + + Unit *unit=game.addUnit(x, y, team, type, level, rand()%256, 0, 0); + if (unit) + { + if (game.teams[team]->startPosSet<1) + { + game.teams[team]->startPosX=viewportX; + game.teams[team]->startPosY=viewportY; + game.teams[team]->startPosSet=1; + } + game.regenerateDiscoveryMap(); + hasMapBeenModified = true; + } + } + else if(action=="select map unit") + { + int x; + int y; + int gid=NOGUID; + game.map.displayToMapCaseAligned(mouseX, mouseY, &x, &y, viewportX, viewportY); + if(game.map.getAirUnit(x, y)!=NOGUID) + { + gid=game.map.getAirUnit(x, y); + } + else if(game.map.getGroundUnit(x, y)!=NOGUID) + { + gid=game.map.getGroundUnit(x, y); + } + if(gid!=NOGUID) + { + performAction("unselect"); + selectedUnitGID=gid; + game.selectedUnit=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; + selectionMode=EditingUnit; + panelMode=UnitEditor; + unitInfoTitle->setUnit(game.selectedUnit); + unitPicture->setUnit(game.selectedUnit); + unitHPLabel->setValues(&game.selectedUnit->hp, &game.selectedUnit->performance[HP]); + unitHPScrollBox ->setValues(&game.selectedUnit->hp, &game.selectedUnit->performance[HP]); + unitWalkLevelLabel->setValues(&game.selectedUnit->level[WALK]); + unitWalkLevelScrollBox->setValues(&game.selectedUnit->level[WALK]); + unitSwimLevelLabel->setValues(&game.selectedUnit->level[SWIM]); + unitSwimLevelScrollBox->setValues(&game.selectedUnit->level[SWIM]); + unitHarvestLevelLabel->setValues(&game.selectedUnit->level[HARVEST]); + unitHarvestLevelScrollBox->setValues(&game.selectedUnit->level[HARVEST]); + unitBuildLevelLabel->setValues(&game.selectedUnit->level[BUILD]); + unitBuildLevelScrollBox->setValues(&game.selectedUnit->level[BUILD]); + unitAttackSpeedLevelLabel->setValues(&game.selectedUnit->level[ATTACK_SPEED]); + unitAttackSpeedLevelScrollBox->setValues(&game.selectedUnit->level[ATTACK_SPEED]); + unitAttackStrengthLevelLabel->setValues(&game.selectedUnit->level[ATTACK_STRENGTH]); + unitAttackStrengthLevelScrollBox->setValues(&game.selectedUnit->level[ATTACK_STRENGTH]); + unitMagicGroundAttackLevelLabel->setValues(&game.selectedUnit->level[MAGIC_ATTACK_GROUND]); + unitMagicGroundAttackLevelScrollBox->setValues(&game.selectedUnit->level[MAGIC_ATTACK_GROUND]); + enableOnlyGroup("unit editor"); + if(!game.selectedUnit->canLearn[WALK]) + { + unitWalkLevelLabel->disable(); + unitWalkLevelScrollBox->disable(); + } + if(!game.selectedUnit->canLearn[SWIM]) + { + unitSwimLevelLabel->disable(); + unitSwimLevelScrollBox->disable(); + } + if(!game.selectedUnit->canLearn[HARVEST]) + { + unitHarvestLevelLabel->disable(); + unitHarvestLevelScrollBox->disable(); + } + if(!game.selectedUnit->canLearn[BUILD]) + { + unitBuildLevelLabel->disable(); + unitBuildLevelScrollBox->disable(); + } + if(!game.selectedUnit->canLearn[ATTACK_SPEED]) + { + unitAttackSpeedLevelLabel->disable(); + unitAttackSpeedLevelScrollBox->disable(); + } + if(!game.selectedUnit->canLearn[ATTACK_STRENGTH]) + { + unitAttackStrengthLevelLabel->disable(); + unitAttackStrengthLevelScrollBox->disable(); + } + if(!game.selectedUnit->canLearn[MAGIC_ATTACK_GROUND]) + { + unitMagicGroundAttackLevelLabel->disable(); + unitMagicGroundAttackLevelScrollBox->disable(); + } + } + } + else if(action=="update unit walk level") + { + Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; + UnitType *ut = u->race->getUnitType(u->typeNum, u->level[WALK]); + u->performance[WALK] = ut->performance[WALK]; + hasMapBeenModified = true; + } + else if(action=="update unit swim level") + { + Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; + UnitType *ut = u->race->getUnitType(u->typeNum, u->level[SWIM]); + u->performance[SWIM] = ut->performance[SWIM]; + hasMapBeenModified = true; + } + else if(action=="update unit harvest level") + { + Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; + UnitType *ut = u->race->getUnitType(u->typeNum, u->level[HARVEST]); + u->performance[HARVEST] = ut->performance[HARVEST]; + hasMapBeenModified = true; + } + else if(action=="update unit build level") + { + Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; + UnitType *ut = u->race->getUnitType(u->typeNum, u->level[BUILD]); + u->performance[BUILD] = ut->performance[BUILD]; + hasMapBeenModified = true; + } + else if(action=="update unit attack speed level") + { + Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; + UnitType *ut = u->race->getUnitType(u->typeNum, u->level[ATTACK_SPEED]); + u->performance[ATTACK_SPEED] = ut->performance[ATTACK_SPEED]; + hasMapBeenModified = true; + } + else if(action=="update unit attack strength level") + { + Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; + UnitType *ut = u->race->getUnitType(u->typeNum, u->level[ATTACK_STRENGTH]); + u->performance[ATTACK_STRENGTH] = ut->performance[ATTACK_STRENGTH]; + hasMapBeenModified = true; + } + else if(action=="update unit magic ground attack level") + { + Unit* u=game.teams[Unit::GIDtoTeam(selectedUnitGID)]->myUnits[Unit::GIDtoID(selectedUnitGID)]; + UnitType *ut = u->race->getUnitType(u->typeNum, u->level[MAGIC_ATTACK_GROUND]); + u->performance[MAGIC_ATTACK_GROUND] = ut->performance[MAGIC_ATTACK_GROUND]; + hasMapBeenModified = true; + } + else if(action=="update unit") + { + hasMapBeenModified = true; + } + else if(action=="select map building") + { + int x; + int y; + game.map.displayToMapCaseAligned(mouseX, mouseY, &x, &y, viewportX, viewportY); + int gid=NOGBID; + for(int t=0; t::iterator virtualIt=game.teams[t]->virtualBuildings.begin(); + virtualIt!=game.teams[t]->virtualBuildings.end(); ++virtualIt) + { + { + Building *b=*virtualIt; + if ((b->posX==x) && (b->posY==y)) + { + gid=b->gid; + break; + } + } + } + } + } + if(gid==NOGBID && game.map.getBuilding(x, y)!=NOGUID) + { + gid=game.map.getBuilding(x, y); + } + if(gid!=NOGBID) + { + performAction("unselect"); + Building* b=game.teams[Building::GIDtoTeam(gid)]->myBuildings[Building::GIDtoID(gid)]; + selectionMode=EditingBuilding; + panelMode=BuildingEditor; + selectedBuildingGID=gid; + enableOnlyGroup("building editor"); + buildingInfoTitle->setBuilding(b); + buildingPicture->setBuilding(b); + bool hpLabel=false; + buildingHPLabel->setValues(&b->hp, &b->type->hpMax); + buildingHPScrollBox->setValues(&b->hp, &b->type->hpMax); + bool foodLabel=false; + buildingFoodQuantityLabel->setValues(&b->ressources[CORN], &b->type->maxRessource[CORN]); + buildingFoodQuantityScrollBox->setValues(&b->ressources[CORN], &b->type->maxRessource[CORN]); + bool assignedLabel=false; + buildingAssignedLabel->setValues(&b->maxUnitWorking); + buildingAssignedScrollBox->setValues(&b->maxUnitWorking); + bool workerRatioLabel=false; + buildingWorkerRatioLabel->setValues(&b->ratio[WORKER]); + buildingWorkerRatioScrollBox->setValues(&b->ratio[WORKER]); + bool explorerRatioLabel=false; + buildingExplorerRatioLabel->setValues(&b->ratio[EXPLORER]); + buildingExplorerRatioScrollBox->setValues(&b->ratio[EXPLORER]); + bool warriorRatioLabel=false; + buildingWarriorRatioLabel->setValues(&b->ratio[WARRIOR]); + buildingWarriorRatioScrollBox->setValues(&b->ratio[WARRIOR]); + bool cherryLabel=false; + buildingCherryLabel->setValues(&b->ressources[CHERRY], &b->type->maxRessource[CHERRY]); + buildingCherryScrollBox->setValues(&b->ressources[CHERRY], &b->type->maxRessource[CHERRY]); + bool orangeLabel=false; + buildingOrangeLabel->setValues(&b->ressources[ORANGE], &b->type->maxRessource[ORANGE]); + buildingOrangeScrollBox->setValues(&b->ressources[ORANGE], &b->type->maxRessource[ORANGE]); + bool pruneLabel=false; + buildingPruneLabel->setValues(&b->ressources[PRUNE], &b->type->maxRessource[PRUNE]); + buildingPruneScrollBox->setValues(&b->ressources[PRUNE], &b->type->maxRessource[PRUNE]); + bool stoneLabel=false; + buildingStoneLabel->setValues(&b->ressources[STONE], &b->type->maxRessource[STONE]); + buildingStoneScrollBox->setValues(&b->ressources[STONE], &b->type->maxRessource[STONE]); + bool bulletsLabel=false; + buildingBulletsLabel->setValues(&b->bullets, &b->type->maxBullets); + buildingBulletsScrollBox->setValues(&b->bullets, &b->type->maxBullets); + bool minimumLevel=false; + buildingMinimumLevelLabel->setValues(&b->minLevelToFlag); + buildingMinimumLevelScrollBox->setValues(&b->minLevelToFlag); + bool radius=false; + buildingRadiusLabel->setValues(&b->unitStayRange, &b->type->maxUnitStayRange); + buildingRadiusScrollBox->setValues(&b->unitStayRange, &b->type->maxUnitStayRange); + if(b->type->isBuildingSite) + { + hpLabel=true; + assignedLabel=true; + } + else if(b->shortTypeNum==IntBuildingType::SWARM_BUILDING) + { + hpLabel=true; + foodLabel=true; + assignedLabel=true; + workerRatioLabel=true; + explorerRatioLabel=true; + warriorRatioLabel=true; + } + else if(b->shortTypeNum==IntBuildingType::FOOD_BUILDING) + { + hpLabel=true; + foodLabel=true; + assignedLabel=true; + } + else if(b->shortTypeNum==IntBuildingType::HEAL_BUILDING) + { + hpLabel=true; + } + else if(b->shortTypeNum==IntBuildingType::WALKSPEED_BUILDING) + { + hpLabel=true; + } + else if(b->shortTypeNum==IntBuildingType::SWIMSPEED_BUILDING) + { + hpLabel=true; + } + else if(b->shortTypeNum==IntBuildingType::ATTACK_BUILDING) + { + hpLabel=true; + } + else if(b->shortTypeNum==IntBuildingType::SCIENCE_BUILDING) + { + hpLabel=true; + } + if(b->shortTypeNum==IntBuildingType::DEFENSE_BUILDING) + { + hpLabel=true; + assignedLabel=true; + stoneLabel=true; + bulletsLabel=true; + } + else if(b->shortTypeNum==IntBuildingType::EXPLORATION_FLAG) + { + assignedLabel=true; + radius=true; + } + else if(b->shortTypeNum==IntBuildingType::WAR_FLAG) + { + assignedLabel=true; + minimumLevel=true; + radius=true; + } + else if(b->shortTypeNum==IntBuildingType::CLEARING_FLAG) + { + assignedLabel=true; + minimumLevel=true; + radius=true; + } + else if(b->shortTypeNum==IntBuildingType::STONE_WALL) + { + hpLabel=true; + } + else if(b->shortTypeNum==IntBuildingType::MARKET_BUILDING) + { + hpLabel=true; + assignedLabel=true; + cherryLabel=true; + orangeLabel=true; + pruneLabel=true; + } + + int ypos=252; + if(!hpLabel) + { + buildingHPLabel->disable(); + buildingHPScrollBox->disable(); + } + else + { + buildingHPLabel->area.y=ypos; + buildingHPScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!foodLabel) + { + buildingFoodQuantityLabel->disable(); + buildingFoodQuantityScrollBox->disable(); + } + else + { + buildingFoodQuantityLabel->area.y=ypos; + buildingFoodQuantityScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!assignedLabel) + { + buildingAssignedLabel->disable(); + buildingAssignedScrollBox->disable(); + } + else + { + buildingAssignedLabel->area.y=ypos; + buildingAssignedScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!workerRatioLabel) + { + buildingWorkerRatioLabel->disable(); + buildingWorkerRatioScrollBox->disable(); + } + else + { + buildingWorkerRatioLabel->area.y=ypos; + buildingWorkerRatioScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!explorerRatioLabel) + { + buildingExplorerRatioLabel->disable(); + buildingExplorerRatioScrollBox->disable(); + } + else + { + buildingExplorerRatioLabel->area.y=ypos; + buildingExplorerRatioScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!warriorRatioLabel) + { + buildingWarriorRatioLabel->disable(); + buildingWarriorRatioScrollBox->disable(); + } + else + { + buildingWarriorRatioLabel->area.y=ypos; + buildingWarriorRatioScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!cherryLabel) + { + buildingCherryLabel->disable(); + buildingCherryScrollBox->disable(); + } + else + { + buildingCherryLabel->area.y=ypos; + buildingCherryScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!orangeLabel) + { + buildingOrangeLabel->disable(); + buildingOrangeScrollBox->disable(); + } + else + { + buildingOrangeLabel->area.y=ypos; + buildingOrangeScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!pruneLabel) + { + buildingPruneLabel->disable(); + buildingPruneScrollBox->disable(); + } + else + { + buildingPruneLabel->area.y=ypos; + buildingPruneScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!stoneLabel) + { + buildingStoneLabel->disable(); + buildingStoneScrollBox->disable(); + } + else + { + buildingStoneLabel->area.y=ypos; + buildingStoneScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!bulletsLabel) + { + buildingBulletsLabel->disable(); + buildingBulletsScrollBox->disable(); + } + else + { + buildingBulletsLabel->area.y=ypos; + buildingBulletsScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!minimumLevel) + { + buildingMinimumLevelLabel->disable(); + buildingMinimumLevelScrollBox->disable(); + } + else + { + buildingMinimumLevelLabel->area.y=ypos; + buildingMinimumLevelScrollBox->area.y=ypos+16; + ypos+=32; + } + + if(!radius) + { + buildingRadiusLabel->disable(); + buildingRadiusScrollBox->disable(); + } + else + { + buildingRadiusLabel->area.y=ypos; + buildingRadiusScrollBox->area.y=ypos+16; + ypos+=32; + } + } + } + else if(action=="update building") + { + hasMapBeenModified = true; + } + else if(action=="compute fertility") + { + //Only compute when its x'ed in, not otherwise + if(isFertilityOn) + { + FertilityCalculatorDialog dialog(globalContainer->gfx, game.map); + dialog.runModal(); + overlay.forceRecompute(); + overlay.compute(game, OverlayArea::Fertility, team); + } + } + else if(action=="quit editor") + { + doQuit=true; + } +} + + + diff --git a/src/map/edit/MapEditClicks.cpp b/src/map/edit/MapEditClicks.cpp new file mode 100644 index 000000000..35b4ddcca --- /dev/null +++ b/src/map/edit/MapEditClicks.cpp @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +void MapEdit::addWidget(MapEditorWidget* widget) +{ + mew.push_back(widget); +} + +bool MapEdit::findAction(int x, int y) +{ + for(std::vector::iterator i=mew.begin(); i!=mew.end(); ++i) + { + MapEditorWidget* mi=*i; + if(mi->is_in(x, y) && mi->enabled) + { + mi->handleClick(mouseX-mi->area.x, mouseY-mi->area.y); + return true; + } + } + return false; +} + + + +void MapEdit::enableOnlyGroup(const std::string& group) +{ + for(std::vector::iterator i=mew.begin(); i!=mew.end(); ++i) + { + if((*i)->group == group || (*i)->group=="any") + { + (*i)->enable(); + } + else + (*i)->disable(); + } +} + + + +void MapEdit::drawWidgets() +{ + for(std::vector::iterator i=mew.begin(); i!=mew.end(); ++i) + { + (*i)->drawSelf(); + } +} + + +void MapEdit::minimapMouseToPos(int mx, int my, int *cx, int *cy, bool forScreenViewport) +{ + // get data for minimap + int mMax; + int szX, szY; + int decX, decY; + Utilities::computeMinimapData(100, game.map.getW(), game.map.getH(), &mMax, &szX, &szY, &decX, &decY); + + mx-=14+decX; + my-=14+decY; + *cx=((mx*game.map.getW())/szX); + *cy=((my*game.map.getH())/szY); + *cx+=game.teams[team]->startPosX-(game.map.getW()/2); + *cy+=game.teams[team]->startPosY-(game.map.getH()/2); + if (forScreenViewport) + { + *cx-=((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); + *cy-=((globalContainer->gfx->getH())>>6); + } + + *cx&=game.map.getMaskW(); + *cy&=game.map.getMaskH(); +} + + + +void MapEdit::handleBrushClick(int mx, int my) +{ + // if we have an area over 32x32, which mean over 128 bytes, send it +// if (brushAccumulator.getAreaSurface() > 32*32) +// { +// sendBrushOrders(); +// } + // we add brush to accumulator + int mapX, mapY; + game.map.displayToMapCaseAligned(mx, my, &mapX, &mapY, viewportX, viewportY); + if(lastPlacementX==mapX && lastPlacementY==mapY) + return; + + if(lastPlacementX == -1) + { + firstPlacementX=mapX; + firstPlacementY=mapY; + } + + int fig = brush.getFigure(); + brushAccumulator.applyBrush(BrushApplication(mapX, mapY, fig), &game.map); + // we get coordinates + int startX = mapX-BrushTool::getBrushDimXMinus(fig); + int startY = mapY-BrushTool::getBrushDimYMinus(fig); + int width = BrushTool::getBrushWidth(fig); + int height = BrushTool::getBrushHeight(fig); + // we update local values + if (brush.getType() == BrushTool::MODE_ADD) + { + for (int y=startY; y 32*32) +// { +// sendBrushOrders(); +// } + // we add brush to accumulator + int mapX, mapY; + game.map.displayToMapCaseAligned(mx+(terrainType>TerrainSelector::Water ? 0 : 16), my+(terrainType>TerrainSelector::Water ? 0 : 16), &mapX, &mapY, viewportX, viewportY); + if(lastPlacementX==mapX && lastPlacementY==mapY) + return; + + if(lastPlacementX == -1) + { + firstPlacementX=mapX; + firstPlacementY=mapY; + } + int fig = brush.getFigure(); + brushAccumulator.applyBrush(BrushApplication(mapX, mapY, fig), &game.map); + // we get coordinates + int startX = mapX-BrushTool::getBrushDimXMinus(fig); + int startY = mapY-BrushTool::getBrushDimYMinus(fig); + int width = BrushTool::getBrushWidth(fig); + int height = BrushTool::getBrushHeight(fig); + // we update local values + if (brush.getType() == BrushTool::MODE_ADD) + { + for (int y=startY; ygetIndex(), x, y); + break; + case BrushTool::CT_NO_RESOURCE_GROWTH: + game.map.getCase(x, y).canRessourcesGrow=false; + break; + } + } + } + else if (brush.getType() == BrushTool::MODE_DEL) + { + for (int y=startY; ygetIndex(), x, y); + break; + case BrushTool::CT_NO_RESOURCE_GROWTH: + game.map.getCase(x, y).canRessourcesGrow=true; + break; + default:break; + } + } + } + lastPlacementX=mapX; + lastPlacementY=mapY; + game.regenerateDiscoveryMap(); +} +void MapEdit::handleDeleteClick(int mx, int my) +{ + handleClick(mx,my,BrushTool::CT_DELETE); +} + + + +void MapEdit::handleAreaClick(int mx, int my) +{ + handleClick(mx,my,BrushTool::CT_AREA); +} + + + +void MapEdit::handleNoRessourceGrowthClick(int mx, int my) +{ + handleClick(mx,my,BrushTool::CT_NO_RESOURCE_GROWTH); +} + + +void MapEdit::regenerateGameHeader() +{ + GameHeader gameHeader; + MapHeader& mapHeader = game.mapHeader; + + int playerNumber=0; + for (int i=0; i +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + + +MapEdit::MapEdit() + : game(NULL, this), keyboardManager(MapEditShortcuts), + minimap(globalContainer->runNoX, + RIGHT_MENU_WIDTH, // menu width + globalContainer->gfx->getW(), // game width + 20, // x offset + 5, // y offset + 128, // width + 128, // height + Minimap::HideFOW) +{ + doQuit=false; + doFullQuit=false; + doQuitAfterLoadSave=false; + + // default value; + viewportX=0; + viewportY=0; + xSpeed=0; + ySpeed=0; + mouseX=0; + mouseY=0; + relMouseX=0; + relMouseY=0; + wasMinimapRendered=false; + + // load menu + menu=Toolkit::getSprite("data/gui/editor"); + + // editor facilities + hasMapBeenModified=false; + team=0; + + selectionMode=PlaceNothing; + + int decX = RIGHT_MENU_OFFSET; + + panelMode=AddBuildings; + buildingView = new PanelIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 136, 32, 32), "any", "building view icon", "switch to building view", 0, AddBuildings); + flagsView = new PanelIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, 136, 32, 32), "any", "flag view icon", "switch to flag view", 28, AddFlagsAndZones); + terrainView = new PanelIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, 136, 32, 32), "any", "terrain view icon", "switch to terrain view", 31, Terrain); + teamsView = new PanelIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+96+decX, 136, 32, 32), "any", "teams view icon", "switch to teams view", 33, Teams); + menuIcon = new MenuIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-32+decX, 0, 32, 32), "any", "menu icon", "open menu screen"); + mapCoordinatesLabel = new TextLabel(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+2+decX, globalContainer->gfx->getH()-95, 75, 10), "any", "map coordinates label", "do nothing", "", false, "0 0"); + addWidget(buildingView); + addWidget(flagsView); + addWidget(terrainView); + addWidget(teamsView); + addWidget(menuIcon); + addWidget(mapCoordinatesLabel); + swarm = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+6, 40, 40), "building view", "swarm", "set place building selection swarm", "swarm", true); + inn = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+6, 40, 40), "building view", "inn", "set place building selection inn", "inn", true); + hospital = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+46*1+6, 40, 40), "building view", "hospital", "set place building selection hospital", "hospital", true); + racetrack = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+46*1+6, 40, 40), "building view", "racetrack", "set place building selection racetrack", "racetrack", true); + swimmingpool = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+46*2+6, 40, 40), "building view", "swimmingpool", "set place building selection swimmingpool", "swimmingpool", true); + barracks = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+46*2+6, 40, 40), "building view", "barracks", "set place building selection barracks", "barracks", true); + school = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+46*3+6, 40, 40), "building view", "school", "set place building selection school", "school", true); + defencetower = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+46*3+6, 40, 40), "building view", "defencetower", "set place building selection defencetower", "defencetower", true); + stonewall = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+12+decX, 128+32+46*4+6, 40, 40), "building view", "stonewall", "set place building selection stonewall", "stonewall", true); + market = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+12+decX, 128+32+46*4+6, 40, 40), "building view", "market", "set place building selection market", "market", true); + building_view_tcs = new TeamColorSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 16+decX, globalContainer->gfx->getH()-74, 96, 32 ), "building view", "building view team selector", "select active team"); + building_view_level1 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, globalContainer->gfx->getH()-36, 32, 32), "building view", "building view level 1", "switch to building level 1", 1, buildingLevel); + building_view_level2 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, globalContainer->gfx->getH()-36, 32, 32), "building view", "building view level 2", "switch to building level 2", 2, buildingLevel); + building_view_level3 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, globalContainer->gfx->getH()-36, 32, 32), "building view", "building view level 3", "switch to building level 3", 3, buildingLevel); + addWidget(swarm); + addWidget(inn); + addWidget(hospital); + addWidget(racetrack); + addWidget(swimmingpool); + addWidget(barracks); + addWidget(school); + addWidget(defencetower); + addWidget(stonewall); + addWidget(market); + addWidget(building_view_tcs); + addWidget(building_view_level1); + addWidget(building_view_level2); + addWidget(building_view_level3); + explorationflag = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+5+decX, 128+32+7, 32, 32), "flag view", "explorationflag", "set place building selection explorationflag", "explorationflag", false); + warflag = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+5+42+decX, 128+32+7, 32, 32), "flag view", "warflag", "set place building selection warflag", "warflag", false); + clearingflag = new BuildingSelectorWidget(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+5+84+decX, 128+32+7, 32, 32), "flag view", "clearingflag", "set place building selection clearingflag", "clearingflag", false); + forbiddenZone = new ZoneSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 216, 32, 32), "flag view", "forbidden zone", "select forbidden zone", ZoneSelector::ForbiddenZone); + guardZone = new ZoneSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+40+decX, 216, 32, 32), "flag view", "guard zone", "select guard zone", ZoneSelector::GuardingZone); + clearingZone = new ZoneSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+80+decX, 216, 32, 32), "flag view", "clearing zone", "select clearing zone", ZoneSelector::ClearingZone); + deleteButton = new BlueButton(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 8+decX, 216+40, 112, 16), "flag view", "delete button", "select delete objects", "[delete]"); + zoneBrushSelector = new BrushSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 216+65, 128, 96), "flag view", "zone brush selector", "handle zone click", brush); + worker = new UnitSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 385, 38, 38), "flag view", "worker selector", "select worker", WORKER); + explorer = new UnitSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+48+decX, 385, 38, 38), "flag view", "explorer selector", "select explorer", EXPLORER); + warrior = new UnitSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+88+decX, 385, 38, 38), "flag view", "warrior selector", "select warrior", WARRIOR); + flag_view_tcs = new TeamColorSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 16+decX, globalContainer->gfx->getH()-74, 96, 32 ), "flag view", "flag view team selector", "select active team"); + flag_view_level1 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, globalContainer->gfx->getH()-36, 32, 32), "flag view", "flag view level 1", "select unit level 1", 1, placingUnitLevel); + flag_view_level2 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, globalContainer->gfx->getH()-36, 32, 32), "flag view", "flag view level 2", "select unit level 2", 2, placingUnitLevel); + flag_view_level3 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, globalContainer->gfx->getH()-36, 32, 32), "flag view", "flag view level 3", "select unit level 3", 3, placingUnitLevel); + flag_view_level4 = new SingleLevelSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+96+decX, globalContainer->gfx->getH()-36, 32, 32), "flag view", "flag view level 3", "select unit level 4", 4, placingUnitLevel); + addWidget(warflag); + addWidget(explorationflag); + addWidget(clearingflag); + addWidget(forbiddenZone); + addWidget(guardZone); + addWidget(clearingZone); + addWidget(deleteButton); + addWidget(zoneBrushSelector); + addWidget(worker); + addWidget(warrior); + addWidget(explorer); + addWidget(flag_view_tcs); + addWidget(flag_view_level1); + addWidget(flag_view_level2); + addWidget(flag_view_level3); + addWidget(flag_view_level4); + + grass = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 172, 32, 32), "terrain view", "grass selector", "select grass", TerrainSelector::Grass); + sand = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, 172, 32, 32), "terrain view", "sand selector", "select sand", TerrainSelector::Sand); + water = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, 172, 32, 32), "terrain view", "water selector", "select water", TerrainSelector::Water); + wheat = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+96+decX, 172, 32, 32), "terrain view", "wheat selector", "select wheat", TerrainSelector::Wheat); + trees = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 210, 32, 32), "terrain view", "trees selector", "select trees", TerrainSelector::Trees); + stone = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, 210, 32, 32), "terrain view", "stone selector", "select stone", TerrainSelector::Stone); + algae = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, 210, 32, 32), "terrain view", "algae selector", "select algae", TerrainSelector::Algae); + papyrus = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+96+decX, 210, 32, 32), "terrain view", "papyrus selector", "select papyrus", TerrainSelector::Papyrus); + orange = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 248, 32, 32), "terrain view", "orange selector", "select orange tree", TerrainSelector::OrangeTree); + cherry = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+32+decX, 248, 32, 32), "terrain view", "cherry selector", "select cherry tree", TerrainSelector::CherryTree); + prune = new TerrainSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+64+decX, 248, 32, 32), "terrain view", "prune selector", "select prune tree", TerrainSelector::PruneTree); + noRessourceGrowthButton = new BlueButton(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 8+decX, 294, 112, 16), "terrain view", "no ressources growth button", "select no ressources growth", "[no ressources growth areas]"); + areasButton = new BlueButton(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 8+decX, 320, 112, 16), "terrain view", "script areas button", "select change areas", "[Script Areas]"); + areaNumber = new NumberCycler(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 336, 8, 16), "terrain view", "script area number selector", "update script area number", 9); + areaNameLabel = new TextLabel(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+24+decX, 336, 104, 16), "terrain view", "script area name label", "open area name", "", false, Toolkit::getStringTable()->getString("[Unnamed Area]")); + terrainBrushSelector = new BrushSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 362, 128, 96), "terrain view", "terrain brush selector", "handle terrain click", brush); + showFertilityOverlay = new Checkbox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 466, 128, 16), "terrain view", "fertility checkbox", "compute fertility", "[Fertility Map]", isFertilityOn); + addWidget(grass); + addWidget(sand); + addWidget(water); + addWidget(wheat); + addWidget(trees); + addWidget(stone); + addWidget(algae); + addWidget(papyrus); + addWidget(orange); + addWidget(cherry); + addWidget(prune); + addWidget(noRessourceGrowthButton); + addWidget(areasButton); + addWidget(areaNumber); + addWidget(areaNameLabel); + addWidget(terrainBrushSelector); + addWidget(showFertilityOverlay); + + increaseTeams = new PlusIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 408, 32, 32), "teams view", "increase teams", "add team"); + decreaseTeams = new MinusIcon(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+40+decX, 408, 32, 32), "teams view", "decrease teams", "remove team"); + team_view_tcs = new TeamColorSelector(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH + 16+decX, 168, 96, 32 ), "teams view", "team view team selector", "select active team"); + addWidget(increaseTeams); + addWidget(decreaseTeams); + addWidget(team_view_tcs); + + unitInfoTitle = new UnitInfoTitle(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+decX, 173, 128, 16), "unit editor", "unit editor title", "", NULL); + unitPicture = new UnitPicture(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+2+decX, 203, 40, 40), "unit editor", "unit editor picture", "", NULL); + unitHPLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "unit editor", "unit editor hp label", "update unit", "[hp]", NULL, static_cast(NULL)); + unitHPScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 112, 16), "unit editor", "unit editor hp scroll box", "", NULL, static_cast(NULL)); + unitWalkLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 284, 128, 16), "unit editor", "unit editor walk level label", "", "[Walk]", NULL, 3); + unitWalkLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 300, 112, 16), "unit editor", "unit editor walk level scroll box", "update unit walk level", NULL, 3); + unitSwimLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 316, 128, 16), "unit editor", "unit editor swim level label", "", "[Swim]", NULL, 3); + unitSwimLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 332, 112, 16), "unit editor", "unit editor swim level scroll box", "update unit swim level", NULL, 3); + unitHarvestLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 348, 128, 16), "unit editor", "unit editor harvest level label", "", "[Harvest]", NULL, 3); + unitHarvestLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 364, 112, 16), "unit editor", "unit editor harvest level scroll box", "update unit harvest level", NULL, 3); + unitBuildLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 380, 128, 16), "unit editor", "unit editor build level label", "", "[Build]", NULL, 3); + unitBuildLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 396, 112, 16), "unit editor", "unit editor build level scroll box", "update unit build level", NULL, 3); + unitAttackSpeedLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 348, 128, 16), "unit editor", "unit editor attack speed level label", "", "[At. speed]", NULL, 3); + unitAttackSpeedLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 364, 112, 16), "unit editor", "unit editor attack speed level scroll box", "update unit attack speed level", NULL, 3); + unitAttackStrengthLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 380, 128, 16), "unit editor", "unit editor attack strength level label", "", "[At. strength]", NULL, 3); + unitAttackStrengthLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 396, 112, 16), "unit editor", "unit editor attack strength level scroll box", "update unit attack strength level", NULL, 3); + unitMagicGroundAttackLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 284, 128, 16), "unit editor", "unit editor ground attack level label", "", "[Magic At. Ground]", NULL, 3); + unitMagicGroundAttackLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 300, 112, 16), "unit editor", "unit editor magic ground attack level scroll box", "update unit magic ground attack level", NULL, 3); + addWidget(unitInfoTitle); + addWidget(unitPicture); + addWidget(unitHPLabel); + addWidget(unitHPScrollBox); + addWidget(unitWalkLevelLabel); + addWidget(unitWalkLevelScrollBox); + addWidget(unitSwimLevelLabel); + addWidget(unitSwimLevelScrollBox); + addWidget(unitHarvestLevelLabel); + addWidget(unitHarvestLevelScrollBox); + addWidget(unitBuildLevelLabel); + addWidget(unitBuildLevelScrollBox); + addWidget(unitAttackSpeedLevelLabel); + addWidget(unitAttackSpeedLevelScrollBox); + addWidget(unitAttackStrengthLevelLabel); + addWidget(unitAttackStrengthLevelScrollBox); + addWidget(unitMagicGroundAttackLevelLabel); + addWidget(unitMagicGroundAttackLevelScrollBox); + + buildingInfoTitle = new BuildingInfoTitle(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+2+decX, 173, 128, 16), "building editor", "building editor info title", "", NULL); + buildingPicture = new BuildingPicture(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+2+decX, 203, 56, 46), "building editor", "building editor picture", "", NULL); + buildingHPLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor hp label", "", "[hp]", NULL, static_cast(NULL)); + buildingHPScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor hp scroll box", "update building", NULL, static_cast(NULL)); + buildingFoodQuantityLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor food label", "", "[Wheat]", NULL, static_cast(NULL)); + buildingFoodQuantityScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor food scroll box", "update building", NULL, static_cast(NULL)); + buildingAssignedLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor assigned label", "", "[assigned]", NULL, 20); + buildingAssignedScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor assigned scroll box", "", NULL, 20); + buildingWorkerRatioLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor worker ratio label", "", "[Worker Ratio]", NULL, 16); + buildingWorkerRatioScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor worker ratio scroll box", "", NULL, 20); + buildingExplorerRatioLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor explorer ratio label", "", "[Explorer Ratio]", NULL, 16); + buildingExplorerRatioScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor explorer ratio scroll box", "", NULL, 20); + buildingWarriorRatioLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor warrior ratio label", "", "[Warrior Ratio]", NULL, 16); + buildingWarriorRatioScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor warrior ratio scroll box", "", NULL, 20); + buildingCherryLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor cherry label", "", "[Cherry]", NULL, static_cast(NULL)); + buildingCherryScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor cherry scroll box", "update building", NULL, static_cast(NULL)); + buildingOrangeLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor orange label", "", "[Orange]", NULL, static_cast(NULL)); + buildingOrangeScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor orange scroll box", "update building", NULL, static_cast(NULL)); + buildingPruneLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor prune label", "", "[Prune]", NULL, static_cast(NULL)); + buildingPruneScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor prune scroll box", "update building", NULL, static_cast(NULL)); + buildingStoneLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor stone label", "", "[Stone]", NULL, static_cast(NULL)); + buildingStoneScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor stone scroll box", "update building", NULL, static_cast(NULL)); + buildingBulletsLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor bullets label", "", "[Bullets]", NULL, static_cast(NULL)); + buildingBulletsScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor bullets scroll box", "update building", NULL, static_cast(NULL)); + buildingMinimumLevelLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor minimum level to flag label", "", "[Minimum Level To Flag]", NULL, 3); + buildingMinimumLevelScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor minimum level to flag scroll box", "update building", NULL, 3); + buildingRadiusLabel = new FractionValueText(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 252, 128, 16), "building editor", "building editor range label", "", "[range]", NULL, static_cast(NULL)); + buildingRadiusScrollBox = new ValueScrollBox(*this, widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+8+decX, 268, 128, 16), "building editor", "building editor range scroll box", "update building", NULL, static_cast(NULL)); + addWidget(buildingInfoTitle); + addWidget(buildingPicture); + addWidget(buildingHPLabel); + addWidget(buildingHPScrollBox); + addWidget(buildingFoodQuantityLabel); + addWidget(buildingFoodQuantityScrollBox); + addWidget(buildingAssignedLabel); + addWidget(buildingAssignedScrollBox); + addWidget(buildingWorkerRatioLabel); + addWidget(buildingWorkerRatioScrollBox); + addWidget(buildingExplorerRatioLabel); + addWidget(buildingExplorerRatioScrollBox); + addWidget(buildingWarriorRatioLabel); + addWidget(buildingWarriorRatioScrollBox); + addWidget(buildingCherryLabel); + addWidget(buildingCherryScrollBox); + addWidget(buildingOrangeLabel); + addWidget(buildingOrangeScrollBox); + addWidget(buildingPruneLabel); + addWidget(buildingPruneScrollBox); + addWidget(buildingStoneLabel); + addWidget(buildingStoneScrollBox); + addWidget(buildingBulletsLabel); + addWidget(buildingBulletsScrollBox); + addWidget(buildingMinimumLevelLabel); + addWidget(buildingMinimumLevelScrollBox); + addWidget(buildingRadiusLabel); + addWidget(buildingRadiusScrollBox); + + selectionName=""; + buildingLevel=0; + brushType = NoBrush; + enableOnlyGroup("building view"); + + isDraggingMinimap=false; + isDraggingZone=false; + isDraggingTerrain=false; + isDraggingDelete=false; + isScrollDragging=false; + isDraggingArea=false; + isDraggingNoRessourceGrowthArea=false; + + lastPlacementX=-1; + lastPlacementY=-1; + firstPlacementX=-1; + firstPlacementY=-1; + + menuScreen = NULL; + scriptEditor=NULL; + teamsEditor=NULL; + showingMenuScreen=false; + showingLoad=false; + showingSave=false; + showingScriptEditor=false; + showingTeamsEditor=false; + + terrainType=TerrainSelector::NoTerrain; + + teamViewSelectorKeys.push_back("[human]"); + teamViewSelectorKeys.push_back("[ai]"); + + + placingUnit=NoUnit; + placingUnitLevel=0; + + selectedUnitGID=NOGUID; + selectedBuildingGID=NOGBID; + + areaName=NULL; + isShowingAreaName=false; + + isFertilityOn=false; +} + + + +MapEdit::~MapEdit() +{ + Toolkit::releaseSprite("data/gui/editor"); + for(std::vector::iterator i=mew.begin(); i!=mew.end(); ++i) + { + delete *i; + } +} diff --git a/src/map/edit/MapEditDelegate.cpp b/src/map/edit/MapEditDelegate.cpp new file mode 100644 index 000000000..8f84dba92 --- /dev/null +++ b/src/map/edit/MapEditDelegate.cpp @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +void MapEdit::delegateMenu(SDL_Event& event) +{ + if(showingMenuScreen) + { + menuScreen->translateAndProcessEvent(&event); + switch (menuScreen->endValue) + { + case MapEditMenuScreen::LOAD_MAP: + { + performAction("close menu screen"); + performAction("open load screen"); + } + break; + case MapEditMenuScreen::SAVE_MAP: + { + performAction("close menu screen"); + performAction("open save screen"); + } + break; + case MapEditMenuScreen::OPEN_SCRIPT_EDITOR: + { + performAction("close menu screen"); + performAction("open scenario editor"); + } + break; + case MapEditMenuScreen::OPEN_TEAMS_EDITOR: + { + performAction("close menu screen"); + performAction("open teams editor"); + } + break; + case MapEditMenuScreen::RETURN_EDITOR: + { + performAction("close menu screen"); + } + break; + case MapEditMenuScreen::QUIT_EDITOR: + { + performAction("close menu screen"); + performAction("quit editor"); + } + break; + } + } + if(showingLoad) + { + loadSaveScreen->translateAndProcessEvent(&event); + switch (loadSaveScreen->endValue) + { + case LoadSaveScreen::OK: + { + load(loadSaveScreen->getFileName()); + performAction("close load screen"); + } + break; + case LoadSaveScreen::CANCEL: + { + performAction("close load screen"); + } + break; + } + } + if(showingSave) + { + loadSaveScreen->translateAndProcessEvent(&event); + switch (loadSaveScreen->endValue) + { + case LoadSaveScreen::OK: + { + save(loadSaveScreen->getFileName(), loadSaveScreen->getName()); + performAction("close save screen"); + } + case LoadSaveScreen::CANCEL: + { + performAction("close save screen"); + } + } + } + if(showingScriptEditor) + { + scriptEditor->translateAndProcessEvent(&event); + switch(scriptEditor->endValue) + { + case ScriptEditorScreen::OK: + case ScriptEditorScreen::CANCEL: + { + performAction("close scenario editor"); + } + } + } + if(showingTeamsEditor) + { + teamsEditor->translateAndProcessEvent(&event); + switch(teamsEditor->endValue) + { + case ScriptEditorScreen::OK: + case ScriptEditorScreen::CANCEL: + { + performAction("close teams editor"); + } + } + } + if(isShowingAreaName) + { + areaName->translateAndProcessEvent(&event); + switch(areaName->endValue) + { + case AskForTextInput::OK: + case AskForTextInput::CANCEL: + { + performAction("close area name"); + } + } + } +} + +void MapEdit::handleMapScroll() +{ + xSpeed = 0; + ySpeed = 0; + int scrollAreaWidth=10; // if the cursor is that close to the border the viewport will scroll + + SDL_PumpEvents(); + const Uint8 *keystate = SDL_GetKeyboardState(NULL); + SDL_Keymod modState = SDL_GetModState(); + int xMotion = 1; + int yMotion = 1; + /* We check that only Control is held to avoid accidentally + matching window manager bindings for switching windows + and/or desktops. */ + if (!(modState & (KMOD_ALT|KMOD_SHIFT))) + { + /* It violates good abstraction principles that I + have to do the calculations in the next two + lines. There should be methods that abstract + these computations. */ + if ((modState & KMOD_CTRL)) + { + /* We move by half screens if Control is held while + the arrow keys are held. So we shift by 6 + instead of 5. (If we shifted by 5, it would be + good to subtract 1 so that there would be a small + overlap between what is viewable both before and + after the motion.) */ + xMotion = ((globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)>>6); + yMotion = ((globalContainer->gfx->getH())>>6); + } + else + { + /* We move the screen by one square at a time if CTRL key + is not being help */ + xMotion = 1; + yMotion = 1; + } + } + else if (modState) + { + /* Probably some keys held down as part of window + manager operations. */ + xMotion = 0; + yMotion = 0; + } + if ( + keystate[SDL_SCANCODE_UP] || + keystate[SDL_SCANCODE_KP_7] || + keystate[SDL_SCANCODE_KP_8] || + keystate[SDL_SCANCODE_KP_9] || + mouseYgfx->getH()-mouseYgfx->getW()-mouseXTerrainSelector::Water ? 0 : 16), mouseY+(terrainType>TerrainSelector::Water ? 0 : 16), &x, &y, viewportX, viewportY); + else + game.map.displayToMapCaseAligned(mouseX, mouseY, &x, &y, viewportX, viewportY); + s << "X: " << x << " Y: " << y; + mapCoordinatesLabel->setLabel(s.str()); +} + diff --git a/src/MapEditDialog.cpp b/src/map/edit/MapEditDialog.cpp similarity index 86% rename from src/MapEditDialog.cpp rename to src/map/edit/MapEditDialog.cpp index 2ec473442..fa498c586 100644 --- a/src/MapEditDialog.cpp +++ b/src/map/edit/MapEditDialog.cpp @@ -1,23 +1,6 @@ -/* - Copyright (C) 2006-2008 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006-2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "FormatableString.h" #include "Game.h" @@ -102,7 +85,7 @@ TeamsEditor::TeamsEditor(Game* game) GameHeader& gameHeader = game->gameHeader; MapHeader& mapHeader = game->mapHeader; - for(int i=0; i=200 && par1<300) { int n = par1-200; - for(int i=0; igetSelectedColor() == color[n]->getSelectedColor() && i!=n) { @@ -203,7 +186,6 @@ void TeamsEditor::onAction(Widget *source, Action action, int par1, int par2) GameHeader& gameHeader = game->gameHeader; int team = -1; int nth = 0; - int n = 0; ///Find which team number this widget is for for(int i=0; igetSelectedColor(); nth = allyTeamNumbers[i]->getIndex(); - n = nth+1; break; } } @@ -231,7 +212,7 @@ void TeamsEditor::generateGameHeader() { GameHeader gameHeader; int count = 0; - for (int i=0; igetState()) { diff --git a/src/MapEditDialog.h b/src/map/edit/MapEditDialog.h similarity index 67% rename from src/MapEditDialog.h rename to src/map/edit/MapEditDialog.h index 9c1fa4486..5843d173c 100644 --- a/src/MapEditDialog.h +++ b/src/map/edit/MapEditDialog.h @@ -1,28 +1,11 @@ -/* - Copyright (C) 2006-2008 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006-2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef MapEditDialog_h -#define MapEditDialog_h +#pragma once #include "GUIBase.h" +#include "Team.h" #include namespace GAGCore @@ -88,8 +71,6 @@ class AskForTextInput : public OverlayScreen }; -const int NumberOfPlayerSelectors = 12; - ///This is the teams editor screen. This is the editor that allows the map creator to choose alliances and arrange teams ///in the map. This is primarily for campaign missions since these settings are overridden for custom games class TeamsEditor : public OverlayScreen @@ -119,5 +100,3 @@ class TeamsEditor : public OverlayScreen //! Multi-text button containing an aiSelector MultiTextButton *aiSelector[Team::MAX_COUNT]; }; - -#endif diff --git a/src/map/edit/MapEditDraw.cpp b/src/map/edit/MapEditDraw.cpp new file mode 100644 index 000000000..2d588d1ae --- /dev/null +++ b/src/map/edit/MapEditDraw.cpp @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +void MapEdit::drawMap(int sx, int sy, int sw, int sh, bool needUpdate, bool doPaintEditMode) +{ +// Utilities::rectClipRect(sx, sy, sw, sh, mapClip); + + globalContainer->gfx->setClipRect(sx, sy, sw, sh); + + Uint32 drawOptions = Game::DRAW_WHOLE_MAP | Game::DRAW_BUILDING_RECT | Game::DRAW_AREA | Game::DRAW_HEALTH_FOOD_BAR | Game::DRAW_SCRIPT_AREAS | Game::DRAW_NO_RESSOURCE_GROWTH_AREAS; + if(isFertilityOn) + { + drawOptions |= Game::DRAW_OVERLAY; + } + + game.drawMap(sx, sy, sw, sh, RIGHT_MENU_WIDTH, 16, viewportX, viewportY, team, drawOptions); +// if (doPaintEditMode) +// paintEditMode(false, false); + + if(widgetRectangle(sx, sy, sw, sh).is_in(mouseX, mouseY)) + { + if(selectionMode==PlaceBuilding) + drawBuildingSelectionOnMap(); + if(selectionMode==PlaceZone) + brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY); + if(selectionMode==PlaceTerrain) + brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY, (terrainType>TerrainSelector::Water ? 0 : 1)); + if(selectionMode==PlaceUnit) + drawPlacingUnitOnMap(); + if(selectionMode==RemoveObject) + brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY); + if(selectionMode==EditingBuilding) + { + Building* selBuild=game.teams[Building::GIDtoTeam(selectedBuildingGID)]->myBuildings[Building::GIDtoID(selectedBuildingGID)]; + globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); + int centerX, centerY; + // Map editor mutates buildings directly — no orderQueue, no pending shadow. + // Use the authoritative position straight from the Building. + game.map.buildingPosToCursor(selBuild->posX, selBuild->posY, selBuild->type->width, selBuild->type->height, ¢erX, ¢erY, viewportX, viewportY); + if (selBuild->owner->teamNumber==team) + globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 0, 0, 190); + else if ((game.teams[team]->allies) & (selBuild->owner->me)) + globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 255, 196, 0); + else if (!selBuild->type->isVirtual) + globalContainer->gfx->drawCircle(centerX, centerY, selBuild->type->width*16, 190, 0, 0); + globalContainer->gfx->setClipRect(); + } + if(selectionMode==ChangeAreas) + { + brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY); + } + if(selectionMode==ChangeNoRessourceGrowthAreas) + brush.drawBrush(mouseX, mouseY, viewportX, viewportY, firstPlacementX, firstPlacementY); + } + + globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW(), globalContainer->gfx->getH()); +} + + + +void MapEdit::drawMiniMap(void) +{ + minimap.draw(team, viewportX, viewportY, (globalContainer->gfx->getW()-RIGHT_MENU_WIDTH)/32, globalContainer->gfx->getH()/32 ); +// paintCoordinates(); +} + + + +void MapEdit::drawMenu(void) +{ + int menuStartW=globalContainer->gfx->getW()-RIGHT_MENU_WIDTH; + int yposition=133; + + if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) + globalContainer->gfx->drawFilledRect(menuStartW, yposition, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128, 0, 0, 0); + else + globalContainer->gfx->drawFilledRect(menuStartW, yposition, RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-128, 0, 0, 40, 180); + + drawMenuEyeCandy(); +} + + + +void MapEdit::drawBuildingSelectionOnMap() +{ + if (selectionName!="") + { + // we get the type of building + int typeNum=globalContainer->buildingsTypes.getTypeNum(selectionName, buildingLevel, false); + if(!isUpgradable(IntBuildingType::shortNumberFromType(selectionName))) + typeNum = globalContainer->buildingsTypes.getTypeNum(selectionName, 0, false); + BuildingType *bt = globalContainer->buildingsTypes.get(typeNum); + Sprite *sprite = bt->gameSpritePtr; + + // we translate dimensions and situation + int tempX, tempY; + int mapX, mapY; + bool isRoom; + game.map.cursorToBuildingPos(mouseX, mouseY, bt->width, bt->height, &tempX, &tempY, viewportX, viewportY); + if (bt->isVirtual) + isRoom = game.checkRoomForBuilding(tempX, tempY, bt, &mapX, &mapY, team); + else + isRoom = game.checkHardRoomForBuilding(tempX, tempY, bt, &mapX, &mapY); + + // modifiy highlight given room +// if (isRoom) +// highlightSelection = std::min(highlightSelection + 0.1f, 1.0f); +// / else +// highlightSelection = std::max(highlightSelection - 0.1f, 0.0f); + + // we get the screen dimensions of the building + int batW = (bt->width)<<5; + int batH = sprite->getH(bt->gameSpriteImage); + int batX = (((mapX-viewportX)&(game.map.wMask))<<5); + int batY = (((mapY-viewportY)&(game.map.hMask))<<5)-(batH-(bt->height<<5)); + + // we draw the building + sprite->setBaseColor(game.teams[team]->color); + globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()); +// int spriteIntensity = 127+static_cast(128.0f*splineInterpolation(1.f, 0.f, 1.f, highlightSelection)); + int spriteIntensity = 127; + globalContainer->gfx->drawSprite(batX, batY, sprite, bt->gameSpriteImage, spriteIntensity); + + if (!bt->isVirtual) + { + if (game.teams[team]->noMoreBuildingSitesCountdown>0) + { + globalContainer->gfx->drawRect(batX, batY, batW, batH, 255, 0, 0, 127); + globalContainer->gfx->drawLine(batX, batY, batX+batW-1, batY+batH-1, 255, 0, 0, 127); + globalContainer->gfx->drawLine(batX+batW-1, batY, batX, batY+batH-1, 255, 0, 0, 127); + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 255, 0, 0, 127)); + globalContainer->gfx->drawString(batX, batY-12, globalContainer->littleFont, FormatableString("%0.%1").arg(game.teams[team]->noMoreBuildingSitesCountdown/40).arg((game.teams[team]->noMoreBuildingSitesCountdown%40)/4).c_str()); + globalContainer->littleFont->popStyle(); + } + else + { + if (isRoom) + globalContainer->gfx->drawRect(batX, batY, batW, batH, 255, 255, 255, 127); + else + globalContainer->gfx->drawRect(batX, batY, batW, batH, 255, 0, 0, 127); + + // We look for its maximum extension size + // we find last's level type num: + BuildingType *lastbt=globalContainer->buildingsTypes.get(typeNum); + int lastTypeNum=typeNum; + int max=0; + while (lastbt->nextLevel>=0) + { + lastTypeNum=lastbt->nextLevel; + lastbt=globalContainer->buildingsTypes.get(lastTypeNum); + if (max++>200) + { + printf("GameGUI: Error: nextLevel architecture is broken.\n"); + assert(false); + break; + } + } + + int exMapX, exMapY; // ex prefix means EXtended building; the last level building type. + bool isExtendedRoom = game.checkHardRoomForBuilding(tempX, tempY, lastbt, &exMapX, &exMapY); + int exBatX=((exMapX-viewportX)&(game.map.wMask))<<5; + int exBatY=((exMapY-viewportY)&(game.map.hMask))<<5; + int exBatW=(lastbt->width)<<5; + int exBatH=(lastbt->height)<<5; + + if (isRoom && isExtendedRoom) + globalContainer->gfx->drawRect(exBatX-1, exBatY-1, exBatW+2, exBatH+2, 255, 255, 255, 127); + else + globalContainer->gfx->drawRect(exBatX-1, exBatY-1, exBatW+2, exBatH+2, 255, 0, 0, 127); + } + } + + } + +} + + + +bool MapEdit::isUpgradable(int buildingLevel) +{ + if(buildingLevel==IntBuildingType::SWARM_BUILDING) + return false; + if(buildingLevel==IntBuildingType::EXPLORATION_FLAG) + return false; + if(buildingLevel==IntBuildingType::WAR_FLAG) + return false; + if(buildingLevel==IntBuildingType::CLEARING_FLAG) + return false; + if(buildingLevel==IntBuildingType::STONE_WALL) + return false; + if(buildingLevel==IntBuildingType::MARKET_BUILDING) + return false; + return true; +} + + + +void MapEdit::drawMenuEyeCandy() +{ + globalContainer->gfx->setClipRect(0, 0, globalContainer->gfx->getW(), globalContainer->gfx->getH()); + + // bar background + if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) + globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 16, 0, 0, 0); + else + globalContainer->gfx->drawFilledRect(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, 16, 0, 0, 40, 180); + + // draw window bar + int pos=globalContainer->gfx->getW()-RIGHT_MENU_WIDTH-32; + for (int i=0; i<=pos; i+=32) + { + globalContainer->gfx->drawSprite(i, 16, globalContainer->gamegui, 16); + } + for (int i=16; igfx->getH(); i+=32) + { + globalContainer->gfx->drawSprite(pos+28, i, globalContainer->gamegui, 17); + } +} + + + +void MapEdit::drawPlacingUnitOnMap() +{ + int type=0; + if(placingUnit==Worker) + type=WORKER; + else if(placingUnit==Warrior) + type=WARRIOR; + else if(placingUnit==Explorer) + type=EXPLORER; + + int level=placingUnitLevel; + + int cx=(mouseX>>5)+viewportX; + int cy=(mouseY>>5)+viewportY; + + int px=mouseX&0xFFFFFFE0; + int py=mouseY&0xFFFFFFE0; + int pw=32; + int ph=32; + + bool isRoom; + if (type==EXPLORER) + isRoom=game.map.isFreeForAirUnit(cx, cy); + else + { + UnitType *ut=game.teams[team]->race.getUnitType(type, level); + isRoom=game.map.isFreeForGroundUnit(cx, cy, ut->performance[SWIM], Team::teamNumberToMask(team)); + } + + int imgid; + if (type==WORKER) + imgid=64; + else if (type==EXPLORER) + imgid=0; + else if (type==WARRIOR) + imgid=256; + else + { + imgid=0; + assert(false); + } + + Sprite *unitSprite=globalContainer->units; + unitSprite->setBaseColor(game.teams[team]->color); + + globalContainer->gfx->drawSprite(px, py, unitSprite, imgid); + + if (isRoom) + globalContainer->gfx->drawRect(px, py, pw, ph, 255, 255, 255, 128); + else + globalContainer->gfx->drawRect(px, py, pw, ph, 255, 0, 0, 128); +} diff --git a/src/map/edit/MapEditEvents.cpp b/src/map/edit/MapEditEvents.cpp new file mode 100644 index 000000000..3602295e8 --- /dev/null +++ b/src/map/edit/MapEditEvents.cpp @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +void MapEdit::processEvent(SDL_Event& event) +{ + if (event.type==SDL_QUIT) + { + doFullQuit=true; + } +# ifdef USE_OSX + else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_q && SDL_GetModState() & KMOD_GUI) + { + doFullQuit=true; + } +# endif +# ifdef USE_WIN32 + else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_F4 && SDL_GetModState() & KMOD_ALT) + { + doFullQuit=true; + } +# endif + + else if(showingMenuScreen || showingLoad || showingSave || showingScriptEditor || showingTeamsEditor || isShowingAreaName) + { + delegateMenu(event); + return; + } + else if(event.type==SDL_MOUSEMOTION) + { + mouseX=event.motion.x; + mouseY=event.motion.y; + relMouseX=event.motion.xrel; + relMouseY=event.motion.yrel; + updateCoordinatesLabel(); + if(isDraggingMinimap) + { + performAction("minimap drag motion", relMouseX, relMouseY); + performAction("scroll horizontal stop", relMouseX, relMouseY); + performAction("scroll vertical stop", relMouseX, relMouseY); + } + else if(isDraggingZone) + { + if(widgetRectangle(0, 16, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-16).is_in(mouseX, mouseY)) + performAction("zone drag motion", relMouseX, relMouseY); + } + else if(isDraggingTerrain) + { + if(widgetRectangle(0, 16, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()-16).is_in(mouseX, mouseY)) + performAction("terrain drag motion", relMouseX, relMouseY); + } + else if(isScrollDragging) + { + performAction("scroll drag motion", relMouseX, relMouseY); + } + else if(isDraggingDelete) + { + performAction("delete drag motion", relMouseX, relMouseY); + } + else if(isDraggingArea) + { + performAction("area drag motion", relMouseX, relMouseY); + } + else if(isDraggingNoRessourceGrowthArea) + { + performAction("no ressource growth area drag motion", relMouseX, relMouseY); + } + } + else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT) + { + if(!findAction(event.button.x, event.button.y) && widgetRectangle(0, 16, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH()).is_in(mouseX, mouseY)) + { + //The button wasn't clicked in any registered area + if(selectionMode==PlaceBuilding) + performAction("place building"); + else if(selectionMode==PlaceZone) + performAction("zone drag start"); + else if(selectionMode==PlaceTerrain) + performAction("terrain drag start"); + else if(selectionMode==PlaceUnit) + performAction("place unit"); + else if(selectionMode==RemoveObject) + performAction("delete drag start"); + else if(selectionMode==ChangeAreas) + performAction("area drag start"); + else if(selectionMode==ChangeNoRessourceGrowthAreas) + performAction("no ressource growth area drag start"); + else + { + performAction("select map unit"); + performAction("select map building"); + } + } + else if(widgetRectangle(globalContainer->gfx->getW()-RIGHT_MENU_WIDTH+RIGHT_MENU_OFFSET+14, 14, 100, 100).is_in(mouseX, mouseY)) + performAction("minimap drag start"); + } + else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_RIGHT) + { + if(selectionMode==PlaceNothing || selectionMode==EditingUnit || selectionMode==EditingBuilding) + performAction("change menu"); + if(selectionMode!=PlaceNothing) + performAction("unselect"); + } + else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_MIDDLE) + { + performAction("scroll drag start"); + } + else if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT) + { + if(isDraggingMinimap) + performAction("minimap drag stop"); + if(isDraggingZone) + performAction("zone drag end"); + if(isDraggingTerrain) + performAction("terrain drag end"); + if(isDraggingDelete) + performAction("delete drag end"); + if(isDraggingArea) + performAction("area drag end"); + if(isDraggingNoRessourceGrowthArea) + performAction("no ressource growth area drag end"); + } + else if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_MIDDLE) + { + if(isScrollDragging) + performAction("scroll drag stop"); + } + else if(event.type==SDL_KEYDOWN) + { + handleKeyPressed(event.key.keysym, true); + } + else if(event.type==SDL_KEYUP) + { + handleKeyPressed(event.key.keysym, false); + } +} + + + +void MapEdit::handleKeyPressed(SDL_Keysym key, bool pressed) +{ + Uint32 action_t = keyboardManager.getAction(KeyPress(key, pressed)); + switch(action_t) + { + case MapEditKeyActions::DoNothing: + break; + case MapEditKeyActions::SwitchToBuildingView: + { + performAction("switch to building view"); + } + break; + case MapEditKeyActions::SwitchToFlagView: + { + performAction("switch to flag view"); + } + break; + case MapEditKeyActions::SwitchToTerrainView: + { + performAction("switch to terrain view"); + } + break; + case MapEditKeyActions::SwitchToTeamsView: + { + performAction("switch to teams view"); + } + break; + case MapEditKeyActions::OpenSaveScreen: + { + performAction("open save screen"); + } + break; + case MapEditKeyActions::OpenLoadScreen: + { + performAction("open load screen"); + } + break; + case MapEditKeyActions::SelectSwarm: + { + performAction("unselect&switch to building view&set place building selection swarm"); + } + break; + case MapEditKeyActions::SelectInn: + { + performAction("unselect&switch to building view&set place building selection inn"); + } + break; + case MapEditKeyActions::SelectHospital: + { + performAction("unselect&switch to building view&set place building selection hospital"); + } + break; + case MapEditKeyActions::SelectRacetrack: + { + performAction("unselect&switch to building view&set place building selection racetrack"); + } + break; + case MapEditKeyActions::SelectSwimmingpool: + { + performAction("unselect&switch to building view&set place building selection swimmingpool"); + } + break; + case MapEditKeyActions::SelectSchool: + { + performAction("unselect&switch to building view&set place building selection school"); + } + break; + case MapEditKeyActions::SelectBarracks: + { + performAction("unselect&switch to building view&set place building selection barracks"); + } + break; + case MapEditKeyActions::SelectTower: + { + performAction("unselect&switch to building view&set place building selection defencetower"); + } + break; + case MapEditKeyActions::SelectStonewall: + { + performAction("unselect&switch to building view&set place building selection stonewall"); + } + break; + case MapEditKeyActions::SelectMarket: + { + performAction("unselect&switch to building view&set place building selection market"); + } + break; + case MapEditKeyActions::SelectExplorationFlag: + { + performAction("unselect&switch to flag view&set place building selection explorationflag"); + } + break; + case MapEditKeyActions::SelectWarFlag: + { + performAction("unselect&switch to flag view&set place building selection warflag"); + } + break; + case MapEditKeyActions::SelectClearingFlag: + { + performAction("unselect&switch to flag view&set place building selection clearingflag"); + } + break; + case MapEditKeyActions::ToggleMenuScreen: + { + if (showingMenuScreen==false) + performAction("open menu screen"); + else if (showingMenuScreen==true) + performAction("close menu screen"); + } + break; + case MapEditKeyActions::SelectDeleteTool: + { + performAction("switch to flag view&select delete objects"); + } + break; + } +} + + diff --git a/src/map/edit/MapEditIO.cpp b/src/map/edit/MapEditIO.cpp new file mode 100644 index 000000000..7efde066a --- /dev/null +++ b/src/map/edit/MapEditIO.cpp @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +bool MapEdit::load(const std::string filename) +{ + assert(filename.size()); + + InputStream *stream = new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(filename)); + if (stream->isEndOfStream()) + { + std::cerr << "MapEdit::load(\"" << filename << "\") : error, can't open file." << std::endl; + delete stream; + return false; + } + else + { + bool rv; + + try + { + rv = game.load(stream); + } + catch (std::exception &e) + { + std::cerr << "Failed to open map: bad format." << std::endl; + + if (!globalContainer->runNoX) + { + // Display an error message + GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_ONEBUTTON, Toolkit::getStringTable()->getString("[ERROR_CANT_LOAD_MAP]"), Toolkit::getStringTable()->getString("[ok]")); + } + + // We can't recover from this, so we quit + doQuitAfterLoadSave = true; + + return false; + } + + delete stream; + if (!rv) + return false; + + // set the editor default values + team = 0; + + areaNameLabel->setLabel(game.map.getAreaName(areaNumber->getIndex())); + + minimap.resetMinimapDrawing(); + + game.map.computeLocalForbidden(team); + game.map.computeLocalClearArea(team); + game.map.computeLocalGuardArea(team); + + hasMapBeenModified = false; + return true; + } + return false; +} + + + +bool MapEdit::save(const std::string filename, const std::string name) +{ + FertilityCalculatorDialog dialog(globalContainer->gfx, game.map); + dialog.runModal(); + + assert(filename.size()); + assert(name.size()); + + hasMapBeenModified = false; + + OutputStream *stream = new BinaryOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(filename)); + if (stream->isEndOfStream()) + { + std::cerr << "MapEdit::save(\"" << filename << "\",\"" << name << "\") : error, can't open file." << std::endl; + delete stream; + return false; + } + else + { + game.save(stream, true, name); + delete stream; + + // Game::save() now restores mapHeader.mapName/isSavedGame so that + // in-game saves don't permanently clobber the live map name. The + // editor relies on the post-save mutation for its "current name" + // UI (the LoadSaveScreen default), so re-apply explicitly. + game.mapHeader.setMapName(name); + game.mapHeader.setIsSavedGame(false); + return true; + } +} + + + +int MapEdit::run(int sizeX, int sizeY, TerrainType terrainType) +{ + game.map.setSize(sizeX, sizeY, terrainType); + game.map.setGame(&game); + return run(); +} + + + +int MapEdit::run(void) +{ + //globalContainer->gfx->setRes(globalContainer->graphicWidth, globalContainer->graphicHeight , 32, globalContainer->graphicFlags, (DrawableSurface::GraphicContextType)globalContainer->settings.graphicType); + +// regenerateClipRect(); + + + minimap.setGame(game); + globalContainer->gfx->setClipRect(); + drawMap(0, 0, globalContainer->gfx->getW()-RIGHT_MENU_WIDTH, globalContainer->gfx->getH(), true, true); + drawMiniMap(); + drawMenu(); + + + if(game.gameHeader.getNumberOfPlayers() == 0) + regenerateGameHeader(); + + bool isRunning=true; + int returnCode=0; + Uint64 startTick, endTick, deltaTick; + while (isRunning) + { + //SDL_Event event; + startTick=SDL_GetTicks64(); + + // we get all pending events but for mousemotion we only keep the last one + SDL_Event event; + while (SDL_PollEvent(&event)) + { + processEvent(event); + } + + // While processing events the user could've tried to load a map that failed. + // Then we can't go through drawing everything because that would segfault. + if(doQuitAfterLoadSave && !showingSave) + { + isRunning = false; + break; + } + + if(!showingMenuScreen && !showingLoad && !showingSave && !showingScriptEditor && !showingTeamsEditor) + { + handleMapScroll(); + viewportX+=xSpeed; + viewportY+=ySpeed; + viewportX&=game.map.getMaskW(); + viewportY&=game.map.getMaskH(); + } + + //special overrides here to allow for scrolling and painting terrain at the same time + if(xSpeed!=0 || ySpeed!=0) + { + if(isDraggingZone) + performAction("zone drag motion"); + else if(isDraggingTerrain) + performAction("terrain drag motion"); + else if(isDraggingDelete) + performAction("delete drag motion"); + else if(isDraggingArea) + performAction("area drag motion"); + else if(isDraggingNoRessourceGrowthArea) + performAction("no ressource growth area drag motion"); + } + + drawMap(0, 0, globalContainer->gfx->getW()-0, globalContainer->gfx->getH(), true, true); + + drawMenu(); + drawMiniMap(); + wasMinimapRendered=false; + drawWidgets(); + if(showingMenuScreen) + { + globalContainer->gfx->setClipRect(); + menuScreen->dispatchTimer(startTick); + menuScreen->dispatchPaint(); + globalContainer->gfx->drawSurface((int)menuScreen->decX, (int)menuScreen->decY, menuScreen->getSurface()); + } + if(showingLoad || showingSave) + { + globalContainer->gfx->setClipRect(); + loadSaveScreen->dispatchTimer(startTick); + loadSaveScreen->dispatchPaint(); + globalContainer->gfx->drawSurface((int)loadSaveScreen->decX, (int)loadSaveScreen->decY, loadSaveScreen->getSurface()); + } + if(showingScriptEditor) + { + globalContainer->gfx->setClipRect(); + scriptEditor->dispatchTimer(startTick); + scriptEditor->dispatchPaint(); + globalContainer->gfx->drawSurface((int)scriptEditor->decX, (int)scriptEditor->decY, scriptEditor->getSurface()); + } + if(showingTeamsEditor) + { + globalContainer->gfx->setClipRect(); + teamsEditor->dispatchTimer(startTick); + teamsEditor->dispatchPaint(); + globalContainer->gfx->drawSurface((int)teamsEditor->decX, (int)teamsEditor->decY, teamsEditor->getSurface()); + } + if(isShowingAreaName) + { + globalContainer->gfx->setClipRect(); + areaName->dispatchTimer(startTick); + areaName->dispatchPaint(); + globalContainer->gfx->drawSurface((int)areaName->decX, (int)areaName->decY, areaName->getSurface()); + } + + + globalContainer->gfx->nextFrame(); + + + endTick=SDL_GetTicks64(); + deltaTick=std::max(0, static_cast(endTick) - static_cast(startTick)); + if (deltaTick<33) + SDL_Delay(33-deltaTick); + if (returnCode==-1) + { + isRunning=false; + } + if(doQuitAfterLoadSave && !showingSave) + { + isRunning=false; + } + if(doQuit) + { + if(hasMapBeenModified) + { + int ret = GAGGUI::MessageBox(globalContainer->gfx, "standard", GAGGUI::MB_THREEBUTTONS, Toolkit::getStringTable()->getString("[save before quit?]"), Toolkit::getStringTable()->getString("[Yes]"), Toolkit::getStringTable()->getString("[No]"), Toolkit::getStringTable()->getString("[Cancel]")); + if(ret == 0) + { + doQuit=false; + doQuitAfterLoadSave=true; + performAction("open save screen"); + } + else if(ret == 1) + { + isRunning=false; + } + else + { + doQuit=false; + } + } + else + { + isRunning=false; + } + } + if(doFullQuit) + { + returnCode = -1; + } + if(!isRunning) + { + SDL_Event event; + while (SDL_PollEvent(&event)); + } + } + + //globalContainer->gfx->setRes(globalContainer->graphicWidth, globalContainer->graphicHeight , 32, globalContainer->graphicFlags, (DrawableSurface::GraphicContextType)globalContainer->settings.graphicType); + return returnCode; +} + + diff --git a/src/MapEditKeyActions.cpp b/src/map/edit/MapEditKeyActions.cpp similarity index 78% rename from src/MapEditKeyActions.cpp rename to src/map/edit/MapEditKeyActions.cpp index e4fdb5fea..24c715038 100644 --- a/src/MapEditKeyActions.cpp +++ b/src/map/edit/MapEditKeyActions.cpp @@ -1,20 +1,5 @@ -/*key - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "MapEditKeyActions.h" diff --git a/src/MapEditKeyActions.h b/src/map/edit/MapEditKeyActions.h similarity index 60% rename from src/MapEditKeyActions.h rename to src/map/edit/MapEditKeyActions.h index d0c23d438..b64b2816a 100644 --- a/src/MapEditKeyActions.h +++ b/src/map/edit/MapEditKeyActions.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __MAPEDIT_KEY_ACTIONS_H -#define __MAPEDIT_KEY_ACTIONS_H +#pragma once #include "SDL.h" #include @@ -74,5 +58,3 @@ namespace MapEditKeyActions extern std::vector names; extern std::map keys; }; - -#endif diff --git a/src/map/edit/Widgets.cpp b/src/map/edit/Widgets.cpp new file mode 100644 index 000000000..057213fda --- /dev/null +++ b/src/map/edit/Widgets.cpp @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +MapEditorWidget::MapEditorWidget(MapEdit& me, const widgetRectangle& rectangle, const std::string& group, const std::string& name, const std::string& action) + : me(me), area(rectangle), group(group), name(name), action(action), enabled(false) +{ + +} + + + +void MapEditorWidget::drawSelf() +{ + if(enabled) + draw(); +} + + + +void MapEditorWidget::disable() +{ + enabled=false; +} + + + +void MapEditorWidget::enable() +{ + enabled=true; +} + + + +void MapEditorWidget::handleClick(int relMouseX, int relMouseY) +{ + me.performAction(action, relMouseX, relMouseY); +} + + + +BuildingSelectorWidget::BuildingSelectorWidget(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& building_type, bool largeSelector) : MapEditorWidget(me, area, group, name, action), building_type(building_type), largeSelector(largeSelector) +{ + +} + + + +void BuildingSelectorWidget::draw() +{ + std::string &type = building_type; + + BuildingType *bt = globalContainer->buildingsTypes.getByType(type.c_str(), me.buildingLevel, false); + if(bt==NULL || !me.isUpgradable(IntBuildingType::shortNumberFromType(type))) + bt = globalContainer->buildingsTypes.getByType(type.c_str(), 0, false); + assert(bt); + + int imgid = bt->miniSpriteImage; + int x, y; + + x=area.x; + y=area.y; + + Sprite *buildingSprite; + if (imgid >= 0) + { + buildingSprite = bt->miniSpritePtr; + } + else + { + buildingSprite = bt->gameSpritePtr; + imgid = bt->gameSpriteImage; + } + + buildingSprite->setBaseColor(me.game.teams[me.team]->color); + globalContainer->gfx->drawSprite(x, y, buildingSprite, imgid); + + // draw selection if needed + if (me.selectionName == type) + { + if (largeSelector) + globalContainer->gfx->drawSprite(x-8, y-5, globalContainer->gamegui, 8); + else + globalContainer->gfx->drawSprite(x-4, y-3, globalContainer->gamegui, 23); + } + globalContainer->gfx->finishDrawingSprite(buildingSprite, 255); + globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); +} + + + +TeamColorSelector::TeamColorSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action) + : MapEditorWidget(me, area, group, name, action) +{ + +} + + + +void TeamColorSelector::draw() +{ + for(int n=0; n<16; ++n) + { + const int xpos = area.x + (n%6)*16; + const int ypos = area.y + (n/6)*16; + if(me.game.teams[n]) + { + if(me.team==n) + globalContainer->gfx->drawFilledRect(xpos, ypos, 16, 16, Color(me.game.teams[n]->color.r, me.game.teams[n]->color.g, me.game.teams[n]->color.b, 128)); + else + globalContainer->gfx->drawFilledRect(xpos, ypos, 16, 16, me.game.teams[n]->color); + + } + } +} + + + +SingleLevelSelector::SingleLevelSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, int level, int& levelNum) + : MapEditorWidget(me, area, group, name, action), level(level), levelNum(levelNum) +{ + +} + + + +void SingleLevelSelector::draw() +{ + globalContainer->gfx->drawSprite(area.x, area.y, me.menu, 30+level-1, (level-1)==levelNum ? 128 : 255); +} + + + +PanelIcon::PanelIcon(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, int iconNumber, int panelModeHilight) + : MapEditorWidget(me, area, group, name, action), iconNumber(iconNumber), panelModeHilight(panelModeHilight) +{ + +} + + + +void PanelIcon::draw() +{ + // draw buttons + if (me.panelMode==panelModeHilight) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, iconNumber+1); + else + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, iconNumber); + +} + + + +MenuIcon::MenuIcon(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action) + : MapEditorWidget(me, area, group, name, action) +{ + +} + + + +void MenuIcon::draw() +{ + // draw buttons + if (me.showingMenuScreen) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 7); + else + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 6); + +} + + + +ZoneSelector::ZoneSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, ZoneType zoneType) + : MapEditorWidget(me, area, group, name, action), zoneType(zoneType) +{ + +} + + + +void ZoneSelector::draw() +{ + bool isSelected=false; + if(zoneType==ForbiddenZone) + { + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 13); + if(me.brushType==MapEdit::ForbiddenBrush) + isSelected=true; + } + else if(zoneType==GuardingZone) + { + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 14); + if(me.brushType==MapEdit::GuardAreaBrush) + isSelected=true; + } + else if(zoneType==ClearingZone) + { + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 25); + if(me.brushType==MapEdit::ClearAreaBrush) + isSelected=true; + } + if(me.selectionMode==MapEdit::PlaceZone && isSelected) + { + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 22); + } +} + + diff --git a/src/map/edit/WidgetsBuilding.cpp b/src/map/edit/WidgetsBuilding.cpp new file mode 100644 index 000000000..446b7eb5f --- /dev/null +++ b/src/map/edit/WidgetsBuilding.cpp @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include "TeamDisplay.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +BuildingInfoTitle::BuildingInfoTitle(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Building* building) + : MapEditorWidget(me, area, group, name, action), building(building) +{ + +} + + + +void BuildingInfoTitle::draw() +{ + Building* selBuild = building; + BuildingType *buildingType = selBuild->type; + Uint8 r, g, b; + + // draw "building" of "player" + std::string title; + std::string key = "[" + buildingType->type + "]"; + title += Toolkit::getStringTable()->getString(key.c_str()); + { + title += " ("; + title += displayPlayerName(*selBuild->owner); + title += ")"; + } + + r=160; + g=160; + b=255; + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); + int titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); + int titlePos = area.x+((area.width-titleLen)/2); + globalContainer->gfx->drawString(titlePos, area.y, globalContainer->littleFont, title.c_str()); + globalContainer->littleFont->popStyle(); +} + + + +void BuildingInfoTitle::setBuilding(Building* aBuilding) +{ + building=aBuilding; +} + + + +BuildingPicture::BuildingPicture(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Building* building) + : MapEditorWidget(me, area, group, name, action), building(building) +{ + +} + + + +void BuildingPicture::draw() +{ + Building* selBuild = building; + BuildingType *buildingType = selBuild->type; + + // building icon + Sprite *miniSprite; + int imgid; + if (buildingType->miniSpriteImage >= 0) + { + miniSprite = buildingType->miniSpritePtr; + imgid = buildingType->miniSpriteImage; + } + else + { + miniSprite = buildingType->gameSpritePtr; + imgid = buildingType->gameSpriteImage; + } + int dx = (56-miniSprite->getW(imgid))/2; + int dy = (46-miniSprite->getH(imgid))/2; + miniSprite->setBaseColor(selBuild->owner->color); + globalContainer->gfx->drawSprite(area.x+dx, area.y+dy, miniSprite, imgid); + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 18); + globalContainer->gfx->finishDrawingSprite(miniSprite, 255); + globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); +} + + + +void BuildingPicture::setBuilding(Building* aBuilding) +{ + building=aBuilding; +} + + + +TextLabel::TextLabel(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& label, bool centered, const std::string& emptyLabel) + : MapEditorWidget(me, area, group, name, action), label(label), emptyLabel(emptyLabel), centered(centered) +{ + +} + + + +void TextLabel::draw() +{ + std::string label=this->label; + if(label=="") + label=this->emptyLabel; + int titleWidth = globalContainer->littleFont->getStringWidth(label.c_str()); + int titleHeight = globalContainer->littleFont->getStringHeight(label.c_str()); + if(centered) + globalContainer->gfx->drawString(area.x+(area.width-titleWidth)/2, area.y+(area.height-titleHeight)/2, globalContainer->littleFont, label.c_str()); + else + globalContainer->gfx->drawString(area.x, area.y, globalContainer->littleFont, label.c_str()); +} + + + +void TextLabel::setLabel(const std::string& aLabel) +{ + label=aLabel; +} + + + +NumberCycler::NumberCycler(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, int maxNumber) + : MapEditorWidget(me, area, group, name, action), maxNumber(maxNumber), currentNumber(1) +{ + +} + + + +void NumberCycler::draw() +{ + std::stringstream s; + s<gfx->drawString(area.x, area.y, globalContainer->standardFont, s.str().c_str()); +} + + + +int NumberCycler::getIndex() +{ + return currentNumber-1; +} + + + +void NumberCycler::handleClick(int relMouseX, int relMouseY) +{ + currentNumber++; + if(currentNumber>maxNumber) + currentNumber=1; + MapEditorWidget::handleClick(relMouseX, relMouseY); +} + + + + +Checkbox::Checkbox(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& text, bool& isActivated) + : MapEditorWidget(me, area, group, name, action), text(text), isActivated(isActivated) +{ + +} + + + +void Checkbox::draw() +{ + globalContainer->gfx->drawRect(area.x, area.y, 16, 16, Color::white); + if(isActivated) + { + globalContainer->gfx->drawLine(area.x+4, area.y+4, area.x+12, area.y+12, Color::white); + globalContainer->gfx->drawLine(area.x+12, area.y+4, area.x+4, area.y+12, Color::white); + } + + std::string translatedText; + translatedText=Toolkit::getStringTable()->getString(text.c_str()); + + globalContainer->gfx->drawString(area.x+20, area.y, globalContainer->littleFont, translatedText); +} + + + +void Checkbox::handleClick(int relMouseX, int relMouseY) +{ + isActivated = !isActivated; + MapEditorWidget::handleClick(relMouseX, relMouseY); +} + + diff --git a/src/map/edit/WidgetsTools.cpp b/src/map/edit/WidgetsTools.cpp new file mode 100644 index 000000000..c4335f920 --- /dev/null +++ b/src/map/edit/WidgetsTools.cpp @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +BrushSelector::BrushSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, BrushTool& brushTool) + : MapEditorWidget(me, area, group, name, action), brushTool(brushTool) +{ + +} + + + +void BrushSelector::draw() +{ + brushTool.draw(area.x, area.y); +} + + + +UnitSelector::UnitSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, int unitType) + : MapEditorWidget(me, area, group, name, action), unitType(unitType) +{ + +} + + + +void UnitSelector::draw() +{ + // draw units + Sprite *unitSprite=globalContainer->units; + unitSprite->setBaseColor(me.game.teams[me.team]->color); + bool drawSelection=false; + if(unitType==WORKER) + { + if(me.selectionMode==MapEdit::PlaceUnit && me.placingUnit==MapEdit::Worker) + drawSelection=true; + globalContainer->gfx->drawSprite(area.x, area.y, unitSprite, 64); + } + else if(unitType==EXPLORER) + { + if(me.selectionMode==MapEdit::PlaceUnit && me.placingUnit==MapEdit::Explorer) + drawSelection=true; + globalContainer->gfx->drawSprite(area.x, area.y, unitSprite, 0); + } + else if(unitType==WARRIOR) + { + if(me.selectionMode==MapEdit::PlaceUnit && me.placingUnit==MapEdit::Warrior) + drawSelection=true; + globalContainer->gfx->drawSprite(area.x, area.y, unitSprite, 256); + } + if(drawSelection) + { + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 23); + } +} + + +TerrainSelector::TerrainSelector(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, TerrainType terrainType) + : MapEditorWidget(me, area, group, name, action), terrainType(terrainType) +{ + +} + + + + +void TerrainSelector::draw() +{ + if(terrainType==Grass) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->terrain, 0); + if(terrainType==Sand) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->terrain, 128); + if(terrainType==Water) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->terrain, 259); + if(terrainType==Wheat) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 19); + if(terrainType==Trees) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 2); + if(terrainType==Stone) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 34); + if(terrainType==Algae) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 44); + if(terrainType==Papyrus) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 24); + if(terrainType==CherryTree) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 54); + if(terrainType==OrangeTree) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 59); + if(terrainType==PruneTree) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 64); + if(me.terrainType==terrainType) + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 22); + if (terrainType == Grass || terrainType == Sand || terrainType == Water) + globalContainer->gfx->finishDrawingSprite(globalContainer->terrain, 255); + else + globalContainer->gfx->finishDrawingSprite(globalContainer->ressources, 255); + if (me.terrainType == terrainType) + globalContainer->gfx->finishDrawingSprite(globalContainer->gamegui, 255); +} + + + +BlueButton::BlueButton(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& text) + : MapEditorWidget(me, area, group, name, action), text(text), selected(false) +{ + +} + + + +void BlueButton::draw() +{ + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 12); + if(selected) + globalContainer->gfx->drawFilledRect(area.x+9, area.y+3, 94, 10, 128, 128, 192); + + std::string translatedText; + translatedText=Toolkit::getStringTable()->getString(text.c_str()); + int len=globalContainer->littleFont->getStringWidth(translatedText.c_str()); + int h=globalContainer->littleFont->getStringHeight(translatedText.c_str()); + globalContainer->gfx->drawString(area.x+9+((94-len)/2), area.y+((16-h)/2), globalContainer->littleFont, translatedText); +} + + + +void BlueButton::setSelected() +{ + selected=true; +} + + + +void BlueButton::setUnselected() +{ + selected=false; +} + + + +PlusIcon::PlusIcon(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action) + : MapEditorWidget(me, area, group, name, action) +{ +} + + + +void PlusIcon::draw() +{ + globalContainer->gfx->drawFilledRect(area.x, area.y, 32, 32, Color(75,0,200)); + globalContainer->gfx->drawRect(area.x, area.y, 32, 32, Color::white); + globalContainer->gfx->drawFilledRect(area.x + 15, area.y + 6, 2, 20, Color::white); + globalContainer->gfx->drawFilledRect(area.x + 6, area.y + 15, 20, 2, Color::white); +} + + + +MinusIcon::MinusIcon(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action) + : MapEditorWidget(me, area, group, name, action) +{ + +} + + + +void MinusIcon::draw() +{ + globalContainer->gfx->drawFilledRect(area.x, area.y, 32, 32, Color(75,0,200)); + globalContainer->gfx->drawRect(area.x, area.y, 32, 32, Color::white); + globalContainer->gfx->drawFilledRect(area.x + 6, area.y + 15, 20, 2, Color::white); +} + + diff --git a/src/map/edit/WidgetsUnit.cpp b/src/map/edit/WidgetsUnit.cpp new file mode 100644 index 000000000..5d701f739 --- /dev/null +++ b/src/map/edit/WidgetsUnit.cpp @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// Copyright (C) 2006 Bradley Arsenault + +#include +#include +#include +#include "GameGUILoadSave.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "MapEdit.h" +#include "MapEditKeyActions.h" +#include "ScriptEditorScreen.h" +#include +#include +#include +#include "TeamDisplay.h" +#include "UnitDisplayNames.h" +#include "UnitEditorScreen.h" +#include "Unit.h" +#include "UnitType.h" +#include "Utilities.h" +#include "FertilityCalculatorDialog.h" +#include "GUIMessageBox.h" +#include "SDLCompat.h" + +UnitInfoTitle::UnitInfoTitle(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Unit* unit) + : MapEditorWidget(me, area, group, name, action), unit(unit) +{ + +} + + + +void UnitInfoTitle::draw() +{ + const int xpos=area.x; + const int ypos=area.y; + Unit* u=unit; + + // draw "unit of player" title + Uint8 r, g, b; + std::string title; + title += getUnitName(u->typeNum); + title += " ("; + + title += displayPlayerName(*u->owner); + title += ")"; + + r=160; + g=160; + b=255; + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); + int titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); + int titlePos = xpos+((128-titleLen)/2); + globalContainer->gfx->drawString(titlePos, ypos, globalContainer->littleFont, title.c_str()); + globalContainer->littleFont->popStyle(); +} + + + +void UnitInfoTitle::setUnit(Unit* aUnit) +{ + unit=aUnit; +} + + + +UnitPicture::UnitPicture(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Unit* unit) + : MapEditorWidget(me, area, group, name, action), unit(unit) +{ + +} + + + +void UnitPicture::draw() +{ + const int xpos=area.x; + const int ypos=area.y; + + // draw unit's image + int imgid; + UnitType *ut=unit->race->getUnitType(unit->typeNum, 0); + assert(unit->action>=0); + assert(unit->actionstartImage[unit->action]; + + int dir=unit->direction; + int delta=unit->delta; + assert(dir>=0); + assert(dir<9); + assert(delta>=0); + assert(delta<256); + if (dir==8) + { + imgid+=8*(delta>>5); + } + else + { + imgid+=8*dir; + imgid+=(delta>>5); + } + + Sprite *unitSprite=globalContainer->units; + unitSprite->setBaseColor(unit->owner->color); + int decX = (32-unitSprite->getW(imgid))/2; + int decY = (32-unitSprite->getH(imgid))/2; + globalContainer->gfx->drawSprite(xpos+12+decX, ypos+7+decY, unitSprite, imgid); + globalContainer->gfx->drawSprite(xpos, ypos, globalContainer->gamegui, 18); +} + + + +void UnitPicture::setUnit(Unit* aUnit) +{ + unit=aUnit; +} + + + +FractionValueText::FractionValueText(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& label, Sint32* numerator, Sint32* denominator) + : MapEditorWidget(me, area, group, name, action), label(label), numerator(numerator), denominator(denominator), isDenominatorPreset(false) +{ + +} + + + +FractionValueText::FractionValueText(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, const std::string& label, Sint32* numerator, Sint32 denominator) + : MapEditorWidget(me, area, group, name, action), label(label), numerator(numerator), denominator(new Sint32(denominator)), isDenominatorPreset(true) +{ + +} + + + +FractionValueText::~FractionValueText() +{ + if(isDenominatorPreset) + delete denominator; +} + + + +void FractionValueText::draw() +{ + globalContainer->gfx->drawString(area.x, area.y, globalContainer->littleFont, FormatableString("%0: %1/%2").arg(Toolkit::getStringTable()->getString(label.c_str())).arg(*numerator).arg(*denominator).c_str()); +} + + + +void FractionValueText::setValues(Sint32* aNumerator, Sint32* aDenominator) +{ + numerator=aNumerator; + denominator=aDenominator; +} + + + +void FractionValueText::setValues(Sint32* aNumerator) +{ + numerator=aNumerator; +} + + + +ValueScrollBox::ValueScrollBox(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Sint32* value, Sint32* max) + : MapEditorWidget(me, area, group, name, action), value(value), max(max), isMaxPreset(false) +{ + +} + + + +ValueScrollBox::ValueScrollBox(MapEdit& me, const widgetRectangle& area, const std::string& group, const std::string& name, const std::string& action, Sint32* value, Sint32 max) + : MapEditorWidget(me, area, group, name, action), value(value), max(new Sint32(max)), isMaxPreset(true) +{ + +} + + + +ValueScrollBox::~ValueScrollBox() +{ + if(isMaxPreset) + delete max; +} + + + +void ValueScrollBox::draw() +{ + //Sometimes a scrollbox gets initiated with max-value 0. A turret construction site has 0/0 stone and 0/0 shots. To not run into arithmetic exceptions those cases are treated here. + if((*max) != 0) + { + globalContainer->gfx->setClipRect(area.x, area.y, 112, 16); + globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 9); + int size=((*value)*92)/(*max); + globalContainer->gfx->setClipRect(area.x+10, area.y, size, 16); + globalContainer->gfx->drawSprite(area.x+10, area.y+3, globalContainer->gamegui, 10); + globalContainer->gfx->setClipRect(); + } +} + + + +void ValueScrollBox::handleClick(int relMouseX, int relMouseY) +{ + if(relMouseX<10) + (*value)=std::max((*value)-1, 0); + else if(relMouseX>102) + (*value)=std::min((*value)+1, (*max)); + else + (*value)=int(float(relMouseX-10) * (float(*max)/float(92))+0.5); + MapEditorWidget::handleClick(relMouseX, relMouseY); +} + + + +void ValueScrollBox::setValues(Sint32* aValue, Sint32* aMax) +{ + value=aValue; + max=aMax; +} + + + +void ValueScrollBox::setValues(Sint32* aValue) +{ + value=aValue; +} + + diff --git a/src/map/generator/GameMaps.cpp b/src/map/generator/GameMaps.cpp new file mode 100644 index 000000000..bdd61da81 --- /dev/null +++ b/src/map/generator/GameMaps.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +bool Game::oldMakeIslandsMap(MapGenerationDescriptor &descriptor) +{ + for (int s=0; sbuildingsTypes.getTypeNum("swarm", 0, false); + if (!checkRoomForBuilding(descriptor.bootX[s], descriptor.bootY[s], globalContainer->buildingsTypes.get(typeNum), -1, false)) + { + if (verbose) + printf("Failed to add swarm of team %d\n", s); + return false; + } + teams[s]->startPosX=descriptor.bootX[s]; + teams[s]->startPosY=descriptor.bootY[s]; + Building *b=addBuilding(descriptor.bootX[s], descriptor.bootY[s], typeNum, s); + assert(b); + for (int i=0; icreateLists(); + } + map.smoothRessources(descriptor.oldIslandSize/10); + return true; +} + +bool Game::makeRandomMap(MapGenerationDescriptor &descriptor) +{ + for (int s=0; sbuildingsTypes.getTypeNum("swarm", 0, false); + if (!checkRoomForBuilding(descriptor.bootX[s], descriptor.bootY[s], globalContainer->buildingsTypes.get(typeNum), s, false)) + { + if (verbose) + printf("Failed to add swarm of team %d\n", s); + return false; + } + teams[s]->startPosX=descriptor.bootX[s]; + teams[s]->startPosY=descriptor.bootY[s]; + Building *b=addBuilding(descriptor.bootX[s], descriptor.bootY[s], typeNum, s); + assert(b); + for (int i=0; icreateLists(); + } + return true; +} + diff --git a/src/map/generator/Generator.cpp b/src/map/generator/Generator.cpp new file mode 100644 index 000000000..9881320a9 --- /dev/null +++ b/src/map/generator/Generator.cpp @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +bool MapGenerator::generateMap(Game& game, MapGenerationDescriptor &descriptor) +{ + if (verbose) + printf("Generating map, please wait ....\n"); + game.map.setSize(descriptor.wDec, descriptor.hDec); + game.map.setGame(&game); + setRandomSyncRandSeed(); + + switch (descriptor.methode) + { + case MapGenerationDescriptor::eUNIFORM: + game.map.makeHomogenMap(descriptor.terrainType); + game.addTeam(); + break; + case MapGenerationDescriptor::eSWAMP: + case MapGenerationDescriptor::eISLANDS: + case MapGenerationDescriptor::eRIVER: + case MapGenerationDescriptor::eCRATERLAKES: + if (!game.map.makeRandomMap(descriptor)) + return false; + if (!game.makeRandomMap(descriptor)) + return false; + break; + case MapGenerationDescriptor::eCONCRETEISLANDS: + if (!computeConcreteIslands(game, descriptor)) + return false; + break; + case MapGenerationDescriptor::eISLES: + if (!computeIsles(game, descriptor)) + return false; + break; + case MapGenerationDescriptor::eOLDRANDOM: + if (!game.map.oldMakeRandomMap(descriptor)) + return false; + if (!game.makeRandomMap(descriptor)) + return false; + break; + case MapGenerationDescriptor::eOLDISLANDS: + if (!game.map.oldMakeIslandsMap(descriptor)) + return false; + if (!game.oldMakeIslandsMap(descriptor)) + return false; + break; + + default: + assert(false); + } + + // compile script + game.sgslScript.compileScript(&game); + + if (verbose) + printf(".... map generated.\n"); + return true; +} + + +bool MapGenerator::computeConcreteIslands(Game& game, MapGenerationDescriptor& descriptor) +{ + game.map.makeHomogenMap(descriptor.terrainType); + for(int i=0; i grid(game.map.getW() * game.map.getH(), 0); + std::vector teamPoints; + std::vector weights1; + std::vector weights2; + std::vector teamAreaNumbers; + std::vector islandAreaNumbers; + + //Add in team bases + for(int i=0; i areaNumbers = teamAreaNumbers; + areaNumbers.insert(areaNumbers.end(), islandAreaNumbers.begin(), islandAreaNumbers.end()); + + // Initially divide up the land + splitUpPoints(game, grid, 0, teamPoints, weights1); + splitUpArea(game, grid, 0, teamPoints, weights2, areaNumbers); + + // Create a heightmap that will be used to give the map a rough edge + std::vector heights(game.map.getW() * game.map.getH(), 75); + adjustHeightmapFromPerlinNoise(game, heights, 15); + + // Compute the distance of every square from the border + std::vector sources; + findBorderPoints(game, grid, sources); + std::vector obstacles; + std::vector distances; + computeDistances(game, sources, obstacles, distances); + + // Locations near the border are deaper, thus causing more water + for(int x=0; x=45 && total_height<=55) + game.map.setUMatPos(x, y, SAND, 1); + else + game.map.setUMatPos(x, y, GRASS, 1); + } + } + game.map.controlSand(); + + // Go through the map again and place alga + for(int x=0; x areaWeights; + std::vector areaNumbers; + for(int j=0; j<2; ++j) + { + areaWeights.push_back(1); + areaNumbers.push_back(areaNumber); + areaNumber+=1; + } + + // Divide the area. Its possible the area will be so small it can't be used + if(divideUpArea(game, grid, islandAreaNumbers[i], areaWeights, areaNumbers)) + { + // Fill in wheat + std::vector points; + getAllPoints(game, grid, areaNumbers[0], points); + fillInResource(game, points, CORN, 2); + points.clear(); + + // Place some fruit + int fruit_n = syncRand()%6+1; + getAllPoints(game, grid, areaNumbers[1], points); + chooseRandomPoints(game, points, fruit_n); + for(unsigned int j=0; jcreateLists(); + } + return true; +} + + + +bool MapGenerator::computeIsles(Game& game, MapGenerationDescriptor& descriptor) +{ + game.map.makeHomogenMap(descriptor.terrainType); + for(int i=0; i grid(game.map.getW() * game.map.getH(), 0); + + // Do the starting locations of the teams + std::vector teamPoints; + std::vector teamWeights; + std::vector teamAreaNumbers; + for(int i=0; i heightmap(game.map.getW() * game.map.getH(), 50); + std::vector teamAreaPoints; + getAllOtherPoints(game, grid, 0, teamAreaPoints); + std::vector obstacles; + + std::vector distances; + computeDistances(game, teamAreaPoints, obstacles, distances); + + // Stamp out the team areas + for(int x=0; x 1 && d <= 11) + heightmap[y * game.map.getW() + x] += (11-d)*10; + else if(d == 1) + heightmap[y * game.map.getW() + x] += 100; + } + } + + // Connect each teams area to each other players area + std::vector connectorPoints; + int connectorArea = areaNumber; + areaNumber+=1; + for(int i=0; i teamI; + std::vector teamJ; + getAllPoints(game, grid, teamAreaNumbers[i], teamI); + getAllPoints(game, grid, teamAreaNumbers[j], teamJ); + chooseRandomPoints(game, teamI, 1); + chooseRandomPoints(game, teamJ, 1); + + // Traverse between the two points + std::vector linePoints; + getAllPointsLine(game, teamI[0].x, teamI[0].y, teamJ[0].x, teamJ[0].y, linePoints); + // If a connection can be made without going through another teams area, then do it + bool failed=false; + for(unsigned int p=0; p5) + { + grid[ny * game.map.getW() + nx] = connectorArea; + } + } + } + } + } + } + } + computeDistances(game, connectorPoints, obstacles, distances); + + // Stamp out the connectors + for(int x=0; x 1 && d <= 4) + heightmap[y * game.map.getW() + x] += (4-d)*33; + else if(d == 1) + heightmap[y * game.map.getW() + x] += 100; + } + } + + + // Use the heightmap to put in water, grass, and sand + adjustHeightmapFromPerlinNoise(game, heightmap, 45); + for(int x=0; x95 && total_height<105) + game.map.setUMatPos(x, y, SAND, 1); + else + game.map.setUMatPos(x, y, GRASS, 1); + } + } + game.map.controlSand(); + + // Reset the grid, and recompute within the boundaries of the various islands + for(int x=0; x connectorDistances = distances; + + // For each team, find a point just off the coast and place algae there + for(int i=0; i sources; + getAllPoints(game, grid, teamAreaNumbers[i], sources); + computeDistances(game, sources, obstacles, distances); + std::vector possible; + for(int x=0; x 4) + { + possible.push_back(MapGeneratorPoint(x, y)); + } + } + } + if(possible.size() == 0) + { + return false; + } + int r = syncRand() % possible.size(); + for(int x=-2; x<=2; ++x) + { + int nx = game.map.normalizeX(possible[r].x + x); + for(int y=-2; y<=2; ++y) + { + int ny = game.map.normalizeY(possible[r].y + y); + game.map.setRessource(nx, ny, ALGA, 1); + } + } + } + + if(!divideUpPlayerLands(game, descriptor, grid, teamAreaNumbers, areaNumber)) + { + return false; + } + + // Initialize final team info + for(int i=0; icreateLists(); + } + return true; +} + + + diff --git a/src/map/generator/GeneratorDivide.cpp b/src/map/generator/GeneratorDivide.cpp new file mode 100644 index 000000000..9e50101be --- /dev/null +++ b/src/map/generator/GeneratorDivide.cpp @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +bool MapGenerator::divideUpPlayerLands(Game& game, MapGenerationDescriptor& descriptor, std::vector& grid, std::vector& teamAreaNumbers, int& areaNumber) +{ + int typeNum=globalContainer->buildingsTypes.getTypeNum("swarm", 0, false); + BuildingType *swarm = globalContainer->buildingsTypes.get(typeNum); + + //Compute the distances from water + std::vector sources; + std::vector obstacles; + std::vector distances; + obstacles.clear(); + getAllPoints(game, grid, 0, sources); + computeDistances(game, sources, obstacles, distances); + + //Create a new heightmap from noise and distance to water + std::vector heightmap(game.map.getW() * game.map.getH(), 50); + adjustHeightmapFromPerlinNoise(game, heightmap, 5); + for(int x=0; x areaWeights; + std::vector areaNumbers; + for(int j=0; j<12; ++j) + { + areaWeights.push_back(1); + areaNumbers.push_back(areaNumber); + areaNumber+=1; + } + + // Divide the area. Its possible the area will be so small it can't be used + if(divideUpArea(game, grid, teamAreaNumbers[i], areaWeights, areaNumbers)) + { + // Sort the list of areas based on how close they are to water + std::vector areaDistances(areaNumbers.size()); + std::vector areaIndexes(areaNumbers.size()); + for(unsigned int j=0; j wheatWoodPoints; + std::vector wheatPoints; + //std::vector woodPoints; + getAllPoints(game, grid, areaNumbers[3], wheatWoodPoints); + getAllPoints(game, grid, areaNumbers[4], wheatWoodPoints); + getAllPoints(game, grid, areaNumbers[5], wheatWoodPoints); + adjustHeightmapFromPoints(game, wheatWoodPoints, heightmap, 10); + for(unsigned int j=0; j 50) + { + game.map.setRessource(wheatWoodPoints[j].x, wheatWoodPoints[j].y, WOOD, 1); + //woodPoints.push_back(wheatWoodPoints[j]); + } + } + wheatWoodPoints.clear(); + + // Place wheat + getAllPoints(game, grid, areaNumbers[0], wheatWoodPoints); + getAllPoints(game, grid, areaNumbers[1], wheatWoodPoints); + getAllPoints(game, grid, areaNumbers[2], wheatWoodPoints); + adjustHeightmapFromPoints(game, wheatWoodPoints, heightmap, 10); + for(unsigned int j=0; j 50) + { + game.map.setRessource(wheatWoodPoints[j].x, wheatWoodPoints[j].y, CORN, 1); + wheatPoints.push_back(wheatWoodPoints[j]); + } + } + + + // These are all points in the base + std::vector baseLocations; + getAllPoints(game, grid, areaNumbers[6], baseLocations); + getAllPoints(game, grid, areaNumbers[7], baseLocations); + getAllPoints(game, grid, areaNumbers[8], baseLocations); + getAllPoints(game, grid, areaNumbers[9], baseLocations); + getAllPoints(game, grid, areaNumbers[10], baseLocations); + getAllPoints(game, grid, areaNumbers[11], baseLocations); + + // Place stone + int numberOfStone = 6; + std::vector stoneLocations = baseLocations; + chooseRandomPoints(game, stoneLocations, numberOfStone); + for(unsigned int j=0; j wheatDistance; + computeDistances(game, wheatPoints, obstacles, wheatDistance); + + // Only consider points between 1 and 4 squares from wheat + std::vector startingLocations; + for(unsigned int j=0; j= 1 && minValue <= 2) + { + startingLocations.push_back(baseLocations[j]); + } + } + + // Place swarms + chooseFreeForBuildingSquares(game, startingLocations, swarm, i); + if(startingLocations.size() == 0) + { + return false; + } + int chosen = syncRand()%startingLocations.size(); + Building* b = addBuilding(game, startingLocations[chosen].x, startingLocations[chosen].y, i, IntBuildingType::SWARM_BUILDING, 1, false); + if(b == NULL) + { + return false; + } + + // Set the initial viewport location + game.teams[i]->startPosX=b->posX; + game.teams[i]->startPosY=b->posY; + game.teams[i]->startPosSet=3; + + // Place units around the swarm + std::vector unitLocations = baseLocations; + chooseFreeForGroundUnits(game, unitLocations, i); + chooseTouchingBuilding(game, unitLocations, b); + chooseRandomPoints(game, unitLocations, descriptor.nbWorkers); + for(unsigned int n=0; n& grid, int areaN, std::vector& weights, std::vector& areaNumbers) +{ + std::vector points; + std::vector splitWeights; + for(unsigned int i=0; i& grid, int areaN, int x, int y, int width, int height) +{ + int h2 = (height/2) * (height/2); + int w2 = (width/2) * (width/2); + int t2 = h2 * w2; + for(int px = -(width/2); px < (width/2); ++px) + { + int nx = game.map.normalizeX(x + px); + int px2 = px*px*h2; + for(int py = -(height/2); py < (height/2); ++py) + { + int ny = game.map.normalizeY(y + py); + int py2 = py*py*w2; + if(px2 + py2 < t2) + { + grid[ny * game.map.getW() + nx] = areaN; + } + } + } +} + + + diff --git a/src/map/generator/GeneratorHeightmap.cpp b/src/map/generator/GeneratorHeightmap.cpp new file mode 100644 index 000000000..06daaa651 --- /dev/null +++ b/src/map/generator/GeneratorHeightmap.cpp @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +void MapGenerator::adjustHeightmapFromPoints(Game& game, std::vector& points, std::vector& heightmap, int value) +{ + for(unsigned int i=0; i& heights, int spread) +{ + HeightMap noise(game.map.getW(), game.map.getH()); + noise.makePlain(4); + for(int x=0; x& sources, std::vector& obstacles, std::vector& heightmap) +{ + std::queue places; + heightmap.clear(); + heightmap.resize(game.map.getW() * game.map.getH(), 0); + for(unsigned int i=0; i> wDec; // Calculate the coordinates of + size_t x = deltaAddrG & wMask; // the current field and of the + + size_t yu = ((y - 1) & hMask); // fields next to it. + size_t yd = ((y + 1) & hMask); // We live on a torus! If we are on + size_t xl = ((x - 1) & wMask); // the "last line" of the map, the + size_t xr = ((x + 1) & wMask); // next line is the line 0 again. + + int g = heightmap[(y << wDec) | x] + 1; + + size_t deltaAddrC[8]; + int *addr; + int side; + + deltaAddrC[0] = (yu << wDec) | xl; // Calculate the positions of the + deltaAddrC[1] = (yu << wDec) | x ; // 8 fields next to us from their + deltaAddrC[2] = (yu << wDec) | xr; // coordinates. + deltaAddrC[3] = (y << wDec) | xr; + deltaAddrC[4] = (yd << wDec) | xr; + deltaAddrC[5] = (yd << wDec) | x ; + deltaAddrC[6] = (yd << wDec) | xl; + deltaAddrC[7] = (y << wDec) | xl; + for (int ci=0; ci<8; ci++) // Check for each of this fields if we + { // can improve its gradient value + addr = &heightmap[deltaAddrC[ci]]; + side = *addr; + if (side==0) + { + *addr = g; + places.push(deltaAddrC[ci]); + } + } + } +} + + + +int MapGenerator::computeAverageDistance(Game& game, std::vector& grid, int areaN, const std::vector& heightmap) +{ + long total = 0; + int count = 0; + for(int x=0; x 0 ? total/count : 0; +} + + + +Building* MapGenerator::addBuilding(Game& game, int x, int y, int team, int type, int level, bool underConstruction) +{ + std::string name = IntBuildingType::typeFromShortNumber(type); + int typeNum=globalContainer->buildingsTypes.getTypeNum(name, level-1, underConstruction); + BuildingType *bt = globalContainer->buildingsTypes.get(typeNum); + if(bt == NULL) + { + return NULL; + } + + if (game.checkRoomForBuilding(x, y, bt, team, false)) + { + if(bt->maxUnitWorking) + return game.addBuilding(x, y, typeNum, team, 1, 0); + else + return game.addBuilding(x, y, typeNum, team, 0, 0); + } + return NULL; +} + + + diff --git a/src/map/generator/GeneratorPoints.cpp b/src/map/generator/GeneratorPoints.cpp new file mode 100644 index 000000000..4118133c9 --- /dev/null +++ b/src/map/generator/GeneratorPoints.cpp @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +void MapGenerator::getAllPoints(Game& game, std::vector& grid, int areaN, std::vector& points) +{ + for(int x=0; x& grid, int areaN, std::vector& points) +{ + for(int x=0; x& points) +{ + int startX = x1; + int endX = x2; + int startY = y1; + int endY = y2; + + int dirX = (endX > startX ? 1 : -1); + int distX = std::abs(endX - startX); + if(distX > game.map.getW()/2) + { + dirX = -dirX; + distX = game.map.getW() - distX; + } + + int dirY = (endY > startY ? 1 : -1); + int distY = std::abs(endY - startY); + if(distY > game.map.getH()/2) + { + dirY = -dirY; + distY = game.map.getH() - distY; + } + + if(distX > distY) + { + int px = 0; + int py = 0; + int y = startY; + for(int x=startX; x!=endX;) + { + px+=1; + points.push_back(MapGeneratorPoint(x, y)); + if(std::abs(px * distY - py * distX) > std::abs(px * distY - (py+1) * distX)) + { + y=game.map.normalizeY(y+dirY); + points.push_back(MapGeneratorPoint(x, y)); + py+=1; + } + x=game.map.normalizeX(x+dirX); + } + } + else + { + int px = 0; + int py = 0; + int x = startX; + for(int y=startY; y!=endY;) + { + py+=1; + points.push_back(MapGeneratorPoint(x, y)); + if(std::abs(py * distX - px * distY) > std::abs(py * distX - (px+1) * distY)) + { + x=game.map.normalizeX(x+dirX); + points.push_back(MapGeneratorPoint(x, y)); + px+=1; + } + y=game.map.normalizeY(y+dirY); + } + } +} + + + +void MapGenerator::findBorderPoints(Game& game, std::vector& grid, std::vector& points) +{ + for(int x=0; x& points, int ressourceType, int maxFillSize) +{ + for(unsigned int n=0; n& points, int n) +{ + n = std::min(int(points.size()), n); + for(int i=0; i& points, BuildingType* type, int team) +{ + std::vector newPoints; + for(unsigned int n=0; n& points, int team) +{ + std::vector newPoints; + for(unsigned int n=0; n& points, Building* building) +{ + std::vector newPoints; + for(unsigned int n=0; ngid)) + { + newPoints.push_back(MapGeneratorPoint(points[n].x, points[n].y)); + } + } + points = newPoints; +} + + + diff --git a/src/map/generator/GeneratorSplit.cpp b/src/map/generator/GeneratorSplit.cpp new file mode 100644 index 000000000..9cd85fd06 --- /dev/null +++ b/src/map/generator/GeneratorSplit.cpp @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +int MapGenerator::splitUpPoints(Game& game, std::vector& grid, int areaN, std::vector& points, std::vector& weights) +{ + std::vector startingPoints; + for(int x=0; x obstacles; + getAllOtherPoints(game, grid, areaN, obstacles); + std::vector sources; + sources.push_back(startingPoints[n]); + std::vector heights; + computeDistances(game, sources, obstacles, heights); + sources.clear(); + + for(unsigned int i=0; i possible; + for(int x=0; x max) + { + max = h; + possible.clear(); + } + if(h >= max) + { + possible.push_back(MapGeneratorPoint(x, y)); + } + } + } + int n = syncRand() % possible.size(); + points[i] = possible[n]; + sources.push_back(points[i]); + computeDistances(game, sources, obstacles, heights); + } + startingPoints.clear(); + heights.clear(); + sources.clear(); + obstacles.clear(); + + bool cont=true; + int minDist = boost::integer_traits::const_max; + while(cont) + { + minDist = boost::integer_traits::const_max; + bool changed=false; + for(unsigned int i=0; i::const_max; + for(unsigned int j=0; j::const_max; + bool invalid=false; + for(unsigned int j=0; j::iterator i = squares[p].begin(); + std::advance(i, randLocation); + squares[p].insert(i, deltaAddrC[ci]); + } + } + } + } + if(!found) + cont = false; + } +} + + + diff --git a/src/HeightMapGenerator.cpp b/src/map/generator/HeightMapGenerator.cpp similarity index 89% rename from src/HeightMapGenerator.cpp rename to src/map/generator/HeightMapGenerator.cpp index 54269e9a5..3cff935c4 100644 --- a/src/HeightMapGenerator.cpp +++ b/src/map/generator/HeightMapGenerator.cpp @@ -1,30 +1,12 @@ -/*************************************************************************** - * HeightMapGenerator.cpp - * - * Sat Jan 14 11:45:30 2006 - * Copyright 2006 Leo Wandersleb - * Email: Leo.Wandersleb@gmx.de - ****************************************************************************/ - -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Leo Wandersleb #include "GlobalContainer.h" #include "HeightMapGenerator.h" #include +#include +#include +#include #include "PerlinNoise.h" /// these faders are factors to be applieable to heightfields. they map (0,0)-(w,h) to [0..1] diff --git a/src/HeightMapGenerator.h b/src/map/generator/HeightMapGenerator.h similarity index 74% rename from src/HeightMapGenerator.h rename to src/map/generator/HeightMapGenerator.h index 99d908a67..5d3081643 100644 --- a/src/HeightMapGenerator.h +++ b/src/map/generator/HeightMapGenerator.h @@ -1,29 +1,7 @@ -/*************************************************************************** - * HeightMapGenerator.h - * - * Sun Jan 8 17:34:38 2006 - * Copyright 2006 Leo Wandersleb - * Email: Leo.Wandersleb@gmx.de - ****************************************************************************/ - -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2006 Leo Wandersleb -#ifndef _HEIGHTMAPGENERATOR_H -#define _HEIGHTMAPGENERATOR_H +#pragma once #include "PerlinNoise.h" @@ -74,5 +52,3 @@ class HeightMap /// class to generate heightmaps to decide where to put resource void stampOutput(char * filename); /// generates the file ~/.glob2/filename and writes the raw 0..255 values of stamp to it. to see it, use convert -size [width]x[height] -depth 8 gray:[filename] test.png where with==height as _stamp is always a square void normalize(); /// fits the values of _map to [0, 1] }; - -#endif /* _HEIGHTMAPGENERATOR_H */ diff --git a/src/MapGenerationDescriptor.cpp b/src/map/generator/MapGenerationDescriptor.cpp similarity index 77% rename from src/MapGenerationDescriptor.cpp rename to src/map/generator/MapGenerationDescriptor.cpp index 06da0bced..7023e74be 100644 --- a/src/MapGenerationDescriptor.cpp +++ b/src/map/generator/MapGenerationDescriptor.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include #include @@ -189,45 +173,45 @@ Uint32 MapGenerationDescriptor::checkSum() cs^=wDec+(hDec<<16); cs^=(Sint32)terrainType; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs^=(Sint32)methode; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= waterRatio; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= sandRatio; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= grassRatio; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= desertRatio; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= wheatRatio; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= fruitRatio; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= woodRatio; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= algaeRatio; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= stoneRatio; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= riverDiameter; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= craterDensity; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= extraIslands; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= smooth; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= oldIslandSize; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= oldBeach; - cs=(cs<<31)|(cs>>1); + cs=rotr1(cs); cs ^= logRepeatAreaTimes; for (unsigned i=0; i>1); + cs=rotr1(cs); cs^=nbWorkers; cs^=nbTeams<<5; diff --git a/src/MapGenerationDescriptor.h b/src/map/generator/MapGenerationDescriptor.h similarity index 62% rename from src/MapGenerationDescriptor.h rename to src/map/generator/MapGenerationDescriptor.h index ad585da0a..41ccd9fbc 100644 --- a/src/MapGenerationDescriptor.h +++ b/src/map/generator/MapGenerationDescriptor.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __MAP_GENERATION_DESCRIPTOR_H -#define __MAP_GENERATION_DESCRIPTOR_H +#pragma once #include "Ressource.h" #include "TerrainType.h" @@ -89,6 +72,3 @@ class MapGenerationDescriptor //! Serialized form of MapGenerationDescriptor Uint8 data[DATA_SIZE]; }; - - -#endif diff --git a/src/MapGenerator.h b/src/map/generator/MapGenerator.h similarity index 74% rename from src/MapGenerator.h rename to src/map/generator/MapGenerator.h index ff3f587a5..2b0e288d8 100644 --- a/src/MapGenerator.h +++ b/src/map/generator/MapGenerator.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef MapGenerator_h -#define MapGenerator_h +#pragma once #include "MapGenerationDescriptor.h" @@ -71,9 +53,6 @@ class MapGenerator ///This function computes all of points that are borders void findBorderPoints(Game& game, std::vector& grid, std::vector& points); - ///This function sets all given points as a specific area on the grid - void setAsArea(Game& game, std::vector& grid, int areaN, std::vector& points); - ///This function fills all given points area with a certain ressource. It will fill in a randomly sized ///square over each grid space no larger than maxFillSize void fillInResource(Game& game, std::vector& points, int ressourceType, int maxFillSize); @@ -102,30 +81,12 @@ class MapGenerator void computeDistances(Game& game, std::vector& sources, std::vector& obstacles, std::vector& heightmap); ///Computes the average height/distance of a area on a heightmap - int computeAverageDistance(Game& game, std::vector& grid, int areaN, std::vector heightmap); - - //This function computes and prints the percentage of the map allocated to each area - void computePercentageOfAreas(Game& game, std::vector& grid); - - //This function takes a grid, and joins areas that share borders such that you get a smaller number of areas. - //The target number of areas should be an integer devisor of the areas being joined - void joinAreas(Game& game, std::vector& grid, std::vector toBeJoined, std::vector target); + int computeAverageDistance(Game& game, std::vector& grid, int areaN, const std::vector& heightmap); ///Adds a building to the map with the given typenum, level, under construction, team and location. ///Returns the pointer if it could, NULL otherwise Building* addBuilding(Game& game, int x, int y, int team, int typenum, int level, bool underConstruction); - ///This is a node used for the joinAreas algorithm - class Node - { - public: - std::vector original; - std::vector borders; - }; - - ///This is a recursive function used by the joinAreas algorithm - bool joinLoop(Game& game, std::vector nodes, std::vector& result, int numberOfJoins); - static const bool verbose=false; }; @@ -165,9 +126,3 @@ class MapGeneratorPoint return x < rhs.x; } }; - - - - - -#endif diff --git a/src/map/generator/MapHomogen.cpp b/src/map/generator/MapHomogen.cpp new file mode 100644 index 000000000..e286379d0 --- /dev/null +++ b/src/map/generator/MapHomogen.cpp @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +///generates a map that is of one terrain type only +void Map::makeHomogenMap(TerrainType terrainType) +{ + for (int y=0; y=0)&&(d<40)&&(syncRand()&4)) + { + if (l<=(int)(syncRand()&3)) + setTerrain(x, y, d+273); + else + { + // we extand ressource: + int dx, dy; + Unit::dxDyFromDirection(syncRand()&7, &dx, &dy); + int nx=x+dx; + int ny=y+dy; + if (getGroundUnit(nx, ny)==NOGUID) + if (((r==WOOD||r==CORN||r==STONE)&&isGrass(nx, ny))||((r==ALGA)&&isWater(nx, ny))) + setTerrain(nx, ny, 272+(r*10)+((syncRand()&1)*5)); + } + } + } +} + +void simulateRandomMap(int smooth, double baseWater, double baseSand, double baseGrass, double *finalWater, double *finalSand, double *finalGrass) +{ + int w=32<<(smooth>>2); + int h=w; + int s=w*h; + int m=s-1; + std::vector undermap(w*h); + + int totalRatio=0x7FFF; + int waterRatio=(int)(baseWater*((double)totalRatio)); + int sandRatio =(int)(baseSand *((double)totalRatio)); + int grassRatio=(int)(baseGrass*((double)totalRatio)); + totalRatio=waterRatio+sandRatio+grassRatio; + + if(totalRatio==0) + { + waterRatio = 1; + sandRatio = 1; + grassRatio = 1; + totalRatio = 3; + } + + + /// First, we create a fully random patchwork: + for (int y=0; y finalWaters(n); + std::vector finalSands(n); + std::vector finalGrasses(n); + + for (int i=0; ifinalWaters[j]) + ws++; + else + we++; + if (sffinalSands[j]) + ss++; + else + se++; + if (gffinalGrasses[j]) + gs++; + else + ge++; + } + if (abs(wb-ws) +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +bool Map::oldMakeIslandsMap(MapGenerationDescriptor &descriptor) +{ + // First, fill with water: + for (int y=0; y65536) + { + minDistSquare=minDistSquare>>1; + //I think that you need to do this only once, in worst case. + //With a few luck you doesn't need to. + c=0; + + } + } + else + { + bootX[i]=x; + bootY[i]=y; + for (int dx=-1; dx<6; dx++) + for (int dy=0; dy<6; dy++) + setUMTerrain(x+dx, y+dy, GRASS); + } + } + + + + // Three, expands islands + for (int s=0; s +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +void Map::oldAddRessourcesIslandsMap(MapGenerationDescriptor &descriptor) +{ + int *bootX=descriptor.bootX; + int *bootY=descriptor.bootY; + + int islandsSize=(int)(((w+h)*descriptor.oldIslandSize)/(400.0*sqrt((double)descriptor.nbTeams))); + if (islandsSize<8) + islandsSize=8; + // let's add ressources... + int smoothRessources=islandsSize/4; + for (int s=0; s0) + setRessource(bootX[s], bootY[s]-p, WOOD, amount); + smallestAmount=amount; + smallestRessource=WOOD; + + //WHEAT + for (d=0; d0) + setRessource(bootX[s]-p, bootY[s], CORN, amount); + if (amount0) + setRessource(bootX[s]+p, bootY[s]+p, smallestRessource, amount); + + //ALGAE + for (d=0; d<2*islandsSize; d++) + if (isWater(bootX[s]+d, bootY[s])) + break; + amount=descriptor.ressource[ALGA]; + amount=smoothRessources; + p=d+smoothRessources-1+amount/2; + if (amount>0) + setRessource(bootX[s]+p, bootY[s], ALGA, amount); + } + + // Let's smooth ressources... + this->smoothRessources(smoothRessources*2); +} + diff --git a/src/map/generator/MapOldRandom.cpp b/src/map/generator/MapOldRandom.cpp new file mode 100644 index 000000000..4f86a3b0e --- /dev/null +++ b/src/map/generator/MapOldRandom.cpp @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" + +void simulateRandomMap(int smooth, double baseWater, double baseSand, double baseGrass, double *finalWater, double *finalSand, double *finalGrass); +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +bool Map::oldMakeRandomMap(MapGenerationDescriptor &descriptor) +{ + int waterRatio=descriptor.waterRatio; + int sandRatio =descriptor.sandRatio ; + int grassRatio=descriptor.grassRatio; + int totalRatio=waterRatio+sandRatio+grassRatio; + int smooth=descriptor.smooth; + double baseWater, baseSand, baseGrass; + + if (totalRatio == 0) + { + baseWater = baseSand = baseGrass = 1.0/3.0; + } + else + { + baseWater=(float)waterRatio/(float)totalRatio; + baseSand =(float)sandRatio /(float)totalRatio; + baseGrass=(float)grassRatio/(float)totalRatio; + } + //Sorry, the equation is too complex for me. We use a numeric approach: + double alphaWater=baseWater; + double alphaSand =baseSand ; + double alphaGrass=baseGrass; + double alphaSum=alphaWater+alphaSand+alphaGrass; + alphaWater/=alphaSum; + alphaSand /=alphaSum; + alphaGrass/=alphaSum; + + for (int r=1; r<=smooth; r++) + { + for (int prec=0; prec<3; prec++) + { + double finalAlphaWater, finalAlphaSand, finalAlphaGrass; + simulateRandomMap(r, alphaWater, alphaSand, alphaGrass, &finalAlphaWater, &finalAlphaSand, &finalAlphaGrass); + + + double errAlphaWater=finalAlphaWater-baseWater; + double errAlphaSand =finalAlphaSand -baseSand ; + double errAlphaGrass=finalAlphaGrass-baseGrass; + + + double betaWater; + double betaSand ; + double betaGrass; + + if (finalAlphaWater) + betaWater=(alphaWater*baseWater)/finalAlphaWater; + else + betaWater=0; + if (finalAlphaSand) + betaSand =(alphaSand *baseSand )/finalAlphaSand ; + else + betaSand=0; + if (finalAlphaGrass) + betaGrass=(alphaGrass*baseGrass)/finalAlphaGrass; + else + betaGrass=0; + double betaSum=betaWater+betaSand+betaGrass; + betaWater/=betaSum; + betaSand /=betaSum; + betaGrass/=betaSum; + + double finalBetaWater, finalBetaSand, finalBetaGrass; + simulateRandomMap(r, betaWater, betaSand, betaGrass, &finalBetaWater, &finalBetaSand, &finalBetaGrass); + + + double errBetaWater=finalBetaWater-baseWater; + double errBetaSand =finalBetaSand -baseSand ; + double errBetaGrass=finalBetaGrass-baseGrass; + + + double projNom=(errBetaWater*errAlphaWater+errBetaSand*errAlphaSand+errBetaGrass*errAlphaGrass); + double projDen=(errAlphaWater*errAlphaWater+errAlphaSand*errAlphaSand+errAlphaGrass*errAlphaGrass); + if (projDen<=0) + continue; + double proj=projNom/projDen; + + + double minErr=DBL_MAX; + for (double cfi=0.0; cfi<=1.0; cfi+=0.1) + { + double cf=cfi*proj; + + double sumCenter=1.0-cf; + double gammaWater=(-cf*betaWater+1.0*alphaWater)/sumCenter; + double gammaSand =(-cf*betaSand +1.0*alphaSand )/sumCenter; + double gammaGrass=(-cf*betaGrass+1.0*alphaGrass)/sumCenter; + if (gammaWater<0.0) + gammaWater=0.0; + if (gammaSand<0.0) + gammaSand=0.0; + if (gammaGrass<0.0) + gammaGrass=0.0; + double gammaSum=gammaWater+gammaSand+gammaGrass; + if (gammaSum<=0) + continue; + gammaWater/=gammaSum; + gammaSand /=gammaSum; + gammaGrass/=gammaSum; + + double finalGammaWater, finalGammaSand, finalGammaGrass; + simulateRandomMap(r, gammaWater, gammaSand, gammaGrass, &finalGammaWater, &finalGammaSand, &finalGammaGrass); + + + double errGammaWater=finalGammaWater-baseWater; + double errGammaSand =finalGammaSand -baseSand ; + double errGammaGrass=finalGammaGrass-baseGrass; + double errGamma=(errGammaWater*errGammaWater+errGammaSand*errGammaSand+errGammaGrass*errGammaGrass); + + if (errGamma0) + allowed[0]=(Uint32)(pow(errWaterRatioCount, 0.125)*4294967296.0); + else + allowed[0]=0; + if (errSandRatioCount>0) + allowed[1]=(Uint32)(pow(errSandRatioCount , 0.125)*4294967296.0); + else + allowed[1]=0; + if (errGrassRatioCount>0) + allowed[2]=(Uint32)(pow(errGrassRatioCount, 0.125)*4294967296.0); + else + allowed[2]=0; + + assert(allowed[0]<=(Uint32)0xFFFFFFFF); + assert(allowed[1]<=(Uint32)0xFFFFFFFF); + assert(allowed[2]<=(Uint32)0xFFFFFFFF); + + if (i==0) + { + allowed[0]=0; + allowed[1]=0; + allowed[2]=0; + } + + for (int y=0; y0); + int* bootX=descriptor.bootX; + int* bootY=descriptor.bootY; + + //TODO: First pass to find the number of available places. + for (int team=0; team7) + { + int centerX=((x+startX)>>1); + int top, bot; + for (top=0; top0); + + int centerY=y+((bot-top)>>1); + bool farEnough=true; + for (int ti=0; timaxSurface && farEnough) + { + maxSurface=surface; + maxX=centerX; + maxY=centerY; + } + } + width=0; + startX=x; + } + } + } + + if (maxSurface<=0) + return false; + assert(maxSurface); + bootX[team]=maxX; + bootY[team]=maxY; + + for (int dx=-1; dx<6; dx++) + for (int dy=0; dy<6; dy++) + setUMTerrain(maxX+dx, maxY+dy, GRASS); + + + } + + // Let's add some green space for teams: + int squareSize=5+(int)(sqrt((double)minDistSquare)/4.5); + for (int team=0; team +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +void Map::oldAddRessourcesRandomMap(MapGenerationDescriptor &descriptor) +{ + int *bootX=descriptor.bootX; + int *bootY=descriptor.bootY; + int nbTeams=descriptor.nbTeams; + int limiteDist=(w+h)/(2*nbTeams); + + // let's add ressources to old map generator + for (int team=0; team3) + break; + else + width=1; + + if (dist*distWeight[resI]+width*widthWeight[resI]>maxDist*distWeight[resI]+maxWidth*widthWeight[resI]) + { + maxWidth=width; + maxDist=dist; + maxDir=dir; + } + } + dirUsed[maxDir]=true; + if (maxWidth>1); + dx*=d; + dy*=d; + + int amount=descriptor.ressource[res]; + if (amount>0) + setRessource(bootX[team]+dx, bootY[team]+dy, res, amount); + } + + if (smallestWidth3) + break; + else + width=1; + + if (dist+width>maxDist+maxWidth) + { + maxWidth=width; + maxDist=dist; + maxDir=dir; + } + } + dirUsed[maxDir]=true; + + int dx, dy; + Unit::dxDyFromDirection(maxDir, &dx, &dy); + int d=maxDist-(maxWidth>>1); + dx*=d; + dy*=d; + + int amount=descriptor.ressource[smallestRessource]; + if (amount>0) + setRessource(bootX[team]+dx, bootY[team]+dy, smallestRessource, amount); + } + + int maxDir=0; + int maxWidth=0; + int maxDist=0; + for (int dir=0; dir<8; dir++) + { + int width=0; + int dx, dy, dist; + Unit::dxDyFromDirection(dir, &dx, &dy); + for (dist=0; dist<2*limiteDist; dist++) + if (isWater(bootX[team]+dx*dist, bootY[team]+dy*dist)) + width++; + else if (width>3) + break; + else + width=1; + + if (dist+width>width+maxWidth) + { + maxWidth=width; + maxDist=dist; + maxDir=dir; + } + } + + int dx, dy; + Unit::dxDyFromDirection(maxDir, &dx, &dy); + int d=maxDist-(maxWidth>>1); + dx*=d; + dy*=d; + + int amount=descriptor.ressource[ALGA]; + if (amount>0) + setRessource(bootX[team]+dx, bootY[team]+dy, ALGA, amount); + } + + // Let's smooth ressources... + int maxAmount=0; + for (int r=0; r<4; r++) + if (maxAmount +#include +#include +#include + +#include "boost/integer_traits.hpp" +#include "boost/integer/common_factor.hpp" +//also the Perlin Noise stuff uses random that is not based on syncRand +#include "boost/random.hpp" +#include "Game.h" +#include "GlobalContainer.h" +#include "HeightMapGenerator.h" +#include "MapGenerationDescriptor.h" +#include "MapGenerator.h" +#include "Map.h" +#include +#include +#include +#include "Unit.h" +#include "Utilities.h" + +/// This random map generator generates a heightfield and then choses levels at which to draw the line between water, sand, gras and sand again (desert) +bool Map::makeRandomMap(MapGenerationDescriptor &descriptor) +{ + /// all under waterLevel is water, under sandLevel is beach, under grassLevel is grass and above grasslevel is desert + float waterLevel, sandLevel, grassLevel, wheatWoodLevel, algaeLevel, stoneLevel; + /// to influence the roughness + float smoothingFactor=(float)(descriptor.smooth+4)*3; + /// the proportions requested through the gui can directly be translated into tile counts of the undermap. + unsigned int waterTiles, sandTiles, grassTiles, wheatWoodTiles, algaeTiles; + /// grass + sand + water + desert as from the gui + unsigned int totalGSWFromUI=descriptor.waterRatio+descriptor.sandRatio+descriptor.grassRatio+descriptor.desertRatio+descriptor.fruitRatio; + /// respect symmetry-requirements + unsigned int wPower2Divider=0, hPower2Divider=0; + int power2Divider=descriptor.logRepeatAreaTimes; + for (int i = 0; i> wPower2Divider) > (h >> hPower2Divider)) + wPower2Divider++; + else + hPower2Divider++; + int wRepeat = 1 << wPower2Divider; + int hRepeat = 1 << hPower2Divider; + unsigned int wHeightMap=(unsigned int)(w/wRepeat); + unsigned int hHeightMap=(unsigned int)(h/hRepeat); + /// lets generate a patch of perlin noise. That's a smooth mapping R^2 to ]0;1[ + HeightMap hm(wHeightMap,hHeightMap); + /// 1 to avoid division by zero, + unsigned int tmpTotal=1+descriptor.waterRatio+descriptor.grassRatio; + unsigned int sectionIslandCount=std::max(1u, static_cast((descriptor.nbTeams+descriptor.extraIslands) / (1 << power2Divider))); + switch (descriptor.methode) + { + case MapGenerationDescriptor::eSWAMP: + hm.makeSwamp(smoothingFactor); + waterTiles=(unsigned int)((float)descriptor.waterRatio*wHeightMap*hHeightMap/(float)tmpTotal); + sandTiles=0; + grassTiles=wHeightMap*hHeightMap-waterTiles; + break; + case MapGenerationDescriptor::eRIVER: + hm.makeRiver(descriptor.riverDiameter*(wHeightMap+hHeightMap)/2/100,smoothingFactor); + waterTiles=(unsigned int)((float)descriptor.waterRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); + sandTiles=(unsigned int)((float)descriptor.sandRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); + grassTiles =(unsigned int)((float)descriptor.grassRatio /(float)totalGSWFromUI*wHeightMap*hHeightMap); + break; + case MapGenerationDescriptor::eCRATERLAKES: + hm.makeCraters(wHeightMap*hHeightMap*descriptor.craterDensity/30000, 30, smoothingFactor); + waterTiles=(unsigned int)((float)descriptor.waterRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); + sandTiles=(unsigned int)((float)descriptor.sandRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); + grassTiles =(unsigned int)((float)descriptor.grassRatio /(float)totalGSWFromUI*wHeightMap*hHeightMap); + break; + case MapGenerationDescriptor::eISLANDS: + hm.makeIslands(sectionIslandCount, smoothingFactor); + waterTiles=(unsigned int)((float)descriptor.waterRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); + sandTiles=(unsigned int)((float)descriptor.sandRatio/(float)totalGSWFromUI*wHeightMap*hHeightMap); + grassTiles =(unsigned int)((float)descriptor.grassRatio /(float)totalGSWFromUI*wHeightMap*hHeightMap); + break; + default: assert(false); + break; + } + /// wheat/wood needs ground to stand on and water. So: + wheatWoodTiles=waterTiles= algaeTiles) + algaeLevel = (float)(i-1)/2048.0; + if (accumulatedHistogram >= waterTiles) + waterLevel = (float)(i-1)/2048.0; + } + while ((sandLevel==0) && (i<2048)) + { + accumulatedHistogram+=histogram[i++]; + if (accumulatedHistogram >= waterTiles+sandTiles) + sandLevel = (float)(i-1)/2048.0; + } + while ((grassLevel==0) && (i<2048)) + { + accumulatedHistogram+=histogram[i++]; + if (wheatWoodLevel==0 && accumulatedHistogram >= waterTiles+sandTiles+wheatWoodTiles) + wheatWoodLevel = (float)(i-1)/2048.0; + if (stoneLevel==0 && accumulatedHistogram >= waterTiles+sandTiles+(wheatWoodTiles / 3)) + stoneLevel = (float)(i-1)/2048.0; + if (accumulatedHistogram >= waterTiles+sandTiles+grassTiles) + grassLevel = (float)(i-1)/2048.0; + } + for (unsigned y=0; y0); + int* bootX=descriptor.bootX; + int* bootY=descriptor.bootY; + + //TODO: First pass to find the number of available places. + for (int team=0; team7) + { + int centerX=((x+startX)>>1); + int top, bot; + for (top=0; top0); + + int centerY=y+((bot-top)>>1); + bool farEnough=true; + for (int ti=0; timaxSurface && farEnough) + { + maxSurface=surface; + maxX=centerX; + maxY=centerY; + } + } + width=0; + startX=x; + } + } + } + + if (maxSurface<=0) + { + //std::cout << "debugoutput 2\n"; + return false; + } + assert(maxSurface); + bootX[team]=maxX; + bootY[team]=maxY; + } + + controlSand(); + regenerateMap(0, 0, w, h); + //now to add primary resources for current map generator + for (unsigned y=0; y 0) + { + for (int q1=0; q1 +#include +#include +#include + + +// Forbidden / Guard area / Clear area gradients + +void Map::updateForbiddenGradient(int teamNumber, bool canSwim) +{ + Uint8 *gradient = forbiddenGradient[teamNumber][canSwim]; + assert(gradient); + Uint32 teamMask = Team::teamNumberToMask(teamNumber); + + // Seed: free cells are sources (255), forbidden interiors are placeholder 1 + // (promoted to 254 in the second pass if they border a free cell), all other + // blockers (resources, buildings, water, immobileUnits) are obstacles. + for (size_t i=0; i> wDec; + size_t x = i & wMask; + size_t yu = ((y - 1) & hMask); + size_t yd = ((y + 1) & hMask); + size_t xl = ((x - 1) & wMask); + size_t xr = ((x + 1) & wMask); + size_t deltaAddrC[8] = { + (yu << wDec) | xl, + (yu << wDec) | x , + (yu << wDec) | xr, + (y << wDec) | xr, + (yd << wDec) | xr, + (yd << wDec) | x , + (yd << wDec) | xl, + (y << wDec) | xl, + }; + for (int ci=0; ci<8; ci++) + { + if (gradient[deltaAddrC[ci]] == GRADIENT_AT_GOAL) + { + gradient[i] = GRADIENT_FORBIDDEN_BORDER; + break; + } + } + } + + updateGlobalGradient(gradient); +} + +void Map::updateForbiddenGradient(int teamNumber) +{ + for (int i=0; i<2; i++) + updateForbiddenGradient(teamNumber, i); +} + +void Map::updateForbiddenGradient() +{ + for (int i=0; imapHeader.getNumberOfTeams(); i++) + updateForbiddenGradient(i); +} + + +void Map::updateGuardAreasGradient(int teamNumber, bool canSwim) +{ + Uint8 *gradient = guardAreasGradient[teamNumber][canSwim]; + assert(gradient); + + Uint32 teamMask = Team::teamNumberToMask(teamNumber); + for (size_t i=0; iteams[teamNumber]->allies)) + gradient[i] = GRADIENT_FORBIDDEN; + else if (!canSwim && isWater(i)) + gradient[i] = GRADIENT_FORBIDDEN; + else if (c.guardArea & teamMask) + gradient[i] = GRADIENT_AT_GOAL; + else + gradient[i] = GRADIENT_UNREACHABLE; + } + + updateGlobalGradient(gradient); +} + +void Map::updateGuardAreasGradient(int teamNumber) +{ + for (int i=0; i<2; i++) + updateGuardAreasGradient(teamNumber, i); +} + +void Map::updateGuardAreasGradient() +{ + for (int i=0; imapHeader.getNumberOfTeams(); i++) + updateGuardAreasGradient(i); +} + + +void Map::updateClearAreasGradient(int teamNumber, bool canSwim) +{ + Uint8 *gradient = clearAreasGradient[teamNumber][canSwim]; + assert(gradient); + + Uint32 teamMask = Team::teamNumberToMask(teamNumber); + for (size_t i=0; iressourcesTypes.get(c.ressource.type)->clearable) + gradient[i] = GRADIENT_AT_GOAL; + else if(immobileUnits[i] != 255) + gradient[i] = GRADIENT_FORBIDDEN; + else if (c.ressource.type != NO_RES_TYPE) + gradient[i] = GRADIENT_FORBIDDEN; + else if (c.building != NOGBID) + gradient[i] = GRADIENT_FORBIDDEN; + else if (!canSwim && isWater(i)) + gradient[i] = GRADIENT_FORBIDDEN; + else + gradient[i] = GRADIENT_UNREACHABLE; + } + + updateGlobalGradient(gradient); +} + +void Map::updateClearAreasGradient(int teamNumber) +{ + for (int i=0; i<2; i++) + updateClearAreasGradient(teamNumber, i); +} + +void Map::updateClearAreasGradient() +{ + for (int i=0; imapHeader.getNumberOfTeams(); i++) + updateClearAreasGradient(i); +} + + diff --git a/src/map/gradient/MapGradientBuilding.cpp b/src/map/gradient/MapGradientBuilding.cpp new file mode 100644 index 000000000..8d89a26e3 --- /dev/null +++ b/src/map/gradient/MapGradientBuilding.cpp @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "BuildingType.h" +#include "Unit.h" +#include "MapInternal.h" + +#include +#include +#include +#include + + +// updateGlobalGradient(Building*), updateLocalRessources + +void Map::updateGlobalGradient(Building *building, bool canSwim) +{ + assert(building); + assert(building->type); + int posX=building->posX; + int posY=building->posY; + int posW=building->type->width; + Uint32 teamMask=building->owner->me; + Uint16 bgid=building->gid; + + Uint8 *gradient=building->globalGradient[canSwim]; + assert(gradient); + + bool isClearingFlag=false; + bool isWarFlag=false; + if (building->type->isVirtual && building->type->zonable[WARRIOR]) + isWarFlag=true; + + memset(gradient, GRADIENT_UNREACHABLE, size); + if (building->type->isVirtual && !building->type->zonable[WORKER]) + { + assert(!building->type->zonableForbidden); + int r=building->unitStayRange; + int r2=r*r; + for (int yi=-r; yi<=r; yi++) + { + int yi2=(yi*yi); + for (int xi=-r; xi<=r; xi++) + if (yi2+(xi*xi)<=r2) + { + size_t addr = coordToIndex(posX+w+xi, posY+h+yi); + if(gradient[addr] == GRADIENT_UNREACHABLE) + gradient[addr] = GRADIENT_AT_GOAL; + } + } + } + else if (building->type->isVirtual && building->type->zonable[WORKER]) + { + assert(!building->type->zonableForbidden); + isClearingFlag=true; + int r=building->unitStayRange; + int r2=r*r; + for (int yi=-r; yi<=r; yi++) + { + int yi2=(yi*yi); + for (int xi=-r; xi<=r; xi++) + if (yi2+(xi*xi)<=r2) + { + size_t addr = coordToIndex(posX+w+xi, posY+h+yi); + if(cases[addr].ressource.type!=NO_RES_TYPE && building->clearingRessources[cases[addr].ressource.type]) + { + if(gradient[addr] == GRADIENT_UNREACHABLE) + gradient[addr] = GRADIENT_AT_GOAL; + } + } + } + } + + for (int y=0; yowner->allies)) + gradient[wyx] = GRADIENT_FORBIDDEN; + else if(gradient[wyx]!=GRADIENT_AT_GOAL) + gradient[wyx] = GRADIENT_UNREACHABLE; + } + } + } + + if (!building->type->isVirtual) + { + // Spiral around the building footprint corner; start one cell NW of the building origin + // (toroidal wrap), stride posW+1 so we cover the perimeter. + bool reachable = spiralFindNonZero(gradient, + (posX - 1) & wMask, (posY - 1) & hMask, + posW + 1, + wMask, hMask, wDec); + building->locked[canSwim] = !reachable; + if (!reachable) + return; + } + else + building->locked[canSwim]=false; + + updateGlobalGradient(gradient); +} + + +bool Map::updateLocalRessources(Building *building, bool canSwim) +{ + assert(building); + assert(building->type); + assert(building->type->isVirtual); + + + int posX=building->posX; + int posY=building->posY; + Uint32 teamMask=building->owner->me; + + Uint8 *gradient=building->localRessources[canSwim]; + if (gradient==NULL) + { + gradient=new Uint8[LOCAL_GRID_AREA]; + building->localRessources[canSwim]=gradient; + } + assert(gradient); + + bool *clearingRessources=building->clearingRessources; + bool anyRessourceToClear=false; + + memset(gradient, GRADIENT_UNREACHABLE, LOCAL_GRID_AREA); + int range=building->unitStayRange; + if (range>LOCAL_GRID_CENTER) + range=LOCAL_GRID_CENTER; + int range2=range*range; + for (int yl=0; yllocalRessourcesCleanTime[canSwim]=0; + if (anyRessourceToClear) + building->anyRessourceToClear[canSwim]=1; + else + { + building->anyRessourceToClear[canSwim]=2; + return false; + } + propagateLocalGradient32(gradient); + return true; +} + + diff --git a/src/map/gradient/MapGradientGlobal.cpp b/src/map/gradient/MapGradientGlobal.cpp new file mode 100644 index 000000000..4c3b5dbe8 --- /dev/null +++ b/src/map/gradient/MapGradientGlobal.cpp @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Unit.h" +#include "MapInternal.h" + +#include +#include +#include +#include + + +// LogFileManager.h does `#define fprintf if(false)fprintf` to silence stale +// log code project-wide. The convergence-failure dump below needs real +// fprintf, so undo the macro for this TU. +#ifdef fprintf +#undef fprintf +#endif + +// Chamfer distance transform with orthogonal=1, diagonal=1 weights (Chebyshev +// distance) on a toroidal grid. Two sweeps per pass — forward (NW, N, NE, W) +// then backward (SE, S, SW, E) — repeated until a full pass writes nothing. +// +// Cell value semantics: +// - 0 = obstacle, never written +// - 1 = free, no source contribution +// - >= 3 = propagation source: a neighbor with value vN lifts this cell to +// vN - 1 if larger. Floor of 3 ensures cand = vN - 1 >= 2 strictly +// exceeds a free cell's seed of 1. +// - sources keep their seed value (cand = vN - 1 < vN never raises it). +// +// Convergence bound: Borgefors 1986 establishes one forward+backward pass +// suffices on a non-toroidal grid *without obstacles*. With obstacles forcing +// path bends and a toroidal wraparound seam, the per-pass propagation can +// only advance along one scan direction, so a path with K direction changes +// (e.g. snaking around a mountain) needs ~K/2 passes. Empirically the gradient +// corpus and G2.game converge in well under 32 passes. +// +// The cap is set to 256 — every write strictly increases a cell value +// (monotonicity), values are bounded by 255, and the propagation floor is 2, +// so a single propagation chain is at most 253 cells long. 256 is the +// theoretical ceiling: any chain longer than that violates monotonicity, so +// the cap acts as a tripwire for that invariant rather than a real-workload +// throttle. On correct code the loop exits in a handful of passes. +void Map::updateGlobalGradient(Uint8 *gradient) +{ + int passes = 0; + bool changed; + do + { + changed = false; + + // Forward sweep: in-set neighbors are NW, N, NE, W (already visited). + for (size_t y = 0; y < (size_t)h; y++) + { + size_t yu = ((y - 1) & hMask); + for (size_t x = 0; x < (size_t)w; x++) + { + Uint8 g = gradient[(y << wDec) | x]; + if (g == 0) + continue; + size_t xl = ((x - 1) & wMask); + size_t xr = ((x + 1) & wMask); + Uint8 best = g; + Uint8 vNW = gradient[(yu << wDec) | xl]; + Uint8 vN = gradient[(yu << wDec) | x ]; + Uint8 vNE = gradient[(yu << wDec) | xr]; + Uint8 vW = gradient[(y << wDec) | xl]; + if (vNW >= 3 && (Uint8)(vNW - 1) > best) best = vNW - 1; + if (vN >= 3 && (Uint8)(vN - 1) > best) best = vN - 1; + if (vNE >= 3 && (Uint8)(vNE - 1) > best) best = vNE - 1; + if (vW >= 3 && (Uint8)(vW - 1) > best) best = vW - 1; + if (best != g) + { + gradient[(y << wDec) | x] = best; + changed = true; + } + } + } + + // Backward sweep: in-set neighbors are SE, S, SW, E (already visited). + for (size_t y = (size_t)h; y-- > 0; ) + { + size_t yd = ((y + 1) & hMask); + for (size_t x = (size_t)w; x-- > 0; ) + { + Uint8 g = gradient[(y << wDec) | x]; + if (g == 0) + continue; + size_t xl = ((x - 1) & wMask); + size_t xr = ((x + 1) & wMask); + Uint8 best = g; + Uint8 vSE = gradient[(yd << wDec) | xr]; + Uint8 vS = gradient[(yd << wDec) | x ]; + Uint8 vSW = gradient[(yd << wDec) | xl]; + Uint8 vE = gradient[(y << wDec) | xr]; + if (vSE >= 3 && (Uint8)(vSE - 1) > best) best = vSE - 1; + if (vS >= 3 && (Uint8)(vS - 1) > best) best = vS - 1; + if (vSW >= 3 && (Uint8)(vSW - 1) > best) best = vSW - 1; + if (vE >= 3 && (Uint8)(vE - 1) > best) best = vE - 1; + if (best != g) + { + gradient[(y << wDec) | x] = best; + changed = true; + } + } + } + + passes++; + if (passes >= 256) + { + fprintf(stderr, "[chamfer] passes >= 256 - monotonicity violated. w=%d h=%d size=%zu\n", + (int)w, (int)h, size); + abort(); + } + } while (changed); +} + + +void Map::updateRessourcesGradient(int teamNumber, Uint8 ressourceType, bool canSwim) +{ + Uint8 *gradient=ressourcesGradient[teamNumber][ressourceType][canSwim]; + assert(gradient); + + Uint32 teamMask=Team::teamNumberToMask(teamNumber); + assert(globalContainer); + for (size_t i=0; i=256 && c.terrain<16+256)) //!canSwim && isWater + gradient[i]=GRADIENT_FORBIDDEN; + else + gradient[i]=GRADIENT_UNREACHABLE; + } + else if (c.ressource.type==ressourceType) + { + if (globalContainer->ressourcesTypes.get(ressourceType)->visibleToBeCollected && !(fogOfWar[i]&teamMask)) + gradient[i]=GRADIENT_FORBIDDEN; + else + gradient[i]=GRADIENT_AT_GOAL; + } + else + gradient[i]=GRADIENT_FORBIDDEN; + } + + updateGlobalGradient(gradient); +} + diff --git a/src/map/gradient/MapGradientLocal.cpp b/src/map/gradient/MapGradientLocal.cpp new file mode 100644 index 000000000..43d6f6859 --- /dev/null +++ b/src/map/gradient/MapGradientLocal.cpp @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "BuildingType.h" +#include "Unit.h" +#include "MapInternal.h" + +#include +#include +#include +#include + + +// updateLocalGradient (32x32 building gradient) + helpers + +namespace { +/** Helper for updateLocalGradient */ +void fillGradientRectangle(Uint8* gradient, int posW, int posH) { + for (int dy=0; dygid); + //printf("updatingLocalGradient (gbid=%d)...\n", building->gid); + assert(building); + assert(building->type); + building->dirtyLocalGradient[canSwim]=false; + int posX=building->posX; + int posY=building->posY; + int posW=building->type->width; + int posH=building->type->height; + Uint32 teamMask=building->owner->me; + Uint16 bgid=building->gid; + + Uint8 *tgtGradient=building->localGradient[canSwim]; + + Uint8 gradient[LOCAL_GRID_AREA]; + + // 1. INITIALIZATION of gradient[]: + // 1a. Set all values to GRADIENT_UNREACHABLE (meaning 'far away, but not inaccessible'). + memset(gradient, GRADIENT_UNREACHABLE, LOCAL_GRID_AREA); + + bool isWarFlag=false; + bool isClearingFlag=false; + if(building->type->isVirtual && building->type->zonable[WARRIOR]) + isWarFlag=true; + if(building->type->isVirtual && building->type->zonable[WORKER]) + isClearingFlag=true; + + // 1b. Set values at target building to GRADIENT_AT_GOAL. + if (building->type->isVirtual && !building->type->zonable[WORKER]) + { + assert(!building->type->zonableForbidden); + int r=building->unitStayRange; + int r2=r*r; + for (int yi=-r; yi<=r; yi++) + { + int yi2=(yi*yi); + int yyi=clip_0_31(LOCAL_GRID_CENTER+yi); + for (int xi=-r; xi<=r; xi++) + { + if (yi2+(xi*xi)<=r2) + { + int xxi=clip_0_31(LOCAL_GRID_CENTER+xi); + gradient[xxi+(yyi<type->isVirtual && building->type->zonable[WORKER]) + { + assert(!building->type->zonableForbidden); + int r=building->unitStayRange; + int r2=r*r; + for (int yi=-r; yi<=r; yi++) + { + int yi2=(yi*yi); + int yyi=clip_0_31(LOCAL_GRID_CENTER+yi); + for (int xi=-r; xi<=r; xi++) + { + if (yi2+(xi*xi)<=r2) + { + size_t addr = coordToIndex(posX+w+xi, posY+h+yi); + if(cases[addr].ressource.type != NO_RES_TYPE && building->clearingRessources[cases[addr].ressource.type]) + { + int xxi=clip_0_31(LOCAL_GRID_CENTER+xi); + gradient[xxi+(yyi<owner->allies)) + gradient[wyx] = GRADIENT_FORBIDDEN; + else if(gradient[wyx]!=GRADIENT_AT_GOAL) + gradient[wyx] = GRADIENT_UNREACHABLE; + } + } + } + + // 2. NEED TO UPDATE? Check boundary conditions to see if they have changed. + // I commented this out, because the tgtGradient is not initialized + // in the first runs: leading to an unconditional jump + // todo: write a real fix + +/* + bool change = false; + + for (int i=0; itype->isVirtual) + { + // Spiral around the building footprint corner; start one cell NW of (CENTER, CENTER), + // stride posW+1 so we wrap around the whole footprint. + bool reachable = spiralFindNonZero(gradient, + LOCAL_GRID_CENTER - 1, LOCAL_GRID_CENTER - 1, + posW + 1, + LOCAL_GRID_W - 1, LOCAL_GRID_W - 1, + LOCAL_GRID_SHIFT); + building->locked[canSwim] = !reachable; + if (!reachable) + { + memcpy(tgtGradient, gradient, LOCAL_GRID_AREA); // Don't leave tgt as-is (it might be dirty) + return; + } + } + else + building->locked[canSwim]=false; + + // 4. PROPAGATION of gradient values. + propagateLocalGradient32(gradient); + + // 5. WRITEBACK (because of the 'any change'-computation). + memcpy(tgtGradient, gradient, LOCAL_GRID_AREA); +} + + +// Chamfer-dilate the 32x32 local gradient buffer in-place. Two depth passes; each pass +// runs an outward sweep from the center then an inward sweep from a corner, with each +// sweep tracing a back-and-forth spiral that visits every cell. At every cell, the +// value is raised toward max(8-neighbors) - 1 (clamped at 1, i.e. GRADIENT_UNREACHABLE); +// 0 (obstacle) and 255 (source) are preserved. OOB neighbors are masked out via the +// LOCAL_GRID_W bit overflow trick — `xpart & LOCAL_GRID_W` flags x=-1 (sign-bit pattern +// has bit 5 set) and x=32, and likewise for y via `ypart & LOCAL_GRID_AREA`. +// +// Two passes ("depth") cover obstacles that fold the propagation path back on itself. +void propagateLocalGradient32(Uint8* gradient) { + for (int depth=0; depth<2; depth++) + { + for (int down=0; down<2; down++) + { + int x, y, dis, die, ddi; + if (down) + { + x=0; + y=0; + dis=LOCAL_GRID_W-1; + die=1; + ddi=-2; + } + else + { + x=LOCAL_GRID_CENTER; + y=LOCAL_GRID_CENTER; + dis=1; + die=LOCAL_GRID_W-1; + ddi=+2; + } + + for (int di=dis; di!=die; di+=ddi) //distance-iterator + { + for (int bi=0; bi<2; bi++) //back-iterator + { + for (int ai=0; ai<4; ai++) //angle-iterator + { + for (int mi=0; mi=0); + assert(y>=0); + assert(x +#include +#include +#include + + +// 5x5 minigrad direction queries (directionFromMinigrad, directionByMinigrad) + +namespace { + +constexpr int MINIGRAD_W = 5; +constexpr int MINIGRAD_AREA = MINIGRAD_W * MINIGRAD_W; // 25 +constexpr int MINIGRAD_CENTER_COORD = MINIGRAD_W / 2; // 2 +constexpr int MINIGRAD_CENTER_INDEX = MINIGRAD_CENTER_COORD // 12 + + MINIGRAD_CENTER_COORD * MINIGRAD_W; +constexpr int MINIGRAD_DIRECTIONS = 8; +constexpr int MINIGRAD_DIAGONAL_FAR = 5; + +// Convert a (col, row) offset relative to the centre of the 5x5 minigrad +// into a linear index into miniGrad[]. rx, ry are in [-2, 2]. +constexpr int minigradIndex(int rx, int ry) +{ + return (MINIGRAD_CENTER_COORD + rx) + (MINIGRAD_CENTER_COORD + ry) * MINIGRAD_W; +} + +// Eight directions probed by directionFromMinigrad, in scoring order. +// Diagonals first (indices 0..3), then cardinals (4..7). The scoring loop +// uses `if (maxg <= g)` so later indices win ties — cardinals therefore +// beat diagonals on equal scores. Re-ordering this table would shift +// tie-break outcomes and diverge replays; preserve the original layout. +// centre : inner-ring cell (one step from grid centre) whose own +// gradient seeds the direction's score. +// far / farCount: outer-ring arc beyond `centre`. A diagonal walks a +// 5-cell L; a cardinal walks a 3-cell row. +// canonicalDir : value passed to Unit::dxDyFromDirection when this +// direction wins. Matches the original maxd->stdd map: +// diagonals 0..3 -> 0,2,4,6 ; cardinals 4..7 -> 1,3,5,7. +struct MinigradDirection { + int centreCol; + int centreRow; + int farCount; + int far[MINIGRAD_DIAGONAL_FAR][2]; + int canonicalDir; +}; + +constexpr MinigradDirection minigradDirections[MINIGRAD_DIRECTIONS] = { + // NW diagonal + { 1, 1, 5, { {0,2}, {0,1}, {0,0}, {1,0}, {2,0} }, 0 }, + // NE diagonal + { 3, 1, 5, { {2,0}, {3,0}, {4,0}, {4,1}, {4,2} }, 2 }, + // SE diagonal + { 3, 3, 5, { {4,2}, {4,3}, {4,4}, {3,4}, {2,4} }, 4 }, + // SW diagonal + { 1, 3, 5, { {2,4}, {1,4}, {0,4}, {0,3}, {0,2} }, 6 }, + // N cardinal — far slots 3,4 unused (farCount=3). + { 2, 1, 3, { {1,0}, {2,0}, {3,0}, {0,0}, {0,0} }, 1 }, + // E cardinal + { 3, 2, 3, { {4,1}, {4,2}, {4,3}, {0,0}, {0,0} }, 3 }, + // S cardinal + { 2, 3, 3, { {1,4}, {2,4}, {3,4}, {0,0}, {0,0} }, 5 }, + // W cardinal + { 1, 2, 3, { {0,1}, {0,2}, {0,3}, {0,0}, {0,0} }, 7 }, +}; + +// Score one direction: pack (max << 8) | mxd, where mxd is the centre +// cell's own gradient and max is min-clamped to 1 then maxed over the +// far arc — but only when the centre is actively propagating (neither +// forbidden nor at-goal). Otherwise max == mxd and the score collapses +// to the centre value duplicated into both bytes. +inline Uint32 scoreMinigradDirection(const Uint8 miniGrad[MINIGRAD_AREA], + const MinigradDirection& dir) +{ + const Uint8 mxd = miniGrad[dir.centreCol + dir.centreRow * MINIGRAD_W]; + Uint8 max = mxd; + if (max && max != GRADIENT_AT_GOAL) + { + max = 1; + for (int i = 0; i < dir.farCount; ++i) + UPDATE_MAX(max, miniGrad[dir.far[i][0] + dir.far[i][1] * MINIGRAD_W]); + } + return (static_cast(max) << 8) | mxd; +} + +} // namespace + +bool Map::directionFromMinigrad(Uint8 miniGrad[25], int *dx, int *dy, const bool strict) const +{ + Uint32 maxs[MINIGRAD_DIRECTIONS]; + for (int d = 0; d < MINIGRAD_DIRECTIONS; ++d) + maxs[d] = scoreMinigradDirection(miniGrad, minigradDirections[d]); + + int centerg = miniGrad[MINIGRAD_CENTER_INDEX]; + centerg = (centerg << 8) | centerg; + int maxg = 0; + int maxd = MINIGRAD_DIRECTIONS; // sentinel; only reachable if every maxs[d] is 0 + bool good = false; + for (int d = 0; d < MINIGRAD_DIRECTIONS; ++d) + { + int g = maxs[d]; + if (strict ? (g > centerg) : (g && g != centerg)) + good = true; + if (maxg <= g) + { + maxg = g; + maxd = d; + } + } + + if (!good) + return false; + + const int stdd = (maxd < MINIGRAD_DIRECTIONS) ? minigradDirections[maxd].canonicalDir : 8; + Unit::dxDyFromDirection(stdd, dx, dy); + return true; +} + +bool Map::directionByMinigrad(Uint32 teamMask, bool canSwim, int x, int y, int *dx, int *dy, const Uint8 *gradient, bool strict) const +{ + Uint8 miniGrad[MINIGRAD_AREA]; + miniGrad[MINIGRAD_CENTER_INDEX] = gradient[x + y * w]; + for (int di = 0; di < 16; di++) + { + int rx = tabFar[di][0]; + int ry = tabFar[di][1]; + int xg = x + rx; + int yg = y + ry; + int g = gradient[coordToIndex(xg, yg)]; + if (g == GRADIENT_FORBIDDEN || g == GRADIENT_AT_GOAL || isFreeForGroundUnit(xg, yg, canSwim, teamMask)) + miniGrad[minigradIndex(rx, ry)] = g; + else + miniGrad[minigradIndex(rx, ry)] = GRADIENT_FORBIDDEN; + } + for (int di = 0; di < 8; di++) + { + int rx = tabClose[di][0]; + int ry = tabClose[di][1]; + int xg = x + rx; + int yg = y + ry; + int g = gradient[coordToIndex(xg, yg)]; + if (g == GRADIENT_FORBIDDEN || isFreeForGroundUnit(xg, yg, canSwim, teamMask)) + miniGrad[minigradIndex(rx, ry)] = g; + else + miniGrad[minigradIndex(rx, ry)] = GRADIENT_FORBIDDEN; + } + return directionFromMinigrad(miniGrad, dx, dy, strict); +} + +bool Map::directionByMinigrad(Uint32 teamMask, bool canSwim, int x, int y, int bx, int by, int *dx, int *dy, Uint8 localGradient[1024], bool strict) const +{ + Uint8 miniGrad[MINIGRAD_AREA]; + for (int ry = 0; ry < MINIGRAD_W; ry++) + for (int rx = 0; rx < MINIGRAD_W; rx++) + { + int gx = (x + rx - MINIGRAD_CENTER_COORD) & wMask; + int gy = (y + ry - MINIGRAD_CENTER_COORD) & hMask; + int lx = (x - bx + LOCAL_GRID_CENTER + rx - MINIGRAD_CENTER_COORD) & wMask; + int ly = (y - by + LOCAL_GRID_CENTER + ry - MINIGRAD_CENTER_COORD) & hMask; + if (lx == wMask) + { + gx = (gx + 1) & wMask; + lx = 0; + } + else if (lx == LOCAL_GRID_W) + { + gx = (gx - 1) & wMask; + lx = LOCAL_GRID_W - 1; + } + if (ly == hMask) + { + gy = (gy + 1) & hMask; + ly = 0; + } + else if (ly == LOCAL_GRID_W) + { + gy = (gy - 1) & hMask; + ly = LOCAL_GRID_W - 1; + } + assert(lx >= 0); + assert(ly >= 0); + assert(lx < LOCAL_GRID_W); + assert(ly < LOCAL_GRID_W); + int g = localGradient[lx + (ly << LOCAL_GRID_SHIFT)]; + if (g == GRADIENT_FORBIDDEN || g == GRADIENT_AT_GOAL + || (rx == MINIGRAD_CENTER_COORD && ry == MINIGRAD_CENTER_COORD) + || isFreeForGroundUnit(gx, gy, canSwim, teamMask)) + miniGrad[rx + ry * MINIGRAD_W] = g; + else + miniGrad[rx + ry * MINIGRAD_W] = GRADIENT_FORBIDDEN; + } + for (int ry = 1; ry <= 3; ry++) + for (int rx = 1; rx <= 3; rx++) + if (miniGrad[rx + ry * MINIGRAD_W] == GRADIENT_AT_GOAL) + { + int gx = (x + rx - MINIGRAD_CENTER_COORD) & wMask; + int gy = (y + ry - MINIGRAD_CENTER_COORD) & hMask; + if (!isFreeForGroundUnit(gx, gy, canSwim, teamMask)) + miniGrad[rx + ry * MINIGRAD_W] = GRADIENT_FORBIDDEN; + } + return directionFromMinigrad(miniGrad, dx, dy, strict); +} + + diff --git a/src/MapHeader.cpp b/src/map/io/MapHeader.cpp similarity index 88% rename from src/MapHeader.cpp rename to src/map/io/MapHeader.cpp index 591e2ba7a..8b585a160 100644 --- a/src/MapHeader.cpp +++ b/src/map/io/MapHeader.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "Version.h" #include "MapHeader.h" @@ -238,6 +223,10 @@ void MapHeader::resetGameSHA1() Uint32 MapHeader::checkSum() const { + // `cs` is signed `Sint32` so the open-coded `(cs<<31)|(cs>>1)` rotate + // uses arithmetic right-shift (sign-extending). See the matching note + // in Building::checkSum — do NOT replace with the unsigned `rotr1` + // helper or the network checksum diverges. Sint32 cs = 0; cs^=versionMajor; cs^=versionMinor; diff --git a/src/MapHeader.h b/src/map/io/MapHeader.h similarity index 82% rename from src/MapHeader.h rename to src/map/io/MapHeader.h index 6d307ffc7..02f333bca 100644 --- a/src/MapHeader.h +++ b/src/map/io/MapHeader.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __MAPHEADER_H -#define __MAPHEADER_H +#pragma once #include "BaseTeam.h" #include "Team.h" @@ -135,5 +119,3 @@ class MapHeader std::string glob2FilenameToName(const std::string& filename); //! create the filename from the directory, end user-visible name and extension. directory and extension must be given without the / and the . std::string glob2NameToFilename(const std::string& dir, const std::string& name, const std::string& extension=""); - -#endif diff --git a/src/map/io/MapIO.cpp b/src/map/io/MapIO.cpp new file mode 100644 index 000000000..5f85b3c30 --- /dev/null +++ b/src/map/io/MapIO.cpp @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Unit.h" +#include "MapInternal.h" + +#ifndef YOG_SERVER_ONLY +#include "render/GameAnimations.h" +#endif // !YOG_SERVER_ONLY + +#include +#include +#include +#include + + +bool Map::load(GAGCore::InputStream *stream, MapHeader& header, Game *game) +{ + assert(header.getVersionMinor()>=16); + + Sint32 versionMinor = header.getVersionMinor(); + + clear(); + + stream->readEnterSection("Map"); + + char signature[4]; + stream->read(signature, 4, "signatureStart"); + if (memcmp(signature, "MapB", 4)!=0) + { + fprintf(stderr, "Map:: Failed to find signature at the beginning of Map.\n"); + return false; + } + + // We load and compute size: + wDec = stream->readSint32("wDec"); + hDec = stream->readSint32("hDec"); + w = 1<read(undermap, size, "undermap"); + stream->readEnterSection("cases"); + for (size_t i=0; ireadEnterSection(i); + mapDiscovered[i] = stream->readUint32("mapDiscovered"); + + cases[i].terrain = stream->readUint16("terrain"); + cases[i].building = stream->readUint16("building"); + + stream->read(&(cases[i].ressource), 4, "ressource"); + cases[i].groundUnit = stream->readUint16("groundUnit"); + cases[i].airUnit = stream->readUint16("airUnit"); + cases[i].forbidden = stream->readUint32("forbidden"); + if(versionMinor < 62) + stream->readUint32("hiddenForbidden"); + cases[i].guardArea = stream->readUint32("guardArea"); + cases[i].clearArea = stream->readUint32("clearArea"); + cases[i].scriptAreas = stream->readUint16("scriptAreas"); + cases[i].canRessourcesGrow = stream->readUint8("canRessourcesGrow"); + if(versionMinor >= 63) + cases[i].fertility = stream->readUint16("fertility"); + fertilityMaximum = std::max(fertilityMaximum, cases[i].fertility); + + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + for(int n=0; n<9; ++n) + { + stream->readEnterSection(n); + setAreaName(n, stream->readText("areaname")); + stream->readLeaveSection(); + } + + if (game) + { + /* Must set game field before following action as they + may need it (in particular + makeDiscoveredAreasExplored uses it). */ + this->game=game; + + // This is a game, so we do compute gradients + for (int t=0; treadSint32("wSector"); + hSector = stream->readSint32("hSector"); + sizeSector = wSector*hSector; + assert(sectors == NULL); + sectors = new Sector[sizeSector]; + +#ifndef YOG_SERVER_ONLY + // Map::setGame is bypassed on the loaded-game path (Game::load uses + // Map::load directly and the game pointer is set inline above), so + // the per-sector render buckets must be sized here too. + if (game) + game->animations->resize(sizeSector); +#endif // !YOG_SERVER_ONLY + + arraysBuilt = true; + + stream->readEnterSection("sectors"); + for (int i=0; ireadEnterSection(i); + if (!sectors[i].load(stream, this->game, versionMinor)) + { + stream->readLeaveSection(3); + return false; + } + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + stream->read(signature, 4, "signatureEnd"); + stream->readLeaveSection(); + + if (memcmp(signature, "MapE", 4)!=0) + { + fprintf(stderr, "Map:: Failed to find signature at the end of Map.\n"); + return false; + } + + return true; +} + +void Map::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Map"); + stream->write("MapB", 4, "signatureStart"); + + // We save size: + stream->writeSint32(wDec, "wDec"); + stream->writeSint32(hDec, "hDec"); + + // We write what's inside the map: + stream->write(undermap, size, "undermap"); + stream->writeEnterSection("cases"); + for (size_t i=0; iwriteEnterSection(i); + stream->writeUint32(mapDiscovered[i], "mapDiscovered"); + + stream->writeUint16(cases[i].terrain, "terrain"); + stream->writeUint16(cases[i].building, "building"); + + stream->write(&(cases[i].ressource), 4, "ressource"); + + stream->writeUint16(cases[i].groundUnit, "groundUnit"); + stream->writeUint16(cases[i].airUnit, "airUnit"); + stream->writeUint32(cases[i].forbidden, "forbidden"); + stream->writeUint32(cases[i].guardArea, "guardArea"); + stream->writeUint32(cases[i].clearArea, "clearArea"); + stream->writeUint16(cases[i].scriptAreas, "scriptAreas"); + stream->writeUint8(cases[i].canRessourcesGrow, "canRessourcesGrow"); + stream->writeUint16(cases[i].fertility, "fertility"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + //Save area names + for(int n=0; n<9; ++n) + { + stream->writeEnterSection(n); + stream->writeText(getAreaName(n), "areaname"); + stream->writeLeaveSection(); + } + + // We save sectors: + stream->writeSint32(wSector, "wSector"); + stream->writeSint32(hSector, "hSector"); + stream->writeEnterSection("sectors"); + for (int i=0; iwriteEnterSection(i); + sectors[i].save(stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->write("MapE", 4, "signatureEnd"); + stream->writeLeaveSection(); +} + + +void Map::addTeam(void) +{ + int numberOfTeam=game->mapHeader.getNumberOfTeams(); + int oldNumberOfTeam=numberOfTeam-1; + assert(numberOfTeam>0); + + for (int t=0; tmapHeader.getNumberOfTeams(); +// int oldNumberOfTeam=numberOfTeam+1; + assert(numberOfTeam or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "BinaryStream.h" #include @@ -170,7 +153,9 @@ void MapThumbnail::encodeData(GAGCore::OutputStream* stream) const //According to zlib documentation, the out buffer must be 0.1% larger than in buffer + 12 bytes unsigned long compressedLength = (128 * 128 * 3 * 1001) / 1000 + 13; Uint8* compressed = new Uint8[compressedLength]; - compress2(compressed, &compressedLength, buffer, 128 * 128 * 3, 9); + int zret = compress2(compressed, &compressedLength, buffer, 128 * 128 * 3, 9); + if (zret != Z_OK) + std::cerr << "MapThumbnail::encodeData: compress2 failed with error " << zret << std::endl; stream->writeUint32(compressedLength, "compressedLength"); stream->write(compressed, compressedLength, "compressed"); stream->writeLeaveSection(); @@ -189,7 +174,9 @@ void MapThumbnail::decodeData(GAGCore::InputStream* stream, Uint32 versionMinor) stream->read(compressed, compressedLength, "compressed"); //uncompress with zlib unsigned long uncompLen = 128 * 128 * 3; - uncompress(buffer, &uncompLen, compressed, compressedLength); + int zret = uncompress(buffer, &uncompLen, compressed, compressedLength); + if (zret != Z_OK) + std::cerr << "MapThumbnail::decodeData: uncompress failed with error " << zret << std::endl; stream->readLeaveSection(); delete[] compressed; loaded=true; diff --git a/src/MapThumbnail.h b/src/map/io/MapThumbnail.h similarity index 50% rename from src/MapThumbnail.h rename to src/map/io/MapThumbnail.h index ed15a4a9b..c9965230c 100644 --- a/src/MapThumbnail.h +++ b/src/map/io/MapThumbnail.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef MapThumbnail_h -#define MapThumbnail_h +#pragma once #include #include "SDL_net.h" @@ -66,5 +48,3 @@ class MapThumbnail int lastW; int lastH; }; - -#endif diff --git a/src/map/pathfind/MapPathfindArea.cpp b/src/map/pathfind/MapPathfindArea.cpp new file mode 100644 index 000000000..d72a672be --- /dev/null +++ b/src/map/pathfind/MapPathfindArea.cpp @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "Unit.h" +#include "MapInternal.h" + +#include +#include +#include +#include + + +// Area pathfinding (forbidden, guard, clear, point-to-point) + +bool Map::pathfindForbidden(const Uint8 *optionGradient, int teamNumber, bool canSwim, int x, int y, int *dx, int *dy) +{ + Uint8 *gradient=forbiddenGradient[teamNumber][canSwim]; + assert(gradient); + + // Pick the neighbor with the highest (base, option) lexicographically. The base gradient + // dominates; the option gradient is used as a tiebreaker. Reject results where the chosen + // base is unreachable (i.e. require base > GRADIENT_UNREACHABLE). + Uint8 bestBase = 0; + Uint8 bestOption = 0; + int maxd = 0; + for (int di=0; di<8; di++) + { + int rx=tabClose[di][0]; + int ry=tabClose[di][1]; + int xg=(x+rx)&wMask; + int yg=(y+ry)&hMask; + if (!isFreeForGroundUnitNoForbidden(xg, yg, canSwim)) + continue; + size_t addr=xg+(yg< bestBase || (base == bestBase && option > bestOption)) + { + bestBase = base; + bestOption = option; + maxd = di; + } + } + if (bestBase > GRADIENT_UNREACHABLE) + { + *dx=tabClose[maxd][0]; + *dy=tabClose[maxd][1]; + return true; + } + return false; +} + +bool Map::pathfindArea(AreaKind kind, int teamNumber, bool canSwim, int x, int y, int *dx, int *dy) +{ + Uint8 *gradient = (kind == AreaKind::Guard) + ? guardAreasGradient[teamNumber][canSwim] + : clearAreasGradient[teamNumber][canSwim]; + Uint8 max = gradient[x + (y<, AStarComparator> openList(compare); + openList.push((x << hDec) + y); + aStarPoints[(x << hDec) + y] = AStarAlgorithmPoint(x,y,0,0,0,0,false); + + //These are all the examined points, so that these positions on aStarPoints + //Can be reset later. Why not reset or re-allocate the whole thing every + //call? Its slow! Use reserve to avoid doing this multiple times + aStarExaminedPoints.reserve(maximumLength*2 + 6); + aStarExaminedPoints.push_back((x << hDec) + y); + + while(!openList.empty()) + { + ///Get the smallest from the heap + int position = openList.top(); + openList.pop(); + + AStarAlgorithmPoint& pos = aStarPoints[position]; + pos.isClosed = true; + + if((pos.x == targetX && pos.y == targetY) || (pos.moveCost > maximumLength)) + { + break; + } + + for(int lx=-1; lx<=1; ++lx) + { + for(int ly=-1; ly<=1; ++ly) + { + int nx = (pos.x + lx + w) & wMask; + int ny = (pos.y + ly + h) & hMask; + int n = (nx << hDec) + ny; + AStarAlgorithmPoint& npos = aStarPoints[n]; + if(npos.isClosed) + { + continue; + } + else + { + int moveCost = pos.moveCost + 1; + int totalCost = moveCost + warpDistMax(targetX, targetY, nx, ny); + + //If this cell hasn't been examined at all yet + if(npos.x == -1) + { + if(isFreeForGroundUnit(nx, ny, canSwim, teamMask) || (nx == targetX && ny == targetY)) + { + //If the parent cell is the starting cell, add in the starting direction + if(pos.dx == 0 && pos.dy == 0) + { + npos = AStarAlgorithmPoint(nx, ny, lx, ly, moveCost, totalCost, false); + openList.push(n); + } + //Else, the direction is the same as the parents node + else + { + npos = AStarAlgorithmPoint(nx, ny, pos.dx, pos.dy, moveCost, totalCost, false); + openList.push(n); + } + aStarExaminedPoints.push_back(n); + } + } + //Check if we can improve this cells value by taking this route + else if(npos.moveCost > moveCost) + { + npos.moveCost = moveCost; + npos.totalCost = totalCost; + npos.dx = pos.dx; + npos.dy = pos.dy; + } + } + } + } + } + + AStarAlgorithmPoint final = aStarPoints[(targetX << hDec) + targetY]; + + //Clear all of the examined points for the next call to this algorithm + for(unsigned i=0; i +#include +#include +#include + + +// Building pathfinding (buildingAvailable, pathfindBuilding, dirtyLocalGradient) + +namespace { + +// Probe a 32x32 local gradient at (lx, ly) and its 8 neighbors. If any cell has a +// reachable gradient (g > GRADIENT_UNREACHABLE), set *dist = GRADIENT_AT_GOAL - g +// (distance to the building) and return true. +bool probeLocalGradient(const Uint8 *gradient, int lx, int ly, int *dist) +{ + Uint8 currentg = gradient[lx + (ly << 5)]; + if (currentg > GRADIENT_UNREACHABLE) + { + *dist = GRADIENT_AT_GOAL - currentg; + return true; + } + for (int d = 0; d < 8; d++) + { + int ddx, ddy; + Unit::dxDyFromDirection(d, &ddx, &ddy); + int lxddx = clip_0_31(lx + ddx); + int lyddy = clip_0_31(ly + ddy); + Uint8 g = gradient[lxddx + (lyddy << 5)]; + if (g > GRADIENT_UNREACHABLE) + { + *dist = GRADIENT_AT_GOAL - g; + return true; + } + } + return false; +} + +} // namespace + +// Probe a full-map global gradient at (x, y) and its 8 neighbors. +bool Map::probeGlobalGradient(const Uint8 *gradient, int x, int y, int *dist) const +{ + Uint8 currentg = gradient[coordToIndex(x, y)]; + if (currentg > GRADIENT_UNREACHABLE) + { + *dist = GRADIENT_AT_GOAL - currentg; + return true; + } + for (int d = 0; d < 8; d++) + { + int ddx, ddy; + Unit::dxDyFromDirection(d, &ddx, &ddy); + Uint8 g = gradient[coordToIndex(x + ddx, y + ddy)]; + if (g > GRADIENT_UNREACHABLE) + { + *dist = GRADIENT_AT_GOAL - g; + return true; + } + } + return false; +} + +bool Map::buildingAvailable(Building *building, bool canSwim, int x, int y, int *dist) +{ + assert(building); + int bx=building->posX; + int by=building->posY; + x&=wMask; + y&=hMask; + assert(x>=0); + assert(y>=0); + + if (isInLocalGradient(x, y, bx, by)) + { + Uint8 *gradient=building->localGradient[canSwim]; + int lx=(x-bx+15+32)&31; + int ly=(y-by+15+32)&31; + + if (!building->dirtyLocalGradient[canSwim] && probeLocalGradient(gradient, lx, ly, dist)) + return true; + + updateLocalGradient(building, canSwim); + if (building->locked[canSwim]) + return false; + + return probeLocalGradient(gradient, lx, ly, dist); + } + + Uint8 *gradient=building->globalGradient[canSwim]; + if (gradient!=NULL) + { + // Existing global gradient: probe without recomputing. Recomputing the full-map + // gradient on every miss is too expensive — callers fall back to other strategies. + if (building->locked[canSwim]) + return false; + return probeGlobalGradient(gradient, x, y, dist); + } + + gradient=new Uint8[size]; + building->globalGradient[canSwim]=gradient; + + updateGlobalGradient(building, canSwim); + if (building->locked[canSwim]) + return false; + + return probeGlobalGradient(gradient, x, y, dist); +} + + +bool Map::pathfindBuilding(Building *building, bool canSwim, int x, int y, int *dx, int *dy) +{ + assert(building); + int bx=building->posX; + int by=building->posY; + assert(x>=0); + assert(y>=0); + Uint32 teamMask=building->owner->me; + if (((cases[x+y*w].forbidden) & teamMask)!=0) + { + int teamNumber=building->owner->teamNumber; + return pathfindForbidden(building->globalGradient[canSwim], teamNumber, canSwim, x, y, dx, dy); + } + Uint8 *gradient=building->localGradient[canSwim]; + if (isInLocalGradient(x, y, bx, by)) + { + int lx=(x-bx+15+32)&31; + int ly=(y-by+15+32)&31; + Uint8 currentg=gradient[lx+(ly<<5)]; + + if (!building->dirtyLocalGradient[canSwim] && currentg==GRADIENT_AT_GOAL) + { + *dx=0; + *dy=0; + return true; + } + + if (!building->dirtyLocalGradient[canSwim] && currentg>GRADIENT_UNREACHABLE) + { + if (directionByMinigrad(teamMask, canSwim, x, y, bx, by, dx, dy, gradient, true)) + return true; + } + + updateLocalGradient(building, canSwim); + if (building->locked[canSwim]) + return false; + + currentg=gradient[lx+ly*32]; + if (currentg>GRADIENT_UNREACHABLE) + { + if (directionByMinigrad(teamMask, canSwim, x, y, bx, by, dx, dy, gradient, true)) + return true; + } + } + // Local 32x32 gradient pathfinding has failed, fall back to the full-size gradient. + + gradient=building->globalGradient[canSwim]; + if (gradient==NULL) + { + gradient=new Uint8[size]; + building->globalGradient[canSwim]=gradient; + } + else + { + if (building->locked[canSwim]) + return false; + Uint8 currentg=gradient[coordToIndex(x, y)]; + if (currentg==GRADIENT_UNREACHABLE) + return false; + + if (directionByMinigrad(teamMask, canSwim, x, y, dx, dy, gradient, true)) + return true; + + // Recomputing the global gradient is expensive; throttle to once every 128 ticks (~5.12s). + if (building->lastGlobalGradientUpdateStepCounter[canSwim]+128>game->stepCounter) + return directionByMinigrad(teamMask, canSwim, x, y, dx, dy, gradient, false); + } + + updateGlobalGradient(building, canSwim); + building->lastGlobalGradientUpdateStepCounter[canSwim]=game->stepCounter; + + if (building->locked[canSwim]) + return false; + + Uint8 currentg=gradient[coordToIndex(x, y)]; + if (currentg>GRADIENT_UNREACHABLE) + { + if (directionByMinigrad(teamMask, canSwim, x, y, dx, dy, gradient, true)) + return true; + } + + return false; +} + + +void Map::dirtyLocalGradient(int x, int y, int wl, int hl, int teamNumber) +{ + y &= hMask; + x &= wMask; + for (int hi=0; hiteams[teamNumber]->myBuildings[Building::GIDtoID(bgid)]; + b->resetLocalRessources(); + } + } + } +} diff --git a/src/map/pathfind/MapPathfindRessource.cpp b/src/map/pathfind/MapPathfindRessource.cpp new file mode 100644 index 000000000..303a12fad --- /dev/null +++ b/src/map/pathfind/MapPathfindRessource.cpp @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "BuildingType.h" +#include "Unit.h" +#include "MapInternal.h" + +#include +#include +#include +#include + + +// Ressource pathfinding for units (pathfindRessource, pathfindLocalRessource, pathfindRandom) + +bool Map::pathfindRessource(int teamNumber, Uint8 ressourceType, bool canSwim, int x, int y, int *dx, int *dy, bool *stopWork) +{ + assert(ressourceTypeposX; + int y=unit->posY; + if ((cases[x+(y<owner->me) + { + if (pathfindForbidden(NULL, unit->owner->teamNumber, (unit->performance[SWIM]>0), x, y, &unit->dx, &unit->dy)) + { + unit->directionFromDxDy(); + } + else + { + unit->dx=0; + unit->dy=0; + unit->direction=8; + } + } + else + { + bool da[8]; + int count=0; + for (int di=0; di<8; di++) + { + int tx=(x+tabClose[di][0])&wMask; + int ty=(y+tabClose[di][1])&hMask; + if (isFreeForGroundUnit(tx, ty, (unit->performance[SWIM]>0), unit->owner->me)) + { + da[di]=true; + count++; + } + else + da[di]=false; + } + if (count==0) + { + unit->dx=0; + unit->dy=0; + unit->direction=8; + return; + } + int dir=syncRand()%count; + for (int di=0; di<8; di++) + if (da[di] && dir--==0) + { + unit->dx=tabClose[di][0]; + unit->dy=tabClose[di][1]; + unit->direction=di; + return; + } + assert(false); + } +} +#endif // !YOG_SERVER_ONLY + +bool Map::pathfindLocalRessource(Building *building, bool canSwim, int x, int y, int *dx, int *dy) +{ + assert(building); + assert(building->type); + assert(building->type->isVirtual); + + int bx=building->posX; + int by=building->posY; + Uint32 teamMask=building->owner->me; + + Uint8 *gradient=building->localRessources[canSwim]; + if (gradient==NULL) + { + if (!updateLocalRessources(building, canSwim)) + return false; + gradient=building->localRessources[canSwim]; + } + assert(gradient); + //HACK: I have no idea what is going on or why isInLocalGradient(x, y, bx, by) was asserted and why isInLocalGradient(x, y, bx, by) checks for the rectangle it is checking for, but this fixes a rare crash. + if(!isInLocalGradient(x, y, bx, by)) + return false; + + int lx=(x-bx+15+32)&31; + int ly=(y-by+15+32)&31; + int max=0; + Uint8 currentg=gradient[lx+(ly<<5)]; + bool found=false; + bool gradientUsable=false; + + // PORT: escalation path — bumps localRessourcesCleanTime by 16 to trigger clearingFlagStep's + // PORT: recompute (which checks >125) sooner. The 125/128 thresholds are slightly mismatched; + // PORT: align them in the Rust port (probably both should be 125). + if (currentg==GRADIENT_UNREACHABLE && (building->localRessourcesCleanTime[canSwim]+=16)<128) + { + // This means there are still ressources, but they are unreachable. + // We wait 5[s] before recomputing anything. + return false; + } + + if (currentg>GRADIENT_UNREACHABLE && currentg!=GRADIENT_AT_GOAL) + { + for (int sd=0; sd<=1; sd++) + for (int d=sd; d<8; d+=2) + { + int ddx, ddy; + Unit::dxDyFromDirection(d, &ddx, &ddy); + int lxddx=clip_0_31(lx+ddx); + int lyddy=clip_0_31(ly+ddy); + Uint8 g=gradient[lxddx+(lyddy<<5)]; + if (!gradientUsable && g>currentg && isHardSpaceForGroundUnit(x+ddx, y+ddy, canSwim, teamMask)) + gradientUsable=true; + if (g>=max && isFreeForGroundUnit(x+ddx, y+ddy, canSwim, teamMask)) + { + max=g; + *dx=ddx; + *dy=ddy; + found=true; + } + } + + if (gradientUsable) + { + if (!found) + { + *dx=0; + *dy=0; + } + return true; + } + } + + updateLocalRessources(building, canSwim); + + max=0; + currentg=gradient[lx+(ly<<5)]; + found=false; + gradientUsable=false; + + if (currentg==GRADIENT_UNREACHABLE) + return false; + + if (currentg==GRADIENT_FORBIDDEN || currentg==GRADIENT_AT_GOAL) + return false; + + for (int sd=0; sd<=1; sd++) + for (int d=sd; d<8; d+=2) + { + int ddx, ddy; + Unit::dxDyFromDirection(d, &ddx, &ddy); + int lxddx=clip_0_31(lx+ddx); + int lyddy=clip_0_31(ly+ddy); + Uint8 g=gradient[lxddx+(lyddy<<5)]; + if (!gradientUsable && g>currentg && isHardSpaceForGroundUnit(x+ddx, y+ddy, canSwim, teamMask)) + gradientUsable=true; + if (g>=max && isFreeForGroundUnit(x+ddx, y+ddy, canSwim, teamMask)) + { + max=g; + *dx=ddx; + *dy=ddy; + found=true; + } + } + + if (!gradientUsable) + return false; + + if (!found) + { + *dx=0; + *dy=0; + } + return true; +} diff --git a/src/NetBroadcastListener.cpp b/src/net/NetBroadcastListener.cpp similarity index 74% rename from src/NetBroadcastListener.cpp rename to src/net/NetBroadcastListener.cpp index 9a68eca02..e645ecde7 100644 --- a/src/NetBroadcastListener.cpp +++ b/src/net/NetBroadcastListener.cpp @@ -1,23 +1,9 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "NetBroadcastListener.h" #include "NetConsts.h" +#include "Order.h" #include "Stream.h" #include "BinaryStream.h" #include "StreamBackend.h" @@ -51,7 +37,7 @@ void NetBroadcastListener::update() while(result == 1) { Uint16 length = SDLNet_Read16(packet->data); - MemoryStreamBackend* msb = new MemoryStreamBackend(packet->data+2, length); + MemoryStreamBackend* msb = new MemoryStreamBackend(packet->data+NET_FRAME_LENGTH_PREFIX_BYTES, length); msb->seekFromStart(0); BinaryInputStream* bis = new BinaryInputStream(msb); diff --git a/src/net/NetBroadcastListener.h b/src/net/NetBroadcastListener.h new file mode 100644 index 000000000..c58053f40 --- /dev/null +++ b/src/net/NetBroadcastListener.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include "SDL_net.h" +#include "LANGameInformation.h" +#include + +///This listens for sub-net broadcasts (finding a LAN game) +class NetBroadcastListener +{ +public: + ///Constructs a NetBroadcastListener, and begins listening + NetBroadcastListener(); + + ~NetBroadcastListener(); + + ///Updates the broadcast listener + void update(); + + ///Gets a list of all the LAN games + const std::vector& getLANGames(); + + ///Gets the IP address for the given lan game + std::string getIPAddress(size_t num); + + ///Enables listening + void enableListening(); + + ///Disables listening + void disableListening(); +private: + UDPsocket socket; + std::vector games; + std::vector timeouts; + std::vector addresses; + Uint64 lastTime; +}; + diff --git a/src/NetBroadcaster.cpp b/src/net/NetBroadcaster.cpp similarity index 69% rename from src/NetBroadcaster.cpp rename to src/net/NetBroadcaster.cpp index 80aa58a78..7683b2fdf 100644 --- a/src/NetBroadcaster.cpp +++ b/src/net/NetBroadcaster.cpp @@ -1,29 +1,15 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "NetBroadcaster.h" #include "NetConsts.h" +#include "Order.h" #include "Stream.h" #include "BinaryStream.h" #include "StreamBackend.h" #include "SDLCompat.h" // for SDL_GetTicks64 fallback #include -#include "boost/lexical_cast.hpp" +#include using namespace GAGCore; @@ -65,10 +51,10 @@ void NetBroadcaster::update() Uint32 length = msb->getPosition(); msb->seekFromStart(0); - UDPpacket* packet = SDLNet_AllocPacket(length+2); - packet->len = length+2; + UDPpacket* packet = SDLNet_AllocPacket(length+NET_FRAME_LENGTH_PREFIX_BYTES); + packet->len = length+NET_FRAME_LENGTH_PREFIX_BYTES; SDLNet_Write16(length, packet->data); - msb->read(packet->data+2, length); + msb->read(packet->data+NET_FRAME_LENGTH_PREFIX_BYTES, length); int result = SDLNet_UDP_Send(socket, 0, packet); if(!result) { diff --git a/src/net/NetBroadcaster.h b/src/net/NetBroadcaster.h new file mode 100644 index 000000000..8a7de7d16 --- /dev/null +++ b/src/net/NetBroadcaster.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include "LANGameInformation.h" +#include "SDL_net.h" + +///This class allows for subnet broadcasting (hosting a LAN game) +class NetBroadcaster +{ +public: + ///Creates a new NetBroadcaster with the given information to broadcast + NetBroadcaster(LANGameInformation& info); + + ~NetBroadcaster(); + + ///Begins broadcasting the following game information + void broadcast(LANGameInformation& info); + + ///Updates the broadcaster + void update(); + + ///Disables broadcasting + void disableBroadcasting(); + + ///Enables broadcasting + void enableBroadcasting(); +private: + LANGameInformation info; + UDPsocket socket; + UDPsocket localsocket; + Uint64 lastTime; + Uint32 timer; +}; + diff --git a/src/NetConnection.cpp b/src/net/NetConnection.cpp similarity index 55% rename from src/NetConnection.cpp rename to src/net/NetConnection.cpp index 6ef15d0a8..f6f3e773b 100644 --- a/src/NetConnection.cpp +++ b/src/net/NetConnection.cpp @@ -1,31 +1,18 @@ - /* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "NetConnection.h" #include +#include +#include #include #include "StreamBackend.h" #include "BinaryStream.h" #include "NetMessage.h" using namespace GAGCore; -using boost::static_pointer_cast; -using boost::shared_ptr; +using std::static_pointer_cast; +using std::shared_ptr; @@ -35,7 +22,7 @@ using boost::shared_ptr; NetConnection::NetConnection(const std::string& naddress, Uint16 port) : connect(incoming, incomingMutex) { - boost::thread thread(boost::ref(connect)); + connectThread = std::thread(std::ref(connect)); connecting=false; openConnection(naddress, port); } @@ -45,16 +32,17 @@ NetConnection::NetConnection(const std::string& naddress, Uint16 port) NetConnection::NetConnection() : connect(incoming, incomingMutex) { - boost::thread thread(boost::ref(connect)); + connectThread = std::thread(std::ref(connect)); } NetConnection::~NetConnection() { - boost::shared_ptr exitthread(new NTExitThread); + std::shared_ptr exitthread(new NTExitThread); connect.sendMessage(exitthread); - while(!connect.hasThreadExited()); + if (connectThread.joinable()) + connectThread.join(); } @@ -63,7 +51,7 @@ void NetConnection::openConnection(const std::string& connectaddress, Uint16 por { address = connectaddress; connecting=true; - boost::shared_ptr toconnect(new NTConnect(connectaddress, port)); + std::shared_ptr toconnect(new NTConnect(connectaddress, port)); connect.sendMessage(toconnect); } @@ -71,7 +59,7 @@ void NetConnection::openConnection(const std::string& connectaddress, Uint16 por void NetConnection::closeConnection() { - boost::shared_ptr close(new NTCloseConnection); + std::shared_ptr close(new NTCloseConnection); connect.sendMessage(close); } @@ -93,24 +81,24 @@ bool NetConnection::isConnecting() void NetConnection::update() { - boost::recursive_mutex::scoped_lock lock(incomingMutex); + std::lock_guard lock(incomingMutex); while(!incoming.empty()) { - boost::shared_ptr message = incoming.front(); + std::shared_ptr message = incoming.front(); incoming.pop(); Uint8 type = message->getMessageType(); switch(type) { case NTMCouldNotConnect: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); //std::cout<<"NetConnection::getMessage(): "<format()< info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); address = info->getIPAddress(); //std::cout<<"NetConnection::getMessage(): "<format()< info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); //std::cout<<"NetConnection::getMessage(): "<format()< info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); recieved.push(info->getMessage()); //std::cout<<"NetConnection::getMessage(): "<format()<getMessage()->format()< NetConnection::getMessage() void NetConnection::sendMessage(shared_ptr message) { //std::cout<<"Sending: "<format()< close(new NTSendMessage(message)); + std::shared_ptr close(new NTSendMessage(message)); connect.sendMessage(close); } @@ -179,11 +167,11 @@ bool NetConnection::attemptConnection(TCPsocket& serverSocket) if(socket) { IPaddress ip = *SDLNet_TCP_GetPeerAddress(socket); - address = boost::lexical_cast((ip.host >> 0 ) & 0xff) + "." + - boost::lexical_cast((ip.host >> 8 ) & 0xff) + "." + - boost::lexical_cast((ip.host >> 16) & 0xff) + "." + - boost::lexical_cast((ip.host >> 24) & 0xff); - boost::shared_ptr accept(new NTAcceptConnection(socket)); + address = std::to_string((ip.host >> 0 ) & 0xff) + "." + + std::to_string((ip.host >> 8 ) & 0xff) + "." + + std::to_string((ip.host >> 16) & 0xff) + "." + + std::to_string((ip.host >> 24) & 0xff); + std::shared_ptr accept(new NTAcceptConnection(socket)); connect.sendMessage(accept); while(connect.isConnected() == false) SDL_Delay(5); diff --git a/src/NetConnection.h b/src/net/NetConnection.h similarity index 62% rename from src/NetConnection.h rename to src/net/NetConnection.h index 8c51fb3ef..9b9f0c66a 100644 --- a/src/NetConnection.h +++ b/src/net/NetConnection.h @@ -1,30 +1,15 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __NetConnection_h -#define __NetConnection_h +#pragma once #include "SDL_net.h" #include "NetConnectionThread.h" #include -#include +#include +#include -using boost::shared_ptr; +using std::shared_ptr; class NetListener; class NetMessage; @@ -77,9 +62,10 @@ class NetConnection private: NetConnectionThread connect; - - std::queue > incoming; - boost::recursive_mutex incomingMutex; + std::thread connectThread; + + std::queue > incoming; + std::recursive_mutex incomingMutex; std::queue > recieved; std::string address; @@ -87,4 +73,3 @@ class NetConnection }; -#endif diff --git a/src/NetConnectionThread.cpp b/src/net/NetConnectionThread.cpp similarity index 60% rename from src/NetConnectionThread.cpp rename to src/net/NetConnectionThread.cpp index 3ebb14208..6911350e6 100644 --- a/src/NetConnectionThread.cpp +++ b/src/net/NetConnectionThread.cpp @@ -1,37 +1,22 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "NetConnectionThread.h" +#include "Order.h" #include "StreamBackend.h" #include "BinaryStream.h" #include "NetMessage.h" #include "SDLCompat.h" -#include "boost/lexical_cast.hpp" +#include using namespace GAGCore; -using boost::static_pointer_cast; +using std::static_pointer_cast; -NetConnectionThread::NetConnectionThread(std::queue >& outgoing, boost::recursive_mutex& outgoingMutex) - : outgoing(outgoing), outgoingMutex(outgoingMutex) +NetConnectionThread::NetConnectionThread(std::queue >& outgoing, std::recursive_mutex& outgoingMutex) + : ThreadMessageQueues(outgoing, outgoingMutex) { set=SDLNet_AllocSocketSet(1); connected=false; - hasExited = false; } @@ -52,9 +37,9 @@ void NetConnectionThread::operator()() //First parse incoming thread messages while(true) { - boost::shared_ptr message; + std::shared_ptr message; { - boost::recursive_mutex::scoped_lock lock(incomingMutex); + std::lock_guard lock(incomingMutex); if(!incoming.empty()) { message = incoming.front(); @@ -70,13 +55,13 @@ void NetConnectionThread::operator()() { case NTMConnect: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); if(!connected) { //Resolve the address if(SDLNet_ResolveHost(&address, info->getServer().c_str(), info->getPort()) == -1) { - boost::shared_ptr error(new NTCouldNotConnect(SDLNet_GetError())); + std::shared_ptr error(new NTCouldNotConnect(SDLNet_GetError())); sendToMainThread(error); } else @@ -85,14 +70,14 @@ void NetConnectionThread::operator()() socket=SDLNet_TCP_Open(&address); if(!socket) { - boost::shared_ptr error(new NTCouldNotConnect(SDLNet_GetError())); + std::shared_ptr error(new NTCouldNotConnect(SDLNet_GetError())); sendToMainThread(error); } else { SDLNet_TCP_AddSocket(set, socket); connected=true; - boost::shared_ptr connected(new NTConnected(info->getServer())); + std::shared_ptr connected(new NTConnected(info->getServer())); sendToMainThread(connected); } } @@ -101,7 +86,7 @@ void NetConnectionThread::operator()() break; case NTMCloseConnection: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); if(connected) { closeConnection(); @@ -110,10 +95,10 @@ void NetConnectionThread::operator()() break; case NTMSendMessage: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); if(connected) { - boost::shared_ptr message = info->getMessage(); + std::shared_ptr message = info->getMessage(); //std::cout<<"Sending: "<format()<getPosition(); msb->seekFromStart(0); - Uint8* newData = new Uint8[length+2]; + Uint8* newData = new Uint8[length+NET_FRAME_LENGTH_PREFIX_BYTES]; SDLNet_Write16(length, newData); - msb->read(newData+2, length); + msb->read(newData+NET_FRAME_LENGTH_PREFIX_BYTES, length); - Uint32 result=SDLNet_TCP_Send(socket, newData, length+2); - if(result<(length+2)) + Uint32 result=SDLNet_TCP_Send(socket, newData, length+NET_FRAME_LENGTH_PREFIX_BYTES); + if(result<(length+NET_FRAME_LENGTH_PREFIX_BYTES)) { - boost::shared_ptr error(new NTLostConnection(SDLNet_GetError())); + std::shared_ptr error(new NTLostConnection(SDLNet_GetError())); sendToMainThread(error); closeConnection(); } @@ -154,7 +139,7 @@ void NetConnectionThread::operator()() break; case NTMAcceptConnection: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); if(!connected) { connected=true; @@ -185,7 +170,7 @@ void NetConnectionThread::operator()() //SDLNet_CheckSockets is used because it is non-blocking if(numReady==-1) { - boost::shared_ptr error(new NTLostConnection(SDLNet_GetError())); + std::shared_ptr error(new NTLostConnection(SDLNet_GetError())); sendToMainThread(error); perror("SDLNet_CheckSockets"); if(connected) @@ -195,11 +180,11 @@ void NetConnectionThread::operator()() else if(numReady) { //Read and interpret the length of the message - Uint8* lengthData = new Uint8[2]; - int amount = SDLNet_TCP_Recv(socket, lengthData, 2); + Uint8* lengthData = new Uint8[NET_FRAME_LENGTH_PREFIX_BYTES]; + int amount = SDLNet_TCP_Recv(socket, lengthData, NET_FRAME_LENGTH_PREFIX_BYTES); if(amount <= 0) { - boost::shared_ptr error(new NTLostConnection(SDLNet_GetError())); + std::shared_ptr error(new NTLostConnection(SDLNet_GetError())); sendToMainThread(error); closeConnection(); } @@ -214,7 +199,7 @@ void NetConnectionThread::operator()() amount = SDLNet_TCP_Recv(socket, data+i, 1); if(amount <= 0) { - boost::shared_ptr error(new NTLostConnection(SDLNet_GetError())); + std::shared_ptr error(new NTLostConnection(SDLNet_GetError())); sendToMainThread(error); closeConnection(); } @@ -237,8 +222,8 @@ void NetConnectionThread::operator()() BinaryInputStream* bis = new BinaryInputStream(msb); //Now interpret the message from the data, and add it to the queue - shared_ptr message = NetMessage::getNetMessage(bis); - boost::shared_ptr recieved(new NTRecievedMessage(message)); + std::shared_ptr message = NetMessage::getNetMessage(bis); + std::shared_ptr recieved(new NTRecievedMessage(message)); sendToMainThread(recieved); //std::cout<<"Recieved: "<format()< message) -{ - boost::recursive_mutex::scoped_lock lock(incomingMutex); - incoming.push(message); -} - - - -bool NetConnectionThread::hasThreadExited() -{ - return hasExited; -} - - - bool NetConnectionThread::isConnected() { return connected; @@ -288,15 +258,3 @@ void NetConnectionThread::closeConnection() SDLNet_TCP_Close(socket); connected=false; } - - - -void NetConnectionThread::sendToMainThread(boost::shared_ptr message) -{ - boost::recursive_mutex::scoped_lock lock(outgoingMutex); - outgoing.push(message); -} - - - - diff --git a/src/net/NetConnectionThread.h b/src/net/NetConnectionThread.h new file mode 100644 index 000000000..64968cc7b --- /dev/null +++ b/src/net/NetConnectionThread.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include "NetConnectionThreadMessage.h" +#include "ThreadMessageQueues.h" + +///Manages a single TCP connection on a worker thread +class NetConnectionThread : public ThreadMessageQueues +{ +public: + NetConnectionThread(std::queue >& outgoing, std::recursive_mutex& outgoingMutex); + + ~NetConnectionThread(); + + ///Runs the net thread + void operator()(); + + ///Returns true if this object is connected + bool isConnected(); + +private: + ///Closes the connection + void closeConnection(); + + IPaddress address; + TCPsocket socket; + SDLNet_SocketSet set; + bool connected; +}; diff --git a/src/NetConnectionThreadMessage.cpp b/src/net/NetConnectionThreadMessage.cpp similarity index 83% rename from src/NetConnectionThreadMessage.cpp rename to src/net/NetConnectionThreadMessage.cpp index ee62e389b..71f47e8f4 100644 --- a/src/NetConnectionThreadMessage.cpp +++ b/src/net/NetConnectionThreadMessage.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "NetConnectionThreadMessage.h" #include @@ -229,7 +214,7 @@ std::string NTLostConnection::getError() const -NTRecievedMessage::NTRecievedMessage(boost::shared_ptr message) +NTRecievedMessage::NTRecievedMessage(std::shared_ptr message) : message(message) { } @@ -264,14 +249,14 @@ bool NTRecievedMessage::operator==(const NetConnectionThreadMessage& rhs) const } -boost::shared_ptr NTRecievedMessage::getMessage() const +std::shared_ptr NTRecievedMessage::getMessage() const { return message; } -NTSendMessage::NTSendMessage(boost::shared_ptr message) +NTSendMessage::NTSendMessage(std::shared_ptr message) : message(message) { } @@ -306,7 +291,7 @@ bool NTSendMessage::operator==(const NetConnectionThreadMessage& rhs) const } -boost::shared_ptr NTSendMessage::getMessage() const +std::shared_ptr NTSendMessage::getMessage() const { return message; } diff --git a/src/NetConnectionThreadMessage.h b/src/net/NetConnectionThreadMessage.h similarity index 80% rename from src/NetConnectionThreadMessage.h rename to src/net/NetConnectionThreadMessage.h index bb24a9d43..0dc1219bc 100644 --- a/src/NetConnectionThreadMessage.h +++ b/src/net/NetConnectionThreadMessage.h @@ -1,28 +1,12 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef NetConnectionThreadMessage_h -#define NetConnectionThreadMessage_h +#pragma once #include #include "SDL_net.h" #include -#include +#include class NetMessage; @@ -189,7 +173,7 @@ class NTRecievedMessage : public NetConnectionThreadMessage { public: ///Creates a NTRecievedMessage event - NTRecievedMessage(boost::shared_ptr message); + NTRecievedMessage(std::shared_ptr message); ///Returns NTMRecievedMessage Uint8 getMessageType() const; @@ -201,9 +185,9 @@ class NTRecievedMessage : public NetConnectionThreadMessage bool operator==(const NetConnectionThreadMessage& rhs) const; ///Retrieves message - boost::shared_ptr getMessage() const; + std::shared_ptr getMessage() const; private: - boost::shared_ptr message; + std::shared_ptr message; }; @@ -214,7 +198,7 @@ class NTSendMessage : public NetConnectionThreadMessage { public: ///Creates a NTSendMessage event - NTSendMessage(boost::shared_ptr message); + NTSendMessage(std::shared_ptr message); ///Returns NTMSendMessage Uint8 getMessageType() const; @@ -226,9 +210,9 @@ class NTSendMessage : public NetConnectionThreadMessage bool operator==(const NetConnectionThreadMessage& rhs) const; ///Retrieves message - boost::shared_ptr getMessage() const; + std::shared_ptr getMessage() const; private: - boost::shared_ptr message; + std::shared_ptr message; }; @@ -279,7 +263,3 @@ class NTExitThread : public NetConnectionThreadMessage //event_append_marker - - - -#endif diff --git a/src/net/NetConsts.h b/src/net/NetConsts.h new file mode 100644 index 000000000..97faefffc --- /dev/null +++ b/src/net/NetConsts.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +const unsigned int LAN_BROADCAST_PORT = 7486; +///This is the first port the system will try, and it will go incrementally up from there +const unsigned int P2P_CONNECTION_PORT_FIRST = 7485; +const unsigned int P2P_CONNECTION_PORT_LAST = 20001; + + +enum OrderTypes +{ + BAD_ORDER=0, + + ORDER_CREATE=20, + ORDER_MODIFY_BUILDING=22, + ORDER_MODIFY_EXCHANGE=23, + ORDER_MODIFY_SWARM=24, + ORDER_MODIFY_FLAG=30, + ORDER_MODIFY_CLEARING_FLAG=31, + ORDER_MODIFY_MIN_LEVEL_TO_FLAG=32, + ORDER_MOVE_FLAG=35, + ORDER_ALTERATE_FORBIDDEN=37, + ORDER_ALTERATE_GUARD_AREA=38, + ORDER_ALTERATE_CLEAR_AREA=39, + ORDER_DELETE=40, + ORDER_CANCEL_DELETE=41, + ORDER_CONSTRUCTION=42, + ORDER_CANCEL_CONSTRUCTION=43, + ORDER_CHANGE_PRIORITY=44, + + ORDER_NULL=51, + ORDER_PAUSE_GAME=59, + ORDER_PLAYER_QUIT_GAME=67, + + ORDER_TEXT_MESSAGE=71, + ORDER_VOICE_DATA=72, + ORDER_SET_ALLIANCE=73, + + ORDER_MAP_MARK=74, + + ORDER_ADJUST_LATENCY=100, + +}; + + diff --git a/src/NetEngine.cpp b/src/net/NetEngine.cpp similarity index 65% rename from src/NetEngine.cpp rename to src/net/NetEngine.cpp index 9bd58b1f3..e2406770b 100644 --- a/src/NetEngine.cpp +++ b/src/net/NetEngine.cpp @@ -1,27 +1,12 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "NetEngine.h" #include -#include "NetMessage.h" +#include "OrderMessages.h" -NetEngine::NetEngine(int numberOfPlayers, int localPlayer, int networkOrderRate, boost::shared_ptr router) +NetEngine::NetEngine(int numberOfPlayers, int localPlayer, int networkOrderRate, std::shared_ptr router) : numberOfPlayers(numberOfPlayers), localPlayer(localPlayer), router(router), networkOrderRate(networkOrderRate) { step=0; @@ -32,7 +17,7 @@ NetEngine::NetEngine(int numberOfPlayers, int localPlayer, int networkOrderRate, -void NetEngine::setNetworkInfo(int nnetworkOrderRate, boost::shared_ptr nrouter) +void NetEngine::setNetworkInfo(int nnetworkOrderRate, std::shared_ptr nrouter) { networkOrderRate = nnetworkOrderRate; router = nrouter; @@ -45,7 +30,7 @@ void NetEngine::advanceStep(Uint32 checksum) step+=1; if(localOrderSendCountdown == 0) { - boost::shared_ptr localOrder; + std::shared_ptr localOrder; if(outgoing.empty()) { @@ -79,11 +64,11 @@ void NetEngine::clearTopOrders() { for(int p=0; p o = orders[p].front(); + std::shared_ptr o = orders[p].front(); ///Handle latency adjustment order if(o->getOrderType() == ORDER_ADJUST_LATENCY) { - boost::shared_ptr al = boost::static_pointer_cast(o); + std::shared_ptr al = std::static_pointer_cast(o); int diff = (al->latencyAdjustment) - currentLatency; if(diff>0) { @@ -91,7 +76,7 @@ void NetEngine::clearTopOrders() { for(unsigned int p=0; p order = boost::shared_ptr(new NullOrder); + std::shared_ptr order = std::shared_ptr(new NullOrder); order->sender=p; orders[p].insert(orders[p].begin(), order); } @@ -105,7 +90,7 @@ void NetEngine::clearTopOrders() -void NetEngine::pushOrder(boost::shared_ptr order, int playerNumber, bool isAI) +void NetEngine::pushOrder(std::shared_ptr order, int playerNumber, bool isAI) { assert(playerNumber>=0); order->sender=playerNumber; @@ -125,14 +110,14 @@ void NetEngine::pushOrder(boost::shared_ptr order, int playerNumber, bool -boost::shared_ptr NetEngine::retrieveOrder(int playerNumber) +std::shared_ptr NetEngine::retrieveOrder(int playerNumber) { return *orders[playerNumber].begin(); } -void NetEngine::addLocalOrder(boost::shared_ptr order) +void NetEngine::addLocalOrder(std::shared_ptr order) { if(order->getOrderType() != ORDER_NULL) { @@ -167,10 +152,10 @@ void NetEngine::flushAllOrders() { while(!outgoing.empty()) { - boost::shared_ptr localOrder; + std::shared_ptr localOrder; localOrder = outgoing.front(); outgoing.pop(); - localOrder->gameCheckSum = static_cast(-1); + localOrder->gameCheckSum = ORDER_CHECKSUM_NONE; if(router) { @@ -191,7 +176,7 @@ void NetEngine::prepareForLatency(int playerNumber, int latency) currentLatency = latency; for(int s=0; s(new NullOrder), playerNumber, true); + pushOrder(std::shared_ptr(new NullOrder), playerNumber, true); } } @@ -223,15 +208,15 @@ Uint32 NetEngine::getWaitingOnMask() bool NetEngine::matchCheckSums() { - Uint32 checksum = static_cast(-1); + Uint32 checksum = ORDER_CHECKSUM_NONE; for(int p=0; pgameCheckSum; - if(playerCheckSum != static_cast(-1)) + if(playerCheckSum != ORDER_CHECKSUM_NONE) { - if(checksum == static_cast(-1)) + if(checksum == ORDER_CHECKSUM_NONE) checksum = playerCheckSum; else if(playerCheckSum != checksum) { @@ -247,7 +232,7 @@ bool NetEngine::matchCheckSums() void NetEngine::increaseLatencyAdjustment() { - boost::shared_ptr latency(new AdjustLatency(currentLatency+1)); + std::shared_ptr latency(new AdjustLatency(currentLatency+1)); addLocalOrder(latency); } diff --git a/src/NetEngine.h b/src/net/NetEngine.h similarity index 65% rename from src/NetEngine.h rename to src/net/NetEngine.h index fc59c8a69..12dea6ef9 100644 --- a/src/NetEngine.h +++ b/src/net/NetEngine.h @@ -1,26 +1,10 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __NetEngine_h -#define __NetEngine_h +#pragma once #include "Order.h" -#include +#include #include #include #include "NetConnection.h" @@ -34,10 +18,10 @@ class NetEngine { public: ///Constructs the NetEngine - NetEngine(int numberOfPlayers, int localPlayer, int networkOrderRate = 1, boost::shared_ptr router = boost::shared_ptr()); + NetEngine(int numberOfPlayers, int localPlayer, int networkOrderRate = 1, std::shared_ptr router = std::shared_ptr()); ///Sets the network game info - void setNetworkInfo(int networkOrderRate, boost::shared_ptr client); + void setNetworkInfo(int networkOrderRate, std::shared_ptr client); ///Advances the step void advanceStep(Uint32 checksum); @@ -46,13 +30,13 @@ class NetEngine void clearTopOrders(); //Pushes an order to the NetEngine. AI's are special because they don't have padding arround orders - void pushOrder(boost::shared_ptr order, int playerNumber, bool isAI); + void pushOrder(std::shared_ptr order, int playerNumber, bool isAI); ///Retrieves the order for the given player for this turn - boost::shared_ptr retrieveOrder(int playerNumber); + std::shared_ptr retrieveOrder(int playerNumber); ///Adds a order from the local player, which will be queued and sent across the network when needed - void addLocalOrder(boost::shared_ptr order); + void addLocalOrder(std::shared_ptr order); ///Tells whether the network is ready at the current tick. For ///the network to be ready, all Orders from all players must be @@ -90,19 +74,18 @@ class NetEngine private: ///This stores the queues with the orders from each player - std::vector > > orders; + std::vector > > orders; ///This queue stores all of the local orders that have to be sent out ///on their turn - std::queue > outgoing; + std::queue > outgoing; int step; int numberOfPlayers; ///This count-downs steps until an order is sent across the network int localOrderSendCountdown; int localPlayer; - boost::shared_ptr router; + std::shared_ptr router; int networkOrderRate; int currentLatency; }; -#endif diff --git a/src/NetGamePlayerManager.cpp b/src/net/NetGamePlayerManager.cpp similarity index 84% rename from src/NetGamePlayerManager.cpp rename to src/net/NetGamePlayerManager.cpp index c99d834f6..96cefc507 100644 --- a/src/NetGamePlayerManager.cpp +++ b/src/net/NetGamePlayerManager.cpp @@ -1,24 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "NetGamePlayerManager.h" #include "FormatableString.h" -#include "YOGServerGame.h" #include "Player.h" #include "AINames.h" @@ -113,8 +97,7 @@ void NetGamePlayerManager::removePlayer(int playerNumber) BasePlayer& bp = gameHeader.getBasePlayer(x); if(bp.type != Player::P_NONE) { - bp.number -= 1; - bp.numberMask = 1u>>bp.number; + bp.setNumber(bp.number - 1); if(bp.type >= Player::P_AI) { FormatableString name("%0 %1"); @@ -219,7 +202,7 @@ int NetGamePlayerManager::chooseTeamNumber() numberOfPlayersPerTeam[bp.teamNumber] += 1; } //Chooes a team number that has the lowest number of players attached - int lowest_number = 10000; + int lowest_number = TEAM_PLAYERCOUNT_INFINITY; int team_number = 0; for(int x=0; x diff --git a/src/NetListener.h b/src/net/NetListener.h similarity index 55% rename from src/NetListener.h rename to src/net/NetListener.h index f54d03b70..cdc705bcb 100644 --- a/src/NetListener.h +++ b/src/net/NetListener.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __NetListener_h -#define __NetListener_h +#pragma once #include "SDL_net.h" #include "NetConnection.h" @@ -60,4 +44,3 @@ class NetListener }; -#endif diff --git a/src/net/NetMessage.h b/src/net/NetMessage.h new file mode 100644 index 000000000..dab5765e7 --- /dev/null +++ b/src/net/NetMessage.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include +#include + +#include "Stream.h" +#include "NetMessageType.h" + +/// A message carried by the net engine. A message has a type tag (from +/// NetMessageType) and a body. The base class provides a static factory that +/// reads a message from a stream and returns a shared_ptr to the appropriate +/// derived class; callers can dispatch using getMessageType() + dynamic_cast. +class NetMessage +{ +public: + virtual ~NetMessage() {} + + /// Returns the message's NetMessageType tag. + virtual Uint8 getMessageType() const = 0; + + /// Reads a message off the stream and returns the appropriate derived class. + static std::shared_ptr getNetMessage(GAGCore::InputStream* stream); + + /// Encodes this message's body to the stream in its serialized form. + virtual void encodeData(GAGCore::OutputStream* stream) const = 0; + + /// Decodes this message's body from the stream. The leading type byte has + /// already been consumed by getNetMessage and can be ignored here. + virtual void decodeData(GAGCore::InputStream* stream) = 0; + + /// Human-readable representation, for debugging and logging. + virtual std::string format() const = 0; + + /// Compares two messages. Derived classes must check that rhs casts to + /// their concrete type before comparing internal data. + virtual bool operator==(const NetMessage& rhs) const = 0; + + /// Provided for convenience; derived classes may override for efficiency. + virtual bool operator!=(const NetMessage& rhs) const; +}; diff --git a/src/net/NetMessageType.h b/src/net/NetMessageType.h new file mode 100644 index 000000000..d2e3df1ef --- /dev/null +++ b/src/net/NetMessageType.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +/// Enumeration of message types carried over the YOG/lobby protocol. +/// The first block (through MNetSendServerInformation) must keep its order +/// to maintain wire compatibility with older clients/servers; later entries +/// may be reordered freely as the protocol is glob2-version-locked. +enum NetMessageType +{ + // These must be kept in this order to maintain compatibility with future versions of glob2 + MNetAcceptRegistration, + MNetAttemptLogin, + MNetAttemptRegistration, + MNetDisconnect, + MNetLoginSuccessful, + MNetPing, + MNetPingReply, + MNetRefuseLogin, + MNetRefuseRegistration, + MNetSendClientInformation, + MNetSendServerInformation, + + // These are all glob2 version dependent and can be kept in any order + MNetAcknowledgeRouter, + MNetAddAI, + MNetAttemptJoinGame, + MNetChangePlayersTeam, + MNetCreateGame, + MNetCreateGameAccepted, + MNetCreateGameRefused, + MNetGameJoinAccepted, + MNetGameJoinRefused, + MNetIPIsBanned, + MNetKickPlayer, + MNetLeaveGame, + MNetNotReadyToLaunch, + MNetPlayerIsBanned, + MNetPlayerJoinsGame, + MNetReadyToLaunch, + MNetRefuseGameStart, + MNetRegisterRouter, + MNetRemoveAI, + MNetRequestGameStart, + MNetRequestFile, + MNetRouterAdministratorLogin, + MNetRouterAdministratorLoginAccepted, + MNetRouterAdministratorLoginRefused, + MNetRouterAdministratorSendCommand, + MNetRouterAdministratorSendText, + MNetSendAfterJoinGameInformation, + MNetSendFileChunk, + MNetSendFileInformation, + MNetSendGameHeader, + MNetSendGamePlayerInfo, + MNetSendGameResult, + MNetSendMapHeader, + MNetSendOrder, + MNetSendReteamingInformation, + MNetSendYOGMessage, + MNetSetGameInRouter, + MNetSetLatencyMode, + MNetStartGame, + MNetUpdateGameList, + MNetUpdatePlayerList, + MNetDownloadableMapInfos, + MNetRequestDownloadableMapList, + MNetRequestMapUpload, + MNetAcceptMapUpload, + MNetRefuseMapUpload, + MNetCancelSendingFile, + MNetCancelRecievingFile, + MNetRequestMapThumbnail, + MNetSendMapThumbnail, + MNetSubmitRatingOnMap, +}; diff --git a/src/NetReteamingInformation.cpp b/src/net/NetReteamingInformation.cpp similarity index 71% rename from src/NetReteamingInformation.cpp rename to src/net/NetReteamingInformation.cpp index c2ecd885d..d548950cc 100644 --- a/src/NetReteamingInformation.cpp +++ b/src/net/NetReteamingInformation.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "NetReteamingInformation.h" @@ -46,7 +31,7 @@ int NetReteamingInformation::getPlayersTeam(const std::string& playerName) const { if(doesPlayerHaveTeam(playerName)) return teams.find(playerName)->second; - return -1; + return RETEAM_NO_TEAM; } diff --git a/src/NetReteamingInformation.h b/src/net/NetReteamingInformation.h similarity index 61% rename from src/NetReteamingInformation.h rename to src/net/NetReteamingInformation.h index f2af01669..cbfd63f29 100644 --- a/src/NetReteamingInformation.h +++ b/src/net/NetReteamingInformation.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef NetReteamingInformation_h -#define NetReteamingInformation_h +#pragma once #include #include @@ -28,6 +12,11 @@ namespace GAGCore class InputStream; } +//! Sentinel returned by NetReteamingInformation::getPlayersTeam() when the +//! given player has no auto-assigned team (i.e. did not appear in the +//! source save-game). See NetReteamingInformation.cpp:34. +static constexpr int RETEAM_NO_TEAM = -1; + ///Reteaming is when you load a YOG save-game in YOG, and if the same players join, it automatically sets their team color ///This class stores reteaming information @@ -59,4 +48,3 @@ class NetReteamingInformation std::map teams; }; -#endif diff --git a/src/NetTestSuite.cpp b/src/net/NetTestSuite.cpp similarity index 95% rename from src/NetTestSuite.cpp rename to src/net/NetTestSuite.cpp index 155d62672..390cbaba4 100644 --- a/src/NetTestSuite.cpp +++ b/src/net/NetTestSuite.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "NetTestSuite.h" #include @@ -23,9 +8,16 @@ #include "BinaryStream.h" #include "NetReteamingInformation.h" +#include "FileTransferMessages.h" +#include "GameCreateMessages.h" +#include "GameHeaderMessages.h" +#include "GameJoinMessages.h" +#include "GameLaunchMessages.h" +#include "LobbyMessages.h" + using namespace GAGCore; -using boost::shared_ptr; +using std::shared_ptr; NetTestSuite::NetTestSuite() { @@ -393,7 +385,7 @@ int NetTestSuite::testNetSendOrder() return 1; shared_ptr netSendOrder1(new NetSendOrder); - netSendOrder1->changeOrder(boost::shared_ptr(new OrderDelete(1))); + netSendOrder1->changeOrder(std::shared_ptr(new OrderDelete(1))); if(!testSerialize(netSendOrder1)) return 2; diff --git a/src/NetTestSuite.h b/src/net/NetTestSuite.h similarity index 71% rename from src/NetTestSuite.h rename to src/net/NetTestSuite.h index dab080b3b..e8bed9b2b 100644 --- a/src/NetTestSuite.h +++ b/src/net/NetTestSuite.h @@ -1,30 +1,16 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef __NetTestSuite_h -#define __NetTestSuite_h - -#include "NetMessage.h" +#include "AuthMessages.h" +#include "OrderMessages.h" +#include "RegistrationMessages.h" #include "NetListener.h" #include "NetConnection.h" #include "YOGGameInfo.h" #include "YOGMessage.h" -#include +#include ///This is a basic test system for the low level net classes, ///NetConnection, NetListener, NetMessage, YOGGameInfo and YOGMessage @@ -42,7 +28,7 @@ class NetTestSuite ///This generic test tests the serialization of a provided object ///by serializing it, deserializing it, and testing for equality - template bool testSerialize(shared_ptr message); + template bool testSerialize(std::shared_ptr message); ///Tests that the initial states of two messages are equal template bool testInitial(); @@ -102,4 +88,3 @@ class NetTestSuite bool runAllTests(); }; -#endif diff --git a/src/IRC.cpp b/src/net/irc/IRC.cpp similarity index 92% rename from src/IRC.cpp rename to src/net/irc/IRC.cpp index fd1fb23a9..7011385b5 100644 --- a/src/IRC.cpp +++ b/src/net/irc/IRC.cpp @@ -1,22 +1,7 @@ -/* - Standalone IRC client - Copyright (C) 2001-2004 Stephane Magnenat - for any question or comment contact me at - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat + +// Standalone IRC client /* About IRC diff --git a/src/IRC.h b/src/net/irc/IRC.h similarity index 84% rename from src/IRC.h rename to src/net/irc/IRC.h index 99ac598f4..43ff89829 100644 --- a/src/IRC.h +++ b/src/net/irc/IRC.h @@ -1,25 +1,9 @@ -/* - Standalone IRC client - Copyright (C) 2001-2004 Stephane Magnenat - for any question or comment contact me at +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +// Standalone IRC client - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __IRC_H -#define __IRC_H +#pragma once #include @@ -178,5 +162,3 @@ class IRC //! Get a string in IRC format bool getString(char data[IRC_MESSAGE_SIZE]); }; - -#endif diff --git a/src/IRCTextMessageHandler.cpp b/src/net/irc/IRCTextMessageHandler.cpp similarity index 52% rename from src/IRCTextMessageHandler.cpp rename to src/net/irc/IRCTextMessageHandler.cpp index 668841352..9786e0809 100644 --- a/src/IRCTextMessageHandler.cpp +++ b/src/net/irc/IRCTextMessageHandler.cpp @@ -1,37 +1,21 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "IRCTextMessageHandler.h" #include "IRCThreadMessage.h" +#include #include #include #include "YOGConsts.h" using namespace GAGCore; -using boost::static_pointer_cast; +using std::static_pointer_cast; IRCTextMessageHandler::IRCTextMessageHandler() : irc(incoming, incomingMutex) { - boost::thread thread(boost::ref(irc)); + ircThread = std::thread(std::ref(irc)); userListModified = false; } @@ -39,13 +23,10 @@ IRCTextMessageHandler::IRCTextMessageHandler() IRCTextMessageHandler::~IRCTextMessageHandler() { //Tell the thread to exit and wait until it does - boost::shared_ptr message1(new ITExitThread); + std::shared_ptr message1(new ITExitThread); irc.sendMessage(message1); - - while(!irc.hasThreadExited()) - { - - } + if (ircThread.joinable()) + ircThread.join(); } @@ -56,8 +37,8 @@ void IRCTextMessageHandler::startIRC(const std::string& username) { nusername.replace(nusername.find(" "), 1, "_"); } - boost::shared_ptr message1(new ITConnect(IRC_SERVER, nusername, 6667)); - boost::shared_ptr message2(new ITJoinChannel(IRC_CHAN)); + std::shared_ptr message1(new ITConnect(IRC_SERVER, nusername, 6667)); + std::shared_ptr message2(new ITJoinChannel(IRC_CHAN)); irc.sendMessage(message1); irc.sendMessage(message2); @@ -67,7 +48,7 @@ void IRCTextMessageHandler::startIRC(const std::string& username) void IRCTextMessageHandler::stopIRC() { - boost::shared_ptr message1(new ITDisconnect); + std::shared_ptr message1(new ITDisconnect); irc.sendMessage(message1); } @@ -75,23 +56,23 @@ void IRCTextMessageHandler::stopIRC() void IRCTextMessageHandler::update() { - boost::recursive_mutex::scoped_lock lock(incomingMutex); + std::lock_guard lock(incomingMutex); while(!incoming.empty()) { - boost::shared_ptr message = incoming.front(); + std::shared_ptr message = incoming.front(); incoming.pop(); Uint8 type = message->getMessageType(); switch(type) { case ITMRecieveMessage: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); sendToAllListeners(info->getMessage()); } break; case ITMUserListModified: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); userListModified = true; users = info->getUsers(); } @@ -118,7 +99,7 @@ void IRCTextMessageHandler::removeTextMessageListener(IRCTextMessageListener* li void IRCTextMessageHandler::sendCommand(const std::string& command) { - boost::shared_ptr message1(new ITSendMessage(command)); + std::shared_ptr message1(new ITSendMessage(command)); irc.sendMessage(message1); } diff --git a/src/IRCTextMessageHandler.h b/src/net/irc/IRCTextMessageHandler.h similarity index 54% rename from src/IRCTextMessageHandler.h rename to src/net/irc/IRCTextMessageHandler.h index d6ada7410..1d1c61827 100644 --- a/src/IRCTextMessageHandler.h +++ b/src/net/irc/IRCTextMessageHandler.h @@ -1,29 +1,12 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __IRCTextMessageHandler_H -#define __IRCTextMessageHandler_H +#pragma once #include "IRCThread.h" -#include "boost/shared_ptr.hpp" +#include +#include ///This class represents an object that can listen for text messages from IRC @@ -72,16 +55,14 @@ class IRCTextMessageHandler void sendToAllListeners(const std::string& message); IRCThread irc; + std::thread ircThread; std::vector listeners; - std::queue > incoming; - boost::recursive_mutex incomingMutex; + std::queue > incoming; + std::recursive_mutex incomingMutex; std::vector users; bool userListModified; }; - - -#endif diff --git a/src/IRCThread.cpp b/src/net/irc/IRCThread.cpp similarity index 52% rename from src/IRCThread.cpp rename to src/net/irc/IRCThread.cpp index 936154061..b4ca2497f 100644 --- a/src/IRCThread.cpp +++ b/src/net/irc/IRCThread.cpp @@ -1,31 +1,14 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "IRCThread.h" #include "IRCThreadMessage.h" -#include -using boost::static_pointer_cast; +using std::static_pointer_cast; -IRCThread::IRCThread(std::queue >& outgoing, boost::recursive_mutex& outgoingMutex) - : outgoing(outgoing), outgoingMutex(outgoingMutex) +IRCThread::IRCThread(std::queue >& outgoing, std::recursive_mutex& outgoingMutex) + : ThreadMessageQueues(outgoing, outgoingMutex) { - hasExited = false; } @@ -37,35 +20,35 @@ void IRCThread::operator()() SDL_Delay(20); { //First parse incoming thread messages - boost::recursive_mutex::scoped_lock lock(incomingMutex); + std::lock_guard lock(incomingMutex); while(!incoming.empty()) { - boost::shared_ptr message = incoming.front(); + std::shared_ptr message = incoming.front(); incoming.pop(); Uint8 type = message->getMessageType(); switch(type) { case ITMConnect: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); irc.connect(info->getServer(), info->getServerPort(), info->getNick()); } break; case ITMDisconnect: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); irc.disconnect(); } break; case ITMSendMessage: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); irc.sendCommand(info->getText()); } break; case ITMJoinChannel: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); irc.joinChannel(info->getChannel()); irc.setChatChannel(info->getChannel()); channel = info->getChannel(); @@ -73,7 +56,7 @@ void IRCThread::operator()() break; case ITMExitThread: { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); irc.disconnect(); hasExited = true; return; @@ -87,7 +70,7 @@ void IRCThread::operator()() irc.step(); if(irc.isChannelUserBeenModified()) { - boost::shared_ptr m(new ITUserListModified); + std::shared_ptr m(new ITUserListModified); if (irc.initChannelUserListing(channel)) { @@ -107,7 +90,7 @@ void IRCThread::operator()() message+=irc.getChatMessageSource(); message+=">"; message+=irc.getChatMessage(); - boost::shared_ptr m(new ITRecieveMessage(message)); + std::shared_ptr m(new ITRecieveMessage(message)); sendToMainThread(m); irc.freeChatMessage(); } @@ -157,7 +140,7 @@ void IRCThread::operator()() message += " : "; message += irc.getInfoMessageText(); } - boost::shared_ptr m(new ITRecieveMessage(message)); + std::shared_ptr m(new ITRecieveMessage(message)); sendToMainThread(m); irc.freeInfoMessage(); } @@ -166,25 +149,3 @@ void IRCThread::operator()() -void IRCThread::sendMessage(boost::shared_ptr message) -{ - boost::recursive_mutex::scoped_lock lock(incomingMutex); - incoming.push(message); -} - - - -bool IRCThread::hasThreadExited() -{ - return hasExited; -} - - - -void IRCThread::sendToMainThread(boost::shared_ptr message) -{ - boost::recursive_mutex::scoped_lock lock(outgoingMutex); - outgoing.push(message); -} - - diff --git a/src/net/irc/IRCThread.h b/src/net/irc/IRCThread.h new file mode 100644 index 000000000..1ba2f6d64 --- /dev/null +++ b/src/net/irc/IRCThread.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include "IRC.h" +#include "ThreadMessageQueues.h" + +class IRCThreadMessage; + +///IRC thread manages IRC +class IRCThread : public ThreadMessageQueues +{ +public: + IRCThread(std::queue >& outgoing, std::recursive_mutex& outgoingMutex); + + ///Runs the IRC thread + void operator()(); + +private: + IRC irc; + std::string channel; +}; diff --git a/src/IRCThreadMessage.cpp b/src/net/irc/IRCThreadMessage.cpp similarity index 86% rename from src/IRCThreadMessage.cpp rename to src/net/irc/IRCThreadMessage.cpp index 24ceb9dbd..db563951f 100644 --- a/src/IRCThreadMessage.cpp +++ b/src/net/irc/IRCThreadMessage.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "IRCThreadMessage.h" #include diff --git a/src/IRCThreadMessage.h b/src/net/irc/IRCThreadMessage.h similarity index 84% rename from src/IRCThreadMessage.h rename to src/net/irc/IRCThreadMessage.h index 4b88b232e..47e188f84 100644 --- a/src/IRCThreadMessage.h +++ b/src/net/irc/IRCThreadMessage.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef IRCThreadMessage_h -#define IRCThreadMessage_h +#pragma once #include #include "SDL_net.h" @@ -251,7 +235,3 @@ class ITUserListModified : public IRCThreadMessage //event_append_marker - - - -#endif diff --git a/src/add_irc_thread_message.py b/src/net/irc/add_irc_thread_message.py similarity index 100% rename from src/add_irc_thread_message.py rename to src/net/irc/add_irc_thread_message.py diff --git a/src/net/message/AuthMessages.cpp b/src/net/message/AuthMessages.cpp new file mode 100644 index 000000000..4584ae33f --- /dev/null +++ b/src/net/message/AuthMessages.cpp @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "AuthMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetSendClientInformation::NetSendClientInformation() +{ + netVersion=NET_PROTOCOL_VERSION; +} + + + +Uint8 NetSendClientInformation::getMessageType() const +{ + return MNetSendClientInformation; +} + + + +void NetSendClientInformation::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendClientInformation"); + stream->writeUint16(netVersion, "netVersion "); + stream->writeLeaveSection(); +} + + + +void NetSendClientInformation::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendClientInformation"); + netVersion=stream->readUint16("netVersion"); + stream->readLeaveSection(); +} + + + +std::string NetSendClientInformation::format() const +{ + std::ostringstream s; + s<<"NetSendClientInformation(netVersion="<(rhs); + if(r.netVersion == netVersion) + { + return true; + } + } + return false; +} + + +Uint16 NetSendClientInformation::getNetVersion() const +{ + return netVersion; +} + + + +NetSendServerInformation::NetSendServerInformation(YOGLoginPolicy loginPolicy, YOGGamePolicy gamePolicy, Uint16 playerID) + : loginPolicy(loginPolicy), gamePolicy(gamePolicy), playerID(playerID) +{ + +} + + + +NetSendServerInformation::NetSendServerInformation() + : loginPolicy(YOGRequirePassword), gamePolicy(YOGSingleGame), playerID(0) +{ + +} + + + +Uint8 NetSendServerInformation::getMessageType() const +{ + return MNetSendServerInformation; +} + + + +void NetSendServerInformation::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendServerInformation"); + stream->writeUint8(loginPolicy, "loginPolicy "); + stream->writeUint8(gamePolicy, "gamePolicy "); + stream->writeUint16(playerID, "playerID "); + stream->writeLeaveSection(); +} + + +void NetSendServerInformation::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendServerInformation"); + loginPolicy=static_cast(stream->readUint8("loginPolicy")); + gamePolicy=static_cast(stream->readUint8("gamePolicy")); + playerID=stream->readUint16("playerID"); + stream->readLeaveSection(); +} + + + +std::string NetSendServerInformation::format() const +{ + std::ostringstream s; + s<<"NetSendServerInformation("; + if(loginPolicy == YOGRequirePassword) + s<<"loginPolicy=YOGRequirePassword; "; + else if(loginPolicy == YOGAnonymousLogin) + s<<"loginPolicy=YOGAnonymousLogin; "; + + if(gamePolicy == YOGSingleGame) + s<<"gamePolicy=YOGSingleGame; "; + else if(gamePolicy == YOGMultipleGames) + s<<"gamePolicy=YOGMultipleGames; "; + + s<<"playerID="<(rhs); + if(r.loginPolicy == loginPolicy && r.gamePolicy == gamePolicy) + { + return true; + } + } + return false; +} + + + +YOGLoginPolicy NetSendServerInformation::getLoginPolicy() const +{ + return loginPolicy; +} + + + +YOGGamePolicy NetSendServerInformation::getGamePolicy() const +{ + return gamePolicy; +} + + + +Uint16 NetSendServerInformation::getPlayerID() const +{ + return playerID; +} + + + +NetAttemptLogin::NetAttemptLogin(const std::string& username, const std::string& password) + : username(username), password(password) +{ + +} + + + +NetAttemptLogin::NetAttemptLogin() +{ + +} + + + +Uint8 NetAttemptLogin::getMessageType() const +{ + return MNetAttemptLogin; +} + + + +void NetAttemptLogin::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetAttemptLogin"); + stream->writeText(username, "username"); + stream->writeText(password, "password"); + stream->writeLeaveSection(); +} + + + +void NetAttemptLogin::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetAttemptLogin"); + username=stream->readText("username"); + password=stream->readText("password"); + stream->readLeaveSection(); +} + + + +std::string NetAttemptLogin::format() const +{ + std::ostringstream s; + s<<"NetAttemptLogin("<<"username=\""<(rhs); + if(r.username == username && r.password==password) + { + return true; + } + } + return false; +} + + + +const std::string& NetAttemptLogin::getUsername() const +{ + return username; +} + + + +const std::string& NetAttemptLogin::getPassword() const +{ + return password; +} + + + +NetLoginSuccessful::NetLoginSuccessful() +{ + +} + + + +Uint8 NetLoginSuccessful::getMessageType() const +{ + return MNetLoginSuccessful; +} + + + +void NetLoginSuccessful::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetLoginSuccessful"); + stream->writeLeaveSection(); +} + + + +void NetLoginSuccessful::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetAttemptLogin"); + stream->readLeaveSection(); + +} + + + +std::string NetLoginSuccessful::format() const +{ + std::ostringstream s; + s<<"NetLoginSuccessful()"; + return s.str(); +} + + + +bool NetLoginSuccessful::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetLoginSuccessful)) + { +// const NetLoginSuccessful& r = dynamic_cast(rhs); + return true; + } + return false; +} + + +NetRefuseLogin::NetRefuseLogin() + : reason(YOGLoginSuccessful) +{ + +} + + + +NetRefuseLogin::NetRefuseLogin(YOGLoginState reason) + : reason(reason) +{ + +} + + + +Uint8 NetRefuseLogin::getMessageType() const +{ + return MNetRefuseLogin; +} + + + +void NetRefuseLogin::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRefuseLogin"); + stream->writeUint8(reason, "reason"); + stream->writeLeaveSection(); +} + + + +void NetRefuseLogin::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRefuseLogin"); + reason=static_cast(stream->readUint8("reason")); + stream->readLeaveSection(); +} + + + +std::string NetRefuseLogin::format() const +{ + std::ostringstream s; + std::string sreason; + if(reason == YOGLoginSuccessful) + sreason="YOGLoginSuccessful"; + if(reason == YOGLoginUnknown) + sreason="YOGLoginUnknown"; + if(reason == YOGPasswordIncorrect) + sreason="YOGPasswordIncorrect"; + if(reason == YOGUsernameAlreadyUsed) + sreason="YOGUsernameAlreadyUsed"; + if(reason == YOGUserNotRegistered) + sreason="YOGUserNotRegistered"; + s<<"NetRefuseLogin(reason="<(rhs); + if(r.reason == reason) + { + return true; + } + } + return false; +} + + + +YOGLoginState NetRefuseLogin::getRefusalReason() const +{ + return reason; +} + + + +NetDisconnect::NetDisconnect() +{ + +} + + + +Uint8 NetDisconnect::getMessageType() const +{ + return MNetDisconnect; +} + + + +void NetDisconnect::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetDisconnect"); + stream->writeLeaveSection(); +} + + +void NetDisconnect::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetDisconnect"); + stream->readLeaveSection(); +} + + + +std::string NetDisconnect::format() const +{ + std::ostringstream s; + s<<"NetDisconnect()"; + return s.str(); +} + + + +bool NetDisconnect::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetDisconnect)) + { +// const NetDisconnect& r = dynamic_cast(rhs); + return true; + } + return false; +} diff --git a/src/net/message/AuthMessages.h b/src/net/message/AuthMessages.h new file mode 100644 index 000000000..9d8219f56 --- /dev/null +++ b/src/net/message/AuthMessages.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "YOGConsts.h" + +/// Sends local protocol-version information to the server, used during the +/// handshake before login. +class NetSendClientInformation : public NetMessage +{ +public: + NetSendClientInformation(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getNetVersion() const; +private: + Uint16 netVersion; +}; + + +/// Server -> client information sent on connect: login policy (anonymous / +/// password required), game policy, and the connection's playerID. +class NetSendServerInformation : public NetMessage +{ +public: + NetSendServerInformation(YOGLoginPolicy loginPolicy, YOGGamePolicy gamePolicy, Uint16 playerID); + NetSendServerInformation(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGLoginPolicy getLoginPolicy() const; + YOGGamePolicy getGamePolicy() const; + Uint16 getPlayerID() const; +private: + YOGLoginPolicy loginPolicy; + YOGGamePolicy gamePolicy; + Uint16 playerID; +}; + + +/// Client -> server login attempt with username and password. +class NetAttemptLogin : public NetMessage +{ +public: + NetAttemptLogin(const std::string& username, const std::string& password); + NetAttemptLogin(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + const std::string& getUsername() const; + const std::string& getPassword() const; +private: + std::string username; + std::string password; +}; + + +/// Tells the client that login succeeded. +class NetLoginSuccessful : public NetMessage +{ +public: + NetLoginSuccessful(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Tells the client that login was refused, carrying the reason. +class NetRefuseLogin : public NetMessage +{ +public: + NetRefuseLogin(); + NetRefuseLogin(YOGLoginState reason); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGLoginState getRefusalReason() const; +private: + YOGLoginState reason; +}; + + +/// Notifies the peer that this side is disconnecting. +class NetDisconnect : public NetMessage +{ +public: + NetDisconnect(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; diff --git a/src/net/message/FileTransferMessages.cpp b/src/net/message/FileTransferMessages.cpp new file mode 100644 index 000000000..c06c928c3 --- /dev/null +++ b/src/net/message/FileTransferMessages.cpp @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "FileTransferMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetRequestFile::NetRequestFile() + : fileID(0) +{ + +} + + + +NetRequestFile::NetRequestFile(Uint16 fileID) + : fileID(fileID) +{ + +} + + + +Uint8 NetRequestFile::getMessageType() const +{ + return MNetRequestFile; +} + + + +void NetRequestFile::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRequestFile"); + stream->writeUint16(fileID, "fileID"); + stream->writeLeaveSection(); +} + + + +void NetRequestFile::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRequestFile"); + fileID = stream->readUint16("fileID"); + stream->readLeaveSection(); +} + + + +std::string NetRequestFile::format() const +{ + std::ostringstream s; + s<<"NetRequestFile(fileID="<(rhs); + if(fileID == r.fileID) + return true; + } + return false; +} + + + +Uint16 NetRequestFile::getFileID() +{ + return fileID; +} + + + +NetSendFileInformation::NetSendFileInformation() + : size(0), fileID(0) +{ + +} + + +NetSendFileInformation::NetSendFileInformation(Uint32 filesize, Uint16 fileID) + : size(filesize), fileID(fileID) +{ +} + + + +Uint8 NetSendFileInformation::getMessageType() const +{ + return MNetSendFileInformation; +} + + + +void NetSendFileInformation::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendFileInformation"); + stream->writeUint32(size, "size"); + stream->writeUint16(fileID, "fileID"); + stream->writeLeaveSection(); +} + + + +void NetSendFileInformation::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendFileInformation"); + size = stream->readUint32("size"); + fileID = stream->readUint16("fileID"); + stream->readLeaveSection(); +} + + + +std::string NetSendFileInformation::format() const +{ + std::ostringstream s; + s<<"NetSendFileInformation(size="<(rhs); + if(r.size == size && r.fileID == fileID) + return true; + } + return false; +} + + + +Uint32 NetSendFileInformation::getFileSize() const +{ + return size; +} + + + +Uint16 NetSendFileInformation::getFileID() const +{ + return fileID; +} + + + +NetSendFileChunk::NetSendFileChunk() +{ + std::fill(data, data+4096, 0); + size=0; + fileID=0; +} + + + +NetSendFileChunk::NetSendFileChunk(std::shared_ptr stream, Uint16 fileID) + : fileID(fileID) +{ + size=0; + int pos=0; + while(!stream->isEndOfStream() && size < 4096) + { + stream->read(data+pos, 1, ""); + //For some reason the last byte is an overread, so it should be ignored + if(!stream->isEndOfStream()) + { + pos+=1; + size+=1; + } + } +} + + + +Uint8 NetSendFileChunk::getMessageType() const +{ + return MNetSendFileChunk; +} + + + +void NetSendFileChunk::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendFileChunk"); + stream->writeUint32(size, "size"); + stream->write(data, size, "data"); + stream->writeUint16(fileID, "fileID"); + stream->writeLeaveSection(); +} + + + +void NetSendFileChunk::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendFileChunk"); + size = stream->readUint32("size"); + stream->read(data, size, "data"); + fileID = stream->readUint16("fileID"); + stream->readLeaveSection(); +} + + + +std::string NetSendFileChunk::format() const +{ + std::ostringstream s; + s<<"NetSendFileChunk(size="<(rhs); + for(int i=0; i<4096; ++i) + { + if(data[i] != r.data[i]) + return false; + } + if(fileID != r.fileID) + return false; + return true; + } + return false; +} + + + +const Uint8* NetSendFileChunk::getBuffer() const +{ + return data; +} + + + +Uint32 NetSendFileChunk::getChunkSize() const +{ + return size; +} + + + +Uint16 NetSendFileChunk::getFileID() const +{ + return fileID; +} + + + +NetCancelSendingFile::NetCancelSendingFile() + : fileID(0) +{ + +} + + + +NetCancelSendingFile::NetCancelSendingFile(Uint16 fileID) + :fileID(fileID) +{ +} + + + +Uint8 NetCancelSendingFile::getMessageType() const +{ + return MNetCancelSendingFile; +} + + + +void NetCancelSendingFile::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetCancelSendingFile"); + stream->writeUint16(fileID, "fileID"); + stream->writeLeaveSection(); +} + + + +void NetCancelSendingFile::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetCancelSendingFile"); + fileID = stream->readUint16("fileID"); + stream->readLeaveSection(); +} + + + +std::string NetCancelSendingFile::format() const +{ + std::ostringstream s; + s<<"NetCancelSendingFile("<<"fileID="<(rhs); + if(r.fileID == fileID) + return true; + } + return false; +} + + +Uint16 NetCancelSendingFile::getFileID() const +{ + return fileID; +} + + + + +NetCancelRecievingFile::NetCancelRecievingFile() + : fileID(0) +{ + +} + + + +NetCancelRecievingFile::NetCancelRecievingFile(Uint16 fileID) + :fileID(fileID) +{ +} + + + +Uint8 NetCancelRecievingFile::getMessageType() const +{ + return MNetCancelRecievingFile; +} + + + +void NetCancelRecievingFile::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetCancelRecievingFile"); + stream->writeUint16(fileID, "fileID"); + stream->writeLeaveSection(); +} + + + +void NetCancelRecievingFile::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetCancelRecievingFile"); + fileID = stream->readUint16("fileID"); + stream->readLeaveSection(); +} + + + +std::string NetCancelRecievingFile::format() const +{ + std::ostringstream s; + s<<"NetCancelRecievingFile("<<"fileID="<(rhs); + if(r.fileID == fileID) + return true; + } + return false; +} + + +Uint16 NetCancelRecievingFile::getFileID() const +{ + return fileID; +} diff --git a/src/net/message/FileTransferMessages.h b/src/net/message/FileTransferMessages.h new file mode 100644 index 000000000..11d9039bd --- /dev/null +++ b/src/net/message/FileTransferMessages.h @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include +#include + +#include "NetMessage.h" +#include "NetMessageType.h" + +/// Client -> server: request a file (typically a map) by its fileID. +class NetRequestFile : public NetMessage +{ +public: + NetRequestFile(); + NetRequestFile(Uint16 fileID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getFileID(); +private: + Uint16 fileID; +}; + + +/// Server -> client: announces an upcoming file transfer (size + fileID). +class NetSendFileInformation : public NetMessage +{ +public: + NetSendFileInformation(); + NetSendFileInformation(Uint32 filesize, Uint16 fileID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint32 getFileSize() const; + Uint16 getFileID() const; +private: + Uint32 size; + Uint16 fileID; +}; + + +/// One chunk of a streamed file transfer. Each message holds up to 4096 bytes +/// drained from the supplied input stream. +class NetSendFileChunk : public NetMessage +{ +public: + NetSendFileChunk(); + + /// Reads from the stream until it ends or the chunk size is reached. + NetSendFileChunk(std::shared_ptr stream, Uint16 fileID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + const Uint8* getBuffer() const; + Uint32 getChunkSize() const; + Uint16 getFileID() const; +private: + Uint32 size; + Uint8 data[4096]; + Uint16 fileID; +}; + + +/// Sender -> receiver: aborts an in-flight outbound file transfer. +class NetCancelSendingFile : public NetMessage +{ +public: + NetCancelSendingFile(); + NetCancelSendingFile(Uint16 fileID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getFileID() const; +private: + Uint16 fileID; +}; + + +/// Receiver -> sender: aborts an in-flight inbound file transfer. +class NetCancelRecievingFile : public NetMessage +{ +public: + NetCancelRecievingFile(); + NetCancelRecievingFile(Uint16 fileID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getFileID() const; +private: + Uint16 fileID; +}; diff --git a/src/net/message/GameCreateMessages.cpp b/src/net/message/GameCreateMessages.cpp new file mode 100644 index 000000000..51832afaf --- /dev/null +++ b/src/net/message/GameCreateMessages.cpp @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "GameCreateMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetCreateGame::NetCreateGame() +{ + +} + + + +NetCreateGame::NetCreateGame(const std::string& gameName) + : gameName(gameName) +{ + +} + + + + +Uint8 NetCreateGame::getMessageType() const +{ + return MNetCreateGame; +} + + + +void NetCreateGame::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetCreateGame"); + stream->writeText(gameName, "gameName"); + stream->writeLeaveSection(); +} + + + +void NetCreateGame::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetCreateGame"); + gameName=stream->readText("gameName"); + stream->readLeaveSection(); +} + + + +std::string NetCreateGame::format() const +{ + std::ostringstream s; + s<<"NetCreateGame(gameName=\""<(rhs); + if(r.gameName == gameName) + return true; + } + return false; +} + + + +const std::string& NetCreateGame::getGameName() const +{ + return gameName; +} + + + +NetCreateGameAccepted::NetCreateGameAccepted() +{ + chatChannel = 0; + gameID = 0; + routerIP = ""; + fileID = 0; +} + + +NetCreateGameAccepted::NetCreateGameAccepted(Uint32 chatChannel, Uint16 gameID, const std::string& routerIP, Uint16 fileID) + : chatChannel(chatChannel), gameID(gameID), routerIP(routerIP), fileID(fileID) +{ + +} + + + +Uint8 NetCreateGameAccepted::getMessageType() const +{ + return MNetCreateGameAccepted; +} + + + +void NetCreateGameAccepted::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetCreateGameAccepted"); + stream->writeUint32(chatChannel, "chatChannel"); + stream->writeUint16(gameID, "gameID"); + stream->writeText(routerIP, "routerIP"); + stream->writeUint16(fileID, "fileID"); + stream->writeLeaveSection(); +} + + + +void NetCreateGameAccepted::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetCreateGameAccepted"); + chatChannel = stream->readUint32("chatChannel"); + gameID = stream->readUint16("gameID"); + routerIP = stream->readText("routerIP"); + fileID = stream->readUint16("fileID"); + stream->readLeaveSection(); +} + + + +std::string NetCreateGameAccepted::format() const +{ + std::ostringstream s; + s<<"NetCreateGameAccepted(chatChannel="<(rhs); + if(chatChannel != r.chatChannel || gameID != r.gameID || routerIP != r.routerIP || fileID != r.fileID) + { + return false; + } + return true; + } + return false; +} + + + +Uint32 NetCreateGameAccepted::getChatChannel() const +{ + return chatChannel; +} + + + +Uint16 NetCreateGameAccepted::getGameID() const +{ + return gameID; +} + + + +const std::string NetCreateGameAccepted::getGameRouterIP() const +{ + return routerIP; +} + + + +Uint16 NetCreateGameAccepted::getFileID() const +{ + return fileID; +} + + + +NetCreateGameRefused::NetCreateGameRefused() +{ + reason = YOGCreateRefusalUnknown; +} + + + +NetCreateGameRefused::NetCreateGameRefused(YOGServerGameCreateRefusalReason reason) + : reason(reason) +{ + +} + + + +Uint8 NetCreateGameRefused::getMessageType() const +{ + return MNetCreateGameRefused; +} + + + +void NetCreateGameRefused::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetCreateGameRefused"); + stream->writeUint8(reason, "reason"); + stream->writeLeaveSection(); +} + + + +void NetCreateGameRefused::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetCreateGameRefused"); + reason = static_cast(stream->readUint8("reason")); + stream->readLeaveSection(); +} + + + +std::string NetCreateGameRefused::format() const +{ + std::ostringstream s; + s<<"NetCreateGameRefused(reason="<(rhs); + if(reason == r.reason) + return true; + } + return false; +} + + +YOGServerGameCreateRefusalReason NetCreateGameRefused::getRefusalReason() const +{ + return reason; +} + + + +NetLeaveGame::NetLeaveGame() +{ + +} + + + +Uint8 NetLeaveGame::getMessageType() const +{ + return MNetLeaveGame; +} + + + +void NetLeaveGame::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetLeaveGame"); + stream->writeLeaveSection(); +} + + + +void NetLeaveGame::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetLeaveGame"); + stream->readLeaveSection(); +} + + + +std::string NetLeaveGame::format() const +{ + std::ostringstream s; + s<<"NetLeaveGame()"; + return s.str(); +} + + + +bool NetLeaveGame::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetLeaveGame)) + { + //const NetLeaveGame& r = dynamic_cast(rhs); + return true; + } + return false; +} diff --git a/src/net/message/GameCreateMessages.h b/src/net/message/GameCreateMessages.h new file mode 100644 index 000000000..655abccef --- /dev/null +++ b/src/net/message/GameCreateMessages.h @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "YOGConsts.h" + +/// Client -> server: create a new lobby game with the given display name. +class NetCreateGame : public NetMessage +{ +public: + NetCreateGame(const std::string& gameName); + NetCreateGame(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + const std::string& getGameName() const; +private: + std::string gameName; +}; + + +/// Server -> creator: game created successfully. Carries the new game's chat +/// channel id, game id, the address of the assigned game-router, and the +/// fileID for the map distribution. +class NetCreateGameAccepted : public NetMessage +{ +public: + NetCreateGameAccepted(); + NetCreateGameAccepted(Uint32 chatChannel, Uint16 gameID, const std::string& routerIP, Uint16 fileID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint32 getChatChannel() const; + Uint16 getGameID() const; + const std::string getGameRouterIP() const; + Uint16 getFileID() const; +private: + Uint32 chatChannel; + Uint16 gameID; + std::string routerIP; + Uint16 fileID; +}; + + +/// Server -> creator: game creation refused, carrying the reason. +class NetCreateGameRefused : public NetMessage +{ +public: + NetCreateGameRefused(); + NetCreateGameRefused(YOGServerGameCreateRefusalReason reason); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGServerGameCreateRefusalReason getRefusalReason() const; +private: + YOGServerGameCreateRefusalReason reason; +}; + + +/// Client -> server: leave the currently-joined game. +class NetLeaveGame : public NetMessage +{ +public: + NetLeaveGame(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; diff --git a/src/net/message/GameHeaderMessages.cpp b/src/net/message/GameHeaderMessages.cpp new file mode 100644 index 000000000..d6e6fe394 --- /dev/null +++ b/src/net/message/GameHeaderMessages.cpp @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "GameHeaderMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetSendMapHeader::NetSendMapHeader() +{ + +} + + + +NetSendMapHeader::NetSendMapHeader(const MapHeader& mapHeader) + : mapHeader(mapHeader) +{ + +} + + + +Uint8 NetSendMapHeader::getMessageType() const +{ + return MNetSendMapHeader; +} + + + +void NetSendMapHeader::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendMapHeader"); + mapHeader.save(stream); + stream->writeLeaveSection(); +} + + + +void NetSendMapHeader::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendMapHeader"); + mapHeader.load(stream); + stream->readLeaveSection(); +} + + + +std::string NetSendMapHeader::format() const +{ + std::ostringstream s; + s<<"NetSendMapHeader(mapname="+mapHeader.getMapName()+")"; + return s.str(); +} + + + +bool NetSendMapHeader::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetSendMapHeader)) + { + //const NetSendMapHeader& r = dynamic_cast(rhs); + return true; + } + return false; +} + + +const MapHeader& NetSendMapHeader::getMapHeader() const +{ + return mapHeader; +} + + + +NetSendGameHeader::NetSendGameHeader() +{ + +} + + +NetSendGameHeader::NetSendGameHeader(const GameHeader& gameHeader) + : gameHeader(gameHeader) +{ + +} + + + +Uint8 NetSendGameHeader::getMessageType() const +{ + return MNetSendGameHeader; +} + + + +void NetSendGameHeader::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendGameHeader"); + gameHeader.saveWithoutPlayerInfo(stream); + stream->writeLeaveSection(); +} + + + +void NetSendGameHeader::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendGameHeader"); + gameHeader.loadWithoutPlayerInfo(stream, VERSION_MINOR); + stream->readLeaveSection(); +} + + + +std::string NetSendGameHeader::format() const +{ + std::ostringstream s; + s<<"NetSendGameHeader()"; + return s.str(); +} + + + +bool NetSendGameHeader::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetSendGameHeader)) + { + //const NetSendGameHeader& r = dynamic_cast(rhs); +// if(gameHeader == r.gameHeader) + return true; + } + return false; +} + + + +void NetSendGameHeader::downloadToGameHeader(GameHeader& newGameHeader) +{ + //This is a special trick used to avoid having to manually copy over every + //variable + MemoryStreamBackend* obackend = new MemoryStreamBackend; + GAGCore::BinaryOutputStream* ostream = new BinaryOutputStream(obackend); + gameHeader.saveWithoutPlayerInfo(ostream); + + + obackend->seekFromStart(0); + MemoryStreamBackend* ibackend = new MemoryStreamBackend(*obackend); + GAGCore::BinaryInputStream* istream = new BinaryInputStream(ibackend); + newGameHeader.loadWithoutPlayerInfo(istream, VERSION_MINOR); + + delete ostream; + delete istream; +} + + + + +NetSendGamePlayerInfo::NetSendGamePlayerInfo() +{ + +} + + + + +NetSendGamePlayerInfo::NetSendGamePlayerInfo(GameHeader& gameHeader) + : gameHeader(gameHeader) +{ +} + + + +Uint8 NetSendGamePlayerInfo::getMessageType() const +{ + return MNetSendGamePlayerInfo; +} + + + +void NetSendGamePlayerInfo::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendGamePlayerInfo"); + gameHeader.savePlayerInfo(stream); + stream->writeLeaveSection(); +} + + + +void NetSendGamePlayerInfo::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendGamePlayerInfo"); + gameHeader.loadPlayerInfo(stream, VERSION_MINOR); + stream->readLeaveSection(); +} + + + +std::string NetSendGamePlayerInfo::format() const +{ + std::ostringstream s; + s<<"NetSendGamePlayerInfo()"; + return s.str(); +} + + + +bool NetSendGamePlayerInfo::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetSendGamePlayerInfo)) + { + //const NetSendGamePlayerInfo& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +void NetSendGamePlayerInfo::downloadToGameHeader(GameHeader& header) +{ + //This is a special trick used to avoid having to manually copy over every + //variable + MemoryStreamBackend* obackend = new MemoryStreamBackend; + GAGCore::BinaryOutputStream* ostream = new BinaryOutputStream(obackend); + gameHeader.savePlayerInfo(ostream); + + obackend->seekFromStart(0); + MemoryStreamBackend* ibackend = new MemoryStreamBackend(*obackend); + GAGCore::BinaryInputStream* istream = new BinaryInputStream(ibackend); + header.loadPlayerInfo(istream, VERSION_MINOR); + + delete ostream; + delete istream; +} diff --git a/src/net/message/GameHeaderMessages.h b/src/net/message/GameHeaderMessages.h new file mode 100644 index 000000000..99a9de13c --- /dev/null +++ b/src/net/message/GameHeaderMessages.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "GameHeader.h" +#include "MapHeader.h" + +/// Sends the map header (terrain dimensions, victory conditions, etc.) to the +/// server when creating or joining a game. +class NetSendMapHeader : public NetMessage +{ +public: + NetSendMapHeader(); + NetSendMapHeader(const MapHeader& mapHeader); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + const MapHeader& getMapHeader() const; +private: + MapHeader mapHeader; +}; + + +/// Sends the game header WITHOUT player info — player data is sent separately +/// via NetSendGamePlayerInfo so each piece can be updated independently. +class NetSendGameHeader : public NetMessage +{ +public: + NetSendGameHeader(); + NetSendGameHeader(const GameHeader& gameHeader); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + void downloadToGameHeader(GameHeader& header); +private: + GameHeader gameHeader; +}; + + +/// Sends the BasePlayer portion of GameHeader (the player slots and their +/// settings). Companion to NetSendGameHeader. +class NetSendGamePlayerInfo : public NetMessage +{ +public: + NetSendGamePlayerInfo(); + NetSendGamePlayerInfo(GameHeader& header); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + void downloadToGameHeader(GameHeader& header); +private: + GameHeader gameHeader; +}; diff --git a/src/net/message/GameJoinMessages.cpp b/src/net/message/GameJoinMessages.cpp new file mode 100644 index 000000000..db135fe45 --- /dev/null +++ b/src/net/message/GameJoinMessages.cpp @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "GameJoinMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetAttemptJoinGame::NetAttemptJoinGame() +{ + gameID = 0; +} + + + +NetAttemptJoinGame::NetAttemptJoinGame(Uint16 gameID) + : gameID(gameID) +{ + +} + + + +Uint8 NetAttemptJoinGame::getMessageType() const +{ + return MNetAttemptJoinGame; +} + + + +void NetAttemptJoinGame::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetAttemptJoinGame"); + stream->writeUint16(gameID, "gameID"); + stream->writeLeaveSection(); +} + + + +void NetAttemptJoinGame::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetAttemptJoinGame"); + gameID=stream->readUint16("gameID"); + stream->readLeaveSection(); +} + + + +std::string NetAttemptJoinGame::format() const +{ + std::ostringstream s; + s<<"NetAttemptJoinGame(gameID="<(rhs); + if(r.gameID == gameID) + return true; + } + return false; +} + + + +Uint16 NetAttemptJoinGame::getGameID() const +{ + return gameID; +} + + + +NetGameJoinAccepted::NetGameJoinAccepted() +{ + chatChannel = 0; +} + + + +NetGameJoinAccepted::NetGameJoinAccepted(Uint32 chatChannel) + : chatChannel(chatChannel) +{ + +} + + + +Uint8 NetGameJoinAccepted::getMessageType() const +{ + return MNetGameJoinAccepted; +} + + + +void NetGameJoinAccepted::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetGameJoinAccepted"); + stream->writeUint32(chatChannel, "chatChannel"); + stream->writeLeaveSection(); +} + + + +void NetGameJoinAccepted::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetGameJoinAccepted"); + chatChannel = stream->readUint32("chatChannel"); + stream->readLeaveSection(); +} + + + +std::string NetGameJoinAccepted::format() const +{ + std::ostringstream s; + s<<"NetGameJoinAccepted(chatChannel="<(rhs); + if(r.chatChannel != chatChannel) + { + return false; + } + return true; + } + return false; +} + + + +Uint32 NetGameJoinAccepted::getChatChannel() const +{ + return chatChannel; +} + + + +NetGameJoinRefused::NetGameJoinRefused() +{ + reason = YOGJoinRefusalUnknown; +} + + + +NetGameJoinRefused::NetGameJoinRefused(YOGServerGameJoinRefusalReason reason) + : reason(reason) +{ + +} + + + +Uint8 NetGameJoinRefused::getMessageType() const +{ + return MNetGameJoinRefused; +} + + + +void NetGameJoinRefused::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetGameJoinRefused"); + stream->writeUint8(reason, "reason"); + stream->writeLeaveSection(); +} + + + +void NetGameJoinRefused::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetGameJoinRefused"); + reason=static_cast(stream->readUint8("reason")); + stream->readLeaveSection(); +} + + + +std::string NetGameJoinRefused::format() const +{ + std::ostringstream s; + std::string sreason; + if(reason == YOGJoinRefusalUnknown) + sreason="YOGJoinRefusalUnknown"; + s<<"NetGameJoinRefused(reason="<(rhs); + if(r.reason == reason) + return true; + } + return false; +} + + + + +YOGServerGameJoinRefusalReason NetGameJoinRefused::getRefusalReason() const +{ + return reason; +} + + + +NetPlayerJoinsGame::NetPlayerJoinsGame() + : playerID(0), playerName("") +{ + +} + + + +NetPlayerJoinsGame::NetPlayerJoinsGame(Uint16 playerID, std::string playerName) + :playerID(playerID), playerName(playerName) +{ +} + + + +Uint8 NetPlayerJoinsGame::getMessageType() const +{ + return MNetPlayerJoinsGame; +} + + + +void NetPlayerJoinsGame::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetPlayerJoinsGame"); + stream->writeUint16(playerID, "playerID"); + stream->writeText(playerName, "playerName"); + stream->writeLeaveSection(); +} + + + +void NetPlayerJoinsGame::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetPlayerJoinsGame"); + playerID = stream->readUint16("playerID"); + playerName = stream->readText("playerName"); + stream->readLeaveSection(); +} + + + +std::string NetPlayerJoinsGame::format() const +{ + std::ostringstream s; + s<<"NetPlayerJoinsGame("<<"playerID="<(rhs); + if(r.playerID == playerID && r.playerName == playerName) + return true; + } + return false; +} + + +Uint16 NetPlayerJoinsGame::getPlayerID() const +{ + return playerID; +} + + + +std::string NetPlayerJoinsGame::getPlayerName() const +{ + return playerName; +} + + + +NetSendAfterJoinGameInformation::NetSendAfterJoinGameInformation() + : info() +{ + +} + + + +NetSendAfterJoinGameInformation::NetSendAfterJoinGameInformation(YOGAfterJoinGameInformation info) + :info(info) +{ +} + + + +Uint8 NetSendAfterJoinGameInformation::getMessageType() const +{ + return MNetSendAfterJoinGameInformation; +} + + + +void NetSendAfterJoinGameInformation::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendAfterJoinGameInformation"); + info.encodeData(stream); + stream->writeLeaveSection(); +} + + + +void NetSendAfterJoinGameInformation::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendAfterJoinGameInformation"); + info.decodeData(stream); + stream->readLeaveSection(); +} + + + +std::string NetSendAfterJoinGameInformation::format() const +{ + std::ostringstream s; + s<<"NetSendAfterJoinGameInformation("<<"="<<"; "<<")"; + return s.str(); +} + + + +bool NetSendAfterJoinGameInformation::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetSendAfterJoinGameInformation)) + { + const NetSendAfterJoinGameInformation& r = dynamic_cast(rhs); + if(r.info == info) + return true; + } + return false; +} + + +YOGAfterJoinGameInformation NetSendAfterJoinGameInformation::getAfterJoinGameInformation() const +{ + return info; +} diff --git a/src/net/message/GameJoinMessages.h b/src/net/message/GameJoinMessages.h new file mode 100644 index 000000000..9f0a6c061 --- /dev/null +++ b/src/net/message/GameJoinMessages.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "YOGAfterJoinGameInformation.h" +#include "YOGConsts.h" + +/// Client -> server: attempt to join a game by its game ID. Future versions +/// may add password-protected games, so a join attempt is not always granted. +class NetAttemptJoinGame : public NetMessage +{ +public: + NetAttemptJoinGame(); + NetAttemptJoinGame(Uint16 gameID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getGameID() const; +private: + Uint16 gameID; +}; + + +/// Server -> client: join request accepted; the player is now in the game. +/// Carries the chat channel for the joined game. +class NetGameJoinAccepted : public NetMessage +{ +public: + NetGameJoinAccepted(); + NetGameJoinAccepted(Uint32 chatChannel); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint32 getChatChannel() const; +private: + Uint32 chatChannel; +}; + + +/// Server -> client: join attempt refused, carrying the reason. +class NetGameJoinRefused : public NetMessage +{ +public: + NetGameJoinRefused(YOGServerGameJoinRefusalReason reason); + NetGameJoinRefused(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGServerGameJoinRefusalReason getRefusalReason() const; +private: + YOGServerGameJoinRefusalReason reason; +}; + + +/// Server -> game members: a new player just joined the game. +class NetPlayerJoinsGame : public NetMessage +{ +public: + NetPlayerJoinsGame(); + NetPlayerJoinsGame(Uint16 playerID, std::string playerName); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getPlayerID() const; + std::string getPlayerName() const; +private: + Uint16 playerID; + std::string playerName; +}; + + +/// Server -> joiner: post-join handoff information (game data not part of +/// the initial accept). +class NetSendAfterJoinGameInformation : public NetMessage +{ +public: + NetSendAfterJoinGameInformation(); + NetSendAfterJoinGameInformation(YOGAfterJoinGameInformation info); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGAfterJoinGameInformation getAfterJoinGameInformation() const; +private: + YOGAfterJoinGameInformation info; +}; diff --git a/src/net/message/GameLaunchMessages.cpp b/src/net/message/GameLaunchMessages.cpp new file mode 100644 index 000000000..a5f9938dd --- /dev/null +++ b/src/net/message/GameLaunchMessages.cpp @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "GameLaunchMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetStartGame::NetStartGame() +{ + +} + + + +Uint8 NetStartGame::getMessageType() const +{ + return MNetStartGame; +} + + + +void NetStartGame::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetStartGame"); + stream->writeLeaveSection(); +} + + + +void NetStartGame::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetStartGame"); + stream->readLeaveSection(); +} + + + +std::string NetStartGame::format() const +{ + std::ostringstream s; + s<<"NetStartGame()"; + return s.str(); +} + + + +bool NetStartGame::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetStartGame)) + { + //const NetStartGame& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetKickPlayer::NetKickPlayer() + : playerID(0), reason(YOGUnknownKickReason) +{ +} + + + +NetKickPlayer::NetKickPlayer(Uint16 playerID, YOGKickReason reason) + : playerID(playerID), reason(reason) +{ +} + + + +Uint8 NetKickPlayer::getMessageType() const +{ + return MNetKickPlayer; +} + + + +void NetKickPlayer::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetKickPlayer"); + stream->writeUint16(playerID, "playerID"); + stream->writeUint8(reason, "reason"); + stream->writeLeaveSection(); +} + + + +void NetKickPlayer::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetKickPlayer"); + playerID = stream->readUint16("playerID"); + reason = static_cast(stream->readUint8("reason")); + stream->readLeaveSection(); +} + + + +std::string NetKickPlayer::format() const +{ + std::ostringstream s; + s<<"NetKickPlayer(playerID="<(rhs); + if(r.playerID == playerID && r.reason == reason) + return true; + } + return false; +} + + + +Uint16 NetKickPlayer::getPlayerID() +{ + return playerID; +} + + + +YOGKickReason NetKickPlayer::getReason() +{ + return reason; +} + + + +NetReadyToLaunch::NetReadyToLaunch() + : playerID(0) +{ + +} + + + +NetReadyToLaunch::NetReadyToLaunch(Uint16 playerID) + : playerID(playerID) +{ +} + + + +Uint8 NetReadyToLaunch::getMessageType() const +{ + return MNetReadyToLaunch; +} + + + +void NetReadyToLaunch::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetReadyToLaunch"); + stream->writeUint16(playerID, "playerID"); + stream->writeLeaveSection(); +} + + + +void NetReadyToLaunch::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetReadyToLaunch"); + playerID = stream->readUint16("playerID"); + stream->readLeaveSection(); +} + + + +std::string NetReadyToLaunch::format() const +{ + std::ostringstream s; + s<<"NetReadyToLaunch("<<"playerID="<(rhs); + if(r.playerID == playerID) + return true; + } + return false; +} + + +Uint16 NetReadyToLaunch::getPlayerID() const +{ + return playerID; +} + + + + +NetNotReadyToLaunch::NetNotReadyToLaunch() + : playerID(0) +{ + +} + + + +NetNotReadyToLaunch::NetNotReadyToLaunch(Uint16 playerID) + :playerID(playerID) +{ +} + + + +Uint8 NetNotReadyToLaunch::getMessageType() const +{ + return MNetNotReadyToLaunch; +} + + + +void NetNotReadyToLaunch::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetNotReadyToLaunch"); + stream->writeUint16(playerID, "playerID"); + stream->writeLeaveSection(); +} + + + +void NetNotReadyToLaunch::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetNotReadyToLaunch"); + playerID = stream->readUint16("playerID"); + stream->readLeaveSection(); +} + + + +std::string NetNotReadyToLaunch::format() const +{ + std::ostringstream s; + s<<"NetNotReadyToLaunch("<<"playerID="<(rhs); + if(r.playerID == playerID) + return true; + } + return false; +} + + +Uint16 NetNotReadyToLaunch::getPlayerID() const +{ + return playerID; +} + + + +NetRequestGameStart::NetRequestGameStart() +{ + +} + + + +Uint8 NetRequestGameStart::getMessageType() const +{ + return MNetRequestGameStart; +} + + + +void NetRequestGameStart::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRequestGameStart"); + stream->writeLeaveSection(); +} + + + +void NetRequestGameStart::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRequestGameStart"); + stream->readLeaveSection(); +} + + + +std::string NetRequestGameStart::format() const +{ + std::ostringstream s; + s<<"NetRequestGameStart()"; + return s.str(); +} + + + +bool NetRequestGameStart::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetRequestGameStart)) + { + //const NetRequestGameStart& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetRefuseGameStart::NetRefuseGameStart() + : refusalReason(YOGUnknownStartRefusalReason) +{ + +} + + + +NetRefuseGameStart::NetRefuseGameStart(YOGServerGameStartRefusalReason refusalReason) + :refusalReason(refusalReason) +{ +} + + + +Uint8 NetRefuseGameStart::getMessageType() const +{ + return MNetRefuseGameStart; +} + + + +void NetRefuseGameStart::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRefuseGameStart"); + stream->writeUint8(refusalReason, "refusalReason"); + stream->writeLeaveSection(); +} + + + +void NetRefuseGameStart::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRefuseGameStart"); + refusalReason = static_cast(stream->readUint8("refusalReason")); + stream->readLeaveSection(); +} + + + +std::string NetRefuseGameStart::format() const +{ + std::ostringstream s; + s<<"NetRefuseGameStart("<<"refusalReason="<(rhs); + if(r.refusalReason == refusalReason) + return true; + } + return false; +} + + +YOGServerGameStartRefusalReason NetRefuseGameStart::getRefusalReason() const +{ + return refusalReason; +} diff --git a/src/net/message/GameLaunchMessages.h b/src/net/message/GameLaunchMessages.h new file mode 100644 index 000000000..66f186c7a --- /dev/null +++ b/src/net/message/GameLaunchMessages.h @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "YOGConsts.h" + +/// Server -> game members: the game has started. +class NetStartGame : public NetMessage +{ +public: + NetStartGame(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Host -> server -> target: kick a player from the game with a reason. +class NetKickPlayer : public NetMessage +{ +public: + NetKickPlayer(); + NetKickPlayer(Uint16 playerID, YOGKickReason reason); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getPlayerID(); + YOGKickReason getReason(); +private: + Uint16 playerID; + YOGKickReason reason; +}; + + +/// Player -> host: this player's launch checklist has cleared. The host waits +/// for all players to be ready before issuing a NetStartGame. +class NetReadyToLaunch : public NetMessage +{ +public: + NetReadyToLaunch(); + NetReadyToLaunch(Uint16 playerID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getPlayerID() const; +private: + Uint16 playerID; +}; + + +/// Player -> host: previous "ready" was retracted (e.g. host changed map). +class NetNotReadyToLaunch : public NetMessage +{ +public: + NetNotReadyToLaunch(); + NetNotReadyToLaunch(Uint16 playerID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getPlayerID() const; +private: + Uint16 playerID; +}; + + +/// Host -> server: ask the server to start the game. +class NetRequestGameStart : public NetMessage +{ +public: + NetRequestGameStart(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Server -> host: refused to start the game (e.g. not all players ready). +class NetRefuseGameStart : public NetMessage +{ +public: + NetRefuseGameStart(); + NetRefuseGameStart(YOGServerGameStartRefusalReason refusalReason); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGServerGameStartRefusalReason getRefusalReason() const; +private: + YOGServerGameStartRefusalReason refusalReason; +}; diff --git a/src/net/message/GameTeamMessages.cpp b/src/net/message/GameTeamMessages.cpp new file mode 100644 index 000000000..8c6a96859 --- /dev/null +++ b/src/net/message/GameTeamMessages.cpp @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "GameTeamMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetRemoveAI::NetRemoveAI() + : playerNum(0) +{ + +} + + + +NetRemoveAI::NetRemoveAI(Uint8 playerNum) + :playerNum(playerNum) +{ +} + + + +Uint8 NetRemoveAI::getMessageType() const +{ + return MNetRemoveAI; +} + + + +void NetRemoveAI::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRemoveAI"); + stream->writeUint8(playerNum, "playerNum"); + stream->writeLeaveSection(); +} + + + +void NetRemoveAI::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRemoveAI"); + playerNum = stream->readUint8("playerNum"); + stream->readLeaveSection(); +} + + + +std::string NetRemoveAI::format() const +{ + std::ostringstream s; + s<<"NetRemoveAI("<<"playerNum="<(rhs); + if(r.playerNum == playerNum) + return true; + } + return false; +} + + +Uint8 NetRemoveAI::getPlayerNumber() const +{ + return playerNum; +} + + + + +NetChangePlayersTeam::NetChangePlayersTeam() + : player(0), team(0) +{ + +} + + + +NetChangePlayersTeam::NetChangePlayersTeam(Uint8 player, Uint8 team) + :player(player), team(team) +{ +} + + + +Uint8 NetChangePlayersTeam::getMessageType() const +{ + return MNetChangePlayersTeam; +} + + + +void NetChangePlayersTeam::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetChangePlayersTeam"); + stream->writeUint8(player, "player"); + stream->writeUint8(team, "team"); + stream->writeLeaveSection(); +} + + + +void NetChangePlayersTeam::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetChangePlayersTeam"); + player = stream->readUint8("player"); + team = stream->readUint8("team"); + stream->readLeaveSection(); +} + + + +std::string NetChangePlayersTeam::format() const +{ + std::ostringstream s; + s<<"NetChangePlayersTeam("<<"player="<(rhs); + if(r.player == player && r.team == team) + return true; + } + return false; +} + + +Uint8 NetChangePlayersTeam::getPlayer() const +{ + return player; +} + + + +Uint8 NetChangePlayersTeam::getTeam() const +{ + return team; +} + + + +NetAddAI::NetAddAI() + : type(0) +{ + +} + + + +NetAddAI::NetAddAI(Uint8 type) + :type(type) +{ +} + + + +Uint8 NetAddAI::getMessageType() const +{ + return MNetAddAI; +} + + + +void NetAddAI::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetAddAI"); + stream->writeUint8(type, "type"); + stream->writeLeaveSection(); +} + + + +void NetAddAI::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetAddAI"); + type = stream->readUint8("type"); + stream->readLeaveSection(); +} + + + +std::string NetAddAI::format() const +{ + std::ostringstream s; + s<<"NetAddAI("<<"type="<(rhs); + if(r.type == type) + return true; + } + return false; +} + + +Uint8 NetAddAI::getType() const +{ + return type; +} + + + + +NetSendReteamingInformation::NetSendReteamingInformation() +{ + +} + + + +NetSendReteamingInformation::NetSendReteamingInformation(NetReteamingInformation reteamingInfo) + :reteamingInfo(reteamingInfo) +{ +} + + + +Uint8 NetSendReteamingInformation::getMessageType() const +{ + return MNetSendReteamingInformation; +} + + + +void NetSendReteamingInformation::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendReteamingInformation"); + reteamingInfo.encodeData(stream); + stream->writeLeaveSection(); +} + + + +void NetSendReteamingInformation::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendReteamingInformation"); + reteamingInfo.decodeData(stream); + stream->readLeaveSection(); +} + + + +std::string NetSendReteamingInformation::format() const +{ + std::ostringstream s; + s<<"NetSendReteamingInformation()"; + return s.str(); +} + + + +bool NetSendReteamingInformation::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetSendReteamingInformation)) + { + const NetSendReteamingInformation& r = dynamic_cast(rhs); + if(r.reteamingInfo == reteamingInfo) + return true; + } + return false; +} + + +NetReteamingInformation NetSendReteamingInformation::getReteamingInfo() const +{ + return reteamingInfo; +} + + + + +NetSendGameResult::NetSendGameResult() + : result(YOGGameResultUnknown) +{ + +} + + + +NetSendGameResult::NetSendGameResult(YOGGameResult result) + :result(result) +{ +} + + + +Uint8 NetSendGameResult::getMessageType() const +{ + return MNetSendGameResult; +} + + + +void NetSendGameResult::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendGameResult"); + stream->writeUint8(static_cast(result), "result"); + stream->writeLeaveSection(); +} + + + +void NetSendGameResult::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendGameResult"); + result = static_cast(stream->readUint8("result")); + stream->readLeaveSection(); +} + + + +std::string NetSendGameResult::format() const +{ + std::ostringstream s; + s<<"NetSendGameResult("<<"result="<(rhs); + if(r.result == result) + return true; + } + return false; +} + + +YOGGameResult NetSendGameResult::getGameResult() const +{ + return result; +} diff --git a/src/net/message/GameTeamMessages.h b/src/net/message/GameTeamMessages.h new file mode 100644 index 000000000..b667d85e1 --- /dev/null +++ b/src/net/message/GameTeamMessages.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "NetReteamingInformation.h" +#include "YOGConsts.h" + +/// Host -> server -> all: add an AI player of the given type to the game. +class NetAddAI : public NetMessage +{ +public: + NetAddAI(); + NetAddAI(Uint8 type); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint8 getType() const; +private: + Uint8 type; +}; + + +/// Host -> server -> all: remove the AI in the given player slot. +class NetRemoveAI : public NetMessage +{ +public: + NetRemoveAI(); + NetRemoveAI(Uint8 playerNum); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint8 getPlayerNumber() const; +private: + Uint8 playerNum; +}; + + +/// Reassigns a player to a different team. +class NetChangePlayersTeam : public NetMessage +{ +public: + NetChangePlayersTeam(); + NetChangePlayersTeam(Uint8 player, Uint8 team); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint8 getPlayer() const; + Uint8 getTeam() const; +private: + Uint8 player; + Uint8 team; +}; + + +/// Carries reteaming information used to balance teams in a rematched game. +class NetSendReteamingInformation : public NetMessage +{ +public: + NetSendReteamingInformation(); + NetSendReteamingInformation(NetReteamingInformation reteamingInfo); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + NetReteamingInformation getReteamingInfo() const; +private: + NetReteamingInformation reteamingInfo; +}; + + +/// Reports the result of a finished game (used by ratings/stats tracking). +class NetSendGameResult : public NetMessage +{ +public: + NetSendGameResult(); + NetSendGameResult(YOGGameResult result); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGGameResult getGameResult() const; +private: + YOGGameResult result; +}; diff --git a/src/net/message/LobbyMessages.cpp b/src/net/message/LobbyMessages.cpp new file mode 100644 index 000000000..338e8c998 --- /dev/null +++ b/src/net/message/LobbyMessages.cpp @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "LobbyMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetUpdateGameList::NetUpdateGameList() +{ + +} + + + +Uint8 NetUpdateGameList::getMessageType() const +{ + return MNetUpdateGameList; +} + + + +void NetUpdateGameList::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetUpdateGameList"); + stream->writeEnterSection("removedGames"); + stream->writeUint8(removedGames.size(), "size"); + for(Uint16 i=0; iwriteUint16(removedGames[i], "removedGames[i]"); + } + stream->writeLeaveSection(); + + stream->writeEnterSection("updatedGames"); + stream->writeUint8(updatedGames.size(), "size"); + for(Uint16 i=0; iwriteLeaveSection(); + + stream->writeLeaveSection(); +} + + + +void NetUpdateGameList::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetUpdateGameList"); + + stream->readEnterSection("removedGames"); + Uint8 size = stream->readUint8("size"); + removedGames.resize(size); + for(Uint16 i=0; ireadUint16("removedGames[i]"); + } + stream->readLeaveSection(); + + stream->readEnterSection("updatedGames"); + size = stream->readUint8("size"); + updatedGames.resize(size); + for(Uint16 i=0; ireadLeaveSection(); + + stream->readLeaveSection(); +} + + + +std::string NetUpdateGameList::format() const +{ + std::ostringstream s; + s<<"NetUpdateGameList(removedGames "<(rhs); + if(r.removedGames == removedGames && r.updatedGames == updatedGames) + { + return true; + } + } + return false; +} + + + +NetUpdatePlayerList::NetUpdatePlayerList() +{ + +} + + + +Uint8 NetUpdatePlayerList::getMessageType() const +{ + return MNetUpdatePlayerList; +} + + + +void NetUpdatePlayerList::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetUpdatePlayerList"); + stream->writeEnterSection("removedPlayers"); + stream->writeUint8(removedPlayers.size(), "size"); + for(Uint16 i=0; iwriteUint16(removedPlayers[i], "removedPlayers[i]"); + } + stream->writeLeaveSection(); + + stream->writeEnterSection("updatedPlayers"); + stream->writeUint8(updatedPlayers.size(), "size"); + for(Uint16 i=0; iwriteLeaveSection(); + + stream->writeLeaveSection(); +} + + + +void NetUpdatePlayerList::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetUpdatePlayerList"); + + stream->readEnterSection("removedPlayers"); + Uint8 size = stream->readUint8("size"); + removedPlayers.resize(size); + for(Uint16 i=0; ireadUint16("removedPlayers[i]"); + } + stream->readLeaveSection(); + + stream->readEnterSection("updatedPlayers"); + size = stream->readUint8("size"); + updatedPlayers.resize(size); + for(Uint16 i=0; ireadLeaveSection(); + + stream->readLeaveSection(); +} + + + +std::string NetUpdatePlayerList::format() const +{ + std::ostringstream s; + s<<"NetUpdatePlayerList(updatedPlayers "<(rhs); + if(updatedPlayers == r.updatedPlayers && removedPlayers == r.removedPlayers) + return true; + } + return false; +} + + + +NetSendYOGMessage::NetSendYOGMessage(Uint32 channel, std::shared_ptr message) + : channel(channel), message(message) +{ + +} + + + +NetSendYOGMessage::NetSendYOGMessage() + : channel(0) +{ + +} + + + +Uint8 NetSendYOGMessage::getMessageType() const +{ + return MNetSendYOGMessage; +} + + + +void NetSendYOGMessage::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendYOGMessage"); + stream->writeUint32(channel, "channel"); + message->encodeData(stream); + stream->writeLeaveSection(); +} + + + +void NetSendYOGMessage::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendYOGMessage"); + channel = stream->readUint32("channel"); + message.reset(new YOGMessage); + message->decodeData(stream); + stream->readLeaveSection(); +} + + + +std::string NetSendYOGMessage::format() const +{ + std::ostringstream s; + s<<"NetSendYOGMessage(channel="<(rhs); + if(channel != r.channel) + return false; + else if(!message && !r.message) + return true; + else if(!message && r.message) + return false; + else if(message && !r.message) + return false; + if((*message) == (*r.message)) + return true; + } + return false; +} + + + +Uint32 NetSendYOGMessage::getChannel() const +{ + return channel; +} + + + +std::shared_ptr NetSendYOGMessage::getMessage() const +{ + return message; +} + + + +NetPlayerIsBanned::NetPlayerIsBanned() +{ + +} + + + +Uint8 NetPlayerIsBanned::getMessageType() const +{ + return MNetPlayerIsBanned; +} + + + +void NetPlayerIsBanned::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetPlayerIsBanned"); + stream->writeLeaveSection(); +} + + + +void NetPlayerIsBanned::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetPlayerIsBanned"); + stream->readLeaveSection(); +} + + + +std::string NetPlayerIsBanned::format() const +{ + std::ostringstream s; + s<<"NetPlayerIsBanned()"; + return s.str(); +} + + + +bool NetPlayerIsBanned::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetPlayerIsBanned)) + { + //const NetPlayerIsBanned& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetIPIsBanned::NetIPIsBanned() +{ + +} + + + +Uint8 NetIPIsBanned::getMessageType() const +{ + return MNetIPIsBanned; +} + + + +void NetIPIsBanned::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetIPIsBanned"); + stream->writeLeaveSection(); +} + + + +void NetIPIsBanned::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetIPIsBanned"); + stream->readLeaveSection(); +} + + + +std::string NetIPIsBanned::format() const +{ + std::ostringstream s; + s<<"NetIPIsBanned()"; + return s.str(); +} + + + +bool NetIPIsBanned::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetIPIsBanned)) + { + //const NetIPIsBanned& r = dynamic_cast(rhs); + return true; + } + return false; +} diff --git a/src/net/message/LobbyMessages.h b/src/net/message/LobbyMessages.h new file mode 100644 index 000000000..303c46adc --- /dev/null +++ b/src/net/message/LobbyMessages.h @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "YOGGameInfo.h" +#include "YOGMessage.h" +#include "YOGPlayerSessionInfo.h" + +/// Updates the user's pre-joining game list. Sends only the differences between +/// the user's known list and the server's current list (additions, removals, +/// changed entries) and lets the receiver reassemble the full list. Both sides +/// must hold matching state for this to work. +class NetUpdateGameList : public NetMessage +{ +public: + NetUpdateGameList(); + + /// Computes and stores the differences between two YOGGameInfo containers. + /// Container needs ::const_iterator, begin(), end(); typical std containers fit. + template void updateDifferences(const container& original, const container& updated); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + /// Applies the recorded differences to the given container. + /// Container needs erase(iter), begin(), end(), insert(iter, object). + template void applyDifferences(container& original) const; +private: + std::vector removedGames; + std::vector updatedGames; +}; + + +/// Same diff-update mechanism as NetUpdateGameList, but for the connected +/// players list (YOGPlayerSessionInfo). +class NetUpdatePlayerList : public NetMessage +{ +public: + NetUpdatePlayerList(); + + template void updateDifferences(const container& original, const container& updated); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + template void applyDifferences(container& original) const; +private: + std::vector removedPlayers; + std::vector updatedPlayers; +}; + + +/// Carries a chat message for a YOG channel (lobby chat, in-game chat, etc.). +class NetSendYOGMessage : public NetMessage +{ +public: + NetSendYOGMessage(Uint32 channel, std::shared_ptr message); + NetSendYOGMessage(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint32 getChannel() const; + std::shared_ptr getMessage() const; +private: + Uint32 channel; + std::shared_ptr message; +}; + + +/// Tells the client that their username has been banned by an administrator. +class NetPlayerIsBanned : public NetMessage +{ +public: + NetPlayerIsBanned(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Tells the client that their IP address has been banned by an administrator. +class NetIPIsBanned : public NetMessage +{ +public: + NetIPIsBanned(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +// --------------------------------------------------------------------------- +// Template definitions +// --------------------------------------------------------------------------- + +template void NetUpdateGameList::updateDifferences(const container& original, const container& updated) +{ + removedGames.clear(); + updatedGames.clear(); + // Find all removed games + for(typename container::const_iterator i = original.begin(); i!=original.end(); ++i) + { + bool found=false; + for(typename container::const_iterator j = updated.begin(); j!=updated.end(); ++j) + { + if(i->getGameID() == j->getGameID()) + { + found=true; + break; + } + } + if(!found) + { + removedGames.push_back(i->getGameID()); + } + } + // Find changed games + for(typename container::const_iterator i = original.begin(); i!=original.end(); ++i) + { + for(typename container::const_iterator j = updated.begin(); j!=updated.end(); ++j) + { + // Same ID but a different property — game has changed and needs to be sent. + if((i->getGameID() == j->getGameID()) && ((*i) != (*j))) + { + updatedGames.push_back(*j); + break; + } + } + } + // Find added games + for(typename container::const_iterator i = updated.begin(); i!=updated.end(); ++i) + { + bool found=false; + for(typename container::const_iterator j = original.begin(); j!=original.end(); ++j) + { + if(i->getGameID() == j->getGameID()) + { + found=true; + break; + } + } + if(!found) + { + updatedGames.push_back(*i); + } + } +} + + +template void NetUpdateGameList::applyDifferences(container& original) const +{ + // Remove the removed games + for(Uint16 i=0; igetGameID() == removedGames[i]) + { + game = j; + break; + } + } + original.erase(game); + } + + // Change the changed games and add the rest + for(Uint16 i=0; igetGameID() == updatedGames[i].getGameID()) + { + (*j) = updatedGames[i]; + found=true; + break; + } + } + if(!found) + { + original.insert(original.end(), updatedGames[i]); + } + } +} + + +template void NetUpdatePlayerList::updateDifferences(const container& original, const container& updated) +{ + removedPlayers.clear(); + updatedPlayers.clear(); + // Find removed players + for(typename container::const_iterator i = original.begin(); i!=original.end(); ++i) + { + bool found=false; + for(typename container::const_iterator j = updated.begin(); j!=updated.end(); ++j) + { + if(i->getPlayerID() == j->getPlayerID()) + { + found=true; + break; + } + } + if(!found) + removedPlayers.push_back(i->getPlayerID()); + } + + // Find added or changed players + for(typename container::const_iterator i = updated.begin(); i!=updated.end(); ++i) + { + bool found=false; + bool changed=false; + for(typename container::const_iterator j = original.begin(); j!=original.end(); ++j) + { + if(i->getPlayerID() == j->getPlayerID()) + { + found=true; + if((*i) != (*j)) + { + changed=true; + } + break; + } + } + if(!found || changed) + updatedPlayers.push_back(*i); + } +} + + +template void NetUpdatePlayerList::applyDifferences(container& original) const +{ + // Remove removed players + for(std::vector::const_iterator i = removedPlayers.begin(); i!=removedPlayers.end(); ++i) + { + for(typename container::iterator j=original.begin(); j!=original.end(); ++j) + { + if(*i == j->getPlayerID()) + { + original.erase(j); + break; + } + } + } + + // Change and/or add the updated players + for(std::vector::const_iterator i=updatedPlayers.begin(); i!=updatedPlayers.end(); ++i) + { + bool found=false; + for(typename container::iterator j=original.begin(); j!=original.end(); ++j) + { + // Same player ID — this player has changed somehow. + if(i->getPlayerID() == j->getPlayerID()) + { + (*j) = (*i); + found = true; + } + } + // Not found — this is a new player. + if(!found) + { + original.insert(original.end(), (*i)); + } + } +} diff --git a/src/net/message/MapDatabaseMessages.cpp b/src/net/message/MapDatabaseMessages.cpp new file mode 100644 index 000000000..a1a529ba7 --- /dev/null +++ b/src/net/message/MapDatabaseMessages.cpp @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "MapDatabaseMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetDownloadableMapInfos::NetDownloadableMapInfos() + : maps() +{ + +} + + + +NetDownloadableMapInfos::NetDownloadableMapInfos(std::vector maps) + :maps(maps) +{ +} + + + +Uint8 NetDownloadableMapInfos::getMessageType() const +{ + return MNetDownloadableMapInfos; +} + + + +void NetDownloadableMapInfos::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetDownloadableMapInfos"); + stream->writeEnterSection("maps"); + stream->writeUint32(maps.size(), "size"); + for(unsigned int i=0; iwriteEnterSection(i); + maps[i].encodeData(stream); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + stream->writeLeaveSection(); +} + + + +void NetDownloadableMapInfos::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetDownloadableMapInfos"); + stream->readEnterSection("maps"); + Uint32 size = stream->readUint32("maps"); + maps.resize(size); + for(unsigned int i=0; ireadEnterSection(i); + maps[i].decodeData(stream, VERSION_MINOR); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + stream->readLeaveSection(); +} + + + +std::string NetDownloadableMapInfos::format() const +{ + std::ostringstream s; + s<<"NetDownloadableMapInfos(maps.size()="<(rhs); + if(r.maps == maps) + return true; + } + return false; +} + + +std::vector NetDownloadableMapInfos::getMaps() const +{ + return maps; +} + + + + +NetRequestDownloadableMapList::NetRequestDownloadableMapList() +{ + +} + + + +Uint8 NetRequestDownloadableMapList::getMessageType() const +{ + return MNetRequestDownloadableMapList; +} + + + +void NetRequestDownloadableMapList::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRequestDownloadableMapList"); + stream->writeLeaveSection(); +} + + + +void NetRequestDownloadableMapList::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRequestDownloadableMapList"); + stream->readLeaveSection(); +} + + + +std::string NetRequestDownloadableMapList::format() const +{ + std::ostringstream s; + s<<"NetRequestDownloadableMapList()"; + return s.str(); +} + + + +bool NetRequestDownloadableMapList::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetRequestDownloadableMapList)) + { + //const NetRequestDownloadableMapList& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetRequestMapThumbnail::NetRequestMapThumbnail() + : mapID(0) +{ + +} + + + +NetRequestMapThumbnail::NetRequestMapThumbnail(Uint16 mapID) + : mapID(mapID) +{ +} + + + +Uint8 NetRequestMapThumbnail::getMessageType() const +{ + return MNetRequestMapThumbnail; +} + + + +void NetRequestMapThumbnail::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRequestMapThumbnail"); + stream->writeUint16(mapID, "mapID"); + stream->writeLeaveSection(); +} + + + +void NetRequestMapThumbnail::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRequestMapThumbnail"); + mapID = stream->readUint16("mapID"); + stream->readLeaveSection(); +} + + + +std::string NetRequestMapThumbnail::format() const +{ + std::ostringstream s; + s<<"NetRequestMapThumbnail("<<"mapID="<(rhs); + if(r.mapID == mapID) + return true; + } + return false; +} + + +Uint16 NetRequestMapThumbnail::getMapID() const +{ + return mapID; +} + + + + +NetSendMapThumbnail::NetSendMapThumbnail() + : mapID(0), thumbnail() +{ + +} + + + +NetSendMapThumbnail::NetSendMapThumbnail(Uint16 mapID, MapThumbnail thumbnail) + :mapID(mapID), thumbnail(thumbnail) +{ +} + + + +Uint8 NetSendMapThumbnail::getMessageType() const +{ + return MNetSendMapThumbnail; +} + + + +void NetSendMapThumbnail::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendMapThumbnail"); + stream->writeUint16(mapID, "mapID"); + thumbnail.encodeData(stream); + stream->writeLeaveSection(); +} + + + +void NetSendMapThumbnail::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendMapThumbnail"); + mapID = stream->readUint16("mapID"); + thumbnail.decodeData(stream, VERSION_MINOR); + stream->readLeaveSection(); +} + + + +std::string NetSendMapThumbnail::format() const +{ + std::ostringstream s; + s<<"NetSendMapThumbnail("<<"mapID="<(rhs); + if(r.mapID == mapID) + return true; + } + return false; +} + + +Uint16 NetSendMapThumbnail::getMapID() const +{ + return mapID; +} + + + +MapThumbnail NetSendMapThumbnail::getThumbnail() const +{ + return thumbnail; +} + + + + +NetSubmitRatingOnMap::NetSubmitRatingOnMap() + : mapID(0), rating(0) +{ + +} + + + +NetSubmitRatingOnMap::NetSubmitRatingOnMap(Uint16 mapID, Uint8 rating) + :mapID(mapID), rating(rating) +{ +} + + + +Uint8 NetSubmitRatingOnMap::getMessageType() const +{ + return MNetSubmitRatingOnMap; +} + + + +void NetSubmitRatingOnMap::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSubmitRatingOnMap"); + stream->writeUint16(mapID, "mapID"); + stream->writeUint8(rating, "rating"); + stream->writeLeaveSection(); +} + + + +void NetSubmitRatingOnMap::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSubmitRatingOnMap"); + mapID = stream->readUint16("mapID"); + rating = stream->readUint8("rating"); + stream->readLeaveSection(); +} + + + +std::string NetSubmitRatingOnMap::format() const +{ + std::ostringstream s; + s<<"NetSubmitRatingOnMap("<<"mapID="<(rhs); + if(r.mapID == mapID && r.rating == rating) + return true; + } + return false; +} + + +Uint16 NetSubmitRatingOnMap::getMapID() const +{ + return mapID; +} + + + +Uint8 NetSubmitRatingOnMap::getRating() const +{ + return rating; +} diff --git a/src/net/message/MapDatabaseMessages.h b/src/net/message/MapDatabaseMessages.h new file mode 100644 index 000000000..7f542f856 --- /dev/null +++ b/src/net/message/MapDatabaseMessages.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "MapThumbnail.h" +#include "YOGDownloadableMapInfo.h" + +/// Server -> client: list of downloadable maps (metadata only). +class NetDownloadableMapInfos : public NetMessage +{ +public: + NetDownloadableMapInfos(); + NetDownloadableMapInfos(std::vector maps); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + std::vector getMaps() const; +private: + std::vector maps; +}; + + +/// Client -> server: please send the current downloadable-maps list. +class NetRequestDownloadableMapList : public NetMessage +{ +public: + NetRequestDownloadableMapList(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Client -> server: send the thumbnail for the given map. +class NetRequestMapThumbnail : public NetMessage +{ +public: + NetRequestMapThumbnail(); + NetRequestMapThumbnail(Uint16 mapID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getMapID() const; +private: + Uint16 mapID; +}; + + +/// Server -> client: thumbnail bitmap for the previously-requested map. +class NetSendMapThumbnail : public NetMessage +{ +public: + NetSendMapThumbnail(); + NetSendMapThumbnail(Uint16 mapID, MapThumbnail thumbnail); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getMapID() const; + MapThumbnail getThumbnail() const; +private: + Uint16 mapID; + MapThumbnail thumbnail; +}; + + +/// Client -> server: submit the user's rating (1-5) for a map. +class NetSubmitRatingOnMap : public NetMessage +{ +public: + NetSubmitRatingOnMap(); + NetSubmitRatingOnMap(Uint16 mapID, Uint8 rating); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getMapID() const; + Uint8 getRating() const; +private: + Uint16 mapID; + Uint8 rating; +}; diff --git a/src/net/message/MapUploadMessages.cpp b/src/net/message/MapUploadMessages.cpp new file mode 100644 index 000000000..5273fce05 --- /dev/null +++ b/src/net/message/MapUploadMessages.cpp @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "MapUploadMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetRequestMapUpload::NetRequestMapUpload() + : mapInfo() +{ + +} + + + +NetRequestMapUpload::NetRequestMapUpload(YOGDownloadableMapInfo mapInfo) + :mapInfo(mapInfo) +{ +} + + + +Uint8 NetRequestMapUpload::getMessageType() const +{ + return MNetRequestMapUpload; +} + + + +void NetRequestMapUpload::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRequestMapUpload"); + mapInfo.encodeData(stream); + stream->writeLeaveSection(); +} + + + +void NetRequestMapUpload::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRequestMapUpload"); + mapInfo.decodeData(stream, VERSION_MINOR); + stream->readLeaveSection(); +} + + + +std::string NetRequestMapUpload::format() const +{ + std::ostringstream s; + s<<"NetRequestMapUpload("<<"""="<<""<<"; "<<")"; + return s.str(); +} + + + +bool NetRequestMapUpload::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetRequestMapUpload)) + { + const NetRequestMapUpload& r = dynamic_cast(rhs); + if(r.mapInfo == mapInfo) + return true; + } + return false; +} + + +YOGDownloadableMapInfo NetRequestMapUpload::getMapInfo() const +{ + return mapInfo; +} + + + + +NetAcceptMapUpload::NetAcceptMapUpload() + : fileID(0) +{ + +} + + + +NetAcceptMapUpload::NetAcceptMapUpload(Uint16 fileID) + :fileID(fileID) +{ +} + + + +Uint8 NetAcceptMapUpload::getMessageType() const +{ + return MNetAcceptMapUpload; +} + + + +void NetAcceptMapUpload::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetAcceptMapUpload"); + stream->writeUint16(fileID, "fileID"); + stream->writeLeaveSection(); +} + + + +void NetAcceptMapUpload::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetAcceptMapUpload"); + fileID = stream->readUint16("fileID"); + stream->readLeaveSection(); +} + + + +std::string NetAcceptMapUpload::format() const +{ + std::ostringstream s; + s<<"NetAcceptMapUpload("<<"fileID="<(rhs); + if(r.fileID == fileID) + return true; + } + return false; +} + + +Uint16 NetAcceptMapUpload::getFileID() const +{ + return fileID; +} + + + + +NetRefuseMapUpload::NetRefuseMapUpload() + : reason(YOGMapUploadReasonUnknown) +{ + +} + + + +NetRefuseMapUpload::NetRefuseMapUpload(YOGMapUploadRefusalReason reason) + :reason(reason) +{ +} + + + +Uint8 NetRefuseMapUpload::getMessageType() const +{ + return MNetRefuseMapUpload; +} + + + +void NetRefuseMapUpload::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRefuseMapUpload"); + stream->writeUint8(static_cast(reason), "reason"); + stream->writeLeaveSection(); +} + + + +void NetRefuseMapUpload::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRefuseMapUpload"); + reason = static_cast(stream->readUint8("reason")); + stream->readLeaveSection(); +} + + + +std::string NetRefuseMapUpload::format() const +{ + std::ostringstream s; + s<<"NetRefuseMapUpload("<<"reason="<(rhs); + if(r.reason == reason) + return true; + } + return false; +} + + +YOGMapUploadRefusalReason NetRefuseMapUpload::getReason() const +{ + return reason; +} diff --git a/src/net/message/MapUploadMessages.h b/src/net/message/MapUploadMessages.h new file mode 100644 index 000000000..a544e16df --- /dev/null +++ b/src/net/message/MapUploadMessages.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "YOGConsts.h" +#include "YOGDownloadableMapInfo.h" + +/// Client -> map-database server: request to upload a new map (carries the +/// map's metadata so the server can check policy/duplicates). +class NetRequestMapUpload : public NetMessage +{ +public: + NetRequestMapUpload(); + NetRequestMapUpload(YOGDownloadableMapInfo mapInfo); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGDownloadableMapInfo getMapInfo() const; +private: + YOGDownloadableMapInfo mapInfo; +}; + + +/// Server -> uploader: upload accepted; here is the fileID to push chunks to. +class NetAcceptMapUpload : public NetMessage +{ +public: + NetAcceptMapUpload(); + NetAcceptMapUpload(Uint16 fileID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getFileID() const; +private: + Uint16 fileID; +}; + + +/// Server -> uploader: upload refused, carrying the reason. +class NetRefuseMapUpload : public NetMessage +{ +public: + NetRefuseMapUpload(); + NetRefuseMapUpload(YOGMapUploadRefusalReason reason); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGMapUploadRefusalReason getReason() const; +private: + YOGMapUploadRefusalReason reason; +}; diff --git a/src/net/message/NetMessage.cpp b/src/net/message/NetMessage.cpp new file mode 100644 index 000000000..c2fff90d9 --- /dev/null +++ b/src/net/message/NetMessage.cpp @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "NetMessage.h" + +#include + +#include "AuthMessages.h" +#include "FileTransferMessages.h" +#include "GameCreateMessages.h" +#include "GameHeaderMessages.h" +#include "GameJoinMessages.h" +#include "GameLaunchMessages.h" +#include "GameTeamMessages.h" +#include "LobbyMessages.h" +#include "MapDatabaseMessages.h" +#include "MapUploadMessages.h" +#include "OrderMessages.h" +#include "RegistrationMessages.h" +#include "RouterAdminMessages.h" +#include "RouterMessages.h" + +std::shared_ptr NetMessage::getNetMessage(GAGCore::InputStream* stream) +{ + Uint8 netType = stream->readUint8("messageType"); + std::shared_ptr message; + switch(netType) + { + case MNetSendOrder: + message.reset(new NetSendOrder); + break; + case MNetSendClientInformation: + message.reset(new NetSendClientInformation); + break; + case MNetSendServerInformation: + message.reset(new NetSendServerInformation); + break; + case MNetAttemptLogin: + message.reset(new NetAttemptLogin); + break; + case MNetLoginSuccessful: + message.reset(new NetLoginSuccessful); + break; + case MNetRefuseLogin: + message.reset(new NetRefuseLogin); + break; + case MNetUpdateGameList: + message.reset(new NetUpdateGameList); + break; + case MNetDisconnect: + message.reset(new NetDisconnect); + break; + case MNetAttemptRegistration: + message.reset(new NetAttemptRegistration); + break; + case MNetAcceptRegistration: + message.reset(new NetAcceptRegistration); + break; + case MNetRefuseRegistration: + message.reset(new NetRefuseRegistration); + break; + case MNetUpdatePlayerList: + message.reset(new NetUpdatePlayerList); + break; + case MNetCreateGame: + message.reset(new NetCreateGame); + break; + case MNetAttemptJoinGame: + message.reset(new NetAttemptJoinGame); + break; + case MNetGameJoinAccepted: + message.reset(new NetGameJoinAccepted); + break; + case MNetGameJoinRefused: + message.reset(new NetGameJoinRefused); + break; + case MNetSendYOGMessage: + message.reset(new NetSendYOGMessage); + break; + case MNetSendMapHeader: + message.reset(new NetSendMapHeader); + break; + case MNetCreateGameAccepted: + message.reset(new NetCreateGameAccepted); + break; + case MNetCreateGameRefused: + message.reset(new NetCreateGameRefused); + break; + case MNetSendGameHeader: + message.reset(new NetSendGameHeader); + break; + case MNetStartGame: + message.reset(new NetStartGame); + break; + case MNetRequestFile: + message.reset(new NetRequestFile); + break; + case MNetSendFileInformation: + message.reset(new NetSendFileInformation); + break; + case MNetSendFileChunk: + message.reset(new NetSendFileChunk); + break; + case MNetKickPlayer: + message.reset(new NetKickPlayer); + break; + case MNetLeaveGame: + message.reset(new NetLeaveGame); + break; + case MNetReadyToLaunch: + message.reset(new NetReadyToLaunch); + break; + case MNetNotReadyToLaunch: + message.reset(new NetNotReadyToLaunch); + break; + case MNetSendGamePlayerInfo: + message.reset(new NetSendGamePlayerInfo); + break; + case MNetRemoveAI: + message.reset(new NetRemoveAI); + break; + case MNetChangePlayersTeam: + message.reset(new NetChangePlayersTeam); + break; + case MNetRequestGameStart: + message.reset(new NetRequestGameStart); + break; + case MNetRefuseGameStart: + message.reset(new NetRefuseGameStart); + break; + case MNetPing: + message.reset(new NetPing); + break; + case MNetPingReply: + message.reset(new NetPingReply); + break; + case MNetSetLatencyMode: + message.reset(new NetSetLatencyMode); + break; + case MNetPlayerJoinsGame: + message.reset(new NetPlayerJoinsGame); + break; + case MNetAddAI: + message.reset(new NetAddAI); + break; + case MNetSendReteamingInformation: + message.reset(new NetSendReteamingInformation); + break; + case MNetSendGameResult: + message.reset(new NetSendGameResult); + break; + case MNetPlayerIsBanned: + message.reset(new NetPlayerIsBanned); + break; + case MNetIPIsBanned: + message.reset(new NetIPIsBanned); + break; + case MNetRegisterRouter: + message.reset(new NetRegisterRouter); + break; + case MNetAcknowledgeRouter: + message.reset(new NetAcknowledgeRouter); + break; + case MNetSetGameInRouter: + message.reset(new NetSetGameInRouter); + break; + case MNetSendAfterJoinGameInformation: + message.reset(new NetSendAfterJoinGameInformation); + break; + case MNetRouterAdministratorLogin: + message.reset(new NetRouterAdministratorLogin); + break; + case MNetRouterAdministratorSendCommand: + message.reset(new NetRouterAdministratorSendCommand); + break; + case MNetRouterAdministratorSendText: + message.reset(new NetRouterAdministratorSendText); + break; + case MNetRouterAdministratorLoginAccepted: + message.reset(new NetRouterAdministratorLoginAccepted); + break; + case MNetRouterAdministratorLoginRefused: + message.reset(new NetRouterAdministratorLoginRefused); + break; + case MNetDownloadableMapInfos: + message.reset(new NetDownloadableMapInfos); + break; + case MNetRequestDownloadableMapList: + message.reset(new NetRequestDownloadableMapList); + break; + case MNetRequestMapUpload: + message.reset(new NetRequestMapUpload); + break; + case MNetAcceptMapUpload: + message.reset(new NetAcceptMapUpload); + break; + case MNetRefuseMapUpload: + message.reset(new NetRefuseMapUpload); + break; + case MNetCancelSendingFile: + message.reset(new NetCancelSendingFile); + break; + case MNetCancelRecievingFile: + message.reset(new NetCancelRecievingFile); + break; + case MNetRequestMapThumbnail: + message.reset(new NetRequestMapThumbnail); + break; + case MNetSendMapThumbnail: + message.reset(new NetSendMapThumbnail); + break; + case MNetSubmitRatingOnMap: + message.reset(new NetSubmitRatingOnMap); + break; + default: + // Untrusted byte from the wire didn't match any known opcode. + // Drop the message and let the caller handle the null shared_ptr + // (existing call sites already guard with `if(!message) return;`). + std::cerr << "NetMessage::getNetMessage: unknown opcode " << (int)netType << std::endl; + return std::shared_ptr(); + } + message->decodeData(stream); + return message; +} + + + +bool NetMessage::operator!=(const NetMessage& rhs) const +{ + return !(*this == rhs); +} diff --git a/src/net/message/OrderMessages.cpp b/src/net/message/OrderMessages.cpp new file mode 100644 index 000000000..9a220d5b9 --- /dev/null +++ b/src/net/message/OrderMessages.cpp @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "OrderMessages.h" +#include +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetSendOrder::NetSendOrder() +{ +} + + + +NetSendOrder::NetSendOrder(std::shared_ptr newOrder) +{ + order=newOrder; +} + + + +void NetSendOrder::changeOrder(std::shared_ptr newOrder) +{ + order = newOrder; +} + + + +std::shared_ptr NetSendOrder::getOrder() +{ + return order; +} + + + +Uint8 NetSendOrder::getMessageType() const +{ + return MNetSendOrder; +} + + + +void NetSendOrder::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSendOrder"); + Uint32 orderLength = order->getDataLength(); + stream->writeUint32(orderLength+1, "size"); + stream->writeUint8(order->getOrderType(), "orderType"); + stream->write(order->getData(), order->getDataLength(), "data"); + stream->writeUint8(order->sender, "sender"); + stream->writeUint32(order->gameCheckSum, "checksum"); + stream->writeLeaveSection(); +} + + + +void NetSendOrder::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSendOrder"); + const Uint32 size = stream->readUint32("size"); + + // Validate before allocating: an attacker- or corruption-supplied multi-GB + // size would otherwise either throw std::bad_alloc (which callers catch + // only as ios_base::failure and therefore miss) or waste a large + // allocation before the downstream "bad format" path fires. The buffer + // below is RAII-managed so any subsequent throw can't leak it. + if (size > MAX_NET_SEND_ORDER_SIZE) + { + std::ostringstream msg; + msg << "NetSendOrder size " << size << " exceeds max " << MAX_NET_SEND_ORDER_SIZE; + throw std::ios_base::failure(msg.str()); + } + + std::vector buffer(size); + stream->read(buffer.data(), size, "data"); + stream->readLeaveSection(); + + order = Order::getOrder(buffer.data(), size, VERSION_MINOR); + + // If this couldn't be interpreted return it returned a NULL order, so we throw. + if (order == std::shared_ptr()) + throw std::ios_base::failure("Couldn't decode data stream to an Order: bad format."); + + order->sender = stream->readUint8("sender"); + order->gameCheckSum = stream->readUint32("checksum"); +} + + + +std::string NetSendOrder::format() const +{ + std::stringstream s; + if(order==NULL) + { + s<<"NetSendOrder()"; + } + else + { + s<<"NetSendOrder(orderType="<(order->getOrderType())<<")"; + } + return s.str(); +} + + + +bool NetSendOrder::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetSendOrder)) + { + const NetSendOrder& r = dynamic_cast(rhs); + if(order==NULL || r.order==NULL) + { + return order == r.order; + } + if(typeid(r.order) == typeid(order)) + { + return true; + } + } + return false; +} + + + +NetPing::NetPing() +{ + +} + + + +Uint8 NetPing::getMessageType() const +{ + return MNetPing; +} + + + +void NetPing::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetPing"); + stream->writeLeaveSection(); +} + + + +void NetPing::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetPing"); + stream->readLeaveSection(); +} + + + +std::string NetPing::format() const +{ + std::ostringstream s; + s<<"NetPing()"; + return s.str(); +} + + + +bool NetPing::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetPing)) + { + //const NetPing& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetPingReply::NetPingReply() +{ + +} + + + +Uint8 NetPingReply::getMessageType() const +{ + return MNetPingReply; +} + + + +void NetPingReply::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetPingReply"); + stream->writeLeaveSection(); +} + + + +void NetPingReply::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetPingReply"); + stream->readLeaveSection(); +} + + + +std::string NetPingReply::format() const +{ + std::ostringstream s; + s<<"NetPingReply()"; + return s.str(); +} + + + +bool NetPingReply::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetPingReply)) + { + //const NetPingReply& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetSetLatencyMode::NetSetLatencyMode() + : latencyAdjustment(0) +{ + +} + + + +NetSetLatencyMode::NetSetLatencyMode(Uint8 latencyAdjustment) + :latencyAdjustment(latencyAdjustment) +{ +} + + + +Uint8 NetSetLatencyMode::getMessageType() const +{ + return MNetSetLatencyMode; +} + + + +void NetSetLatencyMode::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSetLatencyMode"); + stream->writeUint8(latencyAdjustment, "latencyAdjustment"); + stream->writeLeaveSection(); +} + + + +void NetSetLatencyMode::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSetLatencyMode"); + latencyAdjustment = stream->readUint8("latencyAdjustment"); + stream->readLeaveSection(); +} + + + +std::string NetSetLatencyMode::format() const +{ + std::ostringstream s; + s<<"NetSetLatencyMode("<<"latencyAdjustment="<(latencyAdjustment)<<"; "<<")"; + return s.str(); +} + + + +bool NetSetLatencyMode::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetSetLatencyMode)) + { + const NetSetLatencyMode& r = dynamic_cast(rhs); + if(r.latencyAdjustment == latencyAdjustment) + return true; + } + return false; +} + + +Uint8 NetSetLatencyMode::getLatencyAdjustment() const +{ + return latencyAdjustment; +} diff --git a/src/net/message/OrderMessages.h b/src/net/message/OrderMessages.h new file mode 100644 index 000000000..388b5e72e --- /dev/null +++ b/src/net/message/OrderMessages.h @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "Order.h" + +/// Maximum byte length of the payload inside a NetSendOrder envelope. The +/// largest legitimate Order is OrderVoiceData (capped at 2048 bytes by +/// VoiceRecorder); every other Order is tens of bytes. 1 MiB mirrors the +/// string-length cap in BinaryInputStream::readText and leaves three orders +/// of magnitude of headroom against future order growth, while keeping any +/// pre-allocation under control. Used by NetSendOrder::decodeData to reject +/// attacker-supplied envelope sizes before they reach `new Uint8[size]`. +constexpr Uint32 MAX_NET_SEND_ORDER_SIZE = 1u << 20; + +/// Wraps an Order for transmission across the network. The simulation engine +/// produces Orders from local input and AI; NetSendOrder is the wire envelope. +class NetSendOrder : public NetMessage +{ +public: + /// Creates an envelope holding a NULL Order. + NetSendOrder(); + + /// Takes ownership of the supplied Order. + NetSendOrder(std::shared_ptr newOrder); + + /// Replaces any existing Order with the new one. + void addOrder(std::shared_ptr newOrder); + + std::shared_ptr getOrder(); + + void changeOrder(std::shared_ptr newOrder); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + /// Wire format: Uint32 size | size bytes payload | Uint8 sender | Uint32 checksum. + /// Throws std::ios_base::failure if size > MAX_NET_SEND_ORDER_SIZE or if + /// Order::getOrder cannot interpret the payload. All callers + /// (ReplayReader::loadReplay, retrieveOrder, NetMessage::getNetMessage) + /// already treat ios_base::failure as a clean "drop this message" signal. + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +private: + std::shared_ptr order; +}; + + +/// Latency probe sent periodically to measure round-trip time. +class NetPing : public NetMessage +{ +public: + NetPing(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Reply to NetPing. +class NetPingReply : public NetMessage +{ +public: + NetPingReply(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Server -> all clients: change the order-pipeline latency adjustment, used +/// to keep all clients deterministically in sync under variable network conditions. +class NetSetLatencyMode : public NetMessage +{ +public: + NetSetLatencyMode(); + NetSetLatencyMode(Uint8 latencyAdjustment); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint8 getLatencyAdjustment() const; +private: + Uint8 latencyAdjustment; +}; diff --git a/src/net/message/RegistrationMessages.cpp b/src/net/message/RegistrationMessages.cpp new file mode 100644 index 000000000..fa7e2fc11 --- /dev/null +++ b/src/net/message/RegistrationMessages.cpp @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "RegistrationMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetAttemptRegistration::NetAttemptRegistration() +{ + +} + + + + +NetAttemptRegistration::NetAttemptRegistration(const std::string& username, const std::string& password) + : username(username), password(password) +{ + +} + + + + +Uint8 NetAttemptRegistration::getMessageType() const +{ + return MNetAttemptRegistration; +} + + + +void NetAttemptRegistration::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetAttemptRegistration"); + stream->writeText(username, "username"); + stream->writeText(password, "password"); + stream->writeLeaveSection(); +} + + + +void NetAttemptRegistration::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetAttemptRegistration"); + username=stream->readText("username"); + password=stream->readText("password"); + stream->readLeaveSection(); +} + + + +std::string NetAttemptRegistration::format() const +{ + std::ostringstream s; + s<<"NetAttemptRegistration(username=\""<(rhs); + if(username == r.username && password == r.password) + return true; + } + return false; +} + + + +std::string NetAttemptRegistration::getUsername() const +{ + return username; +} + + + +std::string NetAttemptRegistration::getPassword() const +{ + return password; +} + + + +NetAcceptRegistration::NetAcceptRegistration() +{ + +} + + + +Uint8 NetAcceptRegistration::getMessageType() const +{ + return MNetAcceptRegistration; +} + + + +void NetAcceptRegistration::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetAcceptRegistration"); + stream->writeLeaveSection(); +} + + + +void NetAcceptRegistration::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetAcceptRegistration"); + stream->readLeaveSection(); +} + + + +std::string NetAcceptRegistration::format() const +{ + std::ostringstream s; + s<<"NetAcceptRegistration()"; + return s.str(); +} + + + +bool NetAcceptRegistration::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetAcceptRegistration)) + { +// const NetAcceptRegistration& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetRefuseRegistration::NetRefuseRegistration() +{ + reason = YOGLoginUnknown; +} + + + +NetRefuseRegistration::NetRefuseRegistration(YOGLoginState reason) + : reason(reason) +{ + +} + + + +Uint8 NetRefuseRegistration::getMessageType() const +{ + return MNetRefuseRegistration; +} + + + +void NetRefuseRegistration::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRefuseRegistration"); + stream->writeUint8(reason, "reason"); + stream->writeLeaveSection(); +} + + +void NetRefuseRegistration::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRefuseRegistration"); + reason=static_cast(stream->readUint8("reason")); + stream->readLeaveSection(); +} + + + +std::string NetRefuseRegistration::format() const +{ + std::ostringstream s; + std::string sreason; + if(reason == YOGLoginSuccessful) + sreason="YOGLoginSuccessful"; + if(reason == YOGLoginUnknown) + sreason="YOGLoginUnknown"; + if(reason == YOGPasswordIncorrect) + sreason="YOGPasswordIncorrect"; + if(reason == YOGUsernameAlreadyUsed) + sreason="YOGUsernameAlreadyUsed"; + if(reason == YOGUserNotRegistered) + sreason="YOGUserNotRegistered"; + s<<"NetRefuseRegistration(reason="<(rhs); + if(reason == r.reason) + return true; + } + return false; +} + + + +YOGLoginState NetRefuseRegistration::getRefusalReason() const +{ + return reason; +} diff --git a/src/net/message/RegistrationMessages.h b/src/net/message/RegistrationMessages.h new file mode 100644 index 000000000..36b3d92d8 --- /dev/null +++ b/src/net/message/RegistrationMessages.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "YOGConsts.h" + +/// Client -> server account registration request. +class NetAttemptRegistration : public NetMessage +{ +public: + NetAttemptRegistration(); + NetAttemptRegistration(const std::string& username, const std::string& password); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + std::string getUsername() const; + std::string getPassword() const; +private: + std::string username; + std::string password; +}; + + +/// Server -> client: registration accepted. +class NetAcceptRegistration : public NetMessage +{ +public: + NetAcceptRegistration(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Server -> client: registration denied, carrying the reason. +class NetRefuseRegistration : public NetMessage +{ +public: + NetRefuseRegistration(); + NetRefuseRegistration(YOGLoginState reason); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGLoginState getRefusalReason() const; +private: + YOGLoginState reason; +}; diff --git a/src/net/message/RouterAdminMessages.cpp b/src/net/message/RouterAdminMessages.cpp new file mode 100644 index 000000000..6b310dd8f --- /dev/null +++ b/src/net/message/RouterAdminMessages.cpp @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "RouterAdminMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetRouterAdministratorLogin::NetRouterAdministratorLogin() + : password() +{ + +} + + + +NetRouterAdministratorLogin::NetRouterAdministratorLogin(std::string password) + :password(password) +{ +} + + + +Uint8 NetRouterAdministratorLogin::getMessageType() const +{ + return MNetRouterAdministratorLogin; +} + + + +void NetRouterAdministratorLogin::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRouterAdministratorLogin"); + stream->writeText(password, "password"); + stream->writeLeaveSection(); +} + + + +void NetRouterAdministratorLogin::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRouterAdministratorLogin"); + password = stream->readText("password"); + stream->readLeaveSection(); +} + + + +std::string NetRouterAdministratorLogin::format() const +{ + std::ostringstream s; + s<<"NetRouterAdministratorLogin("<<"password="<(rhs); + if(r.password == password) + return true; + } + return false; +} + + +std::string NetRouterAdministratorLogin::getPassword() const +{ + return password; +} + + + + +NetRouterAdministratorSendCommand::NetRouterAdministratorSendCommand() + : command("") +{ + +} + + + +NetRouterAdministratorSendCommand::NetRouterAdministratorSendCommand(std::string command) + :command(command) +{ +} + + + +Uint8 NetRouterAdministratorSendCommand::getMessageType() const +{ + return MNetRouterAdministratorSendCommand; +} + + + +void NetRouterAdministratorSendCommand::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRouterAdministratorSendCommand"); + stream->writeText(command, "command"); + stream->writeLeaveSection(); +} + + + +void NetRouterAdministratorSendCommand::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRouterAdministratorSendCommand"); + command = stream->readText("command"); + stream->readLeaveSection(); +} + + + +std::string NetRouterAdministratorSendCommand::format() const +{ + std::ostringstream s; + s<<"NetRouterAdministratorSendCommand("<<"command="<(rhs); + if(r.command == command) + return true; + } + return false; +} + + +std::string NetRouterAdministratorSendCommand::getCommand() const +{ + return command; +} + + + + +NetRouterAdministratorSendText::NetRouterAdministratorSendText() + : text("") +{ + +} + + + +NetRouterAdministratorSendText::NetRouterAdministratorSendText(std::string text) + :text(text) +{ +} + + + +Uint8 NetRouterAdministratorSendText::getMessageType() const +{ + return MNetRouterAdministratorSendText; +} + + + +void NetRouterAdministratorSendText::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRouterAdministratorSendText"); + stream->writeText(text, "text"); + stream->writeLeaveSection(); +} + + + +void NetRouterAdministratorSendText::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRouterAdministratorSendText"); + text = stream->readText("text"); + stream->readLeaveSection(); +} + + + +std::string NetRouterAdministratorSendText::format() const +{ + std::ostringstream s; + s<<"NetRouterAdministratorSendText("<<"text="<(rhs); + if(r.text == text) + return true; + } + return false; +} + + +std::string NetRouterAdministratorSendText::getText() const +{ + return text; +} + + + + +NetRouterAdministratorLoginAccepted::NetRouterAdministratorLoginAccepted() +{ + +} + + + +Uint8 NetRouterAdministratorLoginAccepted::getMessageType() const +{ + return MNetRouterAdministratorLoginAccepted; +} + + + +void NetRouterAdministratorLoginAccepted::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRouterAdministratorLoginAccepted"); + stream->writeLeaveSection(); +} + + + +void NetRouterAdministratorLoginAccepted::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRouterAdministratorLoginAccepted"); + stream->readLeaveSection(); +} + + + +std::string NetRouterAdministratorLoginAccepted::format() const +{ + std::ostringstream s; + s<<"NetRouterAdministratorLoginAccepted()"; + return s.str(); +} + + + +bool NetRouterAdministratorLoginAccepted::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetRouterAdministratorLoginAccepted)) + { + //const NetRouterAdministratorLoginAccepted& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetRouterAdministratorLoginRefused::NetRouterAdministratorLoginRefused() + : reason(YOGRouterLoginUnknown) +{ + +} + + + +NetRouterAdministratorLoginRefused::NetRouterAdministratorLoginRefused(YOGRouterAdministratorLoginRefusalReason reason) + :reason(reason) +{ +} + + + +Uint8 NetRouterAdministratorLoginRefused::getMessageType() const +{ + return MNetRouterAdministratorLoginRefused; +} + + + +void NetRouterAdministratorLoginRefused::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRouterAdministratorLoginRefused"); + stream->writeUint8(reason, "reason"); + stream->writeLeaveSection(); +} + + + +void NetRouterAdministratorLoginRefused::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRouterAdministratorLoginRefused"); + reason = static_cast(stream->readUint8("reason")); + stream->readLeaveSection(); +} + + + +std::string NetRouterAdministratorLoginRefused::format() const +{ + std::ostringstream s; + s<<"NetRouterAdministratorLoginRefused("<<"reason="<(rhs); + if(r.reason == reason) + return true; + } + return false; +} + + +YOGRouterAdministratorLoginRefusalReason NetRouterAdministratorLoginRefused::getReason() const +{ + return reason; +} diff --git a/src/net/message/RouterAdminMessages.h b/src/net/message/RouterAdminMessages.h new file mode 100644 index 000000000..b02866807 --- /dev/null +++ b/src/net/message/RouterAdminMessages.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" +#include "YOGConsts.h" + +/// Admin -> router: authenticate as a router administrator using a password. +class NetRouterAdministratorLogin : public NetMessage +{ +public: + NetRouterAdministratorLogin(); + NetRouterAdministratorLogin(std::string password); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + std::string getPassword() const; +private: + std::string password; +}; + + +/// Admin -> router: send an administrative command (kick, ban, status, etc). +class NetRouterAdministratorSendCommand : public NetMessage +{ +public: + NetRouterAdministratorSendCommand(); + NetRouterAdministratorSendCommand(std::string command); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + std::string getCommand() const; +private: + std::string command; +}; + + +/// Router -> admin: textual response to an administrative command. +class NetRouterAdministratorSendText : public NetMessage +{ +public: + NetRouterAdministratorSendText(); + NetRouterAdministratorSendText(std::string text); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + std::string getText() const; +private: + std::string text; +}; + + +/// Router -> admin: login accepted. +class NetRouterAdministratorLoginAccepted : public NetMessage +{ +public: + NetRouterAdministratorLoginAccepted(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Router -> admin: login refused, carrying the reason. +class NetRouterAdministratorLoginRefused : public NetMessage +{ +public: + NetRouterAdministratorLoginRefused(); + NetRouterAdministratorLoginRefused(YOGRouterAdministratorLoginRefusalReason reason); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + YOGRouterAdministratorLoginRefusalReason getReason() const; +private: + YOGRouterAdministratorLoginRefusalReason reason; +}; diff --git a/src/net/message/RouterMessages.cpp b/src/net/message/RouterMessages.cpp new file mode 100644 index 000000000..9cff20924 --- /dev/null +++ b/src/net/message/RouterMessages.cpp @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "RouterMessages.h" +#include +#include +#include +#include "Version.h" +#include "BinaryStream.h" + +using namespace GAGCore; + +NetRegisterRouter::NetRegisterRouter() +{ + +} + + + +Uint8 NetRegisterRouter::getMessageType() const +{ + return MNetRegisterRouter; +} + + + +void NetRegisterRouter::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetRegisterRouter"); + stream->writeLeaveSection(); +} + + + +void NetRegisterRouter::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetRegisterRouter"); + stream->readLeaveSection(); +} + + + +std::string NetRegisterRouter::format() const +{ + std::ostringstream s; + s<<"NetRegisterRouter()"; + return s.str(); +} + + + +bool NetRegisterRouter::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetRegisterRouter)) + { + //const NetRegisterRouter& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetAcknowledgeRouter::NetAcknowledgeRouter() +{ + +} + + + +Uint8 NetAcknowledgeRouter::getMessageType() const +{ + return MNetAcknowledgeRouter; +} + + + +void NetAcknowledgeRouter::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetAcknowledgeRouter"); + stream->writeLeaveSection(); +} + + + +void NetAcknowledgeRouter::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetAcknowledgeRouter"); + stream->readLeaveSection(); +} + + + +std::string NetAcknowledgeRouter::format() const +{ + std::ostringstream s; + s<<"NetAcknowledgeRouter()"; + return s.str(); +} + + + +bool NetAcknowledgeRouter::operator==(const NetMessage& rhs) const +{ + if(typeid(rhs)==typeid(NetAcknowledgeRouter)) + { + //const NetAcknowledgeRouter& r = dynamic_cast(rhs); + return true; + } + return false; +} + + + +NetSetGameInRouter::NetSetGameInRouter() + : gameID(0) +{ + +} + + + +NetSetGameInRouter::NetSetGameInRouter(Uint16 gameID) + :gameID(gameID) +{ +} + + + +Uint8 NetSetGameInRouter::getMessageType() const +{ + return MNetSetGameInRouter; +} + + + +void NetSetGameInRouter::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("NetSetGameInRouter"); + stream->writeUint16(gameID, "gameID"); + stream->writeLeaveSection(); +} + + + +void NetSetGameInRouter::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("NetSetGameInRouter"); + gameID = stream->readUint16("gameID"); + stream->readLeaveSection(); +} + + + +std::string NetSetGameInRouter::format() const +{ + std::ostringstream s; + s<<"NetSetGameInRouter("<<"gameID="<(rhs); + if(r.gameID == gameID) + return true; + } + return false; +} + + +Uint16 NetSetGameInRouter::getGameID() const +{ + return gameID; +} diff --git a/src/net/message/RouterMessages.h b/src/net/message/RouterMessages.h new file mode 100644 index 000000000..875f26f64 --- /dev/null +++ b/src/net/message/RouterMessages.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +#include "NetMessage.h" +#include "NetMessageType.h" + +/// Game-router -> matchmaker: announce that this router is ready to host games. +class NetRegisterRouter : public NetMessage +{ +public: + NetRegisterRouter(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Matchmaker -> router: registration acknowledged. +class NetAcknowledgeRouter : public NetMessage +{ +public: + NetAcknowledgeRouter(); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; +}; + + +/// Matchmaker -> router: assign a specific gameID to this router connection. +class NetSetGameInRouter : public NetMessage +{ +public: + NetSetGameInRouter(); + NetSetGameInRouter(Uint16 gameID); + + Uint8 getMessageType() const; + void encodeData(GAGCore::OutputStream* stream) const; + void decodeData(GAGCore::InputStream* stream); + std::string format() const; + bool operator==(const NetMessage& rhs) const; + + Uint16 getGameID() const; +private: + Uint16 gameID; +}; diff --git a/src/render/GameAnimations.cpp b/src/render/GameAnimations.cpp new file mode 100644 index 000000000..7cf11e5fb --- /dev/null +++ b/src/render/GameAnimations.cpp @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#ifndef YOG_SERVER_ONLY + +#include "GameAnimations.h" + +#include "GlobalContainer.h" +#include "map/Map.h" + +UnitDeathAnimation::UnitDeathAnimation(int x, int y, Team *team) +{ + this->x = x; + this->y = y; + this->team = team; + this->ticksLeft = globalContainer->deathAnimation->getFrameCount() - 1; +} + +GameAnimations::GameAnimations(bool enabled, int sectorCount) + : enabled(enabled) +{ + resize(sectorCount); +} + +GameAnimations::~GameAnimations() +{ + clear(); +} + +void GameAnimations::resize(int sectorCount) +{ + clear(); + sectorExplosions.resize(sectorCount); + sectorDeathAnimations.resize(sectorCount); +} + +void GameAnimations::clear() +{ + for (auto& bucket : sectorExplosions) + { + for (BulletExplosion *e : bucket) + delete e; + bucket.clear(); + } + for (auto& bucket : sectorDeathAnimations) + { + for (UnitDeathAnimation *a : bucket) + delete a; + bucket.clear(); + } +} + +void GameAnimations::onBulletImpact(const Map& map, int x, int y) +{ + if (!enabled) + return; + int idx = map.getSectorIndex(x, y); + BulletExplosion *explosion = new BulletExplosion(); + explosion->x = x; + explosion->y = y; + explosion->ticksLeft = globalContainer->bulletExplosion->getFrameCount(); + sectorExplosions[idx].push_front(explosion); +} + +void GameAnimations::onUnitDeath(const Map& map, int x, int y, Team *team) +{ + if (!enabled) + return; + int idx = map.getSectorIndex(x, y); + sectorDeathAnimations[idx].push_back(new UnitDeathAnimation(x, y, team)); +} + +void GameAnimations::step() +{ + if (!enabled) + return; + for (auto& bucket : sectorExplosions) + { + for (auto it = bucket.begin(); it != bucket.end(); ) + { + if ((*it)->ticksLeft > 0) + { + (*it)->ticksLeft--; + ++it; + } + else + { + delete *it; + it = bucket.erase(it); + } + } + } + for (auto& bucket : sectorDeathAnimations) + { + for (auto it = bucket.begin(); it != bucket.end(); ) + { + if ((*it)->ticksLeft > 0) + { + (*it)->ticksLeft--; + ++it; + } + else + { + delete *it; + it = bucket.erase(it); + } + } + } +} + +#endif // !YOG_SERVER_ONLY diff --git a/src/render/GameAnimations.h b/src/render/GameAnimations.h new file mode 100644 index 000000000..eb61e9046 --- /dev/null +++ b/src/render/GameAnimations.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#ifndef YOG_SERVER_ONLY + +#include +#include + +class Map; +class Team; + +//! Visual aftermath of a bullet impact. Render-only — not in Map::checkSum, +//! not network-replicated, not serialized to save/replay files. +struct BulletExplosion +{ + int x, y, ticksLeft; +}; + +//! Visual fade-out played when a unit dies. Render-only — not in Map::checkSum, +//! not network-replicated, not serialized to save/replay files. The team +//! pointer is used purely to color the sprite. +struct UnitDeathAnimation +{ + UnitDeathAnimation(int x, int y, Team *team); + int x, y, ticksLeft; + Team *team; +}; + +//! Per-game render container for bullet explosions and unit death animations. +//! +//! These two effect lists used to live on Sector, alongside the sim-state +//! bullets list. That mixed render state with sim state and forced sim sites +//! (Sector::step, Unit::syncStep death path) to gate their pushes on +//! globalContainer->runNoX. Lifting them into this dedicated render container +//! lets sim code call onBulletImpact / onUnitDeath unconditionally; the +//! enabled flag below is the single point where the runNoX gate now lives. +//! +//! Storage is per-sector (one list per Map sector) to preserve the existing +//! per-sector flush pattern of GraphicContext::finishDrawingSprite in +//! Game::drawMapBulletsExplosionsDeathAnimations. +class GameAnimations +{ +public: + //! @param enabled false when running headless (--nox); the push methods + //! become no-ops and step() does nothing. Construction is still + //! cheap so the lifetime matches Game. + //! @param sectorCount number of Map sectors (Map::getSectorW() * + //! Map::getSectorH()). May be 0 at construction; resize() is + //! called when the map header is set. + GameAnimations(bool enabled, int sectorCount); + ~GameAnimations(); + + GameAnimations(const GameAnimations&) = delete; + GameAnimations& operator=(const GameAnimations&) = delete; + + //! Resize per-sector storage. Called whenever the map dimensions change. + //! Frees any existing entries. + void resize(int sectorCount); + + //! Free all stored animations without changing the sector count. + void clear(); + + //! Record a bullet impact for later rendering. No-op when disabled. + //! @param map needed to map (x,y) to a sector index. + void onBulletImpact(const Map& map, int x, int y); + + //! Record a unit death for later rendering. No-op when disabled. + //! @param map needed to map (x,y) to a sector index. + void onUnitDeath(const Map& map, int x, int y, Team *team); + + //! Tick down ticksLeft on every stored animation and remove the finished + //! ones. Called once per simulation tick from Map::syncStep. + void step(); + + //! Iteration access for the renderer. + const std::list& getExplosions(int sectorIdx) const + { return sectorExplosions[sectorIdx]; } + const std::list& getDeathAnimations(int sectorIdx) const + { return sectorDeathAnimations[sectorIdx]; } + +private: + bool enabled; + std::vector> sectorExplosions; + std::vector> sectorDeathAnimations; +}; + +#endif // !YOG_SERVER_ONLY diff --git a/src/render/GameRender.cpp b/src/render/GameRender.cpp new file mode 100644 index 000000000..436591565 --- /dev/null +++ b/src/render/GameRender.cpp @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "AICastor.h" +#include "AINicowar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "BuildingType.h" +#include "DatasetWriter.h" +#include "Game.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Order.h" +#include "Unit.h" +#include "UnitSkin.h" +#include "Integrity.h" +#include "Utilities.h" +#include "GameGUI.h" +#include "SDLCompat.h" + +#include "MapEdit.h" + +#include "Brush.h" +#include "DynamicClouds.h" +#include "Bullet.h" +#include "TextStream.h" +#include "FertilityCalculatorDialog.h" + +#include "ReplayWriter.h" + +#include "GameRenderInternal.h" + +// Map rendering orchestrator and shared helpers. Split from Game_render.cpp. + + +void Game::drawPointBar(int x, int y, BarOrientation orientation, int maxLength, int actLength, int secondActLength, Uint8 r, Uint8 g, Uint8 b, Uint8 r2, Uint8 g2, Uint8 b2, int barWidth) +{ + assert(maxLength>=0); + assert(maxLength<65536); + assert(actLength<=maxLength); + + if ((orientation==LEFT_TO_RIGHT) || (orientation==RIGHT_TO_LEFT)) + { + /*globalContainer->gfx->drawHorzLine(x, y, maxLength*3+1, 32, 32, 32); + globalContainer->gfx->drawHorzLine(x, y+barWidth+1, maxLength*3+1, 32, 32, 32); + for (int i=0; igfx->drawVertLine(x+i*3, y+1, barWidth, 32, 32, 32); + */ + globalContainer->gfx->drawFilledRect(x, y, maxLength*3+1, barWidth+2, 0, 0, 0); + + if (orientation==LEFT_TO_RIGHT) + { + int i; + for (i=0; igfx->drawFilledRect(x+i*3+1, y+1, 2, barWidth, r, g, b); + for (; igfx->drawFilledRect(x+i*3+1, y+1, 2, barWidth, r2, g2, b2); + for (; igfx->drawRect(x+i*3, y, 4, barWidth+2, r/3, g/3, b/3); + } + else + { + int i; + for (i=0; igfx->drawRect(x+i*3, y, 4, barWidth+2, r/3, g/3, b/3); + for (; igfx->drawFilledRect(x+i*3+1, y+1, 2, barWidth, r2, g2, b2); + for (; igfx->drawFilledRect(x+i*3+1, y+1, 2, barWidth, r, g, b); + } + } + else if ((orientation==BOTTOM_TO_TOP) || (orientation==TOP_TO_BOTTOM)) + { + /*globalContainer->gfx->drawVertLine(x, y, maxLength*3+1, 32, 32, 32); + globalContainer->gfx->drawVertLine(x+barWidth+1, y, maxLength*3+1, 32, 32, 32); + for (int i=0; igfx->drawHorzLine(x+1, y+i*3, barWidth, 32, 32, 32); + */ + globalContainer->gfx->drawFilledRect(x, y, barWidth+2, maxLength*3+1, 0, 0, 0); + + if (orientation==TOP_TO_BOTTOM) + { + int i; + for (i=0; igfx->drawFilledRect(x+1, y+i*3+1, barWidth, 2, r, g, b); + for (; igfx->drawFilledRect(x+1, y+i*3+1, barWidth, 2, r2, g2, b2); + for (; igfx->drawRect(x, y+i*3, 4, barWidth+2, r/3, g/3, b/3); + } + else + { + int i; + for (i=0; igfx->drawRect(x, y+i*3, 4, barWidth+2, r/3, g/3, b/3); + for (; igfx->drawFilledRect(x+1, y+i*3+1, barWidth, 2, r2, g2, b2); + for (; igfx->drawFilledRect(x+1, y+i*3+1, barWidth, 2, r, g, b); + } + } + else + assert(false); +} + + +void Game::drawHealthBar(int x, int y, int maxLength, int actLength, float hpRatio) +{ + if (hpRatio > 0.6f) + drawPointBar(x, y, LEFT_TO_RIGHT, maxLength, actLength, 78, 187, 78); + else if (hpRatio > 0.3f) + drawPointBar(x, y, LEFT_TO_RIGHT, maxLength, actLength, 255, 255, 0); + else + drawPointBar(x, y, LEFT_TO_RIGHT, maxLength, actLength, 255, 0, 0); +} + + +void Game::drawBuildingResourceBar(int x, int y, BuildingType* type, int maxValue, int currentValue, Uint8 r, Uint8 g, Uint8 b) +{ + // Shrink the bar (3px per unit + 1) until it fits within the building's height minus 10px of padding. + int bDiv = 1; + assert(type->height != 0); + while (((maxValue * 3 + 1) / bDiv) > ((type->height * 32) - 10)) + bDiv++; + drawPointBar(x, y, BOTTOM_TO_TOP, maxValue / bDiv, currentValue / bDiv, r, g, b, 1 + bDiv); +} + + + +bool Game::isOnScreen(int left, int top, int right, int bot, int viewportX, int viewportY, int x, int y) +{ + + left += viewportX; + right += viewportX; + top += viewportY; + bot += viewportY; + + if((x >= left-1 && x <= right) || (x+map.getW() >= left-1 && x+map.getW() <= right)) + { + if((y >= top-1 && y <= bot) || (y+map.getH() >= top-1 && y+map.getH() <= bot)) + { + return true; + } + } + return false; +} + + + +void Game::drawMap(int sx, int sy, int sw, int sh, int rightMargin, int topMargin, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, std::set *visibleBuildings, const BuildingGuiStateMap* buildingGuiState) +{ + static int time = 0; + static DynamicClouds ds(&globalContainer->settings); + int left=(sx>>5); + int top=(sy>>5); + int right=((sx+sw+31)>>5); + int bot=((sy+sh+31)>>5); + + time++; + drawMapWater(sw, sh, viewportX, viewportY, time); + drawMapTerrain(left, top, right, bot, viewportX, viewportY, localTeam, drawOptions); + drawMapRessources(left, top, right, bot, viewportX, viewportY, localTeam, drawOptions); + drawMapGroundUnits(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); + drawMapDebugAreas(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); + drawMapGroundBuildings(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, visibleBuildings, buildingGuiState); + drawMapAirUnits(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); + if((drawOptions & DRAW_SCRIPT_AREAS) != 0) + drawMapScriptAreas(left, top, right, bot, viewportX, viewportY); + drawMapBulletsExplosionsDeathAnimations(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); + + // compute and draw cloud shadow if we are in high quality + if ((globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) == 0) + { + ds.compute(viewportX, viewportY, sw, sh, time); + ds.render(globalContainer->gfx, sw, sh, DynamicClouds::SHADOW); + } + + drawMapFogOfWar(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); + drawMapAreas(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); + drawMapOverlayMaps(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); + drawUnitPathLines(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions); + + // draw cloud overlay if we are in high quality + if ((globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) == 0) + ds.render(globalContainer->gfx, sw, sh, DynamicClouds::CLOUD); + + // Draw units that are off the screen for the selected building + + Uint32 visibleTeams = teams[localTeam]->me; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + if(selectedBuilding != NULL && (selectedBuilding->owner->sharedVisionOther & visibleTeams)) + { + for(std::list::iterator i = selectedBuilding->unitsWorking.begin(); i!=selectedBuilding->unitsWorking.end(); ++i) + { + Unit* unit = *i; + if(!isOnScreen(left, top, right, bot, viewportX, viewportY, unit->posX, unit->posY)) + { + drawUnitOffScreen(0, topMargin, sw - rightMargin, sh-topMargin, viewportX, viewportY, unit, drawOptions); + } + } + } + + // we look on the whole map for buildings + // TODO : increase speed, do not count on graphic clipping + if (!globalContainer->replaying || globalContainer->replayShowFlags) + { + // In replays we want to show the flags of all players, so we build a list of whose buildings to show + std::list teamsToShow; + + if (!globalContainer->replaying) + { + // Only add the local team + teamsToShow.push_back(teams[localTeam]); + } + else + { + // Add all teams + for (int i=0; i::iterator teamsIt=teamsToShow.begin(); teamsIt!=teamsToShow.end(); ++teamsIt) + { + for (std::list::iterator virtualIt=(*teamsIt)->virtualBuildings.begin(); + virtualIt!=(*teamsIt)->virtualBuildings.end(); ++virtualIt) + { + Building *building=*virtualIt; + BuildingType *type=building->type; + + int team = building->owner->teamNumber; + + int imgid = type->gameSpriteImage; + + int x, y; + const Sint32 dispX = buildingGuiState ? displayedPosX(*buildingGuiState, *building) : building->posX; + const Sint32 dispY = buildingGuiState ? displayedPosY(*buildingGuiState, *building) : building->posY; + map.mapCaseToDisplayable(dispX, dispY, &x, &y, viewportX, viewportY); + + // all flags are hued: + Sprite *buildingSprite = type->gameSpritePtr; + buildingSprite->setBaseColor(teams[team]->color); + globalContainer->gfx->drawSprite(x, y, buildingSprite, imgid); + + // flag circle: + if (((drawOptions & DRAW_HEALTH_FOOD_BAR) != 0) || (building==selectedBuilding)) + globalContainer->gfx->drawCircle(x+16, y+16, 16+(32*building->unitStayRange), 0, 0, 255); + + if ((drawOptions & DRAW_HEALTH_FOOD_BAR) != 0) + { + int decy=(type->height*32); + int healDecx=(type->width-2)*16+1; + + // TODO : find better color for this + if (type->hpMax) + { + float hpRatio=(float)building->hp/(float)type->hpMax; + drawHealthBar(x+healDecx+6, y+decy-4, 16, 1+(int)(15.0f*hpRatio), hpRatio); + } + + if (building->maxUnitInside>0) + drawPointBar(x+type->width*32-4, y+1, BOTTOM_TO_TOP, building->maxUnitInside, (signed)building->unitsInside.size(), 255, 255, 255); + if (building->maxUnitWorking>0) + drawPointBar(x+type->width*16-((3*building->maxUnitWorking)>>1), y+1,LEFT_TO_RIGHT , building->maxUnitWorking, (signed)building->unitsWorking.size(), 255, 255, 255); + + if ((type->canFeedUnit) || (type->unitProductionTime)) + drawBuildingResourceBar(x+1, y+1, type, type->maxRessource[CORN], building->ressources[CORN], 255, 255, 120); + } + } + } + } + + if (DEBUG_RENDER_GRADIENTS) + for (int y=top-1; y<=bot; y++) + for (int x=left-1; x<=right; x++) + for (int pi=0; piai && players[pi]->ai->implementitionID==AI::CASTOR) + { + AICastor *ai=(AICastor *)players[pi]->ai->aiImplementation; + //Uint8 *gradient=ai->wheatCareMap[1]; + Uint8 *gradient=ai->hydratationMap; + //Uint8 *gradient=ai->enemyWarriorsMap; + //Uint8 *gradient=map.forbiddenGradient[1][0]; + //Uint8 *gradient=map.ressourcesGradient[0][CORN][0]; + + assert(gradient); + size_t addr=((x+viewportX)&map.wMask)+map.w*((y+viewportY)&map.hMask); + Uint8 value=gradient[addr]; + if (value) + globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, value); + + /*Uint8 *gradient2=ai->wheatCareMap[1]; + assert(gradient2); + Uint8 value2=gradient2[addr]; + if (value2) + globalContainer->gfx->drawString((x<<5), (y<<5)+10, globalContainer->littleFont, value2);*/ + break; + } +} diff --git a/src/render/GameRenderBuildings.cpp b/src/render/GameRenderBuildings.cpp new file mode 100644 index 000000000..e5b48da8c --- /dev/null +++ b/src/render/GameRenderBuildings.cpp @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "AICastor.h" +#include "AINicowar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "BuildingType.h" +#include "DatasetWriter.h" +#include "Game.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Order.h" +#include "Unit.h" +#include "UnitSkin.h" +#include "Integrity.h" +#include "Utilities.h" +#include "GameGUI.h" +#include "SDLCompat.h" + +#include "MapEdit.h" + +#include "Brush.h" +#include "DynamicClouds.h" +#include "Bullet.h" +#include "TextStream.h" +#include "FertilityCalculatorDialog.h" + +#include "ReplayWriter.h" + +// Building rendering. Split from Game_render.cpp. + + +struct BuildingPosComp +{ + bool operator () (Building * const & a, Building * const & b) + { + if (a->posY != b->posY) + return a->posY < b->posY; + else + return a->posX < b->posX; + } +}; + + +void Game::drawMapBuilding(int x, int y, int gid, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + Building *building = teams[Building::GIDtoTeam(gid)]->myBuildings[Building::GIDtoID(gid)]; + assert(building); + BuildingType *type=building->type; + Team *team=building->owner; + + int imgid; + if (type->crossConnectMultiImage) + { + // Cross-connect grid lookup. Only non-virtual buildings have + // crossConnectMultiImage, and non-virtual buildings can never be + // moved by the player, so the authoritative posX/posY is correct + // here — no pending shadow to consult. + int add = 0; + Uint16 b; + // Up + b = map.getBuilding(building->posX, building->posY-1); + if ((b != NOGBID) && + (Building::GIDtoTeam(b) == team->teamNumber) && (teams[Building::GIDtoTeam(b)]->myBuildings[Building::GIDtoID(b)]->type == type)) + add |= (1<<3); + // Bottom + b = map.getBuilding(building->posX, building->posY+building->type->height); + if ((b != NOGBID) && + (Building::GIDtoTeam(b) == team->teamNumber) && (teams[Building::GIDtoTeam(b)]->myBuildings[Building::GIDtoID(b)]->type == type)) + add |= (1<<2); + // Left + b = map.getBuilding(building->posX-1, building->posY); + if ((b != NOGBID) && + (Building::GIDtoTeam(b) == team->teamNumber) && (teams[Building::GIDtoTeam(b)]->myBuildings[Building::GIDtoID(b)]->type == type)) + add |= (1<<1); + // Right + b = map.getBuilding(building->posX+building->type->width, building->posY); + if ((b != NOGBID) && + (Building::GIDtoTeam(b) == team->teamNumber) && (teams[Building::GIDtoTeam(b)]->myBuildings[Building::GIDtoID(b)]->type == type)) + add |= (1<<0); + imgid = type->gameSpriteImage + add; + } + else + { + // FIXME : why building->hp is > type->hpMax ? + int hp = std::min(building->hp, type->hpMax); + // hpMax+1 (not hpMax) so that at full HP the integer division stays strictly + // below gameSpriteCount, leaving damageImgShift == 0 (pristine sprite). Using + // plain hpMax would yield shift == -1 at hp == hpMax and trip the assert below. + int damageImgShift = type->gameSpriteCount - ((hp * type->gameSpriteCount) / (type->hpMax+1)) - 1; + assert(damageImgShift >= 0); + imgid = type->gameSpriteImage + damageImgShift; + } +// int x, y; + int dx, dy; + + + // select buildings and set the team colors + Sprite *buildingSprite = type->gameSpritePtr; + dx = (type->width<<5)-buildingSprite->getW(imgid); + dy = (type->height<<5)-buildingSprite->getH(imgid); + buildingSprite->setBaseColor(team->color); + + // draw building + globalContainer->gfx->drawSprite(x+dx, y+dy, buildingSprite, imgid); + globalContainer->gfx->finishDrawingSprite(buildingSprite, 255); + + if ((drawOptions & DRAW_BUILDING_RECT) != 0) + { + int batW=(type->width )<<5; + int batH=(type->height)<<5; + int typeNum=building->typeNum; + globalContainer->gfx->drawRect(x, y, batW, batH, 255, 255, 255, 127); + + BuildingType *lastbt=globalContainer->buildingsTypes.get(typeNum); + int lastTypeNum=typeNum; + int max=0; + while(lastbt->nextLevel>=0) + { + lastTypeNum=lastbt->nextLevel; + lastbt=globalContainer->buildingsTypes.get(lastTypeNum); + if (max++>200) + { + printf("GameGUI: Error: nextLevelTypeNum architecture is broken.\n"); + assert(false); + break; + } + } + int exBatX=x+((lastbt->decLeft-type->decLeft)<<5); + int exBatY=y+((lastbt->decTop-type->decTop)<<5); + int exBatW=(lastbt->width)<<5; + int exBatH=(lastbt->height)<<5; + + globalContainer->gfx->drawRect(exBatX, exBatY, exBatW, exBatH, 255, 255, 255, 127); + } + + Uint32 visibleTeams = teams[localTeam]->me; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + if (((drawOptions & DRAW_HEALTH_FOOD_BAR) != 0) && (building->owner->sharedVisionOther & visibleTeams)) + { + // TODO : find better color for this + if (type->hpMax) + { + int maxWidth, actWidth, addDec; + float hpRatio=(float)building->hp/(float)type->hpMax; + if (type->width==1) + { + maxWidth=8; + actWidth=1+(int)(7.0f*hpRatio); + addDec=2; + } + else + { + maxWidth=16; + actWidth=1+(int)(15.0f*hpRatio); + addDec=7; + } + int decy=(type->height*32); + int healDecx=(type->width-(maxWidth>>3))*16+addDec; + + if (building->hp!=type->hpMax || !building->type->crossConnectMultiImage) + drawHealthBar(x+healDecx, y+decy-4, maxWidth, actWidth, hpRatio); + } + + if (building->maxUnitInside>0) + drawPointBar(x+type->width*32-4, y+1, BOTTOM_TO_TOP, building->maxUnitInside, (signed)building->unitsInside.size(), 255, 255, 255); + if (building->maxUnitWorking>0) + drawPointBar(x+type->width*16-((3*building->maxUnitWorking)>>1), y+1,LEFT_TO_RIGHT , building->maxUnitWorking, (signed)building->unitsWorking.size(), 0, 255, 255, 255, 255, 64, 0); + + if ((type->canFeedUnit) || (type->unitProductionTime)) + drawBuildingResourceBar(x+1, y+1, type, type->maxRessource[CORN], building->ressources[CORN], 255, 255, 120); + + if (type->maxBullets) + drawBuildingResourceBar(x+1, y+1, type, type->maxBullets, building->bullets, 200, 200, 200); + } + + if (drawOptions & DRAW_ACCESSIBILITY) + { + std::ostringstream oss; + oss << building->owner->teamNumber; + int accessW = globalContainer->littleFont->getStringWidth(oss.str().c_str()); + int accessH = globalContainer->littleFont->getStringHeight(oss.str().c_str()); + int accessX = x+(((type->width<<5)-accessW)>>1); + int accessY = y+(((type->height<<5)-accessH)>>1); + globalContainer->gfx->drawFilledRect(accessX-4, accessY, accessW+8, accessH, Color(0, 0, 0, 127)); + globalContainer->gfx->drawRect(accessX-4, accessY, accessW+8, accessH, Color(255, 255, 255, 127)); + globalContainer->gfx->drawString(accessX, accessY, globalContainer->littleFont, oss.str()); + } + + if(highlightBuildingType & (1<shortTypeNum)) + { + globalContainer->gfx->drawSprite(x + buildingSprite->getW(imgid)/2 - 16, y-36, globalContainer->gamegui, 36); + } +} + + +void Game::drawMapGroundBuildings(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, std::set *visibleBuildings, const BuildingGuiStateMap* buildingGuiState) +{ + Uint32 visibleTeams = teams[localTeam]->me; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + std::set drawnBuildings; + for (int y=top-1; y<=bot; y++) + for (int x=left-1; x<=right; x++) + { + Uint16 gid=map.getBuilding(x+viewportX, y+viewportY); + if (gid!=NOGBID) // Then this is a building + { + //globalContainer->gfx->drawRect(x<<5, y<<5, 32, 32, 255, 128, 0); + //globalContainer->gfx->drawRect(2+(x<<5), 2+(y<<5), 28, 28, 255, 128, 0); + + int id = Building::GIDtoID(gid); + int team = Building::GIDtoTeam(gid); + + Building *building=teams[team]->myBuildings[id]; + if(drawnBuildings.find(building)==drawnBuildings.end()) + { + assert(building); // if this fails, and unwanted garbage-UID is on the ground. + if (((drawOptions & DRAW_WHOLE_MAP) != 0) + || Building::GIDtoTeam(gid)==localTeam + || (building->seenByMask & visibleTeams) + || map.isFOWDiscovered(x+viewportX, y+viewportY, visibleTeams)) + { + int px,py; + const Sint32 dispX = buildingGuiState ? displayedPosX(*buildingGuiState, *building) : building->posX; + const Sint32 dispY = buildingGuiState ? displayedPosY(*buildingGuiState, *building) : building->posY; + map.mapCaseToDisplayable(dispX, dispY, &px, &py, viewportX, viewportY); + drawMapBuilding(px, py, gid, viewportX, viewportY, localTeam, drawOptions); + drawnBuildings.insert(building); + } + } + } + } + if(visibleBuildings) + *visibleBuildings = drawnBuildings; +} diff --git a/src/render/GameRenderInternal.h b/src/render/GameRenderInternal.h new file mode 100644 index 000000000..32155b768 --- /dev/null +++ b/src/render/GameRenderInternal.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#ifndef GAME_RENDER_INTERNAL_H +#define GAME_RENDER_INTERNAL_H + +// Shared internals for the src/render/ translation units. Not exposed beyond +// the rendering split-out files. + +// Set to 1 to render AI gradient / coordinate debug overlays. +#define DEBUG_RENDER_GRADIENTS 0 + +#endif // GAME_RENDER_INTERNAL_H diff --git a/src/render/GameRenderOverlay.cpp b/src/render/GameRenderOverlay.cpp new file mode 100644 index 000000000..59c74941a --- /dev/null +++ b/src/render/GameRenderOverlay.cpp @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "AICastor.h" +#include "AINicowar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "BuildingType.h" +#include "DatasetWriter.h" +#include "Game.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Order.h" +#include "Unit.h" +#include "UnitSkin.h" +#include "Integrity.h" +#include "Utilities.h" +#include "GameGUI.h" +#include "SDLCompat.h" + +#include "MapEdit.h" + +#include "Brush.h" +#include "DynamicClouds.h" +#include "Bullet.h" +#include "TextStream.h" +#include "FertilityCalculatorDialog.h" + +#include "ReplayWriter.h" + +#include "GameAnimations.h" + +#define BULLET_IMGID 0 + +// Bullets/explosions/death animations, fog of war, and overlay maps. Split from Game_render.cpp. + + +void Game::drawMapBulletsExplosionsDeathAnimations(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + // Let's paint the bullets and explosions + // TODO : optimise : test only possible sectors to show bullets. + + Sprite *bulletSprite = globalContainer->bullet; + // FIXME : have team in bullets to have the correct color + + Uint32 visibleTeams = teams[localTeam]->me; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + int mapPixW=(map.getW())<<5; + int mapPixH=(map.getH())<<5; + + for (int i=0; i<(map.getSectorW()*map.getSectorH()); i++) + { + Sector *s=map.getSector(i); + // bullets + for (std::list::iterator it=s->bullets.begin();it!=s->bullets.end();it++) + { + int x=(*it)->px-(viewportX<<5); + int y=(*it)->py-(viewportY<<5); + int balisticShift = 0; + + if (x<0) + x+=mapPixW; + if (y<0) + y+=mapPixH; + if ((*it)->ticksInitial) + { + float time = static_cast((*it)->ticksLeft); + float duration = static_cast((*it)->ticksInitial); + float speedX = static_cast((*it)->speedX); + float speedY = static_cast((*it)->speedY); + float K = static_cast(sqrt(speedX * speedX + speedY * speedY)); + balisticShift = static_cast(K * ((-1.0f * time * time) / duration + time)); + } + + //printf("px=(%d, %d) vp=(%d, %d)\n", (*it)->px, (*it)->py, viewportX, viewportY); + if ( (x<=sw) && (y<=sh) ) + { + globalContainer->gfx->drawSprite(x, y-balisticShift, bulletSprite, BULLET_IMGID); + globalContainer->gfx->drawSprite(x+(balisticShift/2), y, bulletSprite, BULLET_IMGID+1); + } + } + globalContainer->gfx->finishDrawingSprite(bulletSprite, 255); + // explosions + for (BulletExplosion *e : animations->getExplosions(i)) + { + if (map.isFOWDiscovered(e->x, e->y, visibleTeams)) + { + int x, y; + map.mapCaseToDisplayable(e->x, e->y, &x, &y, viewportX, viewportY); + int frame = globalContainer->bulletExplosion->getFrameCount() - e->ticksLeft - 1; + int decX = globalContainer->bulletExplosion->getW(frame)>>1; + int decY = globalContainer->bulletExplosion->getH(frame)>>1; + globalContainer->gfx->drawSprite(x+16-decX, y+16-decY, globalContainer->bulletExplosion, frame); + } + } + globalContainer->gfx->finishDrawingSprite(globalContainer->bulletExplosion, 255); + // death animations + for (UnitDeathAnimation *a : animations->getDeathAnimations(i)) + { + if (map.isFOWDiscovered(a->x, a->y, visibleTeams)) + { + int x, y; + map.mapCaseToDisplayable(a->x, a->y, &x, &y, viewportX, viewportY); + int frame = globalContainer->deathAnimation->getFrameCount() - a->ticksLeft - 1; + int decX = globalContainer->deathAnimation->getW(frame)>>1; + int decY = globalContainer->deathAnimation->getH(frame)>>1; + Team *team = a->team; + + globalContainer->deathAnimation->setBaseColor(team->color); + globalContainer->gfx->drawSprite(x+16-decX, y+16-decY-frame, globalContainer->deathAnimation, frame); + } + } + globalContainer->gfx->finishDrawingSprite(globalContainer->deathAnimation, 255); + } +} + +void Game::drawMapFogOfWar(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + if ((drawOptions & DRAW_WHOLE_MAP) == 0) + { + // we have decrease on because we do unalign lookup + for (int y=top-1; y<=bot; y++) + for (int x=left-1; x<=right; x++) + { + unsigned i0, i1, i2, i3; + + /*if ( (!map.isMapDiscovered(x+viewportX, y+viewportY, teams[localTeam]->me))) + { + globalContainer->gfx->drawFilledRect(x<<5, y<<5, 32, 32, 10, 10, 10); + } + else if ( (!map.isFOW(x+viewportX, y+viewportY, teams[localTeam]->me))) + { + globalContainer->gfx->drawSprite(x<<5, y<<5, globalContainer->terrainShader, 0); + }*/ + + Uint32 visibleTeams = teams[localTeam]->me; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + // first draw black + i0=!map.isMapDiscovered(x+viewportX+1, y+viewportY+1, visibleTeams) ? 1 : 0; + i1=!map.isMapDiscovered(x+viewportX, y+viewportY+1, visibleTeams) ? 1 : 0; + i2=!map.isMapDiscovered(x+viewportX+1, y+viewportY, visibleTeams) ? 1 : 0; + i3=!map.isMapDiscovered(x+viewportX, y+viewportY, visibleTeams) ? 1 : 0; + unsigned blackValue = i0 + (i1<<1) + (i2<<2) + (i3<<3); + if (blackValue==15) + globalContainer->gfx->drawFilledRect((x<<5)+16, (y<<5)+16, 32, 32, 0, 0, 0); + else if (blackValue) + globalContainer->gfx->drawSprite((x<<5)+16, (y<<5)+16, globalContainer->terrainBlack, blackValue); + + // then if it isn't full black, draw shade + if (blackValue!=15) + { + i0=!map.isFOWDiscovered(x+viewportX+1, y+viewportY+1, visibleTeams) ? 1 : 0; + i1=!map.isFOWDiscovered(x+viewportX, y+viewportY+1, visibleTeams) ? 1 : 0; + i2=!map.isFOWDiscovered(x+viewportX+1, y+viewportY, visibleTeams) ? 1 : 0; + i3=!map.isFOWDiscovered(x+viewportX, y+viewportY, visibleTeams) ? 1 : 0; + unsigned shadeValue = i0 + (i1<<1) + (i2<<2) + (i3<<3); + + if (shadeValue==15) + globalContainer->gfx->drawFilledRect((x<<5)+16, (y<<5)+16, 32, 32, 0, 0, 0, 127); + else if (shadeValue) + globalContainer->gfx->drawSprite((x<<5)+16, (y<<5)+16, globalContainer->terrainShader, shadeValue); + } + } + globalContainer->gfx->finishDrawingSprite(globalContainer->terrainBlack, 255); + globalContainer->gfx->finishDrawingSprite(globalContainer->terrainShader, 255); + } +} + +void Game::drawMapOverlayMaps(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + if(drawOptions & DRAW_OVERLAY) + { + OverlayArea* overlays; + if(gui) + overlays=&gui->overlay; + else if(edit) + overlays=&edit->overlay; + else assert(false); + int overlayMax=overlays->getMaximum(); + Color overlayColor; + switch(overlays->getOverlayType()) + { + case OverlayArea::Starving: overlayColor=Color(192, 0, 0); break; + case OverlayArea::Damage: overlayColor=Color(192, 0, 0); break; + case OverlayArea::Defence: overlayColor=Color(0, 0, 192); break; + case OverlayArea::Fertility: overlayColor=Color(0, 192, 128); break; + case OverlayArea::None: break; + } + ///Both width and height have +2 to cover half-squares arround the edge of the viewport + int width = (right - left) + 2; + int height = (bot - top) + 2; + + overlayAlphas.resize(width * height); + for (int y=0; yme; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + int rx=(x+viewportX-1+map.getW())%map.getW(); + int ry=(y+viewportY-1+map.getH())%map.getH(); + if(!edit && !map.isMapDiscovered(rx, ry, visibleTeams)) + continue; + if(overlays->getValue(rx, ry)) + { + const int value_c=overlays->getValue(rx, ry); + const int alpha_c=int(float(200)/float(overlayMax) * float(value_c)); + overlayAlphas[width * y + x] = alpha_c; + } + } + } + + ///This is to correct OpenGL's blending not beeing offset correctly to line up with the map tiles + if(globalContainer->gfx->getOptionFlags() & GraphicContext::USEGPU) + globalContainer->gfx->drawAlphaMap(overlayAlphas, width, height, -16, -16, 32, 32, overlayColor); + else + globalContainer->gfx->drawAlphaMap(overlayAlphas, width, height, -32, -32, 32, 32, overlayColor); + } +} diff --git a/src/render/GameRenderTerrain.cpp b/src/render/GameRenderTerrain.cpp new file mode 100644 index 000000000..3623d0fc9 --- /dev/null +++ b/src/render/GameRenderTerrain.cpp @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "AICastor.h" +#include "AINicowar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "BuildingType.h" +#include "DatasetWriter.h" +#include "Game.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Order.h" +#include "Unit.h" +#include "UnitSkin.h" +#include "Integrity.h" +#include "Utilities.h" +#include "GameGUI.h" +#include "SDLCompat.h" + +#include "MapEdit.h" + +#include "Brush.h" +#include "DynamicClouds.h" +#include "Bullet.h" +#include "TextStream.h" +#include "FertilityCalculatorDialog.h" + +#include "ReplayWriter.h" + +#include "GameRenderInternal.h" + +// Terrain, resource, and area rendering. Split from Game_render.cpp. + + +// TODO: WATER_TILE_SIZE is hardcoded to the dimensions of data/gfx/water and would +// silently break if that asset is ever resized. Could be replaced with +// terrainWater->getW(0) / getH(0), but that relies on the sprite being loaded with +// a valid frame 0, and nothing here or at the load site (GlobalContainer::load) +// validates that. If terrainWater fails to load or reports zero size, water tiles +// silently fail to render -- oceans and lakes look visibly broken but the game +// otherwise plays normally, with no log or crash to flag the asset problem. +// The right fix is asset validation at load time (covering ~30 sprites loaded the +// same way in GlobalContainer::load), not a per-render guard here. +void Game::drawMapWater(int sw, int sh, int viewportX, int viewportY, int time) +{ + // Tile size of the data/gfx/water sprite, in pixels. + static const int WATER_TILE_SIZE = 512; + int waterStartX = -(((viewportX<<5)+time/2) % WATER_TILE_SIZE); + int waterStartY = -((viewportY<<5) % WATER_TILE_SIZE); + for (int y=waterStartY; ygfx->drawSprite(x, y, globalContainer->terrainWater, 0); + globalContainer->gfx->finishDrawingSprite(globalContainer->terrainWater, 255); +} + +void Game::drawMapTerrain(int left, int top, int right, int bot, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + Uint32 visibleTeams = teams[localTeam]->me; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + // we draw the terrains, eventually with debug rects: + for (int y=top; y<=bot; y++) + for (int x=left; x<=right; x++) + if ( + map.isMapPartiallyDiscovered( + x+viewportX-1, + y+viewportY-1, + x+viewportX+1, + y+viewportY+1, + visibleTeams) || + ((drawOptions & DRAW_WHOLE_MAP) != 0)) + { + // draw terrain + int id=map.getTerrain(x+viewportX, y+viewportY); + Sprite *sprite; + if (id<272) + { + sprite=globalContainer->terrain; + } + else + { + assert(false); // Now there shouldn't be any more ressources on "terrain". + sprite=globalContainer->ressources; + id-=272; + } + if ((id < 256) || (id >= 256+16)) + globalContainer->gfx->drawSprite(x<<5, y<<5, sprite, id); + } + globalContainer->gfx->finishDrawingSprite(globalContainer->terrain, 255); +} + +void Game::drawMapRessources(int left, int top, int right, int bot, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + Uint32 visibleTeams = teams[localTeam]->me; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + for (int y=top; y<=bot; y++) + for (int x=left; x<=right; x++) + if ( + map.isMapPartiallyDiscovered( + x+viewportX-1, + y+viewportY-1, + x+viewportX+1, + y+viewportY+1, + visibleTeams) || + ((drawOptions & DRAW_WHOLE_MAP) != 0)) + { + const auto& r = map.getRessource(x+viewportX, y+viewportY); + if (r.type!=NO_RES_TYPE) + { + Sprite *sprite=globalContainer->ressources; + int type=r.type; + int amount=r.amount; + int variety=r.variety; + const RessourceType *rt=globalContainer->ressourcesTypes.get(type); + int imgid=rt->gfxId+(variety*rt->sizesCount)+amount; + if (!rt->eternal) + imgid--; + int dx=(sprite->getW(imgid)-32)>>1; + int dy=(sprite->getH(imgid)-32)>>1; + assert(type>=0); + assert(type<(int)globalContainer->ressourcesTypes.size()); + assert(amount>=0); + assert(amount<=rt->sizesCount); + assert(variety>=0); + assert(varietyvarietiesCount); + globalContainer->gfx->drawSprite((x<<5)-dx, (y<<5)-dy, sprite, imgid); + } + } + globalContainer->gfx->finishDrawingSprite(globalContainer->ressources, 255); +} + +void Game::drawMapDebugAreas(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + if (DEBUG_RENDER_GRADIENTS) + for (int y=top-1; y<=bot; y++) + for (int x=left-1; x<=right; x++) + { + //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, ((AICastor *)players[1]->ai->aiImplementation)->wheatCareMap[0][(x+viewportX)+(y+viewportY)*map.w]); + //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, ((AICastor *)players[1]->ai->aiImplementation)->notGrassMap[(x+viewportX)+(y+viewportY)*map.w]); +// globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, map.guardAreasGradient[0][1][(x+viewportX)+(y+viewportY)*map.w]); +// globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, ((Nicowar::AINicowar*)players[3]->ai->aiImplementation)->getGradientManager().getGradient(Nicowar::Gradient::VillageCenter, Nicowar::Gradient::Resource).getHeight(x+viewportX, y+viewportY)); + //((AICastor *)players[0].ai->aiImplementation)->wheatCareMap + } + //if (map.getForbidden(x+viewportX, y+viewportY)) + //{ + //if (!map.isFreeForGroundUnit(x+viewportX, y+viewportY, 1, 1)) + // globalContainer->gfx->drawRect(x<<5, y<<5, 32, 32, 255, 16, 32); + //globalContainer->gfx->drawRect(2+(x<<5), 2+(y<<5), 28, 28, 255, 16, 32); + //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, map.getGradient(1, 5, 0, x+viewportX, y+viewportY)); + //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, map.getGradient(0, STONE, 1, x+viewportX, y+viewportY)); + //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, map.forbiddenGradient[0][0][(x+viewportX)+(y+viewportY)*map.w]); + //globalContainer->gfx->drawString((x<<5), (y<<5)+16, globalContainer->littleFont, ((x+viewportX)&(map.getMaskW()))); + //globalContainer->gfx->drawString((x<<5)+16, (y<<5)+8, globalContainer->littleFont, ((y+viewportY)&(map.getMaskH()))); + //} + + // We draw debug area: + if (DEBUG_RENDER_GRADIENTS) + { + assert(teams[0]); + Building *b=selectedBuilding; + if (b) + for (int y=top-1; y<=bot; y++) + for (int x=left-1; x<=right; x++) + { + //if (map.warpDistMax(b->posX, b->posY, x+viewportX, y+viewportY)<16) + { + //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, "%d", map.getGradient(0, 6, 1, x+viewportX, y+viewportY)); + //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, "%d", map.warpDistMax(b->posX, b->posY, x+viewportX, y+viewportY)); + //int lx=(x+viewportX-b->posX+15+32)&31; + //int ly=(y+viewportY-b->posY+15+32)&31; + //globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, b->localGradient[1][lx+ly*32]); + if(b->globalGradient[1]) + globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, b->globalGradient[1][(x+viewportX) + (y+viewportY)*map.w]); + //globalContainer->gfx->drawString((x<<5), (y<<5)+10, globalContainer->littleFont, lx); + //globalContainer->gfx->drawString((x<<5)+16, (y<<5)+10, globalContainer->littleFont, ly); + //globalContainer->gfx->drawString((x<<5), (y<<5)+16, globalContainer->littleFont, "%d", x+viewportX); + //globalContainer->gfx->drawString((x<<5)+16, (y<<5)+16, globalContainer->littleFont, "%d", y+viewportY); + //globalContainer->gfx->drawString((x<<5), (y<<5)+16, globalContainer->littleFont, "%d", x+viewportX-b->posX+16); + //globalContainer->gfx->drawString((x<<5)+16, (y<<5)+16, globalContainer->littleFont, "%d", y+viewportY-b->posY+16); + } + } + } + + // We draw debug area: + if (DEBUG_RENDER_GRADIENTS) + if (selectedUnit && selectedUnit->verbose) + { + //assert(teams[0]); + Building *b=selectedUnit->attachedBuilding; + //b=teams[0]->myBuildings[21]; + //if (teams[0]->virtualBuildings.size()) + // b=*teams[0]->virtualBuildings.begin(); + if (b && b->localRessources[1]) + for (int y=top-1; y<=bot; y++) + for (int x=left-1; x<=right; x++) + if (map.warpDistMax(b->posX, b->posY, x+viewportX, y+viewportY)<16) + { + int lx=(x+viewportX-b->posX+15)&31; + int ly=(y+viewportY-b->posY+15)&31; + globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, b->localRessources[1][lx+ly*32]); + } + } + + // We draw debug area: + //if (selectedUnit && selectedUnit->verbose) + if (selectedBuilding && selectedBuilding->verbose) + { + //Building *b=NULL; + Building *b=selectedBuilding; + //Building *b=selectedUnit->attachedBuilding; + + //assert(teams[0]); + //Building *b=teams[0]->myBuildings[0]; + //if (teams[0]->virtualBuildings.size()) + // b=*teams[0]->virtualBuildings.begin(); + + int w=map.getW(); + if (b) + for (int y=top-1; y<=bot; y++) + for (int x=left-1; x<=right; x++) + { + if (b->verbose==1 || b->verbose==2) + { + if (b->globalGradient[b->verbose&1]) + globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, + b->globalGradient[b->verbose&1][((x+viewportX)&(map.getMaskW()))+((y+viewportY)&(map.getMaskH()))*w]); + } + else if ((b->verbose==3 || b->verbose==4) && map.isInLocalGradient(x+viewportX, y+viewportY, b->posX, b->posY)) + { + int lx=(x+viewportX-b->posX+15)&31; + int ly=(y+viewportY-b->posY+15)&31; + if (!b->dirtyLocalGradient[b->verbose&1]) + globalContainer->gfx->drawString((x<<5), (y<<5), globalContainer->littleFont, b->localGradient[b->verbose&1][lx+ly*32]); + } + + globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 192, 192, 192)); + globalContainer->gfx->drawString((x<<5), (y<<5)+16, globalContainer->littleFont, (x+viewportX+map.getW())&(map.getMaskW())); + globalContainer->gfx->drawString((x<<5)+16, (y<<5)+8, globalContainer->littleFont, (y+viewportY+map.getH())&(map.getMaskH())); + globalContainer->littleFont->popStyle(); + } + + } +} + +/** + * Draws the visible (viewport) part of the given map + */ +void Game::drawMapAreas(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + static int areaAnimationTick = 0; + + if ((drawOptions & DRAW_AREA) != 0 && (!globalContainer->replaying || globalContainer->replayShowAreas)) + { + drawMapArea(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, &map, &Map::isForbiddenLocal, areaAnimationTick, ForbiddenArea); + drawMapArea(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, &map, &Map::isGuardAreaLocal, areaAnimationTick, GuardArea); + drawMapArea(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, &map, &Map::isClearAreaLocal, areaAnimationTick, ClearingArea); + for (int y=top; ygfx->drawLine((x<<5), 8+(y<<5), 32+(x<<5), 8+(y<<5), 128, 64, 0); + globalContainer->gfx->drawLine((x<<5), 16+(y<<5), 32+(x<<5), 16+(y<<5), 128, 64, 0); + globalContainer->gfx->drawLine((x<<5), 24+(y<<5), 32+(x<<5), 24+(y<<5), 128, 64, 0); +// globalContainer->gfx->drawLine((x<<5), 32+(y<<5), 32+(x<<5), 32+(y<<5), 128, 64, 0); + + if (map.canRessourcesGrow(x+viewportX, y+viewportY-1)) + globalContainer->gfx->drawHorzLine((x<<5), (y<<5), 32, 255, 128, 0); + if (map.canRessourcesGrow(x+viewportX, y+viewportY+1)) + globalContainer->gfx->drawHorzLine((x<<5), 32+(y<<5), 32, 255, 128, 0); + + if (map.canRessourcesGrow(x+viewportX-1, y+viewportY)) + globalContainer->gfx->drawVertLine((x<<5), (y<<5), 32, 255, 128, 0); + if (map.canRessourcesGrow(x+viewportX+1, y+viewportY)) + globalContainer->gfx->drawVertLine(32+(x<<5), (y<<5), 32, 255, 128, 0); + } + } + } + areaAnimationTick++; + } +} + +/** + * Draws the visible (viewport) part of the given map + */ +void Game::drawMapArea(int left, int top, int right, int bot, int sw, + int sh, int viewportX, int viewportY, int localTeam, + Uint32 drawOptions, Map * map, bool (Map::*mapIs)(int, int) const, int areaAnimationTick, + AreaType areaType) +{ + Sprite* sprite; + GAGCore::Color c; + switch (areaType) + { + case ClearingArea: sprite = globalContainer->areaClearing; c = GAGCore::Color(255,255,0); break; + case ForbiddenArea: sprite = globalContainer->areaForbidden; c = GAGCore::Color(255,0,0); break; + case GuardArea: sprite = globalContainer->areaGuard; c = GAGCore::Color(0,0,255); break; + default: assert(false); + } + for (int y=top; y*mapIs)(x+viewportX, y+viewportY)) + { + int randId = (x+viewportX) * 7919 + (y+viewportY) * 17; + int frame = ((randId + areaAnimationTick) % (sprite->getFrameCount() * 2)) / 2; + globalContainer->gfx->drawSprite((x<<5), (y<<5), sprite, frame); + + if (!(map->*mapIs)(x+viewportX, y+viewportY-1)) + globalContainer->gfx->drawHorzLine((x<<5), (y<<5), 32, c); + if (!(map->*mapIs)(x+viewportX, y+viewportY+1)) + globalContainer->gfx->drawHorzLine((x<<5), 32+(y<<5), 32, c); + + if (!(map->*mapIs)(x+viewportX-1, y+viewportY)) + globalContainer->gfx->drawVertLine((x<<5), (y<<5), 32, c); + if (!(map->*mapIs)(x+viewportX+1, y+viewportY)) + globalContainer->gfx->drawVertLine(32+(x<<5), (y<<5), 32, c); + } + } + } + globalContainer->gfx->finishDrawingSprite(sprite, 255); +} + +void Game::drawMapScriptAreas(int left, int top, int right, int bot, int viewportX, int viewportY) +{ + for (int y=top; ygfx->drawString((x<<5)+(n%3)*10, (y<<5)+(n/3)*10, globalContainer->littleFont, str.str()); + + globalContainer->gfx->drawHorzLine((x<<5), (y<<5), 32, 64, 255, 255); + globalContainer->gfx->drawHorzLine((x<<5), 32+(y<<5), 32, 64, 255, 255); + + globalContainer->gfx->drawVertLine((x<<5), (y<<5), 32, 64, 255, 255); + globalContainer->gfx->drawVertLine(32+(x<<5), (y<<5), 32, 64, 255, 255); + } + } + } +} diff --git a/src/render/GameRenderUnits.cpp b/src/render/GameRenderUnits.cpp new file mode 100644 index 000000000..c32a46ac6 --- /dev/null +++ b/src/render/GameRenderUnits.cpp @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include +#include + +#include "AICastor.h" +#include "AINicowar.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "BuildingType.h" +#include "DatasetWriter.h" +#include "Game.h" +#include "GameUtilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Order.h" +#include "Unit.h" +#include "UnitSkin.h" +#include "Integrity.h" +#include "Utilities.h" +#include "GameGUI.h" +#include "SDLCompat.h" + +#include "MapEdit.h" + +#include "Brush.h" +#include "DynamicClouds.h" +#include "Bullet.h" +#include "TextStream.h" +#include "UnitSkin.h" +#include "FertilityCalculatorDialog.h" + +#include "ReplayWriter.h" + +// Unit rendering. Split from Game_render.cpp. + + +void Game::drawUnit(int x, int y, Uint16 gid, int viewportX, int viewportY, int screenW, int screenH, int localTeam, Uint32 drawOptions) +{ + int id=Unit::GIDtoID(gid); + int team=Unit::GIDtoTeam(gid); + Unit *unit=teams[team]->myUnits[id]; + assert(unit); + if (!unit) + { + globalContainer->gfx->drawRect((x<<5)+1, (y<<5)+1, 30, 30, 255, 255, 0); + return; + } + int dx=unit->dx; + int dy=unit->dy; + + Uint32 visibleTeams = teams[localTeam]->me; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + if ((drawOptions & DRAW_WHOLE_MAP) == 0) + if ((!map.isFOWDiscovered(x+viewportX, y+viewportY, visibleTeams))&&(!map.isFOWDiscovered(x+viewportX-dx, y+viewportY-dy, visibleTeams))) + return; + + int imgid; + assert(unit->action>=0); + assert(unit->actiontypeNum]; + imgid=skin.startImage[unit->action]; + int px, py; + map.mapCaseToDisplayable(unit->posX, unit->posY, &px, &py, viewportX, viewportY); + int deltaLeft=255-unit->delta; + if (unit->actiondx*deltaLeft)>>3; + py-=(unit->dy*deltaLeft)>>3; + } + else + { + // TODO : if looks ugly, do something intelligent here + } + + int dir=unit->direction; + int delta=unit->delta; + assert(dir>=0); + assert(dir<9); + assert(delta>=0); + assert(delta<256); + if (dir==8) + { + imgid+=8*(delta>>5); + } + else + { + imgid+=8*dir; + imgid+=(delta>>5); + } + + // draw unit + Sprite *unitSprite = skin.sprite; + unitSprite->setBaseColor(teams[team]->color); + int decX = (unitSprite->getW(imgid)-32)>>1; + int decY = (unitSprite->getH(imgid)-32)>>1; + globalContainer->gfx->drawSprite(px-decX, py-decY, unitSprite, imgid); + + // draw selection + if (unit==selectedUnit) + { + globalContainer->gfx->drawCircle(px+16, py+16, 16, 0, 0, 255); + if (unit->owner->teamNumber == localTeam) + globalContainer->gfx->drawCircle(px+16, py+16, 16, 0, 0, 190); + else if ((teams[localTeam]->allies) & (unit->owner->me)) + globalContainer->gfx->drawCircle(px+16, py+16, 16, 255, 196, 0); + else + globalContainer->gfx->drawCircle(px+16, py+16, 16, 190, 0, 0); + } + + // draw xp animation + if (unit->levelUpAnimation) + { + std::ostringstream oss; + oss << unit->experienceLevel; + globalContainer->standardFont->pushStyle(Font::Style(Font::STYLE_NORMAL, 242, 131, 14)); + globalContainer->gfx->drawString(px + 16 - (globalContainer->standardFont->getStringWidth(oss.str().c_str()) >> 1), py - 16 - 2 *( LEVEL_UP_ANIMATION_FRAME_COUNT - unit->levelUpAnimation), globalContainer->standardFont, oss.str(), 0, (255*unit->levelUpAnimation) / LEVEL_UP_ANIMATION_FRAME_COUNT); + globalContainer->standardFont->popStyle(); + } + + // draw magic animation + if (unit->magicActionAnimation) + { + if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) + { + globalContainer->gfx->drawSprite(px+16-(globalContainer->magiceffect->getW(0)>>1), py+16-(globalContainer->magiceffect->getH(0)>>1), globalContainer->magiceffect, 0); + } + else + { + unsigned alpha = (unit->magicActionAnimation * 255) / MAGIC_ACTION_ANIMATION_FRAME_COUNT; + if (globalContainer->gfx->canDrawStretchedSprite()) + { + int stretchW = ((MAGIC_ACTION_ANIMATION_FRAME_COUNT - unit->magicActionAnimation) * globalContainer->magiceffect->getW(0)) / (MAGIC_ACTION_ANIMATION_FRAME_COUNT * 2); + int stretchH = ((MAGIC_ACTION_ANIMATION_FRAME_COUNT - unit->magicActionAnimation) * globalContainer->magiceffect->getH(0)) / (MAGIC_ACTION_ANIMATION_FRAME_COUNT * 2); + globalContainer->gfx->drawSprite(px+16-stretchW, py+16-stretchH, stretchW*2, stretchH*2, globalContainer->magiceffect, 0, alpha); + } + else + { + globalContainer->gfx->drawSprite(px+16-(globalContainer->magiceffect->getW(0)>>1), py+16-(globalContainer->magiceffect->getH(0)>>1), globalContainer->magiceffect, 0, alpha); + } + } + } + + if ((pxmouseX)&&(pymouseY)&&(((drawOptions & DRAW_WHOLE_MAP) != 0) ||(map.isFOWDiscovered(x+viewportX, y+viewportY, visibleTeams))||(Unit::GIDtoTeam(gid)==localTeam))) + mouseUnit=unit; + + if ((drawOptions & DRAW_HEALTH_FOOD_BAR) != 0 ) + { + drawPointBar(px+1, py+25, LEFT_TO_RIGHT, 10, (unit->hungry*10)/Unit::HUNGRY_MAX, 80, 179, 223); + + float hpRatio=(float)unit->hp/(float)unit->performance[HP]; + drawHealthBar(px+1, py+25+3, 10, 1+(int)(9*hpRatio), hpRatio); + + if ((unit->performance[HARVEST]) && (unit->carriedRessource>=0)) + globalContainer->gfx->drawSprite(px+24, py, globalContainer->ressourceMini, unit->carriedRessource); + globalContainer->gfx->finishDrawingSprite(globalContainer->ressourceMini, 255); + } + + if (drawOptions & DRAW_ACCESSIBILITY) + { + std::ostringstream oss; + oss << unit->owner->teamNumber; + int accessW = globalContainer->littleFont->getStringWidth(oss.str().c_str()); + int accessH = globalContainer->littleFont->getStringHeight(oss.str().c_str()); + int accessX = px+((32-accessW)>>1); + int accessY = py+((32-accessH)>>1); + globalContainer->gfx->drawFilledRect(accessX-4, accessY, accessW+8, accessH, Color(0, 0, 0, 127)); + globalContainer->gfx->drawRect(accessX-4, accessY, accessW+8, accessH, Color(255, 255, 255, 127)); + globalContainer->gfx->drawString(accessX, accessY, globalContainer->littleFont, oss.str()); + } + if(highlightUnitType & (1<typeNum)) + { + globalContainer->gfx->drawSprite(px, py-decY-32, globalContainer->gamegui, 36); + } +} + + +void Game::drawMapGroundUnits(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + //Reset the mouse unit to NULL, as this time arround there may not be a unit + //under the mouse pointer + mouseUnit=NULL; + for (int y=top-1; y<=bot; y++) + for (int x=left-1; x<=right; x++) + { + Uint16 gid=map.getGroundUnit(x+viewportX, y+viewportY); + if (gid!=NOGUID) + drawUnit(x, y, gid, viewportX, viewportY, (sw>>5), (sh>>5), localTeam, drawOptions); + } +} + + +void Game::drawMapAirUnits(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + for (int y=top-1; y<=bot; y++) + for (int x=left-1; x<=right; x++) + { + Uint16 gid=map.getAirUnit(x+viewportX, y+viewportY); + if (gid!=NOGUID) + drawUnit(x, y, gid, viewportX, viewportY, (sw>>5), (sh>>5), localTeam, drawOptions); + } +} + + +void Game::drawUnitPathLines(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions) +{ + if ((drawOptions & DRAW_PATH_LINE) != 0) + { + for(int i=0; imyUnits[i]; + if (unit) + { + drawUnitPathLine(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, unit); + } + } + } + if(selectedUnit != NULL) + { + drawUnitPathLine(left, top, right, bot, sw, sh, viewportX, viewportY, localTeam, drawOptions, selectedUnit); + } +} + + + +void Game::drawUnitPathLine(int left, int top, int right, int bot, int sw, int sh, int viewportX, int viewportY, int localTeam, Uint32 drawOptions, Unit* unit) +{ + Uint32 visibleTeams = teams[localTeam]->me; + if (globalContainer->replaying) visibleTeams = globalContainer->replayVisibleTeams; + + if(unit->owner->sharedVisionOther & visibleTeams) + { + if (unit->validTarget) + { + if(isOnScreen(left,top,right,bot,viewportX,viewportY,unit->posX,unit->posY) || isOnScreen(left,top,right,bot,viewportX,viewportY,unit->targetX,unit->targetY)) + { + int px, py; + map.mapCaseToDisplayableVector(unit->posX, unit->posY, &px, &py, viewportX, viewportY, sw, sh); + int deltaLeft=255-unit->delta; + if (unit->actiondx*deltaLeft)>>3; + py-=(unit->dy*deltaLeft)>>3; + } + + + int lsx, lsy, ldx, ldy; + map.mapCaseToDisplayableVector(unit->targetX, unit->targetY, &ldx, &ldy, viewportX, viewportY, sw, sh); + lsx=px+16; + lsy=py+16; + if (globalContainer->settings.optionFlags & GlobalContainer::OPTION_LOW_SPEED_GFX) + globalContainer->gfx->drawLine(lsx, lsy, ldx+16, ldy+16, 250, 250, 250); + else + globalContainer->gfx->drawLine(lsx, lsy, ldx+16, ldy+16, 250, 250, 250, 128); + } + } + } +} + + + +void Game::drawUnitOffScreen(int sx, int sy, int sw, int sh, int viewportX, int viewportY, Unit* unit, Uint32 drawOptions) +{ + // Get the direction to the unit + int px, py; + map.mapCaseToDisplayableVector(unit->posX, unit->posY, &px, &py, viewportX, viewportY, sw, sh); + int deltaLeft=255-unit->delta; + if (unit->actiondx*deltaLeft)>>3; + py-=(unit->dy*deltaLeft)>>3; + } + + // To get the center of the unit + px+=16; + py+=16; + + // Place the internal box dimensions + int i_sx = sx + 20; + int i_sy = sy + 20; + int i_sw = sw - 40; + int i_sh = sh - 40; + + // The units draw position releative to the center of the internal square + int rel_cx = px - i_sx - i_sw/2; + int rel_cy = py - i_sy - i_sh/2; + if(rel_cx == 0) + rel_cx = 1; + if(rel_cy == 0) + rel_cy = 1; + + //globalContainer->gfx->drawLine(sx + sw/2, sy + sh/2, px, py, Color::white); + + // Decide which edge of the screen the box is on, and compute its center cordinates + int bx = 0; + int by = 0; + float slope = float(rel_cy) / float(rel_cx); + float angle = atan2f(float(rel_cy), float(rel_cx)); + float screen=float(i_sh) / float(i_sw); + if(rel_cx > 0 && std::abs(slope) <= std::abs(screen)) + { + bx = i_sx + i_sw; + by = i_sy + (i_sh/2) + int(slope * float(i_sw/2)); + } + else if(rel_cx < 0 && std::abs(slope) <= std::abs(screen)) + { + bx = i_sx; + by = i_sy + (i_sh/2) - int(slope * float(i_sw/2)); + } + else if(rel_cy > 0 && std::abs(slope) >= std::abs(screen)) + { + bx = i_sx + (i_sw/2) + int(float(i_sh/2) / slope); + by = i_sy + i_sh; + } + else if(rel_cy < 0 && std::abs(slope) >= std::abs(screen)) + { + bx = i_sx + (i_sw/2) - int(float(i_sh/2) / slope); + by = i_sy; + } + + bx -= 20; + by -= 20; + + // draw unit's image + int imgid; + UnitType *ut=unit->race->getUnitType(unit->typeNum, 0); + assert(unit->action>=0); + + assert(unit->actionstartImage[unit->action]; + + int dir=unit->direction; + int delta=unit->delta; + assert(dir>=0); + assert(dir<9); + assert(delta>=0); + assert(delta<256); + if (dir==8) + { + imgid+=8*(delta>>5); + } + else + { + imgid+=8*dir; + imgid+=(delta>>5); + } + + Sprite *unitSprite=globalContainer->units; + unitSprite->setBaseColor(unit->owner->color); + int decX = (32-unitSprite->getW(imgid))>>1; + int decY = (32-unitSprite->getH(imgid))>>1; + + // Draw the code + //globalContainer->gfx->drawFilledRect(bx, by, 40, 40, 0,0,0,128); + //globalContainer->gfx->drawCircle(bx+20, by+20, 20, Color::white); + globalContainer->gfx->drawLine( + bx+20+cosf(angle)*5, + by+20+sinf(angle)*5, + bx+20+cosf(angle)*17, + by+20+sinf(angle)*17, + Color::white); + globalContainer->gfx->drawLine( + bx+20+cosf(angle)*17, + by+20+sinf(angle)*17, + bx+20+cosf(angle-M_PI/6)*10, + by+20+sinf(angle-M_PI/6)*10, + Color::white); + globalContainer->gfx->drawLine( + bx+20+cosf(angle)*17, + by+20+sinf(angle)*17, + bx+20+cosf(angle+M_PI/6)*10, + by+20+sinf(angle+M_PI/6)*10, + Color::white); + globalContainer->gfx->drawSprite(bx+decX+4, by+decY+4, unitSprite, imgid, 160); +} diff --git a/src/render/MapView.cpp b/src/render/MapView.cpp new file mode 100644 index 000000000..fd6496284 --- /dev/null +++ b/src/render/MapView.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Map.h" +#include "Game.h" +#include "Utilities.h" +#include "GlobalContainer.h" +#include "LogFileManager.h" +#include "Unit.h" +#include "MapInternal.h" + +#include +#include +#include +#include + + +// Viewport coordinate conversions + +void Map::mapCaseToDisplayable(int mx, int my, int *px, int *py, int viewportX, int viewportY) const +{ + int x = (mx - viewportX + w) & wMask; + int y = (my - viewportY + h) & hMask; + if (x > (w - HALF_TILE_PX)) + x-=w; + if (y > (h - HALF_TILE_PX)) + y-=h; + *px=x< (w/2 + (screenW/(TILE_PX*2)))) + x-=w; + if (y > (h/2 + (screenH/(TILE_PX*2)))) + y-=h; + *px=x<>TILE_PIXEL_SHIFT)+viewportX)&getMaskW(); + *py=((my>>TILE_PIXEL_SHIFT)+viewportY)&getMaskH(); +} + +void Map::displayToMapCaseUnaligned(int mx, int my, int *px, int *py, int viewportX, int viewportY) const +{ + *px=(((mx+HALF_TILE_PX)>>TILE_PIXEL_SHIFT)+viewportX)&getMaskW(); + *py=(((my+HALF_TILE_PX)>>TILE_PIXEL_SHIFT)+viewportY)&getMaskH(); +} + +void Map::cursorToBuildingPos(int mx, int my, int buildingWidth, int buildingHeight, int *px, int *py, int viewportX, int viewportY) const +{ + int tempX, tempY; + if (buildingWidth&0x1) + tempX=((mx)>>TILE_PIXEL_SHIFT)+viewportX; + else + tempX=((mx+HALF_TILE_PX)>>TILE_PIXEL_SHIFT)+viewportX; + + if (buildingHeight&0x1) + tempY=((my)>>TILE_PIXEL_SHIFT)+viewportY; + else + tempY=((my+HALF_TILE_PX)>>TILE_PIXEL_SHIFT)+viewportY; + + *px=tempX&getMaskW(); + *py=tempY&getMaskH(); +} + +void Map::buildingPosToCursor(int px, int py, int buildingWidth, int buildingHeight, int *mx, int *my, int viewportX, int viewportY) const +{ + mapCaseToDisplayable(px, py, mx, my, viewportX, viewportY); + *mx+=buildingWidth*HALF_TILE_PX; + *my+=buildingHeight*HALF_TILE_PX; +} + + diff --git a/src/Minimap.cpp b/src/render/Minimap.cpp similarity index 88% rename from src/Minimap.cpp rename to src/render/Minimap.cpp index e6416fe7a..92f9d1e91 100644 --- a/src/Minimap.cpp +++ b/src/render/Minimap.cpp @@ -1,28 +1,12 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "Minimap.h" +#include "EngineTiming.h" +#include "FixedPoint.h" #include "Ressource.h" #include "RessourceType.h" -#include "RessourcesTypes.h" #include "GlobalContainer.h" #include "Unit.h" #include @@ -122,7 +106,7 @@ void Minimap::draw(int localteam, int viewportX, int viewportY, int viewportW, i else { ///Render 1/25th of the rows at a time - const int rows_to_render = std::max(1, mini_h/25); + const int rows_to_render = std::max(1, mini_h/MINIMAP_REFRESH_TICKS); refreshPixelRows(update_row, (update_row + rows_to_render) % (mini_h), localteam); update_row += rows_to_render; @@ -304,9 +288,9 @@ void Minimap::computeColors(int row, int localTeam) // Variables for traversing each map square within a minimap square. // Using ?.16 fixed-point representation (gives a 2x speedup): - const int dMx = ((game->map.getW())<<16) / (mini_w); - const int dMy = ((game->map.getH())<<16) / (mini_h); - const int decSPX=offset_x<<16, decSPY=offset_y<<16; + const int dMx = ((game->map.getW())<map.getH())<teams[localTeam]->me; @@ -320,11 +304,11 @@ void Minimap::computeColors(int row, int localTeam) int UnitOrBuildingIndex = -1; // compute - for (int minidyFP=dMy*dy+decSPY; minidyFP<=(dMy*(dy+1))+decSPY; minidyFP+=(1<<16)) { // Fixed-point numbers - int minidy = minidyFP>>16; - for (int minidxFP=dMx*dx+decSPX; minidxFP<=(dMx*(dx+1))+decSPX; minidxFP+=(1<<16)) // Fixed-point numbers + for (int minidyFP=dMy*dy+decSPY; minidyFP<=(dMy*(dy+1))+decSPY; minidyFP+=FIXED_POINT_ONE) { // Fixed-point numbers + int minidy = minidyFP>>FIXED_POINT_SHIFT_16; + for (int minidxFP=dMx*dx+decSPX; minidxFP<=(dMx*(dx+1))+decSPX; minidxFP+=FIXED_POINT_ONE) // Fixed-point numbers { - int minidx = minidxFP>>16; + int minidx = minidxFP>>FIXED_POINT_SHIFT_16; bool seenUnderFOW = false; Uint16 gid=game->map.getAirUnit(minidx, minidy); @@ -419,7 +403,7 @@ void Minimap::computeColors(int row, int localTeam) } for (int i=0; iressourcesTypes.get(i); + const RessourceType *rt = globalContainer->ressourcesTypes.get(i); lr += pcol[i+3]*(rt->minimapR); lg += pcol[i+3]*(rt->minimapG); lb += pcol[i+3]*(rt->minimapB); diff --git a/src/Minimap.h b/src/render/Minimap.h similarity index 69% rename from src/Minimap.h rename to src/render/Minimap.h index 76c667020..73f912f38 100644 --- a/src/Minimap.h +++ b/src/render/Minimap.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef Minimap_h -#define Minimap_h +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once #include "GraphicContext.h" #include "Game.h" @@ -106,4 +88,3 @@ class Minimap }; -#endif diff --git a/src/render/UnitSkin.cpp b/src/render/UnitSkin.cpp new file mode 100644 index 000000000..b6eeeac9e --- /dev/null +++ b/src/render/UnitSkin.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "UnitSkin.h" +#include + +UnitSkin g_unitSkins[NB_UNIT_TYPE]; + +namespace +{ + // Sprite-atlas frame offsets for each unit type, indexed by Abilities enum + // values STOP_WALK..ATTACK_SPEED (which are 0..NB_MOVE-1 in UnitConsts.h). + // Replaces the legacy data/unitsSkins.txt; offsets are a property of the + // data/gfx/unit sprite sheet, not designer-tunable parameters. + constexpr Uint32 SKIN_OFFSETS[NB_UNIT_TYPE][NB_MOVE] = { + // STOP_WALK, STOP_SWIM, STOP_FLY, WALK, SWIM, FLY, BUILD, HARVEST, ATTACK_SPEED + /* WORKER */ { 64, 128, 0, 64, 128, 0, 192, 192, 0 }, + /* EXPLORER */ { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* WARRIOR */ { 256, 320, 0, 256, 320, 0, 0, 0, 384 }, + }; +} + +void initUnitSkins() +{ + GAGCore::Sprite *sprite = GAGCore::Toolkit::getSprite("data/gfx/unit"); + for (int type = 0; type < NB_UNIT_TYPE; ++type) + { + g_unitSkins[type].sprite = sprite; + for (int move = 0; move < NB_MOVE; ++move) + g_unitSkins[type].startImage[move] = SKIN_OFFSETS[type][move]; + } +} diff --git a/src/render/UnitSkin.h b/src/render/UnitSkin.h new file mode 100644 index 000000000..18810eba6 --- /dev/null +++ b/src/render/UnitSkin.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include +#include "UnitConsts.h" + +namespace GAGCore +{ + class Sprite; +} + +struct UnitSkin +{ + GAGCore::Sprite *sprite; + Uint32 startImage[NB_MOVE]; +}; + +// Per-unit-type skin table, indexed by WORKER/EXPLORER/WARRIOR. +// Sprite pointer is null until initUnitSkins() runs (skipped in headless mode). +extern UnitSkin g_unitSkins[NB_UNIT_TYPE]; + +// Loads the shared unit sprite and fills g_unitSkins. Call once at startup. +void initUnitSkins(); diff --git a/src/team/Team.cpp b/src/team/Team.cpp new file mode 100644 index 000000000..a262b6d49 --- /dev/null +++ b/src/team/Team.cpp @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include "BuildingType.h" +#include "EngineTiming.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Marshaling.h" +#include "NetConsts.h" +#include "Team.h" +#include "Unit.h" +#include "Utilities.h" +#include "Player.h" +#include "Integrity.h" + +Team::Team(Game *game) +:BaseTeam() +{ + assert(game); + this->game=game; + this->map=&game->map; + init(); +} + + + + +Team::Team(GAGCore::InputStream *stream, Game *game, Sint32 versionMinor) +:BaseTeam() +{ + assert(game); + this->game=game; + this->map=&game->map; + init(); + bool success = load(stream, &(globalContainer->buildingsTypes), versionMinor); + assert(success); +} + + + + +Team::~Team() +{ + if (!disableRecursiveDestruction) + { + clearMem(); + delete [] myUnits; + delete [] myBuildings; + } +} + + + + +void Team::init(void) +{ + myUnits = new Unit*[Unit::MAX_COUNT]; + myBuildings = new Building*[Building::MAX_COUNT]; + for (int i=0; iteamNumber; + numberOfPlayer=initial->numberOfPlayer; + playersMask=initial->playersMask; + + setCorrectColor(initial->color); + setCorrectMasks(); +} + + + + +bool Team::integrity(void) +{ + checkInvariant(noMoreBuildingSitesCountdown<=noMoreBuildingSitesCountdownMax); + for (int id=0; idintegrity()); + } + for (std::list::iterator it=virtualBuildings.begin(); it!=virtualBuildings.end(); ++it) + { + checkInvariant(*it); + checkInvariant((*it)->type); + checkInvariant((*it)->type->isVirtual); + checkInvariant(myBuildings[Building::GIDtoID((*it)->gid)]); + } + for (std::list::iterator it=clearingFlags.begin(); it!=clearingFlags.end(); ++it) + { + checkInvariant(*it); + checkInvariant((*it)->type); + checkInvariant((*it)->type->isVirtual); + checkInvariant(myBuildings[Building::GIDtoID((*it)->gid)]); + } + + for (int i=0; iintegrity()); + } + return true; +} + + + + +void Team::setCorrectMasks(void) +{ + me=teamNumberToMask(teamNumber); + allies=me; + enemies=~allies; + sharedVisionExchange=me; + sharedVisionFood=me; + sharedVisionOther=me; +} + + + + +void Team::setCorrectColor(const GAGCore::Color& color) +{ + this->color = color; +} + +void Team::setCorrectColor(float value) +{ + float r, g, b; + Utilities::HSVtoRGB(&r, &g, &b, value, TEAM_COLOR_SATURATION, TEAM_COLOR_VALUE); + color = Color((Uint8)(COLOR_CHANNEL_MAX*r), (Uint8)(COLOR_CHANNEL_MAX*g), (Uint8)(COLOR_CHANNEL_MAX*b)); +} + + + + +void Team::update() +{ + for (int i=0; iupdate(); +} + + + + +bool Team::openMarket() +{ + int numberOfTeam=game->mapHeader.getNumberOfTeams(); + for (int ti=0; titeams[ti]->sharedVisionExchange & me)) + return true; + return false; +} + + + + +void Team::checkControllingPlayers(void) +{ + if (!hasWon) + { + bool stillInControl = false; + for (int i=0; igameHeader.getNumberOfPlayers(); i++) + { + if ((game->players[i]->teamNumber == teamNumber) && + game->players[i]->type != Player::P_LOST_DROPPING && + game->players[i]->type != Player::P_LOST_FINAL) + stillInControl = true; + } + isAlive = isAlive && stillInControl; + } +} + + + +void Team::pushGameEvent(GameEvent event) +{ + ///Ignore events when the cooldown is above 0 + GameEventType eventType = event.getEventType(); + if(eventCooldownTimers[eventType] == 0) + { + events.push(std::move(event)); + eventCooldownTimers[eventType] = GAME_EVENT_COOLDOWN_TICKS; + } +} + + + +std::optional Team::getEvent() +{ + if(events.empty()) + return std::nullopt; + + GameEvent event = std::move(events.front()); + events.pop(); + return event; +} + + + +void Team::updateEvents() +{ + for(int i=0; i0) + eventCooldownTimers[i]-=1; + } + + + while(!events.empty()) + { + const GameEvent& event = events.front(); + if((game->stepCounter - event.getStep()) > GAME_EVENT_MAX_AGE_TICKS) + { + events.pop(); + } + else + { + break; + } + } +} + + +bool Team::wasRecentEvent(GameEventType type) +{ + // NOTE: structurally coupled to pushGameEvent — strict-equal returns true + // only on the exact tick the event fired (updateEvents decrements next). + // See bug #8 in the magic-number glossary. + return eventCooldownTimers[type]==GAME_EVENT_COOLDOWN_TICKS; +} + + + + +std::string Team::getFirstPlayerName(void) const +{ + for (int i=0; igameHeader.getNumberOfPlayers(); i++) + { + if (game->players[i]->team == this) + return game->players[i]->name; + } + return {}; +} + + + +void Team::checkWinConditions() +{ + std::list >& conditions = game->gameHeader.getWinningConditions(); + for(std::list >::iterator i = conditions.begin(); i!=conditions.end(); ++i) + { + if((*i)->hasTeamWon(teamNumber, game)) + { + hasWon=true; + hasLost=false; + winCondition = (*i)->getType(); + break; + } + else if((*i)->hasTeamLost(teamNumber, game)) + { + hasWon=false; + hasLost=true; + winCondition = (*i)->getType(); + break; + } + else + { + hasWon=false; + hasLost=false; + winCondition = WCUnknown; + } + } +} diff --git a/src/Team.h b/src/team/Team.h similarity index 69% rename from src/Team.h rename to src/team/Team.h index 0456fa510..7a9ab43b6 100644 --- a/src/Team.h +++ b/src/team/Team.h @@ -1,27 +1,11 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __TEAM_H -#define __TEAM_H +#pragma once #include +#include #include #include #include @@ -30,7 +14,8 @@ #include "TeamStat.h" #include "GameEvent.h" -#include +#include +#include #include "BaseTeam.h" #include "WinningConditions.h" @@ -46,7 +31,47 @@ class Team:public BaseTeam { static const bool verbose = false; public: - static const int MAX_COUNT=32; + //! In-memory cap on simultaneous teams (and players). All gameplay arrays + //! and loops over active teams use this value. The engine has never been + //! tested above 12 — see `docs/replay-verification.md`. + static const int MAX_COUNT=12; + + //! Legacy on-disk slot count baked into the GameHeader player/ally + //! arrays. Every `.map`, `.game`, `.replay`, and save file written by + //! pre-2026 builds reserves this many BasePlayer + allyTeamNumber + //! entries in the GameHeader section, even though only the first + //! MAX_COUNT are ever populated with real data. GameHeader::load reads + //! all MAX_COUNT_ON_DISK entries (discarding the trailing tail) and + //! GameHeader::save writes them back (padded with default values), so + //! the on-disk format stays byte-equal with the existing content + //! library. Do not change without a file-format version bump. + static const int MAX_COUNT_ON_DISK=32; + + //! Tile-padding added around a dirty rect when invalidating gradients + //! that need to be recomputed on the next pass. Shared between team + //! routing and building gradient propagation. See TeamStep.cpp:163. + static constexpr int GRADIENT_DIRTY_PADDING = 16; + + //! Width/height added to a building's footprint when marking the + //! surrounding tiles dirty: pad on each side then subtract 1 so the + //! resulting rect ends one tile inside the second padding band, matching + //! the original literal `31+building->type->width` at TeamStep.cpp:165. + static constexpr int GRADIENT_DIRTY_SIZE_OFFSET = 2 * GRADIENT_DIRTY_PADDING - 1; + + //! Initial value for the "no candidate found yet" score in upgrade + //! pathing (Team::findBestUpgrade): every real score compares less. + //! Equal to INT32_MAX; named at the call site so the intent is clear. + static constexpr Sint32 UPGRADE_SCORE_NONE = INT32_MAX; + + //! HSV saturation/value used to derive default team colours from a + //! hue (see Team::setCorrectColor(float)). Saturation 0.8 keeps the + //! palette readable; value 0.9 keeps it bright but not blown out. + static constexpr float TEAM_COLOR_SATURATION = 0.8f; + static constexpr float TEAM_COLOR_VALUE = 0.9f; + + //! Float multiplier used to convert a 0..1 HSV channel into a 0..255 + //! 8-bit colour value (see Team::setCorrectColor). + static constexpr float COLOR_CHANNEL_MAX = 255.0f; Team(Game *game); Team(GAGCore::InputStream *stream, Game *game, Sint32 versionMinor); @@ -55,7 +80,7 @@ class Team:public BaseTeam void setBaseTeam(const BaseTeam *initial); bool load(GAGCore::InputStream *stream, BuildingsTypes *buildingstypes, Sint32 versionMinor); void save(GAGCore::OutputStream *stream); - + //! Used by MapRandomGenerator to fill correctly the list usually filled by load(stream). void createLists(void); @@ -67,26 +92,26 @@ class Team:public BaseTeam //! Check some available integrity constraints bool integrity(void); - + //! remove the building from all lists not realated to the upgrade/destroying systems void removeFromAbilitiesLists(Building *building); //! add the building from all lists not realated to the upgrade/destroying systems void addToStaticAbilitiesLists(Building *building); - + //! Do a step for each unit, building and bullet in team. void syncStep(void); //! Check if there is still players controlling this team, if not, it is dead void checkControllingPlayers(void); ///Push a new game event into the queue - void pushGameEvent(boost::shared_ptr event); - + void pushGameEvent(GameEvent event); + ///Return the top-most event from the queue and remove it - boost::shared_ptr getEvent(); - + std::optional getEvent(); + ///This returns whether an event of the given type had occurred on the last tick bool wasRecentEvent(GameEventType type); - + ///Updates the list of events. This automatically clears events that get too old, ///and decrements the cooldown timers for each event type void updateEvents(); @@ -95,10 +120,10 @@ class Team:public BaseTeam void setCorrectColor(const GAGCore::Color& color); void setCorrectColor(float value); inline static Uint32 teamNumberToMask(int team) { return 1< *checkSumsVector=NULL, std::vector *checkSumsVectorForBuildings=NULL, std::vector *checkSumsVectorForUnits=NULL); - - //! Return the name of the first player in the team + + //! Return the name of the first human/AI player on this team, or an + //! empty string if no player owns the team (uncontrolled). Locale- + //! agnostic; UI callers wanting the localized "[Uncontrolled]" + //! placeholder must use displayPlayerName() (gui/TeamDisplay.h). std::string getFirstPlayerName(void) const; - + //! This checks all of the win conditions and updates hasWon, hasLost and winCondition void checkWinConditions(); - + private: void init(void); @@ -137,9 +165,9 @@ class Team:public BaseTeam // game is the basic (structural) pointer. Map is used for direct access. Game *game; Map *map; - + Unit **myUnits; - + Building **myBuildings; ///This stores the buildings that need units, listed into their hard priorities. They are sorted based on priority. @@ -147,7 +175,7 @@ class Team:public BaseTeam // those where the 4 "call-lists" (lists of flags or buildings for units to work on/in) : std::list upgrade[NB_ABILITY]; //to upgrade the units' abilities. - + // The list of building which have one specific ability. std::list canFeedUnit; // The buildings with not enough food are not in this list. std::list canHealUnit; @@ -175,7 +203,7 @@ class Team:public BaseTeam Sint32 startPosX, startPosY; Sint32 startPosSet; // {0=unset, 1=any unit, 2=any building, 3=swarm building} Sint32 prestige; - + // Number of unit lost due to conversion Sint32 unitConversionLost; // Number of unit gained due to conversion @@ -186,14 +214,14 @@ class Team:public BaseTeam private: ///Queue of game events - std::queue > events; + std::queue events; ///These timers indicate the cooldown for a particular event type, ///This keeps too many events from being pumped at once. If the ///timer isn't at 0 when a new event is received, the new event ///is ignored. Uint8 eventCooldownTimers[GESize]; - - + + public: ///This is the teams race, which defines its properties Race race; @@ -209,14 +237,10 @@ class Team:public BaseTeam bool hasLost; ///This is the winningCondition that caused this team to win/lose WinningConditionType winCondition; - + //! the stat for this team. It is computed every step, so it is always updated. // TeamStat latestStat; this has been moved to *stats.getLatestStat(); TeamStats stats; - -protected: - FILE *logFile; }; -#endif diff --git a/src/team/TeamLists.cpp b/src/team/TeamLists.cpp new file mode 100644 index 000000000..ea1b9fc42 --- /dev/null +++ b/src/team/TeamLists.cpp @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "BuildingType.h" +#include "Game.h" +#include "Map.h" +#include "Team.h" +#include "Unit.h" + +void Team::createLists(void) +{ + assert(swarms.size()==0); + assert(turrets.size()==0); + assert(virtualBuildings.size()==0); + + swarms.clear(); + turrets.clear(); + virtualBuildings.clear(); + + for (int i=0; itype->unitProductionTime) + swarms.push_back(myBuildings[i]); + if (myBuildings[i]->type->shootingRange) + turrets.push_back(myBuildings[i]); + if (myBuildings[i]->type->isVirtual) + virtualBuildings.push_back(myBuildings[i]); + if (myBuildings[i]->type->zonable[WORKER]) + clearingFlags.push_back(myBuildings[i]); + myBuildings[i]->update(); + } +} + + + + +void Team::clearLists(void) +{ + for (int i=0; iperformance[FLY]) + { + map->setAirUnit(myUnits[i]->posX, myUnits[i]->posY, NOGUID); + } + else + { + map->setGroundUnit(myUnits[i]->posX, myUnits[i]->posY, NOGUID); + } + } + } + + for (int i=0; itype->isVirtual) + { + map->setBuilding(myBuildings[i]->posX, myBuildings[i]->posY, myBuildings[i]->type->width, myBuildings[i]->type->height, NOGBID); + } + } + } + +} + + + + +void Team::clearMem(void) +{ + for (int i=0; itype->upgrade[ui]) + upgrade[ui].remove(building); + + if (building->type->canFeedUnit) + canFeedUnit.remove(building); + if (building->type->canHealUnit) + canHealUnit.remove(building); + if (building->type->canExchange) + canExchange.remove(building); + + if (building->type->unitProductionTime) + swarms.remove(building); + if (building->type->shootingRange) + turrets.remove(building); + + if (building->type->zonable[WORKER]) + clearingFlags.remove(building); + + if (building->type->isVirtual) + virtualBuildings.remove(building); +} + + + + +void Team::addToStaticAbilitiesLists(Building *building) +{ + if (building->type->canExchange) + canExchange.push_back(building); + + if (building->type->unitProductionTime) + swarms.push_back(building); + + if (building->type->shootingRange) + turrets.push_back(building); + + if (building->type->zonable[WORKER]) + clearingFlags.push_back(building); +; + if (building->type->isVirtual) + virtualBuildings.push_back(building); +} diff --git a/src/team/TeamRouting.cpp b/src/team/TeamRouting.cpp new file mode 100644 index 000000000..387b0f03b --- /dev/null +++ b/src/team/TeamRouting.cpp @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include "BuildingType.h" +#include "FixedPoint.h" +#include "Game.h" +#include "Map.h" +#include "Team.h" +#include "Unit.h" + +Building *Team::findNearestHeal(Unit *unit) +{ + if (unit->hungry < 0) + return NULL; + if (unit->performance[FLY]) + { + Sint32 x = unit->posX; + Sint32 y = unit->posY; + Sint32 maxDist = unit->hungry / unit->race->hungryness + unit->hp; + Building *choosen = NULL; + Sint32 bestDist2 = maxDist * maxDist; + for (std::list::iterator bi=canHealUnit.begin(); bi!=canHealUnit.end(); ++bi) + { + Building *b=(*bi); + Sint32 dist2 = map->warpDistSquare(x, y, b->posX, b->posY); + if (dist2 < bestDist2) + { + choosen = b; + bestDist2 = dist2; + } + } + return choosen; + } + else + { + Sint32 x = unit->posX; + Sint32 y = unit->posY; + Sint32 maxDist = unit->hungry / race.hungryness + unit->hp; + bool canSwim = unit->performance[SWIM]; + Building *choosen= NULL; + Sint32 bestDist = maxDist; + for (std::list::iterator bi=canHealUnit.begin(); bi!=canHealUnit.end(); ++bi) + { + int buildingDist;//initialized in buildingAvailable next line + if (map->buildingAvailable((*bi), canSwim, x, y, &buildingDist) && (buildingDist < bestDist)) + { + choosen = (*bi); + bestDist = buildingDist; + } + } + return choosen; + } +} + + + + +Building *Team::findNearestFood(Unit *unit) +{ + MapHeader& header=game->mapHeader; + + bool concurency = false;//Becomes true if there is a team whose inn-view is on for us but who is not allied to us. + for (int ti= 0; ti < header.getNumberOfTeams(); ti++) + if (ti != teamNumber && (game->teams[ti]->sharedVisionFood & me) && !(game->teams[ti]->allies & me)) + { + concurency = true; + break; + } + + // first, we check for the best food an enemy can offer: + Sint32 bestEnemyHappyness = 0; + Sint32 maxDist = std::max(0, unit->hungry) / unit->race->hungryness + unit->hp; + Building *bestEnemyFood = NULL; + if (concurency) + { + if (unit->verbose) + printf("guid=(%d), Team::findNearestFood(), concurency\n", unit->gid); + if (unit->performance[FLY]) + { + Sint32 bestDist = maxDist; + for (int ti = 0; ti < header.getNumberOfTeams(); ti++) + { + if (ti == teamNumber) + continue; + Team *team = game->teams[ti]; + if (!(team->sharedVisionFood & me) || (team->allies & me)) + continue; + for (std::list::iterator bi = team->canFeedUnit.begin(); bi != team->canFeedUnit.end(); ++bi) + { + Sint32 dist = 1 + (Sint32)sqrt(map->warpDistSquare(unit->posX, unit->posY, (*bi)->posX, (*bi)->posY)); + if (dist >= maxDist + || !(*bi)->canConvertUnit() + ) + { + continue; + } + int happyness = (*bi)->availableHappynessLevel(); + if (happyness > bestEnemyHappyness) + { + bestEnemyHappyness = happyness; + bestDist = dist; + bestEnemyFood = *bi; + } + else if (happyness == bestEnemyHappyness && dist < bestDist) + { + bestDist = dist; + bestEnemyFood = *bi; + } + } + } + } + else + { + Sint32 bestDist = maxDist; + bool canSwim = (unit->performance[SWIM] > 0); + for (int ti = 0; ti < header.getNumberOfTeams(); ti++) + { + if (ti == teamNumber) + continue; + Team *team = game->teams[ti]; + if (!(team->sharedVisionFood & me) || (team->allies & me)) + continue; + for (std::list::iterator bi = team->canFeedUnit.begin(); bi != team->canFeedUnit.end(); ++bi) + { + int dist = 1 + (Sint32)sqrt(map->warpDistSquare(unit->posX, unit->posY, (*bi)->posX, (*bi)->posY)); + if (dist >= maxDist + || !(*bi)->canConvertUnit() + ) + { + continue; + } + if (!map->buildingAvailable(*bi, canSwim, unit->posX, unit->posY, &dist)) + continue; + if (dist >= maxDist) + continue; + int happyness = (*bi)->availableHappynessLevel(); + if (happyness > bestEnemyHappyness) + { + bestEnemyHappyness = happyness; + bestDist = dist; + bestEnemyFood = *bi; + } + else if (happyness == bestEnemyHappyness && dist < bestDist) + { + bestDist = dist; + bestEnemyFood = *bi; + } + } + } + } + if (unit->verbose && bestEnemyFood) + printf("guid=(%d), Team::findNearestFood(), bestEnemyHappyness=%d, bestEnemyFood->gid=%d\n", unit->gid, bestEnemyHappyness, bestEnemyFood->gid); + } + + //Second, we check if we have any satisfactory inns on our team. + // That mean it has to be better or equal than the ennemy food. + if (unit->performance[FLY]) + { + Sint32 bestDist = maxDist; + Building *choosenFood = NULL; + for (std::list::iterator bi=canFeedUnit.begin(); bi!=canFeedUnit.end(); ++bi) + { + if ((*bi)->availableHappynessLevel() < bestEnemyHappyness) + continue; + Sint32 dist = 1 + (Sint32)sqrt(map->warpDistSquare(unit->posX, unit->posY, (*bi)->posX, (*bi)->posY)); + if (dist >= bestDist) + continue; + bestDist = dist; + choosenFood = *bi; + } + if (choosenFood) + return choosenFood; + } + else + { + bool canSwim = (unit->performance[SWIM] > 0); + Sint32 bestDist = maxDist; + Building *choosenFood = NULL; + for (std::list::iterator bi=canFeedUnit.begin(); bi!=canFeedUnit.end(); ++bi) + { + if ((*bi)->availableHappynessLevel() < bestEnemyHappyness) + continue; + int dist = 1 + (Sint32)sqrt(map->warpDistSquare(unit->posX, unit->posY, (*bi)->posX, (*bi)->posY)); + if (dist >= bestDist) + continue; + + if (!map->buildingAvailable(*bi, canSwim, unit->posX, unit->posY, &dist)) + continue; + if (dist >= bestDist) + continue; + bestDist = dist; + choosenFood = *bi; + } + if (choosenFood) + return choosenFood; + } + + return bestEnemyFood; +} + + + + +Building *Team::findBestUpgrade(Unit *unit) +{ + Building *choosen=NULL; + Sint32 score=Team::UPGRADE_SCORE_NONE; + int x=unit->posX; + int y=unit->posY; + //TODO: This is bad code. If WALK ever ceases to be the first ability or ARMOR ever ceases + //to be the last, this code will fail. + for (int ability=(int)WALK; ability<(int)ARMOR; ability++) + { + if (unit->canLearn[ability]) + { + if (unit->verbose) + printf("guid=(%d) unit->canLearn[ability=%d]\n", unit->gid, ability); + int actLevel=unit->level[ability]; + for (std::list::iterator bi=upgrade[ability].begin(); bi!=upgrade[ability].end(); ++bi) + { + Building *b=(*bi); + if (unit->verbose) + printf("guid=(%d) b->gid=%d, b->type->level=%d, actLevel=%d\n", unit->gid, b->gid, b->type->level, actLevel); + if (b->type->level >= actLevel) + { + Sint32 newScore=(map->warpDistSquare(b->posX, b->posY, x, y)<maxUnitInside-b->unitsInside.size()); + if (newScoredestinationPurpose=(Sint32)ability; + choosen=b; + score=newScore; + } + } + } + } + } + return choosen; +} + + + + +int Team::maxBuildLevel(void) +{ + int maxLevel=0; + for (int i=0; iperformance[BUILD]) + { + int unitLevel=u->level[BUILD]; + if (unitLevel>maxLevel) + maxLevel=unitLevel; + } + } + return maxLevel; +} diff --git a/src/team/TeamSerialization.cpp b/src/team/TeamSerialization.cpp new file mode 100644 index 000000000..3274edac8 --- /dev/null +++ b/src/team/TeamSerialization.cpp @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "BuildingType.h" +#include "FileFormatVersions.h" +#include "Game.h" +#include "GlobalContainer.h" +#include "Marshaling.h" +#include "Team.h" +#include "Unit.h" +#include "Utilities.h" + +bool Team::load(GAGCore::InputStream *stream, BuildingsTypes *buildingstypes, Sint32 versionMinor) +{ + assert(stream); + assert(buildingsToBeDestroyed.size()==0); + buildingsTryToBuildingSiteRoom.clear(); + + // loading baseteam + if(!BaseTeam::load(stream, versionMinor)) + return false; + + stream->readEnterSection("Team"); + + // normal load + stream->readEnterSection("myUnits"); + for (int i=0; ireadEnterSection(i); + Uint32 isUsed = stream->readUint32("isUsed"); + if (isUsed) + myUnits[i] = new Unit(stream, this, versionMinor); + else + myUnits[i] = NULL; + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + swarms.clear(); + turrets.clear(); + canExchange.clear(); + virtualBuildings.clear(); + clearingFlags.clear(); + + prestige = 0; + stream->readEnterSection("myBuildings"); + for (int i=0; ireadEnterSection(i); + Uint32 isUsed = stream->readUint32("isUsed"); + if (isUsed) + { + myBuildings[i] = new Building(stream, buildingstypes, this, versionMinor); + if (myBuildings[i]->type->unitProductionTime) + swarms.push_back(myBuildings[i]); + if (myBuildings[i]->type->shootingRange) + turrets.push_back(myBuildings[i]); + if (myBuildings[i]->type->canExchange) + canExchange.push_back(myBuildings[i]); + if (myBuildings[i]->type->isVirtual) + virtualBuildings.push_back(myBuildings[i]); + if (myBuildings[i]->type->zonable[WORKER]) + clearingFlags.push_back(myBuildings[i]); + } + else + myBuildings[i] = NULL; + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + // resolve cross reference + stream->readEnterSection("myUnits"); + for (int i=0; ireadEnterSection(i); + myUnits[i]->loadCrossRef(stream, this, versionMinor); + stream->readLeaveSection(); + } + } + stream->readLeaveSection(); + + stream->readEnterSection("myBuildings"); + for (int i=0; ireadEnterSection(i); + myBuildings[i]->loadCrossRef(stream, buildingstypes, this, versionMinor); + if (myBuildings[i]->type->canExchange) + canExchange.push_back(myBuildings[i]); + stream->readLeaveSection(); + } + } + stream->readLeaveSection(); + + allies = stream->readUint32("allies"); + enemies = stream->readUint32("enemies"); + sharedVisionExchange = stream->readUint32("sharedVisionExchange"); + sharedVisionFood = stream->readUint32("sharedVisionFood"); + sharedVisionOther = stream->readUint32("sharedVisionOther"); + me = stream->readUint32("me"); + startPosX = stream->readSint32("startPosX"); + startPosY = stream->readSint32("startPosY"); + startPosSet = stream->readSint32("startPosSet"); + unitConversionLost = stream->readSint32("unitConversionLost"); + unitConversionGained = stream->readSint32("unitConversionGained"); + + stream->readEnterSection("teamRessources"); + for (unsigned int i=0; ireadEnterSection(i); + teamRessources[i] = stream->readUint32("teamRessources"); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + + for(int i=0; ireadLeaveSection(); + return false; + } + stats.step(this, true); + + if(versionMinor >= FILE_FORMAT_VERSION_RACE_FIELD) + { + if(!race.load(stream, versionMinor)) + { + stream->readLeaveSection(); + return false; + } + } + else + { + race.load(); + } + + isAlive = true; + + stream->readLeaveSection(); + return true; +} + + + + +void Team::save(GAGCore::OutputStream *stream) +{ + // saving baseteam + BaseTeam::save(stream); + + stream->writeEnterSection("Team"); + + // saving team + stream->writeEnterSection("myUnits"); + for (int i=0; iwriteEnterSection(i); + if (myUnits[i]) + { + stream->writeUint32(true, "isUsed"); + myUnits[i]->save(stream); + } + else + { + stream->writeUint32(false, "isUsed"); + } + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->writeEnterSection("myBuildings"); + for (int i=0; iwriteEnterSection(i); + if (myBuildings[i]) + { + stream->writeUint32(true, "isUsed"); + myBuildings[i]->save(stream); + } + else + { + stream->writeUint32(false, "isUsed"); + } + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + // save cross reference + stream->writeEnterSection("myUnits"); + for (int i=0; iwriteEnterSection(i); + myUnits[i]->saveCrossRef(stream); + stream->writeLeaveSection(); + } + } + stream->writeLeaveSection(); + + stream->writeEnterSection("myBuildings"); + for (int i=0; iwriteEnterSection(i); + myBuildings[i]->saveCrossRef(stream); + stream->writeLeaveSection(); + } + } + stream->writeLeaveSection(); + + stream->writeUint32(allies, "allies"); + stream->writeUint32(enemies, "enemies"); + stream->writeUint32(sharedVisionOther, "sharedVisionExchange"); + stream->writeUint32(sharedVisionFood, "sharedVisionFood"); + stream->writeUint32(sharedVisionOther, "sharedVisionOther"); + stream->writeUint32(me, "me"); + stream->writeSint32(startPosX, "startPosX"); + stream->writeSint32(startPosY, "startPosY"); + stream->writeSint32(startPosSet, "startPosSet"); + stream->writeSint32(unitConversionLost, "unitConversionLost"); + stream->writeSint32(unitConversionGained, "unitConversionGained"); + + stream->writeEnterSection("teamRessources"); + for (unsigned int i=0; iwriteEnterSection(i); + stream->writeUint32(teamRessources[i], "teamRessources"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stats.save(stream); + race.save(stream); + + stream->writeLeaveSection(); +} + + + + +Uint32 Team::checkSum(std::vector *checkSumsVector, std::vector *checkSumsVectorForBuildings, std::vector *checkSumsVectorForUnits) +{ + Uint32 cs=0; + + cs^=BaseTeam::checkSum(); + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [1+t*20] + + for (int i=0; icheckSum(checkSumsVectorForUnits); + cs=rotr1(cs); + } + if (checkSumsVector) + checkSumsVector->push_back(cs); // [2+t*20] + + for (int i=0; icheckSum(checkSumsVectorForBuildings); + cs=rotr1(cs); + } + if (checkSumsVector) + checkSumsVector->push_back(cs); // [3+t*20] + + for (int i=0; ipush_back(cs); // [4+t*20] + + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [7+t*20] + + cs^=canExchange.size(); + cs^=canFeedUnit.size(); + cs^=canHealUnit.size(); + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [8+t*20] + + cs^=buildingsToBeDestroyed.size(); + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [9+t*20] + cs^=buildingsTryToBuildingSiteRoom.size(); + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [10+t*20] + + cs^=swarms.size(); + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [11+t*20] + cs^=turrets.size(); + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [12+t*20] + + cs^=allies; + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [13+t*20] + cs^=enemies; + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [14+t*20] + cs^=sharedVisionExchange; + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [15+t*20] + cs^=sharedVisionFood; + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [16+t*20] + cs^=sharedVisionOther; + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [17+t*20] + cs^=me; + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [18+t*20] + + cs^=noMoreBuildingSitesCountdown; + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [19+t*20] + + cs^=prestige; + cs=rotr1(cs); + if (checkSumsVector) + checkSumsVector->push_back(cs); // [20+t*20] + + return cs; +} diff --git a/src/team/TeamStep.cpp b/src/team/TeamStep.cpp new file mode 100644 index 000000000..1a9bd91cb --- /dev/null +++ b/src/team/TeamStep.cpp @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include + +#include "BuildingType.h" +#include "Game.h" +#include "GameGUI.h" +#include "GlobalContainer.h" +#include "Map.h" +#include "Team.h" +#include "Unit.h" + +bool Team::prioritize_building(Building* lhs, Building* rhs) +{ + if(lhs->priority != rhs->priority) + return lhs->priority > rhs->priority; + + int priority_lhs=0; + if(lhs->type->shortTypeNum==IntBuildingType::FOOD_BUILDING && !lhs->type->isBuildingSite) + priority_lhs=2+lhs->type->level*10; + else + priority_lhs=1+lhs->type->level*10; + + int priority_rhs=0; + if(rhs->type->shortTypeNum==IntBuildingType::FOOD_BUILDING && !rhs->type->isBuildingSite) + priority_rhs=2+rhs->type->level*10; + else + priority_rhs=1+rhs->type->level*10; + + if(priority_lhs != priority_rhs) + { + return priority_lhs > priority_rhs; + } + else + { + //This uses some fraction math in order to be able to compare the relative percent of units needed + //for each building. The fractions are (needed_units / wanted_units) for both lhs and rhs. + //The trick is to put them into a common denominator, which is done by cross multiplying. + //The denominators don't actually need to be computed, only the numerators. + int ratio_lhs_unit = (lhs->maxUnitWorking - lhs->unitsWorking.size()) * rhs->unitsWorking.size(); + int ratio_rhs_unit = (rhs->maxUnitWorking - rhs->unitsWorking.size()) * lhs->unitsWorking.size(); + if(ratio_lhs_unit == ratio_rhs_unit) + { + int ratio_lhs_ressource = lhs->totalWishedRessource(); + int ratio_rhs_ressource = rhs->totalWishedRessource(); + if(ratio_lhs_ressource != ratio_rhs_ressource) + return ratio_lhs_ressource > ratio_rhs_ressource; + // Tiebreak on gid: std::sort is unstable, so without a final + // total order the position of tied buildings is unspecified + // and can diverge across binaries (= multiplayer desync). + return lhs->gid < rhs->gid; + } + else + { + return ratio_lhs_unit > ratio_rhs_unit; + } + } + return false; +} + + +void Team::add_building_needing_work(Building* b, Sint32 priority) +{ + bool did_find_position=false; + Sint32 p = priority; + std::vector& blist = buildingsNeedingUnits[p]; + for(std::vector::iterator i=blist.begin(); i!=blist.end(); ++i) + { + if(prioritize_building(b, *i)) + { + buildingsNeedingUnits[p].insert(i, b); + did_find_position=true; + break; + } + } + if(!did_find_position) + buildingsNeedingUnits[p].push_back(b); +} + + +void Team::remove_building_needing_work(Building* b, Sint32 priority) +{ + Sint32 p = priority; + buildingsNeedingUnits[p].erase(std::find(buildingsNeedingUnits[p].begin(), buildingsNeedingUnits[p].end(), b)); +} + + + +void Team::updateAllBuildingTasks() +{ + for(std::map, std::greater >::iterator i = buildingsNeedingUnits.begin(); i!=buildingsNeedingUnits.end(); ++i) + { + std::sort(i->second.begin(), i->second.end(), Team::prioritize_building); + bool cont=true; + std::vector foundPer(i->second.size(), true); + while(cont) + { + bool found=false; + for(unsigned j=0; j<(i->second.size()); ++j) + { + if(foundPer[j]) + { + bool thisFound=false; + if(i->second[j]->type->isVirtual) + thisFound |= (i->second)[j]->subscribeForFlagingStep(); + else + thisFound |= (i->second)[j]->subscribeToBringRessourcesStep(); + found |= thisFound; + foundPer[j] = thisFound; + } + } + if(!found) + cont = false; + } + } +} + + + + +void Team::syncStep(void) +{ + integrity(); + + if (noMoreBuildingSitesCountdown>0) + noMoreBuildingSitesCountdown--; + + int nbUsefullUnits = 0; + int nbUsefullUnitsAlone = 0; + for (int i = 0; i < Unit::MAX_COUNT; i++) + { + Unit *u = myUnits[i]; + if (u) + { + if (u->typeNum != EXPLORER) + { + nbUsefullUnits++; + if (u->medical == Unit::MED_FREE || (u->insideTimeout < 0 && u->attachedBuilding && u->attachedBuilding->type->canFeedUnit)) + nbUsefullUnitsAlone++; + } + u->syncStep(); + if (u->isDead) + { + // Sim must not read GameGUI state. Route the selection + // clear through a GUI hook (see GameGUI::onUnitDestroyed). + game->gui->onUnitDestroyed(u); + delete u; + myUnits[i] = NULL; + } + } + } + + bool isDirtyGlobalGradient=false; + for (std::list::iterator it=buildingsWaitingForDestruction.begin(); it!=buildingsWaitingForDestruction.end();) + { + Building *building=*it; + if (building->unitsInside.size()==0) + { + if (building->buildingState==Building::WAITING_FOR_DESTRUCTION) + { + if (!building->type->isVirtual) + { + map->setBuilding(building->posX, building->posY, building->type->width, building->type->height, NOGBID); + map->dirtyLocalGradient(building->posX-Team::GRADIENT_DIRTY_PADDING, building->posY-Team::GRADIENT_DIRTY_PADDING, Team::GRADIENT_DIRTY_SIZE_OFFSET+building->type->width, Team::GRADIENT_DIRTY_SIZE_OFFSET+building->type->height, teamNumber); + isDirtyGlobalGradient=true; + } + building->buildingState=Building::DEAD; + prestige-=(*it)->type->prestige; + buildingsToBeDestroyed.push_front(building); + } + + std::list::iterator ittemp=it; + it=buildingsWaitingForDestruction.erase(ittemp); + } + else + ++it; + } + if (isDirtyGlobalGradient) + { + dirtyGlobalGradient(); + map->updateForbiddenGradient(teamNumber); + map->updateGuardAreasGradient(teamNumber); + map->updateClearAreasGradient(teamNumber); + } + + for (std::list::iterator it=buildingsToBeDestroyed.begin(); it!=buildingsToBeDestroyed.end(); ++it) + { + Building *building=*it; + + removeFromAbilitiesLists(building); + + assert(building->unitsWorking.size()==0); + assert(building->unitsInside.size()==0); + + //TODO: optimisation: we can avoid some of thoses remove(Building *) by keeping a building state to detect which remove() are needed. + buildingsTryToBuildingSiteRoom.remove(building); + + // Sim must not read GameGUI state. Route the selection + // clear through a GUI hook (see GameGUI::onBuildingDestroyed). + game->gui->onBuildingDestroyed(building); + + myBuildings[Building::GIDtoID(building->gid)]=NULL; + delete building; + } + + if (buildingsToBeDestroyed.size()) + buildingsToBeDestroyed.clear(); + + for (std::list::iterator it=buildingsTryToBuildingSiteRoom.begin(); it!=buildingsTryToBuildingSiteRoom.end();) + { + if ((*it)->tryToBuildingSiteRoom()) + { + std::list::iterator ittemp=it; + it=buildingsTryToBuildingSiteRoom.erase(ittemp); + } + else + ++it; + } + + updateAllBuildingTasks(); + + bool isEnoughFoodInSwarm=false; + + for (int i=0; istep(); + } + } + + for (std::list::iterator it=swarms.begin(); it!=swarms.end(); ++it) + { + if (!(*it)->locked[SWIM_VARIANT_CAN_SWIM] && (*it)->ressources[CORN]>(*it)->type->ressourceForOneUnit) + isEnoughFoodInSwarm=true; + (*it)->swarmStep(); + } + + for (std::list::iterator it=turrets.begin(); it!=turrets.end(); ++it) + (*it)->turretStep(game->stepCounter); + + for (std::list::iterator it=clearingFlags.begin(); it!=clearingFlags.end(); ++it) + (*it)->clearingFlagStep(); + + bool isDying= (playersMask==0) + || (!isEnoughFoodInSwarm && nbUsefullUnitsAlone==0 && (nbUsefullUnits==0 || (canFeedUnit.size()==0 && canHealUnit.size()==0))); + if (isAlive && isDying) + { + isAlive=false; + } + + stats.step(this); + updateEvents(); +} + + + + +void Team::dirtyGlobalGradient() +{ + game->dirtyWarFlagGradient(); + for (int id=0; idglobalGradient[canSwim]) + { + //printf("freeing globalGradient for gbid=%d (%p)\n", b->gid, b->globalGradient[canSwim]); + delete[] b->globalGradient[canSwim]; + b->globalGradient[canSwim]=NULL; + b->locked[canSwim]=false; + } + } +} + +void Team::dirtyWarFlagGradient() +{ + for (std::list::const_iterator it = virtualBuildings.begin(); it != virtualBuildings.end(); ++it) + { + Building *b = *it; + if (b->type->zonable[WARRIOR]) + for (int canSwim=0; canSwimglobalGradient[canSwim]) + { + delete[] b->globalGradient[canSwim]; + b->globalGradient[canSwim]=NULL; + b->locked[canSwim]=false; + } + } +} diff --git a/src/unit/Unit.cpp b/src/unit/Unit.cpp new file mode 100644 index 000000000..3f928b791 --- /dev/null +++ b/src/unit/Unit.cpp @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Unit.h" +#include "Race.h" +#include "Team.h" +#include "Map.h" +#include "Game.h" + +#include "Building.h" +#include "Integrity.h" + +#include "EngineTiming.h" +#include "Utilities.h" +#include "GlobalContainer.h" +#include +#include +#include + +Unit::Unit(GAGCore::InputStream *stream, Team *owner, Sint32 versionMinor) +{ + init(0,0,0,0,owner,0); + load(stream, owner, versionMinor); +} + +Unit::Unit(int x, int y, Uint16 gid, Sint32 typeNum, Team *team, int level) +{ + init(x, y, gid, typeNum, team, level); +} + +void Unit::init(int x, int y, Uint16 gid, Sint32 typeNum, Team *team, int level) +{ + // unit specification + this->typeNum = typeNum; + + assert(team); + race=&(team->race); + assert(race); + + // identity + this->gid=gid; + owner=team; + isDead=false; + + // position + posX=x; + posY=y; + delta=0; + dx=0; + dy=0; + direction=UNIT_DIRECTION_NONE; + insideTimeout=0; + speed=32; + + // quality parameters + for (int i=0; iperformance[i]=race->getUnitType(typeNum, level)->performance[i]; + this->level[i]=level; + this->canLearn[i]=(bool)race->getUnitType(typeNum, 3)->performance[i]; //TODO: is is a better way to hack this? + // This hack prevent units from unlearning. Units level 3 must have all the abilities of all preceedings levels + } + + experience = 0; + experienceLevel = 0; + + // states + needToRecheckMedical=true; + medical=MED_FREE; + activity=ACT_RANDOM; + displacement=DIS_RANDOM; + if (performance[FLY]) + movement=MOV_RANDOM_FLY; + else + movement=MOV_RANDOM_GROUND; + + targetX = 0; + targetY = 0; + validTarget = false; + magicActionTimeout = 0; + + underAttackTimer = 0; + + // trigger parameters + hp=0; + + // warriors fight to death TODO: this is overridden !?!? + if (performance[ATTACK_SPEED]) + trigHP = 0; + else + trigHP = 20; + + // warriors wait more tiem before going to eat + hungry = HUNGRY_MAX; + hungryness = race->hungryness; + if (performance[ATTACK_SPEED]) + trigHungry = (hungry*UNIT_HUNGRY_TRIG_NUM_WARRIOR)/UNIT_HUNGRY_TRIG_DEN; + else + trigHungry = hungry/UNIT_HUNGRY_TRIG_DIVISOR_DEFAULT; + trigHungryCarying = hungry/UNIT_HUNGRY_TRIG_DIVISOR_CARRYING; + fruitMask = 0; + fruitCount = 0; + + // NOTE : rewrite hp from level + hp = this->performance[HP]; + trigHP = (hp*UNIT_HP_TRIG_NUM)/UNIT_HP_TRIG_DEN; + + attachedBuilding=NULL; + targetBuilding=NULL; + ownExchangeBuilding=NULL; + destinationPurpose=UNIT_DEST_PURPOSE_NONE; + carriedRessource=UNIT_CARRIED_RESSOURCE_NONE; + jobTimer = 0; + + previousClearingArea=std::nullopt; + previousClearingAreaDistance=0; + + // gui + levelUpAnimation = 0; + magicActionAnimation = 0; + + // debug vars: + verbose=false; +} + +void Unit::setTargetBuilding(Building * b) +{ + if(targetBuilding!=NULL) { + targetBuilding->removeUnitFromHarvesting(this); + } + if(b!=NULL) + { + targetX=b->getMidX(); + targetY=b->getMidY(); + } +//TODO: Deal with "validTarget=true;" + targetBuilding = b; +} + +void Unit::subscriptionSuccess(Building* building, bool inside) +{ + Building* b=building; + + if (building->type->isVirtual) + { + destinationPurpose=UNIT_DEST_PURPOSE_NONE; + activity=ACT_FLAG; + attachedBuilding=b; + setTargetBuilding(b); + if (verbose) + printf("guid=(%d) unitsWorkingSubscribe(findBestZonable) dp=(%d), gbid=(%d)\n", gid, destinationPurpose, b->gid); + } + else if(inside == false) + { + assert(destinationPurpose>=0); + assert(b->neededRessource(destinationPurpose)); + activity=ACT_FILLING; + attachedBuilding=b; + setTargetBuilding(NULL); + if (verbose) + printf("guid=(%d) unitsWorkingSubscribe(findBestZonable) dp=(%d), gbid=(%d)\n", gid, destinationPurpose, b->gid); + } + else + { + activity=ACT_UPGRADING; + attachedBuilding=b; + setTargetBuilding(b); + if (verbose) + printf("guid=(%d) unitsWorkingSubscribe(findBestZonable) dp=(%d), gbid=(%d)\n", gid, destinationPurpose, b->gid); + } + + if (verbose) + printf("guid=(%d), subscriptionSuccess()\n", gid); + + switch(medical) + { + case MED_HUNGRY : + case MED_DAMAGED : + case MED_FREE: + { + switch(activity) + { + case ACT_FLAG: + { + displacement=DIS_GOING_TO_FLAG; + assert(targetBuilding==attachedBuilding); + //targetX=attachedBuilding->getMidX(); + //targetY=attachedBuilding->getMidY(); + validTarget=true; + } + break; + case ACT_UPGRADING: + { + displacement=DIS_GOING_TO_BUILDING; + assert(targetBuilding==attachedBuilding); + //targetX=targetBuilding->getMidX(); + //targetY=targetBuilding->getMidY(); + validTarget=true; + } + break; + case ACT_FILLING: + { + assert(attachedBuilding); + if (carriedRessource==destinationPurpose) + { + displacement=DIS_GOING_TO_BUILDING; + setTargetBuilding(attachedBuilding); + //targetX=targetBuilding->getMidX(); + //targetY=targetBuilding->getMidY(); + validTarget=true; + } + else + { + displacement=DIS_GOING_TO_RESSOURCE; + targetBuilding=NULL; + owner->map->ressourceAvailableUpdate(owner->teamNumber, destinationPurpose, performance[SWIM], posX, posY, &targetX, &targetY, NULL); + validTarget=true; + } + } + break; + case ACT_RANDOM : + { + displacement=DIS_RANDOM; + validTarget=false; + } + break; + default: + assert(false); + } + } + break; + } +} + +void Unit::syncStep(void) +{ + //warrior attacks? + assert(speed>0); + if ((action==ATTACK_SPEED) && (delta>=UNIT_ATTACK_HIT_DELTA) && (delta<(UNIT_ATTACK_HIT_DELTA+speed))) + { + Uint16 enemyGUID=owner->map->getGroundUnit(posX+dx, posY+dy); + if (enemyGUID!=NOGUID) + { + int enemyID=GIDtoID(enemyGUID); + int enemyTeam=GIDtoTeam(enemyGUID); + Unit *enemy=owner->game->teams[enemyTeam]->myUnits[enemyID]; + + int degats=getRealAttackStrength()-enemy->getRealArmor(false); + if (degats<=0) + degats=1; + enemy->hp-=degats; + + enemy->underAttackTimer = UNDER_ATTACK_TIMER_TICKS; + + enemy->owner->pushGameEvent(GameEvent::unitUnderAttack(owner->game->stepCounter, enemy->posX, enemy->posY, enemy->typeNum)); + + incrementExperience(degats); + } + else + { + Uint16 enemyGBID=owner->map->getBuilding(posX+dx, posY+dy); + if (enemyGBID!=NOGBID) + { + int enemyID=Building::GIDtoID(enemyGBID); + int enemyTeam=Building::GIDtoTeam(enemyGBID); + Building *enemy=owner->game->teams[enemyTeam]->myBuildings[enemyID]; + int degats=getRealAttackStrength()-enemy->type->armor; + if (degats<=0) + degats=1; + enemy->hp-=degats; + + enemy->underAttackTimer = UNDER_ATTACK_TIMER_TICKS; + + enemy->owner->pushGameEvent(GameEvent::buildingUnderAttack(owner->game->stepCounter, enemy->posX, enemy->posY, enemy->shortTypeNum)); + + if (enemy->hp<0) + enemy->kill(); + incrementExperience(degats); + } + } + } + + //We give globs 32 ticks to wait for a job before moving onto + //another activity like upgrading + if (medical==MED_FREE && activity==ACT_RANDOM) + { + jobTimer++; + } + + if(underAttackTimer > 0) + underAttackTimer -= 1; + +//#define BURST_UNIT_MODE +#ifdef BURST_UNIT_MODE + delta=0; +#else + if (delta<=UNIT_DELTA_MAX-speed) + { + delta+=speed; + } + else +#endif + { + //printf("action=%d, speed=%d, perf[a]=%d, t->perf[a]=%d\n", action, speed, performance[action], race->getUnitType(typeNum, 0)->performance[action]); + delta+=(speed-UNIT_DELTA_QUANTUM); + + endOfAction(); + + if (performance[FLY]) + { + constexpr int r = UNIT_VISION_RADIUS_FLY; + constexpr int d = 2*UNIT_VISION_RADIUS_FLY + 1; + owner->map->setMapDiscovered(posX-r, posY-r, d, d, owner->sharedVisionOther); + owner->map->setMapBuildingsDiscovered(posX-r, posY-r, d, d, owner->sharedVisionOther, owner->game->teams); + owner->map->setMapExploredByUnit(posX-r, posY-r, d, d, owner->teamNumber); + } + else + { + constexpr int r = UNIT_VISION_RADIUS_GROUND; + constexpr int d = 2*UNIT_VISION_RADIUS_GROUND + 1; + owner->map->setMapDiscovered(posX-r, posY-r, d, d, owner->sharedVisionOther); + owner->map->setMapBuildingsDiscovered(posX-r, posY-r, d, d, owner->sharedVisionOther, owner->game->teams); + owner->map->setMapExploredByUnit(posX-r, posY-r, d, d, owner->teamNumber); + } + } + + // gui + if (levelUpAnimation > 0) + levelUpAnimation--; + if (magicActionAnimation > 0) + magicActionAnimation--; +} diff --git a/src/Unit.h b/src/unit/Unit.h similarity index 69% rename from src/Unit.h rename to src/unit/Unit.h index bf86e5d9f..3f81d5920 100644 --- a/src/Unit.h +++ b/src/unit/Unit.h @@ -1,27 +1,11 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __UNIT_H -#define __UNIT_H +#pragma once #include #include +#include #include #include @@ -35,7 +19,6 @@ class Team; class Race; -class UnitSkin; class Building; namespace GAGCore @@ -96,8 +79,6 @@ class Unit : public UnitUtils int getNextLevelThreshold(void) const; void incrementExperience(int increment); - void skinPointerFromName(void); - public: enum Medical @@ -173,8 +154,46 @@ class Unit : public UnitUtils void handleActivity(void); void handleDisplacement(void); void handleMovement(void); + // handleMovement() helpers — one per Displacement state, plus the pre-switch + // "claim adjacent clearing-area cell" check. Behavior-preserving split of the + // original 555-line switch; keep helpers in lockstep with the dispatcher. + bool tryClaimClearingAreaForHarvesting(); + void handleMovementRemovingBlackAround(); + void handleMovementAttackingAround(); + void handleMovementClearingResources(); + void handleMovementRandom(); + void handleMovementGoingToFlagOrBuilding(); + void handleMovementEnteringBuilding(); + void handleMovementInside(); + void handleMovementExitingBuilding(); + void handleMovementGoingToRessource(); + void handleMovementHarvesting(); + void handleMovementFillingBuilding(); + // Shared post-quality-comparison logic for handleMovementAttackingAround(). + // If newQuality beats `quality`, calls pathfindPointToPoint and on success + // updates movement/dx/dy/targetX/targetY/validTarget and lowers `quality`. + // pathfindPointToPoint writes &dx,&dy regardless of success — by design. + void tryAcquireAttackTarget(int x, int y, int newQuality, int& quality); void handleAction(void); - + // handleAction() helpers — collapse the repeated clear-slot/wrap-move/claim-slot + // pattern. Air vs ground is selected by performance[FLY], matching the existing + // asserts in cases that hardcode one or the other. + void wrapPosition(); + void clearOccupiedMapSlot(); + void claimOccupiedMapSlot(); + // One per Movement (MOV_*) enum value. handleAction() switches into these. + // MOV_INSIDE is a no-op so it has no helper. + void handleActionRandomGround(); + void handleActionRandomFly(); + void handleActionGoingTarget(); + void handleActionFlyingTarget(); + void handleActionGoingDxDy(); + void handleActionEnteringBuilding(); + void handleActionExitingBuilding(); + void handleActionFilling(); + void handleActionAttackingTarget(); + void handleActionHarvesting(); + void endOfAction(void); void setNewValidDirectionGround(void); @@ -183,7 +202,6 @@ class Unit : public UnitUtils void gotoGroundTarget(); //This will set (dx,dy) given targetX/Y. ground asserted. void escapeGroundTarget(); //This will set (dx,dy) opposed to the given targetX/Y, without the care of forbidden flags ground asserted. void simplifyDirection(int ldx, int ldy, int *cdx, int *cdy); - void defaultSkinNameFromType(void); bool locationIsInEnemyGuardTowerRange(int x, int y)const; @@ -192,9 +210,7 @@ class Unit : public UnitUtils // unit specification Sint32 typeNum; // Uint8, WORKER, EXPLORER, WARRIOR Race *race; - UnitSkin *skin; - std::string skinName; - + // identity Uint16 gid; // for reservation see GIDtoID() and GIDtoTeam(). Team *owner; @@ -228,7 +244,7 @@ class Unit : public UnitUtils Sint32 hp; // (Uint8) Sint32 trigHP; // (Uint8) - // hungry : maxfood = 100000 + // hungry Sint32 hungry; // (Uint16) Sint32 hungryness; Sint32 trigHungry; // (Uint16) @@ -259,9 +275,15 @@ class Unit : public UnitUtils int levelUpAnimation; int magicActionAnimation; - // These store the previous clearing area target coordinates - Uint32 previousClearingAreaX; - Uint32 previousClearingAreaY; + // (x, y) of the clearing-area cell this unit has claimed on the map. nullopt = + // no current claim. The pre-tick reset in handleMovement() releases the claim + // and resets this back to nullopt. + struct ClearingAreaClaim { Uint32 x; Uint32 y; }; + std::optional previousClearingArea; + // Distance from this unit's position to its claimed cell at the time of the + // last gradient-based claim (handleMovementRandom only — tryClaimClearingArea + // ForHarvesting does not update this). Read by other units via the cross-unit + // theft check; intentionally retains its prior value across the per-tick reset. Uint32 previousClearingAreaDistance; public: @@ -280,9 +302,5 @@ class Unit : public UnitUtils Uint32 checkSum(std::vector *checkSumsVector); void setTargetBuilding(Building * b); bool verbose; - -protected: - FILE *logFile; }; -#endif diff --git a/src/unit/UnitAction.cpp b/src/unit/UnitAction.cpp new file mode 100644 index 000000000..35d382dc7 --- /dev/null +++ b/src/unit/UnitAction.cpp @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Unit.h" +#include "Race.h" +#include "Team.h" +#include "Map.h" +#include "Game.h" + +#include "Building.h" +#include "Integrity.h" + +#include "Utilities.h" +#include "GlobalContainer.h" + +namespace +{ + // In MOV_RANDOM_FLY, we resample (dx,dy) up to this many times trying to find a + // step that doesn't cross into an enemy guard tower's range. If all attempts + // land inside tower range, the last sampled (dx,dy) is used as a fallback — + // the unit takes the hit rather than stalling. + constexpr int RANDOM_FLY_TOWER_AVOIDANCE_ATTEMPTS = 5; + + // GOING_TARGET_MAX_PATH_LENGTH lives in UnitConsts.h so UnitMovement.cpp's + // tryAcquireAttackTarget can share the same path-budget value. +} + +void Unit::wrapPosition() +{ + posX=(posX+dx)&(owner->map->getMaskW()); + posY=(posY+dy)&(owner->map->getMaskH()); +} + +void Unit::clearOccupiedMapSlot() +{ + if (performance[FLY]) + owner->map->setAirUnit(posX, posY, NOGUID); + else + owner->map->setGroundUnit(posX, posY, NOGUID); +} + +void Unit::claimOccupiedMapSlot() +{ + if (performance[FLY]) + { + assert(owner->map->getAirUnit(posX, posY)==NOGUID); + owner->map->setAirUnit(posX, posY, gid); + } + else + { + assert(owner->map->getGroundUnit(posX, posY)==NOGUID); + owner->map->setGroundUnit(posX, posY, gid); + } +} + +void Unit::handleActionRandomGround() +{ + assert(!performance[FLY]); + clearOccupiedMapSlot(); + owner->map->pathfindRandom(this); + wrapPosition(); + selectPreferredGroundMovement(); + speed=performance[action]; + claimOccupiedMapSlot(); +} + +void Unit::handleActionRandomFly() +{ + assert(performance[FLY]); + clearOccupiedMapSlot(); + for(int q = 0; q < RANDOM_FLY_TOWER_AVOIDANCE_ATTEMPTS; ++q) + { + dx=-1+syncRand()%3; + dy=-1+syncRand()%3; + if(!locationIsInEnemyGuardTowerRange(posX + dx, posY + dy)) + break; + } + directionFromDxDy(); + setNewValidDirectionAir(); + wrapPosition(); + action=FLY; + speed=performance[FLY]; + claimOccupiedMapSlot(); +} + +void Unit::handleActionGoingTarget() +{ + assert(!performance[FLY]); + clearOccupiedMapSlot(); + owner->map->pathfindPointToPoint(posX, posY, targetX, targetY, &dx, &dy, performance[SWIM] > 0, owner->me, GOING_TARGET_MAX_PATH_LENGTH); + directionFromDxDy(); + wrapPosition(); + + if(dx == 0 && dy == 0) + owner->map->markImmobileUnit(posX, posY, owner->teamNumber); + + selectPreferredGroundMovement(); + speed=performance[action]; + claimOccupiedMapSlot(); +} + +void Unit::handleActionFlyingTarget() +{ + // No assert(getAirUnit==NOGUID) on the final claim — two flyers can + // converge on the same target tile, so the destination slot may be + // non-empty. Direct setAirUnit calls preserve that, unlike claimOccupiedMapSlot(). + owner->map->setAirUnit(posX, posY, NOGUID); + + flyToTarget(); + + wrapPosition(); + + action=FLY; + speed=performance[FLY]; + + owner->map->setAirUnit(posX, posY, gid); +} + +void Unit::handleActionGoingDxDy() +{ + clearOccupiedMapSlot(); + + directionFromDxDy(); + + wrapPosition(); + + if(dx == 0 && dy == 0) + owner->map->markImmobileUnit(posX, posY, owner->teamNumber); + + selectPreferredMovement(); + speed=performance[action]; + + claimOccupiedMapSlot(); + + if (verbose) + printf("guid=(%d) MOV_GOING_DX_DY d=(%d, %d; %d).\n", gid, direction, dx, dy); +} + +void Unit::handleActionEnteringBuilding() +{ + // NOTE : this is a hack : We don't delete the unit on the map + // because we have to draw it while it is entering. + // owner->map->setUnit(posX, posY, NOUID); + wrapPosition(); + directionFromDxDy(); + selectPreferredMovement(); + speed=performance[action]; +} + +void Unit::handleActionExitingBuilding() +{ + directionFromDxDy(); + selectPreferredMovement(); + speed=performance[action]; + claimOccupiedMapSlot(); +} + +void Unit::handleActionFilling() +{ + owner->map->markImmobileUnit(posX, posY, owner->teamNumber); + directionFromDxDy(); + action=BUILD; + speed=performance[action]; +} + +void Unit::handleActionAttackingTarget() +{ + owner->map->markImmobileUnit(posX, posY, owner->teamNumber); + directionFromDxDy(); + action=ATTACK_SPEED; + speed=performance[action]; +} + +void Unit::handleActionHarvesting() +{ + owner->map->markImmobileUnit(posX, posY, owner->teamNumber); + directionFromDxDy(); + action=HARVEST; + speed=performance[action]; + assert(speed!=0); +} + +void Unit::handleAction(void) +{ + owner->map->clearImmobileUnit(posX, posY); + switch (movement) + { + case MOV_RANDOM_GROUND: handleActionRandomGround(); break; + case MOV_RANDOM_FLY: handleActionRandomFly(); break; + case MOV_GOING_TARGET: handleActionGoingTarget(); break; + case MOV_FLYING_TARGET: handleActionFlyingTarget(); break; + case MOV_GOING_DX_DY: handleActionGoingDxDy(); break; + case MOV_ENTERING_BUILDING: handleActionEnteringBuilding(); break; + case MOV_EXITING_BUILDING: handleActionExitingBuilding(); break; + case MOV_INSIDE: break; + case MOV_FILLING: handleActionFilling(); break; + case MOV_ATTACKING_TARGET: handleActionAttackingTarget(); break; + case MOV_HARVESTING: handleActionHarvesting(); break; + default: assert(false); break; + } +} diff --git a/src/unit/UnitActivity.cpp b/src/unit/UnitActivity.cpp new file mode 100644 index 000000000..56f6df9c5 --- /dev/null +++ b/src/unit/UnitActivity.cpp @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Unit.h" +#include "Race.h" +#include "Team.h" +#include "Map.h" +#include "Game.h" + +#include "Building.h" +#include "Integrity.h" + +#include "Utilities.h" +#include "GlobalContainer.h" +#include +#include +#include + +void Unit::handleActivity(void) +{ + if ((displacement==DIS_EXITING_BUILDING) + && (typeNum == EXPLORER)) { + // fprintf (stderr, "exiting explorer: gid: %d, medical: %d, destinationPurpose: %d\n", gid, medical, destinationPurpose); + } + + // freeze unit health when inside a building + if ((displacement==DIS_ENTERING_BUILDING) || (displacement==DIS_INSIDE) + || ((displacement==DIS_EXITING_BUILDING) + && ! ((typeNum == EXPLORER) && (medical != MED_FREE)))) + return; + + if (verbose) + printf("guid=(%d) handleActivity (medical=%d, activity=%d) (needToRecheckMedical=%d) (attachedBuilding=%p)...\n", + gid, medical, activity, needToRecheckMedical, attachedBuilding); + + if(activity!=ACT_RANDOM) + jobTimer=0; + + if (medical==MED_FREE) + { + handleMagic(); + + if (activity==ACT_RANDOM) + { + // nothing to do: + //Wait for 32 ticks before doing something else, to allow buildings time to hire units + if(jobTimer>32) + { + // We look for an upgrade + Building* b=owner->findBestUpgrade(this); + if (b) + { + assert(destinationPurpose>=WALK); + assert(destinationPurposegid); + b->subscribeUnitForInside(this); + return; + } + + // we go to a heal building if we'r not fully healed: (1/8 trigger) + if (hp+(performance[HP]/UNIT_HEAL_TRIGGER_INV_RATIO) < performance[HP]) + { + Building *b; + b=owner->findNearestHeal(this); + if (b) + { + destinationPurpose=HEAL; + activity=ACT_UPGRADING; + attachedBuilding=b; + setTargetBuilding(b); + needToRecheckMedical=false; + if (verbose) + printf("guid=(%d) Going to heal building\n", gid); + targetX=attachedBuilding->getMidX(); + targetY=attachedBuilding->getMidY(); + validTarget=true; + b->subscribeUnitForInside(this); + } + else + activity=ACT_RANDOM; + } + } + } + } + else if (needToRecheckMedical) + { + // disconnect from building + if (attachedBuilding) + { + if (verbose) + printf("guid=(%d) Need medical while working, abort work\n", gid); + attachedBuilding->removeUnitFromWorking(this); + attachedBuilding->removeUnitFromInside(this); + attachedBuilding=NULL; + ownExchangeBuilding=NULL; + } + setTargetBuilding(NULL); + + if (medical==MED_HUNGRY) + { + Building *b; + b=owner->findNearestFood(this); + /*if (typeNum == EXPLORER) { + fprintf (stderr, "gid: %d, b: %x\n", gid, b); + }*/ + + if (b!=NULL) + { + Team *currentTeam=owner; + Team *targetTeam=b->owner; + if (currentTeam != targetTeam) + { + // Unit conversion code + + // Send events and keep track of number of unit converted + currentTeam->pushGameEvent(GameEvent::unitLostConversion(owner->game->stepCounter, posX, posY, targetTeam->teamNumber)); + currentTeam->unitConversionLost++; + + targetTeam->pushGameEvent(GameEvent::unitGainedConversion(owner->game->stepCounter, posX, posY, currentTeam->teamNumber)); + targetTeam->unitConversionGained++; + + // Find free slot in other team + int targetID=UNIT_TARGETID_NONE; + for (int i=0; imyUnits[i]==NULL) + { + targetID=i; + break; + } + + // If free slot, do the conversion, change owner and ID + if (targetID!=UNIT_TARGETID_NONE) + { + Sint32 currentID=Unit::GIDtoID(gid); + assert(currentTeam->myUnits[currentID]); + currentTeam->myUnits[currentID]=NULL; + targetTeam->myUnits[targetID]=this; + Uint16 targetGID=(GIDfrom(targetID, targetTeam->teamNumber)); + if (verbose) + printf("Unit guid=%d (%d) switched to guid=%d (%d)\n", gid, Unit::GIDtoTeam(gid), targetGID, Unit::GIDtoTeam(targetGID)); + if (performance[FLY]) + { + assert(owner->map->getAirUnit(posX, posY)==gid); + owner->map->setAirUnit(posX, posY, targetGID); + } + else + { + assert(owner->map->getGroundUnit(posX, posY)==gid); + owner->map->setGroundUnit(posX, posY, targetGID); + } + gid=targetGID; + owner=targetTeam; + } + } + + destinationPurpose=FEED; + activity=ACT_UPGRADING; + attachedBuilding=b; + setTargetBuilding(b); + needToRecheckMedical=false; + if (verbose) + printf("guid=(%d) Subscribed to food at building gbid=(%d)\n", gid, b->gid); + b->subscribeUnitForInside(this); + } + else + activity=ACT_RANDOM; + } + else if (medical==MED_DAMAGED) + { + Building *b; + b=owner->findNearestHeal(this); + if (b!=NULL) + { + destinationPurpose=HEAL; + activity=ACT_UPGRADING; + attachedBuilding=b; + setTargetBuilding(b); + needToRecheckMedical=false; + if (verbose) + printf("guid=(%d) Subscribed to heal at building gbid=(%d)\n", gid, b->gid); + b->subscribeUnitForInside(this); + } + else + activity=ACT_RANDOM; + } + else + assert(false); + } +} diff --git a/src/unit/UnitConsts.h b/src/unit/UnitConsts.h new file mode 100644 index 000000000..b12a23c73 --- /dev/null +++ b/src/unit/UnitConsts.h @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include +#include + +enum Abilities +{ + STOP_WALK=0, + STOP_SWIM=1, + STOP_FLY=2, + + WALK=3, + SWIM=4, + FLY=5, + BUILD=6, + HARVEST=7, + ATTACK_SPEED=8, + ATTACK_STRENGTH=9, + + MAGIC_ATTACK_AIR=10, + MAGIC_ATTACK_GROUND=11, + MAGIC_CREATE_WOOD=12, + MAGIC_CREATE_CORN=13, + MAGIC_CREATE_ALGA=14, + + ARMOR=15, /* old 10 */ + HP=16, /* old 11 */ + + HEAL=17, /* old 12 */ + FEED=18 /* old 13 */ +}; +const int NB_MOVE=9; +const int NB_ABILITY=17; + +const int WORKER=0; +const int EXPLORER=1; +const int WARRIOR=2; +const int NB_UNIT_TYPE=3; + +const int NB_UNIT_LEVELS=4; + +// === Unit `delta` Uint8 wrap (cross-slice) === +//! Maximum value of a unit's per-tile `delta` advancement counter. Used +//! when the counter is treated as "fully arrived" (Unit.cpp:296, 304; +//! UnitMovement.cpp; MapQuery.cpp; TypeSteps.cpp turret bullet timing). +static constexpr int UNIT_DELTA_MAX = 255; +//! Modular quantum that wraps a unit's `delta` counter, equal to +//! UNIT_DELTA_MAX + 1. Used in expressions like (256 - delta) / speed. +static constexpr int UNIT_DELTA_QUANTUM = 256; + +// === Direction encoding (cross-slice) === +// Units encode movement direction as one of 8 cardinal/intercardinal +// directions plus a "no direction" sentinel. The encoding numerically +// collides with COUNT (8 == UNIT_DIRECTION_NONE) — both names are kept so +// each call site reads in its intended meaning. See UnitGeometry.cpp / +// UnitMovement.cpp / MapStep.cpp (`syncRand()&7`). + +//! Number of compass directions a unit can face. +static constexpr int UNIT_DIRECTION_COUNT = 8; +//! Bit-mask form of UNIT_DIRECTION_COUNT - 1; used with `& 7` to wrap a +//! direction index into [0, 8). +static constexpr int UNIT_DIRECTION_MASK = 7; +//! "No direction" sentinel for unit dx/dy encoding (the 9-cell encoding +//! reserves index 8 for "stationary"). Numerically equals +//! UNIT_DIRECTION_COUNT, but the meaning is distinct. +static constexpr int UNIT_DIRECTION_NONE = 8; + +// === Unit attack tunables (cross-slice) === +//! Square radius (in tiles) of a warrior's attack-target search around its +//! current position. See UnitMovement.cpp:232, 234. +static constexpr int UNIT_ATTACK_SEARCH_RADIUS = 8; + +//! Maximum path length budget for MOV_GOING_TARGET's pathfindPointToPoint +//! call. Used both by UnitAction.cpp (random-fly fallback / target-acquire) +//! and UnitMovement.cpp:349 (tryAcquireAttackTarget); the value is shared so +//! both consumers stay in sync. +static constexpr int GOING_TARGET_MAX_PATH_LENGTH = 12; + +// === Bullet damage floor (cross-slice) === +//! Minimum damage a bullet can inflict — clamps any negative-armor or +//! over-mitigated calculation to at least this. See Sector.cpp:127, 128, 151. +static constexpr int BULLET_MIN_DAMAGE = 1; + +// === Per-slice "none" sentinels === +// `-1` is overloaded inside the unit slice (destination purpose, carried +// resource, free-slot search, "resource unreachable"); each gets its own +// name so the meaning is explicit at the call site, even though they share +// the integer value. + +//! `Unit::destinationPurpose` sentinel meaning "no destination chosen yet". +static constexpr int UNIT_DEST_PURPOSE_NONE = -1; +//! `Unit::carriedRessource` sentinel meaning "not carrying anything". +static constexpr int UNIT_CARRIED_RESSOURCE_NONE = -1; +//! Free-slot search sentinel: starting `targetID = -1` means "no free slot +//! found yet" (UnitActivity.cpp conversion code). +static constexpr int UNIT_TARGETID_NONE = -1; +//! `Unit::minDistToResource[]` sentinel meaning "this resource is not +//! reachable from the unit's current position" (UnitStats.cpp). +static constexpr int UNIT_MIN_DIST_NOT_REACHABLE = -1; +//! `Unit::previousClearingAreaDistance` (`Uint32`) sentinel meaning "no +//! claim recorded". Stored as `0xFFFFFFFF`. UnitMovement.cpp:474. +static constexpr Uint32 UNIT_CLEAR_AREA_DISTANCE_NONE = static_cast(-1); +// NOTE: the "no clearing-gradient target" sentinel (254) lives in the map +// slice as `GRADIENT_FORBIDDEN_BORDER` / `GRADIENT_AT_GOAL` and is consumed +// from UnitMovement.cpp:441-442 — no per-slice unit constant is needed. + +// === HP / hunger trigger ratios === +//! Numerator of the "low HP, retreat to heal" trigger: `trigHP = (hp*3)/10`. +//! Set in `Unit::init` after `hp` is overwritten from `performance[HP]`. +static constexpr int UNIT_HP_TRIG_NUM = 3; +//! Denominator of the low-HP retreat trigger. +static constexpr int UNIT_HP_TRIG_DEN = 10; + +//! Numerator for warriors' hunger retreat trigger: +//! `trigHungry = (hungry * 2) / 10` (≈20% remaining food). +static constexpr int UNIT_HUNGRY_TRIG_NUM_WARRIOR = 2; +//! Denominator for warriors' hunger retreat trigger. +static constexpr int UNIT_HUNGRY_TRIG_DEN = 10; + +//! Divisor for non-warriors' hunger retreat trigger: +//! `trigHungry = hungry / 4` (25% remaining food). +static constexpr int UNIT_HUNGRY_TRIG_DIVISOR_DEFAULT = 4; +//! Divisor for the carrying-a-resource hunger trigger: +//! `trigHungryCarying = hungry / 10` (10% remaining food). +static constexpr int UNIT_HUNGRY_TRIG_DIVISOR_CARRYING = 10; + +//! Vision radius (in tiles) granted to flying units; produces a 7x7 reveal +//! window centered on the unit. See Unit.cpp:310-313. +static constexpr int UNIT_VISION_RADIUS_FLY = 3; +//! Vision radius (in tiles) granted to ground units; produces a 3x3 reveal +//! window centered on the unit. See Unit.cpp:316-319. +static constexpr int UNIT_VISION_RADIUS_GROUND = 1; + +//! HP threshold below which a unit is considered dead. The check is +//! strictly `<`, so a unit with `hp == UNIT_HP_DEATH_THRESHOLD` is still +//! alive (UnitMedical.cpp:204). +static constexpr int UNIT_HP_DEATH_THRESHOLD = 0; + +//! Inverse fraction of HP missing that triggers an idle worker to seek +//! healing: `hp + (performance[HP] / N) < performance[HP]`, where +//! `N == UNIT_HEAL_TRIGGER_INV_RATIO`. UnitActivity.cpp:65. +static constexpr int UNIT_HEAL_TRIGGER_INV_RATIO = 10; + +//! Numerator of the "explorer must be ≥90% fed before exiting heal / +//! ≥90% healed before exiting feed" forced-rebound checks. +//! UnitMedical.cpp:173, 180. +static constexpr int EXPLORER_FORCE_FEED_RATIO_NUM = 9; +//! Denominator of the explorer ≥90% rebound check. +static constexpr int EXPLORER_FORCE_FEED_RATIO_DEN = 10; + +//! Magic-attack square radius (tiles) around the casting unit. Used as +//! the half-extent of the loop bounds in UnitMedical.cpp:114. +static constexpr int UNIT_MAGIC_ATTACK_RANGE = 3; + +//! Midpoint of the per-action `delta` window at which a warrior's swing +//! actually lands a hit: hits trigger when `delta` is in +//! [UNIT_ATTACK_HIT_DELTA, UNIT_ATTACK_HIT_DELTA + speed). With +//! `delta` in [0, UNIT_DELTA_QUANTUM), this is the second half of the +//! tick. See Unit.cpp:239. +static constexpr int UNIT_ATTACK_HIT_DELTA = 128; + + diff --git a/src/unit/UnitDisplacement.cpp b/src/unit/UnitDisplacement.cpp new file mode 100644 index 000000000..b6d5bab07 --- /dev/null +++ b/src/unit/UnitDisplacement.cpp @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Unit.h" +#include "Race.h" +#include "Team.h" +#include "Map.h" +#include "Game.h" + +#include "Building.h" +#include "Integrity.h" + +#include "Utilities.h" +#include "GlobalContainer.h" +#include +#include +#include + +void Unit::handleDisplacement(void) +{ + switch (activity) + { + case ACT_RANDOM: + { + if ((medical==MED_FREE)&&((displacement==DIS_RANDOM)||(displacement==DIS_REMOVING_BLACK_AROUND)||(displacement==DIS_ATTACKING_AROUND))) + { + if (performance[FLY]) + displacement=DIS_REMOVING_BLACK_AROUND; + else if (performance[ATTACK_SPEED]) + displacement=DIS_ATTACKING_AROUND; + } + else + displacement=DIS_RANDOM; + validTarget=false; + } + break; + + case ACT_FILLING: + { + assert(attachedBuilding); + assert(displacement!=DIS_RANDOM); + + if (verbose) + printf("guid=(%d) handleDisplacement() ACT_FILLING, displacement=%d\n", gid, displacement); + + if (displacement==DIS_GOING_TO_RESSOURCE) + { + if (auto off = owner->map->doesUnitTouchRessource(this, destinationPurpose)) + { + dx = off->dx; + dy = off->dy; + displacement=DIS_HARVESTING; + validTarget=false; + } + } + else if (displacement==DIS_HARVESTING) + { + // we got the ressource. + carriedRessource=destinationPurpose; + owner->map->decRessource(posX+dx, posY+dy, carriedRessource); + assert(movement == MOV_HARVESTING); + movement = MOV_RANDOM_GROUND; // we do this to avoid the handleMovement() to aditionaly decRessource() the same ressource. + + setTargetBuilding(attachedBuilding); + if (auto off = owner->map->doesUnitTouchBuilding(this, attachedBuilding->gid)) + { + dx = off->dx; + dy = off->dy; + displacement=DIS_FILLING_BUILDING; + validTarget=false; + } + else + { + displacement=DIS_GOING_TO_BUILDING; + targetX=targetBuilding->getMidX(); + targetY=targetBuilding->getMidY(); + validTarget=true; + } + } + else if (displacement==DIS_GOING_TO_BUILDING) + { + assert(targetBuilding); + if (auto off = owner->map->doesUnitTouchBuilding(this, targetBuilding->gid)) + { + dx = off->dx; + dy = off->dy; + displacement=DIS_FILLING_BUILDING; + validTarget=false; + } + } + else if (displacement==DIS_FILLING_BUILDING) + { + bool loopMove=false; + bool exchangeReady=false; + assert(targetBuilding); + if (targetBuilding==ownExchangeBuilding) + { + assert(targetBuilding); + assert(ownExchangeBuilding); + assert(targetBuilding->type->canExchange); + assert(ownExchangeBuilding->type->canExchange); + assert(owner==targetBuilding->owner); + assert(owner==ownExchangeBuilding->owner); + + assert(attachedBuilding); + assert(attachedBuilding->type->canFeedUnit); + assert(destinationPurpose>=HAPPYNESS_BASE); + + // Let's grab the right ressource. + + if (targetBuilding->ressources[destinationPurpose]>0) + { + targetBuilding->removeRessourceFromBuilding(destinationPurpose); + carriedRessource=destinationPurpose; + + setTargetBuilding(attachedBuilding); + displacement=DIS_GOING_TO_BUILDING; + targetX=targetBuilding->getMidX(); + targetY=targetBuilding->getMidY(); + validTarget=true; + exchangeReady=true; + if (verbose) + printf("guid=(%d) took a foreign fruit in our exhange building to food\n", gid); + } + } + else if ((carriedRessource>=0) && (targetBuilding->ressources[carriedRessource]type->maxRessource[carriedRessource])) + { + if (verbose) + printf("guid=(%d) Giving ressource (%d) to building gbid=(%d) old-amount=(%d)\n", gid, destinationPurpose, targetBuilding->gid, targetBuilding->ressources[carriedRessource]); + targetBuilding->addRessourceIntoBuilding(carriedRessource); + carriedRessource=UNIT_CARRIED_RESSOURCE_NONE; + } + + if (!loopMove && !exchangeReady) + { + //NOTE: if attachedBuilding has become NULL; it's beacause the building doesn't need me anymore. + if (!attachedBuilding) + { + if (verbose) + printf("guid=(%d) The building doesn't need me any more.\n", gid); + activity=ACT_RANDOM; + displacement=DIS_RANDOM; + validTarget=false; + assert(needToRecheckMedical); + } + else + { + ///Find a ressource that the building wants and a location to get it from + ///The location may be a market, or the harvesting the ressource from the + ///map. + int needs[MAX_NB_RESSOURCES]; + attachedBuilding->computeWishedRessources(needs); + int teamNumber=owner->teamNumber; + bool canSwim=performance[SWIM]; + int timeLeft = numberOfStepsLeftUntilHungry(); + if (timeLeft > 0) + { + int bestRessource=-1; + int minValue=owner->map->getW()+owner->map->getW(); + bool takeInExchangeBuilding=false; + Map* map=owner->map; + for (int r=0; r0) + { + int distToRessource; + if (map->ressourceAvailable(teamNumber, r, canSwim, posX, posY, &distToRessource)) + { + if ((distToRessource<<1)>=timeLeft) + continue; //We don't choose this ressource, because it won't have time to reach the ressource and bring it back. + int value=distToRessource/need; + if (valuetype->canFeedUnit) + for (std::list::iterator bi=owner->canExchange.begin(); bi!=owner->canExchange.end(); ++bi) + if ((*bi)->ressources[r]>0) + { + int buildingDist; + if (map->buildingAvailable(*bi, canSwim, posX, posY, &buildingDist)) + { + // We increase the cost to get a ressource in an exchange building to reflect the costs to get the ressources to the exchange building. + // increase is +5 as markets will in general be very close to fruits as they are the fruit teleporters. + int value=(buildingDist+5)/need; + if (value=0) + { + destinationPurpose=bestRessource; + assert(activity==ACT_FILLING); + if (takeInExchangeBuilding) + { + displacement=DIS_GOING_TO_BUILDING; + targetX=targetBuilding->getMidX(); + targetY=targetBuilding->getMidY(); + targetBuilding->insertUnitToHarvesting(this); + validTarget=true; + } + else + { + int dummyDist; + if (auto off = owner->map->doesUnitTouchRessource(this, destinationPurpose)) + { + dx = off->dx; + dy = off->dy; + displacement=DIS_HARVESTING; + validTarget=false; + } + else if (map->ressourceAvailableUpdate(teamNumber, destinationPurpose, canSwim, posX, posY, &targetX, &targetY, &dummyDist)) + { + displacement=DIS_GOING_TO_RESSOURCE; + validTarget=true; + } + else + { + assert(false);//You can remove this assert(), but *do* notice me! + stopAttachedForBuilding(false); + } + } + } + else + { + if (verbose) + printf("guid=(%d) can't find any wished ressource, unsubscribing.\n", gid); + stopAttachedForBuilding(false); + } + } + else + { + if (verbose) + printf("guid=(%d) not enough time for anything, unsubscribing.\n", gid); + stopAttachedForBuilding(false); + } + } + } + } + else + { + displacement=DIS_RANDOM; + validTarget=false; + } + } + break; + + case ACT_UPGRADING: + { + assert(attachedBuilding); + + if (displacement==DIS_GOING_TO_BUILDING) + { + if (auto off = owner->map->doesUnitTouchBuilding(this, attachedBuilding->gid)) + { + dx = off->dx; + dy = off->dy; + displacement=DIS_ENTERING_BUILDING; + validTarget=false; + } + } + else if (displacement==DIS_ENTERING_BUILDING) + { + // The unit has already its room in the building, + // then we are sure that the unit can enter. + + if (performance[FLY]) + owner->map->setAirUnit(posX-dx, posY-dy, NOGUID); + else + owner->map->setGroundUnit(posX-dx, posY-dy, NOGUID); + displacement=DIS_INSIDE; + validTarget=false; + + if (destinationPurpose==FEED) + { + insideTimeout=-attachedBuilding->type->timeToFeedUnit; + speed=attachedBuilding->type->insideSpeed; + } + else if (destinationPurpose==HEAL) + { + //insideTimeout=-(attachedBuilding->type->timeToHealUnit*(performance[HP]-hp))/performance[HP]; + insideTimeout=-attachedBuilding->type->timeToHealUnit; + speed=(attachedBuilding->type->insideSpeed*performance[HP])/(performance[HP]-hp); + } + else + { + int levelsToBeUpgraded=attachedBuilding->type->level+1-level[destinationPurpose]; + insideTimeout=-attachedBuilding->type->upgradeTime[destinationPurpose]; + speed=attachedBuilding->type->insideSpeed/levelsToBeUpgraded; + } + } + else if (displacement==DIS_INSIDE) + { + // we stay inside while the unit upgrades. + if (insideTimeout>=0) + { + //printf("Exiting building\n"); + displacement=DIS_EXITING_BUILDING; + validTarget=false; + + if (destinationPurpose==FEED) + { + hungry=HUNGRY_MAX; + fruitCount=attachedBuilding->eatOnce(&fruitMask); + needToRecheckMedical=true; + } + else if (destinationPurpose==HEAL) + { + hp=performance[HP]; + //printf("I'm healed : healt h %d/%d\n", hp, performance[HP]); + needToRecheckMedical=true; + } + else + { + if (attachedBuilding->type->upgradeInParallel) + { + for (int ability = (int)WALK; ability < (int)ARMOR; ability++) + if (canLearn[ability] && attachedBuilding->type->upgrade[ability]) + { + level[ability] = attachedBuilding->type->level + 1; + UnitType *ut = race->getUnitType(typeNum, level[ability]); + performance[ability] = ut->performance[ability]; + } + } + else + { + //printf("Ability %d got level %d\n", destinationPurpose, attachedBuilding->type->level+1); + assert(canLearn[destinationPurpose]); + level[destinationPurpose] = attachedBuilding->type->level + 1; + UnitType *ut = race->getUnitType(typeNum, level[destinationPurpose]); + performance[destinationPurpose] = ut->performance[destinationPurpose]; + //printf("New performance[%d]=%d\n", destinationPurpose, performance[destinationPurpose]); + } + + + } + } + else + { + insideTimeout++; + } + } + else if (displacement==DIS_EXITING_BUILDING) + { + // we want to get out, so we still stay in displacement==DIS_EXITING_BUILDING. + } + else + { + displacement=DIS_RANDOM; + validTarget=false; + } + } + break; + + case ACT_FLAG: + { + assert(attachedBuilding); + displacement=DIS_GOING_TO_FLAG; + targetX=attachedBuilding->posX; + targetY=attachedBuilding->posY; + validTarget=true; + int distance=owner->map->warpDistSquare(targetX, targetY, posX, posY); + int usr=attachedBuilding->unitStayRange; + int usr2=usr*usr; + if (verbose) + printf("guid=(%d) ACT_FLAG distance=%d, usr2=%d\n", gid, distance, usr2); + + if (distance<=usr2) + { + validTarget=false; + if (typeNum==WORKER) + displacement=DIS_CLEARING_RESSOURCES; + else if (typeNum==EXPLORER) + displacement=DIS_REMOVING_BLACK_AROUND; + else if (typeNum==WARRIOR) + displacement=DIS_ATTACKING_AROUND; + else + assert(false); + } + else if (typeNum==WORKER) + { + int usr2plus=1+(usr+1)*(usr+1); + if (distance<=usr2plus) + { + Map *map=owner->map; + for (int tdx=-1; tdx<=1; tdx++) + for (int tdy=-1; tdy<=1; tdy++) + { + int x=posX+tdx; + int y=posY+tdy; + if (map->warpDistSquare(x, y, targetX, targetY)<=usr2 + && map->isRessourceTakeable(x, y, attachedBuilding->clearingRessources)) + { + dx=tdx; + dy=tdy; + validTarget=false; + displacement=DIS_CLEARING_RESSOURCES; + //movement=MOV_HARVESTING; + return; + } + } + } + } + } + break; + + default: + { + assert(false); + break; + } + } +} + +bool Unit::locationIsInEnemyGuardTowerRange(int x, int y)const +{ + //TODO: totally fix this totally hacky implementation. + for(int i=0;igame->teams[i]; + if((t)&&(owner->enemies & t->me)) + { + for(int j=0;jmyBuildings[j]; + if((b)&&(b->shortTypeNum==IntBuildingType::DEFENSE_BUILDING)&&(owner->map->warpDistMax(b->posX,b->posY,posX,posY) <= b->type->shootingRange + 1))return true; + } + } + } + return false; +} diff --git a/src/unit/UnitGeometry.cpp b/src/unit/UnitGeometry.cpp new file mode 100644 index 000000000..4ba77b0e0 --- /dev/null +++ b/src/unit/UnitGeometry.cpp @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Unit.h" +#include "Race.h" +#include "Team.h" +#include "Map.h" +#include "Game.h" + +#include "Building.h" +#include "Integrity.h" + +#include "Utilities.h" +#include "GlobalContainer.h" +#include +#include +#include + +void Unit::setNewValidDirectionGround(void) +{ + assert(!performance[FLY]); + int i=0; + bool swim=(performance[SWIM]>0); + Uint32 me=owner->me; + while ( i<8 && !owner->map->isFreeForGroundUnit(posX+dx, posY+dy, swim, me)) + { + direction=(direction+1)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + i++; + } + if (i==UNIT_DIRECTION_COUNT) + { + direction=UNIT_DIRECTION_NONE; + dxDyFromDirection(); + } +} + +void Unit::setNewValidDirectionAir(void) +{ + assert(performance[FLY]); + int i=0; + while ( i<8 && !owner->map->isFreeForAirUnit(posX+dx, posY+dy)) + { + direction=(direction+1)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + i++; + } + if (i==UNIT_DIRECTION_COUNT) + { + direction=UNIT_DIRECTION_NONE; + dx=0; + dy=0; + } +} + +void Unit::flyToTarget() +{ + assert(performance[FLY]); + int ldx=targetX-posX; + int ldy=targetY-posY; + simplifyDirection(ldx, ldy, &dx, &dy); + directionFromDxDy(); + Map *map=owner->map; + if (map->isFreeForAirUnit(posX+dx, posY+dy)) + return; + int cDirection=direction; + direction=(cDirection+1)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForAirUnit(posX+dx, posY+dy)) + return; + direction=(cDirection+7)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForAirUnit(posX+dx, posY+dy)) + return; + direction=(cDirection+2)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForAirUnit(posX+dx, posY+dy)) + return; + direction=(cDirection+6)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForAirUnit(posX+dx, posY+dy)) + return; + direction=(cDirection+3)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForAirUnit(posX+dx, posY+dy)) + return; + direction=(cDirection+5)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForAirUnit(posX+dx, posY+dy)) + return; + direction=(cDirection+4)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForAirUnit(posX+dx, posY+dy)) + return; + dx=0; + dy=0; + direction=UNIT_DIRECTION_NONE; + if (verbose) + printf("guid=(%d) flyto failed pos=(%d, %d) \n", gid, posX, posY); +} + + +void Unit::escapeGroundTarget() +{ + int ldx=posX-targetX; + int ldy=posY-targetY; + simplifyDirection(ldx, ldy, &dx, &dy); + directionFromDxDy(); + bool canSwim=performance[SWIM]; + Map *map=owner->map; + if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) + return; + int cDirection=direction; + direction=(cDirection+1)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) + return; + direction=(cDirection+7)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) + return; + direction=(cDirection+2)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) + return; + direction=(cDirection+6)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) + return; + direction=(cDirection+3)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) + return; + direction=(cDirection+5)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) + return; + direction=(cDirection+4)&UNIT_DIRECTION_MASK; + dxDyFromDirection(); + if (map->isFreeForGroundUnitNoForbidden(posX+dx, posY+dy, canSwim)) + return; + dx=0; + dy=0; + direction=UNIT_DIRECTION_NONE; + if (verbose) + printf("guid=(%d) escapeGroundTarget failed pos=(%d, %d) \n", gid, posX, posY); +} + +void Unit::endOfAction(void) +{ + handleMedical(); + if (isDead) + return; + handleActivity(); + handleDisplacement(); + handleMovement(); + handleAction(); +} + +// NOTE : position 0 is top left (-1, -1) then run clockwise + +void Unit::directionFromDxDy(void) +{ + const int tab[3][3]={ {0, 1, 2}, + {7, 8, 3}, + {6, 5, 4} }; + assert(dx>=-1); + assert(dx<=1); + assert(dy>=-1); + assert(dy<=1); + direction=tab[dy+1][dx+1]; +} + +void Unit::dxDyFromDirection(void) +{ + dxDyFromDirection(direction,&dx,&dy); +} + +int Unit::directionFromDxDy(int dx, int dy) +{ + const int tab[3][3]={ {0, 1, 2}, + {7, 8, 3}, + {6, 5, 4} }; + assert(dx>=-1); + assert(dx<=1); + assert(dy>=-1); + assert(dy<=1); + return tab[dy+1][dx+1]; +} + +void Unit::simplifyDirection(int ldx, int ldy, int *cdx, int *cdy) +{ + int mapW=owner->map->getW(); + int mapH=owner->map->getH(); + if (ldx>(mapW>>1)) + ldx-=mapW; + else if (ldx<-(mapW>>1)) + ldx+=mapW; + if (ldy>(mapH>>1)) + ldy-=mapH; + else if (ldy<-(mapH>>1)) + ldy+=mapH; + + /* We consider a cell to be vertical or horizontal in + direction (rather than diagonal) if it is 2.41 times more + vertical than horizontal, or vice versa. This is because + the halfway point between 45 degrees and 90 degrees is 67.5 + degrees and sin(67.5 deg) / cos(67.5 deg) = + 2.41421356237. */ + if ((100 * abs(ldx)) > (241 * abs(ldy))) + { + *cdx=SIGN(ldx); + *cdy=0; + } + else if ((100 * abs(ldy)) > (241 * abs(ldx))) + { + *cdx=0; + *cdy=SIGN(ldy); + } + else + { + *cdx=SIGN(ldx); + *cdy=SIGN(ldy); + } +} diff --git a/src/unit/UnitMedical.cpp b/src/unit/UnitMedical.cpp new file mode 100644 index 000000000..f56b22533 --- /dev/null +++ b/src/unit/UnitMedical.cpp @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Unit.h" +#include "Race.h" +#include "Team.h" +#include "Map.h" +#include "Game.h" + +#include "Building.h" +#include "Integrity.h" + +#include "Utilities.h" +#include "GlobalContainer.h" +#ifndef YOG_SERVER_ONLY +#include "render/GameAnimations.h" +#endif // !YOG_SERVER_ONLY +#include +#include +#include + +void Unit::selectPreferredMovement(void) +{ + if (performance[FLY]) + action=FLY; + else if ((performance[SWIM]) && (owner->map->isWater(posX, posY)) ) + action=SWIM; + else if ((performance[WALK]) && (!owner->map->isWater(posX, posY)) ) + action=WALK; + else + assert(false); +} + +void Unit::selectPreferredGroundMovement(void) +{ + assert(!performance[FLY]); + if ((performance[SWIM]) && (owner->map->isWater(posX, posY)) ) + action=SWIM; + else if ((performance[WALK]) && (!owner->map->isWater(posX, posY)) ) + action=WALK; + else + assert(false); +} + +bool Unit::isUnitHungry(void) +{ + int realTrigHungry; + if (carriedRessource==-1) + realTrigHungry=trigHungry; + else + realTrigHungry=trigHungryCarying; + + return (hungry<=realTrigHungry); +} + +void Unit::standardRandomActivity() +{ + attachedBuilding=NULL; + setTargetBuilding(NULL); + ownExchangeBuilding=NULL; + activity=Unit::ACT_RANDOM; + displacement=Unit::DIS_RANDOM; + validTarget=false; + needToRecheckMedical=true; +} + +void Unit::stopAttachedForBuilding(bool goingInside) +{ + if (verbose) + printf("guid=(%d) stopAttachedForBuilding()\n", gid); + assert(attachedBuilding); + + if (goingInside) + { + attachedBuilding->removeUnitFromInside(this); + if (activity==ACT_UPGRADING) + { + assert(displacement==DIS_GOING_TO_BUILDING); + if (destinationPurpose==HEAL || destinationPurpose==FEED) + needToRecheckMedical=true; + } + } + else + { + for (std::list::iterator it=attachedBuilding->unitsInside.begin(); it!=attachedBuilding->unitsInside.end(); ++it) + assert(*it!=this); + } + + activity=ACT_RANDOM; + displacement=DIS_RANDOM; + validTarget=false; + + attachedBuilding->removeUnitFromWorking(this); + attachedBuilding=NULL; + setTargetBuilding(NULL); + ownExchangeBuilding=NULL; + assert(needToRecheckMedical); +} + +void Unit::handleMagic(void) +{ + assert(medical==MED_FREE); + assert((displacement!=DIS_ENTERING_BUILDING) && (displacement!=DIS_INSIDE) && (displacement!=DIS_EXITING_BUILDING)); + + magicActionTimeout--; + if (magicActionTimeout > 0) + return; + + Map *map = &owner->game->map; + Team **teams = owner->game->teams; + + bool hasUsedMagicAction = false; + if (performance[MAGIC_ATTACK_AIR] || performance[MAGIC_ATTACK_GROUND]) + { + std::set damagedBuildings; + damagedBuildings.insert(NOGBID); + constexpr int ATTACK_RANGE = UNIT_MAGIC_ATTACK_RANGE; + for (int yi=posY-ATTACK_RANGE; yi<=posY+ATTACK_RANGE; yi++) + for (int xi=posX-ATTACK_RANGE; xi<=posX+ATTACK_RANGE; xi++) + { + // damaging enemy units: + for (int altitude=0; altitude<2; altitude++) + { + Uint16 targetGUID; + Sint32 attackForce; + if ((altitude == 1) && performance[MAGIC_ATTACK_AIR]) + { + targetGUID = map->getAirUnit(xi, yi); + attackForce = performance[MAGIC_ATTACK_AIR]; + } + else if ((altitude == 0) && performance[MAGIC_ATTACK_GROUND]) + { + targetGUID = map->getGroundUnit(xi, yi); + attackForce = performance[MAGIC_ATTACK_GROUND]; + } + else + continue; + if (targetGUID != NOGUID) + { + Sint32 targetTeam = Unit::GIDtoTeam(targetGUID); + Uint16 targetID = Unit::GIDtoID(targetGUID); + Uint32 targetTeamMask = 1<enemies & targetTeamMask) + { + Unit *enemyUnit = teams[targetTeam]->myUnits[targetID]; + Sint32 damage = attackForce + experienceLevel - enemyUnit->getRealArmor(true); + if (damage > 0) + { + enemyUnit->hp -= damage; + + enemyUnit->owner->pushGameEvent(GameEvent::unitUnderAttack(owner->game->stepCounter, xi, yi, enemyUnit->typeNum)); + + incrementExperience(damage); + magicActionAnimation = MAGIC_ACTION_ANIMATION_FRAME_COUNT; + hasUsedMagicAction = true; + } + } + } + } + + // damaging enemy buildings: this has been removed for balance purposes + } + + Sint32 magicLevel = std::max(level[MAGIC_ATTACK_AIR], level[MAGIC_ATTACK_GROUND]); + if (hasUsedMagicAction) + magicActionTimeout = race->getUnitType(typeNum, level[magicLevel])->magicActionCooldown; + } +} + +void Unit::handleMedical(void) +{ + /* Make sure explorers try to immediately feed after healing to increase their range. */ + if ((typeNum == EXPLORER) && (displacement == DIS_EXITING_BUILDING)) + { + medical=MED_FREE; + if ((destinationPurpose == HEAL) && (hungry < ((HUNGRY_MAX * EXPLORER_FORCE_FEED_RATIO_NUM) / EXPLORER_FORCE_FEED_RATIO_DEN))) + { + // fprintf (stderr, "forcing explorer hunger: gid: %d, hungry: %d\n", gid, hungry); + needToRecheckMedical = 1; + medical = MED_HUNGRY; + return; + } + else if ((destinationPurpose == FEED) && (hp < (((performance[HP]) * EXPLORER_FORCE_FEED_RATIO_NUM) / EXPLORER_FORCE_FEED_RATIO_DEN))) + { + // fprintf (stderr, "forcing explorer healing: gid: %d, hp: %d\n", gid, hp); + needToRecheckMedical = 1; + medical = MED_DAMAGED; + return; + } + } + + if ((displacement==DIS_ENTERING_BUILDING) || (displacement==DIS_INSIDE) || (displacement==DIS_EXITING_BUILDING)) + return; + + if (verbose) + printf("guid=(%d) handleMedical...\n", gid); + hungry -= hungryness; + if (hungry<=0) + hp--; + + medical=MED_FREE; + if (isUnitHungry()) + medical=MED_HUNGRY; + else if (hp<=trigHP) + medical=MED_DAMAGED; + + if (hpremoveUnitFromWorking(this); + attachedBuilding->removeUnitFromInside(this); + attachedBuilding=NULL; + ownExchangeBuilding=NULL; + } + setTargetBuilding(NULL); + // //TODO: in beta4 this line was ommitted. delete? + // ownExchangeBuilding=NULL; + + activity=ACT_RANDOM; + validTarget=false; + + // remove from map + if (performance[FLY]) + owner->map->setAirUnit(posX, posY, NOGUID); + else + owner->map->setGroundUnit(posX, posY, NOGUID); + + if (previousClearingArea) + { + owner->map->setClearingAreaUnclaimed(previousClearingArea->x, previousClearingArea->y, owner->teamNumber); + } + owner->map->clearImmobileUnit(posX, posY); + + // generate death animation (no-op in headless mode) + owner->game->animations->onUnitDeath(*owner->map, posX, posY, owner); + } + isDead = true; + } +} diff --git a/src/unit/UnitMovement.cpp b/src/unit/UnitMovement.cpp new file mode 100644 index 000000000..a73462b63 --- /dev/null +++ b/src/unit/UnitMovement.cpp @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Unit.h" +#include "Race.h" +#include "Team.h" +#include "Map.h" +#include "Game.h" + +#include "Building.h" +#include "Integrity.h" + +#include "FixedPoint.h" +#include "MapInternal.h" +#include "Utilities.h" +#include "GlobalContainer.h" +#include +#include +#include + +void Unit::handleMovement(void) +{ + // Release any clearing-area claim from a prior tick before this unit decides + // what to do this tick. Note: distance is intentionally NOT reset here — it + // must survive the per-tick reset (see Unit.h declaration comment). + if (previousClearingArea) + { + owner->map->setClearingAreaUnclaimed(previousClearingArea->x, previousClearingArea->y, owner->teamNumber); + previousClearingArea.reset(); + } + + if (tryClaimClearingAreaForHarvesting()) + return; + + switch (displacement) + { + case DIS_REMOVING_BLACK_AROUND: + handleMovementRemovingBlackAround(); + break; + case DIS_ATTACKING_AROUND: + handleMovementAttackingAround(); + break; + case DIS_CLEARING_RESSOURCES: + handleMovementClearingResources(); + break; + case DIS_RANDOM: + handleMovementRandom(); + break; + case DIS_GOING_TO_FLAG: + case DIS_GOING_TO_BUILDING: + handleMovementGoingToFlagOrBuilding(); + break; + case DIS_ENTERING_BUILDING: + handleMovementEnteringBuilding(); + break; + case DIS_INSIDE: + handleMovementInside(); + break; + case DIS_EXITING_BUILDING: + handleMovementExitingBuilding(); + break; + case DIS_GOING_TO_RESSOURCE: + handleMovementGoingToRessource(); + break; + case DIS_HARVESTING: + handleMovementHarvesting(); + break; + case DIS_FILLING_BUILDING: + handleMovementFillingBuilding(); + break; + default: + assert(false); + break; + } +} + +bool Unit::tryClaimClearingAreaForHarvesting() +{ + // clearArea code, override behavior locally + if (typeNum == WORKER && + medical == MED_FREE && + (displacement == DIS_RANDOM + || displacement == DIS_GOING_TO_FLAG + || displacement == DIS_GOING_TO_RESSOURCE + || displacement == DIS_GOING_TO_BUILDING)) + { + Map *map = owner->map; + // TODO : be sure this is the right thing to do and add a decent comment + if (movement == MOV_HARVESTING) + { + map->decRessource(posX + dx, posY + dy); + hp -= race->getUnitType(typeNum, level[HARVEST])->harvestDamage; + } + for (int tdx = -1; tdx <= 1; tdx++) + for (int tdy = -1; tdy <= 1; tdy++) + { + int x = (posX + tdx) & map->wMask; + int y = (posY + tdy) & map->hMask; + Case mapCase = map->cases[(y << map->wDec) + x]; + if ((mapCase.clearArea & owner->me) + && (mapCase.ressource.type != NO_RES_TYPE) + && globalContainer->ressourcesTypes.get(mapCase.ressource.type)->clearable + && !(mapCase.forbidden & owner->me)) + { + owner->map->setClearingAreaClaimed(posX+tdx, posY+tdy, owner->teamNumber, gid); + previousClearingArea = ClearingAreaClaim{ + static_cast((posX+tdx) & map->wMask), + static_cast((posY+tdy) & map->hMask) + }; + dx = tdx; + dy = tdy; + movement = MOV_HARVESTING; + return true; + } + } + } + return false; +} + +void Unit::handleMovementRemovingBlackAround() +{ + assert(performance[FLY]); + if (attachedBuilding) + { + movement=MOV_GOING_DX_DY; + int bposX=attachedBuilding->posX; + int bposY=attachedBuilding->posY; + + int ldx=bposX-posX; + int ldy=bposY-posY; + int cdx, cdy; + simplifyDirection(ldx, ldy, &cdx, &cdy); + + dx=-cdy; + dy=cdx; + if (!owner->map->isMapDiscovered(posX+4*cdx, posY+4*cdy, owner->sharedVisionOther)) + { + dx=cdx; + dy=cdy; + } + } + else if ((movement!=MOV_GOING_DX_DY)||((syncRand()&0xFF)<0xEF)) + { + // "c" is the center of the unit, "x" are the sample spots: + // oxoooxo + //ooooooooo + //xooooooox + //ooooooooo + //oooocoooo + //ooooooooo + //xooooooox + //ooooooooo + // oxoooxo + bool found = false; + const int dxTab[8] = {-4, -2, +2, +4, +4, +2, -2, -4}; + const int dyTab[8] = {-2, -4, -4, -2, +2, +4, +4, +2}; + int tab[8]; + for (int i = 0; i < 8; i++) + { + tab[i] = owner->map->getExplored(posX + dxTab[i], posY + dyTab[i], owner->teamNumber); + //also move around enemy towers: + if(locationIsInEnemyGuardTowerRange(posX + dxTab[i], posY + dyTab[i]))tab[i]=1; + } + for (int di = 0; di < 8; di++) + { + int d = (di + direction + 4) % 8; + //Move in a direction in which you circle counter-clockwise + //about explored area, while exploring. + if ((tab[d] > 0) && (tab[(d + 1) % 8] == 0) && (tab[(d + 2) % 8] == 0)) + { + direction = (d + 1) % 8; + dxDyFromDirection(); + movement = MOV_GOING_DX_DY; + found = true; + break; + } + } + if (!found) + { + int scoreX = 0; + int scoreY = 0; + /* The next line should really be calculated only once per game. How to do this? The point is to avoid wrapping around the torus in considering what area is closer to us. */ + int maxRange = (std::min(owner->map->getW(), owner->map->getH())) / 2; + /* We sample cells at various + distances to decide in what + direction there is more + unexplored territory. */ + for (int range = 1; range <= maxRange; range *= 2) + { + for (int delta = -3; delta <= 3; delta++) + { + scoreX += owner->map->getExplored(posX - (4*range), posY + (delta*range), owner->teamNumber); + scoreX -= owner->map->getExplored(posX + (4*range), posY + (delta*range), owner->teamNumber); + scoreY += owner->map->getExplored(posX + (delta*range), posY - (4*range), owner->teamNumber); + scoreY -= owner->map->getExplored(posX + (delta*range), posY + (4*range), owner->teamNumber); + } + } + int cdx, cdy; + simplifyDirection(scoreX, scoreY, &cdx, &cdy); + + if (cdx == 0 && cdy == 0) + movement = MOV_RANDOM_FLY; + else + { + dx = cdx; + dy = cdy; + directionFromDxDy(); + movement = MOV_GOING_DX_DY; + } + } + } + if (movement!=MOV_GOING_DX_DY || owner->map->getAirUnit(posX+dx, posY+dy)!=NOGUID) + movement=MOV_RANDOM_FLY; +} + +void Unit::handleMovementAttackingAround() +{ + assert(performance[ATTACK_SPEED]); + int quality=INT_MAX; // Smaller is better. + movement=MOV_RANDOM_GROUND; + + ///Don't change targets if we still have a valid target + if (auto off = owner->map->doesUnitTouchEnemy(this)) + { + dx = off->dx; + dy = off->dy; + targetX = posX+dx; + targetY = posY+dy; + movement=MOV_ATTACKING_TARGET; + } + else + { + // we look for the best target to attack around us + for (int x=-UNIT_ATTACK_SEARCH_RADIUS; x<=UNIT_ATTACK_SEARCH_RADIUS; x++) + { + for (int y=-UNIT_ATTACK_SEARCH_RADIUS; y<=UNIT_ATTACK_SEARCH_RADIUS; y++) + { + if (owner->map->isFOWDiscovered(posX+x, posY+y, owner->sharedVisionOther)) + { + if (attachedBuilding && + owner->map->warpDistSquare(posX+x, posY+y, attachedBuilding->posX, attachedBuilding->posY) + >((int)attachedBuilding->unitStayRange*(int)attachedBuilding->unitStayRange)) + continue; + Uint16 gid; + gid=owner->map->getBuilding(posX+x, posY+y); + if (gid!=NOGBID) + { + int team=Building::GIDtoTeam(gid); + if (owner->enemies & (1<game->teams[team]->myBuildings[id]; + BuildingType *bt=b->type; + int shootDamage=bt->shootDamage; + newQuality/=(1+shootDamage); + tryAcquireAttackTarget(x, y, newQuality, quality); + } + } + gid=owner->map->getGroundUnit(posX+x, posY+y); + if (gid!=NOGUID) + { + int team=Unit::GIDtoTeam(gid); + Uint32 tm=(1<enemies & tm) + { + int id=Building::GIDtoID(gid); + Unit *u=owner->game->teams[team]->myUnits[id]; + if (((owner->sharedVisionExchange & tm)==0)) + { + int attackStrength=u->getRealAttackStrength(); + int newQuality=((x*x+y*y)<map->pathfindArea(Map::AreaKind::Guard, owner->teamNumber, (performance[SWIM]>0), posX, posY, &dx, &dy)) + { + directionFromDxDy(); + movement = MOV_GOING_DX_DY; + // get the target position of guard area for display + owner->map->getGlobalGradientDestination(owner->map->guardAreasGradient[owner->teamNumber][performance[SWIM]>0], posX, posY, &targetX, &targetY); + validTarget=true; + } + else if (attachedBuilding || (owner->map->getGuardAreasGradient(posX, posY, performance[SWIM]>0, owner->teamNumber) == GRADIENT_AT_GOAL)) + { + // are we into the guard area or war flag, and we have to go to the least known area. + int bestExplored = 3*GRADIENT_AT_GOAL; + int bestDirection = -1; + for (int di = 0; di < 8; di++) + { + int d = (direction + di) & UNIT_DIRECTION_MASK; + int cdx, cdy; + dxDyFromDirection(d, &cdx, &cdy); + if (!owner->map->isFreeForGroundUnit(posX + cdx, posY + cdy, performance[SWIM]>0, owner->me)) + continue; + if (attachedBuilding) + { + if (owner->map->warpDistSquare(posX + cdx, posY + cdy, attachedBuilding->posX, attachedBuilding->posY) + > ((int)attachedBuilding->unitStayRange * (int)attachedBuilding->unitStayRange)) + continue; + } + else + { + if (owner->map->getGuardAreasGradient(posX + cdx, posY + cdy, performance[SWIM]>0, owner->teamNumber) != GRADIENT_AT_GOAL) + continue; + } + Uint8 explored = owner->map->getExplored(posX + 2*cdx, posY + 2*cdy, owner->teamNumber); + explored += owner->map->getExplored(posX + 2*cdx - cdy, posY + 2*cdy + cdx, owner->teamNumber); + explored += owner->map->getExplored(posX + 2*cdx + cdy, posY + 2*cdy - cdx, owner->teamNumber); + if (bestExplored > explored) + { + bestExplored = explored; + bestDirection = d; + } + } + if (bestDirection >= 0) + { + direction = bestDirection; + dxDyFromDirection(); + movement = MOV_GOING_DX_DY; + validTarget = false; + } + else + { + movement = MOV_RANDOM_GROUND; + validTarget = false; + } + } + else + { + // this case happens when no movement could be found because of busy places or because we are in a guard area or because there is no guard area + movement = MOV_RANDOM_GROUND; + validTarget = false; + } + } +} + +void Unit::tryAcquireAttackTarget(int x, int y, int newQuality, int& quality) +{ + if (newQuality >= quality) + return; + bool pathfind = owner->map->pathfindPointToPoint(posX, posY, posX+x, posY+y, &dx, &dy, (performance[SWIM] > 0 ? true : false), owner->me, GOING_TARGET_MAX_PATH_LENGTH); + if (!pathfind) + return; + if (abs(x)<=1 && abs(y)<=1) + { + movement=MOV_ATTACKING_TARGET; + dx=x; + dy=y; + } + else + { + movement=MOV_GOING_TARGET; + } + targetX=posX+x; + targetY=posY+y; + validTarget=true; + quality=newQuality; +} + +void Unit::handleMovementClearingResources() +{ + Map *map=owner->map; + if (movement==MOV_HARVESTING) + { + map->decRessource(posX+dx, posY+dy); + hp -= race->getUnitType(typeNum, level[HARVEST])->harvestDamage; + } + + int bx=attachedBuilding->posX; + int by=attachedBuilding->posY; + int usr=attachedBuilding->unitStayRange; + int usr2=usr*usr; + for (int tdx=-1; tdx<=1; tdx++) + for (int tdy=-1; tdy<=1; tdy++) + { + int x=posX+tdx; + int y=posY+tdy; + if (map->warpDistSquare(x, y, bx, by)<=usr2 && map->isRessourceTakeable(x, y, attachedBuilding->clearingRessources) && !(owner->map->isForbidden(x, y, owner->me))) + { + dx=tdx; + dy=tdy; + movement=MOV_HARVESTING; + return; + } + } + bool canSwim=performance[SWIM]; + assert(attachedBuilding); + if (map->pathfindLocalRessource(attachedBuilding, canSwim, posX, posY, &dx, &dy)) + { + directionFromDxDy(); + movement=MOV_GOING_DX_DY; + } + else if (attachedBuilding->anyRessourceToClear[canSwim]==2) + { + stopAttachedForBuilding(false); + movement=MOV_RANDOM_GROUND; + } + else + movement=MOV_RANDOM_GROUND; +} + +void Unit::handleMovementRandom() +{ + Map *map=owner->map; + std::optional enemyOff; + if (performance[ATTACK_SPEED] && medical==MED_FREE) + enemyOff = map->doesUnitTouchEnemy(this); + if (enemyOff) + { + dx = enemyOff->dx; + dy = enemyOff->dy; + movement=MOV_ATTACKING_TARGET; + } + else if (performance[FLY]) + movement=MOV_RANDOM_FLY; + else if (map->getForbidden(posX, posY)&owner->me) + { + if (map->pathfindForbidden(NULL, owner->teamNumber, (performance[SWIM]>0), posX, posY, &dx, &dy)) + directionFromDxDy(); + else + { + dx=0; + dy=0; + direction=UNIT_DIRECTION_NONE; + } + movement=MOV_GOING_DX_DY; + } + else if(performance[HARVEST]) + { + // g==0: on obstacle. g==1: chamfer never propagated here, so no clearing + // area reachable from this cell. Both cases mean "nothing found". + Uint8 g = owner->map->getClearingGradient(owner->teamNumber, performance[SWIM]>0, posX, posY); + int distance = GRADIENT_AT_GOAL - g; + if(g > GRADIENT_UNREACHABLE && distance < ((hungry-trigHungry) / race->hungryness) && medical == MED_FREE) + { + int tempTargetX, tempTargetY; + bool path = owner->map->getGlobalGradientDestination(owner->map->clearAreasGradient[owner->teamNumber][performance[SWIM]>0], posX, posY, &tempTargetX, &tempTargetY); + int guid = owner->map->isClearingAreaClaimed(tempTargetX, tempTargetY, owner->teamNumber); + int other_distance = INT_MAX; + if(guid != NOGUID) + { + Unit* unit = owner->myUnits[GIDtoID(guid)]; + if(unit) + other_distance = unit->previousClearingAreaDistance; + } + if(path && distance < other_distance) + { + dx=0; + dy=0; + owner->map->pathfindArea(Map::AreaKind::Clear, owner->teamNumber, (performance[SWIM]>0), posX, posY, &dx, &dy); + + targetX = tempTargetX; + targetY = tempTargetY; + previousClearingArea = ClearingAreaClaim{ + static_cast(tempTargetX), + static_cast(tempTargetY) + }; + previousClearingAreaDistance = distance; + + if(guid != NOGUID) + { + Unit* unit = owner->myUnits[GIDtoID(guid)]; + if(unit) + { + unit->previousClearingArea.reset(); + unit->previousClearingAreaDistance=UNIT_CLEAR_AREA_DISTANCE_NONE; + } + } + + //Find clearing resource + directionFromDxDy(); + movement = MOV_GOING_DX_DY; + owner->map->setClearingAreaClaimed(targetX, targetY, owner->teamNumber, gid); + validTarget=true; + } + else + movement=MOV_RANDOM_GROUND; + } + else + movement=MOV_RANDOM_GROUND; + } + else + movement=MOV_RANDOM_GROUND; +} + +void Unit::handleMovementGoingToFlagOrBuilding() +{ + Map *map=owner->map; + bool canSwim=performance[SWIM]; + + std::optional enemyOff; + if (performance[ATTACK_SPEED] && medical==MED_FREE) + enemyOff = map->doesUnitTouchEnemy(this); + if (enemyOff) + { + dx = enemyOff->dx; + dy = enemyOff->dy; + movement=MOV_ATTACKING_TARGET; + } + else if (performance[FLY]) + { + movement=MOV_FLYING_TARGET; + } + else if (map->pathfindBuilding(targetBuilding, canSwim, posX, posY, &dx, &dy)) + { + movement=MOV_GOING_DX_DY; + } + else + { + stopAttachedForBuilding(true); + movement=MOV_RANDOM_GROUND; + } +} + +void Unit::handleMovementEnteringBuilding() +{ + movement=MOV_ENTERING_BUILDING; +} + +void Unit::handleMovementInside() +{ + movement=MOV_INSIDE; +} + +void Unit::handleMovementExitingBuilding() +{ + bool exitFound; + if (performance[FLY]) + exitFound=attachedBuilding->findAirExit(&posX, &posY, &dx, &dy); + else + exitFound=attachedBuilding->findGroundExit(&posX, &posY, &dx, &dy, performance[SWIM]); + if (exitFound) + { + activity=ACT_RANDOM; + movement=MOV_EXITING_BUILDING; + attachedBuilding->removeUnitFromInside(this); + attachedBuilding->updateConstructionState(); + attachedBuilding=NULL; + setTargetBuilding(NULL); + assert(ownExchangeBuilding==NULL); + assert(needToRecheckMedical); + } + else + { + movement=MOV_INSIDE; + } +} + +void Unit::handleMovementGoingToRessource() +{ + Map *map=owner->map; + int teamNumber=owner->teamNumber; + bool canSwim=performance[SWIM]>0; + bool stopWork; + if (map->pathfindRessource(teamNumber, destinationPurpose, canSwim, posX, posY, &dx, &dy, &stopWork)) + { + directionFromDxDy(); + movement=MOV_GOING_DX_DY; + } + else + { + if (stopWork) + stopAttachedForBuilding(false); + movement=MOV_RANDOM_GROUND; + } +} + +void Unit::handleMovementHarvesting() +{ + movement=MOV_HARVESTING; +} + +void Unit::handleMovementFillingBuilding() +{ + movement=MOV_FILLING; +} diff --git a/src/unit/UnitSerialization.cpp b/src/unit/UnitSerialization.cpp new file mode 100644 index 000000000..e9df4a185 --- /dev/null +++ b/src/unit/UnitSerialization.cpp @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Unit.h" +#include "Race.h" +#include "Team.h" +#include "Map.h" +#include "Game.h" + +#include "Building.h" +#include "Integrity.h" + +#include "FileFormatVersions.h" +#include "Utilities.h" +#include "GlobalContainer.h" +#include +#include +#include + +void Unit::load(GAGCore::InputStream *stream, Team *owner, Sint32 versionMinor) +{ + stream->readEnterSection("Unit"); + + // unit specification + typeNum = stream->readSint32("typeNum"); + if (versionMinor < FILE_FORMAT_VERSION_DROP_UNIT_SKIN_NAME) + { + // Pre-v84 saves carried a per-unit skinName string; skin is now derived + // from typeNum at render time, so read and discard for compatibility. + stream->readText("skinName"); + } + race = &(owner->race); + assert(race); + + // identity + gid = stream->readUint16("gid"); + this->owner = owner; + isDead = stream->readSint32("isDead"); + + // position + posX = stream->readSint32("posX"); + posY = stream->readSint32("posY"); + delta = stream->readSint32("delta"); + dx = stream->readSint32("dx"); + dy = stream->readSint32("dy"); + direction = stream->readSint32("direction"); + insideTimeout = stream->readSint32("insideTimeout"); + speed = stream->readSint32("speed"); + + // states + needToRecheckMedical = (bool)stream->readUint32("needToRecheckMedical"); + medical = (Medical)stream->readUint32("medical"); + activity = (Activity)stream->readUint32("activity"); + displacement = (Displacement)stream->readUint32("displacement"); + movement = (Movement)stream->readUint32("movement"); + action = (Abilities)stream->readUint32("action"); + targetX = (Sint32)stream->readSint32("targetX"); + targetY = (Sint32)stream->readSint32("targetY"); + validTarget = (bool)stream->readSint32("validTarget"); + magicActionTimeout = stream->readSint32("magicActionTimeout"); + + // under attack timer + if(versionMinor >= FILE_FORMAT_VERSION_UNDER_ATTACK_TIMER) + underAttackTimer = stream->readUint8("underAttackTimer"); + else + underAttackTimer = 0; + + + // trigger parameters + hp = stream->readSint32("hp"); + trigHP = stream->readSint32("trigHP"); + + // hungry + hungry = stream->readSint32("hungry"); + hungryness = stream->readSint32("hungryness"); + trigHungry = stream->readSint32("trigHungry"); + trigHungryCarying = (trigHungry*4)/10; + fruitMask = stream->readUint32("fruitMask"); + fruitCount = stream->readUint32("fruitCount"); + + // quality parameters + stream->readEnterSection("abilities"); + for (int i=0; ireadEnterSection(i); + performance[i] = stream->readSint32("performance"); + level[i] = stream->readSint32("level"); + canLearn[i] = (bool)stream->readUint32("canLearn"); + stream->readLeaveSection(); + } + stream->readLeaveSection(); + + + experience = stream->readSint32("experience"); + experienceLevel = stream->readSint32("experienceLevel"); + + destinationPurpose = stream->readSint32("destinationPurpose"); + carriedRessource = stream->readSint32("carriedRessource"); + + jobTimer = stream->readSint32("jobTimer"); + + previousClearingArea=std::nullopt; + previousClearingAreaDistance=0; + + // gui + levelUpAnimation = 0; + magicActionAnimation = 0; + jobTimer = 0; + + verbose = false; + + stream->readLeaveSection(); +} + +void Unit::save(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Unit"); + + // unit specification + // we drop the unittype pointer, we save only the number + stream->writeSint32(typeNum, "typeNum"); + + // identity + stream->writeUint16(gid, "gid"); + stream->writeSint32(isDead, "isDead"); + + // position + stream->writeSint32(posX, "posX"); + stream->writeSint32(posY, "posY"); + stream->writeSint32(delta, "delta"); + stream->writeSint32(dx, "dx"); + stream->writeSint32(dy, "dy"); + stream->writeSint32(direction, "direction"); + stream->writeSint32(insideTimeout, "insideTimeout"); + stream->writeSint32(speed, "speed"); + + // states + stream->writeUint32((Uint32)needToRecheckMedical, "needToRecheckMedical"); + stream->writeUint32((Uint32)medical, "medical"); + stream->writeUint32((Uint32)activity, "activity"); + stream->writeUint32((Uint32)displacement, "displacement"); + stream->writeUint32((Uint32)movement, "movement"); + stream->writeUint32((Uint32)action, "action"); + stream->writeSint32(targetX, "targetX"); + stream->writeSint32(targetY, "targetY"); + stream->writeSint32(validTarget, "validTarget"); + stream->writeSint32(magicActionTimeout, "magicActionTimeout"); + + // attack timer + stream->writeUint8(underAttackTimer, "underAttackTimer"); + + // trigger parameters + stream->writeSint32(hp, "hp"); + stream->writeSint32(trigHP, "trigHP"); + + // hungry + stream->writeSint32(hungry, "hungry"); + stream->writeSint32(hungryness, "hungryness"); + stream->writeSint32(trigHungry, "trigHungry"); + stream->writeUint32(fruitMask, "fruitMask"); + stream->writeUint32(fruitCount, "fruitCount"); + + // quality parameters + stream->writeEnterSection("abilities"); + for (int i=0; iwriteEnterSection(i); + stream->writeUint32(performance[i], "performance"); + stream->writeUint32(level[i], "level"); + stream->writeUint32((Uint32)canLearn[i], "canLearn"); + stream->writeLeaveSection(); + } + stream->writeLeaveSection(); + + stream->writeSint32(experience, "experience"); + stream->writeSint32(experienceLevel, "experienceLevel"); + + stream->writeSint32(destinationPurpose, "destinationPurpose"); + stream->writeSint32(carriedRessource, "carriedRessource"); + stream->writeSint32(jobTimer, "jobTimer"); + + + stream->writeLeaveSection(); +} + +void Unit::loadCrossRef(GAGCore::InputStream *stream, Team *owner, Sint32 versionMinor) +{ + stream->readEnterSection("Unit"); + Uint16 gbid; + + gbid = stream->readUint16("attachedBuilding"); + if (gbid == NOGBID) + attachedBuilding = NULL; + else + attachedBuilding = owner->myBuildings[Building::GIDtoID(gbid)]; + + gbid = stream->readUint16("targetBuilding"); + if (gbid == NOGBID) + targetBuilding = NULL; + else + targetBuilding = owner->myBuildings[Building::GIDtoID(gbid)]; + + gbid = stream->readUint16("ownExchangeBuilding"); + if (gbid == NOGBID) + ownExchangeBuilding = NULL; + else + ownExchangeBuilding = owner->myBuildings[Building::GIDtoID(gbid)]; + + stream->readLeaveSection(); +} + +void Unit::saveCrossRef(GAGCore::OutputStream *stream) +{ + stream->writeEnterSection("Unit"); + + if (attachedBuilding) + stream->writeUint16(attachedBuilding->gid, "attachedBuilding"); + else + stream->writeUint16(NOGBID, "attachedBuilding"); + + if (targetBuilding) + stream->writeUint16(targetBuilding->gid, "targetBuilding"); + else + stream->writeUint16(NOGBID, "targetBuilding"); + + if (ownExchangeBuilding) + stream->writeUint16(ownExchangeBuilding->gid, "ownExchangeBuilding"); + else + stream->writeUint16(NOGBID, "ownExchangeBuilding"); + + stream->writeLeaveSection(); +} + +bool Unit::integrity() +{ + checkInvariant(gid<32768); + if (isDead) + return true; + + if (!needToRecheckMedical) + { + checkInvariant(activity==ACT_UPGRADING); + checkInvariant(destinationPurpose==HEAL || destinationPurpose==FEED); + } + return true; +} + +Uint32 Unit::checkSum(std::vector *checkSumsVector) +{ + Uint32 cs=0; + + cs^=typeNum; + if (checkSumsVector) + checkSumsVector->push_back(typeNum);// [0] + cs=rotl1(cs); + + cs^=isDead; + if (checkSumsVector) + checkSumsVector->push_back(isDead);// [1] + cs=rotl1(cs); + cs^=gid; + if (checkSumsVector) + checkSumsVector->push_back(gid);// [2] + cs=rotl1(cs); + + cs^=posX; + if (checkSumsVector) + checkSumsVector->push_back(posX);// [3] + cs=rotl1(cs); + cs^=posY; + if (checkSumsVector) + checkSumsVector->push_back(posY);// [4] + cs=rotl1(cs); + cs^=delta; + if (checkSumsVector) + checkSumsVector->push_back(delta);// [5] + cs=rotl1(cs); + cs^=dx; + if (checkSumsVector) + checkSumsVector->push_back(dx);// [6] + cs^=dy; + if (checkSumsVector) + checkSumsVector->push_back(dy);// [7] + cs^=direction; + if (checkSumsVector) + checkSumsVector->push_back(direction);// [8] + cs=rotl1(cs); + cs^=insideTimeout; + if (checkSumsVector) + checkSumsVector->push_back(insideTimeout);// [9] + cs=rotl1(cs); + cs^=speed; + if (checkSumsVector) + checkSumsVector->push_back(speed);// [10] + cs=rotl1(cs); + + cs^=(int)needToRecheckMedical; + if (checkSumsVector) + checkSumsVector->push_back(needToRecheckMedical);// [11] + cs=rotl1(cs); + cs^=medical; + if (checkSumsVector) + checkSumsVector->push_back(medical);// [12] + cs^=activity; + if (checkSumsVector) + checkSumsVector->push_back(activity);// [13] + cs^=displacement; + if (checkSumsVector) + checkSumsVector->push_back(displacement);// [14] + cs^=movement; + if (checkSumsVector) + checkSumsVector->push_back(movement);// [15] + cs^=action; + if (checkSumsVector) + checkSumsVector->push_back(action);// [16] + cs=rotl1(cs); + cs^=targetX; + if (checkSumsVector) + checkSumsVector->push_back(targetX);// [17] + cs^=targetY; + if (checkSumsVector) + checkSumsVector->push_back(targetY);// [18] + cs=rotl1(cs); + + cs^=hp; + if (checkSumsVector) + checkSumsVector->push_back(hp);// [19] + cs^=trigHP; + if (checkSumsVector) + checkSumsVector->push_back(trigHP);// [20] + cs=rotl1(cs); + + cs^=hungry; + if (checkSumsVector) + checkSumsVector->push_back(hungry);// [21] + cs^=trigHungry; + if (checkSumsVector) + checkSumsVector->push_back(trigHungry);// [22] + cs^=trigHungryCarying; + if (checkSumsVector) + checkSumsVector->push_back(trigHungryCarying);// [23] + cs=rotl1(cs); + + cs^=fruitMask; + if (checkSumsVector) + checkSumsVector->push_back(fruitMask);// [24] + cs^=fruitCount; + if (checkSumsVector) + checkSumsVector->push_back(fruitCount);// [25] + cs=rotl1(cs); + + for (int i=0; ipush_back(cs);// [26] + cs=rotl1(cs); + + cs^=(attachedBuilding!=NULL ? 1:0); + if (checkSumsVector) + checkSumsVector->push_back((attachedBuilding!=NULL ? 1:0));// [27] + cs=rotl1(cs); + cs^=(targetBuilding!=NULL ? 1:0); + if (checkSumsVector) + checkSumsVector->push_back((targetBuilding!=NULL ? 1:0));// [28] + cs^=(ownExchangeBuilding!=NULL ? 2:0); + if (checkSumsVector) + checkSumsVector->push_back((ownExchangeBuilding!=NULL ? 1:0));// [29] + cs=rotl1(cs); + + cs^=destinationPurpose; + if (checkSumsVector) + checkSumsVector->push_back(destinationPurpose);// [31] + cs^=carriedRessource; + if (checkSumsVector) + checkSumsVector->push_back(carriedRessource);// [33] + + if (checkSumsVector) + checkSumsVector->push_back(0);// [34] + if (checkSumsVector) + checkSumsVector->push_back(0);// [35] + if (checkSumsVector) + checkSumsVector->push_back(0);// [36] + if (checkSumsVector) + checkSumsVector->push_back(0);// [37] + if (checkSumsVector) + checkSumsVector->push_back(0);// [38] + if (checkSumsVector) + checkSumsVector->push_back(0);// [39] + + return cs; +} diff --git a/src/unit/UnitStats.cpp b/src/unit/UnitStats.cpp new file mode 100644 index 000000000..0da994588 --- /dev/null +++ b/src/unit/UnitStats.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "Unit.h" +#include "Race.h" +#include "Team.h" +#include "Map.h" +#include "Game.h" + +#include "Building.h" +#include "Integrity.h" + +#include "Utilities.h" +#include "GlobalContainer.h" +#include +#include +#include + +//! Return the real armor, taking into account the reduction due to fruits +int Unit::getRealArmor(bool isMagic) const +{ + int armorReductionPerHappyness = race->getUnitType(typeNum, level[ARMOR])->armorReductionPerHappyness; + if (isMagic) //magic bypasses armor yet fruit penalties still apply + return 0 - fruitCount * armorReductionPerHappyness; + else + return performance[ARMOR] - fruitCount * armorReductionPerHappyness; +} + +//! Return the real attack strengh, taking into account the experience level +int Unit::getRealAttackStrength(void) const +{ + return performance[ATTACK_STRENGTH] + experienceLevel; +} + +//! Return the amount of experience to level-up +int Unit::getNextLevelThreshold(void) const +{ + return (experienceLevel + 1) * (experienceLevel + 1) * race->getUnitType(typeNum, level[ATTACK_STRENGTH])->experiencePerLevel; +} + +//! Increment experience. If level-up occures, handle it. Multiple level-up may occur at once. +void Unit::incrementExperience(int increment) +{ + experience += increment; + int nextLevelThreshold = getNextLevelThreshold(); + while (experience > nextLevelThreshold) + { + experience -= nextLevelThreshold; + experienceLevel++; + nextLevelThreshold = getNextLevelThreshold(); + levelUpAnimation = LEVEL_UP_ANIMATION_FRAME_COUNT; + } +} + +//! Return how many steps we can do until we are hungry +int Unit::numberOfStepsLeftUntilHungry(void) +{ + int timeLeft; + if (hungryness) + timeLeft = (hungry-trigHungry) / hungryness; + else + timeLeft = INT_MAX; + stepsLeftUntilHungry = timeLeft; + return timeLeft; +} + +//! Iterate on all resource types to see if it is gettable +void Unit::computeMinDistToResources(void) +{ + for (size_t ri = 0; ri < MAX_RESSOURCES; ri++) + if (!owner->map->ressourceAvailable(owner->teamNumber, ri, performance[SWIM], posX, posY, &minDistToResource[ri])) + minDistToResource[ri] = UNIT_MIN_DIST_NOT_REACHABLE; + // the dist to an already carried resource is zero + if (carriedRessource >= 0) + minDistToResource[carriedRessource] = 0; +} diff --git a/src/unit/UnitUtils.cpp b/src/unit/UnitUtils.cpp new file mode 100644 index 000000000..40dd837ea --- /dev/null +++ b/src/unit/UnitUtils.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "UnitUtils.h" +#include "Team.h" + + +Sint32 UnitUtils::GIDtoID(Uint16 gid) +{ + assert(gid < UnitUtils::MAX_COUNT * Team::MAX_COUNT); + return (gid % UnitUtils::MAX_COUNT); +} + +Sint32 UnitUtils::GIDtoTeam(Uint16 gid) +{ + assert(gid < UnitUtils::MAX_COUNT * Team::MAX_COUNT); + return (gid / UnitUtils::MAX_COUNT); +} + +Uint16 UnitUtils::GIDfrom(Sint32 id, Sint32 team) +{ + assert(id >= 0); + assert(id < UnitUtils::MAX_COUNT); + assert(team >= 0); + assert(team < Team::MAX_COUNT); + return id + team * UnitUtils::MAX_COUNT; +} diff --git a/src/unit/UnitUtils.h b/src/unit/UnitUtils.h new file mode 100644 index 000000000..3c27aa840 --- /dev/null +++ b/src/unit/UnitUtils.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include + +class UnitUtils +{ + public: + static Sint32 GIDtoID(Uint16 gid); + static Sint32 GIDtoTeam(Uint16 gid); + static Uint16 GIDfrom(Sint32 id, Sint32 team); + + static const int MAX_COUNT = 1024; +}; + + + diff --git a/src/YOGAfterJoinGameInformation.cpp b/src/yog/YOGAfterJoinGameInformation.cpp similarity index 77% rename from src/YOGAfterJoinGameInformation.cpp rename to src/yog/YOGAfterJoinGameInformation.cpp index 2d74a5eea..a0f35678a 100644 --- a/src/YOGAfterJoinGameInformation.cpp +++ b/src/yog/YOGAfterJoinGameInformation.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGAfterJoinGameInformation.h" #include "Version.h" diff --git a/src/YOGAfterJoinGameInformation.h b/src/yog/YOGAfterJoinGameInformation.h similarity index 71% rename from src/YOGAfterJoinGameInformation.h rename to src/yog/YOGAfterJoinGameInformation.h index 6a6aaf387..a38f9ffdf 100644 --- a/src/YOGAfterJoinGameInformation.h +++ b/src/yog/YOGAfterJoinGameInformation.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#ifndef __YOGAfterJoinGameInformation_h -#define __YOGAfterJoinGameInformation_h +#pragma once #include "GameHeader.h" #include "MapHeader.h" @@ -93,4 +76,3 @@ class YOGAfterJoinGameInformation Uint32 fileID; }; -#endif diff --git a/src/YOGClient.cpp b/src/yog/YOGClient.cpp similarity index 80% rename from src/YOGClient.cpp rename to src/yog/YOGClient.cpp index 50fcd1ab9..dcec0cde2 100644 --- a/src/YOGClient.cpp +++ b/src/yog/YOGClient.cpp @@ -1,24 +1,21 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include #include "MultiplayerGame.h" -#include "NetMessage.h" +#include "AuthMessages.h" +#include "FileTransferMessages.h" +#include "GameCreateMessages.h" +#include "GameHeaderMessages.h" +#include "GameJoinMessages.h" +#include "GameLaunchMessages.h" +#include "GameTeamMessages.h" +#include "LobbyMessages.h" +#include "MapDatabaseMessages.h" +#include "MapUploadMessages.h" +#include "OrderMessages.h" +#include "RegistrationMessages.h" +#include "RouterMessages.h" #include "YOGClientBlockedList.h" #include "YOGClientChatChannel.h" #include "YOGClientCommandManager.h" @@ -34,8 +31,8 @@ #include "YOGMessage.h" #include "YOGServer.h" -using boost::static_pointer_cast; -using boost::shared_ptr; +using std::static_pointer_cast; +using std::shared_ptr; YOGClient::YOGClient(const std::string& server) { @@ -158,8 +155,8 @@ void YOGClient::update() shared_ptr info = static_pointer_cast(message); connectionState = ClientOnStandby; loginState = YOGLoginSuccessful; - ratedMapList = boost::shared_ptr(new YOGClientRatedMapList(username)); - blocked = boost::shared_ptr(new YOGClientBlockedList(username)); + ratedMapList = std::shared_ptr(new YOGClientRatedMapList(username)); + blocked = std::shared_ptr(new YOGClientBlockedList(username)); shared_ptr event(new YOGLoginAcceptedEvent); sendToListeners(event); } @@ -178,8 +175,8 @@ void YOGClient::update() shared_ptr info = static_pointer_cast(message); connectionState = ClientOnStandby; loginState = YOGLoginSuccessful; - ratedMapList = boost::shared_ptr(new YOGClientRatedMapList(username)); - blocked = boost::shared_ptr(new YOGClientBlockedList(username)); + ratedMapList = std::shared_ptr(new YOGClientRatedMapList(username)); + blocked = std::shared_ptr(new YOGClientBlockedList(username)); shared_ptr event(new YOGLoginAcceptedEvent); sendToListeners(event); } @@ -394,7 +391,7 @@ void YOGClient::update() message = gameConnection->getMessage(); } } - for(std::map >::iterator i = assembler.begin(); i!=assembler.end();) + for(std::map >::iterator i = assembler.begin(); i!=assembler.end();) { if(i->second) { @@ -403,7 +400,7 @@ void YOGClient::update() } else { - std::map >::iterator to_erase = i; + std::map >::iterator to_erase = i; i++; assembler.erase(to_erase); } @@ -498,21 +495,21 @@ void YOGClient::createGame(const std::string& name) -void YOGClient::setMultiplayerGame(boost::shared_ptr game) +void YOGClient::setMultiplayerGame(std::shared_ptr game) { joinedGame=game; } -boost::shared_ptr YOGClient::getMultiplayerGame() +std::shared_ptr YOGClient::getMultiplayerGame() { return joinedGame; } -void YOGClient::sendNetMessage(boost::shared_ptr message) +void YOGClient::sendNetMessage(std::shared_ptr message) { nc.sendMessage(message); } @@ -533,7 +530,7 @@ void YOGClient::removeYOGClientChatChannel(YOGClientChatChannel* channel) -void YOGClient::sendToListeners(boost::shared_ptr event) +void YOGClient::sendToListeners(std::shared_ptr event) { for(std::list::iterator i = listeners.begin(); i!=listeners.end(); ++i) { @@ -543,14 +540,14 @@ void YOGClient::sendToListeners(boost::shared_ptr event) -void YOGClient::setYOGClientFileAssembler(Uint16 fileID, boost::shared_ptr nassembler) +void YOGClient::setYOGClientFileAssembler(Uint16 fileID, std::shared_ptr nassembler) { assembler[fileID]=nassembler; } -boost::shared_ptr YOGClient::getYOGClientFileAssembler(Uint16 fileID) +std::shared_ptr YOGClient::getYOGClientFileAssembler(Uint16 fileID) { return assembler[fileID]; } @@ -571,28 +568,28 @@ void YOGClient::removeEventListener(YOGClientEventListener* listener) -void YOGClient::setGameConnection(boost::shared_ptr ngameConnection) +void YOGClient::setGameConnection(std::shared_ptr ngameConnection) { gameConnection = ngameConnection; } -boost::shared_ptr YOGClient::getGameConnection() +std::shared_ptr YOGClient::getGameConnection() { return gameConnection; } -boost::shared_ptr YOGClient::getBlockedList() +std::shared_ptr YOGClient::getBlockedList() { return blocked; } -boost::shared_ptr YOGClient::getCommandManager() +std::shared_ptr YOGClient::getCommandManager() { return commands; } @@ -613,14 +610,14 @@ void YOGClient::setMapUploader(YOGClientMapUploader* nuploader) -boost::shared_ptr YOGClient::getDownloadableMapList() +std::shared_ptr YOGClient::getDownloadableMapList() { return downloadableMapList; } -boost::shared_ptr YOGClient::getRatedMapList() +std::shared_ptr YOGClient::getRatedMapList() { return ratedMapList; } @@ -641,56 +638,56 @@ YOGClientMapDownloader* YOGClient::getMapDownloader() -void YOGClient::attachGameServer(boost::shared_ptr nserver) +void YOGClient::attachGameServer(std::shared_ptr nserver) { server = nserver; } -boost::shared_ptr YOGClient::getGameServer() +std::shared_ptr YOGClient::getGameServer() { return server; } -void YOGClient::setP2PConnection(boost::shared_ptr connection) +void YOGClient::setP2PConnection(std::shared_ptr connection) { p2pconnection = connection; } -boost::shared_ptr YOGClient::getP2PConnection() +std::shared_ptr YOGClient::getP2PConnection() { return p2pconnection; } -void YOGClient::setGameListManager(boost::shared_ptr ngameListManager) +void YOGClient::setGameListManager(std::shared_ptr ngameListManager) { gameListManager = ngameListManager; } -boost::shared_ptr YOGClient::getGameListManager() +std::shared_ptr YOGClient::getGameListManager() { return gameListManager; } -void YOGClient::setPlayerListManager(boost::shared_ptr nplayerListManager) +void YOGClient::setPlayerListManager(std::shared_ptr nplayerListManager) { playerListManager = nplayerListManager; } -boost::shared_ptr YOGClient::getPlayerListManager() +std::shared_ptr YOGClient::getPlayerListManager() { return playerListManager; } diff --git a/src/YOGClient.h b/src/yog/YOGClient.h similarity index 71% rename from src/YOGClient.h rename to src/yog/YOGClient.h index a2908ddae..7f805a6e3 100644 --- a/src/YOGClient.h +++ b/src/yog/YOGClient.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGClient_h -#define __YOGClient_h +#pragma once #include "NetConnection.h" #include "YOGConsts.h" @@ -132,40 +116,40 @@ class YOGClient void createGame(const std::string& name); ///Assocciattes the provided MultiplayerGame with this connection - void setMultiplayerGame(boost::shared_ptr game); + void setMultiplayerGame(std::shared_ptr game); ///Returns the assocciatted MultiplayerGame - boost::shared_ptr getMultiplayerGame(); + std::shared_ptr getMultiplayerGame(); ///Sets a file assembler for the given id - void setYOGClientFileAssembler(Uint16 fileID, boost::shared_ptr assembler); + void setYOGClientFileAssembler(Uint16 fileID, std::shared_ptr assembler); ///Returns the map assembler for this connection - boost::shared_ptr getYOGClientFileAssembler(Uint16 fileID); + std::shared_ptr getYOGClientFileAssembler(Uint16 fileID); ///This attaches a game server to this client, for client-hosted games (such as LAN) - void attachGameServer(boost::shared_ptr server); + void attachGameServer(std::shared_ptr server); ///This retrieves the attached game server - boost::shared_ptr getGameServer(); + std::shared_ptr getGameServer(); ///This attaches a P2PConnection to this client - void setP2PConnection(boost::shared_ptr connection); + void setP2PConnection(std::shared_ptr connection); ///This retrieves the attached P2P connection - boost::shared_ptr getP2PConnection(); + std::shared_ptr getP2PConnection(); ///This attaches a YOGClientGameListManager to this client - void setGameListManager(boost::shared_ptr gameListManager); + void setGameListManager(std::shared_ptr gameListManager); ///This retrieves the YOGClientGameListManager of this client - boost::shared_ptr getGameListManager(); + std::shared_ptr getGameListManager(); ///This attaches a YOGClientPlayerListManager to this client - void setPlayerListManager(boost::shared_ptr playerListManager); + void setPlayerListManager(std::shared_ptr playerListManager); ///This retrieves the YOGClientGameListManager of this client - boost::shared_ptr getPlayerListManager(); + std::shared_ptr getPlayerListManager(); ///This adds an event listener void addEventListener(YOGClientEventListener* listener); @@ -174,16 +158,16 @@ class YOGClient void removeEventListener(YOGClientEventListener* listener); ///This attaches a NetConnection to this client for the game-router connection - void setGameConnection(boost::shared_ptr gameConnection); + void setGameConnection(std::shared_ptr gameConnection); ///This retrieves the NetConnection of this clients game-router connection - boost::shared_ptr getGameConnection(); + std::shared_ptr getGameConnection(); ///This retrieves the YOGClientBlockedList of this client - boost::shared_ptr getBlockedList(); + std::shared_ptr getBlockedList(); ///This retrieves the YOGClientCommandManager of this client - boost::shared_ptr getCommandManager(); + std::shared_ptr getCommandManager(); ///This retrieves the YOGClientMapUploader of this client YOGClientMapUploader* getMapUploader(); @@ -192,10 +176,10 @@ class YOGClient void setMapUploader(YOGClientMapUploader* uploader); ///This returns the YOGClientDownloadableMapList - boost::shared_ptr getDownloadableMapList(); + std::shared_ptr getDownloadableMapList(); ///This returns the YOGClientRatedMapList - boost::shared_ptr getRatedMapList(); + std::shared_ptr getRatedMapList(); ///This sets the YOGClientMapDownloader of this client void setMapDownloader(YOGClientMapDownloader* downloader); @@ -217,7 +201,7 @@ class YOGClient friend class YOGClientMapDownloader; ///Sends a message on behalf of the assocciatted MultiplayerGame or YOGClientChatChannel - void sendNetMessage(boost::shared_ptr message); + void sendNetMessage(std::shared_ptr message); ///Adds a new YOGClientChatChannel to recieve chat events (done by YOGClientChatChannel itself) void addYOGClientChatChannel(YOGClientChatChannel* channel); @@ -226,7 +210,7 @@ class YOGClient void removeYOGClientChatChannel(YOGClientChatChannel* channel); ///This sends an event to all the listeners - void sendToListeners(boost::shared_ptr event); + void sendToListeners(std::shared_ptr event); private: std::string username; @@ -245,19 +229,19 @@ class YOGClient std::map chatChannels; - boost::shared_ptr joinedGame; - std::map > assembler; - boost::shared_ptr p2pconnection; - boost::shared_ptr gameListManager; - boost::shared_ptr playerListManager; - boost::shared_ptr gameConnection; - boost::shared_ptr blocked; - boost::shared_ptr commands; - boost::shared_ptr downloadableMapList; - boost::shared_ptr ratedMapList; + std::shared_ptr joinedGame; + std::map > assembler; + std::shared_ptr p2pconnection; + std::shared_ptr gameListManager; + std::shared_ptr playerListManager; + std::shared_ptr gameConnection; + std::shared_ptr blocked; + std::shared_ptr commands; + std::shared_ptr downloadableMapList; + std::shared_ptr ratedMapList; YOGClientMapUploader* uploader; YOGClientMapDownloader* downloader; - boost::shared_ptr server; + std::shared_ptr server; std::list listeners; @@ -265,4 +249,3 @@ class YOGClient -#endif diff --git a/src/YOGClientBlockedList.cpp b/src/yog/YOGClientBlockedList.cpp similarity index 73% rename from src/YOGClientBlockedList.cpp rename to src/yog/YOGClientBlockedList.cpp index 81e54e167..37b9d8730 100644 --- a/src/YOGClientBlockedList.cpp +++ b/src/yog/YOGClientBlockedList.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "FileManager.h" #include "Stream.h" diff --git a/src/yog/YOGClientBlockedList.h b/src/yog/YOGClientBlockedList.h new file mode 100644 index 000000000..03f0c499c --- /dev/null +++ b/src/yog/YOGClientBlockedList.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include +#include + +///This holds a player-end blocked list +class YOGClientBlockedList +{ +public: + YOGClientBlockedList(const std::string& username); + + ///Loads from the blocked list text file + void load(); + + ///Saves to the blocked list text file + void save(); + + ///Adds a player as blocked + void addBlockedPlayer(const std::string& name); + + ///Returns true if the given player is blocked + bool isPlayerBlocked(const std::string& name); + + ///Removes a player from the blocked list + void removeBlockedPlayer(const std::string& name); + + ///Returns a set containing all blocked players + const std::set& getBlockedPlayers() const; +private: + std::set blockedPlayers; + std::string username; +}; + + diff --git a/src/yog/YOGClientChatChannel.cpp b/src/yog/YOGClientChatChannel.cpp new file mode 100644 index 000000000..7bb0c0a19 --- /dev/null +++ b/src/yog/YOGClientChatChannel.cpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "YOGClientChatChannel.h" +#include "YOGClient.h" +#include "YOGMessage.h" +#include "YOGClientChatListener.h" +#include "LobbyMessages.h" + +YOGClientChatChannel::YOGClientChatChannel(Uint32 channelID, std::shared_ptr client) + : client(client), channelID(channelID) +{ + client->addYOGClientChatChannel(this); +} + + + +YOGClientChatChannel::~YOGClientChatChannel() +{ + client->removeYOGClientChatChannel(this); +} + + + +Uint32 YOGClientChatChannel::getHistorySize() const +{ + return messageHistory.size(); +} + + + +const std::shared_ptr YOGClientChatChannel::getMessage(Uint32 n) const +{ + return std::get<0>(messageHistory[n]); +} + + + +boost::posix_time::ptime YOGClientChatChannel::getMessageTime(Uint32 n) const +{ + return std::get<1>(messageHistory[n]); +} + + + +void YOGClientChatChannel::sendMessage(std::shared_ptr message) +{ + if(channelID != static_cast(-1)) + { + messageHistory.push_back(std::make_tuple(message, boost::posix_time::second_clock::local_time())); + std::shared_ptr netmessage(new NetSendYOGMessage(channelID, message)); + client->sendNetMessage(netmessage); + sendToListeners(message); + } +} + + + +Uint32 YOGClientChatChannel::getChannelID() const +{ + return channelID; +} + + + +void YOGClientChatChannel::setChannelID(Uint32 channel) +{ + client->removeYOGClientChatChannel(this); + channelID = channel; + client->addYOGClientChatChannel(this); +} + + + +void YOGClientChatChannel::addListener(YOGClientChatListener* listener) +{ + listeners.push_back(listener); +} + + + +void YOGClientChatChannel::removeListener(YOGClientChatListener* listener) +{ + listeners.remove(listener); +} + + + +void YOGClientChatChannel::recieveMessage(std::shared_ptr message) +{ + messageHistory.push_back(std::make_tuple(message, boost::posix_time::second_clock::local_time())); + sendToListeners(message); +} + + + +void YOGClientChatChannel::sendToListeners(std::shared_ptr message) +{ + for(std::list::iterator i = listeners.begin(); i!=listeners.end(); ++i) + { + (*i)->recieveTextMessage(message); + } +} + + diff --git a/src/YOGClientChatChannel.h b/src/yog/YOGClientChatChannel.h similarity index 54% rename from src/YOGClientChatChannel.h rename to src/yog/YOGClientChatChannel.h index d2d2ac7de..1394266ec 100644 --- a/src/YOGClientChatChannel.h +++ b/src/yog/YOGClientChatChannel.h @@ -1,28 +1,12 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGClientChatChannel_h -#define __YOGClientChatChannel_h +#pragma once #include #include #include "boost/date_time/posix_time/posix_time.hpp" -#include "boost/tuple/tuple.hpp" +#include #include "SDL_net.h" class YOGClient; @@ -36,7 +20,7 @@ class YOGClientChatChannel public: ///Creates a new YOGClientChatChannel, with its channel id and then YOGClient to listen from ///Adds itself to the YOGClient to listen for chat events - YOGClientChatChannel(Uint32 channelID, boost::shared_ptr client); + YOGClientChatChannel(Uint32 channelID, std::shared_ptr client); ///Destroys the YOGClientChatChannel ~YOGClientChatChannel(); @@ -45,13 +29,13 @@ class YOGClientChatChannel Uint32 getHistorySize() const; ///Retrieves YOG message x, where 0 is the first message recieved, and higher gets more recent - const boost::shared_ptr getMessage(Uint32 n) const; + const std::shared_ptr getMessage(Uint32 n) const; ///Retrieves the local time that YOG message x was recieved, where higher x gets more recent boost::posix_time::ptime getMessageTime(Uint32 n) const; ///Sends a message through this channel - void sendMessage(boost::shared_ptr message); + void sendMessage(std::shared_ptr message); ///Returns the channel ID of this channel Uint32 getChannelID() const; @@ -69,17 +53,16 @@ class YOGClientChatChannel friend class YOGClient; ///Recieves a message from the network (called by YOGClient) - void recieveMessage(boost::shared_ptr message); + void recieveMessage(std::shared_ptr message); ///This sends the message to all listeners - void sendToListeners(boost::shared_ptr message); + void sendToListeners(std::shared_ptr message); private: - boost::shared_ptr client; + std::shared_ptr client; Uint32 channelID; - std::vector, boost::posix_time::ptime> > messageHistory; + std::vector, boost::posix_time::ptime> > messageHistory; std::list listeners; }; -#endif diff --git a/src/yog/YOGClientChatListener.cpp b/src/yog/YOGClientChatListener.cpp new file mode 100644 index 000000000..9d17f6453 --- /dev/null +++ b/src/yog/YOGClientChatListener.cpp @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "YOGClientChatListener.h" + diff --git a/src/yog/YOGClientChatListener.h b/src/yog/YOGClientChatListener.h new file mode 100644 index 000000000..0535ae06e --- /dev/null +++ b/src/yog/YOGClientChatListener.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +class YOGMessage; + +///This class is a mix-in class for objects that want to listen for recieved texts +class YOGClientChatListener +{ +public: + virtual ~YOGClientChatListener() {} + + ///Recieves a text message + virtual void recieveTextMessage(std::shared_ptr message)=0; +}; + diff --git a/src/YOGClientCommandManager.cpp b/src/yog/YOGClientCommandManager.cpp similarity index 69% rename from src/YOGClientCommandManager.cpp rename to src/yog/YOGClientCommandManager.cpp index 6a827e5d6..64ba947c0 100644 --- a/src/YOGClientCommandManager.cpp +++ b/src/yog/YOGClientCommandManager.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "FormatableString.h" #include "StringTable.h" @@ -98,7 +83,7 @@ std::string YOGClientCommandManager::executeClientCommand(const std::string& mes { if(tokens[0] == commands[i]->getCommandName()) { - if(!commands[i]->doesMatch(tokens)) + if(!commands[i]->doesMatch(tokens.size())) { text = commands[i]->getHelpMessage(); } diff --git a/src/yog/YOGClientCommandManager.h b/src/yog/YOGClientCommandManager.h new file mode 100644 index 000000000..02537f8e3 --- /dev/null +++ b/src/yog/YOGClientCommandManager.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include + +class YOGClient; +class YOGClientCommand; + +///This manages client commands, like /block +class YOGClientCommandManager +{ +public: + YOGClientCommandManager(YOGClient* client); + + ///Destroys the administration engine + ~YOGClientCommandManager(); + + ///Interprets whether the given message is a client command, and if so + ///executes it. If it wasn't a command, the string this returns will be + ///empty + std::string executeClientCommand(const std::string& message); + +private: + YOGClient* client; + std::vector commands; +}; + + diff --git a/src/YOGClientCommands.cpp b/src/yog/YOGClientCommands.cpp similarity index 56% rename from src/YOGClientCommands.cpp rename to src/yog/YOGClientCommands.cpp index e018ac5f2..1ca77f3eb 100644 --- a/src/YOGClientCommands.cpp +++ b/src/yog/YOGClientCommands.cpp @@ -1,24 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "boost/lexical_cast.hpp" +#include #include "FormatableString.h" -#include "NetMessage.h" #include "StringTable.h" #include "Toolkit.h" #include "YOGClientBlockedList.h" @@ -42,13 +26,6 @@ std::string YOGClientBlockPlayerCommand::getCommandName() -bool YOGClientBlockPlayerCommand::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - std::string YOGClientBlockPlayerCommand::execute(YOGClient* client, const std::vector& tokens) { if(client->getPlayerListManager()->doesPlayerExist(tokens[1])) diff --git a/src/yog/YOGClientCommands.h b/src/yog/YOGClientCommands.h new file mode 100644 index 000000000..1b41c154e --- /dev/null +++ b/src/yog/YOGClientCommands.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include +#include + +class YOGClient; + +///This defines a generic command +class YOGClientCommand +{ +public: + virtual ~YOGClientCommand() {} + + ///Returns this YOGClientCommand help message + virtual std::string getHelpMessage()=0; + + ///Returns the command name for this YOGClientCommand + virtual std::string getCommandName()=0; + + ///Executes the code for the administrator command, returns the output from the command + virtual std::string execute(YOGClient* client, const std::vector& tokens)=0; + + ///Returns true if the token count is within this command's accepted range. + bool doesMatch(std::size_t count) const + { + return int(count) >= minTokens && int(count) <= maxTokens; + } + +protected: + explicit YOGClientCommand(int fixedTokens) : minTokens(fixedTokens), maxTokens(fixedTokens) {} + YOGClientCommand(int min, int max) : minTokens(min), maxTokens(max) {} + +private: + int minTokens; + int maxTokens; +}; + +class YOGClientBlockPlayerCommand : public YOGClientCommand +{ +public: + YOGClientBlockPlayerCommand() : YOGClientCommand(2) {} + std::string getHelpMessage(); + std::string getCommandName(); + std::string execute(YOGClient* client, const std::vector& tokens); +}; + diff --git a/src/YOGClientDownloadableMapList.cpp b/src/yog/YOGClientDownloadableMapList.cpp similarity index 68% rename from src/YOGClientDownloadableMapList.cpp rename to src/yog/YOGClientDownloadableMapList.cpp index 304849943..cce1c0d30 100644 --- a/src/YOGClientDownloadableMapList.cpp +++ b/src/yog/YOGClientDownloadableMapList.cpp @@ -1,27 +1,11 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGClientDownloadableMapList.h" #include "YOGClient.h" -#include "NetMessage.h" +#include "MapDatabaseMessages.h" -using boost::static_pointer_cast; +using std::static_pointer_cast; YOGClientDownloadableMapList::YOGClientDownloadableMapList(YOGClient* client) : client(client) @@ -42,19 +26,19 @@ void YOGClientDownloadableMapList::requestMapListUpdate() { maps.clear(); thumbnails.clear(); - boost::shared_ptr request(new NetRequestDownloadableMapList); + std::shared_ptr request(new NetRequestDownloadableMapList); client->sendNetMessage(request); waitingForList=true; } -void YOGClientDownloadableMapList::recieveMessage(boost::shared_ptr message) +void YOGClientDownloadableMapList::recieveMessage(std::shared_ptr message) { Uint8 type = message->getMessageType(); if(type == MNetDownloadableMapInfos) { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); maps = info->getMaps(); thumbnails.resize(maps.size()); sendUpdateToListeners(); @@ -62,7 +46,7 @@ void YOGClientDownloadableMapList::recieveMessage(boost::shared_ptr } if(type == MNetSendMapThumbnail) { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); for(unsigned int i=0; igetMapID()) @@ -104,7 +88,7 @@ void YOGClientDownloadableMapList::requestThumbnail(const std::string& name) { if(i->getMapHeader().getMapName() == name) { - boost::shared_ptr request(new NetRequestMapThumbnail(i->getMapID())); + std::shared_ptr request(new NetRequestMapThumbnail(i->getMapID())); client->sendNetMessage(request); } } @@ -133,7 +117,7 @@ void YOGClientDownloadableMapList::submitRating(const std::string& name, Uint8 r { if(i->getMapHeader().getMapName() == name) { - boost::shared_ptr request(new NetSubmitRatingOnMap(i->getMapID(), rating)); + std::shared_ptr request(new NetSubmitRatingOnMap(i->getMapID(), rating)); client->sendNetMessage(request); i->setNumberOfRatings(i->getNumberOfRatings() + 1); i->setRatingTotal(i->getRatingTotal() + rating); diff --git a/src/YOGClientDownloadableMapList.h b/src/yog/YOGClientDownloadableMapList.h similarity index 65% rename from src/YOGClientDownloadableMapList.h rename to src/yog/YOGClientDownloadableMapList.h index 9997e1a5a..f8596e7a4 100644 --- a/src/YOGClientDownloadableMapList.h +++ b/src/yog/YOGClientDownloadableMapList.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientDownloadableMapList_h -#define YOGClientDownloadableMapList_h - -#include "boost/shared_ptr.hpp" +#include #include "YOGDownloadableMapInfo.h" #include #include @@ -43,7 +27,7 @@ class YOGClientDownloadableMapList void requestMapListUpdate(); ///Recieves a message from the server - void recieveMessage(boost::shared_ptr message); + void recieveMessage(std::shared_ptr message); ///Returns the list of downloadable games std::vector& getDownloadableMapList(); @@ -78,4 +62,3 @@ class YOGClientDownloadableMapList bool waitingForList; }; -#endif diff --git a/src/yog/YOGClientDownloadableMapListener.cpp b/src/yog/YOGClientDownloadableMapListener.cpp new file mode 100644 index 000000000..a97171c3c --- /dev/null +++ b/src/yog/YOGClientDownloadableMapListener.cpp @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#include "YOGClientDownloadableMapListener.h" + + diff --git a/src/yog/YOGClientDownloadableMapListener.h b/src/yog/YOGClientDownloadableMapListener.h new file mode 100644 index 000000000..85e62174e --- /dev/null +++ b/src/yog/YOGClientDownloadableMapListener.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +class YOGClientDownloadableMapListener +{ +public: + virtual ~YOGClientDownloadableMapListener() {} + + virtual void mapListUpdated() = 0; + virtual void mapThumbnailsUpdated() = 0; +}; + + + diff --git a/src/YOGClientDownloadingMapScreen.cpp b/src/yog/YOGClientDownloadingMapScreen.cpp similarity index 80% rename from src/YOGClientDownloadingMapScreen.cpp rename to src/yog/YOGClientDownloadingMapScreen.cpp index 6c34beb53..da9610a34 100644 --- a/src/YOGClientDownloadingMapScreen.cpp +++ b/src/yog/YOGClientDownloadingMapScreen.cpp @@ -1,20 +1,5 @@ -/* - Copyright 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGClientDownloadingMapScreen.h" @@ -40,7 +25,7 @@ using namespace GAGCore; -YOGClientDownloadingMapScreen::YOGClientDownloadingMapScreen(boost::shared_ptr client, const YOGDownloadableMapInfo& info) +YOGClientDownloadingMapScreen::YOGClientDownloadingMapScreen(std::shared_ptr client, const YOGDownloadableMapInfo& info) : info(info), client(client), downloader(client) { addWidget(new Text(0, 10, ALIGN_FILL, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[downloading map]"))); diff --git a/src/yog/YOGClientDownloadingMapScreen.h b/src/yog/YOGClientDownloadingMapScreen.h new file mode 100644 index 000000000..64679aad3 --- /dev/null +++ b/src/yog/YOGClientDownloadingMapScreen.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + + +#include +#include "Glob2Screen.h" +#include +#include "YOGDownloadableMapInfo.h" +#include "YOGClientMapDownloader.h" + +namespace GAGGUI +{ + class Text; + class TextInput; + class TextArea; + class TextButton; + class TabScreen; + class Widget; + class List; + class ProgressBar; +} + +class YOGClient; +class MapPreview; + +using namespace GAGGUI; + +///This screen appears when you are downloading a map +class YOGClientDownloadingMapScreen : public Glob2Screen +{ +public: + + /// Constructor + YOGClientDownloadingMapScreen(std::shared_ptr client, const YOGDownloadableMapInfo& info); + + ///Responds to widget events + void onAction(Widget *source, Action action, int par1, int par2); + ///Responds to timer events + void onTimer(Uint32 tick); + + enum + { + CANCEL, + CONNECTIONLOST, + FINISHED, + }; +private: + YOGDownloadableMapInfo info; + MapPreview* preview; + std::shared_ptr client; + //! The textual informations about the selected map + Text *mapName, *mapInfo, *mapSize; + Text *authorName; + ProgressBar* downloadStatus; + YOGClientMapDownloader downloader; +}; + + + + + diff --git a/src/YOGClientEvent.cpp b/src/yog/YOGClientEvent.cpp similarity index 81% rename from src/YOGClientEvent.cpp rename to src/yog/YOGClientEvent.cpp index 63ad803f4..3a5c3d151 100644 --- a/src/YOGClientEvent.cpp +++ b/src/yog/YOGClientEvent.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "YOGClientEvent.h" #include diff --git a/src/YOGClientEvent.h b/src/yog/YOGClientEvent.h similarity index 78% rename from src/YOGClientEvent.h rename to src/yog/YOGClientEvent.h index 2f0d783ee..f6656d2bd 100644 --- a/src/YOGClientEvent.h +++ b/src/yog/YOGClientEvent.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGClientEvent_h -#define __YOGClientEvent_h +#pragma once #include #include "SDL_net.h" @@ -181,4 +165,3 @@ class YOGIPBannedEvent : public YOGClientEvent //event_append_marker -#endif diff --git a/src/yog/YOGClientEventListener.cpp b/src/yog/YOGClientEventListener.cpp new file mode 100644 index 000000000..7db7a3d01 --- /dev/null +++ b/src/yog/YOGClientEventListener.cpp @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "YOGClientEventListener.h" diff --git a/src/yog/YOGClientEventListener.h b/src/yog/YOGClientEventListener.h new file mode 100644 index 000000000..4a995482f --- /dev/null +++ b/src/yog/YOGClientEventListener.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#pragma once + +#include + +class YOGClientEvent; + +/// This is a mix-in class. Classes that want to respond to YOG +/// events derive from this class +class YOGClientEventListener +{ +public: + virtual ~YOGClientEventListener() {} + + ///This responds to a YOG event + virtual void handleYOGClientEvent(std::shared_ptr event) = 0; +}; + + diff --git a/src/YOGClientFileAssembler.cpp b/src/yog/YOGClientFileAssembler.cpp similarity index 73% rename from src/YOGClientFileAssembler.cpp rename to src/yog/YOGClientFileAssembler.cpp index 83c9948ae..b6f972b51 100644 --- a/src/YOGClientFileAssembler.cpp +++ b/src/yog/YOGClientFileAssembler.cpp @@ -1,24 +1,9 @@ -/* - Copyright 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "BinaryStream.h" #include "FileManager.h" -#include "NetMessage.h" +#include "FileTransferMessages.h" #include "StreamBackend.h" #include "Stream.h" #include "Toolkit.h" @@ -26,9 +11,9 @@ #include "YOGClient.h" using namespace GAGCore; -using boost::static_pointer_cast; +using std::static_pointer_cast; -YOGClientFileAssembler::YOGClientFileAssembler(boost::weak_ptr client, Uint16 fileID) +YOGClientFileAssembler::YOGClientFileAssembler(std::weak_ptr client, Uint16 fileID) : client(client), fileID(fileID) { obackend = NULL; @@ -57,7 +42,7 @@ void YOGClientFileAssembler::update() void YOGClientFileAssembler::startSendingFile(std::string mapname) { - boost::shared_ptr nclient(client); + std::shared_ptr nclient(client); Toolkit::getFileManager()->gzip(mapname, mapname+".gz"); finished=0; istream.reset(new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(mapname+".gz"))); @@ -82,7 +67,7 @@ void YOGClientFileAssembler::startRecievingFile(std::string mapname) -void YOGClientFileAssembler::handleMessage(boost::shared_ptr message) +void YOGClientFileAssembler::handleMessage(std::shared_ptr message) { Uint8 type = message->getMessageType(); if(type == MNetSendFileInformation) @@ -119,7 +104,7 @@ void YOGClientFileAssembler::handleMessage(boost::shared_ptr message void YOGClientFileAssembler::cancelSendingFile() { - boost::shared_ptr nclient(client); + std::shared_ptr nclient(client); shared_ptr message(new NetCancelSendingFile(fileID)); nclient->sendNetMessage(message); size = 0; @@ -133,7 +118,7 @@ void YOGClientFileAssembler::cancelSendingFile() void YOGClientFileAssembler::cancelRecievingFile() { - boost::shared_ptr nclient(client); + std::shared_ptr nclient(client); shared_ptr message(new NetCancelRecievingFile(fileID)); nclient->sendNetMessage(message); size = 0; @@ -166,7 +151,7 @@ bool YOGClientFileAssembler::fileInformationRecieved() void YOGClientFileAssembler::sendNextChunk() { - boost::shared_ptr nclient(client); + std::shared_ptr nclient(client); shared_ptr message(new NetSendFileChunk(istream, fileID)); finished += message->getChunkSize(); nclient->sendNetMessage(message); diff --git a/src/YOGClientFileAssembler.h b/src/yog/YOGClientFileAssembler.h similarity index 56% rename from src/YOGClientFileAssembler.h rename to src/yog/YOGClientFileAssembler.h index 58052bc18..fe31f2288 100644 --- a/src/YOGClientFileAssembler.h +++ b/src/yog/YOGClientFileAssembler.h @@ -1,26 +1,10 @@ -/* - Copyright 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGClientFileAssembler_h -#define __YOGClientFileAssembler_h +#pragma once #include "boost/date_time/posix_time/posix_time.hpp" -#include "boost/weak_ptr.hpp" +#include #include "SDL_net.h" #include @@ -39,7 +23,7 @@ class YOGClientFileAssembler { public: ///Contructs a YOGClientFileAssembler connected to the given client, and the given fileID - YOGClientFileAssembler(boost::weak_ptr client, Uint16 fileID); + YOGClientFileAssembler(std::weak_ptr client, Uint16 fileID); ///Updates the map assembler void update(); @@ -51,7 +35,7 @@ class YOGClientFileAssembler void startRecievingFile(std::string mapname); ///This recieves a message from YOG - void handleMessage(boost::shared_ptr message); + void handleMessage(std::shared_ptr message); ///This cancels the sending of a file void cancelSendingFile(); @@ -77,10 +61,10 @@ class YOGClientFileAssembler TransferMode mode; Uint32 size; Uint32 finished; - boost::weak_ptr client; + std::weak_ptr client; GAGCore::MemoryStreamBackend* obackend; - boost::shared_ptr ostream; - boost::shared_ptr istream; + std::shared_ptr ostream; + std::shared_ptr istream; std::string filename; Uint16 fileID; boost::posix_time::ptime sendTime; @@ -90,4 +74,3 @@ class YOGClientFileAssembler -#endif diff --git a/src/YOGClientGameConnectionDialog.cpp b/src/yog/YOGClientGameConnectionDialog.cpp similarity index 71% rename from src/YOGClientGameConnectionDialog.cpp rename to src/yog/YOGClientGameConnectionDialog.cpp index a43244fdc..77e82dae6 100644 --- a/src/YOGClientGameConnectionDialog.cpp +++ b/src/yog/YOGClientGameConnectionDialog.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2007-2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007-2008 Bradley Arsenault #include "YOGClientGameConnectionDialog.h" #include "GUIText.h" @@ -29,7 +13,7 @@ using namespace GAGCore; using namespace GAGGUI; -YOGClientGameConnectionDialog::YOGClientGameConnectionDialog(GraphicContext *parentCtx, boost::shared_ptr game) +YOGClientGameConnectionDialog::YOGClientGameConnectionDialog(GraphicContext *parentCtx, std::shared_ptr game) : OverlayScreen(parentCtx, 200, 100), parentCtx(parentCtx), game(game) { addWidget(new Text(0, 20, ALIGN_FILL, ALIGN_LEFT, "standard", Toolkit::getStringTable()->getString("[connecting to game]"))); @@ -112,7 +96,7 @@ void YOGClientGameConnectionDialog::updateGame() -void YOGClientGameConnectionDialog::handleMultiplayerGameEvent(boost::shared_ptr event) +void YOGClientGameConnectionDialog::handleMultiplayerGameEvent(std::shared_ptr event) { Uint8 type = event->getEventType(); if(type == MGEGameRefused) diff --git a/src/yog/YOGClientGameConnectionDialog.h b/src/yog/YOGClientGameConnectionDialog.h new file mode 100644 index 000000000..6cc261153 --- /dev/null +++ b/src/yog/YOGClientGameConnectionDialog.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007-2008 Bradley Arsenault + +#pragma once + +#include "GUIBase.h" +#include "MultiplayerGame.h" +#include +#include "MultiplayerGameEvent.h" +#include "MultiplayerGameEventListener.h" + +class Map; +namespace GAGGUI +{ + class Text; + class ProgressBar; +} +namespace GAGCore +{ + class DrawableSurface; +} + +///This dialog shows progress of the fertility computation +class YOGClientGameConnectionDialog:public GAGGUI::OverlayScreen, public MultiplayerGameEventListener +{ +public: + YOGClientGameConnectionDialog(GAGCore::GraphicContext *parentCtx, std::shared_ptr game); + virtual ~YOGClientGameConnectionDialog(); + virtual void onAction(GAGGUI::Widget *source, GAGGUI::Action action, int par1, int par2); + + using OverlayScreen::execute; // keep base 2-arg execute visible alongside our no-arg overload + ///This screen is modal, this executes it + void execute(); + + ///These are the possible end values + enum EndValue + { + Success, + Failed, + }; +private: + ///This function updates the multiplayer game + void updateGame(); + ///This handles an event from the multiplayer game + void handleMultiplayerGameEvent(std::shared_ptr event); + + GAGCore::GraphicContext *parentCtx; + std::shared_ptr game; +}; + + diff --git a/src/yog/YOGClientGameListListener.cpp b/src/yog/YOGClientGameListListener.cpp new file mode 100644 index 000000000..660612551 --- /dev/null +++ b/src/yog/YOGClientGameListListener.cpp @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#include "YOGClientGameListListener.h" + diff --git a/src/yog/YOGClientGameListListener.h b/src/yog/YOGClientGameListListener.h new file mode 100644 index 000000000..87148b82d --- /dev/null +++ b/src/yog/YOGClientGameListListener.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +///This class represents a listener for game list changes +class YOGClientGameListListener +{ +public: + virtual ~YOGClientGameListListener() {} + + ///This is called when the game list is updated + virtual void gameListUpdated() = 0; +}; + diff --git a/src/YOGClientGameListManager.cpp b/src/yog/YOGClientGameListManager.cpp similarity index 51% rename from src/YOGClientGameListManager.cpp rename to src/yog/YOGClientGameListManager.cpp index 2aeafc0e0..b793f0068 100644 --- a/src/YOGClientGameListManager.cpp +++ b/src/yog/YOGClientGameListManager.cpp @@ -1,44 +1,27 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGClientGameListManager.h" -#include "NetMessage.h" +#include "LobbyMessages.h" #include "YOGClientGameListListener.h" -using boost::static_pointer_cast; +using std::static_pointer_cast; -YOGClientGameListManager::YOGClientGameListManager(YOGClient* client) - : client(client) +YOGClientGameListManager::YOGClientGameListManager(YOGClient* /*client*/) { - } -void YOGClientGameListManager::recieveMessage(boost::shared_ptr message) +void YOGClientGameListManager::recieveMessage(std::shared_ptr message) { Uint8 type = message->getMessageType(); ///This recieves a game list update message if(type==MNetUpdateGameList) { - shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); info->applyDifferences(games); sendToListeners(); } diff --git a/src/YOGClientGameListManager.h b/src/yog/YOGClientGameListManager.h similarity index 54% rename from src/YOGClientGameListManager.h rename to src/yog/YOGClientGameListManager.h index 8e949a870..a30c6cdc7 100644 --- a/src/YOGClientGameListManager.h +++ b/src/yog/YOGClientGameListManager.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientGameListManager_h -#define YOGClientGameListManager_h - -#include "boost/shared_ptr.hpp" +#include #include #include "YOGGameInfo.h" @@ -35,7 +19,7 @@ class YOGClientGameListManager YOGClientGameListManager(YOGClient* client); ///Recieves an incoming message - void recieveMessage(boost::shared_ptr message); + void recieveMessage(std::shared_ptr message); ///This will return the list of games on hosted on the server. const std::list& getGameList() const; @@ -58,7 +42,5 @@ class YOGClientGameListManager std::list games; std::list listeners; - YOGClient* client; }; -#endif diff --git a/src/YOGClientLobbyScreen.cpp b/src/yog/YOGClientLobbyScreen.cpp similarity index 88% rename from src/YOGClientLobbyScreen.cpp rename to src/yog/YOGClientLobbyScreen.cpp index 482097f9c..717c14d3d 100644 --- a/src/YOGClientLobbyScreen.cpp +++ b/src/yog/YOGClientLobbyScreen.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "ChooseMapScreen.h" #include "Engine.h" @@ -93,7 +77,7 @@ void YOGClientPlayerList::drawItem(int x, int y, size_t element) -YOGClientLobbyScreen::YOGClientLobbyScreen(TabScreen* parent, boost::shared_ptr client) +YOGClientLobbyScreen::YOGClientLobbyScreen(TabScreen* parent, std::shared_ptr client) : TabScreenWindow(parent, Toolkit::getStringTable()->getString("[Lobby]")), client(client) { addWidget(new Text(0, 10, ALIGN_FILL, ALIGN_TOP, "menu", Toolkit::getStringTable()->getString("[yog]"))); @@ -191,7 +175,7 @@ void YOGClientLobbyScreen::onAction(Widget *source, Action action, int par1, int } else { - boost::shared_ptr message(new YOGMessage); + std::shared_ptr message(new YOGMessage); message->setSender(client->getUsername()); message->setMessage(textInput->getText()); message->setMessageType(YOGNormalMessage); @@ -223,7 +207,7 @@ void YOGClientLobbyScreen::onTimer(Uint32 tick) int rc = parent->getReturnCode(gameScreen); if(rc!=-1) { - boost::shared_ptr game(client->getMultiplayerGame()); + std::shared_ptr game(client->getMultiplayerGame()); if(rc == MultiplayerGameScreen::Kicked) recieveInternalMessage(Toolkit::getStringTable()->getString("[You where kicked from the game]")); else if(rc == MultiplayerGameScreen::GameCancelled) @@ -239,7 +223,7 @@ void YOGClientLobbyScreen::onTimer(Uint32 tick) else if(game->getGameCreationState() == YOGCreateRefusalUnknown) recieveInternalMessage("Game was refused by server"); } - client->setMultiplayerGame(boost::shared_ptr()); + client->setMultiplayerGame(std::shared_ptr()); gameScreen=-1; updateButtonVisibility(); } @@ -256,7 +240,7 @@ void YOGClientLobbyScreen::onTimer(Uint32 tick) -void YOGClientLobbyScreen::handleYOGClientEvent(boost::shared_ptr event) +void YOGClientLobbyScreen::handleYOGClientEvent(std::shared_ptr event) { //std::cout<<"YOGClientLobbyScreen: recieved event "<format()<getEventType(); @@ -287,7 +271,7 @@ void YOGClientLobbyScreen::handleIRCTextMessage(const std::string& message) -void YOGClientLobbyScreen::recieveTextMessage(boost::shared_ptr message) +void YOGClientLobbyScreen::recieveTextMessage(std::shared_ptr message) { chatWindow->addText(message->formatForReading()); chatWindow->addImage(0); @@ -326,11 +310,11 @@ void YOGClientLobbyScreen::playerListUpdated() void YOGClientLobbyScreen::hostGame() { - ChooseMapScreen cms("maps", "map", false, "games", "game", NULL); + ChooseMapScreen cms("maps", "map", false, "games", "game", false); int rc = cms.execute(globalContainer->gfx, 40); if(rc == ChooseMapScreen::OK) { - boost::shared_ptr game(new MultiplayerGame(client)); + std::shared_ptr game(new MultiplayerGame(client)); client->setMultiplayerGame(game); std::string name = FormatableString(Toolkit::getStringTable()->getString("[%0's game]")).arg(client->getUsername()); game->createNewGame(name); @@ -350,9 +334,9 @@ void YOGClientLobbyScreen::hostGame() void YOGClientLobbyScreen::joinGame() { - if(gameList->getSelectionIndex() != -1) + if(gameList->selection()) { - boost::shared_ptr game(new MultiplayerGame(client)); + std::shared_ptr game(new MultiplayerGame(client)); client->setMultiplayerGame(game); Uint16 id = 0; for (std::list::const_iterator game=client->getGameListManager()->getGameList().begin(); game!=client->getGameListManager()->getGameList().end(); ++game) @@ -398,7 +382,7 @@ void YOGClientLobbyScreen::updateGameList(void) void YOGClientLobbyScreen::updatePlayerList(void) { -// boost::shared_ptr irc = ircChat->getIRC(); +// std::shared_ptr irc = ircChat->getIRC(); // update YOG one playerList->clear(); for (std::list::const_iterator player=client->getPlayerListManager()->getPlayerList().begin(); player!=client->getPlayerListManager()->getPlayerList().end(); ++player) @@ -421,7 +405,7 @@ void YOGClientLobbyScreen::updatePlayerList(void) void YOGClientLobbyScreen::updateBoxInfo() { - if (gameList->getSelectionIndex() != -1) + if (gameList->selection()) { for (std::list::const_iterator game=client->getGameListManager()->getGameList().begin(); game!=client->getGameListManager()->getGameList().end(); ++game) { @@ -441,7 +425,7 @@ void YOGClientLobbyScreen::updateBoxInfo() } } } - else if(playerList->getSelectionIndex() != -1) + else if(playerList->selection()) { if(client->getPlayerListManager()->doesPlayerExist(playerList->get())) { diff --git a/src/YOGClientLobbyScreen.h b/src/yog/YOGClientLobbyScreen.h similarity index 72% rename from src/YOGClientLobbyScreen.h rename to src/yog/YOGClientLobbyScreen.h index 32059b592..b3db21405 100644 --- a/src/YOGClientLobbyScreen.h +++ b/src/yog/YOGClientLobbyScreen.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGSCREEN_H -#define __YOGSCREEN_H +#pragma once #include #include @@ -87,7 +69,7 @@ class YOGClientLobbyScreen : public TabScreenWindow, public YOGClientEventListen { public: ///This takes a YOGClient. The client must be logged in when this is called. - YOGClientLobbyScreen(TabScreen* parent, boost::shared_ptr client); + YOGClientLobbyScreen(TabScreen* parent, std::shared_ptr client); virtual ~YOGClientLobbyScreen(); @@ -96,11 +78,11 @@ class YOGClientLobbyScreen : public TabScreenWindow, public YOGClientEventListen ///Responds to widget events void onAction(Widget *source, Action action, int par1, int par2); ///Responds to YOG events - void handleYOGClientEvent(boost::shared_ptr event); + void handleYOGClientEvent(std::shared_ptr event); ///Handle text message events from IRCTextMessageHandler void handleIRCTextMessage(const std::string& message); ///Handles text message events from the YOGClientChatChannel - void recieveTextMessage(boost::shared_ptr message); + void recieveTextMessage(std::shared_ptr message); ///Handles an internal message void recieveInternalMessage(const std::string& message); ///Handles when the game list has been updated from YOGClientGameListManager @@ -154,12 +136,11 @@ class YOGClientLobbyScreen : public TabScreenWindow, public YOGClientEventListen TextButton *joinButton; TextButton *hostButton; - boost::shared_ptr client; - boost::shared_ptr lobbyChat; - boost::shared_ptr ircChat; + std::shared_ptr client; + std::shared_ptr lobbyChat; + std::shared_ptr ircChat; int gameScreen; }; -#endif diff --git a/src/YOGClientMapDownloadScreen.cpp b/src/yog/YOGClientMapDownloadScreen.cpp similarity index 91% rename from src/YOGClientMapDownloadScreen.cpp rename to src/yog/YOGClientMapDownloadScreen.cpp index b874c98cf..38c9bb5a3 100644 --- a/src/YOGClientMapDownloadScreen.cpp +++ b/src/yog/YOGClientMapDownloadScreen.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "ChooseMapScreen.h" #include @@ -28,7 +13,6 @@ #include #include #include -#include "NetMessage.h" #include "StringTable.h" #include "Toolkit.h" #include "TextSort.h" @@ -41,7 +25,7 @@ using namespace GAGCore; -YOGClientMapDownloadScreen::YOGClientMapDownloadScreen(TabScreen* parent, boost::shared_ptr client) +YOGClientMapDownloadScreen::YOGClientMapDownloadScreen(TabScreen* parent, std::shared_ptr client) : TabScreenWindow(parent, Toolkit::getStringTable()->getString("[Download Maps]")), client(client) { addWidget(new Text(0, 10, ALIGN_FILL, ALIGN_TOP, "menu", Toolkit::getStringTable()->getString("[Download Maps]"))); @@ -230,10 +214,7 @@ void YOGClientMapDownloadScreen::requestMaps() void YOGClientMapDownloadScreen::updateMapInfo() { - if(mapList->getSelectionIndex() != -1) - mapValid=true; - else - mapValid=false; + mapValid = mapList->selection().has_value(); updateMapPreview(); if(mapValid) diff --git a/src/YOGClientMapDownloadScreen.h b/src/yog/YOGClientMapDownloadScreen.h similarity index 72% rename from src/YOGClientMapDownloadScreen.h rename to src/yog/YOGClientMapDownloadScreen.h index 3cbf6b503..5318b500f 100644 --- a/src/YOGClientMapDownloadScreen.h +++ b/src/yog/YOGClientMapDownloadScreen.h @@ -1,26 +1,10 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientMapDownloadScreen_h -#define YOGClientMapDownloadScreen_h +#pragma once #include "GUITabScreenWindow.h" -#include "boost/shared_ptr.hpp" +#include #include "YOGClientDownloadableMapListener.h" namespace GAGGUI @@ -44,7 +28,7 @@ using namespace GAGGUI; class YOGClientMapDownloadScreen : public TabScreenWindow, public YOGClientDownloadableMapListener { public: - YOGClientMapDownloadScreen(TabScreen* parent, boost::shared_ptr client); + YOGClientMapDownloadScreen(TabScreen* parent, std::shared_ptr client); ~YOGClientMapDownloadScreen(); ///Responds to timer events virtual void onTimer(Uint32 tick); @@ -79,7 +63,7 @@ class YOGClientMapDownloadScreen : public TabScreenWindow, public YOGClientDownl void updateMapPreview(); - boost::shared_ptr client; + std::shared_ptr client; List* mapList; //! The widget that will show a preview of the selection map MapPreview *mapPreview; @@ -131,5 +115,4 @@ class MapListSorter SortMethod sortMethod; }; -#endif diff --git a/src/YOGClientMapDownloader.cpp b/src/yog/YOGClientMapDownloader.cpp similarity index 51% rename from src/YOGClientMapDownloader.cpp rename to src/yog/YOGClientMapDownloader.cpp index 04f469b61..70c70d0c5 100644 --- a/src/YOGClientMapDownloader.cpp +++ b/src/yog/YOGClientMapDownloader.cpp @@ -1,28 +1,13 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGClientMapDownloader.h" #include "YOGClient.h" -#include "NetMessage.h" +#include "FileTransferMessages.h" #include "YOGClientFileAssembler.h" -YOGClientMapDownloader::YOGClientMapDownloader(boost::shared_ptr client) +YOGClientMapDownloader::YOGClientMapDownloader(std::shared_ptr client) : client(client) { client->setMapDownloader(this); @@ -41,11 +26,11 @@ void YOGClientMapDownloader::startDownloading(const YOGDownloadableMapInfo& map) { // construct downloader fileID = map.getFileID(); - boost::shared_ptr assembler(new YOGClientFileAssembler(client, fileID)); + std::shared_ptr assembler(new YOGClientFileAssembler(client, fileID)); assembler->startRecievingFile(map.getMapHeader().getFileName()); client->setYOGClientFileAssembler(fileID, assembler); - boost::shared_ptr message(new NetRequestFile(fileID)); + std::shared_ptr message(new NetRequestFile(fileID)); client->sendNetMessage(message); state = DownloadingMap; } @@ -63,7 +48,7 @@ void YOGClientMapDownloader::cancelDownload() -void YOGClientMapDownloader::recieveMessage(boost::shared_ptr message) +void YOGClientMapDownloader::recieveMessage(std::shared_ptr message) { } diff --git a/src/yog/YOGClientMapDownloader.h b/src/yog/YOGClientMapDownloader.h new file mode 100644 index 000000000..46a213505 --- /dev/null +++ b/src/yog/YOGClientMapDownloader.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include "YOGDownloadableMapInfo.h" +#include +#include + +class YOGClient; +class NetMessage; + +///This class manages the downloading of a map from the server +class YOGClientMapDownloader +{ +public: + ///Constructs a map uploader + YOGClientMapDownloader(std::shared_ptr client); + + ///Removes the map uploader + ~YOGClientMapDownloader(); + + ///Starts downloading the given map + void startDownloading(const YOGDownloadableMapInfo& map); + + ///If this downloader is downloading a map, this will cancel the download + void cancelDownload(); + + ///This recieves a message from the server + void recieveMessage(std::shared_ptr message); + + ///This updates the downloader + void update(); + + enum DownloadingState + { + Nothing, + DownloadingMap, + Finished, + }; + ///Returns the current downloading state + DownloadingState getDownloadingState(); + + ///Returns the percent downloaded + int getPercentUploaded(); +private: + DownloadingState state; + std::shared_ptr client; + Uint16 fileID; + std::string mapFile; +}; + diff --git a/src/YOGClientMapUploadScreen.cpp b/src/yog/YOGClientMapUploadScreen.cpp similarity index 86% rename from src/YOGClientMapUploadScreen.cpp rename to src/yog/YOGClientMapUploadScreen.cpp index f26a4223c..a8e2bf44f 100644 --- a/src/YOGClientMapUploadScreen.cpp +++ b/src/yog/YOGClientMapUploadScreen.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include #include "Engine.h" @@ -37,7 +22,7 @@ using namespace GAGCore; -YOGClientMapUploadScreen::YOGClientMapUploadScreen(boost::shared_ptr client, const std::string mapFile) +YOGClientMapUploadScreen::YOGClientMapUploadScreen(std::shared_ptr client, const std::string mapFile) : client(client), uploader(client), mapFile(mapFile) { addWidget(new Text(0, 10, ALIGN_FILL, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[Upload Map]"))); diff --git a/src/yog/YOGClientMapUploadScreen.h b/src/yog/YOGClientMapUploadScreen.h new file mode 100644 index 000000000..c11181e81 --- /dev/null +++ b/src/yog/YOGClientMapUploadScreen.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include "Glob2Screen.h" +#include +#include "YOGClientMapUploader.h" + +namespace GAGGUI +{ + class Text; + class TextInput; + class TextArea; + class TextButton; + class TabScreen; + class Widget; + class List; + class ProgressBar; +} + +class YOGClient; +class MapPreview; + +using namespace GAGGUI; + +/// A widget that maintains the list of players, and draws an icon based +/// on whether that player is from YOG or from IRC +class YOGClientMapUploadScreen : public Glob2Screen +{ +public: + + /// Constructor + YOGClientMapUploadScreen(std::shared_ptr client, const std::string mapFile); + + ///Responds to widget events + void onAction(Widget *source, Action action, int par1, int par2); + ///Responds to timer events + void onTimer(Uint32 tick); + + enum + { + CANCEL, + UPLOAD, + UPLOADFAILED, + UPLOADFINISHED, + CONNECTIONLOST, + }; +private: + MapPreview* preview; + std::shared_ptr client; + YOGClientMapUploader uploader; + Text* uploadStatusText; + ProgressBar* uploadStatus; + //! The textual informations about the selected map + Text *mapInfo, *mapVersion, *mapSize, *mapDate; + TextInput* mapName; + Text *authorNameText; + TextInput* authorName; + std::string mapFile; + bool isUploading; +}; + diff --git a/src/YOGClientMapUploader.cpp b/src/yog/YOGClientMapUploader.cpp similarity index 68% rename from src/YOGClientMapUploader.cpp rename to src/yog/YOGClientMapUploader.cpp index 9985022dc..595356037 100644 --- a/src/YOGClientMapUploader.cpp +++ b/src/yog/YOGClientMapUploader.cpp @@ -1,23 +1,8 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "Engine.h" -#include "NetMessage.h" +#include "MapUploadMessages.h" #include "YOGClientFileAssembler.h" #include "YOGClient.h" #include "YOGClientMapUploader.h" @@ -28,9 +13,9 @@ #include "Stream.h" #include "BinaryStream.h" -using boost::static_pointer_cast; +using std::static_pointer_cast; -YOGClientMapUploader::YOGClientMapUploader(boost::shared_ptr client) +YOGClientMapUploader::YOGClientMapUploader(std::shared_ptr client) : state(Nothing), client(client) { client->setMapUploader(this); @@ -58,7 +43,7 @@ void YOGClientMapUploader::startUploading(const std::string& nmapFile, const std info.setAuthorName(authorName); info.setDimensions(w, h); info.setSize(getCompressedSize(nmapFile)); - boost::shared_ptr message(new NetRequestMapUpload(info)); + std::shared_ptr message(new NetRequestMapUpload(info)); client->sendNetMessage(message); state = WaitingForUploadReply; } @@ -76,7 +61,7 @@ void YOGClientMapUploader::cancelUpload() -void YOGClientMapUploader::recieveMessage(boost::shared_ptr message) +void YOGClientMapUploader::recieveMessage(std::shared_ptr message) { Uint8 type = message->getMessageType(); //This recieves the server information @@ -86,7 +71,7 @@ void YOGClientMapUploader::recieveMessage(boost::shared_ptr message) fileID = info->getFileID(); state = UploadingMap; - boost::shared_ptr assembler(new YOGClientFileAssembler(client, fileID)); + std::shared_ptr assembler(new YOGClientFileAssembler(client, fileID)); assembler->startSendingFile(mapFile); client->setYOGClientFileAssembler(fileID, assembler); } diff --git a/src/YOGClientMapUploader.h b/src/yog/YOGClientMapUploader.h similarity index 54% rename from src/YOGClientMapUploader.h rename to src/yog/YOGClientMapUploader.h index d6ac13d6f..e2c47817e 100644 --- a/src/YOGClientMapUploader.h +++ b/src/yog/YOGClientMapUploader.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientMapUploader_h -#define YOGClientMapUploader_h - -#include "boost/shared_ptr.hpp" +#include #include "YOGConsts.h" #include @@ -31,7 +15,7 @@ class YOGClientMapUploader { public: ///Constructs a map uploader - YOGClientMapUploader(boost::shared_ptr client); + YOGClientMapUploader(std::shared_ptr client); ///Removes the map uploader ~YOGClientMapUploader(); @@ -43,7 +27,7 @@ class YOGClientMapUploader void cancelUpload(); ///This recieves a message from the server - void recieveMessage(boost::shared_ptr message); + void recieveMessage(std::shared_ptr message); ///This updates the uploader void update(); @@ -68,10 +52,9 @@ class YOGClientMapUploader int getCompressedSize(const std::string& mapName); private: UploadingState state; - boost::shared_ptr client; + std::shared_ptr client; Uint16 fileID; YOGMapUploadRefusalReason reason; std::string mapFile; }; -#endif diff --git a/src/YOGClientOptionsScreen.cpp b/src/yog/YOGClientOptionsScreen.cpp similarity index 76% rename from src/YOGClientOptionsScreen.cpp rename to src/yog/YOGClientOptionsScreen.cpp index a5199fb51..b6008d7b2 100644 --- a/src/YOGClientOptionsScreen.cpp +++ b/src/yog/YOGClientOptionsScreen.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include #include @@ -30,7 +15,7 @@ using namespace GAGCore; -YOGClientOptionsScreen::YOGClientOptionsScreen(TabScreen* parent, boost::shared_ptr client) +YOGClientOptionsScreen::YOGClientOptionsScreen(TabScreen* parent, std::shared_ptr client) : TabScreenWindow(parent, Toolkit::getStringTable()->getString("[Options]")), client(client) { addWidget(new Text(0, 10, ALIGN_FILL, ALIGN_TOP, "menu", Toolkit::getStringTable()->getString("[Options]"))); @@ -117,13 +102,12 @@ void YOGClientOptionsScreen::updateBlockedPlayerAdd() void YOGClientOptionsScreen::updateBlockedPlayerRemove() { - if(blockedPlayers->getSelectionIndex()!=-1) + if (auto sel = blockedPlayers->selection()) { std::string name = blockedPlayers->get(); client->getBlockedList()->removeBlockedPlayer(name); - int n = blockedPlayers->getSelectionIndex(); - blockedPlayers->removeText(blockedPlayers->getSelectionIndex()); - blockedPlayers->setSelectionIndex(std::min(int(blockedPlayers->getCount())-1, n)); + blockedPlayers->removeText(*sel); + blockedPlayers->setSelectionIndex(std::min(int(blockedPlayers->getCount())-1, int(*sel))); client->getBlockedList()->save(); } } diff --git a/src/YOGClientOptionsScreen.h b/src/yog/YOGClientOptionsScreen.h similarity index 51% rename from src/YOGClientOptionsScreen.h rename to src/yog/YOGClientOptionsScreen.h index fdec6e29c..20bf3f905 100644 --- a/src/YOGClientOptionsScreen.h +++ b/src/yog/YOGClientOptionsScreen.h @@ -1,27 +1,11 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientOptionsScreen_h -#define YOGClientOptionsScreen_h +#pragma once #include #include "GUITabScreenWindow.h" -#include "boost/shared_ptr.hpp" +#include namespace GAGGUI @@ -45,7 +29,7 @@ class YOGClientOptionsScreen : public TabScreenWindow public: /// Constructor - YOGClientOptionsScreen(TabScreen* parent, boost::shared_ptr client); + YOGClientOptionsScreen(TabScreen* parent, std::shared_ptr client); ///Called when this tab is activated void onActivated(); @@ -67,7 +51,7 @@ class YOGClientOptionsScreen : public TabScreenWindow ///Removes a blocked player from the text move void updateBlockedPlayerRemove(); - boost::shared_ptr client; + std::shared_ptr client; List* blockedPlayers; Text* blockedPlayersText; @@ -76,4 +60,3 @@ class YOGClientOptionsScreen : public TabScreenWindow TextButton* addBlockedPlayer; }; -#endif diff --git a/src/yog/YOGClientPlayerListListener.cpp b/src/yog/YOGClientPlayerListListener.cpp new file mode 100644 index 000000000..5d142e6d9 --- /dev/null +++ b/src/yog/YOGClientPlayerListListener.cpp @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#include "YOGClientPlayerListListener.h" + + diff --git a/src/yog/YOGClientPlayerListListener.h b/src/yog/YOGClientPlayerListListener.h new file mode 100644 index 000000000..bc83c2538 --- /dev/null +++ b/src/yog/YOGClientPlayerListListener.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +class YOGClientPlayerListListener +{ +public: + virtual ~YOGClientPlayerListListener() {} + + virtual void playerListUpdated() = 0; +}; + + + diff --git a/src/YOGClientPlayerListManager.cpp b/src/yog/YOGClientPlayerListManager.cpp similarity index 63% rename from src/YOGClientPlayerListManager.cpp rename to src/yog/YOGClientPlayerListManager.cpp index c0272173e..4494d850a 100644 --- a/src/YOGClientPlayerListManager.cpp +++ b/src/yog/YOGClientPlayerListManager.cpp @@ -1,41 +1,24 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGClientPlayerListManager.h" #include "YOGClientPlayerListListener.h" -#include "NetMessage.h" +#include "LobbyMessages.h" -using boost::static_pointer_cast; +using std::static_pointer_cast; -YOGClientPlayerListManager::YOGClientPlayerListManager(YOGClient* client) - : client(client) +YOGClientPlayerListManager::YOGClientPlayerListManager(YOGClient* /*client*/) { - } -void YOGClientPlayerListManager::recieveMessage(boost::shared_ptr message) +void YOGClientPlayerListManager::recieveMessage(std::shared_ptr message) { Uint8 type = message->getMessageType(); if(type==MNetUpdatePlayerList) { - shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); info->applyDifferences(players); sendToListeners(); } diff --git a/src/YOGClientPlayerListManager.h b/src/yog/YOGClientPlayerListManager.h similarity index 60% rename from src/YOGClientPlayerListManager.h rename to src/yog/YOGClientPlayerListManager.h index 3203a3dd8..697dc1b6a 100644 --- a/src/YOGClientPlayerListManager.h +++ b/src/yog/YOGClientPlayerListManager.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGClientPlayerListManager_h -#define YOGClientPlayerListManager_h - -#include "boost/shared_ptr.hpp" +#include #include #include "YOGPlayerSessionInfo.h" @@ -35,7 +19,7 @@ class YOGClientPlayerListManager YOGClientPlayerListManager(YOGClient* client); ///Recieves an incoming message - void recieveMessage(boost::shared_ptr message); + void recieveMessage(std::shared_ptr message); ///This will return the list of players on hosted on the server. const std::list& getPlayerList() const; @@ -63,7 +47,5 @@ class YOGClientPlayerListManager std::list players; std::list listeners; - YOGClient* client; }; -#endif diff --git a/src/YOGClientRatedMapList.cpp b/src/yog/YOGClientRatedMapList.cpp similarity index 69% rename from src/YOGClientRatedMapList.cpp rename to src/yog/YOGClientRatedMapList.cpp index 241f41fda..5ca146cdb 100644 --- a/src/YOGClientRatedMapList.cpp +++ b/src/yog/YOGClientRatedMapList.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "FileManager.h" #include "Stream.h" diff --git a/src/yog/YOGClientRatedMapList.h b/src/yog/YOGClientRatedMapList.h new file mode 100644 index 000000000..0ff68f65a --- /dev/null +++ b/src/yog/YOGClientRatedMapList.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include + +///This class holds the list of rated maps +class YOGClientRatedMapList +{ +public: + ///Loads the list of rated maps + YOGClientRatedMapList(const std::string& username); + + ///Sets a map that the user has rated by the user + void addRatedMap(const std::string& mapname); + + ///Returns true if the given map has been rated by the user, false otherwise + bool isMapRated(const std::string& mapname); + +private: + ///Saves the list + void save(); + ///Loads the list + void load(); + + std::set maps; + std::string username; +}; + diff --git a/src/YOGClientRouterAdministrator.cpp b/src/yog/YOGClientRouterAdministrator.cpp similarity index 66% rename from src/YOGClientRouterAdministrator.cpp rename to src/yog/YOGClientRouterAdministrator.cpp index 1115ca790..f2aea8932 100644 --- a/src/YOGClientRouterAdministrator.cpp +++ b/src/yog/YOGClientRouterAdministrator.cpp @@ -1,29 +1,14 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include #include #include "NetConnection.h" -#include "NetMessage.h" +#include "RouterAdminMessages.h" #include "YOGClientRouterAdministrator.h" #include "YOGConsts.h" -using boost::static_pointer_cast; +using std::static_pointer_cast; YOGClientRouterAdministrator::YOGClientRouterAdministrator() { @@ -68,7 +53,7 @@ int YOGClientRouterAdministrator::execute() return 1; } - boost::shared_ptr login(new NetRouterAdministratorLogin(password)); + std::shared_ptr login(new NetRouterAdministratorLogin(password)); connect.sendMessage(login); //Parse incoming messages and generate events @@ -88,7 +73,7 @@ int YOGClientRouterAdministrator::execute() if(type == MNetRouterAdministratorLoginRefused) { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); YOGRouterAdministratorLoginRefusalReason reason = info->getReason(); if(reason == YOGRouterLoginWrongPassword) { @@ -112,7 +97,7 @@ int YOGClientRouterAdministrator::execute() std::cout< cmd(new NetRouterAdministratorSendCommand(command)); + std::shared_ptr cmd(new NetRouterAdministratorSendCommand(command)); connect.sendMessage(cmd); //Parse incoming messages and generate events @@ -129,7 +114,7 @@ int YOGClientRouterAdministrator::execute() Uint8 type = message->getMessageType(); if(type == MNetRouterAdministratorSendText) { - boost::shared_ptr info = static_pointer_cast(message); + std::shared_ptr info = static_pointer_cast(message); std::cout<getText()< or +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOG_CONSTS_H -#define __YOG_CONSTS_H +#pragma once #include #include "SDL_net.h" @@ -177,4 +160,3 @@ enum YOGMapUploadRefusalReason YOGMapUploadReasonUnknown, }; -#endif diff --git a/src/YOGDownloadableMapInfo.cpp b/src/yog/YOGDownloadableMapInfo.cpp similarity index 82% rename from src/YOGDownloadableMapInfo.cpp rename to src/yog/YOGDownloadableMapInfo.cpp index 880cc5404..7eaf5935e 100644 --- a/src/YOGDownloadableMapInfo.cpp +++ b/src/yog/YOGDownloadableMapInfo.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGDownloadableMapInfo.h" #include "Stream.h" diff --git a/src/YOGDownloadableMapInfo.h b/src/yog/YOGDownloadableMapInfo.h similarity index 73% rename from src/YOGDownloadableMapInfo.h rename to src/yog/YOGDownloadableMapInfo.h index d9acfd65f..3cdaa2b3d 100644 --- a/src/YOGDownloadableMapInfo.h +++ b/src/yog/YOGDownloadableMapInfo.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGDownloadableMapInfo_h -#define YOGDownloadableMapInfo_h +#pragma once #include "MapHeader.h" #include @@ -111,4 +95,3 @@ class YOGDownloadableMapInfo Uint32 size; }; -#endif diff --git a/src/YOGGameInfo.cpp b/src/yog/YOGGameInfo.cpp similarity index 80% rename from src/YOGGameInfo.cpp rename to src/yog/YOGGameInfo.cpp index 64de56955..00f7a248e 100644 --- a/src/YOGGameInfo.cpp +++ b/src/yog/YOGGameInfo.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "YOGGameInfo.h" #include diff --git a/src/YOGGameInfo.h b/src/yog/YOGGameInfo.h similarity index 73% rename from src/YOGGameInfo.h rename to src/yog/YOGGameInfo.h index 72818d845..9515cbfa7 100644 --- a/src/YOGGameInfo.h +++ b/src/yog/YOGGameInfo.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGGameInfo_h -#define __YOGGameInfo_h +#pragma once #include #include "SDL_net.h" @@ -110,4 +94,3 @@ class YOGGameInfo Uint8 numberOfTeams; }; -#endif diff --git a/src/YOGGameResults.cpp b/src/yog/YOGGameResults.cpp similarity index 69% rename from src/YOGGameResults.cpp rename to src/yog/YOGGameResults.cpp index 993187cd2..49cb805d1 100644 --- a/src/YOGGameResults.cpp +++ b/src/yog/YOGGameResults.cpp @@ -1,20 +1,5 @@ -/* - Copyright 2008 (C) Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGGameResults.h" diff --git a/src/YOGGameResults.h b/src/yog/YOGGameResults.h similarity index 54% rename from src/YOGGameResults.h rename to src/yog/YOGGameResults.h index 8065a9003..ce8ecca3f 100644 --- a/src/YOGGameResults.h +++ b/src/yog/YOGGameResults.h @@ -1,24 +1,8 @@ -/* - Copyright 2008 (C) Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#ifndef YOGGameResults_h -#define YOGGameResults_h +#pragma once #include "YOGConsts.h" #include @@ -55,4 +39,3 @@ class YOGGameResults std::map results; }; -#endif diff --git a/src/YOGLoginScreen.cpp b/src/yog/YOGLoginScreen.cpp similarity index 87% rename from src/YOGLoginScreen.cpp rename to src/yog/YOGLoginScreen.cpp index 622038675..2a2036c75 100644 --- a/src/YOGLoginScreen.cpp +++ b/src/yog/YOGLoginScreen.cpp @@ -1,24 +1,6 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière #include "GlobalContainer.h" #include @@ -40,9 +22,9 @@ #include "YOGLoginScreen.h" #include "YOGRegisterScreen.h" -using boost::static_pointer_cast; +using std::static_pointer_cast; -YOGLoginScreen::YOGLoginScreen(boost::shared_ptr client) +YOGLoginScreen::YOGLoginScreen(std::shared_ptr client) : client(client) { addWidget(new TextButton(440, 420, 180, 40, ALIGN_SCREEN_CENTERED, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[Cancel]"), CANCEL, 27)); @@ -160,7 +142,7 @@ void YOGLoginScreen::onTimer(Uint32 tick) -void YOGLoginScreen::handleYOGClientEvent(boost::shared_ptr event) +void YOGLoginScreen::handleYOGClientEvent(std::shared_ptr event) { //std::cout<<"YOGLoginScreen: recieved event "<format()<getEventType(); diff --git a/src/YOGLoginScreen.h b/src/yog/YOGLoginScreen.h similarity index 50% rename from src/YOGLoginScreen.h rename to src/yog/YOGLoginScreen.h index deb9cb58e..2fa21d8ee 100644 --- a/src/YOGLoginScreen.h +++ b/src/yog/YOGLoginScreen.h @@ -1,26 +1,8 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière - for any question or comment contact us at or - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGLoginScreen_h -#define __YOGLoginScreen_h +#pragma once #include "Glob2Screen.h" #include "YOGClientEventListener.h" @@ -43,7 +25,7 @@ class YOGLoginScreen : public Glob2Screen, public YOGClientEventListener public: ///Construct with the given YOG client. ///The provided client should not yet be connected to YOG. - YOGLoginScreen(boost::shared_ptr client); + YOGLoginScreen(std::shared_ptr client); virtual ~YOGLoginScreen(); enum @@ -73,7 +55,7 @@ class YOGLoginScreen : public Glob2Screen, public YOGClientEventListener void onAction(Widget *source, Action action, int par1, int par2); ///Responds to YOG events - void handleYOGClientEvent(boost::shared_ptr event); + void handleYOGClientEvent(std::shared_ptr event); ///Attempt a login with the entered information void attemptLogin(); @@ -89,8 +71,7 @@ class YOGLoginScreen : public Glob2Screen, public YOGClientEventListener bool wasConnecting; - boost::shared_ptr client; + std::shared_ptr client; bool changeTabAgain; }; -#endif diff --git a/src/YOGMessage.cpp b/src/yog/YOGMessage.cpp similarity index 76% rename from src/YOGMessage.cpp rename to src/yog/YOGMessage.cpp index 044d14245..40ff473bd 100644 --- a/src/YOGMessage.cpp +++ b/src/yog/YOGMessage.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "assert.h" #include "SDL_net.h" diff --git a/src/YOGMessage.h b/src/yog/YOGMessage.h similarity index 64% rename from src/YOGMessage.h rename to src/yog/YOGMessage.h index cbca3efaf..b0e1b801a 100644 --- a/src/YOGMessage.h +++ b/src/yog/YOGMessage.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGMessage_h -#define __YOGMessage_h +#pragma once #include #include "YOGConsts.h" @@ -78,4 +62,3 @@ class YOGMessage -#endif diff --git a/src/yog/YOGPlayerPrivateInfo.cpp b/src/yog/YOGPlayerPrivateInfo.cpp new file mode 100644 index 000000000..eee43470d --- /dev/null +++ b/src/yog/YOGPlayerPrivateInfo.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#include + +#include "YOGPlayerPrivateInfo.h" +#include "SDL_net.h" +#include "Stream.h" + +YOGPlayerPrivateInfo::YOGPlayerPrivateInfo() +{ + +} + + +void YOGPlayerPrivateInfo::encodeData(GAGCore::OutputStream* stream) const +{ + stream->writeEnterSection("YOGPlayerPrivateInfo"); + stream->writeLeaveSection(); +} + + + +void YOGPlayerPrivateInfo::decodeData(GAGCore::InputStream* stream) +{ + stream->readEnterSection("YOGPlayerPrivateInfo"); + stream->readLeaveSection(); +} + + + +bool YOGPlayerPrivateInfo::operator==(const YOGPlayerPrivateInfo& rhs) const +{ + //TODO: what's the point of this? -Wall found it + assert(false); +} + + + +bool YOGPlayerPrivateInfo::operator!=(const YOGPlayerPrivateInfo& rhs) const +{ + assert(false); +} + diff --git a/src/yog/YOGPlayerPrivateInfo.h b/src/yog/YOGPlayerPrivateInfo.h new file mode 100644 index 000000000..d8867ded6 --- /dev/null +++ b/src/yog/YOGPlayerPrivateInfo.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include "SDL_net.h" + +namespace GAGCore +{ + class OutputStream; + class InputStream; +} + +///This class stores information about players that isn't not sent to the client +class YOGPlayerPrivateInfo +{ +public: + ///Constructs a default YOGPlayerPrivateInfo + YOGPlayerPrivateInfo(); + + ///Encodes this YOGPlayerPrivateInfo into a bit stream + void encodeData(GAGCore::OutputStream* stream) const; + + ///Decodes this YOGPlayerPrivateInfo from a bit stream + void decodeData(GAGCore::InputStream* stream); + + ///Test for equality between two YOGPlayerPrivateInfo + bool operator==(const YOGPlayerPrivateInfo& rhs) const; + bool operator!=(const YOGPlayerPrivateInfo& rhs) const; +private: +}; + diff --git a/src/YOGPlayerSessionInfo.cpp b/src/yog/YOGPlayerSessionInfo.cpp similarity index 71% rename from src/YOGPlayerSessionInfo.cpp rename to src/yog/YOGPlayerSessionInfo.cpp index 5f79aa27b..b43c74b6a 100644 --- a/src/YOGPlayerSessionInfo.cpp +++ b/src/yog/YOGPlayerSessionInfo.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "YOGPlayerSessionInfo.h" #include "SDL_net.h" diff --git a/src/YOGPlayerSessionInfo.h b/src/yog/YOGPlayerSessionInfo.h similarity index 62% rename from src/YOGPlayerSessionInfo.h rename to src/yog/YOGPlayerSessionInfo.h index 4d58f4577..e417cf52f 100644 --- a/src/YOGPlayerSessionInfo.h +++ b/src/yog/YOGPlayerSessionInfo.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGPlayerSessionInfo_h -#define __YOGPlayerSessionInfo_h +#pragma once #include #include "SDL_net.h" @@ -73,4 +57,3 @@ class YOGPlayerSessionInfo YOGPlayerStoredInfo stored; }; -#endif diff --git a/src/YOGPlayerStoredInfo.cpp b/src/yog/YOGPlayerStoredInfo.cpp similarity index 75% rename from src/YOGPlayerStoredInfo.cpp rename to src/yog/YOGPlayerStoredInfo.cpp index 77d1632b0..f132b77ff 100644 --- a/src/YOGPlayerStoredInfo.cpp +++ b/src/yog/YOGPlayerStoredInfo.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGPlayerStoredInfo.h" diff --git a/src/YOGPlayerStoredInfo.h b/src/yog/YOGPlayerStoredInfo.h similarity index 65% rename from src/YOGPlayerStoredInfo.h rename to src/yog/YOGPlayerStoredInfo.h index 1b38adb65..29e4ed4ed 100644 --- a/src/YOGPlayerStoredInfo.h +++ b/src/yog/YOGPlayerStoredInfo.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGPlayerStoredInfo_h -#define YOGPlayerStoredInfo_h +#pragma once #include "boost/date_time/posix_time/posix_time.hpp" #include "SDL_net.h" @@ -81,4 +65,3 @@ class YOGPlayerStoredInfo int rating; }; -#endif diff --git a/src/YOGRegisterScreen.cpp b/src/yog/YOGRegisterScreen.cpp similarity index 87% rename from src/YOGRegisterScreen.cpp rename to src/yog/YOGRegisterScreen.cpp index 55fa125cb..dde1444a0 100644 --- a/src/YOGRegisterScreen.cpp +++ b/src/yog/YOGRegisterScreen.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGRegisterScreen.h" @@ -32,9 +17,9 @@ #include "GlobalContainer.h" -using boost::static_pointer_cast; +using std::static_pointer_cast; -YOGRegisterScreen::YOGRegisterScreen(boost::shared_ptr client) +YOGRegisterScreen::YOGRegisterScreen(std::shared_ptr client) : client(client) { addWidget(new Text(0, 18, ALIGN_FILL, ALIGN_SCREEN_CENTERED, "menu", Toolkit::getStringTable()->getString("[Register]"))); @@ -163,7 +148,7 @@ void YOGRegisterScreen::onAction(Widget *source, Action action, int par1, int pa -void YOGRegisterScreen::handleYOGClientEvent(boost::shared_ptr event) +void YOGRegisterScreen::handleYOGClientEvent(std::shared_ptr event) { //std::cout<<"YOGLoginScreen: recieved event "<format()<getEventType(); diff --git a/src/yog/YOGRegisterScreen.h b/src/yog/YOGRegisterScreen.h new file mode 100644 index 000000000..451462452 --- /dev/null +++ b/src/yog/YOGRegisterScreen.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include "Glob2Screen.h" +#include "YOGClientEventListener.h" + + +namespace GAGGUI +{ + class OnOffButton; + class Text; + class TextInput; + class TextArea; + class Animation; +} + +class YOGClient; + +class YOGRegisterScreen : public Glob2Screen, public YOGClientEventListener +{ +public: + ///Construct with the given YOG client. + ///The provided client should not yet be connected to YOG. + YOGRegisterScreen(std::shared_ptr client); + ///Destroy the screen + ~YOGRegisterScreen(); + enum + { + Cancelled, + Connected, + }; + + +private: + enum + { + CANCEL, + REGISTER, + }; + + void onTimer(Uint32 tick); + void onAction(Widget *source, Action action, int par1, int par2); + + ///Responds to YOG events + void handleYOGClientEvent(std::shared_ptr event); + + + ///Attempt a registration with the entered information + void attemptRegistration(); + + TextArea *statusText; + TextInput *login, *password, *passwordRepeat; + Animation *animation; + bool wasConnecting; + bool changeTabAgain; + + + std::shared_ptr client; +}; + diff --git a/src/YOGServer.cpp b/src/yog/YOGServer.cpp similarity index 88% rename from src/YOGServer.cpp rename to src/yog/YOGServer.cpp index 3b4980aec..8e8aab335 100644 --- a/src/YOGServer.cpp +++ b/src/yog/YOGServer.cpp @@ -1,26 +1,11 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include #include "Version.h" #include "NetBroadcaster.h" #include "NetConnection.h" -#include "NetMessage.h" +#include "LobbyMessages.h" #include "NetTestSuite.h" #include "YOGServerChatChannel.h" #include "YOGServerGame.h" @@ -128,9 +113,9 @@ void YOGServer::update() boost::posix_time::time_duration organized_game_time = boost::posix_time::second_clock::local_time().time_of_day(); organized_game_time = boost::posix_time::seconds(7200 - organized_game_time.total_seconds() % 7200); std::stringstream s; - s << "An organized game will occur in "<(organized_game_time.hours())<<" hours and "<(organized_game_time.minutes())<<" minutes. There may be more players on! Feel free to join!"; - boost::shared_ptr m(new YOGMessage(s.str(), "server", YOGAdministratorMessage)); - boost::shared_ptr send(new NetSendYOGMessage(LOBBY_CHAT_CHANNEL, m)); + s << "An organized game will occur in "< m(new YOGMessage(s.str(), "server", YOGAdministratorMessage)); + std::shared_ptr send(new NetSendYOGMessage(LOBBY_CHAT_CHANNEL, m)); for(std::map >::iterator i=players.begin(); i!=players.end(); ++i) { i->second->sendMessage(send); @@ -358,7 +343,7 @@ shared_ptr YOGServer::getPlayer(Uint16 playerID) -boost::shared_ptr YOGServer::getPlayer(const std::string& name) +std::shared_ptr YOGServer::getPlayer(const std::string& name) { for(std::map >::iterator i = players.begin(); i!=players.end(); ++i) { @@ -367,7 +352,7 @@ boost::shared_ptr YOGServer::getPlayer(const std::string& name) return i->second; } } - return boost::shared_ptr(); + return std::shared_ptr(); } diff --git a/src/YOGServer.h b/src/yog/YOGServer.h similarity index 83% rename from src/YOGServer.h rename to src/yog/YOGServer.h index 161c1c135..f43aabddc 100644 --- a/src/YOGServer.h +++ b/src/yog/YOGServer.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGServer_h -#define __YOGServer_h - -#include +#include #include "NetListener.h" #include "YOGConsts.h" #include "YOGGameInfo.h" @@ -115,13 +99,13 @@ class YOGServer YOGServerGameJoinRefusalReason canJoinGame(Uint16 gameID); ///Returns the game assocciatted with the given ID - boost::shared_ptr getGame(Uint16 gameID); + std::shared_ptr getGame(Uint16 gameID); ///Returns the player assocciatted with the given ID - boost::shared_ptr getPlayer(Uint16 playerID); + std::shared_ptr getPlayer(Uint16 playerID); ///Returns the player assocciatted with the given name - boost::shared_ptr getPlayer(const std::string& name); + std::shared_ptr getPlayer(const std::string& name); ///This starts LAN broadcasting of the first game, if it exists void enableLANBroadcasting(); @@ -173,11 +157,11 @@ class YOGServer static const bool organizedGameTimeEnabled = false; NetListener nl; - boost::shared_ptr broadcaster; - boost::shared_ptr new_connection; + std::shared_ptr broadcaster; + std::shared_ptr new_connection; - std::map > players; - std::map > games; + std::map > players; + std::map > games; std::list gameList; std::list playerList; @@ -199,4 +183,3 @@ class YOGServer YOGServerPlayerScoreCalculator scoreCalculator; }; -#endif diff --git a/src/YOGServerAdministrator.cpp b/src/yog/YOGServerAdministrator.cpp similarity index 69% rename from src/YOGServerAdministrator.cpp rename to src/yog/YOGServerAdministrator.cpp index f0672852d..a58989095 100644 --- a/src/YOGServerAdministrator.cpp +++ b/src/yog/YOGServerAdministrator.cpp @@ -1,27 +1,12 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGServerAdministrator.h" #include "YOGServerAdministratorCommands.h" #include "YOGServer.h" #include "YOGServerPlayer.h" #include "YOGMessage.h" -#include "NetMessage.h" +#include "LobbyMessages.h" YOGServerAdministrator::YOGServerAdministrator(YOGServer* server) : server(server) @@ -53,7 +38,7 @@ YOGServerAdministrator::~YOGServerAdministrator() -bool YOGServerAdministrator::executeAdministrativeCommand(const std::string& message, boost::shared_ptr player, bool moderator) +bool YOGServerAdministrator::executeAdministrativeCommand(const std::string& message, std::shared_ptr player, bool moderator) { std::vector tokens; std::string token; @@ -116,7 +101,7 @@ bool YOGServerAdministrator::executeAdministrativeCommand(const std::string& mes { if(tokens[0] == commands[i]->getCommandName()) { - if(!commands[i]->doesMatch(tokens)) + if(!commands[i]->doesMatch(tokens.size())) { sendTextMessage(commands[i]->getHelpMessage(), player); } @@ -132,10 +117,10 @@ bool YOGServerAdministrator::executeAdministrativeCommand(const std::string& mes } -void YOGServerAdministrator::sendTextMessage(const std::string& message, boost::shared_ptr player) +void YOGServerAdministrator::sendTextMessage(const std::string& message, std::shared_ptr player) { - boost::shared_ptr m(new YOGMessage(message, "admin", YOGAdministratorMessage)); - boost::shared_ptr send(new NetSendYOGMessage(LOBBY_CHAT_CHANNEL, m)); + std::shared_ptr m(new YOGMessage(message, "admin", YOGAdministratorMessage)); + std::shared_ptr send(new NetSendYOGMessage(LOBBY_CHAT_CHANNEL, m)); player->sendMessage(send); } diff --git a/src/yog/YOGServerAdministrator.h b/src/yog/YOGServerAdministrator.h new file mode 100644 index 000000000..2002befab --- /dev/null +++ b/src/yog/YOGServerAdministrator.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include +#include + +class YOGServer; +class YOGServerPlayer; +class YOGServerAdministratorCommand; + +///This governs the system of administrative commands to the YOG server +class YOGServerAdministrator +{ +public: + ///Constructs the administration engine + YOGServerAdministrator(YOGServer* server); + + ///Destroys the administration engine + ~YOGServerAdministrator(); + + ///Interprets whether the given message is an administrative command, + ///and if so, executes it. If it was, returns true, otherwise, returns + ///false + bool executeAdministrativeCommand(const std::string& message, std::shared_ptr player, bool moderator); + + ///This sends a message to the player from the administrator engine + void sendTextMessage(const std::string& message, std::shared_ptr player); + +private: + + YOGServer* server; + + std::vector commands; +}; + diff --git a/src/YOGServerAdministratorCommands.cpp b/src/yog/YOGServerAdministratorCommands.cpp similarity index 71% rename from src/YOGServerAdministratorCommands.cpp rename to src/yog/YOGServerAdministratorCommands.cpp index c5306a3ff..8a9eb3f9a 100644 --- a/src/YOGServerAdministratorCommands.cpp +++ b/src/yog/YOGServerAdministratorCommands.cpp @@ -1,27 +1,13 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGServerAdministratorCommands.h" #include "YOGServerAdministrator.h" #include "YOGServer.h" -#include "NetMessage.h" +#include "LobbyMessages.h" #include "YOGServerPlayer.h" -#include "boost/lexical_cast.hpp" +#include +#include std::string YOGServerRestart::getHelpMessage() { @@ -37,15 +23,6 @@ std::string YOGServerRestart::getCommandName() -bool YOGServerRestart::doesMatch(const std::vector& tokens) -{ - if(tokens.size() != 1) - return false; - return true; -} - - - bool YOGServerRestart::allowedForModerator() { return false; @@ -53,7 +30,7 @@ bool YOGServerRestart::allowedForModerator() -void YOGServerRestart::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGServerRestart::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { exit(0); } @@ -74,28 +51,6 @@ std::string YOGMutePlayer::getCommandName() -bool YOGMutePlayer::doesMatch(const std::vector& tokens) -{ - if(tokens.size() == 2) - return true; - - if(tokens.size() == 3) - { - try - { - boost::lexical_cast(tokens[2]); - } - catch(boost::bad_lexical_cast& error) - { - return false; - } - return true; - } - return false; -} - - - bool YOGMutePlayer::allowedForModerator() { return true; @@ -103,12 +58,22 @@ bool YOGMutePlayer::allowedForModerator() -void YOGMutePlayer::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGMutePlayer::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; int time = 10; if(tokens.size() == 3) - time = boost::lexical_cast(tokens[2]); + { + try + { + time = std::stoi(tokens[2]); + } + catch(const std::invalid_argument&) + { + admin->sendTextMessage("Could not parse mute duration: "+tokens[2], player); + return; + } + } if(server->getPlayerStoredInfoManager().doesStoredInfoExist(name)) { boost::posix_time::ptime unmute_time = boost::posix_time::second_clock::local_time() + boost::posix_time::minutes(time); @@ -139,13 +104,6 @@ std::string YOGUnmutePlayer::getCommandName() -bool YOGUnmutePlayer::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGUnmutePlayer::allowedForModerator() { return true; @@ -153,7 +111,7 @@ bool YOGUnmutePlayer::allowedForModerator() -void YOGUnmutePlayer::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGUnmutePlayer::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; if(server->getPlayerStoredInfoManager().doesStoredInfoExist(name)) @@ -185,13 +143,6 @@ std::string YOGResetPassword::getCommandName() -bool YOGResetPassword::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGResetPassword::allowedForModerator() { return false; @@ -199,7 +150,7 @@ bool YOGResetPassword::allowedForModerator() -void YOGResetPassword::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGResetPassword::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; server->getServerPasswordRegistry().resetPlayersPassword(name); @@ -222,13 +173,6 @@ std::string YOGBanPlayer::getCommandName() -bool YOGBanPlayer::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGBanPlayer::allowedForModerator() { return false; @@ -236,7 +180,7 @@ bool YOGBanPlayer::allowedForModerator() -void YOGBanPlayer::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGBanPlayer::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; if(server->getPlayerStoredInfoManager().doesStoredInfoExist(name)) @@ -244,10 +188,10 @@ void YOGBanPlayer::execute(YOGServer* server, YOGServerAdministrator* admin, con YOGPlayerStoredInfo i = server->getPlayerStoredInfoManager().getPlayerStoredInfo(name); i.setBanned(); server->getPlayerStoredInfoManager().setPlayerStoredInfo(name, i); - boost::shared_ptr nplayer = server->getPlayer(name); + std::shared_ptr nplayer = server->getPlayer(name); if(nplayer) { - boost::shared_ptr send(new NetPlayerIsBanned); + std::shared_ptr send(new NetPlayerIsBanned); nplayer->sendMessage(send); nplayer->closeConnection(); } @@ -275,13 +219,6 @@ std::string YOGUnbanPlayer::getCommandName() -bool YOGUnbanPlayer::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGUnbanPlayer::allowedForModerator() { return false; @@ -289,7 +226,7 @@ bool YOGUnbanPlayer::allowedForModerator() -void YOGUnbanPlayer::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGUnbanPlayer::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; if(server->getPlayerStoredInfoManager().doesStoredInfoExist(name)) @@ -321,13 +258,6 @@ std::string YOGShowBannedPlayers::getCommandName() -bool YOGShowBannedPlayers::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 1; -} - - - bool YOGShowBannedPlayers::allowedForModerator() { return false; @@ -335,7 +265,7 @@ bool YOGShowBannedPlayers::allowedForModerator() -void YOGShowBannedPlayers::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGShowBannedPlayers::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::list bannedPlayers = server->getPlayerStoredInfoManager().getBannedPlayers(); std::string line; @@ -367,13 +297,6 @@ std::string YOGBanIP::getCommandName() -bool YOGBanIP::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGBanIP::allowedForModerator() { return false; @@ -381,15 +304,15 @@ bool YOGBanIP::allowedForModerator() -void YOGBanIP::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGBanIP::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; - boost::shared_ptr nplayer = server->getPlayer(name); + std::shared_ptr nplayer = server->getPlayer(name); if(nplayer) { boost::posix_time::ptime unban_time = boost::posix_time::second_clock::local_time() + boost::posix_time::hours(24); server->getServerBannedIPListManager().addBannedIP(nplayer->getPlayerIP(), unban_time); - boost::shared_ptr send(new NetIPIsBanned); + std::shared_ptr send(new NetIPIsBanned); nplayer->sendMessage(send); nplayer->closeConnection(); admin->sendTextMessage("Player "+name+"'s IP "+nplayer->getPlayerIP()+" has been banned.", player); @@ -416,13 +339,6 @@ std::string YOGAddAdministrator::getCommandName() -bool YOGAddAdministrator::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGAddAdministrator::allowedForModerator() { return false; @@ -430,7 +346,7 @@ bool YOGAddAdministrator::allowedForModerator() -void YOGAddAdministrator::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGAddAdministrator::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; @@ -461,13 +377,6 @@ std::string YOGRemoveAdministrator::getCommandName() -bool YOGRemoveAdministrator::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGRemoveAdministrator::allowedForModerator() { return false; @@ -475,7 +384,7 @@ bool YOGRemoveAdministrator::allowedForModerator() -void YOGRemoveAdministrator::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGRemoveAdministrator::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; if(server->getAdministratorList().isAdministrator(name)) @@ -505,13 +414,6 @@ std::string YOGAddModerator::getCommandName() -bool YOGAddModerator::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGAddModerator::allowedForModerator() { return false; @@ -519,7 +421,7 @@ bool YOGAddModerator::allowedForModerator() -void YOGAddModerator::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGAddModerator::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; if(server->getPlayerStoredInfoManager().doesStoredInfoExist(name)) @@ -551,13 +453,6 @@ std::string YOGRemoveModerator::getCommandName() -bool YOGRemoveModerator::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGRemoveModerator::allowedForModerator() { return false; @@ -565,7 +460,7 @@ bool YOGRemoveModerator::allowedForModerator() -void YOGRemoveModerator::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGRemoveModerator::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; if(server->getPlayerStoredInfoManager().doesStoredInfoExist(name)) @@ -597,13 +492,6 @@ std::string YOGRemoveMap::getCommandName() -bool YOGRemoveMap::doesMatch(const std::vector& tokens) -{ - return tokens.size() == 2; -} - - - bool YOGRemoveMap::allowedForModerator() { return false; @@ -611,7 +499,7 @@ bool YOGRemoveMap::allowedForModerator() -void YOGRemoveMap::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player) +void YOGRemoveMap::execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player) { std::string name = tokens[1]; if(server->getMapDatabank().doesMapExist(name)) diff --git a/src/YOGServerAdministratorCommands.h b/src/yog/YOGServerAdministratorCommands.h similarity index 57% rename from src/YOGServerAdministratorCommands.h rename to src/yog/YOGServerAdministratorCommands.h index 1ec556b16..909e86681 100644 --- a/src/YOGServerAdministratorCommands.h +++ b/src/yog/YOGServerAdministratorCommands.h @@ -1,27 +1,11 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerAdministratorCommand_h -#define YOGServerAdministratorCommand_h +#pragma once #include #include -#include "boost/shared_ptr.hpp" +#include class YOGServerAdministrator; class YOGServer; @@ -35,18 +19,29 @@ class YOGServerAdministratorCommand ///Returns this YOGServerAdministratorCommand help message virtual std::string getHelpMessage()=0; - + ///Returns the command name for this YOGServerAdministratorCommand virtual std::string getCommandName()=0; - - ///Returns true if the given set of tokens match whats required for this YOGServerAdministratorCommand - virtual bool doesMatch(const std::vector& tokens)=0; - + ///Returns true if this command can be executed by both moderators and administrators, false if it can only be executed by administrators virtual bool allowedForModerator()=0; - + ///Executes the code for the administrator command - virtual void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player)=0; + virtual void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player)=0; + + ///Returns true if the token count is within this command's accepted range. + bool doesMatch(std::size_t count) const + { + return int(count) >= minTokens && int(count) <= maxTokens; + } + +protected: + explicit YOGServerAdministratorCommand(int fixedTokens) : minTokens(fixedTokens), maxTokens(fixedTokens) {} + YOGServerAdministratorCommand(int min, int max) : minTokens(min), maxTokens(max) {} + +private: + int minTokens; + int maxTokens; }; @@ -55,15 +50,11 @@ class YOGServerAdministratorCommand class YOGServerRestart : public YOGServerAdministratorCommand { public: + YOGServerRestart() : YOGServerAdministratorCommand(1) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -72,15 +63,11 @@ class YOGServerRestart : public YOGServerAdministratorCommand class YOGMutePlayer : public YOGServerAdministratorCommand { public: + YOGMutePlayer() : YOGServerAdministratorCommand(2, 3) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -89,15 +76,11 @@ class YOGMutePlayer : public YOGServerAdministratorCommand class YOGUnmutePlayer : public YOGServerAdministratorCommand { public: + YOGUnmutePlayer() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -106,15 +89,11 @@ class YOGUnmutePlayer : public YOGServerAdministratorCommand class YOGResetPassword : public YOGServerAdministratorCommand { public: + YOGResetPassword() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -123,15 +102,11 @@ class YOGResetPassword : public YOGServerAdministratorCommand class YOGBanPlayer : public YOGServerAdministratorCommand { public: + YOGBanPlayer() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -140,15 +115,11 @@ class YOGBanPlayer : public YOGServerAdministratorCommand class YOGUnbanPlayer : public YOGServerAdministratorCommand { public: + YOGUnbanPlayer() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -157,15 +128,11 @@ class YOGUnbanPlayer : public YOGServerAdministratorCommand class YOGShowBannedPlayers : public YOGServerAdministratorCommand { public: + YOGShowBannedPlayers() : YOGServerAdministratorCommand(1) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -174,15 +141,11 @@ class YOGShowBannedPlayers : public YOGServerAdministratorCommand class YOGBanIP : public YOGServerAdministratorCommand { public: + YOGBanIP() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -191,15 +154,11 @@ class YOGBanIP : public YOGServerAdministratorCommand class YOGAddAdministrator : public YOGServerAdministratorCommand { public: + YOGAddAdministrator() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -208,15 +167,11 @@ class YOGAddAdministrator : public YOGServerAdministratorCommand class YOGRemoveAdministrator : public YOGServerAdministratorCommand { public: + YOGRemoveAdministrator() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -225,15 +180,11 @@ class YOGRemoveAdministrator : public YOGServerAdministratorCommand class YOGAddModerator : public YOGServerAdministratorCommand { public: + YOGAddModerator() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -242,15 +193,11 @@ class YOGAddModerator : public YOGServerAdministratorCommand class YOGRemoveModerator : public YOGServerAdministratorCommand { public: + YOGRemoveModerator() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; @@ -259,15 +206,9 @@ class YOGRemoveModerator : public YOGServerAdministratorCommand class YOGRemoveMap : public YOGServerAdministratorCommand { public: + YOGRemoveMap() : YOGServerAdministratorCommand(2) {} std::string getHelpMessage(); - std::string getCommandName(); - - bool doesMatch(const std::vector& tokens); - bool allowedForModerator(); - - void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, boost::shared_ptr player); + void execute(YOGServer* server, YOGServerAdministrator* admin, const std::vector& tokens, std::shared_ptr player); }; - -#endif diff --git a/src/YOGServerAdministratorList.cpp b/src/yog/YOGServerAdministratorList.cpp similarity index 64% rename from src/YOGServerAdministratorList.cpp rename to src/yog/YOGServerAdministratorList.cpp index 283bf1f97..14d5f4ddd 100644 --- a/src/YOGServerAdministratorList.cpp +++ b/src/yog/YOGServerAdministratorList.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGServerAdministratorList.h" diff --git a/src/yog/YOGServerAdministratorList.h b/src/yog/YOGServerAdministratorList.h new file mode 100644 index 000000000..890d00d85 --- /dev/null +++ b/src/yog/YOGServerAdministratorList.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include + +///This class reads the administrator list +class YOGServerAdministratorList +{ +public: + ///This will read the administrator list + YOGServerAdministratorList(); + + ///Returns true if the given username is an administrator, false otherwise + bool isAdministrator(const std::string& playerName); + + ///Adds the specificed user as an administrator + void addAdministrator(const std::string& playerName); + + ///Removes the specified user from the administrator list + void removeAdministrator(const std::string& playerName); +private: + ///Saves the list of administrators + void save(); + + ///Loads the list of administrators + void load(); + + std::set admins; +}; + + diff --git a/src/YOGServerBannedIPListManager.cpp b/src/yog/YOGServerBannedIPListManager.cpp similarity index 76% rename from src/YOGServerBannedIPListManager.cpp rename to src/yog/YOGServerBannedIPListManager.cpp index 9a1ebe9c1..60a4b7f1f 100644 --- a/src/YOGServerBannedIPListManager.cpp +++ b/src/yog/YOGServerBannedIPListManager.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "BinaryStream.h" #include "boost/date_time/posix_time/posix_time.hpp" diff --git a/src/YOGServerBannedIPListManager.h b/src/yog/YOGServerBannedIPListManager.h similarity index 53% rename from src/YOGServerBannedIPListManager.h rename to src/yog/YOGServerBannedIPListManager.h index 93436a318..e8b8f3c0b 100644 --- a/src/YOGServerBannedIPListManager.h +++ b/src/yog/YOGServerBannedIPListManager.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#ifndef YOGServerBannedIPListManager_h -#define YOGServerBannedIPListManager_h +#pragma once #include #include @@ -54,4 +37,3 @@ class YOGServerBannedIPListManager -#endif diff --git a/src/yog/YOGServerChatChannel.cpp b/src/yog/YOGServerChatChannel.cpp new file mode 100644 index 000000000..6e91468af --- /dev/null +++ b/src/yog/YOGServerChatChannel.cpp @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "YOGServerChatChannel.h" +#include "YOGServerPlayer.h" +#include "YOGMessage.h" +#include "LobbyMessages.h" + + +YOGServerChatChannel::YOGServerChatChannel(Uint32 channel) +: channel(channel) +{ + +} + + + +void YOGServerChatChannel::addPlayer(std::shared_ptr player) +{ + players.push_back(player); +} + + + +void YOGServerChatChannel::removePlayer(std::shared_ptr player) +{ + players.remove(player); +} + + + +void YOGServerChatChannel::routeMessage(std::shared_ptr message, std::shared_ptr sender) +{ + std::shared_ptr netmessage(new NetSendYOGMessage(channel, message)); + for(std::list >::iterator i = players.begin(); i!=players.end(); ++i) + { + if(*i != sender) + (*i)->sendMessage(netmessage); + } +} + + + +size_t YOGServerChatChannel::getNumberOfPlayers() const +{ + return players.size(); +} + diff --git a/src/yog/YOGServerChatChannel.h b/src/yog/YOGServerChatChannel.h new file mode 100644 index 000000000..551fef216 --- /dev/null +++ b/src/yog/YOGServerChatChannel.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include +#include "SDL_net.h" +#include + +class YOGMessage; +class YOGServerPlayer; + +///This represents a chat channel server-side +class YOGServerChatChannel +{ +public: + ///Creates a new chat channel + YOGServerChatChannel(Uint32 channel); + + ///Adds a player to this chat channel + void addPlayer(std::shared_ptr player); + + ///Removes a player from this chat channel + void removePlayer(std::shared_ptr player); + + ///Routes a YOG message to all players in this channel, except for sender + void routeMessage(std::shared_ptr message, std::shared_ptr sender); + + ///Returns the number of players in this chat channel + size_t getNumberOfPlayers() const; +private: + Uint32 channel; + std::list > players; +}; + diff --git a/src/yog/YOGServerChatChannelManager.cpp b/src/yog/YOGServerChatChannelManager.cpp new file mode 100644 index 000000000..0dc79bddd --- /dev/null +++ b/src/yog/YOGServerChatChannelManager.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#include "YOGServerChatChannelManager.h" +#include "YOGServerChatChannel.h" +#include "YOGConsts.h" + + +YOGServerChatChannelManager::YOGServerChatChannelManager() +{ + currentChannelID = LOBBY_CHAT_CHANNEL+1; + + std::shared_ptr newChannel(new YOGServerChatChannel(LOBBY_CHAT_CHANNEL)); + channels.insert(std::make_pair(LOBBY_CHAT_CHANNEL, newChannel)); +} + + + +YOGServerChatChannelManager::~YOGServerChatChannelManager() +{ + +} + + + +void YOGServerChatChannelManager::update() +{ + for(std::map >::iterator i = channels.begin(); i!=channels.end();) + { + if(i->first != LOBBY_CHAT_CHANNEL) + { + if(i->second->getNumberOfPlayers() == 0) + { + std::map >::iterator i2 = i; + i++; + channels.erase(i2); + continue; + } + } + ++i; + } +} + + + +Uint32 YOGServerChatChannelManager::createNewChatChannel() +{ + //This finds an unused channel ID + while(channels.find(currentChannelID) != channels.end()) + { + currentChannelID += 1; + } + Uint32 newChannelID = currentChannelID; + currentChannelID += 1; + + //Creates the channel + std::shared_ptr newChannel(new YOGServerChatChannel(newChannelID)); + channels.insert(std::make_pair(newChannelID, newChannel)); + + return newChannelID; +} + + + +Uint32 YOGServerChatChannelManager::getLobbyChannel() +{ + return LOBBY_CHAT_CHANNEL; +} + + + +std::shared_ptr YOGServerChatChannelManager::getChannel(Uint32 channel) +{ + return channels[channel]; +} + + + diff --git a/src/yog/YOGServerChatChannelManager.h b/src/yog/YOGServerChatChannelManager.h new file mode 100644 index 000000000..936c6d3ef --- /dev/null +++ b/src/yog/YOGServerChatChannelManager.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière + +#pragma once + +#include +#include "SDL_net.h" +#include + +class YOGServerChatChannel; + +///This does serverside management of YOG chat channels +class YOGServerChatChannelManager +{ +public: + ///Creates the YOGServerChatChannelManager + YOGServerChatChannelManager(); + + ///Destroys the YOGServerChatChannelManager + ~YOGServerChatChannelManager(); + + ///This updates the chat channel manager. Removes all chat channels that have no players, except for the lobby + void update(); + + ///Creates a new chat channel, returning its number + Uint32 createNewChatChannel(); + + ///Returns the lobbys channel + Uint32 getLobbyChannel(); + + ///Returns the YOGServerChatChannel for the particular channel + std::shared_ptr getChannel(Uint32 channel); + +private: + Uint32 currentChannelID; + std::map > channels; +}; + + diff --git a/src/yog/YOGServerFileDistributationManager.cpp b/src/yog/YOGServerFileDistributationManager.cpp new file mode 100644 index 000000000..5c51f007b --- /dev/null +++ b/src/yog/YOGServerFileDistributationManager.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#include "YOGServerFileDistributationManager.h" + + +YOGServerFileDistributationManager::YOGServerFileDistributationManager() +{ + currentID=1; +} + + + +int YOGServerFileDistributationManager::allocateFileDistributor() +{ + int id = chooseTransferID(); + files[id] = std::shared_ptr(new YOGServerFileDistributor(id)); + return id; +} + + + +void YOGServerFileDistributationManager::update() +{ + for(std::map >::iterator i = files.begin(); i!=files.end(); ++i) + { + if(i->second) + i->second->update(); + } +} + + + +std::shared_ptr YOGServerFileDistributationManager::getDistributor(Uint16 transferID) +{ + return files[transferID]; +} + + + +void YOGServerFileDistributationManager::removeDistributor(Uint16 transferID) +{ + std::map >::iterator i = files.find(transferID); + if(i != files.end()) + { + files.erase(i); + } +} + + + +Uint16 YOGServerFileDistributationManager::chooseTransferID() +{ + while(files.find(currentID) != files.end()) + { + currentID+=1; + } + return currentID; +} + diff --git a/src/yog/YOGServerFileDistributationManager.h b/src/yog/YOGServerFileDistributationManager.h new file mode 100644 index 000000000..1ca5c354f --- /dev/null +++ b/src/yog/YOGServerFileDistributationManager.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include "SDL_net.h" +#include "YOGServerFileDistributor.h" + +///This class manages all file transfers on the server +class YOGServerFileDistributationManager +{ +public: + ///Constructs a distributor + YOGServerFileDistributationManager(); + + ///Allocates a file distributor, returns the transfer ID + int allocateFileDistributor(); + + ///This updates this distributor + void update(); + + ///This returns the file distributor for the given id + std::shared_ptr getDistributor(Uint16 transferID); + + ///This removes the file distributor + void removeDistributor(Uint16 transferID); +private: + ///Finds an available transfer id + Uint16 chooseTransferID(); + + std::map > files; + Uint16 currentID; +}; + diff --git a/src/YOGServerFileDistributor.cpp b/src/yog/YOGServerFileDistributor.cpp similarity index 52% rename from src/YOGServerFileDistributor.cpp rename to src/yog/YOGServerFileDistributor.cpp index 2dfee2300..379de584f 100644 --- a/src/YOGServerFileDistributor.cpp +++ b/src/yog/YOGServerFileDistributor.cpp @@ -1,24 +1,9 @@ -/* - Copyright 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "BinaryStream.h" #include "FileManager.h" -#include "NetMessage.h" +#include "FileTransferMessages.h" #include "StreamBackend.h" #include "Stream.h" #include "Toolkit.h" @@ -26,7 +11,7 @@ #include "YOGServerPlayer.h" using namespace GAGCore; -using boost::static_pointer_cast; +using std::static_pointer_cast; YOGServerFileDistributor::YOGServerFileDistributor(Uint16 fileID) : fileID(fileID), startedLoading(false), downloadFromPlayerCanceled(false) @@ -43,7 +28,7 @@ void YOGServerFileDistributor::loadFromLocally(const std::string& file) -void YOGServerFileDistributor::loadFromPlayer(boost::shared_ptr nplayer) +void YOGServerFileDistributor::loadFromPlayer(std::shared_ptr nplayer) { player = nplayer; } @@ -52,7 +37,7 @@ void YOGServerFileDistributor::loadFromPlayer(boost::shared_ptr void YOGServerFileDistributor::saveToFile(const std::string& file) { - boost::shared_ptr stream(new BinaryOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(file+".gz"))); + std::shared_ptr stream(new BinaryOutputStream(Toolkit::getFileManager()->openOutputStreamBackend(file+".gz"))); for(unsigned int i=0; iwrite(chunks[i]->getBuffer(), chunks[i]->getChunkSize(), ""); @@ -90,28 +75,28 @@ bool YOGServerFileDistributor::wasUploadingCanceled() void YOGServerFileDistributor::update() { boost::posix_time::ptime localtime = boost::posix_time::second_clock::local_time(); - for(std::vector, boost::posix_time::ptime, int> >::iterator i = players.begin(); i!=players.end();) + for(std::vector, boost::posix_time::ptime, int> >::iterator i = players.begin(); i!=players.end();) { - if(!i->get<0>()->isConnected()) + if(!std::get<0>(*i)->isConnected()) { i = players.erase(i); continue; } - if(i->get<2>() == 0 && fileInfo) + if(std::get<2>(*i) == 0 && fileInfo) { - i->get<0>()->sendMessage(fileInfo); - i->get<2>() = 1; + std::get<0>(*i)->sendMessage(fileInfo); + std::get<2>(*i) = 1; } - else if(i->get<2>() == 0) { + else if(std::get<2>(*i) == 0) { // WORKAROUND continue; } - else if(i->get<2>()-1 < (int)chunks.size() && i->get<1>() < localtime) + else if(std::get<2>(*i)-1 < (int)chunks.size() && std::get<1>(*i) < localtime) { - i->get<0>()->sendMessage(chunks[i->get<2>()-1]); - i->get<2>() += 1; - i->get<1>() = localtime + boost::posix_time::microseconds(100); + std::get<0>(*i)->sendMessage(chunks[std::get<2>(*i)-1]); + std::get<2>(*i) += 1; + std::get<1>(*i) = localtime + boost::posix_time::microseconds(100); } ++i; } @@ -119,19 +104,19 @@ void YOGServerFileDistributor::update() -void YOGServerFileDistributor::addMapRequestee(boost::shared_ptr player) +void YOGServerFileDistributor::addMapRequestee(std::shared_ptr player) { garunteeDataRequested(); - players.push_back(boost::make_tuple(player, boost::posix_time::second_clock::local_time(), 0)); + players.push_back(std::make_tuple(player, boost::posix_time::second_clock::local_time(), 0)); } -void YOGServerFileDistributor::removeMapRequestee(boost::shared_ptr player) +void YOGServerFileDistributor::removeMapRequestee(std::shared_ptr player) { - for(std::vector, boost::posix_time::ptime, int> >::iterator i = players.begin(); i!=players.end(); ++i) + for(std::vector, boost::posix_time::ptime, int> >::iterator i = players.begin(); i!=players.end(); ++i) { - if(i->get<0>() == player) + if(std::get<0>(*i) == player) { players.erase(i); return; @@ -141,7 +126,7 @@ void YOGServerFileDistributor::removeMapRequestee(boost::shared_ptr message, boost::shared_ptr nplayer) +void YOGServerFileDistributor::handleMessage(std::shared_ptr message, std::shared_ptr nplayer) { ///This ignores certain messages that must come from the person uploading the map Uint8 messageType = message->getMessageType(); @@ -168,16 +153,16 @@ void YOGServerFileDistributor::loadDataFromFile() { startedLoading=true; Toolkit::getFileManager()->gzip(fileName, fileName+".gz"); - boost::shared_ptr istream(new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(fileName+".gz"))); + std::shared_ptr istream(new BinaryInputStream(Toolkit::getFileManager()->openInputStreamBackend(fileName+".gz"))); istream->seekFromEnd(0); int size=istream->getPosition(); istream->seekFromStart(0); - fileInfo = boost::shared_ptr(new NetSendFileInformation(size, fileID)); + fileInfo = std::shared_ptr(new NetSendFileInformation(size, fileID)); int ammount=0; while(ammount < size) { - boost::shared_ptr message(new NetSendFileChunk(istream, fileID)); + std::shared_ptr message(new NetSendFileChunk(istream, fileID)); ammount += message->getChunkSize(); chunks.push_back(message); } diff --git a/src/YOGServerFileDistributor.h b/src/yog/YOGServerFileDistributor.h similarity index 51% rename from src/YOGServerFileDistributor.h rename to src/yog/YOGServerFileDistributor.h index 5ef6c6da4..c3fb4f334 100644 --- a/src/YOGServerFileDistributor.h +++ b/src/yog/YOGServerFileDistributor.h @@ -1,27 +1,11 @@ -/* - Copyright 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGServerFileDistributor_h -#define __YOGServerFileDistributor_h +#pragma once #include "boost/date_time/posix_time/posix_time.hpp" -#include "boost/shared_ptr.hpp" -#include "boost/tuple/tuple.hpp" +#include +#include #include "SDL_net.h" #include @@ -42,7 +26,7 @@ class YOGServerFileDistributor void loadFromLocally(const std::string& file); ///Tells this file distributor to load from the given player - void loadFromPlayer(boost::shared_ptr player); + void loadFromPlayer(std::shared_ptr player); ///This tells the file distributor to save all data in the file the given filename locally void saveToFile(const std::string& file); @@ -58,13 +42,13 @@ class YOGServerFileDistributor void update(); ///Add the given player as one requesting the file - void addMapRequestee(boost::shared_ptr player); + void addMapRequestee(std::shared_ptr player); ///Removes the given player from requesting the map - void removeMapRequestee(boost::shared_ptr player); + void removeMapRequestee(std::shared_ptr player); ///Handles the provided message - void handleMessage(boost::shared_ptr message, boost::shared_ptr player); + void handleMessage(std::shared_ptr message, std::shared_ptr player); private: ///Loads from the file void loadDataFromFile(); @@ -77,14 +61,13 @@ class YOGServerFileDistributor bool startedLoading; bool downloadFromPlayerCanceled; std::string fileName; - boost::shared_ptr player; - boost::shared_ptr fileInfo; - std::vector > chunks; - std::vector, boost::posix_time::ptime, int> > players; + std::shared_ptr player; + std::shared_ptr fileInfo; + std::vector > chunks; + std::vector, boost::posix_time::ptime, int> > players; }; -#endif diff --git a/src/YOGServerGame.cpp b/src/yog/YOGServerGame.cpp similarity index 84% rename from src/YOGServerGame.cpp rename to src/yog/YOGServerGame.cpp index 29287d46c..b63fbec18 100644 --- a/src/YOGServerGame.cpp +++ b/src/yog/YOGServerGame.cpp @@ -1,23 +1,12 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include -#include "NetMessage.h" +#include "GameHeaderMessages.h" +#include "GameJoinMessages.h" +#include "GameLaunchMessages.h" +#include "GameTeamMessages.h" +#include "OrderMessages.h" #include "YOGServerChatChannel.h" #include "YOGServerGame.h" #include "YOGServer.h" @@ -64,7 +53,7 @@ void YOGServerGame::update() break; } } - boost::shared_ptr order(new PlayerQuitsGameOrder(p)); + std::shared_ptr order(new PlayerQuitsGameOrder(p)); order->sender = p; shared_ptr message(new NetSendOrder(order)); for(std::vector >::iterator j = players.begin(); j!=players.end(); ++j) @@ -284,8 +273,8 @@ Uint16 YOGServerGame::getGameID() const void YOGServerGame::setReadyToStart(int playerID) { playerManager.setReadyToGo(playerID, true); - boost::shared_ptr message(new NetReadyToLaunch(playerID)); - for(std::vector >::iterator i = players.begin(); i!=players.end(); ++i) + std::shared_ptr message(new NetReadyToLaunch(playerID)); + for(std::vector >::iterator i = players.begin(); i!=players.end(); ++i) { if((*i)->getPlayerID() != playerID) (*i)->sendMessage(message); @@ -297,8 +286,8 @@ void YOGServerGame::setReadyToStart(int playerID) void YOGServerGame::setNotReadyToStart(int playerID) { playerManager.setReadyToGo(playerID, false); - boost::shared_ptr message(new NetNotReadyToLaunch(playerID)); - for(std::vector >::iterator i = players.begin(); i!=players.end(); ++i) + std::shared_ptr message(new NetNotReadyToLaunch(playerID)); + for(std::vector >::iterator i = players.begin(); i!=players.end(); ++i) { if((*i)->getPlayerID() != playerID) (*i)->sendMessage(message); @@ -316,7 +305,7 @@ void YOGServerGame::recieveGameStartRequest() } else { - boost::shared_ptr message(new NetRefuseGameStart(YOGNotAllPlayersReady)); + std::shared_ptr message(new NetRefuseGameStart(YOGNotAllPlayersReady)); host->sendMessage(message); } } @@ -327,7 +316,7 @@ void YOGServerGame::startGame() { chooseLatencyMode(); gameStarted=true; - boost::shared_ptr message(new NetStartGame); + std::shared_ptr message(new NetStartGame); routeMessage(message); server.getGameInfo(gameID).setGameState(YOGGameInfo::GameRunning); } @@ -382,14 +371,14 @@ void YOGServerGame::chooseLatencyMode() if(latency_adjustment != latencyMode && !gameStarted) { - boost::shared_ptr message(new NetSetLatencyMode(latency_adjustment)); + std::shared_ptr message(new NetSetLatencyMode(latency_adjustment)); routeMessage(message); latencyMode = latency_adjustment; } } -void YOGServerGame::setPlayerGameResult(boost::shared_ptr sender, YOGGameResult result) +void YOGServerGame::setPlayerGameResult(std::shared_ptr sender, YOGGameResult result) { if(gameResults.getGameResultState(sender->getPlayerName()) == YOGGameResultUnknown) { diff --git a/src/YOGServerGame.h b/src/yog/YOGServerGame.h similarity index 68% rename from src/YOGServerGame.h rename to src/yog/YOGServerGame.h index e44a47d6c..22a2af6b7 100644 --- a/src/YOGServerGame.h +++ b/src/yog/YOGServerGame.h @@ -1,26 +1,10 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGServerGame_h -#define __YOGServerGame_h +#pragma once #include "MapHeader.h" -#include +#include #include "NetGamePlayerManager.h" #include "NetReteamingInformation.h" #include "YOGGameResults.h" @@ -45,13 +29,13 @@ class YOGServerGame void update(); ///Adds the player to the game - void addPlayer(boost::shared_ptr player); + void addPlayer(std::shared_ptr player); ///Adds an AI to the game void addAIPlayer(AI::ImplementitionID type); ///Removes the player from the game - void removePlayer(boost::shared_ptr player); + void removePlayer(std::shared_ptr player); ///Removes the AI from the game void removeAIPlayer(int playerNum); @@ -60,7 +44,7 @@ class YOGServerGame void setTeam(int playerNum, int teamNum); ///Sets the host of the game - void setHost(boost::shared_ptr player); + void setHost(std::shared_ptr player); ///Sets the map header of the game void setMapHeader(const MapHeader& mapHeader); @@ -73,10 +57,10 @@ class YOGServerGame ///Routes the given message to all players except for the sender, ///unless sender is null - void routeMessage(boost::shared_ptr message, boost::shared_ptr sender=boost::shared_ptr()); + void routeMessage(std::shared_ptr message, std::shared_ptr sender=std::shared_ptr()); ///Kicks the player and sends a kick message to the player - void kickPlayer(boost::shared_ptr message); + void kickPlayer(std::shared_ptr message); ///Returns whether there are no players left in the game bool isEmpty() const; @@ -111,7 +95,7 @@ class YOGServerGame void chooseLatencyMode(); ///This sets a players game result - void setPlayerGameResult(boost::shared_ptr sender, YOGGameResult result); + void setPlayerGameResult(std::shared_ptr sender, YOGGameResult result); ///This sends the games results to the game log, if this game actually went through void sendGameResultsToGameLog(); @@ -127,7 +111,7 @@ class YOGServerGame bool oldReadyToLaunch; bool recievedMapHeader; bool requested; - boost::shared_ptr host; + std::shared_ptr host; GameHeader gameHeader; int latencyMode; Uint64 latencyUpdateTimer; @@ -135,7 +119,7 @@ class YOGServerGame MapHeader mapHeader; NetGamePlayerManager playerManager; NetReteamingInformation reteamingInfo; - std::vector > players; + std::vector > players; Uint16 gameID; Uint32 chatChannel; Uint8 aiNum; @@ -145,4 +129,3 @@ class YOGServerGame }; -#endif diff --git a/src/YOGServerGameLog.cpp b/src/yog/YOGServerGameLog.cpp similarity index 74% rename from src/YOGServerGameLog.cpp rename to src/yog/YOGServerGameLog.cpp index 6e1d5a802..c2e80ee9d 100644 --- a/src/YOGServerGameLog.cpp +++ b/src/yog/YOGServerGameLog.cpp @@ -1,21 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGServerGameLog.h" #include diff --git a/src/yog/YOGServerGameLog.h b/src/yog/YOGServerGameLog.h new file mode 100644 index 000000000..e86ecf271 --- /dev/null +++ b/src/yog/YOGServerGameLog.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include "YOGGameResults.h" +#include "boost/date_time/posix_time/posix_time.hpp" +#include "SDL_net.h" + +///This class keeps a complete list of games played +class YOGServerGameLog +{ +public: + ///Constructs the game log + YOGServerGameLog(); + + ///Adds a game result to the log + void addGameResults(YOGGameResults results); + + ///Updates this game log, periodically saving and changing the log file + void update(); +private: + ///This saves the game log + void save(); + ///This loads the game log + void load(); + ///This is the current hour + boost::posix_time::ptime hour; + ///This is the list of games from this hour + std::vector games; + ///This is the next time the list will be flushed + boost::posix_time::ptime flushTime; + ///This is set when the list has changed + bool modified; +}; + diff --git a/src/yog/YOGServerGameRouter.cpp b/src/yog/YOGServerGameRouter.cpp new file mode 100644 index 000000000..e83101647 --- /dev/null +++ b/src/yog/YOGServerGameRouter.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#include "YOGServerGameRouter.h" +#include "YOGServerRouterPlayer.h" +#include "NetMessage.h" + + +YOGServerGameRouter::YOGServerGameRouter() +{ + +} + + + +void YOGServerGameRouter::addPlayer(std::shared_ptr player) +{ + players.push_back(player); +} + + + +void YOGServerGameRouter::update() +{ + for(std::vector >::iterator i=players.begin(); i!=players.end();) + { + if(!(*i)->isConnected()) + { + Uint32 n = i - players.begin(); + players.erase(i); + i = players.begin() + n; + } + else + { + ++i; + } + } +} + + + +bool YOGServerGameRouter::isEmpty() +{ + if(players.empty()) + return true; + return false; +} + + + +void YOGServerGameRouter::routeMessage(std::shared_ptr message, YOGServerRouterPlayer* sender) +{ + for(std::vector >::iterator i=players.begin(); i!=players.end(); ++i) + { + if(i->get() != sender) + { + (*i)->sendNetMessage(message); + } + } +} + diff --git a/src/yog/YOGServerGameRouter.h b/src/yog/YOGServerGameRouter.h new file mode 100644 index 000000000..eba71b4f0 --- /dev/null +++ b/src/yog/YOGServerGameRouter.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include + +class YOGServerRouterPlayer; +class NetMessage; + +///This class acts is the router for games, it routes messages between all connected players +class YOGServerGameRouter +{ +public: + ///Constructs a YOGServerGameRouter + YOGServerGameRouter(); + + ///Adds a player to this router group + void addPlayer(std::shared_ptr player); + + ///Updates this game + void update(); + + ///Returns true if this game is empty + bool isEmpty(); + + ///Removes a net message to all players + void routeMessage(std::shared_ptr message, YOGServerRouterPlayer* sender); +private: + std::vector > players; +}; + + diff --git a/src/YOGServerMapDatabank.cpp b/src/yog/YOGServerMapDatabank.cpp similarity index 72% rename from src/YOGServerMapDatabank.cpp rename to src/yog/YOGServerMapDatabank.cpp index 52c2d174f..3048686c5 100644 --- a/src/YOGServerMapDatabank.cpp +++ b/src/yog/YOGServerMapDatabank.cpp @@ -1,24 +1,9 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "BinaryStream.h" #include "FileManager.h" -#include "NetMessage.h" +#include "MapDatabaseMessages.h" #include "Toolkit.h" #include "YOGServer.h" #include "YOGServerMapDatabank.h" @@ -80,9 +65,9 @@ bool YOGServerMapDatabank::doesMapExist(const std::string& map) YOGMapUploadRefusalReason YOGServerMapDatabank::canRecieveFromPlayer(const YOGDownloadableMapInfo& map) { - for(std::vector >::iterator i = uploadingMaps.begin(); i!=uploadingMaps.end(); ++i) + for(std::vector >::iterator i = uploadingMaps.begin(); i!=uploadingMaps.end(); ++i) { - if(i->get<0>().getMapHeader().getMapName() == map.getMapHeader().getMapName()) + if(std::get<0>(*i).getMapHeader().getMapName() == map.getMapHeader().getMapName()) return YOGMapUploadReasonMapNameAlreadyExists; } for(std::vector::iterator i = maps.begin(); i!=maps.end(); ++i) @@ -95,32 +80,32 @@ YOGMapUploadRefusalReason YOGServerMapDatabank::canRecieveFromPlayer(const YOGDo -Uint16 YOGServerMapDatabank::recieveMapFromPlayer(const YOGDownloadableMapInfo& map, boost::shared_ptr player) +Uint16 YOGServerMapDatabank::recieveMapFromPlayer(const YOGDownloadableMapInfo& map, std::shared_ptr player) { int fileID = server->getFileDistributionManager().allocateFileDistributor(); server->getFileDistributionManager().getDistributor(fileID)->loadFromPlayer(player); - uploadingMaps.push_back(boost::make_tuple(map, fileID)); + uploadingMaps.push_back(std::make_tuple(map, fileID)); return fileID; } -void YOGServerMapDatabank::sendMapListToPlayer(boost::shared_ptr player) +void YOGServerMapDatabank::sendMapListToPlayer(std::shared_ptr player) { - boost::shared_ptr infos(new NetDownloadableMapInfos(maps)); + std::shared_ptr infos(new NetDownloadableMapInfos(maps)); player->sendMessage(infos); } -void YOGServerMapDatabank::sendMapThumbnailToPlayer(Uint16 mapID, boost::shared_ptr player) +void YOGServerMapDatabank::sendMapThumbnailToPlayer(Uint16 mapID, std::shared_ptr player) { for(std::vector::iterator i = maps.begin(); i!=maps.end(); ++i) { if(i->getMapID() == mapID) { MapThumbnail thumbnail = loadThumbnail(i->getMapHeader().getMapName(), i->getMapHeader().getFileName()); - boost::shared_ptr infos(new NetSendMapThumbnail(mapID, thumbnail)); + std::shared_ptr infos(new NetSendMapThumbnail(mapID, thumbnail)); player->sendMessage(infos); return; } @@ -151,20 +136,20 @@ void YOGServerMapDatabank::submitRating(Uint16 mapID, Uint8 rating) void YOGServerMapDatabank::update() { - for(std::vector >::iterator i=uploadingMaps.begin(); i!=uploadingMaps.end();) + for(std::vector >::iterator i=uploadingMaps.begin(); i!=uploadingMaps.end();) { - if(server->getFileDistributionManager().getDistributor(i->get<1>())->areAllChunksLoaded()) + if(server->getFileDistributionManager().getDistributor(std::get<1>(*i))->areAllChunksLoaded()) { - server->getFileDistributionManager().getDistributor(i->get<1>())->saveToFile(i->get<0>().getMapHeader().getFileName()); - server->getFileDistributionManager().removeDistributor(i->get<1>()); - addMap(i->get<0>()); + server->getFileDistributionManager().getDistributor(std::get<1>(*i))->saveToFile(std::get<0>(*i).getMapHeader().getFileName()); + server->getFileDistributionManager().removeDistributor(std::get<1>(*i)); + addMap(std::get<0>(*i)); Uint32 n = i - uploadingMaps.begin(); uploadingMaps.erase(i); i = uploadingMaps.begin() + n; } - else if(server->getFileDistributionManager().getDistributor(i->get<1>())->wasUploadingCanceled()) + else if(server->getFileDistributionManager().getDistributor(std::get<1>(*i))->wasUploadingCanceled()) { - server->getFileDistributionManager().removeDistributor(i->get<1>()); + server->getFileDistributionManager().removeDistributor(std::get<1>(*i)); Uint32 n = i - uploadingMaps.begin(); uploadingMaps.erase(i); i = uploadingMaps.begin() + n; diff --git a/src/YOGServerMapDatabank.h b/src/yog/YOGServerMapDatabank.h similarity index 61% rename from src/YOGServerMapDatabank.h rename to src/yog/YOGServerMapDatabank.h index 3b3c44626..6947eb118 100644 --- a/src/YOGServerMapDatabank.h +++ b/src/yog/YOGServerMapDatabank.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerMapDatabank_h -#define YOGServerMapDatabank_h - -#include "boost/tuple/tuple.hpp" +#include #include "MapThumbnail.h" #include #include "YOGDownloadableMapInfo.h" @@ -48,13 +32,13 @@ class YOGServerMapDatabank YOGMapUploadRefusalReason canRecieveFromPlayer(const YOGDownloadableMapInfo& map); ///Starts recieving a map from the given player, and returns the file ID for the transfer - Uint16 recieveMapFromPlayer(const YOGDownloadableMapInfo& map, boost::shared_ptr player); + Uint16 recieveMapFromPlayer(const YOGDownloadableMapInfo& map, std::shared_ptr player); ///Sends the list of maps to the given player - void sendMapListToPlayer(boost::shared_ptr player); + void sendMapListToPlayer(std::shared_ptr player); ///Sends a map thumbnail to the given player - void sendMapThumbnailToPlayer(Uint16 mapID, boost::shared_ptr player); + void sendMapThumbnailToPlayer(Uint16 mapID, std::shared_ptr player); ///Submits a rating for a given player. void submitRating(Uint16 mapID, Uint8 rating); @@ -79,7 +63,6 @@ class YOGServerMapDatabank std::vector maps; ///List of maps currently being uploaded - std::vector > uploadingMaps; + std::vector > uploadingMaps; }; -#endif diff --git a/src/YOGServerPasswordRegistry.cpp b/src/yog/YOGServerPasswordRegistry.cpp similarity index 76% rename from src/YOGServerPasswordRegistry.cpp rename to src/yog/YOGServerPasswordRegistry.cpp index 7a694944b..7a444c07b 100644 --- a/src/YOGServerPasswordRegistry.cpp +++ b/src/yog/YOGServerPasswordRegistry.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault #include "YOGServerPasswordRegistry.h" #include "Stream.h" @@ -24,7 +9,7 @@ #include "../gnupg/sha1.c" #include "Version.h" -#include +#include #include @@ -118,7 +103,7 @@ std::string YOGServerPasswordRegistry::transform(const std::string& username, co int i=1; while(salted.size() < 50) { - salted+=boost::lexical_cast(i); + salted+=std::to_string(i); i+=1; } ///Perform SHA1, a cast must be performed to get the data to the right type, but @@ -130,6 +115,6 @@ std::string YOGServerPasswordRegistry::transform(const std::string& username, co SHA1Final(digest, &context); std::string final = ""; for(int i=0; i<20; ++i) - final += boost::lexical_cast(digest[i]) + "-"; + final += std::to_string(static_cast(digest[i])) + "-"; return final; } diff --git a/src/YOGServerPasswordRegistry.h b/src/yog/YOGServerPasswordRegistry.h similarity index 58% rename from src/YOGServerPasswordRegistry.h rename to src/yog/YOGServerPasswordRegistry.h index 850de1bd6..02467f1a6 100644 --- a/src/YOGServerPasswordRegistry.h +++ b/src/yog/YOGServerPasswordRegistry.h @@ -1,23 +1,7 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGServerPasswordRegistry_h -#define __YOGServerPasswordRegistry_h +#pragma once #include "YOGConsts.h" #include @@ -53,4 +37,3 @@ class YOGServerPasswordRegistry }; -#endif diff --git a/src/YOGServerPlayer.cpp b/src/yog/YOGServerPlayer.cpp similarity index 92% rename from src/YOGServerPlayer.cpp rename to src/yog/YOGServerPlayer.cpp index 7e607e40f..87f2faf64 100644 --- a/src/YOGServerPlayer.cpp +++ b/src/yog/YOGServerPlayer.cpp @@ -1,22 +1,19 @@ -/* - Copyright (C) 2007 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "NetMessage.h" +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault + +#include "AuthMessages.h" +#include "FileTransferMessages.h" +#include "GameCreateMessages.h" +#include "GameHeaderMessages.h" +#include "GameJoinMessages.h" +#include "GameLaunchMessages.h" +#include "GameTeamMessages.h" +#include "LobbyMessages.h" +#include "MapDatabaseMessages.h" +#include "MapUploadMessages.h" +#include "OrderMessages.h" +#include "RegistrationMessages.h" +#include "RouterAdminMessages.h" #include "YOGServerChatChannel.h" #include "YOGServerGame.h" #include "YOGServer.h" @@ -24,7 +21,8 @@ #include "YOGServerPlayer.h" #include "SDLCompat.h" -using boost::static_pointer_cast; +using std::shared_ptr; +using std::static_pointer_cast; YOGServerPlayer::YOGServerPlayer(shared_ptr connection, Uint16 id, YOGServer& server) : connection(connection), server(server), playerID(id) @@ -56,10 +54,10 @@ void YOGServerPlayer::update() pingCountdown = 0; } - boost::shared_ptr ngame; + std::shared_ptr ngame; if(!game.expired()) { - ngame = boost::shared_ptr(game); + ngame = std::shared_ptr(game); } //Parse incoming messages. @@ -283,12 +281,12 @@ void YOGServerPlayer::update() if(reason == YOGMapUploadReasonUnknown) { Uint16 fileID = server.getMapDatabank().recieveMapFromPlayer(info->getMapInfo(), server.getPlayer(playerID)); - boost::shared_ptr info = boost::shared_ptr(new NetAcceptMapUpload(fileID)); + std::shared_ptr info = std::shared_ptr(new NetAcceptMapUpload(fileID)); sendMessage(info); } else { - boost::shared_ptr info = boost::shared_ptr(new NetRefuseMapUpload(reason)); + std::shared_ptr info = std::shared_ptr(new NetRefuseMapUpload(reason)); sendMessage(info); } } @@ -375,9 +373,9 @@ std::string YOGServerPlayer::getPlayerIP() -boost::shared_ptr YOGServerPlayer::getGame() +std::shared_ptr YOGServerPlayer::getGame() { - return boost::shared_ptr(game); + return std::shared_ptr(game); } @@ -515,9 +513,9 @@ void YOGServerPlayer::handleCreateGame(const std::string& gameName) { gameID = server.createNewGame(gameName); game = server.getGame(gameID); - boost::shared_ptr ngame(game); + std::shared_ptr ngame(game); updateGamePlayerLists(); - std::string ip = boost::shared_ptr(game)->getRouterIP(); + std::string ip = std::shared_ptr(game)->getRouterIP(); shared_ptr message(new NetCreateGameAccepted(ngame->getChatChannel(), gameID, ip, ngame->getFileID())); connection->sendMessage(message); ngame->addPlayer(server.getPlayer(playerID)); @@ -538,7 +536,7 @@ void YOGServerPlayer::handleJoinGame(Uint16 ngameID) { gameID = ngameID; game = server.getGame(gameID); - boost::shared_ptr ngame(game); + std::shared_ptr ngame(game); shared_ptr message(new NetGameJoinAccepted(ngame->getChatChannel())); connection->sendMessage(message); ngame->addPlayer(server.getPlayer(playerID)); diff --git a/src/YOGServerPlayer.h b/src/yog/YOGServerPlayer.h similarity index 81% rename from src/YOGServerPlayer.h rename to src/yog/YOGServerPlayer.h index e98b6a447..97ac93b2e 100644 --- a/src/YOGServerPlayer.h +++ b/src/yog/YOGServerPlayer.h @@ -1,26 +1,9 @@ -/* - Copyright (C) 2007 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2007 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef __YOGServerPlayer_h -#define __YOGServerPlayer_h - -#include -#include +#include #include #include "NetConnection.h" #include "YOGConsts.h" @@ -33,7 +16,7 @@ class YOGServerGame; class NetMessage; class P2PManager; -using boost::weak_ptr; +using std::weak_ptr; ///This represents a connected user on the YOG server. class YOGServerPlayer { @@ -68,7 +51,7 @@ class YOGServerPlayer std::string getPlayerIP(); ///Returns the game the player is connected to - boost::shared_ptr getGame(); + std::shared_ptr getGame(); ///Returns the players ping such that, statistically, 99.7% of all pings from this client ///would be under this amount, so long as pings are normally distributed, which I've @@ -170,4 +153,3 @@ class YOGServerPlayer -#endif diff --git a/src/YOGServerPlayerScoreCalculator.cpp b/src/yog/YOGServerPlayerScoreCalculator.cpp similarity index 74% rename from src/YOGServerPlayerScoreCalculator.cpp rename to src/yog/YOGServerPlayerScoreCalculator.cpp index a4a51a05f..e90623042 100644 --- a/src/YOGServerPlayerScoreCalculator.cpp +++ b/src/yog/YOGServerPlayerScoreCalculator.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGGameResults.h" #include "GameHeader.h" diff --git a/src/yog/YOGServerPlayerScoreCalculator.h b/src/yog/YOGServerPlayerScoreCalculator.h new file mode 100644 index 000000000..26de05cb9 --- /dev/null +++ b/src/yog/YOGServerPlayerScoreCalculator.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include "YOGGameResults.h" +#include "GameHeader.h" + +class YOGServer; + +//This class does the function of calculating and updating player scores +class YOGServerPlayerScoreCalculator +{ +public: + ///Constructs the score calculator + YOGServerPlayerScoreCalculator(YOGServer* server); + + ///Proccesses the result of a single game + void proccessResults(YOGGameResults& results, GameHeader& header); +private: + YOGServer* server; +}; + diff --git a/src/YOGServerPlayerStoredInfoManager.cpp b/src/yog/YOGServerPlayerStoredInfoManager.cpp similarity index 78% rename from src/YOGServerPlayerStoredInfoManager.cpp rename to src/yog/YOGServerPlayerStoredInfoManager.cpp index f63aad1bf..f6f1f2e55 100644 --- a/src/YOGServerPlayerStoredInfoManager.cpp +++ b/src/yog/YOGServerPlayerStoredInfoManager.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGServerPlayerStoredInfoManager.h" diff --git a/src/YOGServerPlayerStoredInfoManager.h b/src/yog/YOGServerPlayerStoredInfoManager.h similarity index 61% rename from src/YOGServerPlayerStoredInfoManager.h rename to src/yog/YOGServerPlayerStoredInfoManager.h index ad6cc9b9f..136626a56 100644 --- a/src/YOGServerPlayerStoredInfoManager.h +++ b/src/yog/YOGServerPlayerStoredInfoManager.h @@ -1,24 +1,7 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - -#ifndef YOGServerPlayerStoredInfoManager_h -#define YOGServerPlayerStoredInfoManager_h +#pragma once #include "YOGPlayerStoredInfo.h" #include @@ -67,4 +50,3 @@ class YOGServerPlayerStoredInfoManager -#endif diff --git a/src/YOGServerRouter.cpp b/src/yog/YOGServerRouter.cpp similarity index 79% rename from src/YOGServerRouter.cpp rename to src/yog/YOGServerRouter.cpp index bcf71405b..eb13b5ada 100644 --- a/src/YOGServerRouter.cpp +++ b/src/yog/YOGServerRouter.cpp @@ -1,25 +1,10 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "FileManager.h" #include #include "NetConnection.h" -#include "NetMessage.h" +#include "RouterMessages.h" #include "Stream.h" #include "Toolkit.h" #include "YOGConsts.h" @@ -30,7 +15,7 @@ #include using namespace GAGCore; -using boost::static_pointer_cast; +using std::static_pointer_cast; YOGServerRouter::YOGServerRouter() : nl(YOG_ROUTER_PORT), admin(this) @@ -63,7 +48,7 @@ void YOGServerRouter::update() } //Call update to all of the players - for(std::vector >::iterator i=players.begin(); i!=players.end(); ++i) + for(std::vector >::iterator i=players.begin(); i!=players.end(); ++i) { (*i)->update(); } @@ -75,7 +60,7 @@ void YOGServerRouter::update() } //Removes all players that have disconnected - for(std::vector >::iterator i = players.begin(); i!=players.end();) + for(std::vector >::iterator i = players.begin(); i!=players.end();) { if(!(*i)->isConnected()) { @@ -151,7 +136,7 @@ int YOGServerRouter::run() -boost::shared_ptr YOGServerRouter::getGame(Uint16 gameID) +std::shared_ptr YOGServerRouter::getGame(Uint16 gameID) { if(games.find(gameID) == games.end()) games[gameID].reset(new YOGServerGameRouter); diff --git a/src/YOGServerRouter.h b/src/yog/YOGServerRouter.h similarity index 54% rename from src/YOGServerRouter.h rename to src/yog/YOGServerRouter.h index 88720c66c..9fbb94124 100644 --- a/src/YOGServerRouter.h +++ b/src/yog/YOGServerRouter.h @@ -1,25 +1,9 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +#pragma once - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerRouter_h -#define YOGServerRouter_h - -#include "boost/shared_ptr.hpp" +#include #include "SDL_net.h" #include #include @@ -49,7 +33,7 @@ class YOGServerRouter int run(); ///Returns the game id - boost::shared_ptr getGame(Uint16 gameID); + std::shared_ptr getGame(Uint16 gameID); ///Returns true if the password given is correct for the administrator for this server bool isAdministratorPasswordCorrect(const std::string& password); @@ -65,12 +49,11 @@ class YOGServerRouter private: NetListener nl; - boost::shared_ptr new_connection; - boost::shared_ptr yog_connection; - std::map > games; - std::vector > players; + std::shared_ptr new_connection; + std::shared_ptr yog_connection; + std::map > games; + std::vector > players; YOGServerRouterAdministrator admin; bool shutdownMode; }; -#endif diff --git a/src/YOGServerRouterAdministrator.cpp b/src/yog/YOGServerRouterAdministrator.cpp similarity index 71% rename from src/YOGServerRouterAdministrator.cpp rename to src/yog/YOGServerRouterAdministrator.cpp index 23e51a171..5a009e4ec 100644 --- a/src/YOGServerRouterAdministrator.cpp +++ b/src/yog/YOGServerRouterAdministrator.cpp @@ -1,26 +1,10 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include -#include "boost/shared_ptr.hpp" +#include #include -#include "NetMessage.h" +#include "RouterAdminMessages.h" #include "YOGServerRouterAdministrator.h" #include "YOGServerRouterAdministratorCommands.h" #include "YOGServerRouterPlayer.h" @@ -106,7 +90,7 @@ bool YOGServerRouterAdministrator::executeAdministrativeCommand(const std::strin { if(tokens[0] == commands[i]->getCommandName()) { - if(!commands[i]->doesMatch(tokens)) + if(!commands[i]->doesMatch(tokens.size())) { sendTextMessage(commands[i]->getHelpMessage(), player); } @@ -136,7 +120,7 @@ void YOGServerRouterAdministrator::sendTextMessage(const std::string& message, Y void YOGServerRouterAdministrator::flushTexts(YOGServerRouterPlayer* admin) { - boost::shared_ptr text(new NetRouterAdministratorSendText(allText)); + std::shared_ptr text(new NetRouterAdministratorSendText(allText)); admin->sendNetMessage(text); allText.clear(); } diff --git a/src/YOGServerRouterAdministrator.h b/src/yog/YOGServerRouterAdministrator.h similarity index 54% rename from src/YOGServerRouterAdministrator.h rename to src/yog/YOGServerRouterAdministrator.h index 44343de20..9d05a12d0 100644 --- a/src/YOGServerRouterAdministrator.h +++ b/src/yog/YOGServerRouterAdministrator.h @@ -1,26 +1,10 @@ -/* - Copyright (C) 2008 Bradley Arsenault +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef YOGServerRouterAdministrator_h -#define YOGServerRouterAdministrator_h +#pragma once #include -#include "boost/shared_ptr.hpp" +#include #include class YOGServerRouter; @@ -54,4 +38,3 @@ class YOGServerRouterAdministrator std::string allText; }; -#endif diff --git a/src/YOGServerRouterAdministratorCommands.cpp b/src/yog/YOGServerRouterAdministratorCommands.cpp similarity index 53% rename from src/YOGServerRouterAdministratorCommands.cpp rename to src/yog/YOGServerRouterAdministratorCommands.cpp index ad4409231..867359e6a 100644 --- a/src/YOGServerRouterAdministratorCommands.cpp +++ b/src/yog/YOGServerRouterAdministratorCommands.cpp @@ -1,20 +1,5 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGServerRouterAdministratorCommands.h" #include "YOGServerRouter.h" @@ -33,15 +18,6 @@ std::string YOGServerRouterAbortCommand::getCommandName() -bool YOGServerRouterAbortCommand::doesMatch(const std::vector& tokens) -{ - if(tokens.size() == 1) - return true; - return false; -} - - - void YOGServerRouterAbortCommand::execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player) { exit(0); @@ -63,15 +39,6 @@ std::string YOGServerRouterShutdownCommand::getCommandName() -bool YOGServerRouterShutdownCommand::doesMatch(const std::vector& tokens) -{ - if(tokens.size() == 1) - return true; - return false; -} - - - void YOGServerRouterShutdownCommand::execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player) { router->enterShutdownMode(); @@ -94,15 +61,6 @@ std::string YOGServerRouterStatusCommand::getCommandName() -bool YOGServerRouterStatusCommand::doesMatch(const std::vector& tokens) -{ - if(tokens.size() == 1) - return true; - return false; -} - - - void YOGServerRouterStatusCommand::execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player) { admin->sendTextMessage(router->getStatusReport(), player); diff --git a/src/yog/YOGServerRouterAdministratorCommands.h b/src/yog/YOGServerRouterAdministratorCommands.h new file mode 100644 index 000000000..31c920dcf --- /dev/null +++ b/src/yog/YOGServerRouterAdministratorCommands.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include +#include + +class YOGServerRouterAdministrator; +class YOGServerRouter; +class YOGServerRouterPlayer; + +///This defines a generic command +class YOGServerRouterAdministratorCommand +{ +public: + virtual ~YOGServerRouterAdministratorCommand() {} + + ///Returns this YOGServerRouterAdministratorCommand help message + virtual std::string getHelpMessage()=0; + + ///Returns the command name for this YOGServerRouterAdministratorCommand + virtual std::string getCommandName()=0; + + ///Executes the code for the administrator command + virtual void execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player)=0; + + ///Returns true if the token count is within this command's accepted range. + bool doesMatch(std::size_t count) const + { + return int(count) >= minTokens && int(count) <= maxTokens; + } + +protected: + explicit YOGServerRouterAdministratorCommand(int fixedTokens) : minTokens(fixedTokens), maxTokens(fixedTokens) {} + YOGServerRouterAdministratorCommand(int min, int max) : minTokens(min), maxTokens(max) {} + +private: + int minTokens; + int maxTokens; +}; + +///This command hard shuts down the router +class YOGServerRouterAbortCommand : public YOGServerRouterAdministratorCommand +{ +public: + YOGServerRouterAbortCommand() : YOGServerRouterAdministratorCommand(1) {} + std::string getHelpMessage(); + std::string getCommandName(); + void execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player); +}; + + +///This command causes a router to disconnect from the server and turn off once all clients disconnecty +class YOGServerRouterShutdownCommand : public YOGServerRouterAdministratorCommand +{ +public: + YOGServerRouterShutdownCommand() : YOGServerRouterAdministratorCommand(1) {} + std::string getHelpMessage(); + std::string getCommandName(); + void execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player); +}; + + +///This command prints a status report of the YOG server +class YOGServerRouterStatusCommand : public YOGServerRouterAdministratorCommand +{ +public: + YOGServerRouterStatusCommand() : YOGServerRouterAdministratorCommand(1) {} + std::string getHelpMessage(); + std::string getCommandName(); + void execute(YOGServerRouter* router, YOGServerRouterAdministrator* admin, const std::vector& tokens, YOGServerRouterPlayer* player); +}; + diff --git a/src/yog/YOGServerRouterManager.cpp b/src/yog/YOGServerRouterManager.cpp new file mode 100644 index 000000000..cb6fb0a44 --- /dev/null +++ b/src/yog/YOGServerRouterManager.cpp @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#include "YOGServerRouterManager.h" +#include "YOGServer.h" +#include "NetConnection.h" +#include "RouterMessages.h" + +using std::static_pointer_cast; + +YOGServerRouterManager::YOGServerRouterManager(YOGServer& /*server*/) + : listener(YOG_SERVER_ROUTER_PORT) +{ + new_connection.reset(new NetConnection); + n=0; +} + + + +void YOGServerRouterManager::addRouter(std::shared_ptr connection) +{ + shared_ptr info(new NetAcknowledgeRouter); + connection->sendMessage(info); + routers.push_back(connection); +} + + +void YOGServerRouterManager::update() +{ + //First attempt connections with new routers + while(listener.attemptConnection(*new_connection)) + { + addRouter(new_connection); + new_connection.reset(new NetConnection); + } + + //Update all routers + for(std::vector >::iterator i = routers.begin(); i!=routers.end(); ++i) + { + (*i)->update(); + //Parse incoming messages. + shared_ptr message = (*i)->getMessage(); + if(message) + { + Uint8 type = message->getMessageType(); + //This recieves the router information + if(type==MNetRegisterRouter) + { + shared_ptr info = static_pointer_cast(message); + } + } + } + + for(std::vector >::iterator i = routers.begin(); i!=routers.end();) + { + if(!(*i)->isConnected()) + { + Uint32 n = i - routers.begin(); + routers.erase(i); + i = routers.begin() + n; + } + else + { + ++i; + } + } +} + + +std::shared_ptr YOGServerRouterManager::chooseYOGRouter() +{ + n+=1; + if(n == (int)routers.size()) + n = 0; + return routers[n]; +} + diff --git a/src/yog/YOGServerRouterManager.h b/src/yog/YOGServerRouterManager.h new file mode 100644 index 000000000..c2490d9c3 --- /dev/null +++ b/src/yog/YOGServerRouterManager.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include +#include "NetListener.h" + +class NetConnection; +class YOGServer; + +///This class manages the list of YOGServerRouters +class YOGServerRouterManager +{ +public: + ///Creates a YOGServerRouter + YOGServerRouterManager(YOGServer& server); + + ///Adds a connection to a YOG + void addRouter(std::shared_ptr connection); + + ///Updates this manager + void update(); + + ///This chooses a new yog router + std::shared_ptr chooseYOGRouter(); +private: + std::vector > routers; + NetListener listener; + std::shared_ptr new_connection; + int n; +}; + + diff --git a/src/YOGServerRouterPlayer.cpp b/src/yog/YOGServerRouterPlayer.cpp similarity index 53% rename from src/YOGServerRouterPlayer.cpp rename to src/yog/YOGServerRouterPlayer.cpp index 6d2845b67..8f8c89599 100644 --- a/src/YOGServerRouterPlayer.cpp +++ b/src/yog/YOGServerRouterPlayer.cpp @@ -1,45 +1,31 @@ -/* - Copyright (C) 2008 Bradley Arsenault - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault #include "YOGServerRouterPlayer.h" #include "NetConnection.h" -#include "NetMessage.h" +#include "OrderMessages.h" +#include "RouterAdminMessages.h" +#include "RouterMessages.h" #include "YOGServerGameRouter.h" #include "YOGServerRouter.h" -using boost::static_pointer_cast; +using std::static_pointer_cast; -YOGServerRouterPlayer::YOGServerRouterPlayer(boost::shared_ptr connection, YOGServerRouter* router) +YOGServerRouterPlayer::YOGServerRouterPlayer(std::shared_ptr connection, YOGServerRouter* router) : connection(connection), router(router), isAdmin(false) { } -void YOGServerRouterPlayer::setPointer(boost::weak_ptr npointer) +void YOGServerRouterPlayer::setPointer(std::weak_ptr npointer) { pointer = npointer; } -void YOGServerRouterPlayer::sendNetMessage(boost::shared_ptr message) +void YOGServerRouterPlayer::sendNetMessage(std::shared_ptr message) { connection->sendMessage(message); } @@ -67,7 +53,7 @@ void YOGServerRouterPlayer::update() { shared_ptr info = static_pointer_cast(message); game = router->getGame(info->getGameID()); - game->addPlayer(boost::shared_ptr(pointer)); + game->addPlayer(std::shared_ptr(pointer)); } else if(type==MNetRouterAdministratorLogin) { @@ -76,12 +62,12 @@ void YOGServerRouterPlayer::update() if(router->isAdministratorPasswordCorrect(password)) { isAdmin=true; - boost::shared_ptr m = boost::shared_ptr(new NetRouterAdministratorLoginAccepted); + std::shared_ptr m = std::shared_ptr(new NetRouterAdministratorLoginAccepted); sendNetMessage(m); } else { - boost::shared_ptr m = boost::shared_ptr(new NetRouterAdministratorLoginRefused(YOGRouterLoginWrongPassword)); + std::shared_ptr m = std::shared_ptr(new NetRouterAdministratorLoginRefused(YOGRouterLoginWrongPassword)); sendNetMessage(m); } } diff --git a/src/yog/YOGServerRouterPlayer.h b/src/yog/YOGServerRouterPlayer.h new file mode 100644 index 000000000..400551948 --- /dev/null +++ b/src/yog/YOGServerRouterPlayer.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2008 Bradley Arsenault + +#pragma once + +#include +#include "YOGServerRouterAdministrator.h" + +class NetConnection; +class NetMessage; +class YOGServerGameRouter; +class YOGServerRouter; + +///This represents a single connectee to the YOGServerRouterPlayer +class YOGServerRouterPlayer +{ +public: + ///Constructs a YOGServerRouterPlayer to use the given net connection + YOGServerRouterPlayer(std::shared_ptr connection, YOGServerRouter* router); + + ///Provides a weak pointer to this class + void setPointer(std::weak_ptr pointer); + + ///Sends a message to the player + void sendNetMessage(std::shared_ptr message); + + ///Updates this player + void update(); + + ///Returns true if this player is still connected + bool isConnected(); + + ///Returns true if this player is an admin + bool isAdministrator(); + +private: + std::shared_ptr connection; + std::shared_ptr game; + YOGServerRouter* router; + std::weak_ptr pointer; + bool isAdmin; +}; + diff --git a/src/add_yog_event.py b/src/yog/add_yog_event.py similarity index 100% rename from src/add_yog_event.py rename to src/yog/add_yog_event.py diff --git a/test/CampaignLoadHarness.cpp b/test/CampaignLoadHarness.cpp new file mode 100644 index 000000000..382248035 --- /dev/null +++ b/test/CampaignLoadHarness.cpp @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2026 glob2 contributors + +// Behaviour-equivalence harness for src/Campaign.cpp::load. +// +// Verifies that Campaign::load correctly distinguishes valid campaigns from +// missing / empty / garbage / bogus-version files. Pre-fix tree silently +// returns true for the four broken cases; post-fix tree returns false. +// +// Pattern follows WinningConditionsHarness: deterministic golden text on +// stdout, cppunit-free, behaviour-preserving cleanups verified by diff. +// +// Self-contained: synthesizes its own campaign fixture files in /tmp so the +// run is independent of the real campaigns/ directory. + +#include "Campaign.h" +#include "Toolkit.h" + +#include +#include +#include + +namespace { + +const char* kTmpValid = "/tmp/glob2_campaign_harness_valid.txt"; +const char* kTmpEmpty = "/tmp/glob2_campaign_harness_empty.txt"; +const char* kTmpGarbage = "/tmp/glob2_campaign_harness_garbage.txt"; +const char* kTmpVersion0 = "/tmp/glob2_campaign_harness_v0.txt"; +const char* kTmpVersionHi = "/tmp/glob2_campaign_harness_v999.txt"; +const char* kMissing = "/tmp/glob2_campaign_harness_does_not_exist.txt"; + +void writeFile(const char* path, const std::string& content) +{ + FILE* f = std::fopen(path, "wb"); + if (!f) { std::fprintf(stderr, "harness: cannot open %s\n", path); std::exit(2); } + if (!content.empty()) + std::fwrite(content.data(), 1, content.size(), f); + std::fclose(f); +} + +void writeFixtures() +{ + const std::string valid = + "versionMinor = 84;\n" + "campaignName = \"TestCampaign\";\n" + "playerName = \"Tester\";\n" + "maps\n" + "{\n" + "\tmapNum = 2;\n" + "\t0\n" + "\t{\n" + "\t\tCampaignMap\n" + "\t\t{\n" + "\t\t\tmapName = \"Map1\";\n" + "\t\t\tmapFileName = \"fake1.map\";\n" + "\t\t\tisLocked = 0;\n" + "\t\t\tunlockedBy\n" + "\t\t\t{\n" + "\t\t\t\tsize = 0;\n" + "\t\t\t}\n" + "\t\t\tdescription = \"desc1\";\n" + "\t\t\tcompleted = 0;\n" + "\t\t}\n" + "\t}\n" + "\t1\n" + "\t{\n" + "\t\tCampaignMap\n" + "\t\t{\n" + "\t\t\tmapName = \"Map2\";\n" + "\t\t\tmapFileName = \"fake2.map\";\n" + "\t\t\tisLocked = 1;\n" + "\t\t\tunlockedBy\n" + "\t\t\t{\n" + "\t\t\t\tsize = 1;\n" + "\t\t\t\t0\n" + "\t\t\t\t{\n" + "\t\t\t\t\tunlockedBy = \"Map1\";\n" + "\t\t\t\t}\n" + "\t\t\t}\n" + "\t\t\tdescription = \"desc2\";\n" + "\t\t\tcompleted = 0;\n" + "\t\t}\n" + "\t}\n" + "}\n" + "description = \"campaign description\";\n"; + writeFile(kTmpValid, valid); + + writeFile(kTmpEmpty, ""); + + writeFile(kTmpGarbage, "this is not a campaign file at all }} { ;; \xff\xfe\x00 random"); + + const std::string v0 = + "versionMinor = 0;\n" + "campaignName = \"ShouldNotLoad\";\n" + "playerName = \"\";\n" + "maps\n" + "{\n" + "\tmapNum = 0;\n" + "}\n"; + writeFile(kTmpVersion0, v0); + + const std::string vHi = + "versionMinor = 999;\n" + "campaignName = \"FromTheFuture\";\n" + "playerName = \"\";\n" + "maps\n" + "{\n" + "\tmapNum = 0;\n" + "}\n"; + writeFile(kTmpVersionHi, vHi); + + // kMissing: deliberately not created. + std::remove(kMissing); +} + +void runCase(const char* tag, const char* path) +{ + Campaign c; + const bool ok = c.load(path); + std::printf("%-12s ok=%d name=\"%s\" maps=%zu\n", + tag, ok ? 1 : 0, c.getName().c_str(), c.getMapCount()); +} + +} // namespace + +int main(int /*argc*/, char* /*argv*/[]) +{ + // Silence the parser's cerr noise so the golden output stays deterministic. + // TextStream's parser logs to stderr on malformed input; we don't want that + // in the diff. + std::freopen("/dev/null", "w", stderr); + + GAGCore::Toolkit::init("glob2"); + writeFixtures(); + + std::printf("# CampaignLoadHarness golden output\n"); + runCase("valid", kTmpValid); + runCase("missing", kMissing); + runCase("empty", kTmpEmpty); + runCase("garbage", kTmpGarbage); + runCase("version0", kTmpVersion0); + runCase("version+", kTmpVersionHi); + + GAGCore::Toolkit::close(); + return 0; +} diff --git a/test/CampaignLoadTestStubs.cpp b/test/CampaignLoadTestStubs.cpp new file mode 100644 index 000000000..6da0639e7 --- /dev/null +++ b/test/CampaignLoadTestStubs.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2026 glob2 contributors + +// Stubs for symbols referenced by Campaign.o that the load-path harness +// doesn't actually exercise. Campaign::save() calls glob2NameToFilename() +// to build a save path; the real definition lives in MapHeader.cpp, which +// would drag in Map / Game / etc. The harness never invokes save(), so a +// stub satisfies the linker without that transitive surface. + +#include + +std::string glob2NameToFilename(const std::string& /*dir*/, + const std::string& /*name*/, + const std::string& /*extension*/) +{ + return std::string(); +} diff --git a/test/CampaignSelectionHarness.cpp b/test/CampaignSelectionHarness.cpp new file mode 100644 index 000000000..49e67c115 --- /dev/null +++ b/test/CampaignSelectionHarness.cpp @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2026 glob2 contributors + +// Regression harness for CS-051: CampaignMenuScreen::onAction's +// LIST_ELEMENT_SELECTED handler used `campaign.getMap(displayedListIndex)`, +// but the displayed list contains only *unlocked* maps while +// `campaign.maps[]` holds locked entries too. As soon as a campaign has +// any locked map ahead of an unlocked one (the natural case for +// non-linear unlock graphs), the indices diverge and clicking item N +// brings up the preview/description for the wrong mission. +// +// The fix routes selection through `Campaign::findUnlockedMap(name)` so +// that lookup is by the displayed name, not by list position. +// +// This harness proves both halves of the fix: +// 1. The OLD algorithm (`getMap(index)`) returns the WRONG entry on a +// non-linear fixture — confirming the bug was real. +// 2. The NEW algorithm (`findUnlockedMap(name)`) returns the RIGHT +// entry — confirming the fix works. +// +// Pre-fix tree: this harness fails to LINK because `Campaign::findUnlockedMap` +// does not exist. That is the "broken before" signal. +// Post-fix tree: this harness builds, runs, exits 0, and prints a +// deterministic golden line for diff-based verification. + +#include "Campaign.h" + +#include +#include +#include +#include + +namespace { + +int failures = 0; + +#define EXPECT(cond, msg) \ + do { \ + if (!(cond)) { \ + std::fprintf(stderr, "FAIL: %s (%s:%d)\n", \ + (msg), __FILE__, __LINE__); \ + ++failures; \ + } \ + } while (0) + +// Build the regression fixture in memory: +// maps[0] = "Map1" / "fake1.map" unlocked +// maps[1] = "Map2" / "fake2.map" LOCKED +// maps[2] = "Map3" / "fake3.map" unlocked (unlocked-by Map1) +// +// The displayed mission list (which CampaignMenuScreen::repopulateAvailableMissions +// builds by filtering on isUnlocked) is therefore: ["Map1", "Map3"]. +// Position 1 in the displayed list is "Map3" -- but maps[1] is "Map2". +// This is the index/name divergence the bug fix targets. +Campaign buildNonLinearFixture() +{ + Campaign c; + c.setName("RegressionCampaign"); + + CampaignMapEntry m1("Map1", "fake1.map"); + m1.unlockMap(); + c.appendMap(m1); + + CampaignMapEntry m2("Map2", "fake2.map"); + m2.lockMap(); + c.appendMap(m2); + + CampaignMapEntry m3("Map3", "fake3.map"); + m3.unlockMap(); + m3.getUnlockedByMaps().push_back("Map1"); + c.appendMap(m3); + + return c; +} + +// Mirrors CampaignMenuScreen::repopulateAvailableMissions — builds the +// list of names the widget would display, in the same order. +std::vector buildDisplayedList(Campaign& c) +{ + std::vector displayed; + for (unsigned i = 0; i < c.getMapCount(); ++i) + if (c.getMap(i).isUnlocked()) + displayed.push_back(c.getMap(i).getMapName()); + return displayed; +} + +} // namespace + +int main(int /*argc*/, char* /*argv*/[]) +{ + std::printf("# CampaignSelectionHarness golden output\n"); + + Campaign campaign = buildNonLinearFixture(); + std::vector displayed = buildDisplayedList(campaign); + + std::printf("fixture: maps=%zu displayed=%zu\n", + campaign.getMapCount(), displayed.size()); + for (size_t i = 0; i < displayed.size(); ++i) + std::printf(" displayed[%zu] = \"%s\"\n", i, displayed[i].c_str()); + + EXPECT(campaign.getMapCount() == 3, "fixture should have 3 maps"); + EXPECT(displayed.size() == 2, "displayed list should hide the locked map"); + EXPECT(displayed[0] == "Map1", "displayed[0] should be Map1"); + EXPECT(displayed[1] == "Map3", "displayed[1] should be Map3 (skipping locked Map2)"); + + // Simulate the user clicking the second item in the displayed list. + const size_t userClickedIndex = 1; + const std::string userClickedName = displayed[userClickedIndex]; + std::printf("user clicks displayed[%zu] = \"%s\"\n", + userClickedIndex, userClickedName.c_str()); + + // === OLD algorithm (the bug) === + // CampaignMenuScreen used to do `campaign.getMap(getSelectionIndex())`, + // treating the displayed-list index as a campaign.maps index. + CampaignMapEntry& byIndex = campaign.getMap(static_cast(userClickedIndex)); + std::printf("[old] campaign.getMap(%zu) -> name=\"%s\" file=\"%s\"\n", + userClickedIndex, byIndex.getMapName().c_str(), + byIndex.getMapFileName().c_str()); + EXPECT(byIndex.getMapName() == "Map2", + "old algorithm reproduces the bug: returns Map2 instead of Map3"); + EXPECT(byIndex.getMapName() != userClickedName, + "old algorithm must disagree with the user's click on this fixture; " + "if this assertion fails the fixture no longer exercises the bug"); + + // === NEW algorithm (the fix) === + // The post-fix CampaignMenuScreen routes selection through + // Campaign::findUnlockedMap(displayedName). + CampaignMapEntry* byName = campaign.findUnlockedMap(userClickedName); + std::printf("[new] campaign.findUnlockedMap(\"%s\") -> name=\"%s\" file=\"%s\"\n", + userClickedName.c_str(), + byName ? byName->getMapName().c_str() : "(null)", + byName ? byName->getMapFileName().c_str() : "(null)"); + EXPECT(byName != nullptr, "new algorithm should resolve the displayed name"); + EXPECT(byName && byName->getMapName() == "Map3", + "new algorithm should return the actually-clicked map"); + EXPECT(byName && byName->getMapFileName() == "fake3.map", + "new algorithm should yield the correct .map filename"); + + // === Boundary cases the old code crashed/asserted on === + // Selection cleared (displayed name not in campaign): pre-fix + // getMissionName() hit `assert(false)`; the helper returns nullptr so + // the menu screen can no-op gracefully. + CampaignMapEntry* missing = campaign.findUnlockedMap("DoesNotExist"); + std::printf("[new] findUnlockedMap(\"DoesNotExist\") -> %s\n", + missing ? "FOUND (BUG)" : "nullptr"); + EXPECT(missing == nullptr, "lookup of missing name must return nullptr"); + + // Lookup of a locked map's name: must not return the locked entry, + // even if the displayed list somehow contained it. + CampaignMapEntry* locked = campaign.findUnlockedMap("Map2"); + std::printf("[new] findUnlockedMap(\"Map2\" locked) -> %s\n", + locked ? "FOUND (BUG)" : "nullptr"); + EXPECT(locked == nullptr, + "findUnlockedMap must not return a locked entry"); + + std::printf("result: %d failure(s)\n", failures); + return failures == 0 ? 0 : 1; +} diff --git a/test/GameMusicControllerTest.cpp b/test/GameMusicControllerTest.cpp new file mode 100644 index 000000000..7352a98f8 --- /dev/null +++ b/test/GameMusicControllerTest.cpp @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2001-2004 Stephane Magnenat & Luc-Olivier de Charrière +// +// Standalone unit tests for GameMusicController. The controller is a pure +// state machine over GameMusicEvents with no SDL / Team / globalContainer +// dependencies, so this test links only the controller .cpp itself. + +#include + +#include "../src/gui/GameMusicController.h" + +namespace +{ + GameMusicEvents warEvent() + { + GameMusicEvents e; + e.unitUnderAttack = true; + return e; + } + + GameMusicEvents goodEvent() + { + GameMusicEvents e; + e.buildingCompleted = true; + return e; + } + + GameMusicEvents nothing() { return GameMusicEvents{}; } +} + +class GameMusicControllerTest: public CPPUNIT_NS::TestCase +{ +CPPUNIT_TEST_SUITE(GameMusicControllerTest); + CPPUNIT_TEST(testWarEventSetsWarTrackAndTimer); + CPPUNIT_TEST(testGoodEventSetsBuildingTrackAndTimer); + CPPUNIT_TEST(testTimerDecaysToDefaultTrack); + CPPUNIT_TEST(testSimultaneousEventsLastWriterWins); + CPPUNIT_TEST(testResetClearsTimers); + CPPUNIT_TEST(testNoEventNoTrack); + CPPUNIT_TEST(testWarEventOverridesExpiringBuildingTimer); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(void) override {} + void tearDown(void) override {} + +protected: + void testWarEventSetsWarTrackAndTimer() + { + GameMusicController c; + auto track = c.tick(warEvent()); + CPPUNIT_ASSERT(track.has_value()); + CPPUNIT_ASSERT_EQUAL(MusicTrack::WarEvent, *track); + // EVENT_TIMEOUT_TICKS gets set then decremented to 219 in the same tick. + CPPUNIT_ASSERT_EQUAL(GameMusicController::EVENT_TIMEOUT_TICKS - 1, c.getWarTimeoutTicks()); + } + + void testGoodEventSetsBuildingTrackAndTimer() + { + GameMusicController c; + auto track = c.tick(goodEvent()); + CPPUNIT_ASSERT(track.has_value()); + CPPUNIT_ASSERT_EQUAL(MusicTrack::BuildingEvent, *track); + CPPUNIT_ASSERT_EQUAL(GameMusicController::EVENT_TIMEOUT_TICKS - 1, c.getBuildingTimeoutTicks()); + } + + void testTimerDecaysToDefaultTrack() + { + GameMusicController c; + c.tick(warEvent()); + // Drain to the tick where warTimeoutTicks == 1 at the top of tick(). + // After the war event tick, war timer is EVENT_TIMEOUT_TICKS - 1. + // We need it to read 1 at the start of a tick, so we need + // (EVENT_TIMEOUT_TICKS - 1) - 1 more empty ticks to bring it to 1 + // at the start of the *next* tick. + const unsigned ticksUntilOne = GameMusicController::EVENT_TIMEOUT_TICKS - 2; + for (unsigned i = 0; i < ticksUntilOne; ++i) + { + auto t = c.tick(nothing()); + CPPUNIT_ASSERT(!t.has_value()); + } + // Sanity: timer should read 1 at the start of the next tick. + CPPUNIT_ASSERT_EQUAL(1u, c.getWarTimeoutTicks()); + auto track = c.tick(nothing()); + CPPUNIT_ASSERT(track.has_value()); + CPPUNIT_ASSERT_EQUAL(MusicTrack::InGameDefault, *track); + CPPUNIT_ASSERT_EQUAL(0u, c.getWarTimeoutTicks()); + } + + void testSimultaneousEventsLastWriterWins() + { + GameMusicController c; + GameMusicEvents both; + both.unitUnderAttack = true; + both.buildingCompleted = true; + auto track = c.tick(both); + // Original musicStep does the good-event branch second; that's the + // last setNextTrack call before the timeout check, so building wins + // when both fire and neither timer is at 1. + CPPUNIT_ASSERT(track.has_value()); + CPPUNIT_ASSERT_EQUAL(MusicTrack::BuildingEvent, *track); + CPPUNIT_ASSERT_EQUAL(GameMusicController::EVENT_TIMEOUT_TICKS - 1, c.getWarTimeoutTicks()); + CPPUNIT_ASSERT_EQUAL(GameMusicController::EVENT_TIMEOUT_TICKS - 1, c.getBuildingTimeoutTicks()); + } + + void testResetClearsTimers() + { + GameMusicController c; + c.tick(warEvent()); + c.tick(goodEvent()); + CPPUNIT_ASSERT(c.getWarTimeoutTicks() > 0); + CPPUNIT_ASSERT(c.getBuildingTimeoutTicks() > 0); + c.reset(); + CPPUNIT_ASSERT_EQUAL(0u, c.getWarTimeoutTicks()); + CPPUNIT_ASSERT_EQUAL(0u, c.getBuildingTimeoutTicks()); + // A reset controller should behave identically to a fresh one — no + // stale "timer == 1" transition on the very next tick. + auto track = c.tick(nothing()); + CPPUNIT_ASSERT(!track.has_value()); + } + + void testNoEventNoTrack() + { + GameMusicController c; + for (int i = 0; i < 50; ++i) + { + auto t = c.tick(nothing()); + CPPUNIT_ASSERT(!t.has_value()); + } + } + + void testWarEventOverridesExpiringBuildingTimer() + { + // Original musicStep behavior: when an event fires AND the OTHER + // timer hits 1 on the same tick, the InGameDefault branch runs + // last and wins. Verify the controller preserves that ordering. + GameMusicController c; + c.tick(goodEvent()); + // Drain building timer to read 1 at the start of the next tick. + for (unsigned i = 0; i < GameMusicController::EVENT_TIMEOUT_TICKS - 2; ++i) + c.tick(nothing()); + CPPUNIT_ASSERT_EQUAL(1u, c.getBuildingTimeoutTicks()); + // Now fire a war event on the same tick the building timer expires. + auto track = c.tick(warEvent()); + CPPUNIT_ASSERT(track.has_value()); + CPPUNIT_ASSERT_EQUAL(MusicTrack::InGameDefault, *track); + } +}; +CPPUNIT_TEST_SUITE_REGISTRATION(GameMusicControllerTest); diff --git a/test/GradientBFSTest.cpp b/test/GradientBFSTest.cpp new file mode 100644 index 000000000..585798dd1 --- /dev/null +++ b/test/GradientBFSTest.cpp @@ -0,0 +1,175 @@ +/* + Copyright (C) 2026 glob2 contributors + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "GradientBFSTest.h" +#include "echo/Echo.h" + +#include +#include +#include +#include + +CPPUNIT_TEST_SUITE_REGISTRATION( GradientBFSTest ); + +using AIEcho::position; +using AIEcho::Gradients::Gradient; +using AIEcho::Gradients::GradientInfo; + +std::vector GradientBFSTest::run_bfs(int width, int height, + const std::vector& sources, + const std::vector& obstacles) +{ + GradientInfo gi; + Gradient g(gi); + g.width = width; + g.gradient.assign(width * height, 0); + std::queue q; + for (const auto& s : sources) + { + g.gradient[s.y * width + s.x] = 2; + q.push(s); + } + for (const auto& o : obstacles) + g.gradient[o.y * width + o.x] = 1; + g.expand_bfs(q); + CPPUNIT_ASSERT(q.empty()); + return g.gradient; +} + +namespace +{ + Sint16 at(const std::vector& g, int width, int x, int y) + { + return g[y * width + x]; + } + + // Toroidal Chebyshev distance under 8-connectivity. This is the math + // reference for the BFS output on an obstacle-free grid. + int chebyshev_torus(int x1, int y1, int x2, int y2, int w, int h) + { + int dx = std::abs(x1 - x2); + int dy = std::abs(y1 - y2); + dx = std::min(dx, w - dx); + dy = std::min(dy, h - dy); + return std::max(dx, dy); + } +} + +void GradientBFSTest::testEmptyQueueIsNoop() +{ + auto g = run_bfs(4, 4, {}, {}); + for (auto v : g) + CPPUNIT_ASSERT_EQUAL(Sint16(0), v); +} + +void GradientBFSTest::testSingleSourceMatchesChebyshev() +{ + // 5x5 torus, single source at (2,2). Every other cell is within Chebyshev + // distance 2 under 8-connectivity, so expected gradient = dist + 2. + const int W = 5, H = 5; + auto g = run_bfs(W, H, { position(2, 2) }, {}); + for (int y = 0; y < H; ++y) + for (int x = 0; x < W; ++x) + { + Sint16 expected = static_cast(chebyshev_torus(x, y, 2, 2, W, H) + 2); + CPPUNIT_ASSERT_EQUAL(expected, at(g, W, x, y)); + } +} + +void GradientBFSTest::testWrapAroundSmallGrid() +{ + // 4x4 torus, source at (0,0). (3,3) is reached via wrap (Chebyshev = 1) + // rather than the longer non-wrapped path. + const int W = 4, H = 4; + auto g = run_bfs(W, H, { position(0, 0) }, {}); + for (int y = 0; y < H; ++y) + for (int x = 0; x < W; ++x) + { + Sint16 expected = static_cast(chebyshev_torus(x, y, 0, 0, W, H) + 2); + CPPUNIT_ASSERT_EQUAL(expected, at(g, W, x, y)); + } + CPPUNIT_ASSERT_EQUAL(Sint16(3), at(g, W, 3, 3)); +} + +void GradientBFSTest::testObstacleNotOverwritten() +{ + // 3x3 torus, source at (0,0), obstacle at (1,1). On a 3x3 torus all 8 + // non-source cells are direct neighbors of (0,0), so reachable cells get 3 + // and the obstacle stays at 1. + const int W = 3, H = 3; + auto g = run_bfs(W, H, { position(0, 0) }, { position(1, 1) }); + for (int y = 0; y < H; ++y) + for (int x = 0; x < W; ++x) + { + Sint16 expected; + if (x == 0 && y == 0) expected = 2; + else if (x == 1 && y == 1) expected = 1; + else expected = 3; + CPPUNIT_ASSERT_EQUAL(expected, at(g, W, x, y)); + } +} + +void GradientBFSTest::testMultipleSourcesUseMinimum() +{ + // 6x6 torus, sources at (0,0) and (5,5). Each cell takes the minimum + // Chebyshev distance to either source, plus 2. + const int W = 6, H = 6; + auto g = run_bfs(W, H, { position(0, 0), position(5, 5) }, {}); + for (int y = 0; y < H; ++y) + for (int x = 0; x < W; ++x) + { + int d1 = chebyshev_torus(x, y, 0, 0, W, H); + int d2 = chebyshev_torus(x, y, 5, 5, W, H); + Sint16 expected = static_cast(std::min(d1, d2) + 2); + CPPUNIT_ASSERT_EQUAL(expected, at(g, W, x, y)); + } +} + +void GradientBFSTest::testIsolatedCellRemainsUnreachable() +{ + // 7x7 torus, source at (0,0), obstacle ring of 8 obstacles fully enclosing + // (3,3). The center has no non-obstacle neighbor and must stay 0. + const int W = 7, H = 7; + std::vector ring = { + position(2, 2), position(3, 2), position(4, 2), + position(2, 3), position(4, 3), + position(2, 4), position(3, 4), position(4, 4), + }; + auto g = run_bfs(W, H, { position(0, 0) }, ring); + + CPPUNIT_ASSERT_EQUAL(Sint16(0), at(g, W, 3, 3)); + for (const auto& cell : ring) + CPPUNIT_ASSERT_EQUAL(Sint16(1), at(g, W, cell.x, cell.y)); +} + +void GradientBFSTest::testQueueIsDrained() +{ + // Postcondition asserted inside run_bfs(), but verified explicitly here too + // in case run_bfs is later refactored. + GradientInfo gi; + Gradient g(gi); + g.width = 4; + g.gradient.assign(16, 0); + g.gradient[0] = 2; + std::queue q; + q.push(position(0, 0)); + + g.expand_bfs(q); + + CPPUNIT_ASSERT(q.empty()); +} diff --git a/test/GradientBFSTest.h b/test/GradientBFSTest.h new file mode 100644 index 000000000..a40e5e0de --- /dev/null +++ b/test/GradientBFSTest.h @@ -0,0 +1,61 @@ +/* + Copyright (C) 2026 glob2 contributors + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef GRADIENTBFSTEST_H_ +#define GRADIENTBFSTEST_H_ + +#include + +#include +#include + +namespace AIEcho { class position; } + +// Friend of AIEcho::Gradients::Gradient (declared in AIEcho.h). Lives in the +// global namespace because production code only needs a single forward decl +// to friend it without dragging the cppunit headers into AIEcho.h. +class GradientBFSTest: public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( GradientBFSTest ); + CPPUNIT_TEST( testEmptyQueueIsNoop ); + CPPUNIT_TEST( testSingleSourceMatchesChebyshev ); + CPPUNIT_TEST( testWrapAroundSmallGrid ); + CPPUNIT_TEST( testObstacleNotOverwritten ); + CPPUNIT_TEST( testMultipleSourcesUseMinimum ); + CPPUNIT_TEST( testIsolatedCellRemainsUnreachable ); + CPPUNIT_TEST( testQueueIsDrained ); + CPPUNIT_TEST_SUITE_END(); + +public: + void testEmptyQueueIsNoop(); + void testSingleSourceMatchesChebyshev(); + void testWrapAroundSmallGrid(); + void testObstacleNotOverwritten(); + void testMultipleSourcesUseMinimum(); + void testIsolatedCellRemainsUnreachable(); + void testQueueIsDrained(); + + // Drives the production Gradient::expand_bfs on a real instance. Static + // member (rather than a free helper) so it inherits this fixture's friend + // access to Gradient's private members. + static std::vector run_bfs(int width, int height, + const std::vector& sources, + const std::vector& obstacles); +}; + +#endif diff --git a/test/MapQueryTest.cpp b/test/MapQueryTest.cpp new file mode 100644 index 000000000..75ee35655 --- /dev/null +++ b/test/MapQueryTest.cpp @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2026 glob2 contributors + +#include "MapQueryTest.h" + +#include "Map.h" +#include "TerrainType.h" + +CPPUNIT_TEST_SUITE_REGISTRATION( MapQueryTest ); + +namespace +{ + constexpr int kMapDec = 3; // 8x8 = 1<<3 + + // Minimal Map for predicate testing. + // + // We bypass Map::setSize() because it instantiates Sector[], which drags in + // most of the game (Bullet, GameEvent, Building::kill, globalContainer, ...). + // The isFreeFor*/isHardSpaceFor* predicates only need: a sized cases[] vector, + // and w/h/wMask/hMask/wDec/hDec for coordToIndex(). We set those directly. + // + // The destructor resets size fields before Map::~Map() runs, because Map's + // clear() else-branch (the path taken when arraysBuilt==false) asserts + // w==0 / h==0 / etc. — that's a real assert in production, but it expects + // to see a constructed-then-clear()'d Map, not a manually-poked one. + struct GrassMap : Map + { + GrassMap() + { + wDec = kMapDec; + hDec = kMapDec; + w = 1 << kMapDec; + h = 1 << kMapDec; + wMask = w - 1; + hMask = h - 1; + size = static_cast(w * h); + cases.assign(size, Case()); // Case() defaults: terrain=0 (grass), no building, no unit + } + ~GrassMap() + { + w = h = 0; + wMask = hMask = 0; + wDec = hDec = 0; + size = 0; + // Map::~Map() will call clear() which asserts these are 0. + } + + void putBuilding(int x, int y, Uint16 gbid = 0) + { + cases[coordToIndex(x, y)].building = gbid; + } + void putGroundUnit(int x, int y, Uint16 guid = 0) + { + cases[coordToIndex(x, y)].groundUnit = guid; + } + void putRessource(int x, int y, int type = 0) + { + Ressource &r = cases[coordToIndex(x, y)].ressource; + r.type = type; + r.amount = 1; + r.variety = 0; + r.animation = 0; + } + void setForbidden(int x, int y, Uint32 mask) + { + cases[coordToIndex(x, y)].forbidden = mask; + } + // Terrain encoding (see Map.h:336-361): + // grass : terrain < 16 + // sand : 128..143 + // water : 256..271 + void makeWater(int x, int y) + { + cases[coordToIndex(x, y)].terrain = 256; + } + void makeSand(int x, int y) + { + cases[coordToIndex(x, y)].terrain = 128; + } + }; + + constexpr Uint32 kTeam0 = 0x00000001u; + constexpr Uint32 kTeam1 = 0x00000002u; +} + +// ---------------- isFreeForGroundUnit ---------------- + +void MapQueryTest::testFreeForGroundUnit_CleanGrassPasses() +{ + GrassMap g; + CPPUNIT_ASSERT( g.isFreeForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testFreeForGroundUnit_RessourceFails() +{ + GrassMap g; g.putRessource(3, 3); + CPPUNIT_ASSERT( !g.isFreeForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testFreeForGroundUnit_BuildingFails() +{ + GrassMap g; g.putBuilding(3, 3); + CPPUNIT_ASSERT( !g.isFreeForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testFreeForGroundUnit_UnitFails() +{ + GrassMap g; g.putGroundUnit(3, 3); + CPPUNIT_ASSERT( !g.isFreeForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testFreeForGroundUnit_WaterFailsWhenNotSwim() +{ + GrassMap g; g.makeWater(3, 3); + CPPUNIT_ASSERT( !g.isFreeForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testFreeForGroundUnit_WaterPassesWhenSwim() +{ + GrassMap g; g.makeWater(3, 3); + CPPUNIT_ASSERT( g.isFreeForGroundUnit(3, 3, true, kTeam0) ); +} + +void MapQueryTest::testFreeForGroundUnit_ForbiddenFailsWhenMaskMatches() +{ + GrassMap g; g.setForbidden(3, 3, kTeam0); + CPPUNIT_ASSERT( !g.isFreeForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testFreeForGroundUnit_ForbiddenPassesWhenMaskDoesNotMatch() +{ + GrassMap g; g.setForbidden(3, 3, kTeam1); + CPPUNIT_ASSERT( g.isFreeForGroundUnit(3, 3, false, kTeam0) ); +} + +// ---------------- isFreeForGroundUnitNoForbidden ---------------- + +void MapQueryTest::testFreeForGroundUnitNoForbidden_IgnoresForbidden() +{ + GrassMap g; g.setForbidden(3, 3, kTeam0); + // Forbidden bit is set for our team — but the NoForbidden variant ignores it. + CPPUNIT_ASSERT( g.isFreeForGroundUnitNoForbidden(3, 3, false) ); +} + +void MapQueryTest::testFreeForGroundUnitNoForbidden_StillBlocksBuilding() +{ + GrassMap g; g.putBuilding(3, 3); + CPPUNIT_ASSERT( !g.isFreeForGroundUnitNoForbidden(3, 3, false) ); +} + +// ---------------- isFreeForBuilding ---------------- + +void MapQueryTest::testFreeForBuilding_GrassPasses() +{ + GrassMap g; + CPPUNIT_ASSERT( g.isFreeForBuilding(3, 3) ); +} + +void MapQueryTest::testFreeForBuilding_RessourceFails() +{ + GrassMap g; g.putRessource(3, 3); + CPPUNIT_ASSERT( !g.isFreeForBuilding(3, 3) ); +} + +void MapQueryTest::testFreeForBuilding_BuildingFails() +{ + GrassMap g; g.putBuilding(3, 3); + CPPUNIT_ASSERT( !g.isFreeForBuilding(3, 3) ); +} + +void MapQueryTest::testFreeForBuilding_UnitFails() +{ + GrassMap g; g.putGroundUnit(3, 3); + CPPUNIT_ASSERT( !g.isFreeForBuilding(3, 3) ); +} + +void MapQueryTest::testFreeForBuilding_WaterFails() +{ + GrassMap g; g.makeWater(3, 3); + // Buildings can never be placed on non-grass — canSwim is irrelevant here. + CPPUNIT_ASSERT( !g.isFreeForBuilding(3, 3) ); +} + +void MapQueryTest::testFreeForBuilding_SandFails() +{ + GrassMap g; g.makeSand(3, 3); + CPPUNIT_ASSERT( !g.isFreeForBuilding(3, 3) ); +} + +void MapQueryTest::testFreeForBuilding_RectAllGrassPasses() +{ + GrassMap g; + CPPUNIT_ASSERT( g.isFreeForBuilding(2, 2, 3, 3) ); +} + +void MapQueryTest::testFreeForBuilding_RectOneBadTileFails() +{ + GrassMap g; g.putBuilding(3, 3); + // 3x3 starting at (2,2) covers (3,3) — single bad tile fails the whole rect. + CPPUNIT_ASSERT( !g.isFreeForBuilding(2, 2, 3, 3) ); +} + +void MapQueryTest::testFreeForBuilding_RectGidTolerantSameGidPasses() +{ + GrassMap g; g.putBuilding(3, 3, /*gbid=*/42); + // gid-tolerant overload accepts tiles already occupied by gid=42. + CPPUNIT_ASSERT( g.isFreeForBuilding(2, 2, 3, 3, /*gid=*/42) ); +} + +void MapQueryTest::testFreeForBuilding_RectGidTolerantDifferentGidFails() +{ + GrassMap g; g.putBuilding(3, 3, /*gbid=*/42); + CPPUNIT_ASSERT( !g.isFreeForBuilding(2, 2, 3, 3, /*gid=*/99) ); +} + +// ---------------- isHardSpaceForGroundUnit ---------------- + +void MapQueryTest::testHardSpaceForGroundUnit_IgnoresUnit() +{ + GrassMap g; g.putGroundUnit(3, 3); + // HardSpace is "would be free if no unit were here" — so unit presence is OK. + CPPUNIT_ASSERT( g.isHardSpaceForGroundUnit(3, 3, false, kTeam0) ); + // Sanity: the Free variant rejects the same tile. + CPPUNIT_ASSERT( !g.isFreeForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testHardSpaceForGroundUnit_RessourceStillFails() +{ + GrassMap g; g.putRessource(3, 3); + CPPUNIT_ASSERT( !g.isHardSpaceForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testHardSpaceForGroundUnit_BuildingStillFails() +{ + GrassMap g; g.putBuilding(3, 3); + CPPUNIT_ASSERT( !g.isHardSpaceForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testHardSpaceForGroundUnit_WaterFailsWhenNotSwim() +{ + GrassMap g; g.makeWater(3, 3); + CPPUNIT_ASSERT( !g.isHardSpaceForGroundUnit(3, 3, false, kTeam0) ); +} + +void MapQueryTest::testHardSpaceForGroundUnit_ForbiddenStillFails() +{ + GrassMap g; g.setForbidden(3, 3, kTeam0); + CPPUNIT_ASSERT( !g.isHardSpaceForGroundUnit(3, 3, false, kTeam0) ); +} + +// ---------------- isHardSpaceForBuilding ---------------- + +void MapQueryTest::testHardSpaceForBuilding_IgnoresUnit() +{ + GrassMap g; g.putGroundUnit(3, 3); + CPPUNIT_ASSERT( g.isHardSpaceForBuilding(3, 3) ); + CPPUNIT_ASSERT( !g.isFreeForBuilding(3, 3) ); +} + +void MapQueryTest::testHardSpaceForBuilding_RessourceFails() +{ + GrassMap g; g.putRessource(3, 3); + CPPUNIT_ASSERT( !g.isHardSpaceForBuilding(3, 3) ); +} + +void MapQueryTest::testHardSpaceForBuilding_BuildingFails() +{ + GrassMap g; g.putBuilding(3, 3); + CPPUNIT_ASSERT( !g.isHardSpaceForBuilding(3, 3) ); +} + +void MapQueryTest::testHardSpaceForBuilding_NonGrassFails() +{ + GrassMap g; g.makeSand(3, 3); + CPPUNIT_ASSERT( !g.isHardSpaceForBuilding(3, 3) ); +} + +void MapQueryTest::testHardSpaceForBuilding_RectAllGrassPasses() +{ + GrassMap g; + CPPUNIT_ASSERT( g.isHardSpaceForBuilding(2, 2, 3, 3) ); +} + +void MapQueryTest::testHardSpaceForBuilding_RectGidTolerantSameGidPasses() +{ + GrassMap g; g.putBuilding(3, 3, /*gbid=*/42); + CPPUNIT_ASSERT( g.isHardSpaceForBuilding(2, 2, 3, 3, /*gid=*/42) ); +} + +void MapQueryTest::testHardSpaceForBuilding_RectGidTolerantDifferentGidFails() +{ + GrassMap g; g.putBuilding(3, 3, /*gbid=*/42); + CPPUNIT_ASSERT( !g.isHardSpaceForBuilding(2, 2, 3, 3, /*gid=*/99) ); +} + +// ---------------- local-team mirror (CS-546) ---------------- + +void MapQueryTest::testLocalTeam_DefaultsToSentinel() +{ + GrassMap g; + CPPUNIT_ASSERT_EQUAL( Map::NO_LOCAL_TEAM, g.getLocalTeam() ); +} + +void MapQueryTest::testLocalTeam_SetAndGet() +{ + GrassMap g; + g.setLocalTeam(3); + CPPUNIT_ASSERT_EQUAL( static_cast(3), g.getLocalTeam() ); + g.setLocalTeam(0); + CPPUNIT_ASSERT_EQUAL( static_cast(0), g.getLocalTeam() ); +} + +void MapQueryTest::testLocalTeam_SentinelValueIsMinusOne() +{ + // Pinned: sim sites that consult getLocalTeam() compare against teamNumber (>=0), + // so the sentinel must never collide with a real team index. -1 is the convention + // used elsewhere for "no team" (see Game::syncStep's localTeam parameter). + CPPUNIT_ASSERT_EQUAL( static_cast(-1), Map::NO_LOCAL_TEAM ); +} diff --git a/test/MapQueryTest.h b/test/MapQueryTest.h new file mode 100644 index 000000000..c39028403 --- /dev/null +++ b/test/MapQueryTest.h @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2026 glob2 contributors + +#pragma once + +#include + +// Characterization tests for the spatial-query predicates in MapQuery.cpp: +// isFreeFor*, isHardSpaceFor*. The fixture builds an 8x8 grass map and pokes +// individual tile state to exercise each (predicate × deny-reason) pair. +class MapQueryTest: public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( MapQueryTest ); + // isFreeForGroundUnit(x, y, canSwim, teamMask) + CPPUNIT_TEST( testFreeForGroundUnit_CleanGrassPasses ); + CPPUNIT_TEST( testFreeForGroundUnit_RessourceFails ); + CPPUNIT_TEST( testFreeForGroundUnit_BuildingFails ); + CPPUNIT_TEST( testFreeForGroundUnit_UnitFails ); + CPPUNIT_TEST( testFreeForGroundUnit_WaterFailsWhenNotSwim ); + CPPUNIT_TEST( testFreeForGroundUnit_WaterPassesWhenSwim ); + CPPUNIT_TEST( testFreeForGroundUnit_ForbiddenFailsWhenMaskMatches ); + CPPUNIT_TEST( testFreeForGroundUnit_ForbiddenPassesWhenMaskDoesNotMatch ); + + // isFreeForGroundUnitNoForbidden(x, y, canSwim) + CPPUNIT_TEST( testFreeForGroundUnitNoForbidden_IgnoresForbidden ); + CPPUNIT_TEST( testFreeForGroundUnitNoForbidden_StillBlocksBuilding ); + + // isFreeForBuilding(x, y) and rect variants + CPPUNIT_TEST( testFreeForBuilding_GrassPasses ); + CPPUNIT_TEST( testFreeForBuilding_RessourceFails ); + CPPUNIT_TEST( testFreeForBuilding_BuildingFails ); + CPPUNIT_TEST( testFreeForBuilding_UnitFails ); + CPPUNIT_TEST( testFreeForBuilding_WaterFails ); + CPPUNIT_TEST( testFreeForBuilding_SandFails ); + CPPUNIT_TEST( testFreeForBuilding_RectAllGrassPasses ); + CPPUNIT_TEST( testFreeForBuilding_RectOneBadTileFails ); + CPPUNIT_TEST( testFreeForBuilding_RectGidTolerantSameGidPasses ); + CPPUNIT_TEST( testFreeForBuilding_RectGidTolerantDifferentGidFails ); + + // isHardSpaceForGroundUnit(x, y, canSwim, me) + CPPUNIT_TEST( testHardSpaceForGroundUnit_IgnoresUnit ); + CPPUNIT_TEST( testHardSpaceForGroundUnit_RessourceStillFails ); + CPPUNIT_TEST( testHardSpaceForGroundUnit_BuildingStillFails ); + CPPUNIT_TEST( testHardSpaceForGroundUnit_WaterFailsWhenNotSwim ); + CPPUNIT_TEST( testHardSpaceForGroundUnit_ForbiddenStillFails ); + + // isHardSpaceForBuilding family + CPPUNIT_TEST( testHardSpaceForBuilding_IgnoresUnit ); + CPPUNIT_TEST( testHardSpaceForBuilding_RessourceFails ); + CPPUNIT_TEST( testHardSpaceForBuilding_BuildingFails ); + CPPUNIT_TEST( testHardSpaceForBuilding_NonGrassFails ); + CPPUNIT_TEST( testHardSpaceForBuilding_RectAllGrassPasses ); + CPPUNIT_TEST( testHardSpaceForBuilding_RectGidTolerantSameGidPasses ); + CPPUNIT_TEST( testHardSpaceForBuilding_RectGidTolerantDifferentGidFails ); + + // Local-team mirror (CS-546): Map carries the locally-displayed team identity + // so sim code can consult it without reaching into GameGUI. + CPPUNIT_TEST( testLocalTeam_DefaultsToSentinel ); + CPPUNIT_TEST( testLocalTeam_SetAndGet ); + CPPUNIT_TEST( testLocalTeam_SentinelValueIsMinusOne ); + CPPUNIT_TEST_SUITE_END(); + +public: + void testFreeForGroundUnit_CleanGrassPasses(); + void testFreeForGroundUnit_RessourceFails(); + void testFreeForGroundUnit_BuildingFails(); + void testFreeForGroundUnit_UnitFails(); + void testFreeForGroundUnit_WaterFailsWhenNotSwim(); + void testFreeForGroundUnit_WaterPassesWhenSwim(); + void testFreeForGroundUnit_ForbiddenFailsWhenMaskMatches(); + void testFreeForGroundUnit_ForbiddenPassesWhenMaskDoesNotMatch(); + + void testFreeForGroundUnitNoForbidden_IgnoresForbidden(); + void testFreeForGroundUnitNoForbidden_StillBlocksBuilding(); + + void testFreeForBuilding_GrassPasses(); + void testFreeForBuilding_RessourceFails(); + void testFreeForBuilding_BuildingFails(); + void testFreeForBuilding_UnitFails(); + void testFreeForBuilding_WaterFails(); + void testFreeForBuilding_SandFails(); + void testFreeForBuilding_RectAllGrassPasses(); + void testFreeForBuilding_RectOneBadTileFails(); + void testFreeForBuilding_RectGidTolerantSameGidPasses(); + void testFreeForBuilding_RectGidTolerantDifferentGidFails(); + + void testHardSpaceForGroundUnit_IgnoresUnit(); + void testHardSpaceForGroundUnit_RessourceStillFails(); + void testHardSpaceForGroundUnit_BuildingStillFails(); + void testHardSpaceForGroundUnit_WaterFailsWhenNotSwim(); + void testHardSpaceForGroundUnit_ForbiddenStillFails(); + + void testHardSpaceForBuilding_IgnoresUnit(); + void testHardSpaceForBuilding_RessourceFails(); + void testHardSpaceForBuilding_BuildingFails(); + void testHardSpaceForBuilding_NonGrassFails(); + void testHardSpaceForBuilding_RectAllGrassPasses(); + void testHardSpaceForBuilding_RectGidTolerantSameGidPasses(); + void testHardSpaceForBuilding_RectGidTolerantDifferentGidFails(); + + void testLocalTeam_DefaultsToSentinel(); + void testLocalTeam_SetAndGet(); + void testLocalTeam_SentinelValueIsMinusOne(); +}; diff --git a/test/MapQueryTestStubs.cpp b/test/MapQueryTestStubs.cpp new file mode 100644 index 000000000..ffb3bea5f --- /dev/null +++ b/test/MapQueryTestStubs.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2026 glob2 contributors + +// Stubs for symbols referenced by Map.o that the MapQuery test doesn't actually +// exercise. The Sector class is the worst offender: pulling in the real +// Sector.cpp drags in Bullet, GameEvent, Team::pushGameEvent, Building::kill, +// Unit::getRealArmor, and globalContainer — most of the game. These stubs +// satisfy the linker without any of that. +// +// None of these stubs are called at runtime by MapQueryTest. setSize() (which +// would construct a real Sector[]) and setGame() (which would call setGame on +// each Sector) are deliberately bypassed by the GrassMap test fixture. + +#include +#include +#include "Sector.h" +#include "render/GameAnimations.h" + +Sector::Sector(Game *) {} +Sector::~Sector(void) {} +void Sector::setGame(Game *) {} +void Sector::free(void) {} +#ifndef YOG_SERVER_ONLY +void Sector::step(void) {} +#endif +void Sector::save(GAGCore::OutputStream *) {} +bool Sector::load(GAGCore::InputStream *, Game *, Sint32) { return false; } + +#ifndef YOG_SERVER_ONLY +UnitDeathAnimation::UnitDeathAnimation(int x_, int y_, Team *t) + : x(x_), y(y_), ticksLeft(0), team(t) {} + +// Stub for Map::setGame's animations->resize() call. MapQueryTest never invokes +// setGame (the GrassMap fixture bypasses it), but Map.o's symbol is still linked. +void GameAnimations::resize(int) {} +#endif diff --git a/test/NetSendOrderDecodeTest.cpp b/test/NetSendOrderDecodeTest.cpp new file mode 100644 index 000000000..68e625b36 --- /dev/null +++ b/test/NetSendOrderDecodeTest.cpp @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Regression harness for BH-207: NetSendOrder::decodeData must reject a +// corrupt-or-attacker-supplied envelope whose `size` field overflows a sane +// cap, by throwing std::ios_base::failure *before* allocating any buffer. +// Pre-fix: the raw `new Uint8[size]` either escapes std::bad_alloc (which +// callers — loadReplay, retrieveOrder, getNetMessage — catch only as +// ios_base::failure and therefore miss) or wastes a multi-GB allocation +// before the generic "Couldn't decode" throw fires. Post-fix: an explicit +// size check rejects the envelope with a distinct ios_base::failure message +// citing the size cap. Both test cases are independent of which Order +// subclasses are linked — Order::getOrder and the Order/MiscOrder/NullOrder +// ctors are stubbed so only OrderMessages.cpp's behaviour is under test. + +#include +#include +#include +#include +#include +#include +#include "BinaryStream.h" +#include "StreamBackend.h" +#include "NetMessage.h" +#include "Order.h" +#include "OrderMessages.h" + +using namespace GAGCore; + +// --- Stubs --------------------------------------------------------------- +// +// Linking the real Order.cpp / OrderMisc.cpp would drag in every +// OrderCreate / OrderDelete / OrderModify… deserialize symbol through the +// switch in Order::getOrder. We don't need any of them: the bound-check +// fires before getOrder is called, and the happy path returns a NullOrder +// which has no payload-shaped state. +Order::Order(void) +{ + sender = ORDER_SENDER_NONE; + gameCheckSum = ORDER_CHECKSUM_NONE; +} +MiscOrder::MiscOrder() : Order() {} +NullOrder::NullOrder() : MiscOrder() {} + +// NetMessage::operator!= is the only non-pure virtual in NetMessage, so it +// anchors the vtable. Defining it here lets us link OrderMessages.cpp without +// pulling in NetMessage.cpp (whose getNetMessage switch would drag in every +// NetXxx subclass). +bool NetMessage::operator!=(const NetMessage& rhs) const +{ + return !(*this == rhs); +} + +// SHA1 is wired in this project by direct .c-into-.cpp inclusion (see +// YOGServerPasswordRegistry.cpp); the .h has no extern "C" wrapper, so +// libgag_server.a's BinaryOutputStream::write references C++-mangled +// SHA1Update/Init/Final names. Mirror the same trick here to provide the +// definitions without dragging YOGServerPasswordRegistry into the link. +#include "../gnupg/sha1.c" + +std::shared_ptr Order::getOrder(const Uint8 *netData, int netDataLength, Uint32 /*versionMinor*/) +{ + if (netDataLength < 1 || netData == NULL) + return std::shared_ptr(); + if (netData[0] == ORDER_NULL) + return std::shared_ptr(new NullOrder()); + // Anything else: signal "couldn't decode" so decodeData throws. + return std::shared_ptr(); +} + +namespace { + +int g_passed = 0; +int g_failed = 0; + +void check(bool cond, const char* tc, const char* what) +{ + if (cond) + { + ++g_passed; + std::printf(" PASS %s — %s\n", tc, what); + } + else + { + ++g_failed; + std::printf(" FAIL %s — %s\n", tc, what); + } +} + +// Build a BinaryInputStream containing one NetSendOrder envelope: +// Uint32 size | size bytes payload | Uint8 sender | Uint32 checksum +// Mirrors the testSerialize() copy-via-fresh-backend pattern in +// NetTestSuite.cpp so we don't have to chase ownership of the writer's +// backend after BinaryOutputStream's dtor runs. Caller owns the returned +// stream and is responsible for `delete`. +BinaryInputStream* makeStream(Uint32 declaredSize, + const Uint8* payload, + size_t payloadSize, + Uint8 sender, + Uint32 checksum) +{ + MemoryStreamBackend* writeBackend = new MemoryStreamBackend; + MemoryStreamBackend* readBackend = nullptr; + { + BinaryOutputStream out(writeBackend); + out.writeEnterSection("NetSendOrder"); + out.writeUint32(declaredSize, "size"); + if (payloadSize > 0) + out.write(payload, payloadSize, "data"); + out.writeUint8(sender, "sender"); + out.writeUint32(checksum, "checksum"); + out.writeLeaveSection(); + + const size_t totalLen = writeBackend->getPosition(); + readBackend = new MemoryStreamBackend(writeBackend->getBuffer(), totalLen); + } + // `out` is destructed here, freeing writeBackend. readBackend owns its own copy. + readBackend->seekFromStart(0); + return new BinaryInputStream(readBackend); +} + +// TC1 — Oversized `size` field must produce ios_base::failure (not bad_alloc, +// not any other exception type), thrown by the bound-check before allocation. +// Distinguishing pre-fix from post-fix: the post-fix message cites the cap; +// the pre-fix "Couldn't decode data stream to an Order" message does not. +void tc1_rejectsOversizedSize() +{ + // 1 MiB + 1 — just over the documented cap; small enough that pre-fix's + // `new Uint8[size]` won't OOM the test runner, large enough to be + // unambiguously rejected post-fix. + const Uint32 oversized = (1u << 20) + 1u; + BinaryInputStream* stream = makeStream(oversized, nullptr, 0, 0, 0); + + enum class Outcome { NoThrow, IosFailure, BadAlloc, Other }; + Outcome outcome = Outcome::NoThrow; + std::string what; + + try + { + NetSendOrder msg; + msg.decodeData(stream); + } + catch (const std::ios_base::failure& e) + { + outcome = Outcome::IosFailure; + what = e.what(); + } + catch (const std::bad_alloc&) + { + outcome = Outcome::BadAlloc; + } + catch (...) + { + outcome = Outcome::Other; + } + + delete stream; + + check(outcome != Outcome::BadAlloc, "TC1", + "no std::bad_alloc escapes decodeData (pre-fix would on a 64-bit host without overcommit)"); + check(outcome != Outcome::Other, "TC1", + "no unexpected exception type escapes"); + check(outcome == Outcome::IosFailure, "TC1", + "ios_base::failure thrown on oversized size"); + check(what.find("NetSendOrder size") != std::string::npos, "TC1", + "rejection message cites the size cap (proves bound-check fired, not the downstream 'bad format' throw)"); +} + +// TC2 — Well-formed minimal envelope (ORDER_NULL payload, size=1) round-trips. +// Regression coverage that the new bound-check doesn't over-reject valid input. +void tc2_acceptsValidNullOrder() +{ + const Uint8 payload[1] = { ORDER_NULL }; + const Uint8 senderIn = 7; + const Uint32 checksumIn = 0x12345678u; + BinaryInputStream* stream = makeStream(1, payload, 1, senderIn, checksumIn); + + bool decoded = false; + Uint8 orderType = 0xFF; + Uint8 senderOut = 0; + Uint32 checksumOut = 0; + std::string err; + + try + { + NetSendOrder msg; + msg.decodeData(stream); + auto order = msg.getOrder(); + if (order) + { + decoded = true; + orderType = order->getOrderType(); + senderOut = order->sender; + checksumOut = order->gameCheckSum; + } + } + catch (const std::exception& e) { err = e.what(); } + catch (...) { err = "non-std exception"; } + + delete stream; + + check(decoded, "TC2", err.empty() + ? "valid envelope decoded without exception" + : ("expected no throw but got: " + err).c_str()); + check(orderType == ORDER_NULL, "TC2", "decoded order is NullOrder"); + check(senderOut == senderIn, "TC2", "sender round-trips"); + check(checksumOut == checksumIn, "TC2", "checksum round-trips"); +} + +} // namespace + +int main() +{ + std::printf("NetSendOrderDecodeTest — BH-207 regression\n"); + tc1_rejectsOversizedSize(); + tc2_acceptsValidNullOrder(); + std::printf("\n%d passed, %d failed\n", g_passed, g_failed); + return g_failed == 0 ? 0 : 1; +} diff --git a/test/OrderAlterateAreaTest.cpp b/test/OrderAlterateAreaTest.cpp new file mode 100644 index 000000000..842606bba --- /dev/null +++ b/test/OrderAlterateAreaTest.cpp @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Regression harness for BH-195: OrderAlterateArea::setData must reject +// malformed packets where the header-declared bitmap area doesn't match the +// payload length, or where the dimensions would overflow / exceed the brush +// side cap. Pre-fix tree: TC1, TC2 pass; TC3-TC7 fail (or crash in release). +// Post-fix tree: all TCs pass and the binary exits 0. + +#include +#include +#include "Order.h" +#include "Marshaling.h" + +// Minimal stub for the Order base-class constructor. Linking the real +// Order.cpp would drag in OrderCreate/OrderDelete/MessageOrder/etc. +// deserialize symbols (via Order::getOrder's switch) which we don't need. +Order::Order(void) +{ + sender = ORDER_SENDER_NONE; + gameCheckSum = ORDER_CHECKSUM_NONE; +} + +namespace { + +int g_passed = 0; +int g_failed = 0; + +void check(bool cond, const char* tc, const char* what) +{ + if (cond) + { + ++g_passed; + std::printf(" PASS %s — %s\n", tc, what); + } + else + { + ++g_failed; + std::printf(" FAIL %s — %s\n", tc, what); + } +} + +void writeHeader(Uint8* buf, Sint16 minX, Sint16 minY, Sint16 maxX, Sint16 maxY) +{ + addUint8(buf, 0, 0); // teamNumber + addUint8(buf, 0, 1); // type + addSint16(buf, 0, 2); // centerX + addSint16(buf, 0, 4); // centerY + addSint16(buf, minX, 6); + addSint16(buf, minY, 8); + addUint16(buf, static_cast(maxX), 10); + addUint16(buf, static_cast(maxY), 12); +} + +// TC1 — well-formed 10x10 brush is accepted and round-trips. +void tc1_happyPath() +{ + // sideX=sideY=10, bits=100, bytes = bitToByte(100) = ceil(100/8) = 13 + Uint8 buf[ALTERATE_AREA_HEADER_BYTES + 13] = {0}; + writeHeader(buf, 45, 45, 55, 55); + + OrderAlterateForbidden order; + bool ok = order.setData(buf, sizeof(buf), 0); + check(ok, "TC1", "well-formed 10x10 brush accepted"); + if (ok) + { + check(order.maxX - order.minX == 10, "TC1", "sideX round-trips"); + check(order.maxY - order.minY == 10, "TC1", "sideY round-trips"); + } +} + +// TC2 — dataLength below header size is rejected. +void tc2_undersizedHeader() +{ + Uint8 buf[ALTERATE_AREA_HEADER_BYTES - 1] = {0}; + OrderAlterateForbidden order; + bool ok = order.setData(buf, sizeof(buf), 0); + check(!ok, "TC2", "dataLength<14 rejected"); +} + +// TC3 — maxX < minX (negative side) is rejected. +void tc3_negativeSide() +{ + Uint8 buf[ALTERATE_AREA_HEADER_BYTES] = {0}; + writeHeader(buf, 60, 45, 55, 55); // sideX = -5 + OrderAlterateForbidden order; + bool ok = order.setData(buf, sizeof(buf), 0); + check(!ok, "TC3", "negative side rejected"); +} + +// TC4 — side exceeding ORDER_AREA_BRUSH_MAX_SIDE is rejected. +void tc4_sideOverCap() +{ + // sideX=1000, sideY=10 → bits=10000, bytes = bitToByte(10000) = 1250. + // Allocate a buffer large enough that, if the fix is missing in release, + // the OOB read inside BitArray::deserialize doesn't crash the harness + // before we can record the FAIL. + constexpr int payloadLen = 1250; + Uint8 buf[ALTERATE_AREA_HEADER_BYTES + payloadLen] = {0}; + writeHeader(buf, 0, 0, 1000, 10); + OrderAlterateForbidden order; + bool ok = order.setData(buf, sizeof(buf), 0); + check(!ok, "TC4", "side > ORDER_AREA_BRUSH_MAX_SIDE rejected"); +} + +// TC5 — Sint16-extreme header (sideX=sideY=65535) is rejected before the +// signed-int multiply that would otherwise be UB. +void tc5_overflowAttack() +{ + Uint8 buf[ALTERATE_AREA_HEADER_BYTES] = {0}; + writeHeader(buf, -32768, -32768, 32767, 32767); + OrderAlterateForbidden order; + bool ok = order.setData(buf, sizeof(buf), 0); + check(!ok, "TC5", "overflow-attack header rejected without UB"); +} + +// TC6 — 10x10 header claims 13 bitmap bytes but only 5 are provided. +// Pre-fix: BitArray::deserialize std::copies past the end of the buffer. +void tc6_payloadTooShort() +{ + Uint8 buf[ALTERATE_AREA_HEADER_BYTES + 5] = {0}; + writeHeader(buf, 0, 0, 10, 10); + OrderAlterateForbidden order; + bool ok = order.setData(buf, sizeof(buf), 0); + check(!ok, "TC6", "payload-too-short rejected (was OOB read)"); +} + +// TC7 — 2x2 header claims 1 bitmap byte but 100 are provided. +void tc7_payloadTooLong() +{ + Uint8 buf[ALTERATE_AREA_HEADER_BYTES + 100] = {0}; + writeHeader(buf, 0, 0, 2, 2); + OrderAlterateForbidden order; + bool ok = order.setData(buf, sizeof(buf), 0); + check(!ok, "TC7", "payload-too-long rejected"); +} + +} // namespace + +int main() +{ + std::printf("OrderAlterateAreaTest — BH-195 regression\n"); + tc1_happyPath(); + tc2_undersizedHeader(); + tc3_negativeSide(); + tc4_sideOverCap(); + tc5_overflowAttack(); + tc6_payloadTooShort(); + tc7_payloadTooLong(); + std::printf("\n%d passed, %d failed\n", g_passed, g_failed); + return g_failed == 0 ? 0 : 1; +} diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..c7de14a93 --- /dev/null +++ b/test/README.md @@ -0,0 +1,48 @@ +# glob2/test/ + +CppUnit-based test fixtures for the C++ codebase. Built as part of the SCons build; run the resulting `TestsRunner` (and `WinningConditionsHarness`) binaries after `scons -j16`. + +## Map subclass test pattern + +Pattern used by `MapQueryTest.cpp` (commit `2d42c340`). Lets you write tests against `Map`'s predicates with a minimal link surface — no `globalContainer`, no real `Sector` array, no transitive pull of `Bullet` / `Team` / `Building` / `Unit` into the test binary. + +### The fixture: subclass `Map`, bypass `setSize` + +`Map::setSize()` does `new Sector[sizeSector]`, which forces the linker to resolve every `Sector` method (vtable + transitively `Bullet`, `Team::pushGameEvent`, `Building::kill`, `Unit::getRealArmor`, `globalContainer`, ...). Bypass it: + +```cpp +struct GrassMap : Map { + GrassMap() { + wDec = 3; hDec = 3; w = 8; h = 8; // 8x8 map + wMask = 7; hMask = 7; + size = 64; + cases.assign(64, Case{}); // default: terrain=0 (grass), no bldg/unit + // arraysBuilt stays false, so clear() takes the else-branch + } + ~GrassMap() { + // Map::clear()'s else-branch asserts these are 0 before letting Map::~Map() proceed + w = h = wMask = hMask = wDec = hDec = 0; + size = 0; + } +}; +``` + +`cases`, `w` / `h` / `wMask` / `hMask` / `wDec` / `hDec` are all public on `Map`. `arraysBuilt` is also public. Default-constructed `Case` is "grass tile, no occupant, terrain=0, ressource.type=NO_RES_TYPE". + +### Stubs for `Sector` + +Provide a `*TestStubs.cpp` (e.g. `MapQueryTestStubs.cpp`) with empty bodies for `Sector::Sector(Game*)`, `Sector::~Sector`, `Sector::setGame`, `Sector::step`, `Sector::save`, `Sector::load`, `Sector::free`, and `UnitDeathAnimation::UnitDeathAnimation`. `Map.o`'s compiled `setSize` / `setGame` reference `Sector` symbols even though the test never calls them. ~30 lines of stubs avoid pulling all of `Sector.cpp`'s real deps. + +### `SConstruct` surgery + +The predicate test build needed these include paths beyond what existing tests had: `../src/building`, `../src/game/entities`, `../src/team`, `../src/unit`, `../src/gui`, `../src/net`, `../src/net/irc`, `../src/net/message`, `../src/yog`, `../libusl/src`. Linked sources for the predicate test: `Map.cpp`, `MapQuery.cpp`, `MapTerrain.cpp`, `BitArray.cpp`, `Utilities.cpp`, `building/BuildingUtils.cpp`, `unit/UnitUtils.cpp`, plus the stubs. + +### Terrain encoding for tests + +To poke `cases[i].terrain` directly (`regenerateMap` is protected): grass < 16, sand 128–143, water 256–271. See `Map.h:336-361`. + +### When to use this pattern + +- Testing other Map behaviors (`doesUnitTouch*`, `doesPosTouch*`, `setClearingArea*`, `markImmobileUnit`, etc.). +- Adding regression tests around any Map state mutator before refactoring it. +- **Don't use** for behaviors that genuinely need real `Game` / `Team` / `Unit` / `Building` wiring (e.g. `doesUnitTouchEnemy` reaches into `game->teams[]->myBuildings[]`) — those need either a different stub set or a refactor to decouple first. diff --git a/test/SConstruct b/test/SConstruct index e7bddfa23..5ed5bbcdb 100644 --- a/test/SConstruct +++ b/test/SConstruct @@ -2,12 +2,35 @@ import sys env = Environment() -if sys.platform == "linux2": - ccflags = '-Wall -ansi -g' -else: - ccflags = '/EHsc /MD /GR' +# Shared include paths so tests can compile against game headers. +# echo/Echo.h transitively pulls in Map.h / Player.h / Team.h / etc., +# which need libgag and SDL2 headers visible at parse time even when the +# test itself never references those types. +common_cpppath = ['..', '../src', '../src/AI', + '../src/building', '../src/game/entities', + '../src/gui', '../src/team', '../src/unit', + '../src/net', '../src/net/irc', '../src/net/message', + '../src/yog', + '../src/map', '../src/map/edit', '../src/map/generator', + '../src/map/gradient', '../src/map/io', '../src/map/pathfind', + '../libgag/include', '../libusl/src'] +common_defines = ['HAVE_CONFIG_H', '_THREAD_SAFE'] -env.Append(CCFLAGS = ccflags) +if sys.platform.startswith("linux"): + env.Append(CCFLAGS = Split('-Wall -g -std=gnu++17')) + env.Append(CPPPATH = common_cpppath + ['/usr/include/SDL2']) + env.Append(CPPDEFINES = common_defines) +elif sys.platform == "darwin": + env.Append(CCFLAGS = Split('-Wall -g -std=gnu++17')) + env.Append(CPPPATH = common_cpppath + [ + '/opt/homebrew/include', + '/opt/homebrew/include/SDL2', + '/usr/local/include', + '/usr/local/include/SDL2']) + env.Append(LIBPATH = ['/opt/homebrew/lib', '/usr/local/lib']) + env.Append(CPPDEFINES = common_defines) +else: + env.Append(CCFLAGS = Split('/EHsc /MD /GR')) sources = Split(""" TestsRunner.cpp @@ -17,6 +40,22 @@ PerlinNoiseTest.cpp HelloWorldTest.cpp +GradientBFSTest.cpp +../src/ai/echo/GradientBFS.cpp + +MapQueryTest.cpp +MapQueryTestStubs.cpp +../src/map/Map.cpp +../src/map/MapQuery.cpp +../src/map/MapTerrain.cpp +../src/BitArray.cpp +../src/Utilities.cpp +../src/building/BuildingUtils.cpp +../src/unit/UnitUtils.cpp + +GameMusicControllerTest.cpp +../src/gui/GameMusicController.cpp + natsort/NatSortTest.cpp """) @@ -25,3 +64,102 @@ env.Append(LIBS=['cppunit']) env.Program( sources ) + +# Standalone behaviour-equivalence harness for WinningConditions.cpp. +# Emits a deterministic golden text stream covering every WC predicate +# (Death/Allies/Prestige/Script/OpponentsDefeated). To verify a refactor of +# WinningConditions.cpp is behaviour-preserving, run on the cleaned-up tree, +# save stdout, then `git stash` / re-run / diff. Doesn't use cppunit. +harness_sources = Split(""" +WinningConditionsHarness.cpp +WinningConditionsTestStubs.cpp +../src/WinningConditions.cpp +""") + +env.Program( target = 'WinningConditionsHarness', source = harness_sources ) + + +# Standalone behaviour-equivalence harness for src/Campaign.cpp::load. +# Verifies that load() correctly distinguishes valid campaigns from missing, +# empty, garbage, and bogus-version files. Pre-fix tree silently returns true +# for the four broken cases; post-fix tree returns false. Self-contained: +# synthesizes its own /tmp fixture files. Links libgag.a (built by the main +# scons) for FileManager / TextStream / Toolkit; a small stubs file covers +# the unused glob2NameToFilename symbol that Campaign::save references. +campaign_env = env.Clone() +# Use the server-mode libgag (libgag_server.a) so we don't drag in OpenGL / +# SDL_ttf / SDL_image / fribidi etc. Campaign loading only needs FileManager +# / Toolkit / TextStream / StreamBackend, all of which are server-safe. +campaign_env.Append(CPPDEFINES = ['YOG_SERVER_ONLY']) +campaign_env.Append(LIBPATH = ['../build/libgag/src']) +campaign_env.Append(LIBS = ['gag_server', 'z']) +if sys.platform == "darwin" or sys.platform.startswith("linux"): + campaign_env.ParseConfig("pkg-config sdl2 --cflags --libs") + +campaign_sources = Split(""" +CampaignLoadHarness.cpp +CampaignLoadTestStubs.cpp +../src/Campaign.cpp +""") + +campaign_env.Program( target = 'CampaignLoadHarness', source = campaign_sources ) + + +# Standalone regression harness for CS-051 (CampaignMenuScreen selection +# index/name divergence). Builds a non-linear-unlock campaign in memory, +# proves the old `campaign.getMap(displayedIndex)` algorithm picks the +# wrong map, and proves `Campaign::findUnlockedMap(name)` picks the right +# one. Pre-fix tree fails to LINK (helper does not exist); post-fix tree +# builds and exits 0. Reuses the same env / libgag_server.a / stubs as +# CampaignLoadHarness because Campaign.cpp's link surface is identical. +campaign_sel_sources = Split(""" +CampaignSelectionHarness.cpp +CampaignLoadTestStubs.cpp +../src/Campaign.cpp +""") + +campaign_env.Program( target = 'CampaignSelectionHarness', source = campaign_sel_sources ) + + +# Standalone regression harness for BH-195 (OrderAlterateArea::setData +# OOB read on malformed bitmap payload). Builds synthetic Uint8 buffers +# and checks that setData rejects them. Compiled with YOG_SERVER_ONLY so +# OrderModify.cpp's BrushAccumulator constructor is stripped, removing +# the Brush/Map link dependency — only OrderModify.o + BitArray.o are +# needed because the other Order classes in OrderModify.cpp are pure +# byte marshalers with no Game/Team/Unit references. +alterate_env = env.Clone() +alterate_env.Append(CPPDEFINES = ['YOG_SERVER_ONLY']) +if sys.platform == "darwin" or sys.platform.startswith("linux"): + alterate_env.ParseConfig("pkg-config sdl2 --cflags --libs") + +# Give the YOG_SERVER_ONLY builds of OrderModify.cpp / BitArray.cpp unique +# object names so they don't collide with the main-env builds used by +# TestsRunner above (different -D flags → different actions). +alterate_objs = [ + alterate_env.Object('OrderAlterateAreaTest.o', 'OrderAlterateAreaTest.cpp'), + alterate_env.Object('OrderModify-alterate.o', '../src/OrderModify.cpp'), + alterate_env.Object('BitArray-alterate.o', '../src/BitArray.cpp'), +] +alterate_env.Program( target = 'OrderAlterateAreaTest', source = alterate_objs ) + + +# Standalone regression harness for BH-207 (NetSendOrder::decodeData allocating +# a multi-GB Uint8[] from an attacker-supplied size and letting std::bad_alloc +# escape, while callers — loadReplay / retrieveOrder / getNetMessage — only +# catch std::ios_base::failure). The harness stubs Order::getOrder so it +# doesn't need to link the full Order class hierarchy; it just needs +# OrderMessages.cpp + libgag_server.a (for BinaryStream + MemoryStreamBackend). +netsend_env = env.Clone() +netsend_env.Append(CPPDEFINES = ['YOG_SERVER_ONLY']) +netsend_env.Append(LIBPATH = ['../build/libgag/src']) +netsend_env.Append(LIBS = ['gag_server', 'z']) +if sys.platform == "darwin" or sys.platform.startswith("linux"): + netsend_env.ParseConfig("pkg-config sdl2 --cflags --libs") + +netsend_objs = [ + netsend_env.Object('NetSendOrderDecodeTest.o', 'NetSendOrderDecodeTest.cpp'), + netsend_env.Object('OrderMessages-netsend.o', '../src/net/message/OrderMessages.cpp'), +] +netsend_env.Program( target = 'NetSendOrderDecodeTest', source = netsend_objs ) + diff --git a/test/WinningConditionsHarness.cpp b/test/WinningConditionsHarness.cpp new file mode 100644 index 000000000..4c087f2c9 --- /dev/null +++ b/test/WinningConditionsHarness.cpp @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2026 glob2 contributors + +// Behaviour-equivalence harness for src/WinningConditions.cpp. +// +// The five WinningCondition predicates (hasTeamWon/hasTeamLost on Death, +// Allies, Prestige, Script, OpponentsDefeated) read only a small slice of +// game state: +// Team: me, allies, prestige, isAlive, hasWon, hasLost +// Game: mapHeader.numberOfTeams (via getNumberOfTeams), totalPrestige, +// prestigeToReach, sgslScript (hasTeamWon/hasTeamLost only). +// +// Constructing real Team objects pulls in Unit/Building/Race; constructing +// a real Game pulls in globalContainer / replayWriter / SGSL / Map. None of +// that is needed for these predicates. The harness therefore allocates raw +// aligned storage for one Game and N Teams and writes only the fields the +// predicates consult. The storage is never destructed -- non-trivial member +// destructors for std::list/std::map etc. on Team and Game are skipped, and +// the program leaks the storage at exit. WinningConditionsTestStubs.cpp +// supplies the few non-inline methods WC.cpp invokes (MapHeader getters and +// the MapScriptSGSL hooks). +// +// The output is a deterministic stream of one-line records. To verify the +// recent cleanup of WinningConditions.cpp is behaviour-preserving, run the +// harness on the cleaned-up source, save stdout, then `git stash` the WC +// changes, rebuild, run, and `diff` the two outputs. Identical output = +// equivalent behaviour. + +#include "WinningConditions.h" +#include "Game.h" +#include "Team.h" +#include "MapHeader.h" +#include "SGSL.h" + +#include +#include +#include +#include + +namespace harness { + // Defined in WinningConditionsTestStubs.cpp -- the SGSL stub reads these. + extern bool sgslTeamWon[32]; + extern bool sgslTeamLost[32]; +} + +namespace { + +constexpr int kMaxTeams = 4; + +alignas(Game) unsigned char gameStorage[sizeof(Game)]; +alignas(Team) unsigned char teamStorage[kMaxTeams][sizeof(Team)]; + +Game* g() { return reinterpret_cast(gameStorage); } +Team* T(int i) { return reinterpret_cast(teamStorage[i]); } + +void clearAll() +{ + std::memset(gameStorage, 0, sizeof(gameStorage)); + std::memset(teamStorage, 0, sizeof(teamStorage)); + std::memset(harness::sgslTeamWon, 0, sizeof(harness::sgslTeamWon)); + std::memset(harness::sgslTeamLost, 0, sizeof(harness::sgslTeamLost)); +} + +void setupTeams(int n) +{ + g()->mapHeader.setNumberOfTeams(n); + g()->totalPrestige = 0; + g()->prestigeToReach = 0; + for (int i = 0; i < n; ++i) + { + g()->teams[i] = T(i); + T(i)->me = 1u << i; + T(i)->allies = 0; + T(i)->prestige = 0; + T(i)->isAlive = true; + T(i)->hasWon = false; + T(i)->hasLost = false; + } +} + +// Mutually ally every team whose bit is set in `mask` -- each such team's +// `allies` becomes the OR of all those teams' `me` masks. +void mutualAlliances(int n, Uint32 mask) +{ + Uint32 m = 0; + for (int i = 0; i < n; ++i) + if (mask & (1u << i)) m |= T(i)->me; + for (int i = 0; i < n; ++i) + if (mask & (1u << i)) T(i)->allies = m; +} + +void emitWonLost(const char* tag, WinningCondition& cond, int n) +{ + for (int t = 0; t < n; ++t) + { + const bool w = cond.hasTeamWon(t, g()); + const bool l = cond.hasTeamLost(t, g()); + std::printf(" %s team=%d won=%d lost=%d\n", tag, t, w ? 1 : 0, l ? 1 : 0); + } +} + +// ---------------- Death ---------------- +void testDeath() +{ + constexpr int N = 3; + for (unsigned aliveMask = 0; aliveMask < (1u << N); ++aliveMask) + { + clearAll(); + setupTeams(N); + for (int i = 0; i < N; ++i) + T(i)->isAlive = (aliveMask & (1u << i)) != 0; + std::printf("Death/aliveMask=0x%x\n", aliveMask); + WinningConditionDeath wc; + emitWonLost("Death", wc, N); + } +} + +// ---------------- Allies ---------------- +void testAllies() +{ + constexpr int N = 3; + // All 8 alliance partitions on 3 teams (treated as mutual-ally subsets; + // 0b000 = no alliances, 0b011 = {0,1} mutually allied, etc.). Self-only + // alliances (one bit) reduce to "no allies", which is fine. + for (Uint32 ally = 0; ally < (1u << N); ++ally) + { + // hasWon flag on -1 (none), 0, 1, or 2. + for (int winner = -1; winner < N; ++winner) + { + clearAll(); + setupTeams(N); + mutualAlliances(N, ally); + if (winner >= 0) T(winner)->hasWon = true; + std::printf("Allies/allyMask=0x%x winner=%d\n", ally, winner); + WinningConditionAllies wc; + emitWonLost("Allies", wc, N); + } + } + + // One-way alliances: team 0 lists team 1 as an ally, but not vice versa. + // Must NOT count as mutual; team 0 should not win even if team 1 has won. + { + clearAll(); + setupTeams(N); + T(0)->allies = T(0)->me | T(1)->me; + T(1)->allies = T(1)->me; + T(2)->allies = T(2)->me; + T(1)->hasWon = true; + std::printf("Allies/oneWay 0->1, 1 wins\n"); + WinningConditionAllies wc; + emitWonLost("Allies", wc, N); + } +} + +// ---------------- Prestige ---------------- +void testPrestige() +{ + constexpr int N = 3; + struct Case + { + const char* tag; + int totalPrestige; + int prestigeToReach; + std::array teamPrestige; + }; + static const Case cases[] = { + {"all-zero-belowGate", 0, 100, {0, 0, 0}}, + {"all-zero-atGate", 100, 100, {0, 0, 0}}, + {"belowGate-noTie", 50, 100, {10, 30, 10}}, + {"atGate-uniqueMax", 100, 100, {10, 30, 10}}, + {"atGate-tieAtTop", 100, 100, {30, 30, 10}}, + {"atGate-allTied", 100, 100, {25, 25, 25}}, + {"aboveGate-uniqueMax", 200, 100, {50, 100, 25}}, + {"aboveGate-tieAtTop", 200, 100, {100, 100, 25}}, + {"negativePrestige", 100, 100, {-5, 0, -10}}, + }; + for (const auto& c : cases) + { + clearAll(); + setupTeams(N); + g()->totalPrestige = c.totalPrestige; + g()->prestigeToReach = c.prestigeToReach; + for (int i = 0; i < N; ++i) T(i)->prestige = c.teamPrestige[i]; + std::printf("Prestige/%s total=%d gate=%d prestiges=[%d,%d,%d]\n", + c.tag, c.totalPrestige, c.prestigeToReach, + c.teamPrestige[0], c.teamPrestige[1], c.teamPrestige[2]); + WinningConditionPrestige wc; + emitWonLost("Prestige", wc, N); + } +} + +// ---------------- Script ---------------- +void testScript() +{ +#ifndef YOG_SERVER_ONLY + constexpr int N = 3; + for (unsigned wMask = 0; wMask < (1u << N); ++wMask) + { + for (unsigned lMask = 0; lMask < (1u << N); ++lMask) + { + clearAll(); + setupTeams(N); + for (int i = 0; i < N; ++i) + { + harness::sgslTeamWon[i] = (wMask & (1u << i)) != 0; + harness::sgslTeamLost[i] = (lMask & (1u << i)) != 0; + } + std::printf("Script/wonMask=0x%x lostMask=0x%x\n", wMask, lMask); + WinningConditionScript wc; + emitWonLost("Script", wc, N); + } + } +#else + std::printf("Script/skipped (YOG_SERVER_ONLY)\n"); +#endif +} + +// ---------------- OpponentsDefeated ---------------- +void testOpponentsDefeated() +{ + constexpr int N = 3; + for (Uint32 ally = 0; ally < (1u << N); ++ally) + { + for (unsigned lostMask = 0; lostMask < (1u << N); ++lostMask) + { + clearAll(); + setupTeams(N); + mutualAlliances(N, ally); + for (int i = 0; i < N; ++i) + T(i)->hasLost = (lostMask & (1u << i)) != 0; + std::printf("OpponentsDefeated/allyMask=0x%x lostMask=0x%x\n", ally, lostMask); + WinningConditionOpponentsDefeated wc; + emitWonLost("OppDef", wc, N); + } + } + + // One-way alliance: team 0 lists team 1 as ally, team 1 does not. From + // team 0's perspective team 1 must still count as an enemy (mutual check + // fails) -- so if team 1 is undefeated, team 0 should NOT win. + { + clearAll(); + setupTeams(N); + T(0)->allies = T(0)->me | T(1)->me; + T(1)->allies = T(1)->me; + T(2)->allies = T(2)->me; + T(2)->hasLost = true; + std::printf("OpponentsDefeated/oneWay 0->1, 2 lost, 1 alive\n"); + WinningConditionOpponentsDefeated wc; + emitWonLost("OppDef", wc, N); + } +} + +// ---------------- factory dispatch ---------------- +// Walks getDefaultWinningConditions(), reports the type of each condition in +// listed order. Touches the cleaned-up factory only via getType(); a +// regression in the type-tag dispatch would surface as a different list. +void testFactoryOrder() +{ + auto wcs = WinningCondition::getDefaultWinningConditions(); + int idx = 0; + for (const auto& wc : wcs) + { + std::printf("DefaultList/%d type=%d\n", idx++, static_cast(wc->getType())); + } +} + +} // namespace + +int main(int /*argc*/, char* /*argv*/[]) +{ + std::printf("# WinningConditionsHarness golden output\n"); + testFactoryOrder(); + testDeath(); + testAllies(); + testPrestige(); + testScript(); + testOpponentsDefeated(); + return 0; +} diff --git a/test/WinningConditionsTestStubs.cpp b/test/WinningConditionsTestStubs.cpp new file mode 100644 index 000000000..ec1aac112 --- /dev/null +++ b/test/WinningConditionsTestStubs.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (C) 2026 glob2 contributors + +// Stubs for the WinningConditionsHarness. WinningConditions.cpp's predicates +// reach outside the WC translation unit only via: +// - MapHeader::getNumberOfTeams() const (read by every WC loop) +// - MapScriptSGSL::hasTeamWon(unsigned) (Script only) +// - MapScriptSGSL::hasTeamLost(unsigned) (Script only) +// +// The harness itself also calls: +// - MapHeader::setNumberOfTeams(Sint32) (to size the team table) +// +// Linking the real MapHeader.cpp / SGSL.cpp would drag in Game / Map / +// FileManager / globalContainer / etc.; instead these inline-style stubs +// satisfy the linker without any of that. WC predicates are otherwise pure +// field readers, so direct member access on the harness's raw storage is +// enough. + +#include "MapHeader.h" +#include "SGSL.h" +#include "Team.h" + +Sint32 MapHeader::getNumberOfTeams() const +{ + return numberOfTeams; +} + +void MapHeader::setNumberOfTeams(Sint32 teamNum) +{ + numberOfTeams = teamNum; +} + +namespace harness { + bool sgslTeamWon[Team::MAX_COUNT] = {}; + bool sgslTeamLost[Team::MAX_COUNT] = {}; +} + +bool MapScriptSGSL::hasTeamWon(unsigned teamNumber) const +{ + return teamNumber < Team::MAX_COUNT && harness::sgslTeamWon[teamNumber]; +} + +bool MapScriptSGSL::hasTeamLost(unsigned teamNumber) const +{ + return teamNumber < Team::MAX_COUNT && harness::sgslTeamLost[teamNumber]; +} diff --git a/tests/baselines/cpp-refactor.replay b/tests/baselines/cpp-refactor.replay new file mode 100644 index 000000000..8e47d38fe Binary files /dev/null and b/tests/baselines/cpp-refactor.replay differ diff --git a/tests/baselines/cross-replay.checksums b/tests/baselines/cross-replay.checksums new file mode 100644 index 000000000..638616318 Binary files /dev/null and b/tests/baselines/cross-replay.checksums differ diff --git a/tests/baselines/cross-replay.replay b/tests/baselines/cross-replay.replay new file mode 100644 index 000000000..5ade1efc5 Binary files /dev/null and b/tests/baselines/cross-replay.replay differ diff --git a/tests/baselines/gradient/gd-archipelago.replay b/tests/baselines/gradient/gd-archipelago.replay new file mode 100644 index 000000000..8fdc36fe3 Binary files /dev/null and b/tests/baselines/gradient/gd-archipelago.replay differ diff --git a/tests/baselines/gradient/gd-bigarena-long.replay b/tests/baselines/gradient/gd-bigarena-long.replay new file mode 100644 index 000000000..6d811212e Binary files /dev/null and b/tests/baselines/gradient/gd-bigarena-long.replay differ diff --git a/tests/baselines/gradient/gd-large-4ai.replay b/tests/baselines/gradient/gd-large-4ai.replay new file mode 100644 index 000000000..4d47b05d1 Binary files /dev/null and b/tests/baselines/gradient/gd-large-4ai.replay differ diff --git a/tests/baselines/gradient/gd-small-2ai.replay b/tests/baselines/gradient/gd-small-2ai.replay new file mode 100644 index 000000000..a055d452d Binary files /dev/null and b/tests/baselines/gradient/gd-small-2ai.replay differ diff --git a/tools/SConscript b/tools/SConscript index 0d4bcc80e..6e8223af9 100644 --- a/tools/SConscript +++ b/tools/SConscript @@ -9,7 +9,7 @@ if "mksprite" in COMMAND_LINE_TARGETS: Import("env") Import("PackTar") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: PackTar(env["TARFILE"], "mksprite.cpp") PackTar(env["TARFILE"], "README") diff --git a/windows/SConscript b/windows/SConscript index c79f7a028..c00567bc0 100644 --- a/windows/SConscript +++ b/windows/SConscript @@ -7,7 +7,7 @@ if env['mingwcross']: Import("isWindowsPlatform") -if 'dist' or 'install' in COMMAND_LINE_TARGETS: +if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS: PackTar(env["TARFILE"], "glob2.ico") PackTar(env["TARFILE"], "header.bmp") PackTar(env["TARFILE"], "side.bmp")