diff --git a/CMakeLists.txt b/CMakeLists.txt index 68ffb64f..7a8879f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -275,6 +275,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC "src/sfall_kb_helpers.h" "src/sfall_lists.cc" "src/sfall_lists.h" + "src/sfall_metarules.cc" + "src/sfall_metarules.h" "src/sfall_opcodes.cc" "src/sfall_opcodes.h" "src/sfall_arrays.cc" diff --git a/src/actions.cc b/src/actions.cc index 292e5cee..83685468 100644 --- a/src/actions.cc +++ b/src/actions.cc @@ -48,6 +48,9 @@ typedef enum ScienceRepairTargetType { // 0x5106D0 static bool _action_in_explode = false; +// 0x5106D4 +int rotation; + // 0x5106E0 static const int gNormalDeathAnimations[DAMAGE_TYPE_COUNT] = { ANIM_DANCING_AUTOFIRE, diff --git a/src/actions.h b/src/actions.h index 67d425f2..86c0f312 100644 --- a/src/actions.h +++ b/src/actions.h @@ -6,6 +6,8 @@ namespace fallout { +extern int rotation; + int _action_attack(Attack* attack); int _action_use_an_item_on_object(Object* a1, Object* a2, Object* a3); int _action_use_an_object(Object* a1, Object* a2); diff --git a/src/animation.cc b/src/animation.cc index 5663fe64..0b496c26 100644 --- a/src/animation.cc +++ b/src/animation.cc @@ -1711,6 +1711,7 @@ int _make_path(Object* object, int from, int to, unsigned char* rotations, int a return pathfinderFindPath(object, from, to, rotations, a5, _obj_blocking_at); } +// TODO: move pathfinding into another unit // 0x415EFC int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback) { @@ -1939,18 +1940,18 @@ static int _tile_idistance(int tile1, int tile2) } // 0x4163AC -int _make_straight_path(Object* a1, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6) +int _make_straight_path(Object* obj, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6) { - return _make_straight_path_func(a1, from, to, straightPathNodeList, obstaclePtr, a6, _obj_blocking_at); + return _make_straight_path_func(obj, from, to, straightPathNodeList, obstaclePtr, a6, _obj_blocking_at); } // TODO: Rather complex, but understandable, needs testing. // // 0x4163C8 -int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6, PathBuilderCallback* callback) +int _make_straight_path_func(Object* obj, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6, PathBuilderCallback* callback) { if (obstaclePtr != NULL) { - Object* obstacle = callback(a1, from, a1->elevation); + Object* obstacle = callback(obj, from, obj->elevation); if (obstacle != NULL) { if (obstacle != *obstaclePtr && (a6 != 32 || (obstacle->flags & OBJECT_SHOOT_THRU) == 0)) { *obstaclePtr = obstacle; @@ -1961,13 +1962,13 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str int fromX; int fromY; - tileToScreenXY(from, &fromX, &fromY, a1->elevation); + tileToScreenXY(from, &fromX, &fromY, obj->elevation); fromX += 16; fromY += 8; int toX; int toY; - tileToScreenXY(to, &toX, &toY, a1->elevation); + tileToScreenXY(to, &toX, &toY, obj->elevation); toX += 16; toY += 8; @@ -2005,7 +2006,7 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str if (ddx <= ddy) { int middle = ddx - ddy / 2; while (true) { - tile = tileFromScreenXY(tileX, tileY, a1->elevation); + tile = tileFromScreenXY(tileX, tileY, obj->elevation); v22 += 1; if (v22 == a6) { @@ -2016,9 +2017,9 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str if (straightPathNodeList != NULL) { StraightPathNode* pathNode = &(straightPathNodeList[pathNodeIndex]); pathNode->tile = tile; - pathNode->elevation = a1->elevation; + pathNode->elevation = obj->elevation; - tileToScreenXY(tile, &fromX, &fromY, a1->elevation); + tileToScreenXY(tile, &fromX, &fromY, obj->elevation); pathNode->x = tileX - fromX - 16; pathNode->y = tileY - fromY - 8; } @@ -2044,10 +2045,10 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str if (tile != prevTile) { if (obstaclePtr != NULL) { - Object* obj = callback(a1, tile, a1->elevation); - if (obj != NULL) { - if (obj != *obstaclePtr && (a6 != 32 || (obj->flags & OBJECT_SHOOT_THRU) == 0)) { - *obstaclePtr = obj; + Object* obstacle = callback(obj, tile, obj->elevation); + if (obstacle != NULL) { + if (obstacle != *obstaclePtr && (a6 != 32 || (obstacle->flags & OBJECT_SHOOT_THRU) == 0)) { + *obstaclePtr = obstacle; break; } } @@ -2058,7 +2059,7 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str } else { int middle = ddy - ddx / 2; while (true) { - tile = tileFromScreenXY(tileX, tileY, a1->elevation); + tile = tileFromScreenXY(tileX, tileY, obj->elevation); v22 += 1; if (v22 == a6) { @@ -2069,9 +2070,9 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str if (straightPathNodeList != NULL) { StraightPathNode* pathNode = &(straightPathNodeList[pathNodeIndex]); pathNode->tile = tile; - pathNode->elevation = a1->elevation; + pathNode->elevation = obj->elevation; - tileToScreenXY(tile, &fromX, &fromY, a1->elevation); + tileToScreenXY(tile, &fromX, &fromY, obj->elevation); pathNode->x = tileX - fromX - 16; pathNode->y = tileY - fromY - 8; } @@ -2097,10 +2098,10 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str if (tile != prevTile) { if (obstaclePtr != NULL) { - Object* obj = callback(a1, tile, a1->elevation); - if (obj != NULL) { - if (obj != *obstaclePtr && (a6 != 32 || (obj->flags & OBJECT_SHOOT_THRU) == 0)) { - *obstaclePtr = obj; + Object* obstacle = callback(obj, tile, obj->elevation); + if (obstacle != NULL) { + if (obstacle != *obstaclePtr && (a6 != 32 || (obstacle->flags & OBJECT_SHOOT_THRU) == 0)) { + *obstaclePtr = obstacle; break; } } @@ -2118,9 +2119,9 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str if (straightPathNodeList != NULL) { StraightPathNode* pathNode = &(straightPathNodeList[pathNodeIndex]); pathNode->tile = tile; - pathNode->elevation = a1->elevation; + pathNode->elevation = obj->elevation; - tileToScreenXY(tile, &fromX, &fromY, a1->elevation); + tileToScreenXY(tile, &fromX, &fromY, obj->elevation); pathNode->x = tileX - fromX - 16; pathNode->y = tileY - fromY - 8; } @@ -2128,7 +2129,7 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str pathNodeIndex += 1; } else { if (pathNodeIndex > 0 && straightPathNodeList != NULL) { - straightPathNodeList[pathNodeIndex - 1].elevation = a1->elevation; + straightPathNodeList[pathNodeIndex - 1].elevation = obj->elevation; } } diff --git a/src/animation.h b/src/animation.h index 4e146a47..9f0e297f 100644 --- a/src/animation.h +++ b/src/animation.h @@ -145,8 +145,8 @@ int animationRegisterAnimateForever(Object* owner, int anim, int delay); int animationRegisterPing(int flags, int delay); int _make_path(Object* object, int from, int to, unsigned char* a4, int a5); int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback); -int _make_straight_path(Object* a1, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6); -int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6, PathBuilderCallback* callback); +int _make_straight_path(Object* object, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6); +int _make_straight_path_func(Object* object, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6, PathBuilderCallback* callback); void _object_animate(); int _check_move(int* actionPointsPtr); int _dude_move(int actionPoints); diff --git a/src/art.cc b/src/art.cc index 04da9662..07df3813 100644 --- a/src/art.cc +++ b/src/art.cc @@ -447,6 +447,14 @@ void artRender(int fid, unsigned char* dest, int width, int height, int pitch) artUnlock(handle); } +// mapper2.exe: 0x40A03C +int art_list_str(int fid, char* name) +{ + // TODO: Incomplete. + + return -1; +} + // 0x419160 Art* artLock(int fid, CacheEntry** handlePtr) { diff --git a/src/art.h b/src/art.h index f7c31b4f..650b2410 100644 --- a/src/art.h +++ b/src/art.h @@ -123,6 +123,7 @@ char* artGetObjectTypeName(int objectType); int artIsObjectTypeHidden(int objectType); int artGetFidgetCount(int headFid); void artRender(int fid, unsigned char* dest, int width, int height, int pitch); +int art_list_str(int fid, char* name); Art* artLock(int fid, CacheEntry** cache_entry); unsigned char* artLockFrameData(int fid, int frame, int direction, CacheEntry** out_cache_entry); unsigned char* artLockFrameDataReturningSize(int fid, CacheEntry** out_cache_entry, int* widthPtr, int* heightPtr); diff --git a/src/combat.cc b/src/combat.cc index 85481374..6d28fe03 100644 --- a/src/combat.cc +++ b/src/combat.cc @@ -116,7 +116,7 @@ static int attackComputeCriticalHit(Attack* a1); static int _attackFindInvalidFlags(Object* a1, Object* a2); static int attackComputeCriticalFailure(Attack* attack); static void _do_random_cripple(int* flagsPtr); -static int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, bool a6); +static int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, bool useDistance); static void attackComputeDamage(Attack* attack, int ammoQuantity, int a3); static void _check_for_death(Object* a1, int a2, int* a3); static void _set_new_results(Object* a1, int a2); @@ -3476,16 +3476,16 @@ void attackInit(Attack* attack, Object* attacker, Object* defender, int hitMode, } // 0x422F3C -int _combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation) +int _combat_attack(Object* attacker, Object* defender, int hitMode, int hitLocation) { - if (a1 != gDude && hitMode == HIT_MODE_PUNCH && randomBetween(1, 4) == 1) { - int fid = buildFid(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_KICK_LEG, (a1->fid & 0xF000) >> 12, (a1->fid & 0x70000000) >> 28); + if (attacker != gDude && hitMode == HIT_MODE_PUNCH && randomBetween(1, 4) == 1) { + int fid = buildFid(OBJ_TYPE_CRITTER, attacker->fid & 0xFFF, ANIM_KICK_LEG, (attacker->fid & 0xF000) >> 12, (attacker->fid & 0x70000000) >> 28); if (artExists(fid)) { hitMode = HIT_MODE_KICK; } } - attackInit(&_main_ctd, a1, a2, hitMode, hitLocation); + attackInit(&_main_ctd, attacker, defender, hitMode, hitLocation); debugPrint("computing attack...\n"); if (attackCompute(&_main_ctd) == -1) { @@ -3513,7 +3513,7 @@ int _combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation) bool aiming; if (_main_ctd.defenderHitLocation == HIT_LOCATION_TORSO || _main_ctd.defenderHitLocation == HIT_LOCATION_UNCALLED) { - if (a1 == gDude) { + if (attacker == gDude) { interfaceGetCurrentHitMode(&hitMode, &aiming); } else { aiming = false; @@ -3522,22 +3522,22 @@ int _combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation) aiming = true; } - int actionPoints = weaponGetActionPointCost(a1, _main_ctd.hitMode, aiming); + int actionPoints = weaponGetActionPointCost(attacker, _main_ctd.hitMode, aiming); debugPrint("sequencing attack...\n"); if (_action_attack(&_main_ctd) == -1) { return -1; } - if (actionPoints > a1->data.critter.combat.ap) { - a1->data.critter.combat.ap = 0; + if (actionPoints > attacker->data.critter.combat.ap) { + attacker->data.critter.combat.ap = 0; } else { - a1->data.critter.combat.ap -= actionPoints; + attacker->data.critter.combat.ap -= actionPoints; } - if (a1 == gDude) { - interfaceRenderActionPoints(a1->data.critter.combat.ap, _combat_free_move); - _critter_set_who_hit_me(a1, a2); + if (attacker == gDude) { + interfaceRenderActionPoints(attacker->data.critter.combat.ap, _combat_free_move); + _critter_set_who_hit_me(attacker, defender); } // SFALL @@ -3545,19 +3545,19 @@ int _combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation) _combat_call_display = 1; _combat_cleanup_enabled = 1; - aiInfoSetLastTarget(a1, a2); + aiInfoSetLastTarget(attacker, defender); debugPrint("running attack...\n"); return 0; } -// Returns tile one step closer from [a1] to [a2] +// Returns tile one step closer from [attacker] to [target] // // 0x423104 -int _combat_bullet_start(const Object* a1, const Object* a2) +int _combat_bullet_start(const Object* attacker, const Object* target) { - int rotation = tileGetRotationTo(a1->tile, a2->tile); - return tileGetTileInDirection(a1->tile, rotation, 1); + int rotation = tileGetRotationTo(attacker->tile, target->tile); + return tileGetTileInDirection(attacker->tile, rotation, 1); } // 0x423128 @@ -3942,17 +3942,17 @@ static int attackCompute(Attack* attack) attack->tile = tile; - Object* v25 = attack->defender; - _make_straight_path_func(v25, attack->defender->tile, attack->tile, NULL, &v25, 32, _obj_shoot_blocking_at); - if (v25 != NULL && v25 != attack->defender) { - attack->tile = v25->tile; + Object* accidentalTarget = attack->defender; + _make_straight_path_func(accidentalTarget, attack->defender->tile, attack->tile, NULL, &accidentalTarget, 32, _obj_shoot_blocking_at); + if (accidentalTarget != NULL && accidentalTarget != attack->defender) { + attack->tile = accidentalTarget->tile; } else { - v25 = _obj_blocking_at(NULL, attack->tile, attack->defender->elevation); + accidentalTarget = _obj_blocking_at(NULL, attack->tile, attack->defender->elevation); } - if (v25 != NULL && (v25->flags & OBJECT_SHOOT_THRU) == 0) { + if (accidentalTarget != NULL && (accidentalTarget->flags & OBJECT_SHOOT_THRU) == 0) { attack->attackerFlags |= DAM_HIT; - attack->defender = v25; + attack->defender = accidentalTarget; attackComputeDamage(attack, 1, 2); } } @@ -3974,75 +3974,75 @@ static int attackCompute(Attack* attack) // compute_explosion_on_extras // 0x423C10 -void _compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4) +void _compute_explosion_on_extras(Attack* attack, bool isFromAttacker, bool isGrenade, bool noDamage) { - Object* attacker; + Object* targetObj; - if (a2) { - attacker = attack->attacker; + if (isFromAttacker) { + targetObj = attack->attacker; } else { if ((attack->attackerFlags & DAM_HIT) != 0) { - attacker = attack->defender; + targetObj = attack->defender; } else { - attacker = NULL; + targetObj = NULL; } } - int tile; - if (attacker != NULL) { - tile = attacker->tile; + int explosionTile; + if (targetObj != NULL) { + explosionTile = targetObj->tile; } else { - tile = attack->tile; + explosionTile = attack->tile; } - if (tile == -1) { + if (explosionTile == -1) { debugPrint("\nError: compute_explosion_on_extras: Called with bad target/tileNum"); return; } - // TODO: The math in this loop is rather complex and hard to understand. - int v20; - int v22 = 0; + int ringTileIdx; + int radius = 0; int rotation = 0; - int v5 = -1; - int v19 = tile; + int tile = -1; + int ringFirstTile = explosionTile; // SFALL int maxTargets = explosionGetMaxTargets(); + // Check adjacent tiles for possible targets, going ring-by-ring while (attack->extrasLength < maxTargets) { - if (v22 != 0 && (v5 == -1 || (v5 = tileGetTileInDirection(v5, rotation, 1)) != v19)) { - v20++; - if (v20 % v22 == 0) { + if (radius != 0 && (tile == -1 || (tile = tileGetTileInDirection(tile, rotation, 1)) != ringFirstTile)) { + ringTileIdx++; + if (ringTileIdx % radius == 0) { // the larger the radius, the slower we rotate rotation += 1; if (rotation == ROTATION_COUNT) { rotation = ROTATION_NE; } } } else { - v22++; - if (isGrenade && weaponGetGrenadeExplosionRadius(attack->weapon) < v22) { - v5 = -1; - } else if (isGrenade || weaponGetRocketExplosionRadius(attack->weapon) >= v22) { - v5 = tileGetTileInDirection(v19, ROTATION_NE, 1); + radius++; // go to the next ring + if (isGrenade && weaponGetGrenadeExplosionRadius(attack->weapon) < radius) { + tile = -1; + } else if (isGrenade || weaponGetRocketExplosionRadius(attack->weapon) >= radius) { + tile = tileGetTileInDirection(ringFirstTile, ROTATION_NE, 1); } else { - v5 = -1; + tile = -1; } - v19 = v5; + ringFirstTile = tile; rotation = ROTATION_SE; - v20 = 0; + ringTileIdx = 0; } - if (v5 == -1) { + if (tile == -1) { break; } - Object* obstacle = _obj_blocking_at(attacker, v5, attack->attacker->elevation); + Object* obstacle = _obj_blocking_at(targetObj, tile, attack->attacker->elevation); if (obstacle != NULL && FID_TYPE(obstacle->fid) == OBJ_TYPE_CRITTER && (obstacle->data.critter.combat.results & DAM_DEAD) == 0 && (obstacle->flags & OBJECT_SHOOT_THRU) == 0 - && !_combat_is_shot_blocked(obstacle, obstacle->tile, tile, NULL, NULL)) { + && !_combat_is_shot_blocked(obstacle, obstacle->tile, explosionTile, NULL, NULL)) { if (obstacle == attack->attacker) { attack->attackerFlags &= ~DAM_HIT; attackComputeDamage(attack, 1, 2); @@ -4060,7 +4060,7 @@ void _compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4 attack->extrasHitLocation[index] = HIT_LOCATION_TORSO; attack->extras[index] = obstacle; attackInit(&_explosion_ctd, attack->attacker, obstacle, attack->hitMode, HIT_LOCATION_TORSO); - if (!a4) { + if (!noDamage) { _explosion_ctd.attackerFlags |= DAM_HIT; attackComputeDamage(&_explosion_ctd, 1, 2); } @@ -4282,26 +4282,26 @@ static void _do_random_cripple(int* flagsPtr) } // 0x42436C -int _determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode) +int _determine_to_hit(Object* attacker, Object* defender, int hitLocation, int hitMode) { - return attackDetermineToHit(a1, a1->tile, a2, hitLocation, hitMode, true); + return attackDetermineToHit(attacker, attacker->tile, defender, hitLocation, hitMode, true); } // 0x424380 -int _determine_to_hit_no_range(Object* a1, Object* a2, int hitLocation, int hitMode, unsigned char* a5) +int _determine_to_hit_no_range(Object* attacker, Object* defender, int hitLocation, int hitMode, unsigned char* a5) { - return attackDetermineToHit(a1, a1->tile, a2, hitLocation, hitMode, false); + return attackDetermineToHit(attacker, attacker->tile, defender, hitLocation, hitMode, false); } // 0x424394 -int _determine_to_hit_from_tile(Object* a1, int tile, Object* a3, int hitLocation, int hitMode) +int _determine_to_hit_from_tile(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode) { - return attackDetermineToHit(a1, tile, a3, hitLocation, hitMode, true); + return attackDetermineToHit(attacker, tile, defender, hitLocation, hitMode, true); } // determine_to_hit // 0x4243A8 -static int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, bool a6) +static int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, bool useDistance) { Object* weapon = critterGetWeaponForHitMode(attacker, hitMode); @@ -4311,32 +4311,30 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in bool isRangedWeapon = false; - int accuracy; + int toHit; if (weapon == NULL || isUnarmedHitMode(hitMode)) { - accuracy = skillGetValue(attacker, SKILL_UNARMED); + toHit = skillGetValue(attacker, SKILL_UNARMED); } else { - accuracy = weaponGetSkillValue(attacker, hitMode); - - int modifier = 0; + toHit = weaponGetSkillValue(attacker, hitMode); int attackType = weaponGetAttackTypeForHitMode(weapon, hitMode); if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) { isRangedWeapon = true; - int v29 = 0; - int v25 = 0; + int perceptionBonusMult = 0; + int minEffectiveDist = 0; int weaponPerk = weaponGetPerk(weapon); switch (weaponPerk) { case PERK_WEAPON_LONG_RANGE: - v29 = 4; + perceptionBonusMult = 4; break; case PERK_WEAPON_SCOPE_RANGE: - v29 = 5; - v25 = 8; + perceptionBonusMult = 5; + minEffectiveDist = 8; break; default: - v29 = 2; + perceptionBonusMult = 2; break; } @@ -4347,71 +4345,72 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in perception += 2 * perkGetRank(gDude, PERK_SHARPSHOOTER); } + int distanceMod = 0; // SFALL: Fix for `determine_to_hit_func` function taking distance // into account when called from `determine_to_hit_no_range`. - if (defender != NULL && a6) { - modifier = objectGetDistanceBetweenTiles(attacker, tile, defender, defender->tile); + if (defender != NULL && useDistance) { + distanceMod = objectGetDistanceBetweenTiles(attacker, tile, defender, defender->tile); } else { - modifier = 0; + distanceMod = 0; } - if (modifier >= v25) { - int penalty = attacker == gDude - ? v29 * (perception - 2) - : v29 * perception; + if (distanceMod >= minEffectiveDist) { + int perceptionBonus = attacker == gDude + ? perceptionBonusMult * (perception - 2) + : perceptionBonusMult * perception; - modifier -= penalty; + distanceMod -= perceptionBonus; } else { - modifier += v25; + distanceMod += minEffectiveDist; } - if (-2 * perception > modifier) { - modifier = -2 * perception; + if (distanceMod < -2 * perception) { + distanceMod = -2 * perception; } - if (modifier >= 0) { + if (distanceMod >= 0) { if ((attacker->data.critter.combat.results & DAM_BLIND) != 0) { - modifier *= -12; + distanceMod *= -12; } else { - modifier *= -4; + distanceMod *= -4; } } else { - modifier *= -4; + distanceMod *= -4; } - if (a6 || modifier > 0) { - accuracy += modifier; + if (useDistance || distanceMod > 0) { + toHit += distanceMod; } - modifier = 0; + int numCrittersInLof = 0; - if (defender != NULL && a6) { - _combat_is_shot_blocked(attacker, tile, defender->tile, defender, &modifier); + if (defender != NULL && useDistance) { + _combat_is_shot_blocked(attacker, tile, defender->tile, defender, &numCrittersInLof); } - accuracy -= 10 * modifier; + toHit -= 10 * numCrittersInLof; } if (attacker == gDude && traitIsSelected(TRAIT_ONE_HANDER)) { if (weaponIsTwoHanded(weapon)) { - accuracy -= 40; + toHit -= 40; } else { - accuracy += 20; + toHit += 20; } } int minStrength = weaponGetMinStrengthRequired(weapon); - modifier = minStrength - critterGetStat(attacker, STAT_STRENGTH); + int minStrengthMod = minStrength - critterGetStat(attacker, STAT_STRENGTH); if (attacker == gDude && perkGetRank(gDude, PERK_WEAPON_HANDLING) != 0) { - modifier -= 3; + minStrengthMod -= 3; } - if (modifier > 0) { - accuracy -= 20 * modifier; + if (minStrengthMod > 0) { + toHit -= 20 * minStrengthMod; } if (weaponGetPerk(weapon) == PERK_WEAPON_ACCURATE) { - accuracy += 20; + toHit += 20; } } @@ -4422,17 +4421,17 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in armorClass = 0; } - accuracy -= armorClass; + toHit -= armorClass; } if (isRangedWeapon) { - accuracy += hit_location_penalty[hitLocation]; + toHit += hit_location_penalty[hitLocation]; } else { - accuracy += hit_location_penalty[hitLocation] / 2; + toHit += hit_location_penalty[hitLocation] / 2; } if (defender != NULL && (defender->flags & OBJECT_MULTIHEX) != 0) { - accuracy += 15; + toHit += 15; } if (attacker == gDude) { @@ -4447,45 +4446,45 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in } if (lightIntensity <= 26214) - accuracy -= 40; + toHit -= 40; else if (lightIntensity <= 39321) - accuracy -= 25; + toHit -= 25; else if (lightIntensity <= 52428) - accuracy -= 10; + toHit -= 10; } if (_gcsd != NULL) { - accuracy += _gcsd->accuracyBonus; + toHit += _gcsd->accuracyBonus; } if ((attacker->data.critter.combat.results & DAM_BLIND) != 0) { - accuracy -= 25; + toHit -= 25; } if (targetIsCritter && defender != NULL && (defender->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) { - accuracy += 40; + toHit += 40; } if (attacker->data.critter.combat.team != gDude->data.critter.combat.team) { switch (settings.preferences.combat_difficulty) { case 0: - accuracy -= 20; + toHit -= 20; break; case 2: - accuracy += 20; + toHit += 20; break; } } - if (accuracy > 95) { - accuracy = 95; + if (toHit > 95) { + toHit = 95; } - if (accuracy < -100) { + if (toHit < -100) { debugPrint("Whoa! Bad skill value in determine_to_hit!\n"); } - return accuracy; + return toHit; } // 0x4247B8 @@ -5880,39 +5879,35 @@ void _combat_highlight_change() _combat_highlight = targetHighlight; } -// Probably calculates line of sight or determines if object can see other object. +// Checks if line of fire to the target object is blocked or not. Optionally calculate number of critters on the line of fire. // // 0x426CC4 -bool _combat_is_shot_blocked(Object* a1, int from, int to, Object* a4, int* a5) +bool _combat_is_shot_blocked(Object* sourceObj, int from, int to, Object* targetObj, int* numCrittersOnLof) { - if (a5 != NULL) { - *a5 = 0; + if (numCrittersOnLof != NULL) { + *numCrittersOnLof = 0; } - Object* obstacle = a1; + Object* obstacle = sourceObj; int current = from; while (obstacle != NULL && current != to) { - _make_straight_path_func(a1, current, to, 0, &obstacle, 32, _obj_shoot_blocking_at); + _make_straight_path_func(sourceObj, current, to, 0, &obstacle, 32, _obj_shoot_blocking_at); if (obstacle != NULL) { - if (FID_TYPE(obstacle->fid) != OBJ_TYPE_CRITTER && obstacle != a4) { + if (FID_TYPE(obstacle->fid) != OBJ_TYPE_CRITTER && obstacle != targetObj) { return true; } - if (a5 != NULL) { - if (obstacle != a4) { - if (a4 != NULL) { - // SFALL: Fix for combat_is_shot_blocked_ engine - // function not taking the flags of critters in the - // line of fire into account when calculating the hit - // chance penalty of ranged attacks in - // determine_to_hit_func_ engine function. - if ((obstacle->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_DOWN | DAM_KNOCKED_OUT)) == 0) { - *a5 += 1; - - if ((obstacle->flags & OBJECT_MULTIHEX) != 0) { - *a5 += 1; - } - } + if (numCrittersOnLof != NULL && obstacle != targetObj && targetObj != NULL) { + // SFALL: Fix for combat_is_shot_blocked_ engine + // function not taking the flags of critters in the + // line of fire into account when calculating the hit + // chance penalty of ranged attacks in + // determine_to_hit_func_ engine function. + if ((obstacle->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_DOWN | DAM_KNOCKED_OUT)) == 0) { + *numCrittersOnLof += 1; + + if ((obstacle->flags & OBJECT_MULTIHEX) != 0) { + *numCrittersOnLof += 1; } } } @@ -6830,4 +6825,9 @@ void combat_reset_hit_location_penalty() } } +Attack* combat_get_data() +{ + return &_main_ctd; +} + } // namespace fallout diff --git a/src/combat.h b/src/combat.h index 4dc0430f..6a1667c1 100644 --- a/src/combat.h +++ b/src/combat.h @@ -34,13 +34,13 @@ void _combat_over_from_load(); void _combat_give_exps(int exp_points); void _combat_turn_run(); void _combat(STRUCT_664980* attack); -void attackInit(Attack* attack, Object* a2, Object* a3, int a4, int a5); -int _combat_attack(Object* a1, Object* a2, int a3, int a4); -int _combat_bullet_start(const Object* a1, const Object* a2); -void _compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4); +void attackInit(Attack* attack, Object* attacker, Object* defender, int hitMode, int hitLocation); +int _combat_attack(Object* attacker, Object* defender, int hitMode, int hitLocation); +int _combat_bullet_start(const Object* attacker, const Object* target); +void _compute_explosion_on_extras(Attack* attack, bool isFromAttacker, bool isGrenade, bool noDamage); int _determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode); -int _determine_to_hit_no_range(Object* a1, Object* a2, int a3, int a4, unsigned char* a5); -int _determine_to_hit_from_tile(Object* a1, int a2, Object* a3, int a4, int a5); +int _determine_to_hit_no_range(Object* attacker, Object* defender, int hitLocation, int hitMode, unsigned char* a5); +int _determine_to_hit_from_tile(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode); void attackComputeDeathFlags(Attack* attack); void _apply_damage(Attack* attack, bool animated); void _combat_display(Attack* attack); @@ -52,7 +52,7 @@ void _combat_attack_this(Object* a1); void _combat_outline_on(); void _combat_outline_off(); void _combat_highlight_change(); -bool _combat_is_shot_blocked(Object* a1, int from, int to, Object* a4, int* a5); +bool _combat_is_shot_blocked(Object* sourceObj, int from, int to, Object* targetObj, int* numCrittersOnLof); int _combat_player_knocked_out_by(); int _combat_explode_scenery(Object* a1, Object* a2); void _combat_delete_critter(Object* obj); @@ -74,6 +74,7 @@ bool damageModGetDisplayBonusDamage(); int combat_get_hit_location_penalty(int hit_location); void combat_set_hit_location_penalty(int hit_location, int penalty); void combat_reset_hit_location_penalty(); +Attack* combat_get_data(); static inline bool isInCombat() { diff --git a/src/combat_ai.cc b/src/combat_ai.cc index 797d9e06..2110b9da 100644 --- a/src/combat_ai.cc +++ b/src/combat_ai.cc @@ -701,6 +701,30 @@ static int aiPacketWrite(File* stream, AiPacket* ai) return 0; } +// 0x428058 +int combat_ai_num() +{ + return gAiPacketsLength; +} + +// 0x428060 +char* combat_ai_name(int packet_num) +{ + int index; + + if (packet_num < 0 || packet_num >= gAiPacketsLength) { + return NULL; + } + + for (index = 0; index < gAiPacketsLength; index++) { + if (gAiPackets[index].packet_num == packet_num) { + return gAiPackets[index].name; + } + } + + return NULL; +} + // Get ai from object // // 0x4280B4 @@ -2327,61 +2351,61 @@ static int _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender) } // 0x429FC8 -static int _ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, bool taunt) +static int _ai_move_steps_closer(Object* critter, Object* target, int actionPoints, bool taunt) { if (actionPoints <= 0) { return -1; } - int distance = aiGetDistance(a1); + int distance = aiGetDistance(critter); if (distance == DISTANCE_STAY) { return -1; } if (distance == DISTANCE_STAY_CLOSE) { - if (a2 != gDude) { - int currentDistance = objectGetDistanceBetween(a1, gDude); + if (target != gDude) { + int currentDistance = objectGetDistanceBetween(critter, gDude); if (currentDistance > 5 - && objectGetDistanceBetween(a2, gDude) > 5 + && objectGetDistanceBetween(target, gDude) > 5 && currentDistance + actionPoints > 5) { return -1; } } } - if (objectGetDistanceBetween(a1, a2) <= 1) { + if (objectGetDistanceBetween(critter, target) <= 1) { return -1; } reg_anim_begin(ANIMATION_REQUEST_RESERVED); if (taunt) { - _combatai_msg(a1, NULL, AI_MESSAGE_TYPE_MOVE, 0); + _combatai_msg(critter, NULL, AI_MESSAGE_TYPE_MOVE, 0); } - Object* v18 = a2; + Object* initialTarget = target; bool shouldUnhide; - if ((a2->flags & OBJECT_MULTIHEX) != 0) { + if ((target->flags & OBJECT_MULTIHEX) != 0) { shouldUnhide = true; - a2->flags |= OBJECT_HIDDEN; + target->flags |= OBJECT_HIDDEN; } else { shouldUnhide = false; } - if (pathfinderFindPath(a1, a1->tile, a2->tile, NULL, 0, _obj_blocking_at) == 0) { + if (pathfinderFindPath(critter, critter->tile, target->tile, NULL, 0, _obj_blocking_at) == 0) { _moveBlockObj = NULL; - if (pathfinderFindPath(a1, a1->tile, a2->tile, NULL, 0, _obj_ai_blocking_at) == 0 + if (pathfinderFindPath(critter, critter->tile, target->tile, NULL, 0, _obj_ai_blocking_at) == 0 && _moveBlockObj != NULL && PID_TYPE(_moveBlockObj->pid) == OBJ_TYPE_CRITTER) { if (shouldUnhide) { - a2->flags &= ~OBJECT_HIDDEN; + target->flags &= ~OBJECT_HIDDEN; } - a2 = _moveBlockObj; - if ((a2->flags & OBJECT_MULTIHEX) != 0) { + target = _moveBlockObj; + if ((target->flags & OBJECT_MULTIHEX) != 0) { shouldUnhide = true; - a2->flags |= OBJECT_HIDDEN; + target->flags |= OBJECT_HIDDEN; } else { shouldUnhide = false; } @@ -2389,25 +2413,25 @@ static int _ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, bool } if (shouldUnhide) { - a2->flags &= ~OBJECT_HIDDEN; + target->flags &= ~OBJECT_HIDDEN; } - int tile = a2->tile; - if (a2 == v18) { - _cai_retargetTileFromFriendlyFire(a1, a2, &tile); + int tile = target->tile; + if (target == initialTarget) { + _cai_retargetTileFromFriendlyFire(critter, target, &tile); } - if (actionPoints >= critterGetStat(a1, STAT_MAXIMUM_ACTION_POINTS) / 2 && artCritterFidShouldRun(a1->fid)) { - if ((a2->flags & OBJECT_MULTIHEX) != 0) { - animationRegisterRunToObject(a1, a2, actionPoints, 0); + if (actionPoints >= critterGetStat(critter, STAT_MAXIMUM_ACTION_POINTS) / 2 && artCritterFidShouldRun(critter->fid)) { + if ((target->flags & OBJECT_MULTIHEX) != 0) { + animationRegisterRunToObject(critter, target, actionPoints, 0); } else { - animationRegisterRunToTile(a1, tile, a1->elevation, actionPoints, 0); + animationRegisterRunToTile(critter, tile, critter->elevation, actionPoints, 0); } } else { - if ((a2->flags & OBJECT_MULTIHEX) != 0) { - animationRegisterMoveToObject(a1, a2, actionPoints, 0); + if ((target->flags & OBJECT_MULTIHEX) != 0) { + animationRegisterMoveToObject(critter, target, actionPoints, 0); } else { - animationRegisterMoveToTile(a1, tile, a1->elevation, actionPoints, 0); + animationRegisterMoveToTile(critter, tile, critter->elevation, actionPoints, 0); } } @@ -2665,35 +2689,35 @@ static int _ai_attack(Object* attacker, Object* defender, int hitMode) } // 0x42A7D8 -static int _ai_try_attack(Object* a1, Object* a2) +static int _ai_try_attack(Object* attacker, Object* defender) { - _critter_set_who_hit_me(a1, a2); + _critter_set_who_hit_me(attacker, defender); - CritterCombatData* combatData = &(a1->data.critter.combat); + CritterCombatData* combatData = &(attacker->data.critter.combat); bool taunt = true; - Object* weapon = critterGetItem2(a1); + Object* weapon = critterGetItem2(attacker); if (weapon != NULL && itemGetType(weapon) != ITEM_TYPE_WEAPON) { weapon = NULL; } - int hitMode = _ai_pick_hit_mode(a1, weapon, a2); - int minToHit = aiGetPacket(a1)->min_to_hit; + int hitMode = _ai_pick_hit_mode(attacker, weapon, defender); + int minToHit = aiGetPacket(attacker)->min_to_hit; - int actionPoints = a1->data.critter.combat.ap; + int actionPoints = attacker->data.critter.combat.ap; int safeDistance = 0; - int v42 = 0; + int actionPointsToUse = 0; if (weapon != NULL - || (critterGetBodyType(a2) == BODY_TYPE_BIPED - && ((a2->fid & 0xF000) >> 12 == 0) - && artExists(buildFid(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_THROW_PUNCH, 0, a1->rotation + 1)))) { + || (critterGetBodyType(defender) == BODY_TYPE_BIPED + && ((defender->fid & 0xF000) >> 12 == 0) + && artExists(buildFid(OBJ_TYPE_CRITTER, attacker->fid & 0xFFF, ANIM_THROW_PUNCH, 0, attacker->rotation + 1)))) { // SFALL: Check the safety of weapons based on the selected attack mode // instead of always the primary weapon hit mode. - if (_combat_safety_invalidate_weapon(a1, weapon, hitMode, a2, &safeDistance)) { - _ai_switch_weapons(a1, &hitMode, &weapon, a2); + if (_combat_safety_invalidate_weapon(attacker, weapon, hitMode, defender, &safeDistance)) { + _ai_switch_weapons(attacker, &hitMode, &weapon, defender); } } else { - _ai_switch_weapons(a1, &hitMode, &weapon, a2); + _ai_switch_weapons(attacker, &hitMode, &weapon, defender); } unsigned char rotations[800]; @@ -2704,37 +2728,37 @@ static int _ai_try_attack(Object* a1, Object* a2) break; } - int reason = _combat_check_bad_shot(a1, a2, hitMode, false); + int reason = _combat_check_bad_shot(attacker, defender, hitMode, false); if (reason == COMBAT_BAD_SHOT_NO_AMMO) { // out of ammo - if (aiHaveAmmo(a1, weapon, &ammo)) { + if (aiHaveAmmo(attacker, weapon, &ammo)) { int remainingAmmoQuantity = weaponReload(weapon, ammo); if (remainingAmmoQuantity == 0 && ammo != NULL) { _obj_destroy(ammo); } if (remainingAmmoQuantity != -1) { - int volume = _gsound_compute_relative_volume(a1); + int volume = _gsound_compute_relative_volume(attacker); const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_READY, weapon, hitMode, NULL); _gsound_play_sfx_file_volume(sfx, volume); - _ai_magic_hands(a1, weapon, 5002); + _ai_magic_hands(attacker, weapon, 5002); // SFALL: Fix incorrect AP cost when AI reloads a weapon. // CE: There is a commented out code which checks // available action points before performing reload. Not // sure why it was commented, probably needs additional // testing. - int actionPointsRequired = weaponGetActionPointCost(a1, HIT_MODE_RIGHT_WEAPON_RELOAD, false); - if (a1->data.critter.combat.ap >= actionPointsRequired) { - a1->data.critter.combat.ap -= actionPointsRequired; + int actionPointsRequired = weaponGetActionPointCost(attacker, HIT_MODE_RIGHT_WEAPON_RELOAD, false); + if (attacker->data.critter.combat.ap >= actionPointsRequired) { + attacker->data.critter.combat.ap -= actionPointsRequired; } else { - a1->data.critter.combat.ap = 0; + attacker->data.critter.combat.ap = 0; } } } else { - ammo = _ai_search_environ(a1, ITEM_TYPE_AMMO); + ammo = _ai_search_environ(attacker, ITEM_TYPE_AMMO); if (ammo != NULL) { - ammo = _ai_retrieve_object(a1, ammo); + ammo = _ai_retrieve_object(attacker, ammo); if (ammo != NULL) { int remainingAmmoQuantity = weaponReload(weapon, ammo); if (remainingAmmoQuantity == 0) { @@ -2742,62 +2766,63 @@ static int _ai_try_attack(Object* a1, Object* a2) } if (remainingAmmoQuantity != -1) { - int volume = _gsound_compute_relative_volume(a1); + int volume = _gsound_compute_relative_volume(attacker); const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_READY, weapon, hitMode, NULL); _gsound_play_sfx_file_volume(sfx, volume); - _ai_magic_hands(a1, weapon, 5002); + _ai_magic_hands(attacker, weapon, 5002); // SFALL: Fix incorrect AP cost when AI reloads a // weapon. // CE: See note above, probably need to check // available action points before performing // reload. - int actionPointsRequired = weaponGetActionPointCost(a1, HIT_MODE_RIGHT_WEAPON_RELOAD, false); - if (a1->data.critter.combat.ap >= actionPointsRequired) { - a1->data.critter.combat.ap -= actionPointsRequired; + int actionPointsRequired = weaponGetActionPointCost(attacker, HIT_MODE_RIGHT_WEAPON_RELOAD, false); + if (attacker->data.critter.combat.ap >= actionPointsRequired) { + attacker->data.critter.combat.ap -= actionPointsRequired; } else { - a1->data.critter.combat.ap = 0; + attacker->data.critter.combat.ap = 0; } } } } else { - int volume = _gsound_compute_relative_volume(a1); + int volume = _gsound_compute_relative_volume(attacker); const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_OUT_OF_AMMO, weapon, hitMode, NULL); _gsound_play_sfx_file_volume(sfx, volume); - _ai_magic_hands(a1, weapon, 5001); + _ai_magic_hands(attacker, weapon, 5001); - if (_inven_unwield(a1, 1) == 0) { + if (_inven_unwield(attacker, 1) == 0) { _combat_turn_run(); } - _ai_switch_weapons(a1, &hitMode, &weapon, a2); + _ai_switch_weapons(attacker, &hitMode, &weapon, defender); } } } else if (reason == COMBAT_BAD_SHOT_NOT_ENOUGH_AP || reason == COMBAT_BAD_SHOT_ARM_CRIPPLED || reason == COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED) { // 3 - not enough action points // 6 - crippled one arm for two-handed weapon // 7 - both hands crippled - if (_ai_switch_weapons(a1, &hitMode, &weapon, a2) == -1) { + if (_ai_switch_weapons(attacker, &hitMode, &weapon, defender) == -1) { return -1; } } else if (reason == COMBAT_BAD_SHOT_OUT_OF_RANGE) { // target out of range - int accuracy = _determine_to_hit_no_range(a1, a2, HIT_LOCATION_UNCALLED, hitMode, rotations); - if (accuracy < minToHit) { - debugPrint("%s: FLEEING: Can't possibly Hit Target!", critterGetName(a1)); - _ai_run_away(a1, a2); + int toHitNoRange = _determine_to_hit_no_range(attacker, defender, HIT_LOCATION_UNCALLED, hitMode, rotations); + if (toHitNoRange < minToHit) { + // hit chance is too low even at point blank range (not taking range into account) + debugPrint("%s: FLEEING: Can't possibly Hit Target!", critterGetName(attacker)); + _ai_run_away(attacker, defender); return 0; } if (weapon != NULL) { - if (_ai_move_steps_closer(a1, a2, actionPoints, taunt) == -1) { + if (_ai_move_steps_closer(attacker, defender, actionPoints, taunt) == -1) { return -1; } taunt = false; } else { - if (_ai_switch_weapons(a1, &hitMode, &weapon, a2) == -1 || weapon == NULL) { + if (_ai_switch_weapons(attacker, &hitMode, &weapon, defender) == -1 || weapon == NULL) { // NOTE: Uninline. - if (_ai_move_closer(a1, a2, taunt) == -1) { + if (_ai_move_closer(attacker, defender, taunt) == -1) { return -1; } } @@ -2805,66 +2830,66 @@ static int _ai_try_attack(Object* a1, Object* a2) } } else if (reason == COMBAT_BAD_SHOT_AIM_BLOCKED) { // aim is blocked - if (_ai_move_steps_closer(a1, a2, a1->data.critter.combat.ap, taunt) == -1) { + if (_ai_move_steps_closer(attacker, defender, attacker->data.critter.combat.ap, taunt) == -1) { return -1; } taunt = false; } else if (reason == COMBAT_BAD_SHOT_OK) { - int accuracy = _determine_to_hit(a1, a2, HIT_LOCATION_UNCALLED, hitMode); + int accuracy = _determine_to_hit(attacker, defender, HIT_LOCATION_UNCALLED, hitMode); if (safeDistance != 0) { - if (_ai_move_away(a1, a2, safeDistance) == -1) { + if (_ai_move_away(attacker, defender, safeDistance) == -1) { return -1; } } if (accuracy < minToHit) { - int accuracyNoRange = _determine_to_hit_no_range(a1, a2, HIT_LOCATION_UNCALLED, hitMode, rotations); - if (accuracyNoRange < minToHit) { - debugPrint("%s: FLEEING: Can't possibly Hit Target!", critterGetName(a1)); - _ai_run_away(a1, a2); + int toHitNoRange = _determine_to_hit_no_range(attacker, defender, HIT_LOCATION_UNCALLED, hitMode, rotations); + if (toHitNoRange < minToHit) { + debugPrint("%s: FLEEING: Can't possibly Hit Target!", critterGetName(attacker)); + _ai_run_away(attacker, defender); return 0; } if (actionPoints > 0) { - int v24 = pathfinderFindPath(a1, a1->tile, a2->tile, rotations, 0, _obj_blocking_at); - if (v24 == 0) { - v42 = actionPoints; + int pathLength = pathfinderFindPath(attacker, attacker->tile, defender->tile, rotations, 0, _obj_blocking_at); + if (pathLength == 0) { + actionPointsToUse = actionPoints; } else { - if (v24 < actionPoints) { - actionPoints = v24; + if (pathLength < actionPoints) { + actionPoints = pathLength; } - int tile = a1->tile; + int tile = attacker->tile; int index; for (index = 0; index < actionPoints; index++) { tile = tileGetTileInDirection(tile, rotations[index], 1); - v42++; + actionPointsToUse++; - int v27 = _determine_to_hit_from_tile(a1, tile, a2, HIT_LOCATION_UNCALLED, hitMode); - if (v27 >= minToHit) { + int toHit = _determine_to_hit_from_tile(attacker, tile, defender, HIT_LOCATION_UNCALLED, hitMode); + if (toHit >= minToHit) { break; } } if (index == actionPoints) { - v42 = actionPoints; + actionPointsToUse = actionPoints; } } } - if (_ai_move_steps_closer(a1, a2, v42, taunt) == -1) { - debugPrint("%s: FLEEING: Can't possibly get closer to Target!", critterGetName(a1)); - _ai_run_away(a1, a2); + if (_ai_move_steps_closer(attacker, defender, actionPointsToUse, taunt) == -1) { + debugPrint("%s: FLEEING: Can't possibly get closer to Target!", critterGetName(attacker)); + _ai_run_away(attacker, defender); return 0; } taunt = false; - if (_ai_attack(a1, a2, hitMode) == -1 || weaponGetActionPointCost(a1, hitMode, 0) > a1->data.critter.combat.ap) { + if (_ai_attack(attacker, defender, hitMode) == -1 || weaponGetActionPointCost(attacker, hitMode, 0) > attacker->data.critter.combat.ap) { return -1; } } else { - if (_ai_attack(a1, a2, hitMode) == -1 || weaponGetActionPointCost(a1, hitMode, 0) > a1->data.critter.combat.ap) { + if (_ai_attack(attacker, defender, hitMode) == -1 || weaponGetActionPointCost(attacker, hitMode, 0) > attacker->data.critter.combat.ap) { return -1; } } diff --git a/src/combat_ai.h b/src/combat_ai.h index 12cf21dc..1e11dfb6 100644 --- a/src/combat_ai.h +++ b/src/combat_ai.h @@ -30,6 +30,8 @@ void aiReset(); int aiExit(); int aiLoad(File* stream); int aiSave(File* stream); +int combat_ai_num(); +char* combat_ai_name(int packet_num); int aiGetAreaAttackMode(Object* obj); int aiGetRunAwayMode(Object* obj); int aiGetBestWeapon(Object* obj); diff --git a/src/critter.cc b/src/critter.cc index 4439eb03..c5765497 100644 --- a/src/critter.cc +++ b/src/critter.cc @@ -1396,4 +1396,40 @@ bool _critter_flag_check(int pid, int flag) return (proto->critter.data.flags & flag) != 0; } +// 0x42E6F0 +void critter_flag_set(int pid, int flag) +{ + Proto* proto; + + if (pid == -1) { + return; + } + + if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) { + return; + } + + protoGetProto(pid, &proto); + + proto->critter.data.flags |= flag; +} + +// 0x42E71C +void critter_flag_unset(int pid, int flag) +{ + Proto* proto; + + if (pid == -1) { + return; + } + + if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) { + return; + } + + protoGetProto(pid, &proto); + + proto->critter.data.flags &= ~flag; +} + } // namespace fallout diff --git a/src/critter.h b/src/critter.h index 7423834a..ef4f873c 100644 --- a/src/critter.h +++ b/src/critter.h @@ -70,6 +70,8 @@ int critterGetMovementPointCostAdjustedForCrippledLegs(Object* critter, int a2); bool critterIsEncumbered(Object* critter); bool critterIsFleeing(Object* a1); bool _critter_flag_check(int pid, int flag); +void critter_flag_set(int pid, int flag); +void critter_flag_unset(int pid, int flag); } // namespace fallout diff --git a/src/debug.cc b/src/debug.cc index 3c7faced..fd149a99 100644 --- a/src/debug.cc +++ b/src/debug.cc @@ -60,7 +60,7 @@ void _debug_register_mono() // 0x4C6D18 void _debug_register_log(const char* fileName, const char* mode) { - if ((mode[0] == 'w' && mode[1] == 'a') && mode[1] == 't') { + if ((mode[0] == 'w' || mode[0] == 'a') && mode[1] == 't') { if (_fd != NULL) { fclose(_fd); } @@ -108,14 +108,8 @@ void _debug_register_env() // NOTE: Uninline. _debug_register_screen(); } else if (strcmp(copy, "gnw") == 0) { - if (gDebugPrintProc != _win_debug) { - if (_fd != NULL) { - fclose(_fd); - _fd = NULL; - } - - gDebugPrintProc = _win_debug; - } + // NOTE: Uninline. + _debug_register_func(_win_debug); } internal_free(copy); diff --git a/src/draw.cc b/src/draw.cc index 674b0acc..721a95e4 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -27,7 +27,7 @@ void bufferDrawLine(unsigned char* buf, int pitch, int x1, int y1, int x2, int y p1 = buf + pitch * y1 + x1; p2 = buf + pitch * y2 + x2; - while (p1 < p2) { + while (p1 <= p2) { *p1 = color; *p2 = color; p1 += pitch; diff --git a/src/game.cc b/src/game.cc index abe6613b..648e501d 100644 --- a/src/game.cc +++ b/src/game.cc @@ -65,6 +65,7 @@ #include "trait.h" #include "version.h" #include "window_manager.h" +#include "window_manager_private.h" #include "worldmap.h" namespace fallout { @@ -168,6 +169,20 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4 showSplash(); } + // CE: Handle debug mode (exactly as seen in `mapper2.exe`). + const char* debugMode = settings.debug.mode.c_str(); + if (compat_stricmp(debugMode, "environment") == 0) { + _debug_register_env(); + } else if (compat_stricmp(debugMode, "screen") == 0) { + _debug_register_screen(); + } else if (compat_stricmp(debugMode, "log") == 0) { + _debug_register_log("debug.log", "wt"); + } else if (compat_stricmp(debugMode, "mono") == 0) { + _debug_register_mono(); + } else if (compat_stricmp(debugMode, "gnw") == 0) { + _debug_register_func(_win_debug); + } + interfaceFontsInit(); fontManagerAdd(&gModernFontManager); fontSetCurrent(font); diff --git a/src/game_config.cc b/src/game_config.cc index dde349d7..7063ec79 100644 --- a/src/game_config.cc +++ b/src/game_config.cc @@ -125,10 +125,26 @@ bool gameConfigInit(bool isMapper, int argc, char** argv) char* ch = strrchr(executable, '\\'); if (ch != NULL) { *ch = '\0'; - snprintf(gGameConfigFilePath, sizeof(gGameConfigFilePath), "%s\\%s", executable, GAME_CONFIG_FILE_NAME); + if (isMapper) { + snprintf(gGameConfigFilePath, + sizeof(gGameConfigFilePath), + "%s\\%s", + executable, + MAPPER_CONFIG_FILE_NAME); + } else { + snprintf(gGameConfigFilePath, + sizeof(gGameConfigFilePath), + "%s\\%s", + executable, + GAME_CONFIG_FILE_NAME); + } *ch = '\\'; } else { - strcpy(gGameConfigFilePath, GAME_CONFIG_FILE_NAME); + if (isMapper) { + strcpy(gGameConfigFilePath, MAPPER_CONFIG_FILE_NAME); + } else { + strcpy(gGameConfigFilePath, GAME_CONFIG_FILE_NAME); + } } // Read contents of `fallout2.cfg` into config. The values from the file diff --git a/src/game_config.h b/src/game_config.h index a4447623..78a12992 100644 --- a/src/game_config.h +++ b/src/game_config.h @@ -5,8 +5,8 @@ namespace fallout { -// The file name of the main config file. #define GAME_CONFIG_FILE_NAME "fallout2.cfg" +#define MAPPER_CONFIG_FILE_NAME "mapper2.cfg" #define GAME_CONFIG_SYSTEM_KEY "system" #define GAME_CONFIG_PREFERENCES_KEY "preferences" diff --git a/src/game_mouse.cc b/src/game_mouse.cc index 9f82fcd1..193ee4f1 100644 --- a/src/game_mouse.cc +++ b/src/game_mouse.cc @@ -1328,6 +1328,12 @@ int gameMouseGetCursor() return gGameMouseCursor; } +// 0x44C9F0 +void gmouse_set_mapper_mode(int mode) +{ + _gmouse_mapper_mode = mode; +} + // 0x44C9F8 void _gmouse_3d_enable_modes() { @@ -2458,4 +2464,9 @@ void gameMouseRefreshImmediately() renderPresent(); } +Object* gmouse_get_outlined_object() +{ + return gGameMouseHighlightedItem; +} + } // namespace fallout diff --git a/src/game_mouse.h b/src/game_mouse.h index fc656332..73544b25 100644 --- a/src/game_mouse.h +++ b/src/game_mouse.h @@ -86,6 +86,7 @@ void gameMouseRefresh(); void _gmouse_handle_event(int mouseX, int mouseY, int mouseState); int gameMouseSetCursor(int cursor); int gameMouseGetCursor(); +void gmouse_set_mapper_mode(int mode); void gameMouseSetMode(int a1); int gameMouseGetMode(); void gameMouseCycleMode(); @@ -102,6 +103,7 @@ void gameMouseLoadItemHighlight(); void _gmouse_remove_item_outline(Object* object); void gameMouseRefreshImmediately(); +Object* gmouse_get_outlined_object(); } // namespace fallout diff --git a/src/game_sound.cc b/src/game_sound.cc index 618457b6..f1dc99b8 100644 --- a/src/game_sound.cc +++ b/src/game_sound.cc @@ -1370,7 +1370,7 @@ char* gameSoundBuildInterfaceName(const char* a1) // 0x451760 char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* target) { - int v6; + int soundVariant; char weaponSoundCode; char effectTypeCode; char materialCode; @@ -1384,12 +1384,12 @@ char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* ta if (hitMode != HIT_MODE_LEFT_WEAPON_PRIMARY && hitMode != HIT_MODE_RIGHT_WEAPON_PRIMARY && hitMode != HIT_MODE_PUNCH) { - v6 = 2; + soundVariant = 2; } else { - v6 = 1; + soundVariant = 1; } } else { - v6 = 1; + soundVariant = 1; } int damageType = weaponGetDamageType(NULL, weapon); @@ -1438,7 +1438,7 @@ char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* ta } } - snprintf(_sfx_file_name, sizeof(_sfx_file_name), "W%c%c%1d%cXX%1d", effectTypeCode, weaponSoundCode, v6, materialCode, 1); + snprintf(_sfx_file_name, sizeof(_sfx_file_name), "W%c%c%1d%cXX%1d", effectTypeCode, weaponSoundCode, soundVariant, materialCode, 1); compat_strupr(_sfx_file_name); return _sfx_file_name; } diff --git a/src/geometry.cc b/src/geometry.cc index e18eabb4..9eb97fbe 100644 --- a/src/geometry.cc +++ b/src/geometry.cc @@ -107,6 +107,62 @@ void _rect_clip_list(RectListNode** rectListNodePtr, Rect* rect) } } +// 0x4C6AAC +RectListNode* rect_clip(Rect* b, Rect* t) +{ + RectListNode* list = NULL; + Rect clipped_t; + + if (rectIntersection(t, b, &clipped_t) == 0) { + RectListNode** next = &list; + Rect clipped_b[4]; + int k; + + clipped_b[0].left = b->left; + clipped_b[0].top = b->top; + clipped_b[0].right = b->right; + clipped_b[0].bottom = clipped_t.top - 1; + + clipped_b[1].left = b->left; + clipped_b[1].top = clipped_t.top; + clipped_b[1].right = clipped_t.left - 1; + clipped_b[1].bottom = clipped_t.bottom; + + clipped_b[2].left = clipped_t.right + 1; + clipped_b[2].top = clipped_t.top; + clipped_b[2].right = b->right; + clipped_b[2].bottom = clipped_t.bottom; + + clipped_b[3].left = b->left; + clipped_b[3].top = clipped_t.bottom + 1; + clipped_b[3].right = b->right; + clipped_b[3].bottom = b->bottom; + + for (k = 0; k < 4; k++) { + if (clipped_b[k].left <= clipped_b[k].right && clipped_b[k].top <= clipped_b[k].bottom) { + *next = _rect_malloc(); + if (*next == NULL) { + return NULL; + } + + (*next)->rect = clipped_b[k]; + (*next)->next = NULL; + next = &((*next)->next); + } + } + } else { + list = _rect_malloc(); + if (list == NULL) { + return NULL; + } + + list->rect = *b; + list->next = NULL; + } + + return list; +} + // 0x4C6BB8 RectListNode* _rect_malloc() { diff --git a/src/geometry.h b/src/geometry.h index da322fe7..095258c1 100644 --- a/src/geometry.h +++ b/src/geometry.h @@ -27,6 +27,7 @@ typedef struct RectListNode { void _GNW_rect_exit(); void _rect_clip_list(RectListNode** rectListNodePtr, Rect* rect); +RectListNode* rect_clip(Rect* b, Rect* t); RectListNode* _rect_malloc(); void _rect_free(RectListNode* entry); void rectUnion(const Rect* s1, const Rect* s2, Rect* r); diff --git a/src/graph_lib.cc b/src/graph_lib.cc index 08a65dd9..6a432bda 100644 --- a/src/graph_lib.cc +++ b/src/graph_lib.cc @@ -52,6 +52,14 @@ unsigned char HighRGB(unsigned char color) return std::max(std::max(r, g), b); } +// 0x44ED98 +int load_lbm_to_buf(const char* path, unsigned char* buffer, int a3, int a4, int a5, int a6, int a7) +{ + // TODO: Incomplete. + + return -1; +} + // 0x44F250 int graphCompress(unsigned char* a1, unsigned char* a2, int a3) { diff --git a/src/graph_lib.h b/src/graph_lib.h index 4364973e..8278c6a7 100644 --- a/src/graph_lib.h +++ b/src/graph_lib.h @@ -4,6 +4,7 @@ namespace fallout { unsigned char HighRGB(unsigned char color); +int load_lbm_to_buf(const char* path, unsigned char* buffer, int a3, int a4, int a5, int a6, int a7); int graphCompress(unsigned char* a1, unsigned char* a2, int a3); int graphDecompress(unsigned char* a1, unsigned char* a2, int a3); void grayscalePaletteUpdate(int a1, int a2); diff --git a/src/input.cc b/src/input.cc index 38abf11c..1cfa9a05 100644 --- a/src/input.cc +++ b/src/input.cc @@ -245,6 +245,13 @@ int inputGetInput() return -1; } +// 0x4C8BC8 +void get_input_position(int* x, int* y) +{ + *x = _input_mx; + *y = _input_my; +} + // 0x4C8BDC void _process_bk() { diff --git a/src/input.h b/src/input.h index c90ae555..8671ea38 100644 --- a/src/input.h +++ b/src/input.h @@ -20,6 +20,7 @@ typedef int(ScreenshotHandler)(int width, int height, unsigned char* buffer, uns int inputInit(int a1); void inputExit(); int inputGetInput(); +void get_input_position(int* x, int* y); void _process_bk(); void enqueueInputEvent(int a1); void inputEventQueueReset(); diff --git a/src/interpreter.cc b/src/interpreter.cc index ab866cee..a147ce8a 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -31,13 +31,13 @@ static int _outputStr(char* a1); static int _checkWait(Program* program); static char* programGetCurrentProcedureName(Program* s); static opcode_t stackReadInt16(unsigned char* data, int pos); -static int stackReadInt32(unsigned char* a1, int a2); -static void stackWriteInt16(int value, unsigned char* a2, int a3); -static void stackWriteInt32(int value, unsigned char* stack, int pos); -static void stackPushInt16(unsigned char* a1, int* a2, int value); -static void stackPushInt32(unsigned char* a1, int* a2, int value); -static int stackPopInt32(unsigned char* a1, int* a2); -static opcode_t stackPopInt16(unsigned char* a1, int* a2); +static int stackReadInt32(unsigned char* data, int pos); +static void stackWriteInt16(int value, unsigned char* data, int pos); +static void stackWriteInt32(int value, unsigned char* data, int pos); +static void stackPushInt16(unsigned char* data, int* pointer, int value); +static void stackPushInt32(unsigned char* data, int* pointer, int value); +static int stackPopInt32(unsigned char* data, int* pointer); +static opcode_t stackPopInt16(unsigned char* data, int* pointer); static void _interpretIncStringRef(Program* program, opcode_t opcode, int value); static void programReturnStackPushInt16(Program* program, int value); static opcode_t programReturnStackPopInt16(Program* program); @@ -51,7 +51,7 @@ static void opPush(Program* program); static void opPushBase(Program* program); static void opPopBase(Program* program); static void opPopToBase(Program* program); -static void op802C(Program* program); +static void opSetGlobal(Program* program); static void opDump(Program* program); static void opDelayedCall(Program* program); static void opConditionalCall(Program* program); @@ -230,17 +230,17 @@ static char* programGetCurrentProcedureName(Program* program) int procedureCount = stackReadInt32(program->procedures, 0); unsigned char* ptr = program->procedures + 4; - int procedureOffset = stackReadInt32(ptr, 16); - int identifierOffset = stackReadInt32(ptr, 0); + int procedureOffset = stackReadInt32(ptr, offsetof(Procedure, bodyOffset)); + int identifierOffset = stackReadInt32(ptr, offsetof(Procedure, nameOffset)); for (int index = 0; index < procedureCount; index++) { - int nextProcedureOffset = stackReadInt32(ptr + 24, 16); + int nextProcedureOffset = stackReadInt32(ptr + 24, offsetof(Procedure, bodyOffset)); if (program->instructionPointer >= procedureOffset && program->instructionPointer < nextProcedureOffset) { return (char*)(program->identifiers + identifierOffset); } ptr += 24; - identifierOffset = stackReadInt32(ptr, 0); + identifierOffset = stackReadInt32(ptr, offsetof(Procedure, nameOffset)); } return _aCouldnTFindPro; @@ -712,7 +712,7 @@ static void opPopToBase(Program* program) } // 0x467DE0 -static void op802C(Program* program) +static void opSetGlobal(Program* program) { program->basePointer = program->stackValues->size(); } @@ -745,10 +745,10 @@ static void opDelayedCall(Program* program) delay += 1000 * _timerFunc() / _timerTick; } - int flags = stackReadInt32(procedure_ptr, 4); + int flags = stackReadInt32(procedure_ptr, offsetof(Procedure, flags)); - stackWriteInt32(delay, procedure_ptr, 8); - stackWriteInt32(flags | PROCEDURE_FLAG_TIMED, procedure_ptr, 4); + stackWriteInt32(delay, procedure_ptr, offsetof(Procedure, time)); + stackWriteInt32(flags | PROCEDURE_FLAG_TIMED, procedure_ptr, offsetof(Procedure, flags)); } // 0x468034 @@ -761,10 +761,10 @@ static void opConditionalCall(Program* program) } unsigned char* procedure_ptr = program->procedures + 4 + 24 * data[0]; - int flags = stackReadInt32(procedure_ptr, 4); + int flags = stackReadInt32(procedure_ptr, offsetof(Procedure, flags)); - stackWriteInt32(flags | PROCEDURE_FLAG_CONDITIONAL, procedure_ptr, 4); - stackWriteInt32(data[1], procedure_ptr, 12); + stackWriteInt32(flags | PROCEDURE_FLAG_CONDITIONAL, procedure_ptr, offsetof(Procedure, flags)); + stackWriteInt32(data[1], procedure_ptr, offsetof(Procedure, conditionOffset)); } // 0x46817C @@ -788,9 +788,9 @@ static void opCancel(Program* program) } Procedure* proc = (Procedure*)(program->procedures + 4 + data * sizeof(*proc)); - proc->field_4 = 0; - proc->field_8 = 0; - proc->field_C = 0; + proc->flags = 0; + proc->time = 0; + proc->conditionOffset = 0; } // 0x468330 @@ -802,9 +802,9 @@ static void opCancelAll(Program* program) // TODO: Original code uses different approach, check. Procedure* proc = (Procedure*)(program->procedures + 4 + index * sizeof(*proc)); - proc->field_4 = 0; - proc->field_8 = 0; - proc->field_C = 0; + proc->flags = 0; + proc->time = 0; + proc->conditionOffset = 0; } } @@ -2030,12 +2030,12 @@ static void opCall(Program* program) unsigned char* ptr = program->procedures + 4 + 24 * value; - int flags = stackReadInt32(ptr, 4); - if ((flags & 4) != 0) { + int flags = stackReadInt32(ptr, offsetof(Procedure, flags)); + if ((flags & PROCEDURE_FLAG_IMPORTED) != 0) { // TODO: Incomplete. } else { - program->instructionPointer = stackReadInt32(ptr, 16); - if ((flags & 0x10) != 0) { + program->instructionPointer = stackReadInt32(ptr, offsetof(Procedure, bodyOffset)); + if ((flags & PROCEDURE_FLAG_CRITICAL) != 0) { program->flags |= PROGRAM_FLAG_CRITICAL_SECTION; } } @@ -2238,7 +2238,7 @@ static void opFetchProcedureAddress(Program* program) { int procedureIndex = programStackPopInteger(program); - int address = stackReadInt32(program->procedures + 4 + sizeof(Procedure) * procedureIndex, 16); + int address = stackReadInt32(program->procedures + 4 + sizeof(Procedure) * procedureIndex, offsetof(Procedure, bodyOffset)); programStackPushInteger(program, address); } @@ -2298,8 +2298,8 @@ static void opExportProcedure(Program* program) unsigned char* proc_ptr = program->procedures + 4 + sizeof(Procedure) * procedureIndex; - char* procedureName = programGetIdentifier(program, stackReadInt32(proc_ptr, 0)); - int procedureAddress = stackReadInt32(proc_ptr, 16); + char* procedureName = programGetIdentifier(program, stackReadInt32(proc_ptr, offsetof(Procedure, nameOffset))); + int procedureAddress = stackReadInt32(proc_ptr, offsetof(Procedure, bodyOffset)); if (externalProcedureCreate(program, procedureName, procedureAddress, argumentCount) != 0) { char err[256]; @@ -2468,9 +2468,9 @@ static void opCheckProcedureArgumentCount(Program* program) int expectedArgumentCount = programStackPopInteger(program); int procedureIndex = programStackPopInteger(program); - int actualArgumentCount = stackReadInt32(program->procedures + 4 + 24 * procedureIndex, 20); + int actualArgumentCount = stackReadInt32(program->procedures + 4 + 24 * procedureIndex, offsetof(Procedure, argCount)); if (actualArgumentCount != expectedArgumentCount) { - const char* identifier = programGetIdentifier(program, stackReadInt32(program->procedures + 4 + 24 * procedureIndex, 0)); + const char* identifier = programGetIdentifier(program, stackReadInt32(program->procedures + 4 + 24 * procedureIndex, offsetof(Procedure, nameOffset))); char err[260]; snprintf(err, sizeof(err), "Wrong number of args to procedure %s\n", identifier); programFatalError(err); @@ -2491,7 +2491,7 @@ static void opLookupStringProc(Program* program) // Start with 1 since we've skipped main procedure, which is always at // index 0. for (int index = 1; index < procedureCount; index++) { - int offset = stackReadInt32(procedurePtr, 0); + int offset = stackReadInt32(procedurePtr, offsetof(Procedure, nameOffset)); const char* procedureName = programGetIdentifier(program, offset); if (compat_stricmp(procedureName, procedureNameToLookup) == 0) { programStackPushInteger(program, index); @@ -2556,7 +2556,7 @@ void interpreterRegisterOpcodeHandlers() interpreterRegisterOpcode(OPCODE_POP_BASE, opPopBase); interpreterRegisterOpcode(OPCODE_POP_TO_BASE, opPopToBase); interpreterRegisterOpcode(OPCODE_PUSH_BASE, opPushBase); - interpreterRegisterOpcode(OPCODE_SET_GLOBAL, op802C); + interpreterRegisterOpcode(OPCODE_SET_GLOBAL, opSetGlobal); interpreterRegisterOpcode(OPCODE_FETCH_PROCEDURE_ADDRESS, opFetchProcedureAddress); interpreterRegisterOpcode(OPCODE_DUMP, opDump); interpreterRegisterOpcode(OPCODE_IF, opIf); @@ -2776,9 +2776,9 @@ void _executeProc(Program* program, int procedureIndex) char err[256]; procedurePtr = program->procedures + 4 + sizeof(Procedure) * procedureIndex; - procedureFlags = stackReadInt32(procedurePtr, 4); + procedureFlags = stackReadInt32(procedurePtr, offsetof(Procedure, flags)); if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) { - procedureIdentifier = programGetIdentifier(program, stackReadInt32(procedurePtr, 0)); + procedureIdentifier = programGetIdentifier(program, stackReadInt32(procedurePtr, offsetof(Procedure, nameOffset))); externalProgram = externalProcedureGetProgram(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount); if (externalProgram != NULL) { if (externalProcedureArgumentCount == 0) { @@ -2795,7 +2795,7 @@ void _executeProc(Program* program, int procedureIndex) _setupExternalCall(program, externalProgram, externalProcedureAddress, 28); procedurePtr = externalProgram->procedures + 4 + sizeof(Procedure) * procedureIndex; - procedureFlags = stackReadInt32(procedurePtr, 4); + procedureFlags = stackReadInt32(procedurePtr, offsetof(Procedure, flags)); if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0) { // NOTE: Uninline. @@ -2803,7 +2803,7 @@ void _executeProc(Program* program, int procedureIndex) _interpret(externalProgram, 0); } } else { - procedureAddress = stackReadInt32(procedurePtr, 16); + procedureAddress = stackReadInt32(procedurePtr, offsetof(Procedure, bodyOffset)); // NOTE: Uninline. _setupCall(program, procedureAddress, 20); @@ -2826,7 +2826,7 @@ int programFindProcedure(Program* program, const char* name) unsigned char* ptr = program->procedures + 4; for (int index = 0; index < procedureCount; index++) { - int identifierOffset = stackReadInt32(ptr, offsetof(Procedure, field_0)); + int identifierOffset = stackReadInt32(ptr, offsetof(Procedure, nameOffset)); if (compat_stricmp((char*)(program->identifiers + identifierOffset), name) == 0) { return index; } @@ -2851,10 +2851,10 @@ void _executeProcedure(Program* program, int procedureIndex) jmp_buf env; procedurePtr = program->procedures + 4 + sizeof(Procedure) * procedureIndex; - procedureFlags = stackReadInt32(procedurePtr, 4); + procedureFlags = stackReadInt32(procedurePtr, offsetof(Procedure, flags)); if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) { - procedureIdentifier = programGetIdentifier(program, stackReadInt32(procedurePtr, 0)); + procedureIdentifier = programGetIdentifier(program, stackReadInt32(procedurePtr, offsetof(Procedure, nameOffset))); externalProgram = externalProcedureGetProgram(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount); if (externalProgram != NULL) { if (externalProcedureArgumentCount == 0) { @@ -2872,7 +2872,7 @@ void _executeProcedure(Program* program, int procedureIndex) _interpretOutput(err); } } else { - procedureAddress = stackReadInt32(procedurePtr, 16); + procedureAddress = stackReadInt32(procedurePtr, offsetof(Procedure, bodyOffset)); // NOTE: Uninline. _setupCall(program, procedureAddress, 24); @@ -2908,14 +2908,14 @@ static void _doEvents() procedurePtr = programListNode->program->procedures + 4; for (procedureIndex = 0; procedureIndex < procedureCount; procedureIndex++) { - procedureFlags = stackReadInt32(procedurePtr, 4); + procedureFlags = stackReadInt32(procedurePtr, offsetof(Procedure, flags)); if ((procedureFlags & PROCEDURE_FLAG_CONDITIONAL) != 0) { memcpy(env, programListNode->program, sizeof(env)); oldProgramFlags = programListNode->program->flags; oldInstructionPointer = programListNode->program->instructionPointer; programListNode->program->flags = 0; - programListNode->program->instructionPointer = stackReadInt32(procedurePtr, 12); + programListNode->program->instructionPointer = stackReadInt32(procedurePtr, offsetof(Procedure, conditionOffset)); _interpret(programListNode->program, -1); if ((programListNode->program->flags & PROGRAM_FLAG_0x04) == 0) { @@ -2926,16 +2926,16 @@ static void _doEvents() if (data != 0) { // NOTE: Uninline. - stackWriteInt32(0, procedurePtr, 4); + stackWriteInt32(0, procedurePtr, offsetof(Procedure, flags)); _executeProc(programListNode->program, procedureIndex); } } memcpy(programListNode->program, env, sizeof(env)); } else if ((procedureFlags & PROCEDURE_FLAG_TIMED) != 0) { - if ((unsigned int)stackReadInt32(procedurePtr, 8) < time) { + if ((unsigned int)stackReadInt32(procedurePtr, offsetof(Procedure, time)) < time) { // NOTE: Uninline. - stackWriteInt32(0, procedurePtr, 4); + stackWriteInt32(0, procedurePtr, offsetof(Procedure, flags)); _executeProc(programListNode->program, procedureIndex); } } diff --git a/src/interpreter.h b/src/interpreter.h index 6095292a..6f404166 100644 --- a/src/interpreter.h +++ b/src/interpreter.h @@ -133,12 +133,12 @@ enum RawValueType { typedef unsigned short opcode_t; typedef struct Procedure { - int field_0; - int field_4; - int field_8; - int field_C; - int field_10; - int field_14; + int nameOffset; + int flags; + int time; + int conditionOffset; + int bodyOffset; + int argCount; } Procedure; class ProgramValue { diff --git a/src/interpreter_extra.cc b/src/interpreter_extra.cc index b2d32951..ca3e5e00 100644 --- a/src/interpreter_extra.cc +++ b/src/interpreter_extra.cc @@ -1580,12 +1580,22 @@ static void opPickup(Program* program) return; } - if (script->target == NULL) { + Object* self = script->target; + + // SFALL: Override `self` via `op_set_self`. + // CE: Implementation is different. Sfall integrates via `scriptGetSid` by + // returning fake script with overridden `self` (and `target` in this case). + if (script->overriddenSelf != nullptr) { + self = script->overriddenSelf; + script->overriddenSelf = nullptr; + } + + if (self == NULL) { scriptPredefinedError(program, "pickup_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } - actionPickUp(script->target, object); + actionPickUp(self, object); } // drop_obj @@ -4548,6 +4558,15 @@ static void opUseObjectOnObject(Program* program) } Object* self = scriptGetSelf(program); + + // SFALL: Override `self` via `op_set_self`. + // CE: Implementation is different. Sfall integrates via `scriptGetSid` by + // returning fake script with overridden `self`. + if (script->overriddenSelf != nullptr) { + self = script->overriddenSelf; + script->overriddenSelf = nullptr; + } + if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) { _action_use_an_item_on_object(self, target, item); } else { diff --git a/src/inventory.cc b/src/inventory.cc index 351584c3..4b531260 100644 --- a/src/inventory.cc +++ b/src/inventory.cc @@ -5981,4 +5981,9 @@ int _inven_set_timer(Object* a1) return seconds; } +Object* inven_get_current_target_obj() +{ + return _target_stack[_target_curr_stack]; +} + } // namespace fallout diff --git a/src/inventory.h b/src/inventory.h index 58dcf318..7f4aa006 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -27,6 +27,7 @@ int inventoryOpenLooting(Object* a1, Object* a2); int inventoryOpenStealing(Object* a1, Object* a2); void inventoryOpenTrade(int win, Object* a2, Object* a3, Object* a4, int a5); int _inven_set_timer(Object* a1); +Object* inven_get_current_target_obj(); } // namespace fallout diff --git a/src/loadsave.cc b/src/loadsave.cc index 4ed29102..0e997d0b 100644 --- a/src/loadsave.cc +++ b/src/loadsave.cc @@ -45,6 +45,7 @@ #include "random.h" #include "scripts.h" #include "settings.h" +#include "sfall_config.h" #include "sfall_global_scripts.h" #include "sfall_global_vars.h" #include "skill.h" @@ -60,6 +61,12 @@ namespace fallout { +#define LOAD_SAVE_SIGNATURE "FALLOUT SAVE FILE" +#define LOAD_SAVE_DESCRIPTION_LENGTH 30 +#define LOAD_SAVE_HANDLER_COUNT 27 + +#define LSGAME_MSG_NAME "LSGAME.MSG" + #define LS_WINDOW_WIDTH 640 #define LS_WINDOW_HEIGHT 480 @@ -82,8 +89,8 @@ namespace fallout { #define ITEMS_DIR_NAME "items" #define PROTO_FILE_EXT "pro" -#define LOAD_SAVE_DESCRIPTION_LENGTH (30) -#define LOAD_SAVE_HANDLER_COUNT (27) +typedef int LoadGameHandler(File* stream); +typedef int SaveGameHandler(File* stream); typedef enum LoadSaveWindowType { LOAD_SAVE_WINDOW_TYPE_SAVE_GAME, @@ -106,33 +113,28 @@ typedef enum LoadSaveScrollDirection { LOAD_SAVE_SCROLL_DIRECTION_DOWN, } LoadSaveScrollDirection; -typedef int LoadGameHandler(File* stream); -typedef int SaveGameHandler(File* stream); - -#define LSGAME_MSG_NAME ("LSGAME.MSG") - -typedef struct STRUCT_613D30 { - char field_0[24]; - short field_18; - short field_1A; +typedef struct LoadSaveSlotData { + char signature[24]; + short versionMinor; + short versionMajor; // TODO: The type is probably char, but it's read with the same function as // reading unsigned chars, which in turn probably result of collapsing // reading functions. - unsigned char field_1C; - char character_name[32]; + unsigned char versionRelease; + char characterName[32]; char description[LOAD_SAVE_DESCRIPTION_LENGTH]; - short field_5C; - short field_5E; - short field_60; - int field_64; - short field_68; - short field_6A; - short field_6C; - int field_70; - short field_74; - short field_76; - char file_name[16]; -} STRUCT_613D30; + short fileMonth; + short fileDay; + short fileYear; + int fileTime; + short gameMonth; + short gameDay; + short gameYear; + unsigned int gameTime; + short elevation; + short map; + char fileName[16]; +} LoadSaveSlotData; typedef enum LoadSaveFrm { LOAD_SAVE_FRM_BACKGROUND, @@ -155,10 +157,10 @@ static int lsgLoadGameInSlot(int slot); static int lsgSaveHeaderInSlot(int slot); static int lsgLoadHeaderInSlot(int slot); static int _GetSlotList(); -static void _ShowSlotList(int a1); -static void _DrawInfoBox(int a1); -static int _LoadTumbSlot(int a1); -static int _GetComment(int a1); +static void _ShowSlotList(int windowType); +static void _DrawInfoBox(int slot); +static int _LoadTumbSlot(int slot); +static int _GetComment(int slot); static int _get_input_str2(int win, int doneKeyCode, int cancelKeyCode, char* description, int maxLength, int x, int y, int textColor, int backgroundColor, int flags); static int _DummyFunc(File* stream); static int _PrepLoad(File* stream); @@ -166,8 +168,7 @@ static int _EndLoad(File* stream); static int _GameMap2Slot(File* stream); static int _SlotMap2Game(File* stream); static int _mygets(char* dest, File* stream); -static int _copy_file(const char* a1, const char* a2); -static int _MapDirErase(const char* path, const char* a2); +static int _copy_file(const char* existingFileName, const char* newFileName); static int _SaveBackup(); static int _RestoreSave(); static int _LoadObjDudeCid(File* stream); @@ -200,7 +201,7 @@ static bool gLoadSaveWindowIsoWasEnabled = false; static int _map_backup_count = -1; // 0x5193C8 -static int _automap_db_flag = 0; +static bool _automap_db_flag = false; // 0x5193CC static const char* _patches = NULL; @@ -268,7 +269,7 @@ static LoadGameHandler* _master_load_list[LOAD_SAVE_HANDLER_COUNT] = { }; // 0x5194C4 -static int _loadingGame = 0; +static bool _loadingGame = false; // lsgame.msg // @@ -276,7 +277,7 @@ static int _loadingGame = 0; static MessageList gLoadSaveMessageList; // 0x613D30 -static STRUCT_613D30 _LSData[10]; +static LoadSaveSlotData _LSData[10]; // 0x614280 static int _LSstatus[10]; @@ -328,6 +329,9 @@ static int gLoadSaveWindowOldFont; static FrmImage _loadsaveFrmImages[LOAD_SAVE_FRM_COUNT]; +static int quickSaveSlots = 0; +static bool autoQuickSaveSlots = false; + // 0x47B7E4 void _InitLoadSave() { @@ -335,17 +339,22 @@ void _InitLoadSave() _slot_cursor = 0; _patches = settings.system.master_patches_path.c_str(); - _MapDirErase("MAPS\\", "SAV"); - _MapDirErase(PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME "\\", PROTO_FILE_EXT); - _MapDirErase(PROTO_DIR_NAME "\\" ITEMS_DIR_NAME "\\", PROTO_FILE_EXT); + MapDirErase("MAPS\\", "SAV"); + MapDirErase(PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME "\\", PROTO_FILE_EXT); + MapDirErase(PROTO_DIR_NAME "\\" ITEMS_DIR_NAME "\\", PROTO_FILE_EXT); + + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_AUTO_QUICK_SAVE, &quickSaveSlots); + if (quickSaveSlots > 0 && quickSaveSlots <= 10) { + autoQuickSaveSlots = true; + } } // 0x47B85C void _ResetLoadSave() { - _MapDirErase("MAPS\\", "SAV"); - _MapDirErase(PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME "\\", PROTO_FILE_EXT); - _MapDirErase(PROTO_DIR_NAME "\\" ITEMS_DIR_NAME "\\", PROTO_FILE_EXT); + MapDirErase("MAPS\\", "SAV"); + MapDirErase(PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME "\\", PROTO_FILE_EXT); + MapDirErase(PROTO_DIR_NAME "\\" ITEMS_DIR_NAME "\\", PROTO_FILE_EXT); } // SaveGame @@ -359,7 +368,18 @@ int lsgSaveGame(int mode) _ls_error_code = 0; _patches = settings.system.master_patches_path.c_str(); + // SFALL: skip slot selection if auto quicksave is enabled + if (autoQuickSaveSlots) { + _quick_done = true; + } + if (mode == LOAD_SAVE_MODE_QUICK && _quick_done) { + // SFALL: cycle through first N slots for quicksaving + if (autoQuickSaveSlots) { + if (++_slot_cursor >= quickSaveSlots) { + _slot_cursor = 0; + } + } snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); strcat(_gmpath, "SAVE.DAT"); @@ -473,7 +493,7 @@ int lsgSaveGame(int mode) break; } - _ShowSlotList(0); + _ShowSlotList(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME); _DrawInfoBox(_slot_cursor); windowRefresh(gLoadSaveWindow); @@ -978,7 +998,7 @@ int lsgLoadGame(int mode) break; } - _ShowSlotList(2); + _ShowSlotList(LOAD_SAVE_WINDOW_TYPE_LOAD_GAME); _DrawInfoBox(_slot_cursor); windowRefresh(gLoadSaveWindow); renderPresent(); @@ -1149,7 +1169,7 @@ int lsgLoadGame(int mode) break; } - _ShowSlotList(2); + _ShowSlotList(LOAD_SAVE_WINDOW_TYPE_LOAD_GAME); _DrawInfoBox(_slot_cursor); windowRefresh(gLoadSaveWindow); } @@ -1196,7 +1216,7 @@ int lsgLoadGame(int mode) } _DrawInfoBox(_slot_cursor); - _ShowSlotList(2); + _ShowSlotList(LOAD_SAVE_WINDOW_TYPE_LOAD_GAME); } windowRefresh(gLoadSaveWindow); @@ -1549,7 +1569,7 @@ static int lsgPerformSaveGame() debugPrint("\nLOADSAVE: ** Error opening save game for writing! **\n"); _RestoreSave(); snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); - _MapDirErase(_gmpath, "BAK"); + MapDirErase(_gmpath, "BAK"); _partyMemberUnPrepSave(); backgroundSoundResume(); return -1; @@ -1562,7 +1582,7 @@ static int lsgPerformSaveGame() fileClose(_flptr); _RestoreSave(); snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); - _MapDirErase(_gmpath, "BAK"); + MapDirErase(_gmpath, "BAK"); _partyMemberUnPrepSave(); backgroundSoundResume(); return -1; @@ -1576,7 +1596,7 @@ static int lsgPerformSaveGame() fileClose(_flptr); _RestoreSave(); snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); - _MapDirErase(_gmpath, "BAK"); + MapDirErase(_gmpath, "BAK"); _partyMemberUnPrepSave(); backgroundSoundResume(); return -1; @@ -1657,7 +1677,7 @@ static int lsgPerformSaveGame() } snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); - _MapDirErase(_gmpath, "BAK"); + MapDirErase(_gmpath, "BAK"); gLoadSaveMessageListItem.num = 140; if (messageListGetItem(&gLoadSaveMessageList, &gLoadSaveMessageListItem)) { @@ -1672,7 +1692,7 @@ static int lsgPerformSaveGame() } // 0x47DC60 -int _isLoadingGame() +bool _isLoadingGame() { return _loadingGame; } @@ -1680,7 +1700,7 @@ int _isLoadingGame() // 0x47DC68 static int lsgLoadGameInSlot(int slot) { - _loadingGame = 1; + _loadingGame = true; if (isInCombat()) { interfaceBarEndButtonsHide(false); @@ -1691,13 +1711,13 @@ static int lsgLoadGameInSlot(int slot) snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); strcat(_gmpath, "SAVE.DAT"); - STRUCT_613D30* ptr = &(_LSData[slot]); + LoadSaveSlotData* ptr = &(_LSData[slot]); debugPrint("\nLOADSAVE: Load name: %s\n", ptr->description); _flptr = fileOpen(_gmpath, "rb"); if (_flptr == NULL) { debugPrint("\nLOADSAVE: ** Error opening load game file for reading! **\n"); - _loadingGame = 0; + _loadingGame = false; return -1; } @@ -1706,7 +1726,7 @@ static int lsgLoadGameInSlot(int slot) debugPrint("\nLOADSAVE: ** Error reading save game header! **\n"); fileClose(_flptr); gameReset(); - _loadingGame = 0; + _loadingGame = false; return -1; } @@ -1721,7 +1741,7 @@ static int lsgLoadGameInSlot(int slot) debugPrint("LOADSAVE: Load function #%d data size read: %d bytes.\n", index, fileTell(_flptr) - pos); fileClose(_flptr); gameReset(); - _loadingGame = 0; + _loadingGame = false; return -1; } @@ -1750,7 +1770,7 @@ static int lsgLoadGameInSlot(int slot) } snprintf(_str, sizeof(_str), "%s\\", "MAPS"); - _MapDirErase(_str, "BAK"); + MapDirErase(_str, "BAK"); _proto_dude_update_gender(); // Game Loaded. @@ -1761,7 +1781,7 @@ static int lsgLoadGameInSlot(int slot) debugPrint("\nError: Couldn't find LoadSave Message!"); } - _loadingGame = 0; + _loadingGame = false; // SFALL: Start global scripts. sfall_gl_scr_exec_start_proc(); @@ -1774,10 +1794,10 @@ static int lsgSaveHeaderInSlot(int slot) { _ls_error_code = 4; - STRUCT_613D30* ptr = &(_LSData[slot]); - strncpy(ptr->field_0, "FALLOUT SAVE FILE", 24); + LoadSaveSlotData* ptr = &(_LSData[slot]); + strncpy(ptr->signature, LOAD_SAVE_SIGNATURE, 24); - if (fileWrite(ptr->field_0, 1, 24, _flptr) == -1) { + if (fileWrite(ptr->signature, 1, 24, _flptr) == -1) { return -1; } @@ -1785,22 +1805,22 @@ static int lsgSaveHeaderInSlot(int slot) temp[0] = VERSION_MAJOR; temp[1] = VERSION_MINOR; - ptr->field_18 = temp[0]; - ptr->field_1A = temp[1]; + ptr->versionMinor = temp[0]; + ptr->versionMajor = temp[1]; if (fileWriteInt16List(_flptr, temp, 2) == -1) { return -1; } - ptr->field_1C = VERSION_RELEASE; + ptr->versionRelease = VERSION_RELEASE; if (fileWriteUInt8(_flptr, VERSION_RELEASE) == -1) { return -1; } char* characterName = critterGetName(gDude); - strncpy(ptr->character_name, characterName, 32); + strncpy(ptr->characterName, characterName, 32); - if (fileWrite(ptr->character_name, 32, 1, _flptr) != 1) { + if (fileWrite(ptr->characterName, 32, 1, _flptr) != 1) { return -1; } @@ -1815,16 +1835,16 @@ static int lsgSaveHeaderInSlot(int slot) temp[1] = local->tm_mon + 1; temp[2] = local->tm_year + 1900; - ptr->field_5E = temp[0]; - ptr->field_5C = temp[1]; - ptr->field_60 = temp[2]; - ptr->field_64 = local->tm_hour + local->tm_min; + ptr->fileDay = temp[0]; + ptr->fileMonth = temp[1]; + ptr->fileYear = temp[2]; + ptr->fileTime = local->tm_hour + local->tm_min; if (fileWriteInt16List(_flptr, temp, 3) == -1) { return -1; } - if (_db_fwriteLong(_flptr, ptr->field_64) == -1) { + if (_db_fwriteLong(_flptr, ptr->fileTime) == -1) { return -1; } @@ -1836,23 +1856,23 @@ static int lsgSaveHeaderInSlot(int slot) temp[0] = month; temp[1] = day; temp[2] = year; - ptr->field_70 = gameTimeGetTime(); + ptr->gameTime = gameTimeGetTime(); if (fileWriteInt16List(_flptr, temp, 3) == -1) { return -1; } - if (_db_fwriteLong(_flptr, ptr->field_70) == -1) { + if (fileWriteUInt32(_flptr, ptr->gameTime) == -1) { return -1; } - ptr->field_74 = gElevation; - if (fileWriteInt16(_flptr, ptr->field_74) == -1) { + ptr->elevation = gElevation; + if (fileWriteInt16(_flptr, ptr->elevation) == -1) { return -1; } - ptr->field_76 = mapGetCurrentMap(); - if (fileWriteInt16(_flptr, ptr->field_76) == -1) { + ptr->map = mapGetCurrentMap(); + if (fileWriteInt16(_flptr, ptr->map) == -1) { return -1; } @@ -1861,8 +1881,8 @@ static int lsgSaveHeaderInSlot(int slot) // NOTE: Uppercased from "sav". char* v1 = _strmfe(_str, mapName, "SAV"); - strncpy(ptr->file_name, v1, 16); - if (fileWrite(ptr->file_name, 16, 1, _flptr) != 1) { + strncpy(ptr->fileName, v1, 16); + if (fileWrite(ptr->fileName, 16, 1, _flptr) != 1) { return -1; } @@ -1885,13 +1905,13 @@ static int lsgLoadHeaderInSlot(int slot) { _ls_error_code = 3; - STRUCT_613D30* ptr = &(_LSData[slot]); + LoadSaveSlotData* ptr = &(_LSData[slot]); - if (fileRead(ptr->field_0, 1, 24, _flptr) != 24) { + if (fileRead(ptr->signature, 1, 24, _flptr) != 24) { return -1; } - if (strncmp(ptr->field_0, "FALLOUT SAVE FILE", 18) != 0) { + if (strncmp(ptr->signature, LOAD_SAVE_SIGNATURE, 18) != 0) { debugPrint("\nLOADSAVE: ** Invalid save file on load! **\n"); _ls_error_code = 2; return -1; @@ -1902,20 +1922,20 @@ static int lsgLoadHeaderInSlot(int slot) return -1; } - ptr->field_18 = v8[0]; - ptr->field_1A = v8[1]; + ptr->versionMinor = v8[0]; + ptr->versionMajor = v8[1]; - if (fileReadUInt8(_flptr, &(ptr->field_1C)) == -1) { + if (fileReadUInt8(_flptr, &(ptr->versionRelease)) == -1) { return -1; } - if (ptr->field_18 != 1 || ptr->field_1A != 2 || ptr->field_1C != 'R') { - debugPrint("\nLOADSAVE: Load slot #%d Version: %d.%d%c\n", slot, ptr->field_18, ptr->field_1A, ptr->field_1C); + if (ptr->versionMinor != 1 || ptr->versionMajor != 2 || ptr->versionRelease != 'R') { + debugPrint("\nLOADSAVE: Load slot #%d Version: %d.%d%c\n", slot, ptr->versionMinor, ptr->versionMajor, ptr->versionRelease); _ls_error_code = 1; return -1; } - if (fileRead(ptr->character_name, 32, 1, _flptr) != 1) { + if (fileRead(ptr->characterName, 32, 1, _flptr) != 1) { return -1; } @@ -1927,11 +1947,11 @@ static int lsgLoadHeaderInSlot(int slot) return -1; } - ptr->field_5C = v8[0]; - ptr->field_5E = v8[1]; - ptr->field_60 = v8[2]; + ptr->fileMonth = v8[0]; + ptr->fileDay = v8[1]; + ptr->fileYear = v8[2]; - if (_db_freadInt(_flptr, &(ptr->field_64)) == -1) { + if (_db_freadInt(_flptr, &(ptr->fileTime)) == -1) { return -1; } @@ -1939,23 +1959,23 @@ static int lsgLoadHeaderInSlot(int slot) return -1; } - ptr->field_68 = v8[0]; - ptr->field_6A = v8[1]; - ptr->field_6C = v8[2]; + ptr->gameMonth = v8[0]; + ptr->gameDay = v8[1]; + ptr->gameYear = v8[2]; - if (_db_freadInt(_flptr, &(ptr->field_70)) == -1) { + if (fileReadUInt32(_flptr, &(ptr->gameTime)) == -1) { return -1; } - if (fileReadInt16(_flptr, &(ptr->field_74)) == -1) { + if (fileReadInt16(_flptr, &(ptr->elevation)) == -1) { return -1; } - if (fileReadInt16(_flptr, &(ptr->field_76)) == -1) { + if (fileReadInt16(_flptr, &(ptr->map)) == -1) { return -1; } - if (fileRead(ptr->file_name, 1, 16, _flptr) != 16) { + if (fileRead(ptr->fileName, 1, 16, _flptr) != 16) { return -1; } @@ -2009,7 +2029,7 @@ static int _GetSlotList() } // 0x47E6D8 -static void _ShowSlotList(int a1) +static void _ShowSlotList(int windowType) { bufferFill(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 87 + 55, 230, 353, LS_WINDOW_WIDTH, gLoadSaveWindowBuffer[LS_WINDOW_WIDTH * 86 + 55] & 0xFF); @@ -2017,7 +2037,7 @@ static void _ShowSlotList(int a1) for (int index = 0; index < 10; index += 1) { int color = index == _slot_cursor ? _colorTable[32747] : _colorTable[992]; - const char* text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, a1 != 0 ? 110 : 109); + const char* text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, windowType != 0 ? 110 : 109); snprintf(_str, sizeof(_str), "[ %s %.2d: ]", text, index + 1); fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * y + 55, _str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); @@ -2051,7 +2071,7 @@ static void _ShowSlotList(int a1) } // 0x47E8E0 -static void _DrawInfoBox(int a1) +static void _DrawInfoBox(int slot) { blitBufferToBuffer(_loadsaveFrmImages[LOAD_SAVE_FRM_BACKGROUND].getData() + LS_WINDOW_WIDTH * 254 + 396, 164, 60, LS_WINDOW_WIDTH, gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 254 + 396, 640); @@ -2059,26 +2079,28 @@ static void _DrawInfoBox(int a1) const char* text; int color = _colorTable[992]; - switch (_LSstatus[a1]) { + switch (_LSstatus[slot]) { case SLOT_STATE_OCCUPIED: - do { - STRUCT_613D30* ptr = &(_LSData[a1]); - fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 254 + 396, ptr->character_name, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); - - int v4 = ptr->field_70 / 600; - int v5 = v4 % 60; - int v6 = 25 * (v4 / 60 % 24); - int v21 = 4 * v6 + v5; - - text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 116 + ptr->field_68); - snprintf(_str, sizeof(_str), "%.2d %s %.4d %.4d", ptr->field_6A, text, ptr->field_6C, v21); + if (1) { + LoadSaveSlotData* ptr = &(_LSData[slot]); + fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 254 + 396, ptr->characterName, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); + + snprintf(_str, + sizeof(_str), + "%.2d %s %.4d %.4d", + ptr->gameDay, + getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 116 + ptr->gameMonth), + ptr->gameYear, + 100 * ((ptr->gameTime / 600) / 60 % 24) + (ptr->gameTime / 600) % 60); int v2 = fontGetLineHeight(); fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * (256 + v2) + 397, _str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); - const char* v22 = mapGetName(ptr->field_76, ptr->field_74); - const char* v9 = mapGetCityName(ptr->field_76); - snprintf(_str, sizeof(_str), "%s %s", v9, v22); + snprintf(_str, + sizeof(_str), + "%s %s", + mapGetCityName(ptr->map), + mapGetName(ptr->map, ptr->elevation)); int y = v2 + 3 + v2 + 256; short beginnings[WORD_WRAP_MAX_COUNT]; @@ -2093,7 +2115,7 @@ static void _DrawInfoBox(int a1) y += v2 + 2; } } - } while (0); + } return; case SLOT_STATE_EMPTY: // Empty. @@ -2120,30 +2142,28 @@ static void _DrawInfoBox(int a1) } // 0x47EC48 -static int _LoadTumbSlot(int a1) +static int _LoadTumbSlot(int slot) { - File* stream; - int v2; - - v2 = _LSstatus[_slot_cursor]; - if (v2 != 0 && v2 != 2 && v2 != 3) { + if (_LSstatus[_slot_cursor] != SLOT_STATE_EMPTY + && _LSstatus[_slot_cursor] != SLOT_STATE_ERROR + && _LSstatus[_slot_cursor] != SLOT_STATE_UNSUPPORTED_VERSION) { snprintf(_str, sizeof(_str), "%s\\%s%.2d\\%s", "SAVEGAME", "SLOT", _slot_cursor + 1, "SAVE.DAT"); debugPrint(" Filename %s\n", _str); - stream = fileOpen(_str, "rb"); + File* stream = fileOpen(_str, "rb"); if (stream == NULL) { - debugPrint("\nLOADSAVE: ** (A) Error reading thumbnail #%d! **\n", a1); + debugPrint("\nLOADSAVE: ** (A) Error reading thumbnail #%d! **\n", slot); return -1; } if (fileSeek(stream, 131, SEEK_SET) != 0) { - debugPrint("\nLOADSAVE: ** (B) Error reading thumbnail #%d! **\n", a1); + debugPrint("\nLOADSAVE: ** (B) Error reading thumbnail #%d! **\n", slot); fileClose(stream); return -1; } if (fileRead(_thumbnail_image, LS_PREVIEW_SIZE, 1, stream) != 1) { - debugPrint("\nLOADSAVE: ** (C) Error reading thumbnail #%d! **\n", a1); + debugPrint("\nLOADSAVE: ** (C) Error reading thumbnail #%d! **\n", slot); fileClose(stream); return -1; } @@ -2155,7 +2175,7 @@ static int _LoadTumbSlot(int a1) } // 0x47ED5C -static int _GetComment(int a1) +static int _GetComment(int slot) { // Maintain original position in original resolution, otherwise center it. int commentWindowX = screenGetWidth() != 640 @@ -2256,7 +2276,7 @@ static int _GetComment(int a1) char description[LOAD_SAVE_DESCRIPTION_LENGTH]; if (_LSstatus[_slot_cursor] == SLOT_STATE_OCCUPIED) { - strncpy(description, _LSData[a1].description, LOAD_SAVE_DESCRIPTION_LENGTH); + strncpy(description, _LSData[slot].description, LOAD_SAVE_DESCRIPTION_LENGTH); } else { memset(description, '\0', LOAD_SAVE_DESCRIPTION_LENGTH); } @@ -2265,8 +2285,8 @@ static int _GetComment(int a1) int backgroundColor = *(_loadsaveFrmImages[LOAD_SAVE_FRM_BOX].getData() + _loadsaveFrmImages[LOAD_SAVE_FRM_BOX].getWidth() * 35 + 24); if (_get_input_str2(window, 507, 508, description, LOAD_SAVE_DESCRIPTION_LENGTH - 1, 24, 35, _colorTable[992], backgroundColor, 0) == 0) { - strncpy(_LSData[a1].description, description, LOAD_SAVE_DESCRIPTION_LENGTH); - _LSData[a1].description[LOAD_SAVE_DESCRIPTION_LENGTH - 1] = '\0'; + strncpy(_LSData[slot].description, description, LOAD_SAVE_DESCRIPTION_LENGTH); + _LSData[slot].description[LOAD_SAVE_DESCRIPTION_LENGTH - 1] = '\0'; rc = 1; } else { rc = 0; @@ -2396,7 +2416,7 @@ static int _PrepLoad(File* stream) gameReset(); gameMouseSetCursor(MOUSE_CURSOR_WAIT_PLANET); gMapHeader.name[0] = '\0'; - gameTimeSetTime(_LSData[_slot_cursor].field_70); + gameTimeSetTime(_LSData[_slot_cursor].gameTime); return 0; } @@ -2404,7 +2424,7 @@ static int _PrepLoad(File* stream) static int _EndLoad(File* stream) { wmMapMusicStart(); - dudeSetName(_LSData[_slot_cursor].character_name); + dudeSetName(_LSData[_slot_cursor].characterName); interfaceBarRefresh(); indicatorBarRefresh(); tileWindowRefresh(); @@ -2466,7 +2486,7 @@ static int _GameMap2Slot(File* stream) snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); - if (_MapDirErase(_gmpath, "SAV") == -1) { + if (MapDirErase(_gmpath, "SAV") == -1) { fileNameListFree(&fileNameList, 0); return -1; } @@ -2545,19 +2565,19 @@ static int _SlotMap2Game(File* stream) snprintf(_str0, sizeof(_str0), "%s\\", PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME); - if (_MapDirErase(_str0, PROTO_FILE_EXT) == -1) { + if (MapDirErase(_str0, PROTO_FILE_EXT) == -1) { debugPrint("LOADSAVE: returning 3\n"); return -1; } snprintf(_str0, sizeof(_str0), "%s\\", PROTO_DIR_NAME "\\" ITEMS_DIR_NAME); - if (_MapDirErase(_str0, PROTO_FILE_EXT) == -1) { + if (MapDirErase(_str0, PROTO_FILE_EXT) == -1) { debugPrint("LOADSAVE: returning 4\n"); return -1; } snprintf(_str0, sizeof(_str0), "%s\\", "MAPS"); - if (_MapDirErase(_str0, "SAV") == -1) { + if (MapDirErase(_str0, "SAV") == -1) { debugPrint("LOADSAVE: returning 5\n"); return -1; } @@ -2623,7 +2643,7 @@ static int _SlotMap2Game(File* stream) return -1; } - if (mapLoadSaved(_LSData[_slot_cursor].file_name) == -1) { + if (mapLoadSaved(_LSData[_slot_cursor].fileName) == -1) { debugPrint("LOADSAVE: returning 13\n"); return -1; } @@ -2659,7 +2679,7 @@ static int _mygets(char* dest, File* stream) } // 0x47FE58 -static int _copy_file(const char* a1, const char* a2) +static int _copy_file(const char* existingFileName, const char* newFileName) { File* stream1; File* stream2; @@ -2673,7 +2693,7 @@ static int _copy_file(const char* a1, const char* a2) buf = NULL; result = -1; - stream1 = fileOpen(a1, "rb"); + stream1 = fileOpen(existingFileName, "rb"); if (stream1 == NULL) { goto out; } @@ -2683,7 +2703,7 @@ static int _copy_file(const char* a1, const char* a2) goto out; } - stream2 = fileOpen(a2, "wb"); + stream2 = fileOpen(newFileName, "wb"); if (stream2 == NULL) { goto out; } @@ -2720,7 +2740,7 @@ static int _copy_file(const char* a1, const char* a2) } if (stream2 != NULL) { - fileClose(stream1); + fileClose(stream2); } if (buf != NULL) { @@ -2736,11 +2756,11 @@ void lsgInit() { char path[COMPAT_MAX_PATH]; snprintf(path, sizeof(path), "%s\\", "MAPS"); - _MapDirErase(path, "SAV"); + MapDirErase(path, "SAV"); } // 0x480040 -static int _MapDirErase(const char* relativePath, const char* extension) +int MapDirErase(const char* relativePath, const char* extension) { char path[COMPAT_MAX_PATH]; snprintf(path, sizeof(path), "%s*.%s", relativePath, extension); @@ -2824,7 +2844,7 @@ static int _SaveBackup() char* v2 = _strmfe(_str2, "AUTOMAP.DB", "BAK"); snprintf(_str1, sizeof(_str1), "%s\\%s", _gmpath, v2); - _automap_db_flag = 0; + _automap_db_flag = false; File* stream2 = fileOpen(_str0, "rb"); if (stream2 != NULL) { @@ -2834,7 +2854,7 @@ static int _SaveBackup() return -1; } - _automap_db_flag = 1; + _automap_db_flag = true; } return 0; diff --git a/src/loadsave.h b/src/loadsave.h index 77e67578..567548e4 100644 --- a/src/loadsave.h +++ b/src/loadsave.h @@ -18,8 +18,9 @@ void _InitLoadSave(); void _ResetLoadSave(); int lsgSaveGame(int mode); int lsgLoadGame(int mode); -int _isLoadingGame(); +bool _isLoadingGame(); void lsgInit(); +int MapDirErase(const char* path, const char* extension); int _MapDirEraseFile_(const char* a1, const char* a2); } // namespace fallout diff --git a/src/map.cc b/src/map.cc index 4166e601..333935f3 100644 --- a/src/map.cc +++ b/src/map.cc @@ -108,7 +108,7 @@ int* gMapLocalVars = NULL; // map_vars // 0x51956C -static int* gMapGlobalVars = NULL; +int* gMapGlobalVars = NULL; // local_vars_num // 0x519570 @@ -116,7 +116,7 @@ int gMapLocalVarsLength = 0; // map_vars_num // 0x519574 -static int gMapGlobalVarsLength = 0; +int gMapGlobalVarsLength = 0; // Current elevation. // @@ -1746,7 +1746,7 @@ static int mapHeaderWrite(MapHeader* ptr, File* stream) if (fileWriteInt32(stream, ptr->darkness) == -1) return -1; if (fileWriteInt32(stream, ptr->globalVariablesCount) == -1) return -1; if (fileWriteInt32(stream, ptr->field_34) == -1) return -1; - if (fileWriteInt32(stream, ptr->lastVisitTime) == -1) return -1; + if (fileWriteUInt32(stream, ptr->lastVisitTime) == -1) return -1; if (fileWriteInt32List(stream, ptr->field_3C, 44) == -1) return -1; return 0; @@ -1766,7 +1766,7 @@ static int mapHeaderRead(MapHeader* ptr, File* stream) if (fileReadInt32(stream, &(ptr->darkness)) == -1) return -1; if (fileReadInt32(stream, &(ptr->globalVariablesCount)) == -1) return -1; if (fileReadInt32(stream, &(ptr->field_34)) == -1) return -1; - if (fileReadInt32(stream, &(ptr->lastVisitTime)) == -1) return -1; + if (fileReadUInt32(stream, &(ptr->lastVisitTime)) == -1) return -1; if (fileReadInt32List(stream, ptr->field_3C, 44) == -1) return -1; return 0; diff --git a/src/map.h b/src/map.h index 0cdebb11..70e8d7f6 100644 --- a/src/map.h +++ b/src/map.h @@ -54,7 +54,7 @@ typedef struct MapHeader { int field_34; // Time in game ticks when PC last visited this map. - int lastVisitTime; + unsigned int lastVisitTime; int field_3C[44]; } MapHeader; @@ -69,7 +69,9 @@ typedef void IsoWindowRefreshProc(Rect* rect); extern int gMapSid; extern int* gMapLocalVars; +extern int* gMapGlobalVars; extern int gMapLocalVarsLength; +extern int gMapGlobalVarsLength; extern int gElevation; extern MessageList gMapMessageList; diff --git a/src/mapper/map_func.cc b/src/mapper/map_func.cc new file mode 100644 index 00000000..1fcd1367 --- /dev/null +++ b/src/mapper/map_func.cc @@ -0,0 +1,180 @@ +#include "mapper/map_func.h" + +#include "actions.h" +#include "color.h" +#include "game_mouse.h" +#include "input.h" +#include "map.h" +#include "memory.h" +#include "mouse.h" +#include "proto.h" +#include "svga.h" +#include "tile.h" +#include "window_manager.h" +#include "window_manager_private.h" + +namespace fallout { + +// 0x5595CC +static bool block_obj_view_on = false; + +// 0x4825B0 +void setup_map_dirs() +{ + // TODO: Incomplete. +} + +// 0x4826B4 +void copy_proto_lists() +{ + // TODO: Incomplete. +} + +// 0x482708 +void place_entrance_hex() +{ + int x; + int y; + int tile; + + while (inputGetInput() != -2) { + } + + if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { + if (_mouse_click_in(0, 0, _scr_size.right - _scr_size.left, _scr_size.bottom - _scr_size.top - 100)) { + mouseGetPosition(&x, &y); + + tile = tileFromScreenXY(x, y, gElevation); + if (tile != -1) { + if (tileSetCenter(tile, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) == 0) { + mapSetEnteringLocation(tile, gElevation, rotation); + } else { + win_timed_msg("ERROR: Entrance out of range!", _colorTable[31744]); + } + } + } + } +} + +// 0x4841C4 +void pick_region(Rect* rect) +{ + Rect temp; + int x; + int y; + + gameMouseSetCursor(MOUSE_CURSOR_PLUS); + gameMouseObjectsHide(); + + while (1) { + if (inputGetInput() == -2 + && (mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { + break; + } + } + + get_input_position(&x, &y); + temp.left = x; + temp.top = y; + temp.right = x; + temp.bottom = y; + + while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) { + inputGetInput(); + + get_input_position(&x, &y); + + if (x != temp.right || y != temp.bottom) { + erase_rect(rect); + sort_rect(rect, &temp); + draw_rect(rect, _colorTable[32747]); + } + } + + erase_rect(rect); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + gameMouseObjectsShow(); +} + +// 0x484294 +void sort_rect(Rect* a, Rect* b) +{ + if (b->right > b->left) { + a->left = b->left; + a->right = b->right; + } else { + a->left = b->right; + a->right = b->left; + } + + if (b->bottom > b->top) { + a->top = b->top; + a->bottom = b->bottom; + } else { + a->top = b->bottom; + a->bottom = b->top; + } +} + +// 0x4842D4 +void draw_rect(Rect* rect, unsigned char color) +{ + int width = rect->right - rect->left; + int height = rect->bottom - rect->top; + int max_dimension; + + if (height < width) { + max_dimension = width; + } else { + max_dimension = height; + } + + unsigned char* buffer = (unsigned char*)internal_malloc(max_dimension); + if (buffer != NULL) { + memset(buffer, color, max_dimension); + _scr_blit(buffer, width, 1, 0, 0, width, 1, rect->left, rect->top); + _scr_blit(buffer, 1, height, 0, 0, 1, height, rect->left, rect->top); + _scr_blit(buffer, width, 1, 0, 0, width, 1, rect->left, rect->bottom); + _scr_blit(buffer, 1, height, 0, 0, 1, height, rect->right, rect->top); + internal_free(buffer); + } +} + +// 0x4843A0 +void erase_rect(Rect* rect) +{ + Rect r = *rect; + + r.bottom = rect->top; + windowRefreshAll(&r); + + r.bottom = rect->bottom; + r.left = rect->right; + windowRefreshAll(&r); + + r.left = rect->left; + r.top = rect->bottom; + windowRefreshAll(&r); + + r.top = rect->top; + r.right = rect->left; + windowRefreshAll(&r); +} + +// 0x484400 +int toolbar_proto(int type, int id) +{ + if (id < proto_max_id(type)) { + return (type << 24) | id; + } else { + return -1; + } +} + +// 0x485D44 +bool map_toggle_block_obj_viewing_on() +{ + return block_obj_view_on; +} + +} // namespace fallout diff --git a/src/mapper/map_func.h b/src/mapper/map_func.h new file mode 100644 index 00000000..ff066275 --- /dev/null +++ b/src/mapper/map_func.h @@ -0,0 +1,20 @@ +#ifndef FALLOUT_MAPPER_MAP_FUNC_H_ +#define FALLOUT_MAPPER_MAP_FUNC_H_ + +#include "geometry.h" + +namespace fallout { + +void setup_map_dirs(); +void copy_proto_lists(); +void place_entrance_hex(); +void pick_region(Rect* rect); +void sort_rect(Rect* a, Rect* b); +void draw_rect(Rect* rect, unsigned char color); +void erase_rect(Rect* rect); +int toolbar_proto(int type, int id); +bool map_toggle_block_obj_viewing_on(); + +} // namespace fallout + +#endif /* FALLOUT_MAPPER_MAP_FUNC_H_ */ diff --git a/src/mapper/mapper.cc b/src/mapper/mapper.cc new file mode 100644 index 00000000..f69d23ff --- /dev/null +++ b/src/mapper/mapper.cc @@ -0,0 +1,1743 @@ +#include "mapper/mapper.h" + +#include + +#include "actions.h" +#include "animation.h" +#include "art.h" +#include "color.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_mouse.h" +#include "graph_lib.h" +#include "inventory.h" +#include "kb.h" +#include "loadsave.h" +#include "mapper/map_func.h" +#include "mapper/mp_proto.h" +#include "mapper/mp_targt.h" +#include "mapper/mp_text.h" +#include "memory.h" +#include "mouse.h" +#include "object.h" +#include "proto.h" +#include "settings.h" +#include "svga.h" +#include "tile.h" +#include "window_manager.h" +#include "window_manager_private.h" + +namespace fallout { + +static void MapperInit(); +static int mapper_edit_init(int argc, char** argv); +static void mapper_edit_exit(); +static int bookmarkInit(); +static int bookmarkExit(); +static void bookmarkHide(); +static void bookmarkUnHide(); +static int categoryInit(); +static int categoryExit(); +static int categoryHide(); +static int categoryToggleState(); +static int categoryUnhide(); +static bool proto_user_is_librarian(); +static void edit_mapper(); +static void mapper_load_toolbar(int a1, int a2); +static void redraw_toolname(); +static void clear_toolname(); +static void update_toolname(int* pid, int type, int id); +static void update_high_obj_name(Object* obj); +static void mapper_destroy_highlight_obj(Object** a1, Object** a2); +static void mapper_refresh_rotation(); +static void update_art(int a1, int a2); +static void handle_new_map(int* a1, int* a2); +static int mapper_mark_exit_grid(); +static void mapper_mark_all_exit_grids(); + +// TODO: Underlying menu/pulldown interface wants menu items to be non-const, +// needs some refactoring. + +static char kSeparator[] = ""; + +static char kNew[] = " New "; +static char kOpen[] = " Open "; +static char kSave[] = " Save "; +static char kSaveAs[] = " Save As... "; +static char kInfo[] = " Info "; +static char kOpenFromText[] = " Open From Text "; +static char kQuit[] = " Quit "; + +static char kCreatePattern[] = " Create Pattern "; +static char kUsePattern[] = " Use Pattern "; +static char kMoveMap[] = " Move Map "; +static char kMoveMapElev[] = " Move Map Elev "; +static char kCopyMapElev[] = " Copy Map Elev "; +static char kEditObjDude[] = " Edit Obj_dude "; +static char kFlushCache[] = " Flush Cache "; +static char kToggleAnimStepping[] = " Toggle Anim Stepping "; +static char kFixMapObjectsToPids[] = " Fix map-objects to pids "; +static char kSetBookmark[] = " Set Bookmark "; +static char kToggleBlockObjView[] = " Toggle Block Obj View "; +static char kToggleClickToScroll[] = " Toggle Click-To-Scroll "; +static char kSetExitGridData[] = " Set Exit-Grid Data "; +static char kMarkExitGrids[] = " Mark Exit-Grids "; +static char kMarkAllExitGrids[] = " Mark *ALL* Exit Grids "; +static char kClearMapLevel[] = " *Clear Map Level* "; +static char kCreateAllMapTexts[] = " Create ALL MAP TEXTS "; +static char kRebuildAllMaps[] = " Rebuild ALL MAPS "; + +static char kListAllScripts[] = " List all Scripts "; +static char kSetStartHex[] = " Set Start Hex "; +static char kPlaceSpatialScript[] = " Place Spatial Script "; +static char kDeleteSpatialScript[] = " Delete Spatial Script "; +static char kDeleteAllSpatialScripts[] = " Delete *ALL* Spatial SCRIPTS! "; +static char kCreateScript[] = " Create Script "; +static char kSetMapScript[] = " Set Map Script "; +static char kShowMapScript[] = " Show Map Script "; + +static char kRebuildWeapons[] = " Rebuild Weapons "; +static char kRebuildProtoList[] = " Rebuild Proto List "; +static char kRebuildAll[] = " Rebuild ALL "; +static char kRebuildBinary[] = " Rebuild Binary "; +static char kArtToProtos[] = " Art => New Protos "; +static char kSwapPrototypse[] = " Swap Prototypes "; + +static char kTmpMapName[] = "TMP$MAP#.MAP"; + +// 0x559618 +int rotate_arrows_x_offs[] = { + 31, + 38, + 31, + 11, + 3, + 11, +}; + +// 0x559630 +int rotate_arrows_y_offs[] = { + 7, + 23, + 37, + 37, + 23, + 7, +}; + +// 0x559648 +char* menu_0[] = { + kNew, + kOpen, + kSave, + kSaveAs, + kSeparator, + kInfo, + kOpenFromText, + kQuit, +}; + +// 0x559668 +char* menu_1[] = { + kCreatePattern, + kUsePattern, + kSeparator, + kMoveMap, + kMoveMapElev, + kCopyMapElev, + kSeparator, + kEditObjDude, + kFlushCache, + kToggleAnimStepping, + kFixMapObjectsToPids, + kSetBookmark, + kToggleBlockObjView, + kToggleClickToScroll, + kSetExitGridData, + kMarkExitGrids, + kMarkAllExitGrids, + kClearMapLevel, + kSeparator, + kCreateAllMapTexts, + kRebuildAllMaps, +}; + +// 0x5596BC +char* menu_2[] = { + kListAllScripts, + kSetStartHex, + kPlaceSpatialScript, + kDeleteSpatialScript, + kDeleteAllSpatialScripts, + kCreateScript, + kSetMapScript, + kShowMapScript, +}; + +// 0x5596DC +char* menu_3[] = { + kRebuildWeapons, + kRebuildProtoList, + kRebuildAll, + kRebuildBinary, + kSeparator, + kArtToProtos, + kSwapPrototypse, +}; + +// 0x5596F8 +char** menu_names[] = { + menu_0, + menu_1, + menu_2, + menu_3, +}; + +// 0x559748 +MapTransition mapInfo = { -1, -1, 0, 0 }; + +// 0x559880 +int max_art_buttons = 7; + +// 0x559884 +int art_scale_width = 49; + +// 0x559888 +int art_scale_height = 48; + +// 0x5598A0 +static bool map_entered = false; + +// 0x5598A4 +static char* tmp_map_name = kTmpMapName; + +// 0x5598A8 +static int bookmarkWin = -1; + +// 0x5598AC +static int categoryWin = -1; + +// 0x5598B0 +static bool categoryIsHidden = false; + +// 0x6EAA40 +int menu_val_0[8]; + +// 0x6EAA60 +int menu_val_2[8]; + +// 0x6EAA80 +unsigned char e_num[4][19 * 26]; + +// 0x6EBD28 +unsigned char rotate_arrows[2][6][10 * 10]; + +// 0x6EC408 +int menu_val_1[21]; + +// 0x6EC468 +unsigned char* art_shape; + +// 0x6EC46C +int to_paint_bid; + +// 0x6EC470 +int edit_bid; + +// 0x6EC474 +int paste_bid; + +// 0x6EC478 +int misc_bid; + +// 0x6EC47C +int tile_bid; + +// 0x6EC480 +int copy_bid; + +// 0x6EC484 +int delete_bid; + +// 0x6EC488 +int wall_bid; + +// 0x6EC48C +int obj_bid; + +// 0x6EC490 +int to_topdown_bid; + +// 0x6EC494 +int roof_bid; + +// 0x6EC498 +int hex_bid; + +// 0x6EC49C +int to_iso_bid; + +// 0x6EC4A0 +int scen_bid; + +// 0x6EC4A4 +int crit_bid; + +// 0x6EC4A8 +unsigned char* tool; + +// 0x6EC4AC +int tool_win; + +// 0x6EC4B0 +int menu_bar; + +// 0x6EC4B4 +unsigned char* lbm_buf; + +// 0x6EC4B8 +unsigned char height_inc_up[18 * 23]; + +// 0x6EC656 +unsigned char height_dec_up[18 * 23]; + +// 0x6EC7F4 +unsigned char height_dec_down[18 * 23]; + +// 0x6EC992 +unsigned char height_inc_down[18 * 23]; + +// 0x6ECB30 +unsigned char obj_down[66 * 13]; + +// 0x6ECE8A +unsigned char to_iso_down[58 * 13]; + +// 0x6ED17C +unsigned char scen_up[66 * 13]; + +// 0x6ED4D6 +unsigned char roof_up[58 * 13]; + +// 0x6ED7C8 +unsigned char crit_down[66 * 13]; + +// 0x6EDB22 +unsigned char obj_up[66 * 13]; + +// 0x6EDE7C +unsigned char crit_up[66 * 13]; + +// 0x6EE1D6 +unsigned char to_topdown_down[58 * 13]; + +// 0x6EE4C8 +unsigned char hex_up[58 * 13]; + +// 0x6EE7BA +unsigned char hex_down[58 * 13]; + +// 0x6EEAAC +unsigned char to_topdown_up[58 * 13]; + +// 0x6EED9E +unsigned char scen_down[66 * 13]; + +// 0x6EF0F8 +unsigned char edec_down[18 * 23]; + +// 0x6EF296 +unsigned char to_iso_up[58 * 13]; + +// 0x6EF588 +unsigned char roof_down[58 * 13]; + +// 0x6EF87A +unsigned char r_up[18 * 23]; + +// 0x6EFA18 +unsigned char einc_down[18 * 23]; + +// 0x6EFBB6 +unsigned char shift_l_up[18 * 23]; + +// 0x6EFD54 +unsigned char edec_up[18 * 23]; + +// 0x6EFEF2 +unsigned char shift_r_up[18 * 23]; + +// 0x6F0090 +unsigned char shift_r_down[18 * 23]; + +// 0x6F022E +unsigned char r_down[18 * 23]; + +// 0x6F03CC +unsigned char einc_up[18 * 23]; + +// 0x6F056A +unsigned char l_down[18 * 23]; + +// 0x6F0708 +unsigned char shift_l_down[18 * 23]; + +// 0x6F08A6 +unsigned char l_up[18 * 23]; + +// 0x6F0A44 +unsigned char to_edit_up[45 * 43]; + +// 0x6F11D3 +unsigned char erase_up[45 * 43]; + +// 0x6F1962 +unsigned char copy_group_up[45 * 43]; + +// 0x6F20F1 +unsigned char to_paint_down[45 * 43]; + +// 0x6F2880 +unsigned char erase_down[45 * 43]; + +// 0x6F300F +unsigned char copy_group_down[45 * 43]; + +// 0x6F379E +unsigned char to_edit_down[45 * 43]; + +// 0x6F3F2D +unsigned char copy_up[49 * 19]; + +// 0x6F42D0 +unsigned char misc_down[53 * 18]; + +// 0x6F4581 +unsigned char wall_down[53 * 18]; + +// 0x6F4832 +unsigned char delete_up[49 * 19]; + +// 0x6F4BD5 +unsigned char edit_up[49 * 19]; + +// 0x6F4F78 +unsigned char tile_up[53 * 18]; + +// 0x6F5229 +unsigned char edit_down[49 * 19]; + +// 0x6F55CC +unsigned char paste_down[49 * 19]; + +// 0x6F596F +unsigned char delete_down[49 * 19]; + +// 0x6F5D12 +unsigned char tile_down[53 * 18]; + +// 0x6F5FC3 +unsigned char copy_down[49 * 19]; + +// 0x6F6366 +unsigned char misc_up[53 * 18]; + +// 0x6F6617 +unsigned char paste_up[49 * 19]; + +// 0x6F69BA +unsigned char to_paint_up[1935]; + +// 0x6F7149 +unsigned char wall_up[53 * 18]; + +// 0x6F73FA +bool draw_mode; + +// 0x6F73FB +bool view_mode; + +// gnw_main +// 0x485DD0 +int mapper_main(int argc, char** argv) +{ + MapperInit(); + + if (mapper_edit_init(argc, argv) == -1) { + mem_check(); + return 0; + } + + edit_mapper(); + mapper_edit_exit(); + mem_check(); + + return 0; +} + +// 0x485E00 +void MapperInit() +{ + menu_val_0[0] = KEY_ALT_N; + menu_val_0[1] = KEY_ALT_O; + menu_val_0[2] = KEY_ALT_S; + menu_val_0[3] = KEY_ALT_A; + menu_val_0[4] = KEY_ESCAPE; + menu_val_0[5] = KEY_ALT_K; + menu_val_0[6] = KEY_ALT_I; + menu_val_0[7] = KEY_ESCAPE; + + menu_val_1[0] = KEY_ALT_U; + menu_val_1[1] = KEY_ALT_Y; + menu_val_1[2] = KEY_ESCAPE; + menu_val_1[3] = KEY_ALT_G; + menu_val_1[4] = 4186; + menu_val_1[5] = 4188; + menu_val_1[6] = KEY_ESCAPE; + menu_val_1[7] = KEY_ALT_B; + menu_val_1[8] = KEY_ALT_E; + menu_val_1[9] = KEY_ALT_D; + menu_val_1[10] = KEY_LOWERCASE_B; + menu_val_1[11] = 2165; + menu_val_1[12] = 3123; + menu_val_1[13] = KEY_ALT_Z; + menu_val_1[14] = 5677; + menu_val_1[15] = 5678; + menu_val_1[16] = 5679; + menu_val_1[17] = 5666; + menu_val_1[18] = KEY_ESCAPE; + menu_val_1[19] = 5406; + menu_val_1[20] = 5405; + + menu_val_2[0] = KEY_LOWERCASE_I; + menu_val_2[1] = 5400; + menu_val_2[2] = KEY_LOWERCASE_S; + menu_val_2[3] = KEY_CTRL_F8; + menu_val_2[4] = 5410; + menu_val_2[5] = KEY_GRAVE; + menu_val_2[6] = KEY_ALT_W; + menu_val_2[7] = 5544; +} + +// 0x485F94 +int mapper_edit_init(int argc, char** argv) +{ + int index; + + if (gameInitWithOptions("FALLOUT Mapper", true, 2, 0, argc, argv) == -1) { + return -1; + } + + tileEnable(); + gmouse_set_mapper_mode(true); + settings.system.executable = "mapper"; + + if (settings.mapper.override_librarian) { + can_modify_protos = true; + target_override_protection(); + } + + setup_map_dirs(); + mapper_load_toolbar(4, 0); + + max_art_buttons = (_scr_size.right - _scr_size.left - 136) / 50; + art_shape = (unsigned char*)internal_malloc(art_scale_height * art_scale_width); + if (art_shape == NULL) { + printf("Can't malloc memory!!\n"); + exit(1); + } + + menu_bar = windowCreate(0, + 0, + rectGetWidth(&_scr_size), + 16, + _colorTable[0], + WINDOW_HIDDEN); + _win_register_menu_bar(menu_bar, + 0, + 0, + rectGetWidth(&_scr_size), + 16, + 260, + _colorTable[8456]); + _win_register_menu_pulldown(menu_bar, + 8, + "FILE", + 289, + 8, + menu_names[0], + 260, + _colorTable[8456]); + _win_register_menu_pulldown(menu_bar, + 40, + "TOOLS", + 303, + 21, + menu_names[1], + 260, + _colorTable[8456]); + _win_register_menu_pulldown(menu_bar, + 80, + "SCRIPTS", + 276, + 8, + menu_names[2], + 260, + _colorTable[8456]); + + if (can_modify_protos) { + _win_register_menu_pulldown(menu_bar, + 130, + "LIBRARIAN", + 292, + 6, + &(menu_1[14]), + 260, + _colorTable[8456]); + } + + tool_win = windowCreate(0, + _scr_size.bottom - 99, + rectGetWidth(&_scr_size), + 100, + 256, + 0); + tool = windowGetBuffer(tool_win); + + lbm_buf = (unsigned char*)internal_malloc(640 * 480); + load_lbm_to_buf("data\\mapper2.lbm", + lbm_buf, + rectGetWidth(&_scr_size), + 0, + 0, + _scr_size.right - _scr_size.left, + 479); + + // + blitBufferToBuffer(lbm_buf + 380 * rectGetWidth(&_scr_size), + rectGetWidth(&_scr_size), + 100, + rectGetWidth(&_scr_size), + tool, + rectGetWidth(&_scr_size)); + + // + blitBufferToBuffer(lbm_buf + 406 * (rectGetWidth(&_scr_size)) + 101, + 18, + 23, + rectGetWidth(&_scr_size), + l_up, + 18); + blitBufferToBuffer(lbm_buf + 253 * (rectGetWidth(&_scr_size)) + 101, + 18, + 23, + rectGetWidth(&_scr_size), + l_down, + 18); + buttonCreate(tool_win, + 101, + 26, + 18, + 23, + -1, + -1, + 45, + -1, + l_up, + l_down, + NULL, + 0); + + // + blitBufferToBuffer(lbm_buf + 406 * (rectGetWidth(&_scr_size)) + 622, + 18, + 23, + rectGetWidth(&_scr_size), + r_up, + 18); + blitBufferToBuffer(lbm_buf + 253 * (rectGetWidth(&_scr_size)) + 622, + 18, + 23, + rectGetWidth(&_scr_size), + r_down, + 18); + buttonCreate(tool_win, + _scr_size.right - 18, + 1, + 18, + 23, + -1, + -1, + 61, + -1, + r_up, + r_down, + NULL, + 0); + + // + blitBufferToBuffer(lbm_buf + 381 * (rectGetWidth(&_scr_size)) + 101, + 18, + 23, + rectGetWidth(&_scr_size), + shift_l_up, + 18); + blitBufferToBuffer(lbm_buf + 228 * (rectGetWidth(&_scr_size)) + 101, + 18, + 23, + rectGetWidth(&_scr_size), + shift_l_down, + 18); + buttonCreate(tool_win, + 101, + 1, + 18, + 23, + -1, + -1, + 95, + -1, + shift_l_up, + shift_l_down, + NULL, + 0); + + // + blitBufferToBuffer(lbm_buf + 381 * (rectGetWidth(&_scr_size)) + 622, + 18, + 23, + rectGetWidth(&_scr_size), + shift_r_up, + 18); + blitBufferToBuffer(lbm_buf + 228 * (rectGetWidth(&_scr_size)) + 622, + 18, + 23, + rectGetWidth(&_scr_size), + shift_r_down, + 18); + buttonCreate(tool_win, + _scr_size.right - 18, + 1, + 18, + 23, + -1, + -1, + 43, + -1, + shift_r_up, + shift_r_down, + NULL, + 0); + + // + for (index = 0; index < max_art_buttons; index++) { + int btn = buttonCreate(tool_win, + index * (art_scale_width + 1) + 121, + 1, + art_scale_width, + art_scale_height, + index + max_art_buttons + 161, + 58, + 160 + index, + -1, + NULL, + NULL, + NULL, + 0); + buttonSetRightMouseCallbacks(btn, 160 + index, -1, NULL, NULL); + } + + // ELEVATION INC + blitBufferToBuffer(lbm_buf + 431 * (rectGetWidth(&_scr_size)) + 1, + 18, + 23, + rectGetWidth(&_scr_size), + einc_up, + 18); + blitBufferToBuffer(lbm_buf + 325 * (rectGetWidth(&_scr_size)) + 1, + 18, + 23, + rectGetWidth(&_scr_size), + einc_down, + 18); + buttonCreate(tool_win, + 1, + 51, + 18, + 23, + -1, + -1, + 329, + -1, + einc_up, + einc_down, + NULL, + 0); + + // ELEVATION DEC + blitBufferToBuffer(lbm_buf + 456 * (rectGetWidth(&_scr_size)) + 1, + 18, + 23, + rectGetWidth(&_scr_size), + edec_up, + 18); + blitBufferToBuffer(lbm_buf + 350 * (rectGetWidth(&_scr_size)) + 1, + 18, + 23, + rectGetWidth(&_scr_size), + edec_down, + 18); + buttonCreate(tool_win, + 1, + 76, + 18, + 23, + -1, + -1, + 337, + -1, + edec_up, + edec_down, + NULL, + 0); + + // ELEVATION + for (index = 0; index < 4; index++) { + blitBufferToBuffer(lbm_buf + 293 * rectGetWidth(&_scr_size) + 19 * index, + 19, + 26, + rectGetWidth(&_scr_size), + e_num[1], + 19); + } + + view_mode = false; + + // + blitBufferToBuffer(lbm_buf + 169 * (rectGetWidth(&_scr_size)) + 64, + 58, + 13, + rectGetWidth(&_scr_size), + to_iso_up, + 58); + blitBufferToBuffer(lbm_buf + 108 * (rectGetWidth(&_scr_size)) + 64, + 58, + 13, + rectGetWidth(&_scr_size), + to_iso_down, + 58); + + // ROOF + blitBufferToBuffer(lbm_buf + 464 * (rectGetWidth(&_scr_size)) + 64, + 58, + 13, + rectGetWidth(&_scr_size), + roof_up, + 58); + blitBufferToBuffer(lbm_buf + 358 * (rectGetWidth(&_scr_size)) + 64, + 58, + 13, + rectGetWidth(&_scr_size), + roof_down, + 58); + roof_bid = buttonCreate(tool_win, + 64, + 69, + 58, + 13, + -1, + -1, + 'r', + 'r', + roof_up, + roof_down, + NULL, + BUTTON_FLAG_0x01); + + if (tileRoofIsVisible()) { + tile_toggle_roof(false); + } + + // HEX + blitBufferToBuffer(lbm_buf + 464 * (rectGetWidth(&_scr_size)) + 64, + 58, + 13, + rectGetWidth(&_scr_size), + hex_up, + 58); + blitBufferToBuffer(lbm_buf + 358 * (rectGetWidth(&_scr_size)) + 64, + 58, + 13, + rectGetWidth(&_scr_size), + hex_down, + 58); + hex_bid = buttonCreate(tool_win, + 64, + 84, + 58, + 13, + -1, + -1, + 350, + 350, + hex_up, + hex_down, + NULL, + BUTTON_FLAG_0x01); + + // OBJ + blitBufferToBuffer(lbm_buf + 434 * (rectGetWidth(&_scr_size)) + 125, + 66, + 13, + rectGetWidth(&_scr_size), + obj_up, + 66); + blitBufferToBuffer(lbm_buf + 328 * (rectGetWidth(&_scr_size)) + 125, + 66, + 13, + rectGetWidth(&_scr_size), + obj_down, + 66); + obj_bid = buttonCreate(tool_win, + 125, + 54, + 66, + 13, + -1, + -1, + 350, + 350, + obj_up, + obj_down, + NULL, + BUTTON_FLAG_0x01); + + // CRIT + blitBufferToBuffer(lbm_buf + 449 * (rectGetWidth(&_scr_size)) + 125, + 66, + 13, + rectGetWidth(&_scr_size), + crit_up, + 66); + blitBufferToBuffer(lbm_buf + 343 * (rectGetWidth(&_scr_size)) + 125, + 66, + 13, + rectGetWidth(&_scr_size), + crit_down, + 66); + crit_bid = buttonCreate(tool_win, + 125, + 69, + 66, + 13, + -1, + -1, + 351, + 351, + crit_up, + crit_down, + NULL, + BUTTON_FLAG_0x01); + + // SCEN + blitBufferToBuffer(lbm_buf + 434 * (rectGetWidth(&_scr_size)) + 194, + 53, + 13, + rectGetWidth(&_scr_size), + scen_up, + 53); + blitBufferToBuffer(lbm_buf + 328 * (rectGetWidth(&_scr_size)) + 194, + 53, + 13, + rectGetWidth(&_scr_size), + scen_down, + 53); + scen_bid = buttonCreate(tool_win, + 125, + 84, + 66, + 13, + -1, + -1, + 352, + 352, + scen_up, + scen_down, + NULL, + BUTTON_FLAG_0x01); + + // WALL + blitBufferToBuffer(lbm_buf + 434 * (rectGetWidth(&_scr_size)) + 194, + 53, + 13, + rectGetWidth(&_scr_size), + wall_up, + 53); + blitBufferToBuffer(lbm_buf + 328 * (rectGetWidth(&_scr_size)) + 194, + 53, + 13, + rectGetWidth(&_scr_size), + wall_down, + 53); + wall_bid = buttonCreate(tool_win, + 194, + 54, + 53, + 13, + -1, + -1, + 355, + 355, + wall_up, + wall_down, + NULL, + BUTTON_FLAG_0x01); + + // MISC + blitBufferToBuffer(lbm_buf + 464 * (rectGetWidth(&_scr_size)) + 194, + 53, + 13, + rectGetWidth(&_scr_size), + misc_up, + 53); + blitBufferToBuffer(lbm_buf + 358 * (rectGetWidth(&_scr_size)) + 194, + 53, + 13, + rectGetWidth(&_scr_size), + misc_down, + 53); + misc_bid = buttonCreate(tool_win, + 194, + 84, + 53, + 13, + -1, + -1, + 355, + 355, + misc_up, + misc_down, + NULL, + BUTTON_FLAG_0x01); + + // HEIGHT INC + blitBufferToBuffer(lbm_buf + 431 * rectGetWidth(&_scr_size) + 251, + 18, + 23, + rectGetWidth(&_scr_size), + height_inc_up, + 18); + blitBufferToBuffer(lbm_buf + 325 * rectGetWidth(&_scr_size) + 251, + 18, + 23, + rectGetWidth(&_scr_size), + height_inc_down, + 18); + buttonCreate(tool_win, + 251, + 51, + 18, + 23, + -1, + -1, + 371, + -1, + height_dec_up, + height_dec_down, + NULL, + 0); + + // HEIGHT DEC + blitBufferToBuffer(lbm_buf + 456 * rectGetWidth(&_scr_size) + 251, + 18, + 23, + rectGetWidth(&_scr_size), + height_dec_up, + 18); + blitBufferToBuffer(lbm_buf + 350 * rectGetWidth(&_scr_size) + 251, + 18, + 23, + rectGetWidth(&_scr_size), + height_dec_down, + 18); + buttonCreate(tool_win, + 251, + 76, + 18, + 23, + -1, + -1, + 371, + -1, + height_dec_up, + height_dec_down, + NULL, + 0); + + // ARROWS + for (index = 0; index < ROTATION_COUNT; index++) { + int x = rotate_arrows_x_offs[index] + 285; + int y = rotate_arrows_y_offs[index] + 25; + unsigned char v1 = lbm_buf[27 * (_scr_size.right + 1) + 287]; + int k; + + blitBufferToBuffer(lbm_buf + y * rectGetWidth(&_scr_size) + x, + 10, + 10, + rectGetWidth(&_scr_size), + rotate_arrows[1][index], + 10); + + for (k = 0; k < 100; k++) { + if (rotate_arrows[1][index][k] == v1) { + rotate_arrows[1][index][k] = 0; + } + } + + blitBufferToBuffer(lbm_buf + y * rectGetWidth(&_scr_size) + x - 52, + 10, + 10, + rectGetWidth(&_scr_size), + rotate_arrows[0][index], + 10); + + for (k = 0; k < 100; k++) { + if (rotate_arrows[1][index][k] == v1) { + rotate_arrows[1][index][k] = 0; + } + } + } + + // COPY + blitBufferToBuffer(lbm_buf + 435 * (rectGetWidth(&_scr_size)) + 325, + 49, + 19, + rectGetWidth(&_scr_size), + copy_up, + 49); + blitBufferToBuffer(lbm_buf + 329 * (rectGetWidth(&_scr_size)) + 325, + 49, + 19, + rectGetWidth(&_scr_size), + copy_down, + 49); + copy_bid = buttonCreate(tool_win, + 325, + 55, + 49, + 19, + -1, + -1, + 99, + -1, + copy_up, + copy_down, + 0, + 0); + + // PASTE + blitBufferToBuffer(lbm_buf + 457 * (rectGetWidth(&_scr_size)) + 325, + 49, + 19, + rectGetWidth(&_scr_size), + paste_up, + 49); + blitBufferToBuffer(lbm_buf + 351 * (rectGetWidth(&_scr_size)) + 325, + 49, + 19, + rectGetWidth(&_scr_size), + paste_down, + 49); + paste_bid = buttonCreate(tool_win, + 325, + 77, + 49, + 19, + -1, + -1, + 67, + -1, + paste_up, + paste_down, + NULL, + 0); + + // EDIT + blitBufferToBuffer(lbm_buf + 435 * (rectGetWidth(&_scr_size)) + 378, + 49, + 19, + rectGetWidth(&_scr_size), + edit_up, + 49); + blitBufferToBuffer(lbm_buf + 329 * (rectGetWidth(&_scr_size)) + 378, + 49, + 19, + rectGetWidth(&_scr_size), + edit_down, + 49); + edit_bid = buttonCreate(tool_win, + 378, + 55, + 49, + 19, + -1, + -1, + 101, + -1, + edit_up, + edit_down, + NULL, + 0); + + // DELETE + blitBufferToBuffer(lbm_buf + 457 * rectGetWidth(&_scr_size) + 378, + 49, + 19, + rectGetWidth(&_scr_size), + delete_up, + 49); + blitBufferToBuffer(lbm_buf + 351 * rectGetWidth(&_scr_size) + 378, + 49, + 19, + rectGetWidth(&_scr_size), + delete_down, + 49); + delete_bid = buttonCreate(tool_win, + 378, + 77, + 49, + 19, + -1, + -1, + 339, + -1, + delete_up, + delete_down, + NULL, + 0); + + draw_mode = false; + + blitBufferToBuffer(lbm_buf + 169 * rectGetWidth(&_scr_size) + 430, + 45, + 43, + rectGetWidth(&_scr_size), + to_edit_up, + 45); + blitBufferToBuffer(lbm_buf + 108 * rectGetWidth(&_scr_size) + 430, + 45, + 43, + rectGetWidth(&_scr_size), + to_edit_down, + 45); + + blitBufferToBuffer(lbm_buf + 169 * rectGetWidth(&_scr_size) + 327, + 45, + 43, + rectGetWidth(&_scr_size), + copy_group_up, + 45); + blitBufferToBuffer(lbm_buf + 108 * rectGetWidth(&_scr_size) + 327, + 45, + 43, + rectGetWidth(&_scr_size), + copy_group_down, + 45); + + blitBufferToBuffer(lbm_buf + 169 * rectGetWidth(&_scr_size) + 379, + 45, + 43, + rectGetWidth(&_scr_size), + erase_up, + 45); + blitBufferToBuffer(lbm_buf + 108 * rectGetWidth(&_scr_size) + 379, + 45, + 43, + rectGetWidth(&_scr_size), + erase_down, + 45); + + internal_free(lbm_buf); + windowRefresh(tool_win); + + if (bookmarkInit() == -1) { + debugPrint("\nbookmarkInit() Failed!"); + } + + if (categoryInit() == -1) { + debugPrint("\ncategoryInit() Failed!"); + } + + tileScrollBlockingDisable(); + tileScrollLimitingDisable(); + init_mapper_protos(); + _map_init(); + target_init(); + mouseShowCursor(); + + if (settings.mapper.rebuild_protos) { + proto_build_all_texts(); + settings.mapper.rebuild_protos = false; + } + + return 0; +} + +// 0x48752C +void mapper_edit_exit() +{ + remove(tmp_map_name); + remove("\\fallout\\cd\\data\\maps\\TMP$MAP#.MAP"); + remove("\\fallout\\cd\\data\\maps\\TMP$MAP#.CFG"); + + MapDirErase("MAPS\\", "SAV"); + + if (can_modify_protos) { + copy_proto_lists(); + + // NOTE: There is a call to an ambiguous function at `0x4B9ACC`, likely + // `proto_save`. + } + + target_exit(); + _map_exit(); + bookmarkExit(); + categoryExit(); + + windowDestroy(tool_win); + tool = NULL; + + windowDestroy(menu_bar); + + internal_free(art_shape); + gameExit(); +} + +// 0x4875B4 +int bookmarkInit() +{ + return 0; +} + +// 0x4875B8 +int bookmarkExit() +{ + if (bookmarkWin == -1) { + return -1; + } + + windowDestroy(bookmarkWin); + bookmarkWin = -1; + + return 0; +} + +// 0x4875E0 +void bookmarkHide() +{ + if (bookmarkWin != -1) { + windowHide(bookmarkWin); + } +} + +// 0x4875F8 +void bookmarkUnHide() +{ + if (bookmarkWin != -1) { + windowShow(bookmarkWin); + } +} + +// 0x4875B4 +int categoryInit() +{ + return 0; +} + +// 0x487700 +int categoryExit() +{ + if (categoryWin == -1) { + return -1; + } + + windowDestroy(categoryWin); + categoryWin = -1; + + return 0; +} + +// 0x487728 +int categoryHide() +{ + if (categoryWin == -1) { + return -1; + } + + windowHide(categoryWin); + categoryIsHidden = true; + + return 0; +} + +// 0x487768 +int categoryToggleState() +{ + if (categoryIsHidden) { + return categoryUnhide(); + } else { + return categoryHide(); + } +} + +// 0x487774 +int categoryUnhide() +{ + if (categoryWin == -1) { + return -1; + } + + windowShow(categoryWin); + categoryIsHidden = false; + + return 0; +} + +// 0x487784 +bool proto_user_is_librarian() +{ + if (!settings.mapper.librarian) { + return false; + } + + can_modify_protos = true; + return true; +} + +// 0x4877D0 +void edit_mapper() +{ + // TODO: Incomplete. +} + +// 0x48AFFC +void mapper_load_toolbar(int a1, int a2) +{ + // TODO: Incomplete. +} + +// 0x48B16C +void print_toolbar_name(int object_type) +{ + Rect rect; + char name[80]; + + rect.left = 0; + rect.top = 0; + rect.right = 0; + rect.bottom = 22; + bufferFill(tool + 2 + 2 * (_scr_size.right - _scr_size.left) + 2, + 96, + _scr_size.right - _scr_size.left + 1, + 19, + _colorTable[21140]); + + sprintf(name, "%s", artGetObjectTypeName(object_type)); + name[0] = toupper(name[0]); + windowDrawText(tool_win, name, 0, 7, 7, _colorTable[32747] | 0x2000000); + windowRefreshRect(tool_win, &rect); +} + +// 0x48B230 +void redraw_toolname() +{ + Rect rect; + + rect.left = _scr_size.right - _scr_size.left - 149; + rect.top = 60; + rect.right = _scr_size.right - _scr_size.left + 1; + rect.bottom = 95; + windowRefreshRect(tool_win, &rect); +} + +// 0x48B278 +void clear_toolname() +{ + windowDrawText(tool_win, "", 120, _scr_size.right - _scr_size.left - 149, 60, 260); + windowDrawText(tool_win, "", 120, _scr_size.right - _scr_size.left - 149, 70, 260); + windowDrawText(tool_win, "", 120, _scr_size.right - _scr_size.left - 149, 80, 260); + redraw_toolname(); +} + +// 0x48B328 +void update_toolname(int* pid, int type, int id) +{ + Proto* proto; + + *pid = toolbar_proto(type, id); + + if (protoGetProto(*pid, &proto) == -1) { + return; + } + + windowDrawText(tool_win, + protoGetName(proto->pid), + 120, + _scr_size.right - _scr_size.left - 149, + 60, + 260); + + switch (PID_TYPE(proto->pid)) { + case OBJ_TYPE_ITEM: + windowDrawText(tool_win, + gItemTypeNames[proto->item.type], + 120, + _scr_size.right - _scr_size.left - 149, + 70, + 260); + break; + case OBJ_TYPE_CRITTER: + windowDrawText(tool_win, + "", + 120, + _scr_size.right - _scr_size.left - 149, + 70, + 260); + break; + case OBJ_TYPE_WALL: + windowDrawText(tool_win, + proto_wall_light_str(proto->wall.flags), + 120, + _scr_size.right - _scr_size.left - 149, + 70, + 260); + break; + case OBJ_TYPE_TILE: + windowDrawText(tool_win, + "", + 120, + _scr_size.right - _scr_size.left - 149, + 70, + 260); + break; + case OBJ_TYPE_MISC: + windowDrawText(tool_win, + "", + 120, + _scr_size.right - _scr_size.left - 149, + 70, + 260); + break; + default: + windowDrawText(tool_win, + "", + 120, + _scr_size.right - _scr_size.left - 149, + 70, + 260); + break; + } + + windowDrawText(tool_win, + "", + 120, + _scr_size.right - _scr_size.left - 149, + 80, + 260); + + redraw_toolname(); +} + +// 0x48B5BC +void update_high_obj_name(Object* obj) +{ + Proto* proto; + + if (protoGetProto(obj->pid, &proto) != -1) { + windowDrawText(tool_win, protoGetName(obj->pid), 120, _scr_size.right - _scr_size.left - 149, 60, 260); + windowDrawText(tool_win, "", 120, _scr_size.right - _scr_size.left - 149, 70, 260); + windowDrawText(tool_win, "", 120, _scr_size.right - _scr_size.left - 149, 80, 260); + redraw_toolname(); + } +} + +// 0x48B680 +void mapper_destroy_highlight_obj(Object** a1, Object** a2) +{ + Rect rect; + int elevation; + + if (a2 != NULL && *a2 != NULL) { + elevation = (*a2)->elevation; + reg_anim_clear(*a2); + objectDestroy(*a2, &rect); + tileWindowRefreshRect(&rect, elevation); + *a2 = NULL; + } + + if (a1 != NULL && *a1 != NULL) { + elevation = (*a1)->elevation; + objectDestroy(*a1, &rect); + tileWindowRefreshRect(&rect, elevation); + *a1 = NULL; + } +} + +// 0x48B6EC +void mapper_refresh_rotation() +{ + Rect rect; + char string[2]; + int index; + + rect.left = 270; + rect.top = 431 - (_scr_size.bottom - 99); + rect.right = 317; + rect.bottom = rect.top + 47; + + sprintf(string, "%d", rotation); + + if (tool != NULL) { + windowFill(tool_win, + 290, + 452 - (_scr_size.bottom - 99), + 10, + 12, + tool[(452 - (_scr_size.bottom - 99)) * (_scr_size.right + 1) + 289]); + windowDrawText(tool_win, + string, + 10, + 292, + 452 - (_scr_size.bottom - 99), + 0x2010104); + + for (index = 0; index < 6; index++) { + int x = rotate_arrows_x_offs[index] + 269; + int y = rotate_arrows_y_offs[index] + (430 - (_scr_size.bottom - 99)); + + blitBufferToBufferTrans(rotate_arrows[index == rotation][index], + 10, + 10, + 10, + tool + y * (_scr_size.right + 1) + x, + _scr_size.right + 1); + } + + windowRefreshRect(tool_win, &rect); + } else { + debugPrint("Error: mapper_refresh_rotation: tool buffer invalid!"); + } +} + +// 0x48B850 +void update_art(int a1, int a2) +{ + // TODO: Incomplete. +} + +// 0x48C524 +void handle_new_map(int* a1, int* a2) +{ + Rect rect; + + rect.left = 30; + rect.top = 62; + rect.right = 50; + rect.bottom = 88; + blitBufferToBuffer(e_num[gElevation], + 19, + 26, + 19, + tool + rect.top * rectGetWidth(&_scr_size) + rect.left, + rectGetWidth(&_scr_size)); + windowRefreshRect(tool_win, &rect); + + if (*a1 < 0 || *a1 > 6) { + *a1 = 4; + } + + *a2 = 0; + update_art(*a1, *a2); + + print_toolbar_name(OBJ_TYPE_TILE); + + map_entered = false; + + if (tileRoofIsVisible()) { + tile_toggle_roof(true); + } +} + +// 0x48C604 +int mapper_inven_unwield(Object* obj, int right_hand) +{ + Object* item; + int fid; + + reg_anim_begin(ANIMATION_REQUEST_RESERVED); + + if (right_hand) { + item = critterGetItem2(obj); + } else { + item = critterGetItem1(obj); + } + + if (item != NULL) { + item->flags &= ~OBJECT_IN_ANY_HAND; + } + + animationRegisterAnimate(obj, ANIM_PUT_AWAY, 0); + + fid = buildFid(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, 0, 0, (obj->fid & 0x70000000) >> 28); + animationRegisterSetFid(obj, fid, 0); + + return reg_anim_end(); +} + +// 0x48C678 +int mapper_mark_exit_grid() +{ + int y; + int x; + int tile; + Object* obj; + + for (y = -2000; y != 2000; y += 200) { + for (x = -10; x < 10; x++) { + tile = gGameMouseBouncingCursor->tile + y + x; + + obj = objectFindFirstAtElevation(gElevation); + while (obj != NULL) { + if (isExitGridPid(obj->pid)) { + obj->data.misc.map = mapInfo.map; + obj->data.misc.tile = mapInfo.tile; + obj->data.misc.elevation = mapInfo.elevation; + obj->data.misc.rotation = mapInfo.rotation; + } + obj = objectFindNextAtElevation(); + } + } + } + + return 0; +} + +// 0x48C704 +void mapper_mark_all_exit_grids() +{ + Object* obj; + + obj = objectFindFirstAtElevation(gElevation); + while (obj != NULL) { + if (isExitGridPid(obj->pid)) { + obj->data.misc.map = mapInfo.map; + obj->data.misc.tile = mapInfo.tile; + obj->data.misc.elevation = mapInfo.elevation; + obj->data.misc.rotation = mapInfo.rotation; + } + obj = objectFindNextAtElevation(); + } +} + +} // namespace fallout diff --git a/src/mapper/mapper.h b/src/mapper/mapper.h new file mode 100644 index 00000000..4ae73cfc --- /dev/null +++ b/src/mapper/mapper.h @@ -0,0 +1,23 @@ +#ifndef FALLOUT_MAPPER_MAPPER_H_ +#define FALLOUT_MAPPER_MAPPER_H_ + +#include "map.h" +#include "obj_types.h" + +namespace fallout { + +extern MapTransition mapInfo; + +extern int menu_val_0[8]; +extern int menu_val_2[8]; +extern int menu_val_1[21]; +extern unsigned char* tool; +extern int tool_win; + +int mapper_main(int argc, char** argv); +void print_toolbar_name(int object_type); +int mapper_inven_unwield(Object* obj, int right_hand); + +} // namespace fallout + +#endif /* FALLOUT_MAPPER_MAPPER_H_ */ diff --git a/src/mapper/mp_proto.cc b/src/mapper/mp_proto.cc new file mode 100644 index 00000000..6e1b1be4 --- /dev/null +++ b/src/mapper/mp_proto.cc @@ -0,0 +1,607 @@ +#include "mapper/mp_proto.h" + +#include + +#include "art.h" +#include "color.h" +#include "combat_ai.h" +#include "critter.h" +#include "input.h" +#include "kb.h" +#include "mapper/mp_targt.h" +#include "memory.h" +#include "proto.h" +#include "svga.h" +#include "window_manager.h" +#include "window_manager_private.h" + +namespace fallout { + +#define CRITTER_FLAG_COUNT 10 + +#define YES 0 +#define NO 1 + +static int proto_choose_container_flags(Proto* proto); +static int proto_subdata_setup_int_button(const char* title, int key, int value, int min_value, int max_value, int* y, int a7); +static int proto_subdata_setup_fid_button(const char* title, int key, int fid, int* y, int a5); +static int proto_subdata_setup_pid_button(const char* title, int key, int pid, int* y, int a5); +static void proto_critter_flags_redraw(int win, int pid); +static int proto_critter_flags_modify(int pid); +static int mp_pick_kill_type(); + +static char kYes[] = "YES"; +static char kNo[] = "NO"; + +// 0x53DAFC +static char default_proto_builder_name[36] = "EVERTS SCOTTY"; + +// 0x559924 +char* proto_builder_name = default_proto_builder_name; + +// 0x559B94 +static const char* wall_light_strs[] = { + "North/South", + "East/West", + "North Corner", + "South Corner", + "East Corner", + "West Corner", +}; + +// 0x559C50 +static char* yesno[] = { + kYes, + kNo, +}; + +// 0x559C58 +int edit_window_color = 1; + +// 0x559C60 +bool can_modify_protos = false; + +// 0x559C68 +static int subwin = -1; + +// 0x559C6C +static int critFlagList[CRITTER_FLAG_COUNT] = { + CRITTER_NO_STEAL, + CRITTER_NO_DROP, + CRITTER_NO_LIMBS, + CRITTER_NO_AGE, + CRITTER_NO_HEAL, + CRITTER_INVULNERABLE, + CRITTER_FLAT, + CRITTER_SPECIAL_DEATH, + CRITTER_LONG_LIMBS, + CRITTER_NO_KNOCKBACK, +}; + +// 0x559C94 +static const char* critFlagStrs[CRITTER_FLAG_COUNT] = { + "_Steal", + "_Drop", + "_Limbs", + "_Ages", + "_Heal", + "Invuln.,", + "_Flattens", + "Special", + "Rng", + "_Knock", +}; + +// 0x4922F8 +void init_mapper_protos() +{ + edit_window_color = _colorTable[10570]; + can_modify_protos = target_overriden(); +} + +// 0x492840 +int proto_choose_container_flags(Proto* proto) +{ + int win = windowCreate(320, + 185, + 220, + 205, + edit_window_color, + WINDOW_MOVE_ON_TOP); + if (win == -1) { + return -1; + } + + _win_register_text_button(win, + 10, + 11, + -1, + -1, + -1, + '1', + "Magic Hands Grnd", + 0); + + if ((proto->item.data.container.openFlags & 0x1) != 0) { + windowDrawText(win, + yesno[YES], + 50, + 125, + 15, + _colorTable[32747] | 0x10000); + } else { + windowDrawText(win, + yesno[NO], + 50, + 125, + 15, + _colorTable[32747] | 0x10000); + } + + _win_register_text_button(win, + 10, + 32, + -1, + -1, + -1, + '2', + "Cannot Pick Up", + 0); + + if (_proto_action_can_pickup(proto->pid)) { + windowDrawText(win, + yesno[YES], + 50, + 125, + 36, + _colorTable[32747] | 0x10000); + } else { + windowDrawText(win, + yesno[NO], + 50, + 125, + 36, + _colorTable[32747] | 0x10000); + } + + windowDrawBorder(win); + windowRefresh(win); + + while (1) { + sharedFpsLimiter.mark(); + + int input = inputGetInput(); + if (input == KEY_ESCAPE + || input == KEY_BAR + || input == KEY_RETURN) { + break; + } + + if (input == '1') { + proto->item.data.container.openFlags ^= 0x1; + + if ((proto->item.data.container.openFlags & 0x1) != 0) { + windowDrawText(win, + yesno[YES], + 50, + 125, + 15, + _colorTable[32747] | 0x10000); + } else { + windowDrawText(win, + yesno[NO], + 50, + 125, + 15, + _colorTable[32747] | 0x10000); + } + + windowRefresh(win); + } else if (input == '2') { + proto->item.extendedFlags ^= 0x8000; + + if (_proto_action_can_pickup(proto->pid)) { + windowDrawText(win, + yesno[YES], + 50, + 125, + 36, + _colorTable[32747] | 0x10000); + } else { + windowDrawText(win, + yesno[NO], + 50, + 125, + 36, + _colorTable[32747] | 0x10000); + } + + windowRefresh(win); + } + + renderPresent(); + sharedFpsLimiter.throttle(); + } + + windowDestroy(win); + + return 0; +} + +// 0x492A3C +int proto_subdata_setup_int_button(const char* title, int key, int value, int min_value, int max_value, int* y, int a7) +{ + char text[36]; + int button_x; + int value_offset_x; + + button_x = 10; + value_offset_x = 90; + + if (a7 == 9) { + *y -= 189; + } + + if (a7 > 8) { + button_x = 165; + value_offset_x -= 16; + } + + _win_register_text_button(subwin, + button_x, + *y, + -1, + -1, + -1, + key, + title, + 0); + + if (value >= min_value && value < max_value) { + sprintf(text, "%d", value); + windowDrawText(subwin, + text, + 38, + button_x + value_offset_x, + *y + 4, + _colorTable[32747] | 0x10000); + } else { + windowDrawText(subwin, + "", + 38, + button_x + value_offset_x, + *y + 4, + _colorTable[31744] | 0x10000); + } + + *y += 21; + + return 0; +} + +// 0x492B28 +int proto_subdata_setup_fid_button(const char* title, int key, int fid, int* y, int a5) +{ + char text[36]; + char* pch; + int button_x; + int value_offset_x; + + button_x = 10; + value_offset_x = 90; + + if (a5 == 9) { + *y -= 189; + } + + if (a5 > 8) { + button_x = 165; + value_offset_x -= 16; + } + + _win_register_text_button(subwin, + button_x, + *y, + -1, + -1, + -1, + key, + title, + 0); + + if (art_list_str(fid, text) != -1) { + pch = strchr(text, '.'); + if (pch != NULL) { + *pch = '\0'; + } + + windowDrawText(subwin, + text, + 80, + button_x + value_offset_x, + *y + 4, + _colorTable[32747] | 0x10000); + } else { + windowDrawText(subwin, + "None", + 80, + button_x + value_offset_x, + *y + 4, + _colorTable[992] | 0x10000); + } + + *y += 21; + + return 0; +} + +// 0x492C20 +int proto_subdata_setup_pid_button(const char* title, int key, int pid, int* y, int a5) +{ + int button_x; + int value_offset_x; + + button_x = 10; + value_offset_x = 90; + + if (a5 == 9) { + *y -= 189; + } + + if (a5 > 8) { + button_x = 165; + value_offset_x = 74; + } + + _win_register_text_button(subwin, + button_x, + *y, + -1, + -1, + -1, + key, + title, + 0); + + if (pid != -1) { + windowDrawText(subwin, + protoGetName(pid), + 49, + button_x + value_offset_x, + *y + 4, + _colorTable[32747] | 0x10000); + } else { + windowDrawText(subwin, + "None", + 49, + button_x + value_offset_x, + *y + 4, + _colorTable[992] | 0x10000); + } + + *y += 21; + + return 0; +} + +// 0x495438 +const char* proto_wall_light_str(int flags) +{ + if ((flags & 0x8000000) != 0) { + return wall_light_strs[1]; + } + + if ((flags & 0x10000000) != 0) { + return wall_light_strs[2]; + } + + if ((flags & 0x20000000) != 0) { + return wall_light_strs[3]; + } + + if ((flags & 0x40000000) != 0) { + return wall_light_strs[4]; + } + + if ((flags & 0x80000000) != 0) { + return wall_light_strs[5]; + } + + return wall_light_strs[0]; +} + +// 0x4960B8 +void proto_critter_flags_redraw(int win, int pid) +{ + int index; + int color; + int x = 110; + + for (index = 0; index < CRITTER_FLAG_COUNT; index++) { + if (_critter_flag_check(pid, critFlagList[index])) { + color = _colorTable[992]; + } else { + color = _colorTable[10570]; + } + + windowDrawText(win, critFlagStrs[index], 44, x, 195, color | 0x10000); + x += 48; + } +} + +// 0x496120 +int proto_critter_flags_modify(int pid) +{ + Proto* proto; + int rc; + int flags = 0; + int index; + + if (protoGetProto(pid, &proto) == -1) { + return -1; + } + + rc = win_yes_no("Can't be stolen from?", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_NO_STEAL; + } + + rc = win_yes_no("Can't Drop items?", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_NO_DROP; + } + + rc = win_yes_no("Can't lose limbs?", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_NO_LIMBS; + } + + rc = win_yes_no("Dead Bodies Can't Age?", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_NO_AGE; + } + + rc = win_yes_no("Can't Heal by Aging?", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_NO_HEAL; + } + + rc = win_yes_no("Is Invlunerable????", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_INVULNERABLE; + } + + rc = win_yes_no("Can't Flatten on Death?", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_FLAT; + } + + rc = win_yes_no("Has Special Death?", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_SPECIAL_DEATH; + } + + rc = win_yes_no("Has Extra Hand-To-Hand Range?", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_LONG_LIMBS; + } + + rc = win_yes_no("Can't be knocked back?", 340, 200, _colorTable[15855]); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + flags |= CRITTER_NO_KNOCKBACK; + } + + if (!can_modify_protos) { + win_timed_msg("Can't modify protos!", _colorTable[31744] | 0x10000); + return -1; + } + + for (index = 0; index < CRITTER_FLAG_COUNT; index++) { + if ((critFlagList[index] & flags) != 0) { + critter_flag_set(pid, critFlagList[index]); + } else { + critter_flag_unset(pid, critFlagList[index]); + } + } + + return 0; +} + +// 0x497520 +int mp_pick_kill_type() +{ + char* names[KILL_TYPE_COUNT]; + int index; + + for (index = 0; index < KILL_TYPE_COUNT; index++) { + names[index] = killTypeGetName(index); + } + + return _win_list_select("Kill Type", + names, + KILL_TYPE_COUNT, + NULL, + 50, + 100, + _colorTable[15855]); +} + +// 0x497568 +int proto_pick_ai_packet(int* value) +{ + int count; + char** names; + int index; + int rc; + + count = combat_ai_num(); + if (count <= 0) { + return -1; + } + + names = (char**)internal_malloc(sizeof(char*) * count); + for (index = 0; index < count; index++) { + names[index] = (char*)internal_malloc(strlen(combat_ai_name(index)) + 1); + strcpy(names[index], combat_ai_name(index)); + } + + rc = _win_list_select("AI Packet", + names, + count, + NULL, + 50, + 100, + _colorTable[15855]); + if (rc != -1) { + *value = rc; + } + + for (index = 0; index < count; index++) { + internal_free(names[index]); + } + + internal_free(names); + return 0; +} + +} // namespace fallout diff --git a/src/mapper/mp_proto.h b/src/mapper/mp_proto.h new file mode 100644 index 00000000..e4e14431 --- /dev/null +++ b/src/mapper/mp_proto.h @@ -0,0 +1,15 @@ +#ifndef FALLOUT_MAPPER_MP_PROTO_H_ +#define FALLOUT_MAPPER_MP_PROTO_H_ + +namespace fallout { + +extern char* proto_builder_name; +extern bool can_modify_protos; + +void init_mapper_protos(); +const char* proto_wall_light_str(int flags); +int proto_pick_ai_packet(int* value); + +} // namespace fallout + +#endif /* FALLOUT_MAPPER_MP_PROTO_H_ */ diff --git a/src/mapper/mp_scrpt.cc b/src/mapper/mp_scrpt.cc new file mode 100644 index 00000000..eb2f9c6c --- /dev/null +++ b/src/mapper/mp_scrpt.cc @@ -0,0 +1,90 @@ +#include "mapper/mp_scrpt.h" + +#include "art.h" +#include "object.h" +#include "scripts.h" +#include "tile.h" + +namespace fallout { + +// 0x49B170 +int map_scr_remove_spatial(int tile, int elevation) +{ + Script* scr; + Object* obj; + Rect rect; + + scr = scriptGetFirstSpatialScript(elevation); + while (scr != NULL) { + if (builtTileGetTile(scr->sp.built_tile) == tile) { + scriptRemove(scr->sid); + + scr = scriptGetFirstSpatialScript(elevation); + continue; + } + + scr = scriptGetNextSpatialScript(); + } + + obj = objectFindFirstAtElevation(elevation); + while (obj != NULL) { + if (obj->tile == tile && buildFid(OBJ_TYPE_INTERFACE, 3, 0, 0, 0) == obj->fid) { + objectDestroy(obj, &rect); + tileWindowRefreshRect(&rect, elevation); + + obj = objectFindFirstAtElevation(elevation); + continue; + } + + obj = objectFindNextAtElevation(); + } + + return 0; +} + +// 0x49B214 +int map_scr_remove_all_spatials() +{ + int elevation; + Script* scr; + Object* obj; + int sid; + + for (elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + scr = scriptGetFirstSpatialScript(elevation); + while (scr != NULL) { + scriptRemove(scr->sid); + + scr = scriptGetFirstSpatialScript(elevation); + } + + obj = objectFindFirstAtElevation(elevation); + while (obj != NULL) { + if (buildFid(OBJ_TYPE_INTERFACE, 3, 0, 0, 0) == obj->fid) { + objectDestroy(obj, NULL); + + obj = objectFindFirstAtElevation(elevation); + continue; + } + + obj = objectFindNextAtElevation(); + } + } + + tileWindowRefresh(); + + for (sid = 0; sid < 15000; sid++) { + if (scriptGetScript(sid, &scr) != -1) { + if (scr->owner != NULL) { + if (scr->owner->pid == 0x500000C) { + scr->owner->sid = -1; + scriptRemove(sid); + } + } + } + } + + return 0; +} + +} // namespace fallout diff --git a/src/mapper/mp_scrpt.h b/src/mapper/mp_scrpt.h new file mode 100644 index 00000000..64c2d590 --- /dev/null +++ b/src/mapper/mp_scrpt.h @@ -0,0 +1,11 @@ +#ifndef FALLOUT_MAPPER_MP_SCRPTR_H_ +#define FALLOUT_MAPPER_MP_SCRPTR_H_ + +namespace fallout { + +int map_scr_remove_spatial(int tile, int elevation); +int map_scr_remove_all_spatials(); + +} // namespace fallout + +#endif /* FALLOUT_MAPPER_MP_SCRPTR_H_ */ diff --git a/src/mapper/mp_targt.cc b/src/mapper/mp_targt.cc new file mode 100644 index 00000000..ab618370 --- /dev/null +++ b/src/mapper/mp_targt.cc @@ -0,0 +1,531 @@ +#include "mapper/mp_targt.h" + +#include + +#include "art.h" +#include "game.h" +#include "map.h" +#include "mapper/mp_proto.h" +#include "memory.h" +#include "proto.h" +#include "window_manager_private.h" + +namespace fallout { + +#define TARGET_DAT "target.dat" + +typedef struct TargetNode { + TargetSubNode subnode; + struct TargetNode* next; +} TargetNode; + +typedef struct TargetList { + TargetNode* tail; + int count; + int next_tid; +} TargetList; + +// 0x53F354 +static char default_target_path_base[] = "\\fallout2\\dev\\proto\\"; + +// 0x559CC4 +static TargetList targetlist = { NULL, 0, 0 }; + +// 0x559CD0 +static char* target_path_base = default_target_path_base; + +// 0x559DBC +static bool tgt_overriden = false; + +// 0x49B2F0 +void target_override_protection() +{ + char* name; + + tgt_overriden = true; + + name = getenv("MAIL_NAME"); + if (name != NULL) { + // NOTE: Unsafe, backing buffer is 32 byte max. + strcpy(proto_builder_name, strupr(name)); + } else { + strcpy(proto_builder_name, "UNKNOWN"); + } +} + +// 0x49B2F0 +bool target_overriden() +{ + return tgt_overriden; +} + +// 0x49B34C +void target_make_path(char* path, int pid) +{ + if (_cd_path_base[0] != '\0' && _cd_path_base[1] == ':') { + strncpy(path, _cd_path_base, 2); + strcat(path, target_path_base); + } else { + strcpy(path, target_path_base); + } + + if (pid != -1) { + strcat(path, artGetObjectTypeName(PID_TYPE(pid))); + } +} + +// 0x49B424 +int target_init() +{ + target_remove_all(); + target_header_load(); + + return 0; +} + +// 0x49B434 +int target_exit() +{ + if (can_modify_protos) { + target_header_save(); + target_remove_all(); + } else { + target_remove_all(); + } + + return 0; +} + +// 0x49B454 +int target_header_save() +{ + char path[COMPAT_MAX_PATH]; + FILE* stream; + + target_make_path(path, -1); + strcat(path, TARGET_DAT); + + stream = fopen(path, "wb"); + if (stream == NULL) { + return -1; + } + + if (fwrite(&targetlist, sizeof(targetlist), 1, stream) != 1) { + // FIXME: Leaking `stream`. + return -1; + } + + fclose(stream); + return 0; +} + +// 0x49B4E8 +int target_header_load() +{ + char path[COMPAT_MAX_PATH]; + FILE* stream; + + target_make_path(path, -1); + strcat(path, TARGET_DAT); + + stream = fopen(path, "rb"); + if (stream == NULL) { + return -1; + } + + if (fread(&targetlist, sizeof(targetlist), 1, stream) != 1) { + // FIXME: Leaking `stream`. + return -1; + } + + targetlist.tail = NULL; + targetlist.count = 0; + + fclose(stream); + return 0; +} + +// 0x49B58C +int target_save(int pid) +{ + char path[COMPAT_MAX_PATH]; + size_t len; + char* extension; + FILE* stream; + TargetSubNode* subnode; + + if (target_ptr(pid, &subnode) == -1) { + return -1; + } + + target_make_path(path, pid); + + len = strlen(path); + path[len] = '\\'; + _proto_list_str(pid, path + len + 1); + + extension = strchr(path + len + 1, '.'); + if (extension != NULL) { + strcpy(extension + 1, "tgt"); + } else { + strcat(path, ".tgt"); + } + + stream = fopen(path, "wb"); + if (stream == NULL) { + return -1; + } + + while (subnode != NULL) { + fwrite(subnode, sizeof(TargetSubNode), 1, stream); + subnode = subnode->next; + } + + fclose(stream); + + return 0; +} + +// 0x49B6BC +int target_load(int pid, TargetSubNode** subnode_ptr) +{ + char path[COMPAT_MAX_PATH]; + size_t len; + char* extension; + FILE* stream; + TargetSubNode* subnode; + + target_make_path(path, pid); + + len = strlen(path); + path[len] = '\\'; + _proto_list_str(pid, path + len + 1); + + extension = strchr(path + len + 1, '.'); + if (extension != NULL) { + strcpy(extension + 1, "tgt"); + } else { + strcat(path, ".tgt"); + } + + stream = fopen(path, "rb"); + if (stream == NULL) { + *subnode_ptr = NULL; + return -1; + } + + if (target_find_free_subnode(&subnode) == -1) { + *subnode_ptr = NULL; + // FIXME: Leaks `stream`. + return -1; + } + + fread(subnode, sizeof(TargetSubNode), 1, stream); + + *subnode_ptr = subnode; + + while (subnode->next != NULL) { + subnode->next = (TargetSubNode*)internal_malloc(sizeof(TargetSubNode)); + if (subnode->next == NULL) { + // FIXME: Leaks `stream`. + return -1; + } + + subnode = subnode->next; + fread(subnode, sizeof(TargetSubNode), 1, stream); + } + + fclose(stream); + + return 0; +} + +// 0x49B9C0 +int target_find_free_subnode(TargetSubNode** subnode_ptr) +{ + TargetNode* node = (TargetNode*)internal_malloc(sizeof(TargetNode)); + if (node == NULL) { + *subnode_ptr = NULL; + return -1; + } + + *subnode_ptr = &(node->subnode); + + node->subnode.pid = -1; + node->subnode.next = NULL; + node->next = targetlist.tail; + + targetlist.tail = node; + targetlist.count++; + + return 0; +} + +// 0x49BA10 +int target_new(int pid, int* tid_ptr) +{ + TargetSubNode* subnode; + TargetSubNode* new_subnode; + + if (target_ptr(pid, &subnode) == -1) { + if (target_find_free_subnode(&subnode) == -1) { + return -1; + } + } + + new_subnode = (TargetSubNode*)internal_malloc(sizeof(TargetSubNode)); + if (new_subnode == NULL) { + return -1; + } + + new_subnode->next = NULL; + + while (subnode->next != NULL) { + subnode = subnode->next; + } + + subnode->next = new_subnode; + + new_subnode->pid = pid; + new_subnode->tid = targetlist.next_tid; + + *tid_ptr = targetlist.next_tid; + + targetlist.count++; + targetlist.next_tid++; + + return 0; +} + +// 0x49BBD4 +int target_remove(int pid) +{ + TargetNode* node; + TargetSubNode* subnode; + TargetSubNode* subnode_next; + + node = targetlist.tail; + while (node != NULL) { + if (node->subnode.pid == pid) { + break; + } + node = node->next; + } + + if (node == NULL) { + return -1; + } + + subnode = node->subnode.next; + + if (node == targetlist.tail) { + targetlist.tail = targetlist.tail->next; + } + + internal_free(node); + + while (subnode != NULL) { + subnode_next = subnode->next; + internal_free(subnode); + subnode = subnode_next; + } + + return 0; +} + +// 0x49BC3C +int target_remove_tid(int pid, int tid) +{ + TargetNode* node; + TargetSubNode* subnode; + TargetSubNode* subnode_next; + + node = targetlist.tail; + while (node != NULL) { + if (node->subnode.pid == pid) { + break; + } + node = node->next; + } + + if (node == NULL) { + return -1; + } + + if (node->subnode.tid == tid) { + subnode_next = node->subnode.next; + if (subnode_next != NULL) { + memcpy(&(node->subnode), subnode_next, sizeof(TargetSubNode)); + internal_free(subnode_next); + } else { + target_remove(pid); + } + + // FIXME: Should probably return 0 here. + } else { + subnode = &(node->subnode); + while (subnode != NULL) { + subnode_next = subnode->next; + if (subnode_next->tid == tid) { + subnode->next = subnode_next->next; + internal_free(subnode_next); + return 0; + } + + subnode = subnode_next; + } + } + + return -1; +} + +// 0x49BCBC +int target_remove_all() +{ + TargetNode* node; + TargetNode* node_next; + TargetSubNode* subnode; + TargetSubNode* subnode_next; + + node = targetlist.tail; + targetlist.tail = NULL; + + while (node != NULL) { + node_next = node->next; + + subnode = node->subnode.next; + while (subnode != NULL) { + subnode_next = subnode->next; + internal_free(subnode); + subnode = subnode_next; + } + + internal_free(node); + node = node_next; + } + + return 0; +} + +// 0x49BD00 +int target_ptr(int pid, TargetSubNode** subnode_ptr) +{ + TargetNode* node = targetlist.tail; + while (node != NULL) { + if (node->subnode.pid == pid) { + *subnode_ptr = &(node->subnode); + return 0; + } + } + + return target_load(pid, subnode_ptr); +} + +// 0x49BD38 +int target_tid_ptr(int pid, int tid, TargetSubNode** subnode_ptr) +{ + TargetSubNode* subnode; + if (target_ptr(pid, &subnode) == -1) { + return -1; + } + + while (subnode != NULL) { + if (subnode->tid == tid) { + *subnode_ptr = subnode; + return 0; + } + } + + return -1; +} + +// 0x49BD98 +int pick_rot() +{ + int value; + win_get_num_i(&value, + -1, + 5, + false, + "Rotation", + 100, + 100); + return value; +} + +// 0x49BDD0 +int target_pick_global_var(int* value_ptr) +{ + int value; + int rc; + + if (gGameGlobalVarsLength == 0) { + return -1; + } + + rc = win_get_num_i(&value, + 0, + gGameGlobalVarsLength - 1, + false, + "Global Variable Index #:", + 100, + 100); + if (rc == -1) { + return -1; + } + + *value_ptr = value; + return 0; +} + +// 0x49BE20 +int target_pick_map_var(int* value_ptr) +{ + int value; + int rc; + + if (gMapGlobalVarsLength == 0) { + return -1; + } + + rc = win_get_num_i(&value, + 0, + gMapGlobalVarsLength - 1, + false, + "Map Variable Index #:", + 100, + 100); + if (rc == -1) { + return -1; + } + + *value_ptr = value; + return 0; +} + +// 0x49BE70 +int target_pick_local_var(int* value_ptr) +{ + int value; + int rc; + + if (gMapLocalVarsLength == 0) { + return -1; + } + + rc = win_get_num_i(&value, + 0, + gMapLocalVarsLength - 1, + false, + "Local Variable Index #:", + 100, + 100); + if (rc == -1) { + return -1; + } + + *value_ptr = value; + return 0; +} + +} // namespace fallout diff --git a/src/mapper/mp_targt.h b/src/mapper/mp_targt.h new file mode 100644 index 00000000..eb706958 --- /dev/null +++ b/src/mapper/mp_targt.h @@ -0,0 +1,72 @@ +#ifndef FALLOUT_MAPPER_MP_TARGT_H_ +#define FALLOUT_MAPPER_MP_TARGT_H_ + +namespace fallout { + +typedef struct TargetSubNode { + int pid; + int tid; + int field_8; + int field_C; + int field_10; + int field_14; + int field_18; + int field_1C; + int field_20; + int field_24; + struct TargetSubNode* next; + int field_2C; + int field_30; + int field_34; + int field_38; + int field_3C; + int field_40; + int field_44; + int field_48; + int field_4C; + int field_50; + int field_54; + int field_58; + int field_5C; + int field_60; + int field_64; + int field_68; + int field_6C; + int field_70; + int field_74; + int field_78; + int field_7C; + int field_80; + int field_84; + int field_88; + int field_8C; + int field_90; + int field_94; + int field_98; + int field_9C; +} TargetSubNode; + +void target_override_protection(); +bool target_overriden(); +void target_make_path(char* path, int pid); +int target_init(); +int target_exit(); +int target_header_save(); +int target_header_load(); +int target_save(int pid); +int target_load(int pid, TargetSubNode** subnode_ptr); +int target_find_free_subnode(TargetSubNode** subnode_ptr); +int target_new(int pid, int* tid_ptr); +int target_remove(int pid); +int target_remove_tid(int pid, int tid); +int target_remove_all(); +int target_ptr(int pid, TargetSubNode** subnode_ptr); +int target_tid_ptr(int pid, int tid, TargetSubNode** subnode_ptr); +int pick_rot(); +int target_pick_global_var(int* value_ptr); +int target_pick_map_var(int* value_ptr); +int target_pick_local_var(int* value_ptr); + +} // namespace fallout + +#endif /* FALLOUT_MAPPER_MP_TARGT_H_ */ diff --git a/src/mapper/mp_text.cc b/src/mapper/mp_text.cc new file mode 100644 index 00000000..3d40fb8d --- /dev/null +++ b/src/mapper/mp_text.cc @@ -0,0 +1,13 @@ +#include "mapper/mp_text.h" + +namespace fallout { + +// 0x49DAC4 +int proto_build_all_texts() +{ + // TODO: Incomplete. + + return 0; +} + +} // namespace fallout diff --git a/src/mapper/mp_text.h b/src/mapper/mp_text.h new file mode 100644 index 00000000..b2758780 --- /dev/null +++ b/src/mapper/mp_text.h @@ -0,0 +1,10 @@ +#ifndef FALLOUT_MAPPER_MP_TEXT_H_ +#define FALLOUT_MAPPER_MP_TEXT_H_ + +namespace fallout { + +int proto_build_all_texts(); + +} // namespace fallout + +#endif /* FALLOUT_MAPPER_MP_TEXT_H_ */ diff --git a/src/memory.cc b/src/memory.cc index 5f150473..c51ff393 100644 --- a/src/memory.cc +++ b/src/memory.cc @@ -32,7 +32,6 @@ typedef struct MemoryBlockFooter { static void* memoryBlockMallocImpl(size_t size); static void* memoryBlockReallocImpl(void* ptr, size_t size); static void memoryBlockFreeImpl(void* ptr); -static void memoryBlockPrintStats(); static void* mem_prep_block(void* block, size_t size); static void memoryBlockValidate(void* block); @@ -176,10 +175,8 @@ static void memoryBlockFreeImpl(void* ptr) } } -// NOTE: Not used. -// // 0x4C5C5C -static void memoryBlockPrintStats() +void mem_check() { if (gMallocProc == memoryBlockMallocImpl) { debugPrint("Current memory allocated: %6d blocks, %9u bytes total\n", gMemoryBlocksCurrentCount, gMemoryBlocksCurrentSize); diff --git a/src/memory.h b/src/memory.h index facaa544..96b0364e 100644 --- a/src/memory.h +++ b/src/memory.h @@ -9,6 +9,7 @@ char* internal_strdup(const char* string); void* internal_malloc(size_t size); void* internal_realloc(void* ptr, size_t size); void internal_free(void* ptr); +void mem_check(); } // namespace fallout diff --git a/src/object.cc b/src/object.cc index a597a76c..4c46d215 100644 --- a/src/object.cc +++ b/src/object.cc @@ -2375,10 +2375,10 @@ bool _obj_occupied(int tile, int elevation) } // 0x48B848 -Object* _obj_blocking_at(Object* a1, int tile, int elev) +Object* _obj_blocking_at(Object* excludeObj, int tile, int elev) { ObjectListNode* objectListNode; - Object* v7; + Object* obj; int type; if (!hexGridTileIsValid(tile)) { @@ -2387,14 +2387,14 @@ Object* _obj_blocking_at(Object* a1, int tile, int elev) objectListNode = gObjectListHeadByTile[tile]; while (objectListNode != NULL) { - v7 = objectListNode->obj; - if (v7->elevation == elev) { - if ((v7->flags & OBJECT_HIDDEN) == 0 && (v7->flags & OBJECT_NO_BLOCK) == 0 && v7 != a1) { - type = FID_TYPE(v7->fid); + obj = objectListNode->obj; + if (obj->elevation == elev) { + if ((obj->flags & OBJECT_HIDDEN) == 0 && (obj->flags & OBJECT_NO_BLOCK) == 0 && obj != excludeObj) { + type = FID_TYPE(obj->fid); if (type == OBJ_TYPE_CRITTER || type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) { - return v7; + return obj; } } } @@ -2406,15 +2406,15 @@ Object* _obj_blocking_at(Object* a1, int tile, int elev) if (hexGridTileIsValid(neighboor)) { objectListNode = gObjectListHeadByTile[neighboor]; while (objectListNode != NULL) { - v7 = objectListNode->obj; - if ((v7->flags & OBJECT_MULTIHEX) != 0) { - if (v7->elevation == elev) { - if ((v7->flags & OBJECT_HIDDEN) == 0 && (v7->flags & OBJECT_NO_BLOCK) == 0 && v7 != a1) { - type = FID_TYPE(v7->fid); + obj = objectListNode->obj; + if ((obj->flags & OBJECT_MULTIHEX) != 0) { + if (obj->elevation == elev) { + if ((obj->flags & OBJECT_HIDDEN) == 0 && (obj->flags & OBJECT_NO_BLOCK) == 0 && obj != excludeObj) { + type = FID_TYPE(obj->fid); if (type == OBJ_TYPE_CRITTER || type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) { - return v7; + return obj; } } } @@ -2428,7 +2428,7 @@ Object* _obj_blocking_at(Object* a1, int tile, int elev) } // 0x48B930 -Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev) +Object* _obj_shoot_blocking_at(Object* excludeObj, int tile, int elev) { if (!hexGridTileIsValid(tile)) { return NULL; @@ -2439,7 +2439,7 @@ Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev) Object* candidate = objectListItem->obj; if (candidate->elevation == elev) { unsigned int flags = candidate->flags; - if ((flags & OBJECT_HIDDEN) == 0 && ((flags & OBJECT_NO_BLOCK) == 0 || (flags & OBJECT_SHOOT_THRU) == 0) && candidate != obj) { + if ((flags & OBJECT_HIDDEN) == 0 && ((flags & OBJECT_NO_BLOCK) == 0 || (flags & OBJECT_SHOOT_THRU) == 0) && candidate != excludeObj) { int type = FID_TYPE(candidate->fid); // SFALL: Fix to prevent corpses from blocking line of fire. if ((type == OBJ_TYPE_CRITTER && !critterIsDead(candidate)) @@ -2464,7 +2464,7 @@ Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev) unsigned int flags = candidate->flags; if ((flags & OBJECT_MULTIHEX) != 0) { if (candidate->elevation == elev) { - if ((flags & OBJECT_HIDDEN) == 0 && (flags & OBJECT_NO_BLOCK) == 0 && candidate != obj) { + if ((flags & OBJECT_HIDDEN) == 0 && (flags & OBJECT_NO_BLOCK) == 0 && candidate != excludeObj) { int type = FID_TYPE(candidate->fid); // SFALL: Fix to prevent corpses from blocking line of // fire. @@ -2484,7 +2484,7 @@ Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev) } // 0x48BA20 -Object* _obj_ai_blocking_at(Object* a1, int tile, int elevation) +Object* _obj_ai_blocking_at(Object* excludeObj, int tile, int elevation) { if (!hexGridTileIsValid(tile)) { return NULL; @@ -2496,7 +2496,7 @@ Object* _obj_ai_blocking_at(Object* a1, int tile, int elevation) if (object->elevation == elevation) { if ((object->flags & OBJECT_HIDDEN) == 0 && (object->flags & OBJECT_NO_BLOCK) == 0 - && object != a1) { + && object != excludeObj) { int objectType = FID_TYPE(object->fid); if (objectType == OBJ_TYPE_CRITTER || objectType == OBJ_TYPE_SCENERY @@ -2525,7 +2525,7 @@ Object* _obj_ai_blocking_at(Object* a1, int tile, int elevation) if (object->elevation == elevation) { if ((object->flags & OBJECT_HIDDEN) == 0 && (object->flags & OBJECT_NO_BLOCK) == 0 - && object != a1) { + && object != excludeObj) { int objectType = FID_TYPE(object->fid); if (objectType == OBJ_TYPE_CRITTER || objectType == OBJ_TYPE_SCENERY @@ -2571,7 +2571,7 @@ int _obj_scroll_blocking_at(int tile, int elev) } // 0x48BB88 -Object* _obj_sight_blocking_at(Object* a1, int tile, int elevation) +Object* _obj_sight_blocking_at(Object* excludeObj, int tile, int elevation) { ObjectListNode* objectListNode = gObjectListHeadByTile[tile]; while (objectListNode != NULL) { @@ -2579,7 +2579,7 @@ Object* _obj_sight_blocking_at(Object* a1, int tile, int elevation) if (object->elevation == elevation && (object->flags & OBJECT_HIDDEN) == 0 && (object->flags & OBJECT_LIGHT_THRU) == 0 - && object != a1) { + && object != excludeObj) { int objectType = FID_TYPE(object->fid); if (objectType == OBJ_TYPE_SCENERY || objectType == OBJ_TYPE_WALL) { return object; diff --git a/src/object.h b/src/object.h index c3973177..f9ccfb5e 100644 --- a/src/object.h +++ b/src/object.h @@ -71,11 +71,11 @@ Object* objectFindFirstAtLocation(int elevation, int tile); Object* objectFindNextAtLocation(); void objectGetRect(Object* obj, Rect* rect); bool _obj_occupied(int tile_num, int elev); -Object* _obj_blocking_at(Object* a1, int tile_num, int elev); -Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev); -Object* _obj_ai_blocking_at(Object* a1, int tile, int elevation); +Object* _obj_blocking_at(Object* excludeObj, int tile_num, int elev); +Object* _obj_shoot_blocking_at(Object* excludeObj, int tile, int elev); +Object* _obj_ai_blocking_at(Object* excludeObj, int tile, int elevation); int _obj_scroll_blocking_at(int tile_num, int elev); -Object* _obj_sight_blocking_at(Object* a1, int tile_num, int elev); +Object* _obj_sight_blocking_at(Object* excludeObj, int tile_num, int elev); int objectGetDistanceBetween(Object* object1, Object* object2); int objectGetDistanceBetweenTiles(Object* object1, int tile1, Object* object2, int tile2); int objectListCreate(int tile, int elevation, int objectType, Object*** objectsPtr); diff --git a/src/pipboy.cc b/src/pipboy.cc index 5b58020c..45f79db0 100644 --- a/src/pipboy.cc +++ b/src/pipboy.cc @@ -1962,7 +1962,7 @@ static bool pipboyRest(int hours, int minutes, int duration) double v2 = v1 * (1.0 / 1440.0) * 3.5 + 0.25; double v3 = (double)minutes / v1 * v2; if (minutes != 0) { - int gameTime = gameTimeGetTime(); + unsigned int gameTime = gameTimeGetTime(); double v4 = v3 * 20.0; int v5 = 0; @@ -2025,7 +2025,7 @@ static bool pipboyRest(int hours, int minutes, int duration) } if (hours != 0 && !rc) { - int gameTime = gameTimeGetTime(); + unsigned int gameTime = gameTimeGetTime(); double v7 = (v2 - v3) * 20.0; for (int hour = 0; hour < (int)v7; hour++) { @@ -2143,9 +2143,7 @@ static bool pipboyRest(int hours, int minutes, int duration) } } - int gameTime = gameTimeGetTime(); - int nextEventGameTime = queueGetNextEventTime(); - if (gameTime > nextEventGameTime) { + if (gameTimeGetTime() > queueGetNextEventTime()) { if (queueProcessEvents()) { debugPrint("PIPBOY: Returning from Queue trigger...\n"); _proc_bail_flag = 1; diff --git a/src/proto.cc b/src/proto.cc index efb05b03..09200545 100644 --- a/src/proto.cc +++ b/src/proto.cc @@ -24,7 +24,6 @@ namespace fallout { -static int _proto_critter_init(Proto* a1, int a2); static int objectCritterCombatDataRead(CritterCombatData* data, File* stream); static int objectCritterCombatDataWrite(CritterCombatData* data, File* stream); static int _proto_update_gen(Object* obj); @@ -39,8 +38,7 @@ static int _proto_load_pid(int pid, Proto** out_proto); static int _proto_find_free_subnode(int type, Proto** out_ptr); static void _proto_remove_some_list(int type); static void _proto_remove_list(int type); -static int _proto_new_id(int a1); -static int _proto_max_id(int a1); +static int _proto_new_id(int type); // 0x50CF3C static char _aProto_0[] = "proto\\"; @@ -169,7 +167,7 @@ char* _proto_none_str; static char* gBodyTypeNames[BODY_TYPE_COUNT]; // 0x664834 -static char* gItemTypeNames[ITEM_TYPE_COUNT]; +char* gItemTypeNames[ITEM_TYPE_COUNT]; // 0x66484C static char* gDamageTypeNames[DAMAGE_TYPE_COUNT]; @@ -187,8 +185,8 @@ static char** _perk_code_strs; // 0x6648BC static char** _critter_stats_list; -// NOTE: Inlined. -void _proto_make_path(char* path, int pid) +// 0x49E270 +void proto_make_path(char* path, int pid) { strcpy(path, _cd_path_base); strcat(path, _proto_path_base); @@ -211,7 +209,7 @@ int _proto_list_str(int pid, char* proto_path) } char path[COMPAT_MAX_PATH]; - _proto_make_path(path, pid); + proto_make_path(path, pid); strcat(path, "\\"); strcat(path, artGetObjectTypeName(PID_TYPE(pid))); strcat(path, ".lst"); @@ -370,32 +368,143 @@ char* protoGetDescription(int pid) return protoGetMessage(pid, PROTOTYPE_MESSAGE_DESCRIPTION); } +// 0x49EB2C +int proto_item_init(Proto* proto, int a2) +{ + int v1 = a2 & 0xFFFFFF; + + proto->item.pid = -1; + proto->item.messageId = 100 * v1; + proto->item.fid = buildFid(OBJ_TYPE_ITEM, v1 - 1, 0, 0, 0); + if (!artExists(proto->item.fid)) { + proto->item.fid = buildFid(OBJ_TYPE_ITEM, 0, 0, 0, 0); + } + proto->item.lightDistance = 0; + proto->item.lightIntensity = 0; + proto->item.flags = 0xA0000008; + proto->item.extendedFlags = 0xA000; + proto->item.sid = -1; + proto->item.type = ITEM_TYPE_MISC; + proto_item_subdata_init(proto, proto->item.type); + proto->item.material = 1; + proto->item.size = 1; + proto->item.weight = 10; + proto->item.cost = 0; + proto->item.inventoryFid = -1; + proto->item.field_80 = '0'; + + return 0; +} + +// 0x49EBFC +int proto_item_subdata_init(Proto* proto, int type) +{ + int index; + + switch (type) { + case ITEM_TYPE_ARMOR: + proto->item.data.armor.armorClass = 0; + + for (index = 0; index < DAMAGE_TYPE_COUNT; index++) { + proto->item.data.armor.damageResistance[index] = 0; + proto->item.data.armor.damageThreshold[index] = 0; + } + + proto->item.data.armor.perk = -1; + proto->item.data.armor.maleFid = -1; + proto->item.data.armor.femaleFid = -1; + break; + case ITEM_TYPE_CONTAINER: + proto->item.data.container.openFlags = 0; + proto->item.data.container.maxSize = 250; + proto->item.extendedFlags |= 0x800; + break; + case ITEM_TYPE_DRUG: + proto->item.data.drug.stat[0] = STAT_STRENGTH; + proto->item.data.drug.stat[1] = -1; + proto->item.data.drug.stat[2] = -1; + proto->item.data.drug.amount[0] = 0; + proto->item.data.drug.amount[1] = 0; + proto->item.data.drug.amount[2] = 0; + proto->item.data.drug.duration1 = 0; + proto->item.data.drug.amount1[0] = 0; + proto->item.data.drug.amount1[1] = 0; + proto->item.data.drug.amount1[2] = 0; + proto->item.data.drug.duration2 = 0; + proto->item.data.drug.amount2[0] = 0; + proto->item.data.drug.amount2[1] = 0; + proto->item.data.drug.amount2[2] = 0; + proto->item.data.drug.addictionChance = 0; + proto->item.data.drug.withdrawalEffect = 0; + proto->item.data.drug.withdrawalOnset = 0; + proto->item.extendedFlags |= 0x1000; + break; + case ITEM_TYPE_WEAPON: + proto->item.data.weapon.animationCode = 0; + proto->item.data.weapon.minDamage = 0; + proto->item.data.weapon.maxDamage = 0; + proto->item.data.weapon.damageType = 0; + proto->item.data.weapon.maxRange1 = 0; + proto->item.data.weapon.maxRange2 = 0; + proto->item.data.weapon.projectilePid = -1; + proto->item.data.weapon.minStrength = 0; + proto->item.data.weapon.actionPointCost1 = 0; + proto->item.data.weapon.actionPointCost2 = 0; + proto->item.data.weapon.criticalFailureType = 0; + proto->item.data.weapon.perk = -1; + proto->item.data.weapon.rounds = 0; + proto->item.data.weapon.caliber = 0; + proto->item.data.weapon.ammoTypePid = -1; + proto->item.data.weapon.ammoCapacity = 0; + proto->item.data.weapon.soundCode = 0; + break; + case ITEM_TYPE_AMMO: + proto->item.data.ammo.caliber = 0; + proto->item.data.ammo.quantity = 20; + proto->item.data.ammo.armorClassModifier = 0; + proto->item.data.ammo.damageResistanceModifier = 0; + proto->item.data.ammo.damageMultiplier = 1; + proto->item.data.ammo.damageDivisor = 1; + break; + case ITEM_TYPE_MISC: + proto->item.data.misc.powerTypePid = -1; + proto->item.data.misc.powerType = 20; + break; + case ITEM_TYPE_KEY: + proto->item.data.key.keyCode = -1; + proto->item.extendedFlags |= 0x1000; + break; + } + + return 0; +} + // 0x49EDB4 -static int _proto_critter_init(Proto* a1, int a2) +int proto_critter_init(Proto* proto, int pid) { if (!_protos_been_initialized) { return -1; } - int v1 = a2 & 0xFFFFFF; + int num = pid & 0xFFFFFF; - a1->pid = -1; - a1->messageId = 100 * v1; - a1->fid = buildFid(OBJ_TYPE_CRITTER, v1 - 1, 0, 0, 0); - a1->critter.lightDistance = 0; - a1->critter.lightIntensity = 0; - a1->critter.flags = 0x20000000; - a1->critter.extendedFlags = 0x6000; - a1->critter.sid = -1; - a1->critter.data.flags = 0; - a1->critter.data.bodyType = 0; - a1->critter.headFid = -1; - a1->critter.aiPacket = 1; - if (!artExists(a1->fid)) { - a1->fid = buildFid(OBJ_TYPE_CRITTER, 0, 0, 0, 0); - } - - CritterProtoData* data = &(a1->critter.data); + proto->pid = -1; + proto->messageId = 100 * num; + proto->fid = buildFid(OBJ_TYPE_CRITTER, num - 1, 0, 0, 0); + proto->critter.lightDistance = 0; + proto->critter.lightIntensity = 0; + proto->critter.flags = 0x20000000; + proto->critter.extendedFlags = 0x6000; + proto->critter.sid = -1; + proto->critter.data.flags = 0; + proto->critter.data.bodyType = 0; + proto->critter.headFid = -1; + proto->critter.aiPacket = 1; + if (!artExists(proto->fid)) { + proto->fid = buildFid(OBJ_TYPE_CRITTER, 0, 0, 0, 0); + } + + CritterProtoData* data = &(proto->critter.data); data->experience = 60; data->killType = 0; data->damageType = 0; @@ -828,6 +937,165 @@ int _proto_dude_init(const char* path) return 0; } +// 0x49FBBC +int proto_scenery_init(Proto* proto, int pid) +{ + int num = pid & 0xFFFFFF; + + proto->scenery.pid = -1; + proto->scenery.messageId = 100 * num; + proto->scenery.fid = buildFid(OBJ_TYPE_SCENERY, num - 1, 0, 0, 0); + if (!artExists(proto->scenery.fid)) { + proto->scenery.fid = buildFid(OBJ_TYPE_SCENERY, 0, 0, 0, 0); + } + proto->scenery.lightDistance = 0; + proto->scenery.lightIntensity = 0; + proto->scenery.flags = 0; + proto->scenery.extendedFlags = 0x2000; + proto->scenery.sid = -1; + proto->scenery.type = SCENERY_TYPE_GENERIC; + proto_scenery_subdata_init(proto, proto->scenery.type); + proto->scenery.field_2C = -1; + proto->scenery.field_34 = '0'; + + return 0; +} + +// 0x49FC74 +int proto_scenery_subdata_init(Proto* proto, int type) +{ + switch (type) { + case SCENERY_TYPE_DOOR: + proto->scenery.data.door.openFlags = 0; + proto->scenery.extendedFlags |= 0x800; + break; + case SCENERY_TYPE_STAIRS: + proto->scenery.data.stairs.field_0 = -1; + proto->scenery.data.stairs.field_4 = -1; + proto->scenery.extendedFlags |= 0x800; + break; + case SCENERY_TYPE_ELEVATOR: + proto->scenery.data.elevator.type = -1; + proto->scenery.data.elevator.level = -1; + proto->scenery.extendedFlags |= 0x800; + break; + case SCENERY_TYPE_LADDER_UP: + proto->scenery.data.ladder.field_0 = -1; + proto->scenery.extendedFlags |= 0x800; + break; + case SCENERY_TYPE_LADDER_DOWN: + proto->scenery.data.ladder.field_0 = -1; + proto->scenery.extendedFlags |= 0x800; + break; + } + + return 0; +} + +// 0x49FCFC +int proto_wall_init(Proto* proto, int pid) +{ + int num = pid & 0xFFFFFF; + + proto->wall.pid = -1; + proto->wall.messageId = 100 * num; + proto->wall.fid = buildFid(OBJ_TYPE_WALL, num - 1, 0, 0, 0); + if (!artExists(proto->wall.fid)) { + proto->wall.fid = buildFid(OBJ_TYPE_WALL, 0, 0, 0, 0); + } + proto->wall.lightDistance = 0; + proto->wall.lightIntensity = 0; + proto->wall.flags = 0; + proto->wall.extendedFlags = 0x2000; + proto->wall.sid = -1; + proto->wall.material = 1; + + return 0; +} + +// 0x49FD84 +int proto_tile_init(Proto* proto, int pid) +{ + int num = pid & 0xFFFFFF; + + proto->tile.pid = -1; + proto->tile.messageId = 100 * num; + proto->tile.fid = buildFid(OBJ_TYPE_TILE, num - 1, 0, 0, 0); + if (!artExists(proto->tile.fid)) { + proto->tile.fid = buildFid(OBJ_TYPE_TILE, 0, 0, 0, 0); + } + proto->tile.flags = 0; + proto->tile.extendedFlags = 0x2000; + proto->tile.sid = -1; + proto->tile.material = 1; + + return 0; +} + +// 0x49FDFC +int proto_misc_init(Proto* proto, int pid) +{ + int num = pid & 0xFFFFFF; + + proto->misc.pid = -1; + proto->misc.messageId = 100 * num; + proto->misc.fid = buildFid(OBJ_TYPE_MISC, num - 1, 0, 0, 0); + if (!artExists(proto->misc.fid)) { + proto->misc.fid = buildFid(OBJ_TYPE_MISC, 0, 0, 0, 0); + } + proto->misc.lightDistance = 0; + proto->misc.lightIntensity = 0; + proto->misc.flags = 0; + proto->misc.extendedFlags = 0; + + return 0; +} + +// 0x49FE74 +int proto_copy_proto(int srcPid, int dstPid) +{ + int srcType; + int dstType; + Proto* src; + Proto* dst; + + srcType = PID_TYPE(srcPid); + dstType = PID_TYPE(dstPid); + if (srcType != dstType) { + return -1; + } + + if (protoGetProto(srcPid, &src) == -1) { + return -1; + } + + if (protoGetProto(dstPid, &dst) == -1) { + return -1; + } + + memcpy(dst, src, _proto_sizes[srcType]); + dst->pid = dstPid; + + return 0; +} + +// 0x49FEDC +bool proto_is_subtype(Proto* proto, int subtype) +{ + if (subtype == -1) { + return true; + } + + switch (PID_TYPE(proto->pid)) { + case OBJ_TYPE_ITEM: + return proto->item.type == subtype; + case OBJ_TYPE_SCENERY: + return proto->scenery.type == subtype; + } + + return false; +} + // proto_data_member // 0x49FFD8 int protoGetDataMember(int pid, int member, ProtoDataMemberValue* value) @@ -1083,7 +1351,7 @@ int protoInit() compat_mkdir(path); // TODO: Get rid of cast. - _proto_critter_init((Proto*)&gDudeProto, 0x1000000); + proto_critter_init((Proto*)&gDudeProto, 0x1000000); gDudeProto.pid = 0x1000000; gDudeProto.fid = buildFid(OBJ_TYPE_CRITTER, 1, 0, 0, 0); @@ -1202,7 +1470,7 @@ void protoReset() int i; // TODO: Get rid of cast. - _proto_critter_init((Proto*)&gDudeProto, 0x1000000); + proto_critter_init((Proto*)&gDudeProto, 0x1000000); gDudeProto.pid = 0x1000000; gDudeProto.fid = buildFid(OBJ_TYPE_CRITTER, 1, 0, 0, 0); @@ -1251,7 +1519,7 @@ static int _proto_header_load() ptr->max_entries_num = 1; char path[COMPAT_MAX_PATH]; - _proto_make_path(path, index << 24); + proto_make_path(path, index << 24); strcat(path, "\\"); strcat(path, artGetObjectTypeName(index)); strcat(path, ".lst"); @@ -1661,7 +1929,7 @@ int _proto_save_pid(int pid) } char path[260]; - _proto_make_path(path, pid); + proto_make_path(path, pid); strcat(path, "\\"); _proto_list_str(pid, path + strlen(path)); @@ -1682,7 +1950,7 @@ int _proto_save_pid(int pid) static int _proto_load_pid(int pid, Proto** protoPtr) { char path[COMPAT_MAX_PATH]; - _proto_make_path(path, pid); + proto_make_path(path, pid); strcat(path, "\\"); if (_proto_list_str(pid, path + strlen(path)) == -1) { @@ -1761,6 +2029,48 @@ static int _proto_find_free_subnode(int type, Proto** protoPtr) return 0; } +// 0x4A1E90 +int proto_new(int* pid, int type) +{ + Proto* proto; + + if (_proto_find_free_subnode(type, &proto) == -1) { + return -1; + } + + *pid = _proto_new_id(type) | (type << 24); + switch (type) { + case OBJ_TYPE_ITEM: + proto_item_init(proto, *pid); + proto->item.pid = *pid; + break; + case OBJ_TYPE_CRITTER: + proto_critter_init(proto, *pid); + proto->critter.pid = *pid; + break; + case OBJ_TYPE_SCENERY: + proto_scenery_init(proto, *pid); + proto->scenery.pid = *pid; + break; + case OBJ_TYPE_WALL: + proto_wall_init(proto, *pid); + proto->wall.pid = *pid; + break; + case OBJ_TYPE_TILE: + proto_tile_init(proto, *pid); + proto->tile.pid = *pid; + break; + case OBJ_TYPE_MISC: + proto_misc_init(proto, *pid); + proto->misc.pid = *pid; + break; + default: + return -1; + } + + return 0; +} + // Evict top most proto cache block. // // 0x4A2040 @@ -1850,18 +2160,18 @@ int protoGetProto(int pid, Proto** protoPtr) } // 0x4A21DC -static int _proto_new_id(int a1) +static int _proto_new_id(int type) { - int result = _protoLists[a1].max_entries_num; - _protoLists[a1].max_entries_num = result + 1; + int result = _protoLists[type].max_entries_num; + _protoLists[type].max_entries_num = result + 1; return result; } // 0x4A2214 -static int _proto_max_id(int a1) +int proto_max_id(int type) { - return _protoLists[a1].max_entries_num; + return _protoLists[type].max_entries_num; } // 0x4A22C0 diff --git a/src/proto.h b/src/proto.h index d6f13e6d..40cded97 100644 --- a/src/proto.h +++ b/src/proto.h @@ -101,8 +101,9 @@ extern char _cd_path_base[COMPAT_MAX_PATH]; extern MessageList gProtoMessageList; extern char* _proto_none_str; +extern char* gItemTypeNames[ITEM_TYPE_COUNT]; -void _proto_make_path(char* path, int pid); +void proto_make_path(char* path, int pid); int _proto_list_str(int pid, char* proto_path); size_t proto_size(int type); bool _proto_action_can_use(int pid); @@ -112,20 +113,32 @@ int _proto_action_can_pickup(int pid); char* protoGetMessage(int pid, int message); char* protoGetName(int pid); char* protoGetDescription(int pid); +int proto_item_init(Proto* proto, int pid); +int proto_item_subdata_init(Proto* proto, int type); +int proto_critter_init(Proto* proto, int pid); void objectDataReset(Object* obj); int objectDataRead(Object* obj, File* stream); int objectDataWrite(Object* obj, File* stream); int _proto_update_init(Object* obj); int _proto_dude_update_gender(); int _proto_dude_init(const char* path); +int proto_scenery_init(Proto* proto, int pid); +int proto_scenery_subdata_init(Proto* proto, int type); +int proto_wall_init(Proto* proto, int pid); +int proto_tile_init(Proto* proto, int pid); +int proto_misc_init(Proto* proto, int pid); +int proto_copy_proto(int srcPid, int dstPid); +bool proto_is_subtype(Proto* proto, int subtype); int protoGetDataMember(int pid, int member, ProtoDataMemberValue* value); int protoInit(); void protoReset(); void protoExit(); int _proto_save_pid(int pid); +int proto_new(int* pid, int type); void _proto_remove_all(); -int protoGetProto(int pid, Proto** out_proto); +int protoGetProto(int pid, Proto** protoPtr); int _ResetPlayer(); +int proto_max_id(int type); static bool isExitGridPid(int pid) { diff --git a/src/queue.cc b/src/queue.cc index 96d1b62a..42d6bf99 100644 --- a/src/queue.cc +++ b/src/queue.cc @@ -18,8 +18,7 @@ namespace fallout { typedef struct QueueListNode { - // TODO: Make unsigned. - int time; + unsigned int time; int type; Object* owner; void* data; @@ -102,7 +101,7 @@ int queueLoad(File* stream) break; } - if (fileReadInt32(stream, &(queueListNode->time)) == -1) { + if (fileReadUInt32(stream, &(queueListNode->time)) == -1) { internal_free(queueListNode); rc = -1; break; @@ -169,27 +168,20 @@ int queueLoad(File* stream) } } - if (oldListHead != NULL) { - QueueListNode** v13 = &gQueueListHead; - QueueListNode* v15; - do { - while (true) { - QueueListNode* v14 = *v13; - if (v14 == NULL) { - break; - } + while (oldListHead != NULL) { + QueueListNode** queueListNodePtr = &gQueueListHead; + while (*queueListNodePtr != NULL) { + if ((*queueListNodePtr)->time > oldListHead->time) { + break; + } - if (v14->time > oldListHead->time) { - break; - } + queueListNodePtr = &((*queueListNodePtr)->next); + } - v13 = &(v14->next); - } - v15 = oldListHead->next; - oldListHead->next = *v13; - *v13 = oldListHead; - oldListHead = v15; - } while (v15 != NULL); + QueueListNode* next = oldListHead->next; + oldListHead->next = *queueListNodePtr; + *queueListNodePtr = oldListHead; + oldListHead = next; } return rc; @@ -217,7 +209,7 @@ int queueSave(File* stream) Object* object = queueListNode->owner; int objectId = object != NULL ? object->id : -2; - if (fileWriteInt32(stream, queueListNode->time) == -1) { + if (fileWriteUInt32(stream, queueListNode->time) == -1) { return -1; } @@ -250,9 +242,7 @@ int queueAddEvent(int delay, Object* obj, void* data, int eventType) return -1; } - int v1 = gameTimeGetTime(); - int v2 = v1 + delay; - newQueueListNode->time = v2; + newQueueListNode->time = gameTimeGetTime() + delay; newQueueListNode->type = eventType; newQueueListNode->owner = obj; newQueueListNode->data = data; @@ -261,22 +251,18 @@ int queueAddEvent(int delay, Object* obj, void* data, int eventType) obj->flags |= OBJECT_QUEUED; } - QueueListNode** v3 = &gQueueListHead; + QueueListNode** queueListNodePtr = &gQueueListHead; - if (gQueueListHead != NULL) { - QueueListNode* v4; + while (*queueListNodePtr != NULL) { + if (newQueueListNode->time < (*queueListNodePtr)->time) { + break; + } - do { - v4 = *v3; - if (v2 < v4->time) { - break; - } - v3 = &(v4->next); - } while (v4->next != NULL); + queueListNodePtr = &((*queueListNodePtr)->next); } - newQueueListNode->next = *v3; - *v3 = newQueueListNode; + newQueueListNode->next = *queueListNodePtr; + *queueListNodePtr = newQueueListNode; return 0; } @@ -357,19 +343,20 @@ bool queueHasEvent(Object* owner, int eventType) // 0x4A26D0 int queueProcessEvents() { - int time = gameTimeGetTime(); - int v1 = 0; + unsigned int time = gameTimeGetTime(); + // TODO: this is 0 or 1, but in some cases -1. Probably needs to be bool. + int stopProcess = 0; while (gQueueListHead != NULL) { QueueListNode* queueListNode = gQueueListHead; - if (time < queueListNode->time || v1 != 0) { + if (time < queueListNode->time || stopProcess != 0) { break; } gQueueListHead = queueListNode->next; EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]); - v1 = eventTypeDescription->handlerProc(queueListNode->owner, queueListNode->data); + stopProcess = eventTypeDescription->handlerProc(queueListNode->owner, queueListNode->data); if (eventTypeDescription->freeProc != NULL) { eventTypeDescription->freeProc(queueListNode->data); @@ -378,7 +365,7 @@ int queueProcessEvents() internal_free(queueListNode); } - return v1; + return stopProcess; } // 0x4A2748 @@ -437,10 +424,8 @@ void _queue_clear_type(int eventType, QueueEventHandler* fn) } } -// TODO: Make unsigned. -// // 0x4A2808 -int queueGetNextEventTime() +unsigned int queueGetNextEventTime() { if (gQueueListHead == NULL) { return 0; diff --git a/src/queue.h b/src/queue.h index d77179e6..78b7197b 100644 --- a/src/queue.h +++ b/src/queue.h @@ -66,7 +66,7 @@ bool queueHasEvent(Object* owner, int eventType); int queueProcessEvents(); void queueClear(); void _queue_clear_type(int eventType, QueueEventHandler* fn); -int queueGetNextEventTime(); +unsigned int queueGetNextEventTime(); void _queue_leaving_map(); bool queueIsEmpty(); void* queueFindFirstEvent(Object* owner, int eventType); diff --git a/src/random.cc b/src/random.cc index 82f47b37..321e3898 100644 --- a/src/random.cc +++ b/src/random.cc @@ -100,7 +100,7 @@ int randomRoll(int difficulty, int criticalSuccessModifier, int* howMuchPtr) // 0x4A3030 static int randomTranslateRoll(int delta, int criticalSuccessModifier) { - int gameTime = gameTimeGetTime(); + unsigned int gameTime = gameTimeGetTime(); // SFALL: Remove criticals time limits. bool criticalsTimeLimitsRemoved = false; diff --git a/src/scripts.cc b/src/scripts.cc index f0d2c60f..1485fff4 100644 --- a/src/scripts.cc +++ b/src/scripts.cc @@ -127,7 +127,7 @@ static int _script_engine_game_mode = 0; // Game time in ticks (1/10 second). // // 0x51C720 -static int gGameTime = 302400; +static unsigned int gGameTime = 302400; // 0x51C724 static const int gGameTimeDaysPerMonth[12] = { @@ -277,12 +277,10 @@ static int gMovieTimerArtimer2; static int gMovieTimerArtimer3; static int gMovieTimerArtimer4; -// TODO: Make unsigned. -// // Returns game time in ticks (1/10 second). // // 0x4A3330 -int gameTimeGetTime() +unsigned int gameTimeGetTime() { return gGameTime; } @@ -351,7 +349,7 @@ char* gameTimeGetTimeString() // TODO: Make unsigned. // // 0x4A347C -void gameTimeSetTime(int time) +void gameTimeSetTime(unsigned int time) { if (time == 0) { time = 1; @@ -407,7 +405,7 @@ int gameTimeScheduleUpdateEvent() int gameTimeEventProcess(Object* obj, void* data) { int movie_index; - int v4; + int stopProcess; movie_index = -1; @@ -423,17 +421,17 @@ int gameTimeEventProcess(Object* obj, void* data) _scriptsCheckGameEvents(&movie_index, -1); } - v4 = _critter_check_rads(gDude); + stopProcess = _critter_check_rads(gDude); _queue_clear_type(4, 0); gameTimeScheduleUpdateEvent(); if (movie_index != -1) { - v4 = 1; + stopProcess = 1; } - return v4; + return stopProcess; } // 0x4A3690 @@ -775,9 +773,7 @@ static void _script_chk_timed_events() if (v1) { while (!queueIsEmpty()) { - int time = gameTimeGetTime(); - int v2 = queueGetNextEventTime(); - if (time < v2) { + if (gameTimeGetTime() < queueGetNextEventTime()) { break; } @@ -917,8 +913,8 @@ int scriptsHandleRequests() } } - if ((gScriptsRequests & SCRIPT_REQUEST_0x02) != 0) { - gScriptsRequests &= ~SCRIPT_REQUEST_0x02; + if ((gScriptsRequests & SCRIPT_REQUEST_TOWN_MAP) != 0) { + gScriptsRequests &= ~SCRIPT_REQUEST_TOWN_MAP; wmTownMap(); } @@ -1132,6 +1128,16 @@ void _scripts_request_combat_locked(STRUCT_664980* a1) gScriptsRequests |= (SCRIPT_REQUEST_0x0400 | SCRIPT_REQUEST_COMBAT); } +// 0x4A461C +void scripts_request_townmap() +{ + if (isInCombat()) { + _game_user_wants_to_quit = 1; + } + + gScriptsRequests |= SCRIPT_REQUEST_TOWN_MAP; +} + // request_world_map() // 0x4A4644 void scriptsRequestWorldMap() @@ -2208,6 +2214,8 @@ int scriptAdd(int* sidPtr, int scriptType) scr->procs[index] = SCRIPT_PROC_NO_PROC; } + scr->overriddenSelf = nullptr; + scriptListExtent->length++; return 0; diff --git a/src/scripts.h b/src/scripts.h index 0c4b5080..782591cf 100644 --- a/src/scripts.h +++ b/src/scripts.h @@ -25,7 +25,7 @@ namespace fallout { typedef enum ScriptRequests { SCRIPT_REQUEST_COMBAT = 0x01, - SCRIPT_REQUEST_0x02 = 0x02, + SCRIPT_REQUEST_TOWN_MAP = 0x02, SCRIPT_REQUEST_WORLD_MAP = 0x04, SCRIPT_REQUEST_ELEVATOR = 0x08, SCRIPT_REQUEST_EXPLOSION = 0x10, @@ -143,17 +143,19 @@ typedef struct Script { int field_D4; int field_D8; int field_DC; + + Object* overriddenSelf; } Script; extern const char* gScriptProcNames[SCRIPT_PROC_COUNT]; -int gameTimeGetTime(); +unsigned int gameTimeGetTime(); void gameTimeGetDate(int* monthPtr, int* dayPtr, int* yearPtr); int gameTimeGetHour(); char* gameTimeGetTimeString(); void gameTimeAddTicks(int a1); void gameTimeAddSeconds(int a1); -void gameTimeSetTime(int time); +void gameTimeSetTime(unsigned int time); int gameTimeScheduleUpdateEvent(); int gameTimeEventProcess(Object* obj, void* data); int _scriptsCheckGameEvents(int* moviePtr, int window); @@ -175,6 +177,7 @@ int scriptsHandleRequests(); int _scripts_check_state_in_combat(); int scriptsRequestCombat(STRUCT_664980* a1); void _scripts_request_combat_locked(STRUCT_664980* ptr); +void scripts_request_townmap(); void scriptsRequestWorldMap(); int scriptsRequestElevator(Object* a1, int a2); int scriptsRequestExplosion(int tile, int elevation, int minDamage, int maxDamage); diff --git a/src/sfall_arrays.cc b/src/sfall_arrays.cc index e074b4c2..c0d064bc 100644 --- a/src/sfall_arrays.cc +++ b/src/sfall_arrays.cc @@ -11,6 +11,7 @@ #include #include "interpreter.h" +#include "sfall_lists.h" namespace fallout { @@ -647,6 +648,25 @@ ProgramValue ScanArray(ArrayId arrayId, const ProgramValue& val, Program* progra return arr->ScanArray(val, program); } +ArrayId ListAsArray(int type) +{ + std::vector objects; + sfall_lists_fill(type, objects); + + int count = static_cast(objects.size()); + ArrayId arrayId = CreateTempArray(count, 0); + auto arr = get_array_by_id(arrayId); + + // A little bit ugly and likely inefficient. + for (int index = 0; index < count; index++) { + arr->SetArray(ProgramValue { index }, + ArrayElement { ProgramValue { objects[index] }, nullptr }, + false); + } + + return arrayId; +} + ArrayId StringSplit(const char* str, const char* split) { size_t splitLen = strlen(split); diff --git a/src/sfall_arrays.h b/src/sfall_arrays.h index bd96ad54..d976afbd 100644 --- a/src/sfall_arrays.h +++ b/src/sfall_arrays.h @@ -26,6 +26,7 @@ void ResizeArray(ArrayId arrayId, int newLen); void DeleteAllTempArrays(); int StackArray(const ProgramValue& key, const ProgramValue& val, Program* program); ProgramValue ScanArray(ArrayId arrayId, const ProgramValue& val, Program* program); +ArrayId ListAsArray(int type); ArrayId StringSplit(const char* str, const char* split); diff --git a/src/sfall_config.cc b/src/sfall_config.cc index b85a83b9..353bb379 100644 --- a/src/sfall_config.cc +++ b/src/sfall_config.cc @@ -57,6 +57,7 @@ bool sfallConfigInit(int argc, char** argv) configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER2, 180); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER3, 270); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER4, 360); + configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_AUTO_QUICK_SAVE, 0); configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_INI_CONFIG_FOLDER, ""); configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_GLOBAL_SCRIPT_PATHS, ""); diff --git a/src/sfall_config.h b/src/sfall_config.h index 1f786ce0..da2c2985 100644 --- a/src/sfall_config.h +++ b/src/sfall_config.h @@ -71,6 +71,7 @@ namespace fallout { #define SFALL_CONFIG_NUMBERS_IS_DIALOG_KEY "NumbersInDialogue" #define SFALL_CONFIG_INI_CONFIG_FOLDER "IniConfigFolder" #define SFALL_CONFIG_GLOBAL_SCRIPT_PATHS "GlobalScriptPaths" +#define SFALL_CONFIG_AUTO_QUICK_SAVE "AutoQuickSave" #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MULTIPLIER 1 #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR 3 diff --git a/src/sfall_ini.cc b/src/sfall_ini.cc index 91af85c1..f4a2c913 100644 --- a/src/sfall_ini.cc +++ b/src/sfall_ini.cc @@ -145,4 +145,53 @@ bool sfall_ini_get_string(const char* triplet, char* value, size_t size) return true; } +bool sfall_ini_set_int(const char* triplet, int value) +{ + char stringValue[20]; + compat_itoa(value, stringValue, 10); + + return sfall_ini_set_string(triplet, stringValue); +} + +bool sfall_ini_set_string(const char* triplet, const char* value) +{ + char fileName[kFileNameMaxSize]; + char section[kSectionMaxSize]; + + const char* key = parse_ini_triplet(triplet, fileName, section); + if (key == nullptr) { + return false; + } + + Config config; + if (!configInit(&config)) { + return false; + } + + char path[COMPAT_MAX_PATH]; + bool loaded = false; + + if (basePath[0] != '\0' && !is_system_file_name(fileName)) { + // Attempt to load requested file in base directory. + snprintf(path, sizeof(path), "%s\\%s", basePath, fileName); + loaded = configRead(&config, path, false); + } + + if (!loaded) { + // There was no base path set, requested file is a system config, or + // non-system config file was not found the base path - attempt to load + // from current working directory. + strcpy(path, fileName); + loaded = configRead(&config, path, false); + } + + configSetString(&config, section, key, value); + + bool saved = configWrite(&config, path, false); + + configFree(&config); + + return saved; +} + } // namespace fallout diff --git a/src/sfall_ini.h b/src/sfall_ini.h index ac4cdd86..092b40cc 100644 --- a/src/sfall_ini.h +++ b/src/sfall_ini.h @@ -14,6 +14,12 @@ bool sfall_ini_get_int(const char* triplet, int* value); /// Reads string key identified by "fileName|section|key" triplet into `value`. bool sfall_ini_get_string(const char* triplet, char* value, size_t size); +/// Writes integer key identified by "fileName|section|key" triplet. +bool sfall_ini_set_int(const char* triplet, int value); + +/// Writes string key identified by "fileName|section|key" triplet. +bool sfall_ini_set_string(const char* triplet, const char* value); + } // namespace fallout #endif /* FALLOUT_SFALL_INI_H_ */ diff --git a/src/sfall_lists.cc b/src/sfall_lists.cc index a582ae27..55150b16 100644 --- a/src/sfall_lists.cc +++ b/src/sfall_lists.cc @@ -1,7 +1,6 @@ #include "sfall_lists.h" #include -#include #include "object.h" #include "scripts.h" @@ -66,9 +65,42 @@ int sfallListsCreate(int listType) int listId = _state->nextListId++; List& list = _state->lists[listId]; - if (listType == LIST_TILES) { + sfall_lists_fill(listType, list.objects); + + return listId; +} + +Object* sfallListsGetNext(int listId) +{ + auto it = _state->lists.find(listId); + if (it != _state->lists.end()) { + List& list = it->second; + if (list.pos < list.objects.size()) { + return list.objects[list.pos++]; + } + } + + return nullptr; +} + +void sfallListsDestroy(int listId) +{ + auto it = _state->lists.find(listId); + if (it != _state->lists.end()) { + _state->lists.erase(it); + } +} + +void sfall_lists_fill(int type, std::vector& objects) +{ + if (type == LIST_TILES) { // For unknown reason this list type is not implemented in Sfall. - } else if (listType == LIST_SPATIAL) { + return; + } + + objects.reserve(100); + + if (type == LIST_SPATIAL) { for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { Script* script = scriptGetFirstSpatialScript(elevation); while (script != nullptr) { @@ -76,7 +108,7 @@ int sfallListsCreate(int listType) if (obj == nullptr) { obj = scriptGetSelf(script->program); } - list.objects.push_back(obj); + objects.push_back(obj); script = scriptGetNextSpatialScript(); } } @@ -89,46 +121,24 @@ int sfallListsCreate(int listType) // // As a small optimization |LIST_ALL| is handled separately since there // is no need to check object type. - if (listType == LIST_ALL) { + if (type == LIST_ALL) { Object* obj = objectFindFirst(); while (obj != nullptr) { - list.objects.push_back(obj); + objects.push_back(obj); obj = objectFindNext(); } } else { Object* obj = objectFindFirst(); while (obj != nullptr) { int objectType = PID_TYPE(obj->pid); - if (objectType < kObjectTypeToListTypeSize && kObjectTypeToListType[objectType] == listType) { - list.objects.push_back(obj); + if (objectType < kObjectTypeToListTypeSize + && kObjectTypeToListType[objectType] == type) { + objects.push_back(obj); } obj = objectFindNext(); } } } - - return listId; -} - -Object* sfallListsGetNext(int listId) -{ - auto it = _state->lists.find(listId); - if (it != _state->lists.end()) { - List& list = it->second; - if (list.pos < list.objects.size()) { - return list.objects[list.pos++]; - } - } - - return nullptr; -} - -void sfallListsDestroy(int listId) -{ - auto it = _state->lists.find(listId); - if (it != _state->lists.end()) { - _state->lists.erase(it); - } } } // namespace fallout diff --git a/src/sfall_lists.h b/src/sfall_lists.h index cbcd2391..67f3455a 100644 --- a/src/sfall_lists.h +++ b/src/sfall_lists.h @@ -1,6 +1,8 @@ #ifndef FALLOUT_SFALL_LISTS_H_ #define FALLOUT_SFALL_LISTS_H_ +#include + #include "obj_types.h" namespace fallout { @@ -22,6 +24,7 @@ void sfallListsExit(); int sfallListsCreate(int listType); Object* sfallListsGetNext(int listId); void sfallListsDestroy(int listId); +void sfall_lists_fill(int type, std::vector& objects); } // namespace fallout diff --git a/src/sfall_metarules.cc b/src/sfall_metarules.cc new file mode 100644 index 00000000..8c3e1101 --- /dev/null +++ b/src/sfall_metarules.cc @@ -0,0 +1,291 @@ +#include "sfall_metarules.h" + +#include + +#include "combat.h" +#include "debug.h" +#include "game.h" +#include "game_dialog.h" +#include "game_mouse.h" +#include "interface.h" +#include "inventory.h" +#include "object.h" +#include "sfall_ini.h" +#include "text_font.h" +#include "tile.h" +#include "window.h" +#include "worldmap.h" + +namespace fallout { + +typedef void(MetaruleHandler)(Program* program, int args); + +// Simplified cousin of `SfallMetarule` from Sfall. +typedef struct MetaruleInfo { + const char* name; + MetaruleHandler* handler; + int minArgs; + int maxArgs; +} MetaruleInfo; + +static void mf_car_gas_amount(Program* program, int args); +static void mf_combat_data(Program* program, int args); +static void mf_critter_inven_obj2(Program* program, int args); +static void mf_dialog_obj(Program* program, int args); +static void mf_get_cursor_mode(Program* program, int args); +static void mf_get_flags(Program* program, int args); +static void mf_get_object_data(Program* program, int args); +static void mf_get_text_width(Program* program, int args); +static void mf_intface_redraw(Program* program, int args); +static void mf_loot_obj(Program* program, int args); +static void mf_metarule_exist(Program* program, int args); +static void mf_outlined_object(Program* program, int args); +static void mf_set_cursor_mode(Program* program, int args); +static void mf_set_flags(Program* program, int args); +static void mf_set_ini_setting(Program* program, int args); +static void mf_set_outline(Program* program, int args); +static void mf_show_window(Program* program, int args); +static void mf_tile_refresh_display(Program* program, int args); + +constexpr MetaruleInfo kMetarules[] = { + { "car_gas_amount", mf_car_gas_amount, 0, 0 }, + { "combat_data", mf_combat_data, 0, 0 }, + { "critter_inven_obj2", mf_critter_inven_obj2, 2, 2 }, + { "dialog_obj", mf_dialog_obj, 0, 0 }, + { "get_cursor_mode", mf_get_cursor_mode, 0, 0 }, + { "get_flags", mf_get_flags, 1, 1 }, + { "get_object_data", mf_get_object_data, 2, 2 }, + { "get_text_width", mf_get_text_width, 1, 1 }, + { "intface_redraw", mf_intface_redraw, 0, 1 }, + { "loot_obj", mf_loot_obj, 0, 0 }, + { "metarule_exist", mf_metarule_exist, 1, 1 }, + { "outlined_object", mf_outlined_object, 0, 0 }, + { "set_cursor_mode", mf_set_cursor_mode, 1, 1 }, + { "set_flags", mf_set_flags, 2, 2 }, + { "set_ini_setting", mf_set_ini_setting, 2, 2 }, + { "set_outline", mf_set_outline, 2, 2 }, + { "show_window", mf_show_window, 0, 1 }, + { "tile_refresh_display", mf_tile_refresh_display, 0, 0 }, +}; + +constexpr int kMetarulesMax = sizeof(kMetarules) / sizeof(kMetarules[0]); + +void mf_car_gas_amount(Program* program, int args) +{ + programStackPushInteger(program, wmCarGasAmount()); +} + +void mf_combat_data(Program* program, int args) +{ + if (isInCombat()) { + programStackPushPointer(program, combat_get_data()); + } else { + programStackPushPointer(program, nullptr); + } +} + +void mf_critter_inven_obj2(Program* program, int args) +{ + int slot = programStackPopInteger(program); + Object* obj = static_cast(programStackPopPointer(program)); + + switch (slot) { + case 0: + programStackPushPointer(program, critterGetArmor(obj)); + break; + case 1: + programStackPushPointer(program, critterGetItem2(obj)); + break; + case 2: + programStackPushPointer(program, critterGetItem1(obj)); + break; + case -2: + programStackPushInteger(program, obj->data.inventory.length); + break; + default: + programFatalError("mf_critter_inven_obj2: invalid type"); + } +} + +void mf_dialog_obj(Program* program, int args) +{ + if (GameMode::isInGameMode(GameMode::kDialog)) { + programStackPushPointer(program, gGameDialogSpeaker); + } else { + programStackPushPointer(program, nullptr); + } +} + +void mf_get_cursor_mode(Program* program, int args) +{ + programStackPushInteger(program, gameMouseGetMode()); +} + +void mf_get_flags(Program* program, int args) +{ + Object* object = static_cast(programStackPopPointer(program)); + programStackPushInteger(program, object->flags); +} + +void mf_get_object_data(Program* program, int args) +{ + size_t offset = static_cast(programStackPopInteger(program)); + void* ptr = programStackPopPointer(program); + + if (offset % 4 != 0) { + programFatalError("mf_get_object_data: bad offset %d", offset); + } + + int value = *reinterpret_cast(reinterpret_cast(ptr) + offset); + programStackPushInteger(program, value); +} + +void mf_get_text_width(Program* program, int args) +{ + const char* string = programStackPopString(program); + programStackPushInteger(program, fontGetStringWidth(string)); +} + +void mf_intface_redraw(Program* program, int args) +{ + if (args == 0) { + interfaceBarRefresh(); + } else { + // TODO: Incomplete. + programFatalError("mf_intface_redraw: not implemented"); + } + + programStackPushInteger(program, -1); +} + +void mf_loot_obj(Program* program, int args) +{ + if (GameMode::isInGameMode(GameMode::kInventory)) { + programStackPushPointer(program, inven_get_current_target_obj()); + } else { + programStackPushPointer(program, nullptr); + } +} + +void mf_metarule_exist(Program* program, int args) +{ + const char* metarule = programStackPopString(program); + + for (int index = 0; index < kMetarulesMax; index++) { + if (strcmp(kMetarules[index].name, metarule) == 0) { + programStackPushInteger(program, 1); + return; + } + } + + programStackPushInteger(program, 0); +} + +void mf_outlined_object(Program* program, int args) +{ + programStackPushPointer(program, gmouse_get_outlined_object()); +} + +void mf_set_cursor_mode(Program* program, int args) +{ + int mode = programStackPopInteger(program); + gameMouseSetMode(mode); + programStackPushInteger(program, -1); +} + +void mf_set_flags(Program* program, int args) +{ + int flags = programStackPopInteger(program); + Object* object = static_cast(programStackPopPointer(program)); + + object->flags = flags; + + programStackPushInteger(program, -1); +} + +void mf_set_ini_setting(Program* program, int args) +{ + ProgramValue value = programStackPopValue(program); + const char* triplet = programStackPopString(program); + + if (value.isString()) { + const char* stringValue = programGetString(program, value.opcode, value.integerValue); + if (!sfall_ini_set_string(triplet, stringValue)) { + debugPrint("set_ini_setting: unable to write '%s' to '%s'", + stringValue, + triplet); + } + } else { + int integerValue = value.asInt(); + if (!sfall_ini_set_int(triplet, integerValue)) { + debugPrint("set_ini_setting: unable to write '%d' to '%s'", + integerValue, + triplet); + } + } + + programStackPushInteger(program, -1); +} + +void mf_set_outline(Program* program, int args) +{ + int outline = programStackPopInteger(program); + Object* object = static_cast(programStackPopPointer(program)); + object->outline = outline; + programStackPushInteger(program, -1); +} + +void mf_show_window(Program* program, int args) +{ + if (args == 0) { + _windowShow(); + } else if (args == 1) { + const char* windowName = programStackPopString(program); + if (!_windowShowNamed(windowName)) { + debugPrint("show_window: window '%s' is not found", windowName); + } + } + + programStackPushInteger(program, -1); +} + +void mf_tile_refresh_display(Program* program, int args) +{ + tileWindowRefresh(); + programStackPushInteger(program, -1); +} + +void sfall_metarule(Program* program, int args) +{ + static ProgramValue values[6]; + + for (int index = 0; index < args; index++) { + values[index] = programStackPopValue(program); + } + + const char* metarule = programStackPopString(program); + + for (int index = 0; index < args; index++) { + programStackPushValue(program, values[index]); + } + + int metaruleIndex = -1; + for (int index = 0; index < kMetarulesMax; index++) { + if (strcmp(kMetarules[index].name, metarule) == 0) { + metaruleIndex = index; + break; + } + } + + if (metaruleIndex == -1) { + programFatalError("op_sfall_func: '%s' is not implemented", metarule); + } + + if (args < kMetarules[metaruleIndex].minArgs || args > kMetarules[metaruleIndex].maxArgs) { + programFatalError("op_sfall_func: '%s': invalid number of args", metarule); + } + + kMetarules[metaruleIndex].handler(program, args); +} + +} // namespace fallout diff --git a/src/sfall_metarules.h b/src/sfall_metarules.h new file mode 100644 index 00000000..360fc0cc --- /dev/null +++ b/src/sfall_metarules.h @@ -0,0 +1,12 @@ +#ifndef FALLOUT_SFALL_METARULES_H_ +#define FALLOUT_SFALL_METARULES_H_ + +#include "interpreter.h" + +namespace fallout { + +void sfall_metarule(Program* program, int args); + +} // namespace fallout + +#endif /* FALLOUT_SFALL_METARULES_H_ */ diff --git a/src/sfall_opcodes.cc b/src/sfall_opcodes.cc index ea45d4e9..d202c1b1 100644 --- a/src/sfall_opcodes.cc +++ b/src/sfall_opcodes.cc @@ -26,6 +26,7 @@ #include "sfall_ini.h" #include "sfall_kb_helpers.h" #include "sfall_lists.h" +#include "sfall_metarules.h" #include "stat.h" #include "svga.h" #include "tile.h" @@ -33,6 +34,18 @@ namespace fallout { +typedef enum ExplosionMetarule { + EXPL_FORCE_EXPLOSION_PATTERN = 1, + EXPL_FORCE_EXPLOSION_ART = 2, + EXPL_FORCE_EXPLOSION_RADIUS = 3, + EXPL_FORCE_EXPLOSION_DMGTYPE = 4, + EXPL_STATIC_EXPLOSION_RADIUS = 5, + EXPL_GET_EXPLOSION_DAMAGE = 6, + EXPL_SET_DYNAMITE_EXPLOSION_DAMAGE = 7, + EXPL_SET_PLASTIC_EXPLOSION_DAMAGE = 8, + EXPL_SET_EXPLOSION_MAX_TARGET = 9, +} ExplosionMetarule; + static constexpr int kVersionMajor = 4; static constexpr int kVersionMinor = 3; static constexpr int kVersionPatch = 4; @@ -138,6 +151,13 @@ static void op_in_world_map(Program* program) programStackPushInteger(program, GameMode::isInGameMode(GameMode::kWorldmap) ? 1 : 0); } +// force_encounter +static void op_force_encounter(Program* program) +{ + int map = programStackPopInteger(program); + wmForceEncounter(map, 0); +} + // set_world_map_pos static void op_set_world_map_pos(Program* program) { @@ -268,6 +288,13 @@ static void op_abs(Program* program) } } +// get_script +static void op_get_script(Program* program) +{ + Object* obj = static_cast(programStackPopPointer(program)); + programStackPushInteger(program, obj->field_80 + 1); +} + // get_proto_data static void op_get_proto_data(Program* program) { @@ -318,6 +345,19 @@ static void op_set_proto_data(Program* program) *reinterpret_cast(reinterpret_cast(proto) + offset) = value; } +// set_self +static void op_set_self(Program* program) +{ + Object* obj = static_cast(programStackPopPointer(program)); + + int sid = scriptGetSid(program); + + Script* scr; + if (scriptGetScript(sid, &scr) == 0) { + scr->overriddenSelf = obj; + } +} + // list_begin static void opListBegin(Program* program) { @@ -541,6 +581,22 @@ static void op_get_attack_type(Program* program) } } +// force_encounter_with_flags +static void op_force_encounter_with_flags(Program* program) +{ + unsigned int flags = programStackPopInteger(program); + int map = programStackPopInteger(program); + wmForceEncounter(map, flags); +} + +// list_as_array +static void op_list_as_array(Program* program) +{ + int type = programStackPopInteger(program); + int arrayId = ListAsArray(type); + programStackPushInteger(program, arrayId); +} + // atoi static void opParseInt(Program* program) { @@ -620,6 +676,64 @@ static void opGetStringLength(Program* program) programStackPushInteger(program, static_cast(strlen(string))); } +// metarule2_explosions +static void op_explosions_metarule(Program* program) +{ + int param2 = programStackPopInteger(program); + int param1 = programStackPopInteger(program); + int metarule = programStackPopInteger(program); + + switch (metarule) { + case EXPL_FORCE_EXPLOSION_PATTERN: + if (param1 != 0) { + explosionSetPattern(2, 4); + } else { + explosionSetPattern(0, 6); + } + programStackPushInteger(program, 0); + break; + case EXPL_FORCE_EXPLOSION_ART: + explosionSetFrm(param1); + programStackPushInteger(program, 0); + break; + case EXPL_FORCE_EXPLOSION_RADIUS: + explosionSetRadius(param1); + programStackPushInteger(program, 0); + break; + case EXPL_FORCE_EXPLOSION_DMGTYPE: + explosionSetDamageType(param1); + programStackPushInteger(program, 0); + break; + case EXPL_STATIC_EXPLOSION_RADIUS: + weaponSetGrenadeExplosionRadius(param1); + weaponSetRocketExplosionRadius(param2); + programStackPushInteger(program, 0); + break; + case EXPL_GET_EXPLOSION_DAMAGE: + if (1) { + int minDamage; + int maxDamage; + explosiveGetDamage(param1, &minDamage, &maxDamage); + + ArrayId arrayId = CreateTempArray(2, 0); + SetArray(arrayId, ProgramValue { 0 }, ProgramValue { minDamage }, false, program); + SetArray(arrayId, ProgramValue { 1 }, ProgramValue { maxDamage }, false, program); + + programStackPushInteger(program, arrayId); + } + break; + case EXPL_SET_DYNAMITE_EXPLOSION_DAMAGE: + explosiveSetDamage(PROTO_ID_DYNAMITE_I, param1, param2); + break; + case EXPL_SET_PLASTIC_EXPLOSION_DAMAGE: + explosiveSetDamage(PROTO_ID_PLASTIC_EXPLOSIVES_I, param1, param2); + break; + case EXPL_SET_EXPLOSION_MAX_TARGET: + explosionSetMaxTargets(param1); + break; + } +} + // pow (^) static void op_power(Program* program) { @@ -859,6 +973,48 @@ static void opArtExists(Program* program) programStackPushInteger(program, artExists(fid)); } +// sfall_func0 +static void op_sfall_func0(Program* program) +{ + sfall_metarule(program, 0); +} + +// sfall_func1 +static void op_sfall_func1(Program* program) +{ + sfall_metarule(program, 1); +} + +// sfall_func2 +static void op_sfall_func2(Program* program) +{ + sfall_metarule(program, 2); +} + +// sfall_func3 +static void op_sfall_func3(Program* program) +{ + sfall_metarule(program, 3); +} + +// sfall_func4 +static void op_sfall_func4(Program* program) +{ + sfall_metarule(program, 4); +} + +// sfall_func5 +static void op_sfall_func5(Program* program) +{ + sfall_metarule(program, 5); +} + +// sfall_func6 +static void op_sfall_func6(Program* program) +{ + sfall_metarule(program, 6); +} + // div (/) static void op_div(Program* program) { @@ -894,6 +1050,7 @@ void sfallOpcodesInit() interpreterRegisterOpcode(0x816A, op_set_global_script_repeat); interpreterRegisterOpcode(0x816C, op_key_pressed); interpreterRegisterOpcode(0x8170, op_in_world_map); + interpreterRegisterOpcode(0x8171, op_force_encounter); interpreterRegisterOpcode(0x8172, op_set_world_map_pos); interpreterRegisterOpcode(0x8193, opGetCurrentHand); interpreterRegisterOpcode(0x819B, op_set_global_script_type); @@ -908,8 +1065,10 @@ void sfallOpcodesInit() interpreterRegisterOpcode(0x81EB, op_get_ini_string); interpreterRegisterOpcode(0x81EC, op_sqrt); interpreterRegisterOpcode(0x81ED, op_abs); + interpreterRegisterOpcode(0x81F5, op_get_script); interpreterRegisterOpcode(0x8204, op_get_proto_data); interpreterRegisterOpcode(0x8205, op_set_proto_data); + interpreterRegisterOpcode(0x8206, op_set_self); interpreterRegisterOpcode(0x820D, opListBegin); interpreterRegisterOpcode(0x820E, opListNext); interpreterRegisterOpcode(0x820F, opListEnd); @@ -927,6 +1086,7 @@ void sfallOpcodesInit() interpreterRegisterOpcode(0x8221, opGetScreenHeight); interpreterRegisterOpcode(0x8224, op_create_message_window); interpreterRegisterOpcode(0x8228, op_get_attack_type); + interpreterRegisterOpcode(0x8229, op_force_encounter_with_flags); interpreterRegisterOpcode(0x822D, opCreateArray); interpreterRegisterOpcode(0x822E, opSetArray); interpreterRegisterOpcode(0x822F, opGetArray); @@ -936,6 +1096,7 @@ void sfallOpcodesInit() interpreterRegisterOpcode(0x8233, opTempArray); interpreterRegisterOpcode(0x8234, opFixArray); interpreterRegisterOpcode(0x8235, opStringSplit); + interpreterRegisterOpcode(0x8236, op_list_as_array); interpreterRegisterOpcode(0x8237, opParseInt); interpreterRegisterOpcode(0x8238, op_atof); interpreterRegisterOpcode(0x8239, opScanArray); @@ -945,6 +1106,7 @@ void sfallOpcodesInit() interpreterRegisterOpcode(0x8253, opTypeOf); interpreterRegisterOpcode(0x8256, opGetArrayKey); interpreterRegisterOpcode(0x8257, opStackArray); + interpreterRegisterOpcode(0x8261, op_explosions_metarule); interpreterRegisterOpcode(0x8263, op_power); interpreterRegisterOpcode(0x8267, opRound); interpreterRegisterOpcode(0x826B, opGetMessage); @@ -952,6 +1114,13 @@ void sfallOpcodesInit() interpreterRegisterOpcode(0x826F, op_obj_blocking_at); interpreterRegisterOpcode(0x8271, opPartyMemberList); interpreterRegisterOpcode(0x8274, opArtExists); + interpreterRegisterOpcode(0x8276, op_sfall_func0); + interpreterRegisterOpcode(0x8277, op_sfall_func1); + interpreterRegisterOpcode(0x8278, op_sfall_func2); + interpreterRegisterOpcode(0x8279, op_sfall_func3); + interpreterRegisterOpcode(0x827A, op_sfall_func4); + interpreterRegisterOpcode(0x827B, op_sfall_func5); + interpreterRegisterOpcode(0x827C, op_sfall_func6); interpreterRegisterOpcode(0x827F, op_div); } diff --git a/src/skill.cc b/src/skill.cc index 9093068a..2ea2a2fb 100644 --- a/src/skill.cc +++ b/src/skill.cc @@ -1151,7 +1151,7 @@ static int skillGetFreeUsageSlot(int skill) } } - int time = gameTimeGetTime(); + unsigned int time = gameTimeGetTime(); int hoursSinceLastUsage = (time - _timesSkillUsed[skill][0]) / GAME_TIME_TICKS_PER_HOUR; if (hoursSinceLastUsage <= 24) { return -1; diff --git a/src/svga.cc b/src/svga.cc index d8ef4570..d24b8569 100644 --- a/src/svga.cc +++ b/src/svga.cc @@ -112,6 +112,7 @@ void _zero_vid_mem() int _GNW95_init_mode_ex(int width, int height, int bpp) { bool fullscreen = true; + int scale = 1; Config resolutionConfig; if (configInit(&resolutionConfig)) { @@ -155,6 +156,18 @@ int _GNW95_init_mode_ex(int width, int height, int bpp) } #endif + int scaleValue; + if (configGetInt(&resolutionConfig, "MAIN", "SCALE_2X", &scaleValue)) { + scale = scaleValue + 1; // 0 = 1x, 1 = 2x + // Only allow scaling if resulting game resolution is >= 640x480 + if ((width / scale) < 640 || (height / scale) < 480) { + scale = 1; + } else { + width /= scale; + height /= scale; + } + } + configGetBool(&resolutionConfig, "IFACE", "IFACE_BAR_MODE", &gInterfaceBarMode); configGetInt(&resolutionConfig, "IFACE", "IFACE_BAR_WIDTH", &gInterfaceBarWidth); configGetInt(&resolutionConfig, "IFACE", "IFACE_BAR_SIDE_ART", &gInterfaceSidePanelsImageId); @@ -189,7 +202,7 @@ int _GNW95_init_mode_ex(int width, int height, int bpp) configFree(&resolutionConfig); } - if (_GNW95_init_window(width, height, fullscreen) == -1) { + if (_GNW95_init_window(width, height, fullscreen, scale) == -1) { return -1; } @@ -217,7 +230,7 @@ int _init_vesa_mode(int width, int height) } // 0x4CAEDC -int _GNW95_init_window(int width, int height, bool fullscreen) +int _GNW95_init_window(int width, int height, bool fullscreen, int scale) { #ifdef __vita__ if (SDL_Init(SDL_INIT_VIDEO) != 0) { @@ -260,7 +273,7 @@ int _GNW95_init_window(int width, int height, bool fullscreen) windowFlags |= SDL_WINDOW_FULLSCREEN; } - gSdlWindow = SDL_CreateWindow(gProgramWindowTitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, windowFlags); + gSdlWindow = SDL_CreateWindow(gProgramWindowTitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width * scale, height * scale, windowFlags); if (gSdlWindow == NULL) { return -1; } diff --git a/src/svga.h b/src/svga.h index 66591e8b..aa7d3df6 100644 --- a/src/svga.h +++ b/src/svga.h @@ -35,7 +35,7 @@ void _get_start_mode_(); void _zero_vid_mem(); int _GNW95_init_mode_ex(int width, int height, int bpp); int _init_vesa_mode(int width, int height); -int _GNW95_init_window(int width, int height, bool fullscreen); +int _GNW95_init_window(int width, int height, bool fullscreen, int scale); int directDrawInit(int width, int height, int bpp); void directDrawFree(); void directDrawSetPaletteInRange(unsigned char* a1, int a2, int a3); diff --git a/src/tile.cc b/src/tile.cc index af68a398..4bee912e 100644 --- a/src/tile.cc +++ b/src/tile.cc @@ -654,6 +654,17 @@ static void tileRefreshGame(Rect* rect, int elevation) gTileWindowRefreshProc(&rectToUpdate); } +// 0x4B1634 +void tile_toggle_roof(bool refresh) +{ + gTileRoofIsVisible = !gTileRoofIsVisible; + + if (refresh) { + // NOTE: Uninline. + tileWindowRefresh(); + } +} + // 0x4B166C int tileRoofIsVisible() { diff --git a/src/tile.h b/src/tile.h index ae227d2e..97acc8fa 100644 --- a/src/tile.h +++ b/src/tile.h @@ -26,6 +26,7 @@ void tileEnable(); void tileWindowRefreshRect(Rect* rect, int elevation); void tileWindowRefresh(); int tileSetCenter(int tile, int flags); +void tile_toggle_roof(bool refresh); int tileRoofIsVisible(); int tileToScreenXY(int tile, int* x, int* y, int elevation); int tileFromScreenXY(int x, int y, int elevation, bool ignoreBounds = false); diff --git a/src/window.cc b/src/window.cc index fd9e9d82..8d496dda 100644 --- a/src/window.cc +++ b/src/window.cc @@ -70,6 +70,8 @@ typedef struct ManagedWindow { typedef int (*INITVIDEOFN)(); +static void redrawButton(ManagedButton* managedButton); + // 0x51DCAC static int _holdTime = 250; @@ -663,6 +665,38 @@ void _setButtonGFX(int width, int height, unsigned char* normal, unsigned char* } } +// 0x4B75F4 +static void redrawButton(ManagedButton* managedButton) +{ + _win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, false); +} + +// 0x4B7610 +bool _windowHide() +{ + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + if (managedWindow->window == -1) { + return false; + } + + windowHide(managedWindow->window); + + return true; +} + +// 0x4B7648 +bool _windowShow() +{ + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + if (managedWindow->window == -1) { + return false; + } + + windowShow(managedWindow->window); + + return true; +} + // 0x4B7734 int _windowWidth() { @@ -1714,7 +1748,8 @@ bool _windowAddButtonGfx(const char* buttonName, char* pressedFileName, char* no buttonSetMask(managedButton->btn, managedButton->normal); } - _win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, 0); + // NOTE: Uninline. + redrawButton(managedButton); return true; } @@ -1952,7 +1987,8 @@ bool _windowAddButtonTextWithOffsets(const char* buttonName, const char* text, i buttonSetMask(managedButton->btn, managedButton->normal); } - _win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, 0); + // NOTE: Uninline. + redrawButton(managedButton); return true; } @@ -2646,4 +2682,19 @@ void _fillBuf3x3(unsigned char* src, int srcWidth, int srcHeight, unsigned char* destWidth); } +bool _windowShowNamed(const char* windowName) +{ + for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) { + ManagedWindow* managedWindow = &(gManagedWindows[index]); + if (managedWindow->window != -1) { + if (compat_stricmp(managedWindow->name, windowName) == 0) { + windowShow(managedWindow->window); + return true; + } + } + } + + return false; +} + } // namespace fallout diff --git a/src/window.h b/src/window.h index 2ae415ce..dde6b198 100644 --- a/src/window.h +++ b/src/window.h @@ -63,6 +63,8 @@ void _doRightButtonPress(int btn, int keyCode); void sub_4B704C(int btn, int mouseEvent); void _doRightButtonRelease(int btn, int keyCode); void _setButtonGFX(int width, int height, unsigned char* normal, unsigned char* pressed, unsigned char* a5); +bool _windowHide(); +bool _windowShow(); int _windowWidth(); int _windowHeight(); bool _windowDraw(); @@ -127,6 +129,8 @@ void _drawScaledBuf(unsigned char* dest, int destWidth, int destHeight, unsigned void _alphaBltBuf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* alphaWindowBuffer, unsigned char* alphaBuffer, unsigned char* dest, int destPitch); void _fillBuf3x3(unsigned char* src, int srcWidth, int srcHeight, unsigned char* dest, int destWidth, int destHeight); +bool _windowShowNamed(const char* name); + } // namespace fallout #endif /* WINDOW_H */ diff --git a/src/window_manager.cc b/src/window_manager.cc index e0f170a8..f0142959 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -32,7 +32,7 @@ static void windowFree(int win); static void _win_buffering(bool a1); static void _win_move(int win_index, int x, int y); static void _win_clip(Window* window, RectListNode** rect, unsigned char* a3); -static void _win_drag(int win); +static void win_drag(int win); static void _refresh_all(Rect* rect, unsigned char* a2); static Button* buttonGetButton(int btn, Window** out_win); static int paletteOpenFileImpl(const char* path, int flags); @@ -549,9 +549,9 @@ void windowDrawText(int win, const char* str, int width, int x, int y, int color } } - if (color & 0xFF00) { - int t = (color & 0xFF00) >> 8; - textColor = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[t]]; + if ((color & 0xFF00) != 0) { + int colorIndex = (color & 0xFF) - 1; + textColor = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; } else { textColor = color; } @@ -582,9 +582,9 @@ void windowDrawLine(int win, int left, int top, int right, int bottom, int color return; } - if (color & 0xFF00) { - int t = (color & 0xFF00) >> 8; - color = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[t]]; + if ((color & 0xFF00) != 0) { + int colorIndex = (color & 0xFF) - 1; + color = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; } bufferDrawLine(window->buffer, window->width, left, top, right, bottom, color); @@ -604,8 +604,8 @@ void windowDrawRect(int win, int left, int top, int right, int bottom, int color } if ((color & 0xFF00) != 0) { - int v1 = (color & 0xFF00) >> 8; - color = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[v1]]; + int colorIndex = (color & 0xFF) - 1; + color = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; } if (right < left) { @@ -643,8 +643,8 @@ void windowFill(int win, int x, int y, int width, int height, int color) color = _colorTable[_GNW_wcolor[0]] & 0xFF; } } else if ((color & 0xFF00) != 0) { - int v1 = (color & 0xFF00) >> 8; - color = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[v1]]; + int colorIndex = (color & 0xFF) - 1; + color = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; } if (color < 256) { @@ -1000,10 +1000,13 @@ void _win_clip(Window* window, RectListNode** rectListNodePtr, unsigned char* a3 } // 0x4D765C -void _win_drag(int win) +void win_drag(int win) { - // TODO: Probably somehow related to self-run functionality, skip for now. Window* window = windowGetWindow(win); + int dx = 0; + int dy = 0; + int mx; + int my; if (!gWindowSystemInitialized) { return; @@ -1015,8 +1018,7 @@ void _win_drag(int win) windowShow(win); - Rect rect; - rectCopy(&rect, &(window->rect)); + Rect rect = window->rect; tickersExecute(); @@ -1024,7 +1026,65 @@ void _win_drag(int win) _mouse_info(); } - if ((window->flags & WINDOW_MANAGED) && (window->rect.left & 3)) { + while ((mouseGetEvent() & MOUSE_EVENT_ANY_BUTTON_UP) == 0) { + sharedFpsLimiter.mark(); + + if (dx != 0 || dy != 0) { + window->rect.left += dx; + window->rect.top += dy; + window->rect.right += dx; + window->rect.bottom += dy; + _GNW_win_refresh(window, &(window->rect), NULL); + + RectListNode* rectListNode = rect_clip(&rect, &(window->rect)); + while (rectListNode != NULL) { + RectListNode* next = rectListNode->next; + + // NOTE: Uninline. + windowRefreshAll(&(rectListNode->rect)); + + _rect_free(rectListNode); + rectListNode = next; + } + + rect = window->rect; + } + + mouseGetPosition(&mx, &my); + tickersExecute(); + + if (vcrUpdate() != 3) { + _mouse_info(); + } + + dx = mx; + dy = my; + mouseGetPosition(&mx, &my); + + dx = mx - dx; + dy = my - dy; + + if (dx + window->rect.left < _scr_size.left) { + dx = _scr_size.left - window->rect.left; + } + + if (dx + window->rect.right > _scr_size.right) { + dx = _scr_size.right - window->rect.right; + } + + if (dy + window->rect.top < _scr_size.top) { + dy = _scr_size.top - window->rect.top; + } + + if (dy + window->rect.bottom > _scr_size.bottom) { + dy = _scr_size.bottom - window->rect.bottom; + } + + renderPresent(); + sharedFpsLimiter.throttle(); + } + + if ((window->flags & WINDOW_MANAGED) != 0 && (window->rect.left & 3) != 0) { _win_move(window->id, window->rect.left, window->rect.top); } } @@ -1197,19 +1257,18 @@ Button* buttonGetButton(int btn, Window** windowPtr) } // 0x4D7A34 -int _GNW_check_menu_bars(int a1) +int _GNW_check_menu_bars(int input) { if (!gWindowSystemInitialized) { return -1; } - int v1 = a1; for (int index = gWindowsLength - 1; index >= 1; index--) { Window* window = gWindows[index]; if (window->menuBar != NULL) { for (int pulldownIndex = 0; pulldownIndex < window->menuBar->pulldownsLength; pulldownIndex++) { - if (v1 == window->menuBar->pulldowns[pulldownIndex].keyCode) { - v1 = _GNW_process_menu(window->menuBar, pulldownIndex); + if (input == window->menuBar->pulldowns[pulldownIndex].keyCode) { + input = _GNW_process_menu(window->menuBar, pulldownIndex); break; } } @@ -1220,7 +1279,7 @@ int _GNW_check_menu_bars(int a1) } } - return v1; + return input; } // 0x4D69DC @@ -1987,17 +2046,17 @@ int _GNW_check_buttons(Window* window, int* keyCodePtr) } if (button != NULL) { - if ((button->flags & BUTTON_FLAG_0x10) != 0 + if ((button->flags & BUTTON_DRAG_HANDLE) != 0 && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_DOWN) != 0 && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_REPEAT) == 0) { - _win_drag(window->id); + win_drag(window->id); _button_draw(button, window, button->normalImage, true, NULL, true); } - } else if ((window->flags & WINDOW_FLAG_0x80) != 0) { + } else if ((window->flags & WINDOW_DRAGGABLE_BY_BACKGROUND) != 0) { v25 |= mouseEvent << 8; if ((mouseEvent & MOUSE_EVENT_ANY_BUTTON_DOWN) != 0 && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_REPEAT) == 0) { - _win_drag(window->id); + win_drag(window->id); } } diff --git a/src/window_manager.h b/src/window_manager.h index 265aa7e2..2f765632 100644 --- a/src/window_manager.h +++ b/src/window_manager.h @@ -42,8 +42,10 @@ typedef enum WindowFlags { WINDOW_MODAL = 0x10, WINDOW_TRANSPARENT = 0x20, WINDOW_FLAG_0x40 = 0x40, - // Draggable? - WINDOW_FLAG_0x80 = 0x80, + + /// Specifies that the window is draggable by clicking and moving anywhere + /// in its background. + WINDOW_DRAGGABLE_BY_BACKGROUND = 0x80, WINDOW_MANAGED = 0x100, } WindowFlags; @@ -52,7 +54,9 @@ typedef enum ButtonFlags { BUTTON_FLAG_0x02 = 0x02, BUTTON_FLAG_0x04 = 0x04, BUTTON_FLAG_DISABLED = 0x08, - BUTTON_FLAG_0x10 = 0x10, + + /// Specifies that the button is a drag handle for parent window. + BUTTON_DRAG_HANDLE = 0x10, BUTTON_FLAG_TRANSPARENT = 0x20, BUTTON_FLAG_0x40 = 0x40, BUTTON_FLAG_GRAPHIC = 0x010000, @@ -173,7 +177,7 @@ int windowGetWidth(int win); int windowGetHeight(int win); int windowGetRect(int win, Rect* rect); int _win_check_all_buttons(); -int _GNW_check_menu_bars(int a1); +int _GNW_check_menu_bars(int input); void programWindowSetTitle(const char* title); bool showMesageBox(const char* str); int buttonCreate(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, unsigned char* up, unsigned char* dn, unsigned char* hover, int flags); diff --git a/src/window_manager_private.cc b/src/window_manager_private.cc index af94de7c..9c5f04e4 100644 --- a/src/window_manager_private.cc +++ b/src/window_manager_private.cc @@ -17,17 +17,15 @@ namespace fallout { -typedef struct STRUCT_6B2340 { - int field_0; - int field_4; -} STRUCT_6B2340; - -typedef struct STRUCT_6B2370 { - int field_0; - // win - int field_4; - int field_8; -} STRUCT_6B2370; +/// Maximum number of timed messages. +static constexpr int kTimedMsgs = 5; + +static int get_num_i(int win, int* value, int max_chars_wcursor, bool clear, bool allow_negative, int x, int y); +static void tm_watch_msgs(); +static void tm_kill_msg(); +static void tm_kill_out_of_order(int queueIndex); +static void tm_click_response(int btn, int keyCode); +static bool tm_index_active(int queueIndex); // 0x51E414 static int _wd = -1; @@ -36,34 +34,45 @@ static int _wd = -1; static MenuBar* _curr_menu = NULL; // 0x51E41C -static bool _tm_watch_active = false; +static bool tm_watch_active = false; +// NOTE: Also anonymous in the original code. +// // 0x6B2340 -static STRUCT_6B2340 _tm_location[5]; +static struct { + bool taken; + int y; +} tm_location[kTimedMsgs]; // 0x6B2368 -static int _tm_text_x; +static int tm_text_x; // 0x6B236C -static int _tm_h; +static int tm_h; +// NOTE: Also anonymous in the original code. +// // 0x6B2370 -static STRUCT_6B2370 _tm_queue[5]; +static struct { + unsigned int created; + int id; + int location; +} tm_queue[kTimedMsgs]; // 0x6B23AC -static unsigned int _tm_persistence; +static unsigned int tm_persistence; // 0x6B23B0 -static int _scr_center_x; +static int scr_center_x; // 0x6B23B4 -static int _tm_text_y; +static int tm_text_y; // 0x6B23B8 -static int _tm_kill; +static int tm_kill; // 0x6B23BC -static int _tm_add; +static int tm_add; // x // @@ -314,7 +323,7 @@ int _win_list_select_at(const char* title, char** items, int itemsLength, ListSe NULL, NULL, NULL, - BUTTON_FLAG_0x10); + BUTTON_DRAG_HANDLE); windowRefresh(win); @@ -604,7 +613,7 @@ int _win_get_str(char* dest, int length, const char* title, int x, int y) windowRefresh(win); - _win_input_str(win, + int rc = _win_input_str(win, dest, length, 16, @@ -614,7 +623,88 @@ int _win_get_str(char* dest, int length, const char* title, int x, int y) windowDestroy(win); - return 0; + return rc; +} + +// 0x4DB920 +int win_yes_no(const char* question, int x, int y, int color) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + int height = 3 * fontGetLineHeight() + 16; + int width = std::max(fontGetStringWidth(question) + 16, 144) + 16; + + int win = windowCreate(x, y, width, height, 0x100, WINDOW_MODAL | WINDOW_MOVE_ON_TOP); + if (win == -1) { + return -1; + } + + windowDrawBorder(win); + + unsigned char* windowBuffer = windowGetWindow(win)->buffer; + + int textColor; + if ((color & 0xFF00) != 0) { + int colorIndex = (color & 0xFF) - 1; + textColor = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; + } else { + textColor = color; + } + + fontDrawText(windowBuffer + 8 * width + 16, + question, + width, + width, + textColor); + + _win_register_text_button(win, + width / 2 - 64, + height - fontGetLineHeight() - 14, + -1, + -1, + -1, + 'y', + "Yes", + 0); + _win_register_text_button(win, + width / 2 + 16, + height - fontGetLineHeight() - 14, + -1, + -1, + -1, + 'n', + "No", + 0); + windowRefresh(win); + + // NOTE: Original code is slightly different but does the same thing. + int rc = -1; + while (rc == -1) { + sharedFpsLimiter.mark(); + + switch (inputGetInput()) { + case KEY_ESCAPE: + case 'n': + case 'N': + rc = 0; + break; + case KEY_RETURN: + case KEY_SPACE: + case 'y': + case 'Y': + rc = 1; + break; + } + + renderPresent(); + sharedFpsLimiter.throttle(); + } + + windowDestroy(win); + + return rc; } // 0x4DBA98 @@ -645,9 +735,8 @@ int _win_msg(const char* string, int x, int y, int color) int textColor; if ((color & 0xFF00) != 0) { - int index = (color & 0xFF) - 1; - textColor = _colorTable[_GNW_wcolor[index]]; - textColor |= color & ~0xFFFF; + int colorIndex = (color & 0xFF) - 1; + textColor = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; } else { textColor = color; } @@ -723,6 +812,12 @@ int _win_debug(char* string) return -1; } + // CE: Debug window metrics were designed for default DOS-style font (0). + // We don't expect caller to properly set one, so without manually forcing + // it debug window might contain mixed fonts. + int oldFont = fontGetCurrent(); + fontSetCurrent(0); + int lineHeight = fontGetLineHeight(); if (_wd == -1) { @@ -791,7 +886,7 @@ int _win_debug(char* string) NULL, NULL, NULL, - BUTTON_FLAG_0x10); + BUTTON_DRAG_HANDLE); } char temp[2]; @@ -829,6 +924,9 @@ int _win_debug(char* string) windowRefresh(_wd); + // CE: Restore font. + fontSetCurrent(oldFont); + return 0; } @@ -1118,11 +1216,333 @@ int _win_input_str(int win, char* dest, int maxLength, int x, int y, int textCol return 0; } +// 0x4DCD68 +int win_get_num_i(int* value, int min, int max, bool clear, const char* title, int x, int y) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + if (max < min) { + return -1; + } + + if (*value < min) { + *value = min; + } else if (*value > max) { + *value = max; + } + + int original = *value; + int max_chars_wcursor = _calc_max_field_chars_wcursor(min, max); + if (max_chars_wcursor == -1) { + return -1; + } + + int v2 = fontGetMonospacedCharacterWidth() * max_chars_wcursor; + + int width = fontGetStringWidth(title); + if (width < v2) { + width = v2; + } + + width += 16; + if (width < 160) { + width = 160; + } + + int height = 5 * fontGetLineHeight() + 16; + int v3 = (width - v2) / 2; + int v4 = fontGetLineHeight(); + int v5 = fontGetLineHeight() + 2; + + int win = windowCreate(x, y, width, height, 0x100, WINDOW_MODAL | WINDOW_MOVE_ON_TOP); + if (win == -1) { + return -1; + } + + windowDrawBorder(win); + windowFill(win, v3, v4 + 14, v2, v5, 0x100 | 1); + windowDrawText(win, title, width - 16, 8, 8, 0x100 | 5); + + bufferDrawRectShadowed(windowGetBuffer(win), + width, + v3 - 2, + v4 + 12, + v3 + v2 + 1, + v4 + 14 + v5 - 1, + _colorTable[_GNW_wcolor[2]], + _colorTable[_GNW_wcolor[1]]); + + _win_register_text_button(win, + width / 2 - 72, + height - fontGetLineHeight() - 14, + -1, + -1, + -1, + KEY_RETURN, + "Done", + 0); + + _win_register_text_button(win, + width / 2 + 16, + height - fontGetLineHeight() - 14, + -1, + -1, + -1, + KEY_ESCAPE, + "Cancel", + 0); + + char* hint = (char*)internal_malloc(80); + if (hint == NULL) { + return -1; + } + + sprintf(hint, "Please enter a number between %d and %d.", min, max); + windowRefresh(win); + + int rc; + while (1) { + rc = get_num_i(win, value, max_chars_wcursor, clear, min < 0, v3, v4 + 14); + if (*value >= min && *value <= max) { + break; + } + + if (rc == -1) { + break; + } + + _win_msg(hint, x - 70, y + 100, 0x100 | 6); + *value = original; + } + + internal_free(hint); + windowDestroy(win); + + return rc; +} + // 0x4DBD04 int process_pull_down(int win, Rect* rect, char** items, int itemsLength, int foregroundColor, int backgroundColor, MenuBar* menuBar, int pulldownIndex) { - // TODO: Incomplete. - return -1; + if (menuBar != NULL) { + unsigned char* parentWindowBuffer = windowGetWindow(menuBar->win)->buffer; + MenuPulldown* pulldown = &(menuBar->pulldowns[pulldownIndex]); + + int x = pulldown->rect.left; + int y = pulldown->rect.top; + int width = pulldown->rect.right - x + 1; + int height = pulldown->rect.bottom - y + 1; + + int color1 = menuBar->foregroundColor; + if ((color1 & 0xFF00) != 0) { + int colorIndex = (color1 & 0xFF) - 1; + color1 = (color1 & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; + } + + int color2 = menuBar->backgroundColor; + if ((color2 & 0xFF00) != 0) { + int colorIndex = (color2 & 0xFF) - 1; + color2 = (color2 & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; + } + + _swap_color_buf(parentWindowBuffer + width * y + x, + width, + height, + windowGetWidth(menuBar->win), + color1, + color2); + windowRefreshRect(menuBar->win, &(pulldown->rect)); + } + + unsigned char* windowBuffer = windowGetWindow(win)->buffer; + int width = rectGetWidth(rect); + int height = rectGetHeight(rect); + + int focusedIndex = -1; + int rc; + int mx1; + int my1; + int mx2; + int my2; + int input; + + mouseGetPosition(&mx1, &my1); + + while (1) { + sharedFpsLimiter.mark(); + + input = inputGetInput(); + if (input != -1) { + break; + } + + mouseGetPosition(&mx2, &my2); + + if (mx2 < mx1 - 4 + || mx2 > mx1 + 4 + || my2 < my1 - 4 + || my2 > my1 + 4) { + break; + } + + renderPresent(); + sharedFpsLimiter.throttle(); + } + + while (1) { + sharedFpsLimiter.mark(); + + mouseGetPosition(&mx2, &my2); + + if (input == -2) { + if (menuBar != NULL) { + if (_mouse_click_in(menuBar->rect.left, menuBar->rect.top, menuBar->rect.right, menuBar->rect.bottom)) { + int index; + for (index = 0; index < menuBar->pulldownsLength; index++) { + MenuPulldown* pulldown = &(menuBar->pulldowns[index]); + if (_mouse_click_in(pulldown->rect.left, pulldown->rect.top, pulldown->rect.right, pulldown->rect.bottom)) { + break; + } + } + + if (index < menuBar->pulldownsLength && index != pulldownIndex) { + rc = -2 - index; + break; + } + } + } + } + + if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_UP) != 0 + || ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0 + && (mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) == 0)) { + if (_mouse_click_in(rect->left, rect->top + 8, rect->right, rect->bottom - 9)) { + rc = focusedIndex; + } else { + rc = -1; + } + break; + } + + bool done = false; + switch (input) { + case KEY_ESCAPE: + rc = -1; + done = true; + break; + case KEY_RETURN: + rc = focusedIndex; + done = true; + break; + case KEY_ARROW_LEFT: + if (menuBar != NULL && pulldownIndex > 0) { + rc = -2 - (pulldownIndex - 1); + done = true; + } + break; + case KEY_ARROW_RIGHT: + if (menuBar != NULL && pulldownIndex < menuBar->pulldownsLength - 1) { + rc = -2 - (pulldownIndex + 1); + done = true; + } + break; + case KEY_ARROW_UP: + while (focusedIndex > 0) { + focusedIndex--; + + if (items[focusedIndex][0] != '\0') { + break; + } + } + input = -3; + break; + case KEY_ARROW_DOWN: + while (focusedIndex < itemsLength - 1) { + focusedIndex++; + + if (items[focusedIndex][0] != '\0') { + break; + } + } + input = -3; + break; + default: + if (mx2 != mx1 || my2 != my1) { + if (_mouse_click_in(rect->left, rect->top + 8, rect->right, rect->bottom - 9)) { + input = (my2 - rect->top - 8) / fontGetLineHeight(); + if (input != -1) { + focusedIndex = items[input][0] != '\0' ? input : -1; + input = -3; + } + } + + mx1 = mx2; + my1 = my2; + } + break; + } + + if (done) { + break; + } + + if (input == -3) { + windowFill(win, 2, 8, width - 4, height - 16, backgroundColor); + _win_text(win, items, itemsLength, width - 4, 2, 8, foregroundColor); + + if (focusedIndex != -1) { + _lighten_buf(windowBuffer + (focusedIndex * fontGetLineHeight() + 8) * width + 2, + width - 4, + fontGetLineHeight(), + width); + } + + windowRefresh(win); + } + + input = inputGetInput(); + + renderPresent(); + sharedFpsLimiter.throttle(); + } + + if (menuBar != NULL) { + unsigned char* parentWindowBuffer = windowGetWindow(menuBar->win)->buffer; + MenuPulldown* pulldown = &(menuBar->pulldowns[pulldownIndex]); + + int x = pulldown->rect.left; + int y = pulldown->rect.top; + int width = pulldown->rect.right - x + 1; + int height = pulldown->rect.bottom - y + 1; + + int color1 = menuBar->foregroundColor; + if ((color1 & 0xFF00) != 0) { + int colorIndex = (color1 & 0xFF) - 1; + color1 = (color1 & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; + } + + int color2 = menuBar->backgroundColor; + if ((color2 & 0xFF00) != 0) { + int colorIndex = (color2 & 0xFF) - 1; + color2 = (color2 & ~0xFFFF) | _colorTable[_GNW_wcolor[colorIndex]]; + } + + _swap_color_buf(parentWindowBuffer + width * y + x, + width, + height, + windowGetWidth(menuBar->win), + color1, + color2); + windowRefreshRect(menuBar->win, &(pulldown->rect)); + + renderPresent(); + } + + windowDestroy(win); + + return rc; } // 0x4DC930 @@ -1188,167 +1608,338 @@ size_t _calc_max_field_chars_wcursor(int value1, int value2) return std::max(len1, len2) + 1; } +// 0x4DD0AC +int get_num_i(int win, int* value, int max_chars_wcursor, bool clear, bool allow_negative, int x, int y) +{ + bool first_press = false; + + Window* window = windowGetWindow(win); + if (window == NULL) { + return -1; + } + + int original = *value; + + int width = max_chars_wcursor * fontGetMonospacedCharacterWidth(); + int height = fontGetLineHeight(); + + char* string = (char*)internal_malloc(max_chars_wcursor + 1); + + if (clear) { + string[0] = '\0'; + } else { + snprintf(string, + max_chars_wcursor + 1, + "%d", + *value); + } + + int cursorPos = strlen(string); + string[cursorPos] = '_'; + string[cursorPos + 1] = '\0'; + + windowDrawText(win, string, width, x, y, 0x100 | 4); + + Rect rect; + rect.left = x; + rect.top = y; + rect.right = x + width; + rect.bottom = y + height; + windowRefreshRect(win, &rect); + + bool done = false; + while (cursorPos <= max_chars_wcursor && !done) { + sharedFpsLimiter.mark(); + + int input = inputGetInput(); + if (input == KEY_RETURN) { + done = true; + } else if (input == KEY_BACKSPACE) { + if (cursorPos > 0) { + int stringWidth = fontGetStringWidth(string); + if (first_press) { + string[0] = '_'; + string[1] = '\0'; + cursorPos = 1; + first_press = false; + } else { + string[cursorPos - 1] = '_'; + string[cursorPos] = '\0'; + cursorPos--; + } + + windowFill(win, x, y, stringWidth, height, 0x100 | 1); + windowDrawText(win, string, width, x, y, 0x100 | 4); + windowRefreshRect(win, &rect); + } + } else if (input == KEY_ESCAPE) { + *value = original; + internal_free(string); + + return -1; + } else if (input == KEY_ARROW_LEFT) { + if (cursorPos > 0) { + int stringWidth = fontGetStringWidth(string); + string[cursorPos - 1] = '_'; + string[cursorPos] = '\0'; + windowFill(win, x, y, stringWidth, height, 0x100 | 1); + windowDrawText(win, string, width, x, y, 0x100 | 4); + windowRefreshRect(win, &rect); + + first_press = false; + cursorPos--; + } + } else { + if (cursorPos != max_chars_wcursor - 1) { + if ((input == '-' && allow_negative) + || (input >= '0' && input <= '9')) { + string[cursorPos] = input; + string[cursorPos + 1] = '_'; + string[cursorPos + 2] = '\0'; + + int stringWidth = fontGetStringWidth(string); + windowFill(win, x, y, stringWidth, height, 0x100 | 1); + windowDrawText(win, string, width, x, y, 0x100 | 4); + windowRefreshRect(win, &rect); + + first_press = false; + cursorPos++; + } + } + } + + renderPresent(); + sharedFpsLimiter.throttle(); + } + + *value = atoi(string); + internal_free(string); + + return 0; +} + // 0x4DD3EC void _GNW_intr_init() { - int v1, v2; - int i; + int spacing; + int top; + int index; - _tm_persistence = 3000; - _tm_add = 0; - _tm_kill = -1; - _scr_center_x = _scr_size.right / 2; + tm_persistence = 3000; + tm_add = 0; + tm_kill = -1; + scr_center_x = _scr_size.right / 2; if (_scr_size.bottom >= 479) { - _tm_text_y = 16; - _tm_text_x = 16; + tm_text_y = 16; + tm_text_x = 16; } else { - _tm_text_y = 10; - _tm_text_x = 10; + tm_text_y = 10; + tm_text_x = 10; } - _tm_h = 2 * _tm_text_y + fontGetLineHeight(); + tm_h = 2 * tm_text_y + fontGetLineHeight(); - v1 = _scr_size.bottom >> 3; - v2 = _scr_size.bottom >> 2; + spacing = _scr_size.bottom / 8; + top = _scr_size.bottom / 4; - for (i = 0; i < 5; i++) { - _tm_location[i].field_4 = v1 * i + v2; - _tm_location[i].field_0 = 0; + for (index = 0; index < kTimedMsgs; index++) { + tm_location[index].y = spacing * index + top; + tm_location[index].taken = false; } } // 0x4DD4A4 void _GNW_intr_exit() { - tickersRemove(_tm_watch_msgs); - while (_tm_kill != -1) { - _tm_kill_msg(); + tickersRemove(tm_watch_msgs); + while (tm_kill != -1) { + tm_kill_msg(); } } +// 0x4DD4C8 +int win_timed_msg(const char* msg, int color) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + if (tm_add == tm_kill) { + return -1; + } + + int width = fontGetStringWidth(msg) + 2 * tm_text_x; + int x = scr_center_x - width / 2; + int height = tm_h; + int y; + + int index; + for (index = 0; index < kTimedMsgs; index++) { + if (!tm_location[index].taken) { + tm_location[index].taken = true; + y = tm_location[index].y; + break; + } + } + + if (index == kTimedMsgs) { + return -1; + } + + int win = windowCreate(x, y, width, height, 0x100 | 1, WINDOW_MOVE_ON_TOP); + windowDrawBorder(win); + windowDrawText(win, msg, 0, tm_text_x, tm_text_y, color); + + int btn = buttonCreate(win, + 0, + 0, + width, + height, + -1, + -1, + -1, + -1, + NULL, + NULL, + NULL, + 0); + buttonSetMouseCallbacks(btn, NULL, NULL, NULL, tm_click_response); + + windowRefresh(win); + + tm_queue[tm_add].id = win; + tm_queue[tm_add].created = getTicks(); + tm_queue[tm_add].location = index; + + tm_add++; + if (tm_add == kTimedMsgs) { + tm_add = 0; + } else if (tm_kill == -1) { + tm_kill = 0; + tickersAdd(tm_watch_msgs); + } + + return 0; +} + // 0x4DD66C -void _tm_watch_msgs() +void tm_watch_msgs() { - if (_tm_watch_active) { + if (tm_watch_active) { return; } - _tm_watch_active = 1; - while (_tm_kill != -1) { - if (getTicksSince(_tm_queue[_tm_kill].field_0) < _tm_persistence) { + tm_watch_active = true; + while (tm_kill != -1) { + if (getTicksSince(tm_queue[tm_kill].created) < tm_persistence) { break; } - _tm_kill_msg(); + tm_kill_msg(); } - _tm_watch_active = 0; + tm_watch_active = false; } // 0x4DD6C0 -void _tm_kill_msg() +void tm_kill_msg() { - int v0; + if (tm_kill != -1) { + windowDestroy(tm_queue[tm_kill].id); + tm_location[tm_queue[tm_kill].location].taken = false; - v0 = _tm_kill; - if (v0 != -1) { - windowDestroy(_tm_queue[_tm_kill].field_4); - _tm_location[_tm_queue[_tm_kill].field_8].field_0 = 0; - - if (v0 == 5) { - v0 = 0; + tm_kill++; + if (tm_kill == kTimedMsgs) { + tm_kill = 0; } - if (v0 == _tm_add) { - _tm_add = 0; - _tm_kill = -1; - tickersRemove(_tm_watch_msgs); - v0 = _tm_kill; + if (tm_kill == tm_add) { + tm_add = 0; + tm_kill = -1; + tickersRemove(tm_watch_msgs); } } - - _tm_kill = v0; } // 0x4DD744 -void _tm_kill_out_of_order(int queueIndex) +void tm_kill_out_of_order(int queue_index) { - int v7; - int v6; + int copy_from; + int copy_to; - if (_tm_kill == -1) { + if (tm_kill == -1) { return; } - if (!_tm_index_active(queueIndex)) { + if (!tm_index_active(queue_index)) { return; } - windowDestroy(_tm_queue[queueIndex].field_4); + windowDestroy(tm_queue[queue_index].id); - _tm_location[_tm_queue[queueIndex].field_8].field_0 = 0; + tm_location[tm_queue[queue_index].location].taken = false; - if (queueIndex != _tm_kill) { - v6 = queueIndex; - do { - v7 = v6 - 1; - if (v7 < 0) { - v7 = 4; - } + copy_to = queue_index; + while (copy_to != tm_kill) { + copy_from = copy_to - 1; + if (copy_from < 0) { + copy_from = kTimedMsgs - 1; + } - memcpy(&(_tm_queue[v6]), &(_tm_queue[v7]), sizeof(STRUCT_6B2370)); - v6 = v7; - } while (v7 != _tm_kill); + tm_queue[copy_to] = tm_queue[copy_from]; + copy_to = copy_from; } - if (++_tm_kill == 5) { - _tm_kill = 0; + tm_kill++; + if (tm_kill == kTimedMsgs) { + tm_kill = 0; } - if (_tm_add == _tm_kill) { - _tm_add = 0; - _tm_kill = -1; - tickersRemove(_tm_watch_msgs); + if (tm_add == tm_kill) { + tm_add = 0; + tm_kill = -1; + tickersRemove(tm_watch_msgs); } } // 0x4DD82C -void _tm_click_response(int btn) +void tm_click_response(int btn, int keyCode) { int win; - int queueIndex; + int queue_index; - if (_tm_kill == -1) { + if (tm_kill == -1) { return; } win = buttonGetWindowId(btn); - queueIndex = _tm_kill; - while (win != _tm_queue[queueIndex].field_4) { - queueIndex++; - if (queueIndex == 5) { - queueIndex = 0; + queue_index = tm_kill; + while (win != tm_queue[queue_index].id) { + queue_index++; + if (queue_index == kTimedMsgs) { + queue_index = 0; } - if (queueIndex == _tm_kill || !_tm_index_active(queueIndex)) + if (queue_index == tm_kill || !tm_index_active(queue_index)) { return; + } } - _tm_kill_out_of_order(queueIndex); + tm_kill_out_of_order(queue_index); } // 0x4DD870 -int _tm_index_active(int queueIndex) +bool tm_index_active(int queue_index) { - if (_tm_kill != _tm_add) { - if (_tm_kill >= _tm_add) { - if (queueIndex >= _tm_add && queueIndex < _tm_kill) - return 0; - } else if (queueIndex < _tm_kill || queueIndex >= _tm_add) { - return 0; - } + if (tm_kill == tm_add) { + return true; + } + + if (tm_kill < tm_add) { + return queue_index >= tm_kill && queue_index < tm_add; } - return 1; + + return queue_index < tm_add || queue_index >= tm_kill; } } // namespace fallout diff --git a/src/window_manager_private.h b/src/window_manager_private.h index 449a2ed2..cfdecc73 100644 --- a/src/window_manager_private.h +++ b/src/window_manager_private.h @@ -16,6 +16,7 @@ extern char gProgramWindowTitle[256]; int _win_list_select(const char* title, char** fileList, int fileListLength, ListSelectionHandler* callback, int x, int y, int color); int _win_list_select_at(const char* title, char** items, int itemsLength, ListSelectionHandler* callback, int x, int y, int color, int start); int _win_get_str(char* dest, int length, const char* title, int x, int y); +int win_yes_no(const char* question, int x, int y, int color); int _win_msg(const char* string, int x, int y, int color); int _win_pull_down(char** items, int itemsLength, int x, int y, int color); int _create_pull_down(char** stringList, int stringListLength, int x, int y, int foregroundColor, int backgroundColor, Rect* rect); @@ -29,14 +30,11 @@ int _win_width_needed(char** fileNameList, int fileNameListLength); int _win_input_str(int win, char* dest, int maxLength, int x, int y, int textColor, int backgroundColor); int process_pull_down(int win, Rect* rect, char** items, int itemsLength, int a5, int a6, MenuBar* menuBar, int pulldownIndex); int _GNW_process_menu(MenuBar* menuBar, int pulldownIndex); +int win_get_num_i(int* value, int min, int max, bool clear, const char* title, int x, int y); size_t _calc_max_field_chars_wcursor(int value1, int value2); void _GNW_intr_init(); void _GNW_intr_exit(); -void _tm_watch_msgs(); -void _tm_kill_msg(); -void _tm_kill_out_of_order(int queueIndex); -void _tm_click_response(int btn); -int _tm_index_active(int queueIndex); +int win_timed_msg(const char* msg, int color); } // namespace fallout diff --git a/src/worldmap.cc b/src/worldmap.cc index 3243d8d2..8b302557 100644 --- a/src/worldmap.cc +++ b/src/worldmap.cc @@ -817,6 +817,8 @@ static double gGameTimeIncRemainder = 0.0; static FrmImage _backgroundFrmImage; static FrmImage _townFrmImage; static bool wmFaded = false; +static int wmForceEncounterMapId = -1; +static unsigned int wmForceEncounterFlags = 0; static inline bool cityIsValid(int city) { @@ -929,6 +931,9 @@ static int wmGenDataInit() wmGenData.tabsScrollingDelta = 0; wmGenData.viewportMaxX = 0; + wmForceEncounterMapId = -1; + wmForceEncounterFlags = 0; + return 0; } @@ -979,6 +984,9 @@ static int wmGenDataReset() wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY); + wmForceEncounterMapId = -1; + wmForceEncounterFlags = 0; + return 0; } @@ -3347,6 +3355,33 @@ static int wmRndEncounterOccurred() } } + // SFALL: Handle forced encounter. + // CE: In Sfall a check for forced encounter is inserted instead of check + // for Horrigan encounter (above). This implemenation gives Horrigan + // encounter a priority. + if (wmForceEncounterMapId != -1) { + if ((wmForceEncounterFlags & ENCOUNTER_FLAG_NO_CAR) != 0) { + if (wmGenData.isInCar) { + wmMatchAreaContainingMapIdx(wmForceEncounterMapId, &(wmGenData.currentCarAreaId)); + } + } + + // For unknown reason fadeout and blinking icon are mutually exclusive. + if ((wmForceEncounterFlags & ENCOUNTER_FLAG_FADEOUT) != 0) { + wmFadeOut(); + } else if ((wmForceEncounterFlags & ENCOUNTER_FLAG_NO_ICON) == 0) { + bool special = (wmForceEncounterFlags & ENCOUNTER_FLAG_ICON_SP) != 0; + wmBlinkRndEncounterIcon(special); + } + + mapLoadById(wmForceEncounterMapId); + + wmForceEncounterMapId = -1; + wmForceEncounterFlags = 0; + + return 1; + } + // NOTE: Uninline. wmPartyFindCurSubTile(); @@ -6608,4 +6643,21 @@ void wmCarSetCurrentArea(int area) wmGenData.currentCarAreaId = area; } +void wmForceEncounter(int map, unsigned int flags) +{ + if ((wmForceEncounterFlags & (1 << 31)) != 0) { + return; + } + + wmForceEncounterMapId = map; + wmForceEncounterFlags = flags; + + // I don't quite understand the reason why locking needs one more flag. + if ((wmForceEncounterFlags & ENCOUNTER_FLAG_LOCK) != 0) { + wmForceEncounterFlags |= (1 << 31); + } else { + wmForceEncounterFlags &= ~(1 << 31); + } +} + } // namespace fallout diff --git a/src/worldmap.h b/src/worldmap.h index 901b0a61..20ea1c2a 100644 --- a/src/worldmap.h +++ b/src/worldmap.h @@ -229,6 +229,12 @@ typedef enum Map { MAP_IN_GAME_MOVIE1 = 149, } Map; +#define ENCOUNTER_FLAG_NO_CAR 0x1 +#define ENCOUNTER_FLAG_LOCK 0x2 +#define ENCOUNTER_FLAG_NO_ICON 0x4 +#define ENCOUNTER_FLAG_ICON_SP 0x8 +#define ENCOUNTER_FLAG_FADEOUT 0x10 + extern unsigned char* circleBlendTable; int wmWorldMap_init(); @@ -279,6 +285,7 @@ int wmTeleportToArea(int areaIdx); void wmSetPartyWorldPos(int x, int y); void wmCarSetCurrentArea(int area); +void wmForceEncounter(int map, unsigned int flags); } // namespace fallout