Compare commits

..
Author SHA1 Message Date
cambragol 3e574882f3 Update worldmap.cc 2025-05-20 14:57:14 +09:00
cambragol 5995592664 Update worldmap.cc 2025-05-20 07:02:01 +09:00
Mike Klaas 982c5fcb42 Fix visibility of city circles and names
Currently, cities are not drawn until at least half of it is onscreen, meaning they "pop in" suddenly.  This fixes that problem.

Also, the check for city name was too aggressive, meaning the name of small cities (e.g. Motel in Sonora) would fail to render near the bottom of the screen.
2025-05-14 21:58:49 -07:00
sherief 9cd309b263 Merge pull request #128 from fallout2-ce/gapless-music
Gapless music.
2025-05-14 01:51:24 -07:00
Mike Klaas de7f12f3ca Merge pull request #125 from fallout2-ce/remove-text-on-wm-transition
Remove floaters when moving to a new world map location
2025-05-13 23:20:51 -07:00
Sherief Farouk 7a2edd5677 The clang-format gods are fickle. 2025-05-13 04:02:05 -07:00
Sherief Farouk 66b68f84dc Pleasing the code formatting gods. 2025-05-13 03:57:59 -07:00
Sherief Farouk fef49692e1 _gsound_background_play_level_music() skips rewind when asked to play same track and gapless is on. 2025-05-13 03:37:02 -07:00
Sherief Farouk 6c3c0d34c2 Skipping loading sound when gapless music is enabled. 2025-05-13 03:34:01 -07:00
Sherief Farouk 42e016e4f9 Declared SFall config flag for gapless music. 2025-05-13 03:33:00 -07:00
Mike Klaas 66542dc28a Merge pull request #121 from fallout2-ce/deobfuscate-item
Deobfuscate item.h,cc
2025-05-12 23:20:10 -07:00
Mike Klaas 5270cf0d55 Merge pull request #124 from fallout2-ce/mikeklaas-update-readme
Update README.md
2025-05-12 23:19:27 -07:00
Vasilii Rogin e598d06c4a Merge pull request #86 from roginvs/test_winxp_support
Linking stdlib statically for win32
2025-05-13 08:34:32 +03:00
Mike Klaas 66f7137f74 Remove floaters when moving to a new world map location
Fixes #115
2025-05-12 21:35:13 -07:00
Mike Klaas cf59439352 Update README.md 2025-05-12 16:51:40 -07:00
Mike Klaas 02ee5cba5d Update README.md
Update README with a pointer to the right release url, updated f2_res.ini support, and mention that this is a fork.
2025-05-12 16:50:37 -07:00
sherief 218fd20700 Merge pull request #51 from cambragol/patch-11
Update pipboy.cc for full pagination
2025-05-12 01:26:12 -07:00
Mike Klaas 7e18a6980a Deobfuscate item.h,cc
As always, no logic changes, only variable/field renames
2025-05-11 23:05:44 -07:00
Vasilii Rogin 66c0218741 Merge pull request #117 from fallout2-ce/fix_crash_on_game_load
Load pipboy messages when rendering saveslots
2025-05-11 14:05:01 +03:00
Vasilii Rogin 8fc789d6f9 Merge pull request #118 from fallout2-ce/add_pr_template
Add pull request template
2025-05-11 12:50:01 +03:00
Vasilii Rogin ddc5be3b6d Add PR template 2025-05-11 12:49:03 +03:00
Vasilii Rogin 833b6d8213 Fix formatting using this command:
docker run --rm \
  -v $(pwd):/app --workdir /app  \
  --user $(id -u):$(id -g) silkeh/clang:18 \
  bash -c 'find src -type f -name \*.cc -o -name \*.h | xargs clang-format -i'
