Compare commits

...
36 Commits
Author SHA1 Message Date
Mike Klaas d4bb81a919 fix windows 2026-04-19 14:59:51 -07:00
github-actions[bot] 7ea5265a01 chore: auto-format with clang-format 2026-04-19 21:50:37 +00:00
Mike Klaas 649d255ca2 cleanup 2026-04-19 14:48:33 -07:00
Mike Klaas 64af575bdf WIP 2026-04-19 14:07:07 -07:00
Mike KlaasandClaude Sonnet 4.6 03d703b734 Fix FO1 worldmap: area names, starting position, and initial visibility
- wmGetAreaName/wmGetAreaIdxName: fall back to city->name (stripping
  the NN_ prefix) when map.msg lacks 1500+ entries as in FO1
- wmGenDataInit/wmGenDataReset: start near Vault 13 (823,72) instead
  of FO2's Military Base default (173,122) in FO1 mode
- wmAreaInit: mark all start_state=On areas as visitedState=1 at init
  in FO1 mode; FO1 locations are known from game start unlike FO2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:31:09 -07:00
Mike KlaasandClaude Sonnet 4.6 19dfece38e Make pip-boy always available in FO1 mode
wmMapPipboyActive() checks the MOVIE_VSUIT flag which is never set in
FO1, causing pipboyOpen() to show a "not wearing pipboy" error instead
of opening. FO1's Vault Dweller always has the pip-boy.

Set pipboy_available_at_game_start = true unconditionally in FO1 mode
so the check is bypassed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:19:04 -07:00
Mike KlaasandClaude Sonnet 4.6 7c361af576 Fix aiSave/aiLoad aborting on missing proto for FO1 party member pids
FO1's party.txt (from fo1_shims) contains FO2 NPC pids whose protos
don't exist in FO1's DAT archive. protoGetProto() fails for these,
causing aiSave/aiLoad to return -1 and abort the save/load.

Change return -1 to continue so unresolvable party member pids are
silently skipped, matching the intent of the existing pid == -2 guard.

TODO: same root cause as loadsave — replace party.txt with FO1-only pids.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:18:59 -07:00
Mike KlaasandClaude Sonnet 4.6 0dc93bab88 Fix save/load proto copy skipping empty _proto_list_str paths (FO1)
fo1_shims/data/party.txt contains FO2 NPC pids absent from FO1's
critters.lst. _proto_list_str() returns 0 with an empty path for those
entries; the resulting directory-only path (e.g. "proto/critters/")
passes compat_access() and then causes fileCopyCompressed() or
_gzdecompress_file() to fail trying to open a directory.

Guard both _GameMap2Slot and _SlotMap2Game to skip any entry where
_proto_list_str() returns an empty path.