2025-05-11 12:37:42 +03:00
Vasilii Rogin 3163319290 Revert "Set MSVC_RUNTIME_LIBRARY in the makefile"
This reverts commit 2e8a65593e.
2025-04-27 18:25:15 +03:00
Vasilii Rogin 252d2ef263 Bump required cmake version 2025-04-27 18:20:39 +03:00
Vasilii Rogin ef09142375 Revert "Use cache string force"
This reverts commit a4ba0240f1.
2025-04-27 18:19:35 +03:00
Vasilii Rogin a4ba0240f1 Use cache string force 2025-04-27 18:13:14 +03:00
Vasilii Rogin 2e8a65593e Set MSVC_RUNTIME_LIBRARY in the makefile 2025-04-27 18:05:42 +03:00
Vasilii Rogin f35d1efab2 Revert "Set MSVC_RUNTIME_LIBRARY in the makefile"
This reverts commit 42a6a16f73.
2025-04-27 18:03:17 +03:00
Vasilii Rogin 42a6a16f73 Set MSVC_RUNTIME_LIBRARY in the makefile 2025-04-27 17:58:12 +03:00
Vasilii Rogin 85389ef2f3 Linking stdlib statically 2025-04-27 14:45:05 +03:00
cambragol 5e93479192 Update pipboy.cc
Clang formatting?
2025-04-21 20:39:11 +09:00
cambragol afeec298bf Update pipboy.cc for full pagination
Adds full pagination to Status, Data and Automaps main and sub pages to prevent overflow.
2025-04-20 18:37:52 +09:00
14 changed files with 825 additions and 496 deletions
+31
View File
@@ -0,0 +1,31 @@
### Description
<!--- Clearly describe the changes introduced by this PR. What was fixed, added, or updated? --->
### Reproduction Steps and Savefile (if available)
<!--- Provide detailed steps to reproduce the issue. Include a savefile if applicable, preferrably for Sonora --->
### Screenshots
<!--- If the PR includes visual changes, add screenshots here. --->
<!---
**Important Notes**
- *Preview builds are not available for pull requests from forked repositories.* To enable preview builds, submit the PR from a branch within this repository.
- *Formatting issues? You have two options:*
1. **Fix formatting automatically** using the following Docker command: *(If you're using Windows, remove `--user $(id -u):$(id -g)`)*
```bash
docker run --rm \
-v $(pwd):/app --workdir /app \
--user $(id -u):$(id -g) silkeh/clang:18 \
bash -c 'find src -type f -name \*.cc -o -name \*.h | xargs clang-format -i'
```
2. **Skip formatting check** by adding the `skip-formatting` label to this PR.
--->
+3 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.13)
cmake_minimum_required(VERSION 3.18)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
@@ -305,12 +305,14 @@ if(WIN32)
_CRT_NONSTDC_NO_WARNINGS
NOMINMAX
WIN32_LEAN_AND_MEAN
_STATIC_CPPLIB
)
endif()
if(WIN32)
target_link_libraries(${EXECUTABLE_NAME}
winmm
libcpmt
)
endif()
+1 -1
View File
@@ -33,7 +33,7 @@
"cacheVariables": {
"CMAKE_GENERATOR_TOOLSET": "v141_xp",
"CMAKE_C_FLAGS": "/D_WIN32_WINNT=0x0501",
"CMAKE_CXX_FLAGS": "/D_WIN32_WINNT=0x0501",
"CMAKE_CXX_FLAGS": "/D_WIN32_WINNT=0x0501 /D_STATIC_CPPLIB",
"CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$<CONFIG:Debug>:Debug>"
}
},
+13 -6
View File
@@ -1,14 +1,14 @@
# Fallout 2 Community Edition
Fallout 2 Community Edition is a fully working re-implementation of Fallout 2, with the same original gameplay, engine bugfixes, and some quality of life improvements, that works (mostly) hassle-free on multiple platforms.
Fallout 2 Community Edition is a fully working re-implementation of Fallout 2, with the same original gameplay, engine bugfixes, and some quality of life improvements, that works (mostly) hassle-free on multiple platforms. This is a fork of the original project, which isn't getting regular updates.
Popular Fallout 2 total conversion mods are partially supported. Original versions of Nevada and Sonora (that do not rely on extended features provided by Sfall) likely work, although there is no complete walkthrough confirmation yet. [Fallout 2 Restoration Project](https://github.com/BGforgeNet/Fallout2_Restoration_Project), [Fallout Et Tu](https://github.com/rotators/Fo1in2) and [Olympus 2207](https://olympus2207.com) are not yet supported. Other mods (particularly Resurrection and Yesterday) are not tested.
Popular Fallout 2 total conversion mods are partially supported. Original versions of Nevada and Sonora (that do not rely on extended features provided by Sfall) work. [Fallout 2 Restoration Project](https://github.com/BGforgeNet/Fallout2_Restoration_Project), [Fallout Et Tu](https://github.com/rotators/Fo1in2) and [Olympus 2207](https://olympus2207.com) are not yet supported. Other mods (particularly Resurrection and Yesterday) are not tested.
There is also [Fallout Community Edition](https://github.com/alexbatalov/fallout1-ce).
There is also [Fallout Community Edition](https://github.com/alexbatalov/fallout1-ce) (not affiliated with this fork).
## Installation
You must own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2), [Epic Games](https://store.epicgames.com/p/fallout-2) or [Steam](https://store.steampowered.com/app/38410). Download latest [release](https://github.com/alexbatalov/fallout2-ce/releases) or build from source. You can also check latest [debug](https://github.com/alexbatalov/fallout2-ce/actions) build intended for testers.
You must own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2), [Epic Games](https://store.epicgames.com/p/fallout-2) or [Steam](https://store.steampowered.com/app/38410). Download latest [release](https://github.com/fallout2-ce/fallout2-ce/releases) or build from source.
### Windows
@@ -102,7 +102,11 @@ The second configuration file is `f2_res.ini`. Use it to change game window size
[MAIN]
SCR_WIDTH=1280
SCR_HEIGHT=720
WINDOWED=1
WINDOWED=1 ; 0 = fullscreen
SCALE_2X=1 ; 0 = original scale, 1 = 2x
[IFACE]
IFACE_BAR_MODE, IFACE_BAR_SIDE_ART, IFACE_BAR_SIDES_ORI, IFACE_BAR_WIDTH supported (require assets in appropriate places)
```
Recommendations:
@@ -112,7 +116,10 @@ Recommendations:
In time this stuff will receive in-game interface, right now you have to do it manually.
The third configuration file is `ddraw.ini` (part of Sfall). There are dozens of options that adjust or override engine behaviour and gameplay mechanics. This file is intended for modders and advanced users. Currently only a small subset of these settings are actually implemented.
The third configuration file is `ddraw.ini` (part of Sfall). There are dozens of options that adjust or override engine behaviour and gameplay mechanics. This file is intended for modders and advanced users. Currently only a small subset of these settings are actually implemented. For high res configs, the following is recommended:
```[Main]
HiResMode=1
```
## Contributing
+11 -2
View File
@@ -21,6 +21,7 @@
#include "queue.h"
#include "random.h"
#include "settings.h"
#include "sfall_config.h"
#include "sound_effects_cache.h"
#include "stat.h"
#include "svga.h"
@@ -745,9 +746,17 @@ int backgroundSoundLoad(const char* fileName, int a2, int a3, int a4)
}
// 0x450A08
int _gsound_background_play_level_music(const char* a1, int a2)
int _gsound_background_play_level_music(const char* fileName, int a2)
{
return backgroundSoundLoad(a1, a2, 14, 16);
int gaplessMusic = 0;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_GAPLESS_MUSIC, &gaplessMusic);
if (gaplessMusic) {
if (!strcmp(fileName, gBackgroundSoundFileName)) {
return 0;
}
}
return backgroundSoundLoad(fileName, a2, 14, 16);
}
// 0x450AB4
+30 -25
View File
@@ -50,10 +50,10 @@ static int stealthBoyTurnOff(Object* critter, Object* item);
static int _insert_drug_effect(Object* critter, Object* item, int duration, int* stats, int* mods);
static void _perform_drug_effect(Object* critter, int* stats, int* mods, bool isImmediate);
static bool _drug_effect_allowed(Object* critter, int pid);
static int _insert_withdrawal(Object* obj, int a2, int a3, int a4, int a5);
static int _item_wd_clear_all(Object* a1, void* data);
static void performWithdrawalStart(Object* obj, int perk, int a3);
static void performWithdrawalEnd(Object* obj, int a2);
static int _insert_withdrawal(Object* obj, int active, int duration, int perk, int pid);
static int _item_wd_clear_all(Object* obj, void* data);
static void performWithdrawalStart(Object* obj, int perk, int pid);
static void performWithdrawalEnd(Object* obj, int perk);
static int drugGetAddictionGvarByPid(int drugPid);
static void dudeSetAddiction(int drugPid);
static void dudeClearAddiction(int drugPid);
@@ -1550,6 +1550,9 @@ bool weaponCanBeReloadedWith(Object* weapon, Object* ammo)
}
// 0x478918
// weaponReload adds ammo to the weapon and removes it from the given ammo stack
// return -1 if ammo is incompatible with weapon; otherwise returns the number of
// rounds left in the ammo stack
int weaponReload(Object* weapon, Object* ammo)
{
if (!weaponCanBeReloadedWith(weapon, ammo)) {
@@ -1568,26 +1571,26 @@ int weaponReload(Object* weapon, Object* ammo)
}
// NOTE: Uninline.
int v10 = ammoGetQuantity(ammo);
int quantity = ammoGetQuantity(ammo);
int v11 = v10;
int left = quantity;
if (ammoQuantity < ammoCapacity) {
int v12;
if (ammoQuantity + v10 > ammoCapacity) {
v11 = v10 - (ammoCapacity - ammoQuantity);
v12 = ammoCapacity;
int newQuantity;
if (ammoQuantity + quantity > ammoCapacity) {
left = quantity - (ammoCapacity - ammoQuantity);
newQuantity = ammoCapacity;
} else {
v11 = 0;
v12 = ammoQuantity + v10;
left = 0;
newQuantity = ammoQuantity + quantity;
}
weapon->data.item.weapon.ammoTypePid = ammo->pid;
ammoSetQuantity(ammo, v11);
ammoSetQuantity(weapon, v12);
ammoSetQuantity(ammo, left);
ammoSetQuantity(weapon, newQuantity);
}
return v11;
return left;
}
// 0x478A1C
@@ -2914,14 +2917,14 @@ int drugEffectEventWrite(File* stream, void* data)
}
// 0x47A290
static int _insert_withdrawal(Object* obj, int a2, int duration, int perk, int pid)
static int _insert_withdrawal(Object* obj, int active, int duration, int perk, int pid)
{
WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)internal_malloc(sizeof(*withdrawalEvent));
if (withdrawalEvent == nullptr) {
return -1;
}
withdrawalEvent->field_0 = a2;
withdrawalEvent->active = active;
withdrawalEvent->pid = pid;
withdrawalEvent->perk = perk;
@@ -2942,7 +2945,7 @@ int _item_wd_clear(Object* obj, void* data)
return 0;
}
if (!withdrawalEvent->field_0) {
if (!withdrawalEvent->active) {
performWithdrawalEnd(obj, withdrawalEvent->perk);
}
@@ -2950,11 +2953,11 @@ int _item_wd_clear(Object* obj, void* data)
}
// 0x47A324
static int _item_wd_clear_all(Object* a1, void* data)
static int _item_wd_clear_all(Object* obj, void* data)
{
WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data;
if (a1 != _wd_obj) {
if (obj != _wd_obj) {
return 0;
}
@@ -2962,11 +2965,12 @@ static int _item_wd_clear_all(Object* a1, void* data)
return 0;
}
if (!withdrawalEvent->field_0) {
if (!withdrawalEvent->active) {
performWithdrawalEnd(_wd_obj, withdrawalEvent->perk);
}
_insert_withdrawal(a1, 1, _wd_onset, withdrawalEvent->perk, withdrawalEvent->pid);
// schedule start of withdrawal
_insert_withdrawal(obj, 1, _wd_onset, withdrawalEvent->perk, withdrawalEvent->pid);
_wd_obj = nullptr;
@@ -2978,7 +2982,7 @@ int withdrawalEventProcess(Object* obj, void* data)
{
WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data;
if (withdrawalEvent->field_0) {
if (withdrawalEvent->active) {
performWithdrawalStart(obj, withdrawalEvent->perk, withdrawalEvent->pid);
} else {
if (withdrawalEvent->perk == PERK_JET_ADDICTION) {
@@ -3009,7 +3013,7 @@ int withdrawalEventRead(File* stream, void** dataPtr)
return -1;
}
if (fileReadInt32(stream, &(withdrawalEvent->field_0)) == -1) goto err;
if (fileReadInt32(stream, &(withdrawalEvent->active)) == -1) goto err;
if (fileReadInt32(stream, &(withdrawalEvent->pid)) == -1) goto err;
if (fileReadInt32(stream, &(withdrawalEvent->perk)) == -1) goto err;
@@ -3027,7 +3031,7 @@ int withdrawalEventWrite(File* stream, void* data)
{
WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data;
if (fileWriteInt32(stream, withdrawalEvent->field_0) == -1) return -1;
if (fileWriteInt32(stream, withdrawalEvent->active) == -1) return -1;
if (fileWriteInt32(stream, withdrawalEvent->pid) == -1) return -1;
if (fileWriteInt32(stream, withdrawalEvent->perk) == -1) return -1;
@@ -3064,6 +3068,7 @@ static void performWithdrawalStart(Object* obj, int perk, int pid)
}
}
// schedule end of withdrawal
_insert_withdrawal(obj, 0, duration, perk, pid);
}
+7 -7
View File
@@ -49,13 +49,13 @@ bool dudeIsWeaponDisabled(Object* weapon);
int itemGetInventoryFid(Object* obj);
Object* critterGetWeaponForHitMode(Object* critter, int hitMode);
int itemGetActionPointCost(Object* obj, int hitMode, bool aiming);
int itemGetQuantity(Object* obj, Object* a2);
int itemGetQuantity(Object* obj, Object* item);
int itemIsQueued(Object* obj);
Object* itemReplace(Object* a1, Object* a2, int a3);
Object* itemReplace(Object* owner, Object* itemToReplace, int flags);
bool itemIsHidden(Object* obj);
int weaponGetAttackTypeForHitMode(Object* a1, int a2);
int weaponGetSkillForHitMode(Object* a1, int a2);
int weaponGetSkillValue(Object* a1, int a2);
int weaponGetAttackTypeForHitMode(Object* weapon, int hitMode);
int weaponGetSkillForHitMode(Object* weapon, int hitMode);
int weaponGetSkillValue(Object* critter, int hitMode);
int weaponGetDamageMinMax(Object* weapon, int* minDamagePtr, int* maxDamagePtr);
int weaponGetDamage(Object* critter, int hitMode);
int weaponGetDamageType(Object* critter, Object* weapon);
@@ -123,14 +123,14 @@ int _item_d_clear(Object* obj, void* data);
int drugEffectEventProcess(Object* obj, void* data);
int drugEffectEventRead(File* stream, void** dataPtr);
int drugEffectEventWrite(File* stream, void* data);
int _item_wd_clear(Object* obj, void* a2);
int _item_wd_clear(Object* obj, void* data);
int withdrawalEventProcess(Object* obj, void* data);
int withdrawalEventRead(File* stream, void** dataPtr);
int withdrawalEventWrite(File* stream, void* data);
int itemGetTotalCaps(Object* obj);
int itemCapsAdjust(Object* obj, int amount);
int itemGetMoney(Object* obj);
int itemSetMoney(Object* obj, int a2);
int itemSetMoney(Object* obj, int amount);
bool booksGetInfo(int bookPid, int* messageIdPtr, int* skillPtr);
bool explosionEmitsLight();
+4 -4
View File
@@ -2303,8 +2303,8 @@ static void _ShowSlotList(int windowType)
int inactiveColor = _colorTable[8804];
{
MessageListItem messageListItemBack = {201 , 0, nullptr, nullptr};
if (!messageListGetItem(&gPipboyMessageList, &messageListItemBack)){
MessageListItem messageListItemBack = { 201, 0, nullptr, nullptr };
if (!messageListGetItem(&gPipboyMessageList, &messageListItemBack)) {
debugPrint("Error: Couldn't find LoadSave Message!");
messageListItemBack.text = "BACK";
}
@@ -2316,8 +2316,8 @@ static void _ShowSlotList(int windowType)
_currentSlotPage > 0 ? activeColor : inactiveColor);
}
{
MessageListItem messageListItemMore = {200 , 0, nullptr, nullptr};
if (!messageListGetItem(&gPipboyMessageList, &messageListItemMore)){
MessageListItem messageListItemMore = { 200, 0, nullptr, nullptr };
if (!messageListGetItem(&gPipboyMessageList, &messageListItemMore)) {
debugPrint("Error: Couldn't find LoadSave Message!");
messageListItemMore.text = "MORE";
}
+11 -2
View File
@@ -36,6 +36,7 @@
#include "random.h"
#include "scripts.h"
#include "settings.h"
#include "sfall_config.h"
#include "svga.h"
#include "text_object.h"
#include "tile.h"
@@ -819,7 +820,11 @@ int mapLoadById(int map)
static int mapLoad(File* stream)
{
_map_save_in_game(true);
backgroundSoundLoad("wind2", 12, 13, 16);
int gaplessMusic = 0;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_GAPLESS_MUSIC, &gaplessMusic);
if (!gaplessMusic) {
backgroundSoundLoad("wind2", 12, 13, 16);
}
isoDisable();
_partyMemberPrepLoad();
_gmouse_disable_scrolling();
@@ -1248,12 +1253,16 @@ int mapHandleTransition()
if (gMapTransition.map == -1) {
if (!isInCombat()) {
animationStop();
wmTownMap();
// SFALL: Remove text floaters when moving to the world map
textObjectsReset();
wmTownMap(); // nb this is a world map transition
memset(&gMapTransition, 0, sizeof(gMapTransition));
}
} else if (gMapTransition.map == -2) {
if (!isInCombat()) {
animationStop();
// SFALL: Remove text floaters when moving to the world map
textObjectsReset();
wmWorldMap();
memset(&gMapTransition, 0, sizeof(gMapTransition));
}
+653 -429
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -31,7 +31,7 @@ typedef struct DrugEffectEvent {
} DrugEffectEvent;
typedef struct WithdrawalEvent {
int field_0;
int active; // 0 == end withdrawal, 1 == start withdrawal
int pid;
int perk;
} WithdrawalEvent;
+1
View File
@@ -68,6 +68,7 @@ bool sfallConfigInit(int argc, char** argv)
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_PIPBOY_AVAILABLE_AT_GAMESTART, 0);
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_WALK_DISTANCE, 5);
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_AUTO_OPEN_DOORS, 0);
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_GAPLESS_MUSIC, 0);
char path[COMPAT_MAX_PATH];
char* executable = argv[0];
+1
View File
@@ -80,6 +80,7 @@ namespace fallout {
#define SFALL_CONFIG_PIPBOY_AVAILABLE_AT_GAMESTART "PipBoyAvailableAtGameStart"
#define SFALL_CONFIG_USE_WALK_DISTANCE "UseWalkDistance"
#define SFALL_CONFIG_AUTO_OPEN_DOORS "AutoOpenDoors"
#define SFALL_CONFIG_GAPLESS_MUSIC "GaplessMusic"
#define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MULTIPLIER 1
#define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR 3
+58 -18
View File
@@ -534,7 +534,7 @@ static int wmTileGrabArt(int tileIdx);
static int wmInterfaceRefresh();
static void wmInterfaceRefreshDate(bool shouldRefreshWindow);
static int wmMatchWorldPosToArea(int x, int y, int* areaIdxPtr);
static int wmInterfaceDrawCircleOverlay(CityInfo* cityInfo, CitySizeDescription* citySizeInfo, unsigned char* buffer, int x, int y);
static int wmInterfaceDrawCircleOverlay(CityInfo* cityInfo, CitySizeDescription* citySizeInfo, unsigned char* buffer, int x, int y, int destPitch);
static void wmInterfaceDrawSubTileRectFogged(unsigned char* dest, int width, int height, int pitch);
static int wmInterfaceDrawSubTileList(TileInfo* tileInfo, int column, int row, int x, int y, int a6);
static int wmDrawCursorStopped();
@@ -5221,17 +5221,50 @@ static int wmInterfaceRefresh()
}
// Render cities.
for (int index = 0; index < wmMaxAreaNum; index++) {
CityInfo* cityInfo = &(wmAreaInfoList[index]);
if (cityInfo->state != CITY_STATE_UNKNOWN) {
CitySizeDescription* citySizeDescription = &(wmSphereData[cityInfo->size]);
int cityX = cityInfo->x - wmWorldOffsetX;
int cityY = cityInfo->y - wmWorldOffsetY;
if (cityX >= 0 && cityX <= 472 - citySizeDescription->frmImage.getWidth()
&& cityY >= 0 && cityY <= 465 - citySizeDescription->frmImage.getHeight()) {
wmInterfaceDrawCircleOverlay(cityInfo, citySizeDescription, wmBkWinBuf, cityX, cityY);
constexpr int PADDING = 100;
int viewportWidth = 472;
int viewportHeight = 465;
int bufferWidth = viewportWidth + PADDING * 2;
int bufferHeight = viewportHeight + PADDING * 2;
unsigned char* paddedBuf = (unsigned char*)malloc(bufferWidth * bufferHeight);
if (paddedBuf != nullptr) {
// Copy current wmBkWinBuf into center of paddedBuf
memset(paddedBuf, 0, bufferWidth * bufferHeight); // Clear padding
for (int y = 0; y < viewportHeight; y++) {
memcpy(
paddedBuf + bufferWidth * (y + PADDING) + PADDING,
wmBkWinBuf + y * WM_WINDOW_WIDTH,
viewportWidth);
}
// Draw cities onto a padded buffer
for (int index = 0; index < wmMaxAreaNum; index++) {
CityInfo* cityInfo = &wmAreaInfoList[index];
if (cityInfo->state != CITY_STATE_UNKNOWN) {
CitySizeDescription* citySizeDescription = &wmSphereData[cityInfo->size];
int cityX = cityInfo->x - wmWorldOffsetX + PADDING;
int cityY = cityInfo->y - wmWorldOffsetY + PADDING;
if (cityX >= 0 && cityX <= bufferWidth - citySizeDescription->frmImage.getWidth() &&
cityY >= 0 && cityY <= bufferHeight - citySizeDescription->frmImage.getHeight()) {
wmInterfaceDrawCircleOverlay(cityInfo, citySizeDescription, paddedBuf, cityX, cityY, bufferWidth);
}
}
}
// Copy center portion back to wmBkWinBuf
for (int y = 0; y < viewportHeight; y++) {
memcpy(
wmBkWinBuf + y * WM_WINDOW_WIDTH,
paddedBuf + bufferWidth * (y + PADDING) + PADDING,
viewportWidth);
}
free(paddedBuf);
}
// Hide unknown subtiles, dim unvisited.
@@ -5384,39 +5417,46 @@ static int wmMatchWorldPosToArea(int x, int y, int* areaIdxPtr)
return 0;
}
// Function augmented to add destPitch to align city icons properly
// 0x4C3FA8
static int wmInterfaceDrawCircleOverlay(CityInfo* city, CitySizeDescription* citySizeDescription, unsigned char* dest, int x, int y)
static int wmInterfaceDrawCircleOverlay(
CityInfo* city,
CitySizeDescription* citySizeDescription,
unsigned char* dest,
int x,
int y,
int destPitch)
{
_dark_translucent_trans_buf_to_buf(citySizeDescription->frmImage.getData(),
_dark_translucent_trans_buf_to_buf(
citySizeDescription->frmImage.getData(),
citySizeDescription->frmImage.getWidth(),
citySizeDescription->frmImage.getHeight(),
citySizeDescription->frmImage.getWidth(),
dest,
x,
y,
WM_WINDOW_WIDTH,
destPitch,
0x10000,
circleBlendTable,
_commonGrayTable);
// CE: Slightly increase whitespace between cirle and city name.
int nameY = y + citySizeDescription->frmImage.getHeight() + 3;
int maxY = 464 - fontGetLineHeight();
int maxY = (464) + 200 - fontGetLineHeight(); // 200 for the PADDING
if (nameY < maxY) {
MessageListItem messageListItem;
char name[40];
if (wmAreaIsKnown(city->areaId)) {
// NOTE: Uninline.
wmGetAreaName(city, name);
} else {
strncpy(name, getmsg(&wmMsgFile, &messageListItem, 1004), 40);
}
int width = fontGetStringWidth(name);
fontDrawText(dest + WM_WINDOW_WIDTH * nameY + x + citySizeDescription->frmImage.getWidth() / 2 - width / 2,
fontDrawText(
dest + destPitch * nameY + x + citySizeDescription->frmImage.getWidth() / 2 - width / 2,
name,
width,
WM_WINDOW_WIDTH,
destPitch,
_colorTable[992] | FONT_SHADOW);
}