TODO: replace fo1_shims party.txt with a minimal FO1-only version and
remove these guards.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:18:54 -07:00
Mike KlaasandClaude Sonnet 4.6 64bb2af1fd Skip global scripts in FO1 mode
FO1 had no global script system. The gl*.int pattern matches glowgen.int
from FO1's DATA/SCRIPTS, which then fails at script_action with 'can't
match program to sid' errors on every map load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:49:07 -07:00
Mike KlaasandClaude Sonnet 4.6 cf56c0ecef Fix operator precedence bug in globalVarsRead
*variablesListPtr[N-1] was parsed as *(variablesListPtr[N-1]) due to []
having higher precedence than unary *. When N >= 2 this reads a garbage
pointer and crashes. FO1 maps have bare-integer GAM files (no = sign),
which triggered this path on every variable after the first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:45:35 -07:00
Mike KlaasandClaude Sonnet 4.6 161d114306 Update project plan: FO1in2 findings and worldmap art shim progress
Document FO1in2 worldmap asset approach: extended INTRFACE.LST (508
entries), custom FO1 worldmap tiles and city art, reliance on FO2 UI
FRMs. Note worldmap fix as in-progress pending test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:35:42 -07:00
Mike KlaasandClaude Sonnet 4.6 4823bd328f Fix save game failure when party member protos are in DAT archive (FO1)
_GameMap2Slot copies modified party member protos to the save slot.
In FO1, party member protos reside only in MASTER.DAT and are never
written to the patches directory, so fileCopyCompressed fails to open
the source. Skip protos that are absent from the physical filesystem
(they are unmodified and don't need to be saved).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:16:00 -07:00
Mike KlaasandClaude Sonnet 4.6 62f4dae07e Fix pip-boy crash when holodisk.txt is absent (FO1)
FO1 has no holodisks; holodiskInit() was returning -1 on missing
data\holodisk.txt, aborting pip-boy initialization. Treat missing
file as empty data (return 0) in FO1-compatible builds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:10:05 -07:00
Mike KlaasandClaude Sonnet 4.6 f05831648b Update FO1 plan: add open problem tracker table and recent fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 17:55:34 -07:00
Mike KlaasandClaude Sonnet 4.6 692cef5c98 Fix combat AI crash: make ai.txt *_end fields optional for FO1
FO1's ai.txt only has *_start and last_msg fields; FO2 has explicit
*_end fields. CE bailed on the first missing *_end, leaving gAiPackets
freed but non-null, then aiGetPacketByNum() returned the dangling
pointer causing a strlen crash in _combatai_msg via debugPrint.

Make all *_end fields optional: fall back to the next *_start (FO1-CE
convention where each *_start doubles as the inclusive end of the
preceding range). For hit_groin_end, read last_msg and store last_msg-1
so the existing ++ yields last_msg as the inclusive upper bound.

FO2 is unaffected: its ai.txt always supplies explicit *_end values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 17:55:08 -07:00
Mike KlaasandClaude Sonnet 4.6 d68790a096 Fix FO1 player art and character editor init
- art.cc: when hmwarr is absent from FO1's CRITTERS.LST, alias tribal
  slots to jumpsuit so _art_vault_guy_num resolves to hmjmps instead
  of critter index 0
- character_editor.cc: treat missing karmavar.txt / genrep.txt as empty
  (return 0) rather than fatal so the character editor opens in FO1 mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 17:50:30 -07:00
Mike Klaas 97cd20d21e Fix FO1 direct launch datafile fallback 2026-04-17 16:41:16 -07:00
Mike Klaas f4bb72b9e8 Add FO1 credits and quotes text shims 2026-04-17 16:27:12 -07:00
Mike Klaas f56a96ec9c Add FO1 enddeath shim from FO1in2 2026-04-17 16:15:20 -07:00
Mike Klaas 2504c64735 Add FO1 party shim from FO1in2 2026-04-17 16:08:01 -07:00
Mike Klaas 8a14b2b3ed Add FO1 city shim from FO1in2 2026-04-17 16:06:42 -07:00
Mike Klaas bdf5d253ba Add FO1 maps shim and restore worldmap map lookups 2026-04-17 16:05:18 -07:00
Mike Klaas 9d2462ef44 Use FO1in2 worldmap shim and skip missing FO1 map lookups 2026-04-17 15:33:01 -07:00
Mike Klaas be9169b234 Update FO1 plan progress with shim commit hash 2026-04-17 15:22:34 -07:00
Mike Klaas aa54707d60 Load FO1 worldmap shim asset from fo1_shims 2026-04-17 15:22:19 -07:00
Mike Klaas 61947f5fb9 Allow FO1 startup without enddeath.txt 2026-04-17 15:11:51 -07:00
Mike Klaas cead52bbd1 Add minimal FO1 worldmap fallback when worldmap.txt is missing 2026-04-17 15:11:42 -07:00
Mike Klaas f80a61d359 Update FO1 plan with ASan workflow and latest progress 2026-04-17 15:07:12 -07:00
Mike Klaas 34b3232422 Fail gracefully when FO1 worldmap.txt is missing 2026-04-17 15:05:34 -07:00
Mike Klaas a323a1292c Add FO1 fallback perk strings for proto init 2026-04-17 15:03:04 -07:00
Mike Klaas cf6b3e75f3 Use fallout2.cfg and f2_res.ini in FO1 mode 2026-04-17 14:59:30 -07:00
Mike Klaas a1dafba37a Update FO1 original-assets plan with progress status 2026-04-17 14:56:46 -07:00
Mike Klaas f264827cd6 Add Fallout 1 DAT backend and dfile integration 2026-04-17 14:53:33 -07:00
Mike Klaas 544eb92d8e Log missing datafile alerts to debug output 2026-04-17 14:51:01 -07:00
Mike Klaas 1e9b81dd17 Resolve config and data paths from install root 2026-04-17 14:47:03 -07:00
Mike Klaas b5c420274f Add FO1/FO2 variant startup and config policy 2026-04-17 14:42:34 -07:00
39 changed files with 12182 additions and 157 deletions
+12
View File
@@ -98,6 +98,12 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/file_find.h"
"src/file_utils.cc"
"src/file_utils.h"
"src/fo1db/fo1_assoc.cc"
"src/fo1db/fo1_assoc.h"
"src/fo1db/fo1_db.cc"
"src/fo1db/fo1_db.h"
"src/fo1db/fo1_lzss.cc"
"src/fo1db/fo1_lzss.h"
"src/font_manager.cc"
"src/font_manager.h"
"src/game_config.cc"
@@ -114,6 +120,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/game_movie.h"
"src/game_sound.cc"
"src/game_sound.h"
"src/game_variant.cc"
"src/game_variant.h"
"src/game_vars.h"
"src/game.cc"
"src/game.h"
@@ -322,6 +330,10 @@ if(WIN32)
)
endif()
if(MSVC)
target_compile_options(${EXECUTABLE_NAME} PRIVATE /EHsc)
endif()
if(WIN32)
target_link_libraries(${EXECUTABLE_NAME}
winmm
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
#
# enddeath.txt
#
# format of each line is:
#
# gvar, value, worldAreaKnown, worldAreaNotKnown, minLevel, %, narrator file
#
# Where:
#
# gvar is the GVAR_* number for the variable controlling the slide.
#
# value is the value that the gvar must be >= to for this slide
# to be shown
#
# worldAreaKnown is the # of a worldmap area that has to be known
# in order to get this one as an option
#
# worldAreaNotKnown is the # of a worldmap area that has to be *not*
# known in order to get this one as an option
#
# minLevel is the minimum player level in order to get this one as
# an option
#
# % is the % chance of a given line to occur
#
# narrator file is the base filename (no path path or extension) used
# for the narrator voice-over and subtitles. Maximum of 8 characters.
#
#####
# Generic Deaths
#####
-1, 0, -1, -1, 0, 20, NAR_3
-1, 0, -1, -1, 0, 20, NAR_4
-1, 0, -1, -1, 0, 20, NAR_5
-1, 0, -1, -1, 0, 20, NAR_6
-1, 0, 8, -1, 0, 20, NAR_8
#####
# Specific Deaths
#####
-1, 0, -1, 0, 200, 0, nar_dth3
-1, 0, -1, 0, 200, 0, nar_dth4
-1, 0, -1, 0, 200, 0, nar_dth5
-1, 0, -1, 0, 200, 0, nar_dth6
-1, 0, -1, 0, 200, 0, nar_dth7
-1, 0, -1, 0, 200, 0, nar_dth8
-1, 0, -1, 0, 200, 0, nar_dth9
# Special Radiation Death
491, 999, 3, -1, 150, 0, NAR_7
#####
# Joke Deaths
#####
-1, 0, -1, -1, 10, 1, nar_jdd1
-1, 0, -1, -1, 10, 1, nar_jdd3
-1, 0, -1, -1, 10, 1, nar_jdd4
-1, 0, -1, -1, 10, 1, nar_jdd5
-1, 0, -1, -1, 10, 1, nar_jdd6
File diff suppressed because it is too large Load Diff
+104
View File
@@ -0,0 +1,104 @@
;
; Party Member valid option data
;
;
; Warning: Adding a party member here means you must also add it to party.h!
;
;
; Note: Party Member level-up float messages are found in game\misc.msg!
;
[Party Member 0] ; Player -- Not actually used!
party_member_pid=16777216
area_attack_mode=always, sometimes, be_sure, be_careful, be_absolutely_sure
attack_who=whomever_attacking_me, strongest, weakest, whomever, closest
best_weapon=no_pref, melee, melee_over_ranged, ranged_over_melee, ranged, unarmed, unarmed_over_thrown, random
chem_use=clean, stims_when_hurt_little, stims_when_hurt_lots, sometimes, anytime, always
distance=stay_close, charge, snipe, on_your_own, stay
run_away_mode=none, coward, finger_hurts, bleeding, not_feeling_good, tourniquet, never
disposition=none, custom, coward, defensive, aggressive, berserk
level_minimum=0
level_up_every=0
level_pids=-1
[Party Member 1] ; pMIan_PID
party_member_pid=16777292
area_attack_mode=always, sometimes, be_sure, be_careful, be_absolutely_sure
attack_who=whomever_attacking_me, strongest, weakest, whomever, closest
best_weapon=no_pref, melee, ranged_over_melee, ranged
chem_use=clean, stims_when_hurt_little, stims_when_hurt_lots, sometimes, anytime
distance=stay_close, charge, snipe, on_your_own, stay
run_away_mode=none, not_feeling_good, tourniquet, never
disposition=none, custom, defensive, aggressive, berserk
level_minimum=3
level_up_every=3
level_pids=16777530,16777531,16777532,16777533
[Party Member 2] ; pMDog_PID
party_member_pid=16777338
area_attack_mode=
attack_who=whomever_attacking_me, strongest, weakest, whomever, closest
best_weapon=unarmed
chem_use=clean
distance=stay_close, charge, on_your_own, stay
run_away_mode=none, not_feeling_good, tourniquet, never
disposition=none, custom, aggressive, berserk
level_minimum=4
level_up_every=3
level_pids=16777542,16777543,16777544,16777545
[Party Member 3] ; pMTycho_PID
party_member_pid=16777426
area_attack_mode=always, sometimes, be_sure, be_careful, be_absolutely_sure
attack_who=whomever_attacking_me, strongest, weakest, whomever, closest
best_weapon=no_pref, ranged_over_melee, ranged, unarmed
chem_use=clean, stims_when_hurt_little, stims_when_hurt_lots, sometimes, anytime
distance=stay_close, charge, snipe, on_your_own, stay
run_away_mode=none, not_feeling_good, tourniquet, never
disposition=none, custom, defensive, aggressive, berserk
level_minimum=4
level_up_every=3
level_pids=16777534,16777535,16777536,16777537
[Party Member 4] ; pMKatja_PID
party_member_pid=16777518
area_attack_mode=sometimes, be_sure, be_careful, be_absolutely_sure
attack_who=whomever_attacking_me, weakest, whomever, closest
best_weapon=no_pref, melee, melee_over_ranged, ranged_over_melee, ranged, unarmed
chem_use=stims_when_hurt_little, stims_when_hurt_lots
distance=stay_close, charge, on_your_own, stay
run_away_mode=none, coward, finger_hurts, bleeding, not_feeling_good
disposition=none, custom, defensive, aggressive, coward
level_minimum=4
level_up_every=2
level_pids=16777538,16777539,16777540,16777541
[Party Member 5] ; pMTandi_PID
party_member_pid=16777279
area_attack_mode=always, sometimes, be_sure, be_careful, be_absolutely_sure
attack_who=whomever_attacking_me, strongest, weakest, whomever, closest
best_weapon=no_pref, melee, melee_over_ranged, ranged_over_melee, ranged, unarmed
chem_use=clean, stims_when_hurt_little, stims_when_hurt_lots, sometimes, anytime, always
distance=stay_close, charge, snipe, on_your_own, stay
run_away_mode=none, coward, finger_hurts, bleeding, not_feeling_good, tourniquet, never
disposition=none, custom, coward, defensive, aggressive, berserk
level_minimum=0
level_up_every=0
level_pids=-1
[Party Member 6] ; pMMrHandyC_PID
party_member_pid=16777529
area_attack_mode=always, sometimes, be_sure, be_careful, be_absolutely_sure
attack_who=whomever_attacking_me, strongest, weakest, whomever, closest
best_weapon=no_pref, melee, melee_over_ranged, ranged_over_melee, ranged, unarmed
chem_use=clean
distance=stay_close, charge, snipe, on_your_own, stay
run_away_mode=none, coward, finger_hurts, bleeding, not_feeling_good, tourniquet, never
disposition=none, custom, coward, defensive, aggressive, berserk
level_minimum=0
level_up_every=0
level_pids=-1
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+402
View File
@@ -0,0 +1,402 @@
;
; Fallout team quotes
;
; The fonts used to display this file are the reverse of the
; credits.txt file. In other words, the font used to display
; titles in credits.txt (the lines with the '@') is the font
; used to display these quotes. Putting an '@' at the start of
; a line in this file will use the font used to display the
; names in the credits.txt file (currently 104).
;
; Max line length with the current font (103) is about 63.
; Anything longer than that won't be displayed, as it won't fit
; on the screen.
;
; All lines are automatically centered on the display.
;
; See top of credits.txt for additional info.
;
@Quotes from the Fallout team:
(Press ESC if you don't want to see some raw language)
I asked for anatomically correct, but not naked!
- Tim C.
I appreciate the fact that ?? is only speaking to us when spoken to.
- Leonard
So now that you are past the learning curve,
you can blow up people much faster, right?
- Tim C.
So how fast can _you_ draw a bunny?
- JT
I didn't do it yet today because I was waiting for Leonard's head.
- Scott C.
Did you see Steven Spielberg go by?
- Tim C.
No, I was taking a piss.
- Mark Harrison
Blueberry muffins are one of Man's crowning achievements.
- JT
Out of my way. I'm a fucking genius.
- Jason
Dismemberment is just inherently funny.
- JT
I had a wooden one, but Spence broke it.
- Jesse
You can say penis, but you can't say cocksucker.
- Leonard
It's fun to make fun of people who are stupid.
- JT
Crap is the hardest stuff to make.
- Michael
No, I haven't shot anyone today.
- JT
But 15 _is_ an even number!
- Leonard
But 15 isn't divisible by 2.
- Tim.
You know what I mean. I meant 15 is prime.
- Leonard
I'll be going on to death at the end of the day.
- Michael
I'm tired of looking at that peanut.
- Tim C.
I only wash my nose. It's the only thing that gets dirty.
- Fred
I'm going to jiujitsu his ass.
- Leonard
I'll have to remember or else I'll forget.
- Jesse
You never know what wacky things I'm going to do with my hand.
- Tim C.
Sorry I'm late, but Mary needed marbles and I had lost mine.
- Chris T.
It's fun hitting other people's balls.
- Tim C.
I'm going to kill this game.
- Mark Morgan
Do you want help putting on your bikini?
- Fred to Tim C.
I like to get them in the butt.
- Tim C.
I need more peasants!
- Fred
If I hadn't had sex, I would have called Tom or Jesse.
- Scotty
Did you know you can talk to people who don't have heads?
- Jesse
We'll have a little bowl hair-cut kid so you
can't tell if it's a boy or a girl when you're killing it.
- Leonard
It's just a flag!
- Scotty
I'll tell them to give me more exploding peasants.
- Fred
Do you need help changing his pants?
- Leonard
I was getting home and getting ready to get some,
and I remembered it was a holiday.
- Scotty
I'd rather drink a bottle of urine than THAT!
- Scotty
Rusty wants any body he can have.
- Gus
Nick's are worn and falling apart and orange.
- Jesse
I never let work interfere with my cooking.
- Tim C.
I don't want you to stop making them,
because it's the only time I get any.
- Scotty
I vote for Hate.
- Jason
That reminds me of Toxic Avenger, the unrated version.
- Scotty
You know, we should lock down sex.
- Chris T.
How do I kill people? I need some dead bodies.
- Robert H.
Don't quote me on that.
- Leonard
I had a weird dream involving Scotty,
and I don't even want to remember it.
- Jesse
What?!
- Scotty
I don't know what we're going to do about it.
I guess we're going to have to start cutting heads.
- Tim C.
Well, that's true and that's untrue.
- Gus
But I like being a woman!
- Scotty
I'm used to playing with myself.
- Scotty
That's the essence of art -- making it all work together.
- Leonard
Did I tell you I broke my thingy?
- Dave
I want more randomness in my randomology.
- Robert H.
I can't finish the AI without drugs.
- Tim C.
Please do not make fun of my poop.
- Tim C.
I know how to tell males from females now.
- Tim C.
Excuse me, my butt did that.
- Jesse
I'm going to kick you in the butt.
- Robert H. to Tim C.
Because it's soft as a baby's behind.
- Jesse.
How do you know?
- Scotty
I can do Troy. I used to be his monkey.
- Chris T.
I had one once, but I pumped it too hard and I broke it.
- Scotty
I stripped and wiped.
- Scotty
Why can't I be a lesbian?
- Shanna
I wonder what that thing hanging there is.
- Shanna
I need more intelligence.
- Nick
At the same time, I want to make sure that
I don't have dead people stand up.
- Tim C.
I'd model dust, if I could.
- Jason
I'm on top now.
- Evan
I do a good cow.
- Greg B.
If he's on top of me, I'm gonna be pissed!
- Shanna
If I was a girl, I'd do him.
- Evan
You won't be here when I get off.
- Mike Krueger
Well, I was playing with it last night but I
couldn't get anything to come out!
- Mike Krueger
Stop shaking my monkey!
- Matt Murakami
I like people who say poop.
- Savina
It's not unstable, it's just buggy.
There's a difference.
- Chris J.
Instead of sexual urges, they get
sucking blood urges.
- Scotty
I like little boys who wear skirts.
- Mike Krueger
His iguana-stick is way too long.
- Leonard
Fallout is "By Americans, For Americans" and
the rest of the world can just get stuffed.
- Scotty
All the Brotherhood do it. They know how.
- Nick
I'm going to teach you to script,
that'll be torture.
- Nick
His little thing looks better anyway.
- Evan
He TOLD me to touch it!
- Nick
I'm really scared of the people on the Internet.
They're all dumb. Except for that one guy.
- Scotty
I haven't touched it for two months.
- Nick
Well, you better start touching it.
- Evan
You can never be paranoid enough.
- Scotty
You should be 14-18 when you blow the Master.
- Jess
Oh, wait a minute, talk to Jesse.
Jesse usually has lots of drugs.
- Scotty
I burnt my part, for one thing.
- Mark H.
I'm usually a lot happier about the
game when I'm not playing it.
- Scotty
You gotta take it easy, this is my first time.
- Dave Hendee
It's really quick to get it up.
- Mark H.
I can find the hole without looking now.
- Ed H.
I've actually had it both ways, but I'm not sure why...
- Dan S.
I can touch it everywhere and it seems to work.
- Scotty
OK, I was sleeping with Ian...
- Gus
What is the sound of one man working on another?
- Dan S.
I hate being fucked.
- Gus
I think our game has a high potential
for being very violent.
- Jason A.
I'm not going to be beaten by cheesecake.
- Tim C.
I have new respect for crowbars.
(pause)
Well, at least it isn't about sex.
- Scotty
That thing is a little too
big to stick right there.
- Matt G.
I'm not going to do the cows.
- Dan S.
The cows are glad to hear that.
- Robert H.
But I love faggot pizza.
- Scotty
It won't crash any more. In theory.
- Mark H.
I wasn't wearing anything
the first time I won the game.
- Tim C.
@FIN
+8
View File
@@ -9,6 +9,7 @@
#include "debug.h"
#include "draw.h"
#include "game.h"
#include "game_variant.h"
#include "memory.h"
#include "object.h"
#include "proto.h"
@@ -231,6 +232,13 @@ int artInit()
critterFileNames += 13;
}
// FO1 does not have a tribal look; use jumpsuit as the default player art.
if (gameVariantIsFallout1() && _art_vault_guy_num == 0) {
_art_vault_guy_num = _art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_MALE];
_art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_MALE] = _art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_MALE];
_art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_FEMALE] = _art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_FEMALE];
}
for (int critterIndex = 0; critterIndex < gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {
if (!fileReadString(string, sizeof(string), stream)) {
break;
+2 -2
View File
@@ -6988,7 +6988,7 @@ static int karmaInit()
File* stream = fileOpen("data\\karmavar.txt", "rt");
if (stream == nullptr) {
return -1;
return 0;
}
char string[256];
@@ -7087,7 +7087,7 @@ static int genericReputationInit()
File* stream = fileOpen("data\\genrep.txt", "rt");
if (stream == nullptr) {
return -1;
return 0;
}
char string[256];
+5
View File
@@ -610,6 +610,8 @@ static bool characterSelectorWindowRenderFace()
// 0x4A7EA8
static bool characterSelectorWindowRenderStats()
{
static char emptyString[] = "";
char* str;
char text[260];
int length;
@@ -828,6 +830,9 @@ static bool characterSelectorWindowRenderStats()
y += vh;
str = skillGetName(skills[index]);
if (str == nullptr) {
str = emptyString;
}
strcpy(text, str);
length = fontGetStringWidth(text);
+39 -14
View File
@@ -420,29 +420,50 @@ int aiInit()
if (!configGetInt(&config, sectionEntry->key, "outline_color", &(ai->outline_color))) goto err;
if (!configGetInt(&config, sectionEntry->key, "chance", &(ai->chance))) goto err;
if (!configGetInt(&config, sectionEntry->key, "run_start", &(ai->run.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "run_end", &(ai->run.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "move_start", &(ai->move.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "move_end", &(ai->move.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "attack_start", &(ai->attack.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "attack_end", &(ai->attack.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "miss_start", &(ai->miss.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "miss_end", &(ai->miss.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_head_start", &(ai->hit[HIT_LOCATION_HEAD].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_head_end", &(ai->hit[HIT_LOCATION_HEAD].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_arm_start", &(ai->hit[HIT_LOCATION_LEFT_ARM].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_arm_end", &(ai->hit[HIT_LOCATION_LEFT_ARM].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_arm_start", &(ai->hit[HIT_LOCATION_RIGHT_ARM].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_arm_end", &(ai->hit[HIT_LOCATION_RIGHT_ARM].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_torso_start", &(ai->hit[HIT_LOCATION_TORSO].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_torso_end", &(ai->hit[HIT_LOCATION_TORSO].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_leg_start", &(ai->hit[HIT_LOCATION_RIGHT_LEG].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_leg_end", &(ai->hit[HIT_LOCATION_RIGHT_LEG].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_leg_start", &(ai->hit[HIT_LOCATION_LEFT_LEG].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_leg_end", &(ai->hit[HIT_LOCATION_LEFT_LEG].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_eyes_start", &(ai->hit[HIT_LOCATION_EYES].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_eyes_end", &(ai->hit[HIT_LOCATION_EYES].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_groin_start", &(ai->hit[HIT_LOCATION_GROIN].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_groin_end", &(ai->hit[HIT_LOCATION_GROIN].end))) goto err;
// *_end fields are optional: FO1's ai.txt omits them and uses each *_start as
// the inclusive upper bound of the preceding range (FO1-CE convention).
// FO2's ai.txt always supplies explicit *_end values.
if (!configGetInt(&config, sectionEntry->key, "run_end", &(ai->run.end)))
ai->run.end = ai->move.start;
if (!configGetInt(&config, sectionEntry->key, "move_end", &(ai->move.end)))
ai->move.end = ai->attack.start;
if (!configGetInt(&config, sectionEntry->key, "attack_end", &(ai->attack.end)))
ai->attack.end = ai->miss.start;
if (!configGetInt(&config, sectionEntry->key, "miss_end", &(ai->miss.end)))
ai->miss.end = ai->hit[HIT_LOCATION_HEAD].start;
if (!configGetInt(&config, sectionEntry->key, "hit_head_end", &(ai->hit[HIT_LOCATION_HEAD].end)))
ai->hit[HIT_LOCATION_HEAD].end = ai->hit[HIT_LOCATION_LEFT_ARM].start;
if (!configGetInt(&config, sectionEntry->key, "hit_left_arm_end", &(ai->hit[HIT_LOCATION_LEFT_ARM].end)))
ai->hit[HIT_LOCATION_LEFT_ARM].end = ai->hit[HIT_LOCATION_RIGHT_ARM].start;
if (!configGetInt(&config, sectionEntry->key, "hit_right_arm_end", &(ai->hit[HIT_LOCATION_RIGHT_ARM].end)))
ai->hit[HIT_LOCATION_RIGHT_ARM].end = ai->hit[HIT_LOCATION_TORSO].start;
if (!configGetInt(&config, sectionEntry->key, "hit_torso_end", &(ai->hit[HIT_LOCATION_TORSO].end)))
ai->hit[HIT_LOCATION_TORSO].end = ai->hit[HIT_LOCATION_RIGHT_LEG].start;
if (!configGetInt(&config, sectionEntry->key, "hit_right_leg_end", &(ai->hit[HIT_LOCATION_RIGHT_LEG].end)))
ai->hit[HIT_LOCATION_RIGHT_LEG].end = ai->hit[HIT_LOCATION_LEFT_LEG].start;
if (!configGetInt(&config, sectionEntry->key, "hit_left_leg_end", &(ai->hit[HIT_LOCATION_LEFT_LEG].end)))
ai->hit[HIT_LOCATION_LEFT_LEG].end = ai->hit[HIT_LOCATION_EYES].start;
if (!configGetInt(&config, sectionEntry->key, "hit_eyes_end", &(ai->hit[HIT_LOCATION_EYES].end)))
ai->hit[HIT_LOCATION_EYES].end = ai->hit[HIT_LOCATION_GROIN].start;
// groin: FO1 uses last_msg as the inclusive end; CE expects the value before
// the ++ below, so store last_msg - 1 so that ++ yields last_msg.
if (!configGetInt(&config, sectionEntry->key, "hit_groin_end", &(ai->hit[HIT_LOCATION_GROIN].end))) {
int lastMsg;
if (!configGetInt(&config, sectionEntry->key, "last_msg", &lastMsg)) goto err;
ai->hit[HIT_LOCATION_GROIN].end = lastMsg - 1;
}
ai->hit[HIT_LOCATION_GROIN].end++;
@@ -577,8 +598,11 @@ int aiLoad(File* stream)
int pid = gPartyMemberPids[index];
if (pid != -1 && PID_TYPE(pid) == OBJ_TYPE_CRITTER) {
Proto* proto;
// TODO: fo1_shims party.txt contains FO2 pids with no valid proto
// in FO1. Skip them rather than abort; fix by supplying a minimal
// FO1-only party.txt.
if (protoGetProto(pid, &proto) == -1) {
return -1;
continue;
}
AiPacket* ai = aiGetPacketByNum(proto->critter.aiPacket);
@@ -598,8 +622,9 @@ int aiSave(File* stream)
int pid = gPartyMemberPids[index];
if (pid != -1 && PID_TYPE(pid) == OBJ_TYPE_CRITTER) {
Proto* proto;
// TODO: same FO1 party.txt issue as aiLoad — skip invalid pids.
if (protoGetProto(pid, &proto) == -1) {
return -1;
continue;
}
AiPacket* ai = aiGetPacketByNum(proto->critter.aiPacket);
+29
View File
@@ -4,6 +4,8 @@
#include <stdlib.h>
#include <string.h>
#include "debug.h"
#include "game_variant.h"
#include "platform_compat.h"
#include "xfile.h"
@@ -37,6 +39,22 @@ static int gFileReadProgressChunkSize;
// 0x673044
static FileList* gFileListHead;
static bool dbShouldUseFo1Fallback(const char* path)
{
if (path == nullptr) {
return false;
}
const char* baseName = strrchr(path, '/');
if (baseName == nullptr) {
baseName = strrchr(path, '\\');
}
baseName = baseName != nullptr ? baseName + 1 : path;
return compat_stricmp(baseName, "master.dat") == 0 || compat_stricmp(baseName, "critter.dat") == 0;
}
// Opens file database.
//
// Returns -1 if [filePath1] was specified, but could not be opened by the
@@ -48,6 +66,17 @@ int dbOpen(const char* filePath1, const char* filePath2)
{
if (filePath1 != nullptr) {
if (!xbaseOpen(filePath1)) {
if (dbShouldUseFo1Fallback(filePath1)) {
gameVariantForceFallout1();
debugPrint("dbOpen: retrying %s with Fallout 1 backend.\n", filePath1);
if (xbaseOpen(filePath1)) {
if (filePath2 != nullptr) {
xbaseOpen(filePath2);
}
return 0;
}
}
return -1;
}
}
+215 -5
View File
@@ -7,8 +7,11 @@
#include <algorithm>
#include "fpattern_windows.h"
#include <fpattern/fpattern.h>
#include "debug.h"
#include "fo1db/fo1_db.h"
#include "game_variant.h"
#include "platform_compat.h"
namespace fallout {
@@ -38,6 +41,8 @@ namespace fallout {
#define DFILE_HAS_COMPRESSED_UNGETC (0x10)
static int dbaseFindEntryByFilePath(const void* file, const void* entryName);
static DBase* dbaseOpenFo1(const char* filePath);
static bool dbaseShouldUseFo1Backend(const char* filePath);
static DFile* dfileOpenInternal(DBase* dbase, const char* filename, const char* mode, DFile* dfile);
static int dfileReadCharInternal(DFile* stream);
static bool dfileReadCompressed(DFile* stream, void* ptr, size_t size);
@@ -52,6 +57,11 @@ DBase* dbaseOpen(const char* filePath)
FILE* stream = compat_fopen(filePath, "rb");
if (stream == nullptr) {
if (dbaseShouldUseFo1Backend(filePath)) {
gameVariantForceFallout1();
return dbaseOpenFo1(filePath);
}
return nullptr;
}
@@ -153,11 +163,15 @@ DBase* dbaseOpen(const char* filePath)
return dbase;
err:
dbaseClose(dbase);
fclose(stream);
if (dbaseShouldUseFo1Backend(filePath)) {
gameVariantForceFallout1();
return dbaseOpenFo1(filePath);
}
return nullptr;
}
@@ -176,6 +190,20 @@ bool dbaseClose(DBase* dbase)
curr = next;
}
if (dbase->backend == DBASE_BACKEND_FO1) {
if (dbase->fo1Database != INVALID_DATABASE_HANDLE && dbase->fo1Database != nullptr) {
db_close(dbase->fo1Database);
}
if (dbase->path != nullptr) {
free(dbase->path);
}
memset(dbase, 0, sizeof(*dbase));
free(dbase);
return true;
}
if (dbase->entries != nullptr) {
for (int index = 0; index < dbase->entriesLength; index++) {
DBaseEntry* entry = &(dbase->entries[index]);
@@ -201,10 +229,25 @@ bool dbaseClose(DBase* dbase)
// 0x4E5308
bool dbaseFindFirstEntry(DBase* dbase, DFileFindData* findFileData, const char* pattern)
{
// .dat files always have windows style paths
findFileData->backend = dbase->backend;
if (dbase->backend == DBASE_BACKEND_FO1) {
db_select(dbase->fo1Database);
findFileData->fo1FileNames = nullptr;
findFileData->fo1FileNamesLength = db_get_file_list(pattern, &findFileData->fo1FileNames, nullptr, 0);
if (findFileData->fo1FileNamesLength <= 0) {
return false;
}
strcpy(findFileData->fileName, findFileData->fo1FileNames[0]);
strcpy(findFileData->pattern, pattern);
findFileData->index = 0;
return true;
}
for (int index = 0; index < dbase->entriesLength; index++) {
DBaseEntry* entry = &(dbase->entries[index]);
if (fpattern_windows_match(pattern, entry->path)) {
if (fpattern_match(pattern, entry->path)) {
strcpy(findFileData->fileName, entry->path);
strcpy(findFileData->pattern, pattern);
findFileData->index = index;
@@ -218,9 +261,20 @@ bool dbaseFindFirstEntry(DBase* dbase, DFileFindData* findFileData, const char*
// 0x4E53A0
bool dbaseFindNextEntry(DBase* dbase, DFileFindData* findFileData)
{
if (dbase->backend == DBASE_BACKEND_FO1) {
int index = findFileData->index + 1;
if (index >= findFileData->fo1FileNamesLength) {
return false;
}
strcpy(findFileData->fileName, findFileData->fo1FileNames[index]);
findFileData->index = index;
return true;
}
for (int index = findFileData->index + 1; index < dbase->entriesLength; index++) {
DBaseEntry* entry = &(dbase->entries[index]);
if (fpattern_windows_match(findFileData->pattern, entry->path)) {
if (fpattern_match(findFileData->pattern, entry->path)) {
strcpy(findFileData->fileName, entry->path);
findFileData->index = index;
return true;
@@ -233,6 +287,12 @@ bool dbaseFindNextEntry(DBase* dbase, DFileFindData* findFileData)
// 0x4E541C
bool dbaseFindClose(DBase* dbase, DFileFindData* findFileData)
{
if (dbase->backend == DBASE_BACKEND_FO1 && findFileData->fo1FileNames != nullptr) {
db_free_file_list(&findFileData->fo1FileNames, nullptr);
findFileData->fo1FileNames = nullptr;
findFileData->fo1FileNamesLength = 0;
}
return true;
}
@@ -241,6 +301,11 @@ bool dbaseFindClose(DBase* dbase, DFileFindData* findFileData)
// 0x4E5424
long dfileGetSize(DFile* stream)
{
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_filelength(stream->fo1File);
}
return stream->entry->uncompressedSize;
}
@@ -253,6 +318,34 @@ int dfileClose(DFile* stream)
int rc = 0;
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
rc = db_fclose(stream->fo1File);
DFile* curr = stream->dbase->dfileHead;
DFile* prev = nullptr;
while (curr != nullptr) {
if (curr == stream) {
break;
}
prev = curr;
curr = curr->next;
}
if (curr != nullptr) {
if (prev == nullptr) {
stream->dbase->dfileHead = stream->next;
} else {
prev->next = stream->next;
}
}
memset(stream, 0, sizeof(*stream));
free(stream);
return rc;
}
if (stream->entry->compressed == 1) {
if (inflateEnd(stream->decompressionStream) != Z_OK) {
rc = -1;
@@ -310,6 +403,32 @@ DFile* dfileOpen(DBase* dbase, const char* filePath, const char* mode)
assert(filePath); // dfile.c, 296
assert(mode); // dfile.c, 297
if (dbase->backend == DBASE_BACKEND_FO1) {
if (mode[0] != 'r') {
return nullptr;
}
db_select(dbase->fo1Database);
DB_FILE* fo1File = db_fopen(filePath, mode);
if (fo1File == nullptr) {
return nullptr;
}
DFile* dfile = (DFile*)malloc(sizeof(*dfile));
if (dfile == nullptr) {
db_fclose(fo1File);
return nullptr;
}
memset(dfile, 0, sizeof(*dfile));
dfile->backend = DBASE_BACKEND_FO1;
dfile->dbase = dbase;
dfile->fo1File = fo1File;
dfile->next = dbase->dfileHead;
dbase->dfileHead = dfile;
return dfile;
}
return dfileOpenInternal(dbase, filePath, mode, nullptr);
}
@@ -321,6 +440,10 @@ int dfilePrintFormattedArgs(DFile* stream, const char* format, va_list args)
assert(stream); // "stream", "dfile.c", 368
assert(format); // "format", "dfile.c", 369
if (stream->backend == DBASE_BACKEND_FO1) {
return -1;
}
return -1;
}
@@ -334,6 +457,11 @@ int dfileReadChar(DFile* stream)
{
assert(stream); // "stream", "dfile.c", 384
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_fgetc(stream->fo1File);
}
if ((stream->flags & DFILE_EOF) != 0 || (stream->flags & DFILE_ERROR) != 0) {
return -1;
}
@@ -363,6 +491,11 @@ char* dfileReadString(char* string, int size, DFile* stream)
assert(size); // "n", "dfile.c", 408
assert(stream); // "stream", "dfile.c", 409
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_fgets(string, size, stream->fo1File);
}
if ((stream->flags & DFILE_EOF) != 0 || (stream->flags & DFILE_ERROR) != 0) {
return nullptr;
}
@@ -407,6 +540,11 @@ int dfileWriteChar(int ch, DFile* stream)
{
assert(stream); // "stream", "dfile.c", 437
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_fputc(ch, stream->fo1File);
}
return -1;
}
@@ -418,6 +556,11 @@ int dfileWriteString(const char* string, DFile* stream)
assert(string); // "s", "dfile.c", 448
assert(stream); // "stream", "dfile.c", 449
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_fputs(string, stream->fo1File);
}
return -1;
}
@@ -429,6 +572,11 @@ size_t dfileRead(void* ptr, size_t size, size_t count, DFile* stream)
assert(ptr); // "ptr", "dfile.c", 499
assert(stream); // "stream", dfile.c, 500
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_fread(ptr, size, count, stream->fo1File);
}
if ((stream->flags & DFILE_EOF) != 0 || (stream->flags & DFILE_ERROR) != 0) {
return 0;
}
@@ -480,6 +628,11 @@ size_t dfileWrite(const void* ptr, size_t size, size_t count, DFile* stream)
assert(ptr); // "ptr", "dfile.c", 538
assert(stream); // "stream", "dfile.c", 539
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_fwrite(ptr, size, count, stream->fo1File);
}
return count - 1;
}
@@ -490,6 +643,11 @@ int dfileSeek(DFile* stream, long offset, int origin)
{
assert(stream); // "stream", "dfile.c", 569
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_fseek(stream->fo1File, offset, origin);
}
if ((stream->flags & DFILE_ERROR) != 0) {
return 1;
}
@@ -599,6 +757,11 @@ long dfileTell(DFile* stream)
{
assert(stream); // "stream", "dfile.c", 654
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_ftell(stream->fo1File);
}
return stream->position;
}
@@ -609,6 +772,12 @@ void dfileRewind(DFile* stream)
{
assert(stream); // "stream", "dfile.c", 664
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
db_rewind(stream->fo1File);
return;
}
dfileSeek(stream, 0, SEEK_SET);
stream->flags &= ~DFILE_ERROR;
@@ -621,9 +790,34 @@ int dfileEof(DFile* stream)
{
assert(stream); // "stream", "dfile.c", 685
if (stream->backend == DBASE_BACKEND_FO1) {
db_select(stream->dbase->fo1Database);
return db_feof(stream->fo1File);
}
return stream->flags & DFILE_EOF;
}
static DBase* dbaseOpenFo1(const char* filePath)
{
DB_DATABASE* fo1Database = db_init(filePath, nullptr, nullptr, 0);
if (fo1Database == INVALID_DATABASE_HANDLE) {
return nullptr;
}
DBase* dbase = (DBase*)malloc(sizeof(*dbase));
if (dbase == nullptr) {
db_close(fo1Database);
return nullptr;
}
memset(dbase, 0, sizeof(*dbase));
dbase->backend = DBASE_BACKEND_FO1;
dbase->path = compat_strdup(filePath);
dbase->fo1Database = fo1Database;
return dbase;
}
// The [bsearch] comparison callback, which is used to find [DBaseEntry] for
// specified [filePath].
//
@@ -636,6 +830,22 @@ static int dbaseFindEntryByFilePath(const void* file, const void* entryName)
return compat_stricmp(filePath, entry->path);
}
static bool dbaseShouldUseFo1Backend(const char* filePath)
{
if (filePath == nullptr) {
return false;
}
const char* baseName = strrchr(filePath, '/');
if (baseName == nullptr) {
baseName = strrchr(filePath, '\\');
}
baseName = baseName != nullptr ? baseName + 1 : filePath;
return compat_stricmp(baseName, "master.dat") == 0 || compat_stricmp(baseName, "critter.dat") == 0;
}
// 0x4E5D9C
static DFile* dfileOpenInternal(DBase* dbase, const char* filePath, const char* mode, DFile* dfile)
{
+18
View File
@@ -12,9 +12,18 @@ namespace fallout {
typedef struct DBase DBase;
typedef struct DBaseEntry DBaseEntry;
typedef struct DFile DFile;
typedef struct DB_DATABASE DB_DATABASE;
typedef struct DB_FILE DB_FILE;
typedef enum DBaseBackend {
DBASE_BACKEND_FO2,
DBASE_BACKEND_FO1,
} DBaseBackend;
// A representation of .DAT file.
typedef struct DBase {
DBaseBackend backend;
// The path of .DAT file that this structure represents.
char* path;
@@ -29,6 +38,8 @@ typedef struct DBase {
// The head of linked list of open file handles.
DFile* dfileHead;
DB_DATABASE* fo1Database;
} DBase;
typedef struct DBaseEntry {
@@ -41,6 +52,7 @@ typedef struct DBaseEntry {
// A handle to open entry in .DAT file.
typedef struct DFile {
DBaseBackend backend;
DBase* dbase;
DBaseEntry* entry;
int flags;
@@ -90,6 +102,8 @@ typedef struct DFile {
// [DFile]s are stored in [DBase] in reverse order, so it's actually a
// previous opened file, not next.
DFile* next;
DB_FILE* fo1File;
} DFile;
typedef struct DFileFindData {
@@ -108,6 +122,10 @@ typedef struct DFileFindData {
// [dbaseFindNextEntry] succeed so that subsequent calls to [dbaseFindNextEntry]
// knows where to start search from.
int index;
char** fo1FileNames;
int fo1FileNamesLength;
DBaseBackend backend;
} DFileFindData;
DBase* dbaseOpen(const char* filename);
+5
View File
@@ -18,6 +18,7 @@
#include "game_mouse.h"
#include "game_movie.h"
#include "game_sound.h"
#include "game_variant.h"
#include "input.h"
#include "map.h"
#include "memory.h"
@@ -1013,6 +1014,10 @@ int endgameDeathEndingInit()
stream = fileOpen("data\\enddeath.txt", "rt");
if (stream == nullptr) {
if (gameVariantIsFallout1()) {
debugPrint("\nendgameDeathEndingInit: data\\enddeath.txt is missing in FO1 mode, continuing without death-ending table.");
return 0;
}
return -1;
}
File diff suppressed because it is too large Load Diff
+70
View File
@@ -0,0 +1,70 @@
#ifndef FALLOUT_FO1DB_ASSOC_H_
#define FALLOUT_FO1DB_ASSOC_H_
#include <stdio.h>
#include "fo1_db.h"
namespace fallout {
typedef void*(assoc_malloc_func)(size_t size);
typedef void*(assoc_realloc_func)(void* ptr, size_t newSize);
typedef void(assoc_free_func)(void* ptr);
typedef int(assoc_load_func)(FILE* stream, void* buffer, size_t size, int flags);
typedef int(assoc_save_func)(FILE* stream, void* buffer, size_t size, int flags);
typedef int(assoc_load_func_db)(DB_FILE* stream, void* buffer, size_t size, int flags);
typedef int(assoc_save_func_db)(DB_FILE* stream, void* buffer, size_t size, int flags);
typedef struct assoc_func_list {
assoc_load_func* loadFunc;
assoc_save_func* saveFunc;
assoc_load_func_db* loadFuncDB;
assoc_save_func_db* saveFuncDB;
assoc_load_func* newLoadFunc;
} assoc_func_list;
// A tuple containing individual key-value pair of an assoc array.
typedef struct assoc_pair {
char* name;
void* data;
} assoc_pair;
// A collection of key/value pairs.
//
// The keys in assoc array are always strings. Internally pairs are kept sorted
// by the key. Both keys and values are copied when new entry is added to
// assoc array. For this reason the size of the value's type is provided during
// assoc array initialization.
typedef struct assoc_array {
int init_flag;
// The number of key/value pairs in the array.
int size;
// The capacity of key/value pairs in [entries] array.
int max;
// The size of the values in bytes.
size_t datasize;
// IO callbacks.
assoc_func_list load_save_funcs;
// The array of key-value pairs.
assoc_pair* list;
} assoc_array;
int assoc_init(assoc_array* a, int n, size_t datasize, assoc_func_list* assoc_funcs);
int assoc_resize(assoc_array* a, int n);
int assoc_free(assoc_array* a);
int assoc_search(assoc_array* a, const char* name);
int assoc_insert(assoc_array* a, const char* name, const void* data);
int assoc_delete(assoc_array* a, const char* name);
int assoc_copy(assoc_array* dst, assoc_array* src);
int assoc_load(FILE* fp, assoc_array* a, int flags);
int assoc_save(FILE* fp, assoc_array* a, int flags);
void assoc_register_mem(assoc_malloc_func* malloc_func, assoc_realloc_func* realloc_func, assoc_free_func* free_func);
} // namespace fallout
#endif /* FALLOUT_FO1DB_ASSOC_H_ */
+3020
View File
File diff suppressed because it is too large Load Diff
+125
View File
@@ -0,0 +1,125 @@
#ifndef FALLOUT_FO1DB_DB_H_
#define FALLOUT_FO1DB_DB_H_
#include <stddef.h>
namespace fallout {
#define INVALID_DATABASE_HANDLE ((DB_DATABASE*)-1)
#define db_init fo1db_init
#define db_select fo1db_select
#define db_current fo1db_current
#define db_total fo1db_total
#define db_close fo1db_close
#define db_dir_entry fo1db_dir_entry
#define db_read_to_buf fo1db_read_to_buf
#define db_fopen fo1db_fopen
#define db_fclose fo1db_fclose
#define db_fread fo1db_fread
#define db_fgetc fo1db_fgetc
#define db_fgets fo1db_fgets
#define db_fwrite fo1db_fwrite
#define db_fputc fo1db_fputc
#define db_fputs fo1db_fputs
#define db_fseek fo1db_fseek
#define db_ftell fo1db_ftell
#define db_rewind fo1db_rewind
#define db_feof fo1db_feof
#define db_get_file_list fo1db_get_file_list
#define db_free_file_list fo1db_free_file_list
#define db_filelength fo1db_filelength
typedef struct DB_FILE DB_FILE;
typedef struct DB_DATABASE DB_DATABASE;
typedef struct dir_entry_s {
int flags;
int offset;
int length;
int field_C;
} dir_entry;
typedef void db_read_callback();
typedef void*(db_malloc_func)(size_t size);
typedef char*(db_strdup_func)(const char* string);
typedef void(db_free_func)(void* ptr);
DB_DATABASE* db_init(const char* datafile, const char* datafile_path, const char* patches_path, int show_cursor);
int db_select(DB_DATABASE* db_handle);
DB_DATABASE* db_current();
int db_total();
int db_close(DB_DATABASE* db_handle);
void db_exit();
int db_dir_entry(const char* filePath, dir_entry* de);
int db_read_to_buf(const char* filePath, unsigned char* ptr);
DB_FILE* db_fopen(const char* filename, const char* mode);
int db_fclose(DB_FILE* stream);
size_t db_fread(void* buf, size_t size, size_t count, DB_FILE* stream);
int db_fgetc(DB_FILE* stream);
int db_ungetc(int ch, DB_FILE* stream);
char* db_fgets(char* str, size_t size, DB_FILE* stream);
size_t db_fwrite(const void* buf, size_t size, size_t count, DB_FILE* stream);
int db_fputc(int ch, DB_FILE* stream);
int db_fputs(const char* s, DB_FILE* stream);
int db_fseek(DB_FILE* stream, long offset, int origin);
long db_ftell(DB_FILE* stream);
void db_rewind(DB_FILE* stream);
int db_freadByte(DB_FILE* stream, unsigned char* c);
int db_freadShort(DB_FILE* stream, unsigned short* s);
int db_freadInt(DB_FILE* stream, int* i);
int db_freadLong(DB_FILE* stream, unsigned long* l);
int db_freadFloat(DB_FILE* stream, float* q);
int db_fwriteByte(DB_FILE* stream, unsigned char c);
int db_fwriteShort(DB_FILE* stream, unsigned short s);
int db_fwriteInt(DB_FILE* stream, int i);
int db_fwriteLong(DB_FILE* stream, unsigned long l);
int db_fwriteFloat(DB_FILE* stream, float q);
int db_freadByteCount(DB_FILE* stream, unsigned char* c, int count);
int db_freadShortCount(DB_FILE* stream, unsigned short* s, int count);
int db_freadIntCount(DB_FILE* stream, int* i, int count);
int db_freadLongCount(DB_FILE* stream, unsigned long* l, int count);
int db_freadFloatCount(DB_FILE* stream, float* q, int count);
int db_fwriteByteCount(DB_FILE* stream, unsigned char* c, int count);
int db_fwriteShortCount(DB_FILE* stream, unsigned short* s, int count);
int db_fwriteIntCount(DB_FILE* stream, int* i, int count);
int db_fwriteLongCount(DB_FILE* stream, unsigned long* l, int count);
int db_fwriteFloatCount(DB_FILE* stream, float* q, int count);
int db_fprintf(DB_FILE* stream, const char* format, ...);
int db_feof(DB_FILE* stream);
int db_get_file_list(const char* filespec, char*** filelist, char*** desclist, int desclen);
void db_free_file_list(char*** file_list, char*** desclist);
long db_filelength(DB_FILE* stream);
void db_register_mem(db_malloc_func* malloc_func, db_strdup_func* strdup_func, db_free_func* free_func);
void db_register_callback(db_read_callback* callback, size_t threshold);
void db_enable_hash_table();
int db_reset_hash_tables();
int db_add_hash_entry(const char* path, int sep);
int db_freadUInt8(DB_FILE* stream, unsigned char* valuePtr);
int db_freadInt8(DB_FILE* stream, char* valuePtr);
int db_freadUInt16(DB_FILE* stream, unsigned short* valuePtr);
int db_freadInt16(DB_FILE* stream, short* valuePtr);
int db_freadUInt32(DB_FILE* stream, unsigned int* valuePtr);
int db_freadInt32(DB_FILE* stream, int* valuePtr);
int db_freadUInt8List(DB_FILE* stream, unsigned char* arr, int count);
int db_freadInt8List(DB_FILE* stream, char* arr, int count);
int db_freadInt16List(DB_FILE* stream, short* arr, int count);
int db_freadInt32List(DB_FILE* stream, int* arr, int count);
int db_freadBool(DB_FILE* stream, bool* valuePtr);
int db_fwriteUInt8(DB_FILE* stream, unsigned char value);
int db_fwriteInt8(DB_FILE* stream, char value);
int db_fwriteUInt16(DB_FILE* stream, unsigned short value);
int db_fwriteInt16(DB_FILE* stream, short value);
int db_fwriteUInt32(DB_FILE* stream, unsigned int value);
int db_fwriteInt32(DB_FILE* stream, int value);
int db_fwriteUInt8List(DB_FILE* stream, unsigned char* arr, int count);
int db_fwriteInt8List(DB_FILE* stream, char* arr, int count);
int db_fwriteInt16List(DB_FILE* stream, short* arr, int count);
int db_fwriteInt32List(DB_FILE* stream, int* arr, int count);
int db_fwriteBool(DB_FILE* stream, bool value);
} // namespace fallout
#endif /* FALLOUT_FO1DB_DB_H_ */

Some files were not shown because too many files have changed in this diff Show More