Files
fs2open.github.com/code/mission/missionparse.cpp
T
2008-04-06 18:53:01 +00:00

8115 lines
249 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
/*
* $Logfile: /Freespace2/code/Mission/MissionParse.cpp $
* $Revision: 2.178.2.47 $
* $Date: 2008-01-17 07:43:23 $
* $Author: Goober5000 $
*
* main upper level code for parsing stuff
*
* $Log: not supported by cvs2svn $
* Revision 2.178.2.46 2007/12/29 21:03:46 karajorma
* Since you can't actually save a mission without this entry and FS2 crashes without it this probably shouldn't be optional.
*
* Revision 2.178.2.45 2007/12/08 20:07:54 karajorma
* Add the use alt as callsign option.
* Add a few typo and comment fixes while I'm at it
*
* Revision 2.178.2.44 2007/11/28 21:00:34 karajorma
* Change the way that score is parsed from mission files to make it easier on developers.
*
* Revision 2.178.2.43 2007/11/24 04:33:49 wmcoolmon
* Various minor sign/casting fixes
*
* Revision 2.178.2.42 2007/11/21 07:27:47 Goober5000
* add Wing Commander Saga's fiction viewer
*
* Revision 2.178.2.41 2007/11/20 04:58:22 Goober5000
* fix ship-type-destroyed
*
* Revision 2.178.2.40 2007/11/19 18:11:01 Goober5000
* remove warning... properly this time
*
* Revision 2.178.2.39 2007/11/18 06:57:06 Goober5000
* gak -- stupid mistake
*
* Revision 2.178.2.38 2007/11/14 13:49:31 karajorma
* Fix peristent variables in missions played from the techroom
*
* Revision 2.178.2.37 2007/09/02 18:52:51 Goober5000
* fix for #1455 plus a bit of cleanup
*
* Revision 2.178.2.36 2007/09/02 02:07:44 Goober5000
* added fixes for #1415 and #1483, made sure every read_file_text had a corresponding setjmp, and sync'd the parse error messages between HEAD and stable
*
* Revision 2.178.2.35 2007/07/28 21:17:54 Goober5000
* make the parse object array dynamic; also made the docking bitstrings dynamic
*
* Revision 2.178.2.34 2007/07/23 16:08:27 Kazan
* Autopilot updates, minor misc fixes, working MSVC2005 project files
*
* Revision 2.178.2.33 2007/05/26 12:08:19 Goober5000
* when importing FS1 missions, account for the shuffled Head-TP4
*
* Revision 2.178.2.32 2007/04/09 18:23:08 karajorma
* Fix for Mantis 1312
*
* Revision 2.178.2.31 2007/03/23 06:25:31 karajorma
* That probably shouldn't be an assertion
*
* Revision 2.178.2.30 2007/02/25 03:58:06 Goober5000
* use dynamic memory instead of a static buffer for ship-specific replacement textures
*
* Revision 2.178.2.29 2007/02/21 01:43:32 Goober5000
* remove duplicate model texture replacement
*
* Revision 2.178.2.28 2007/02/20 04:19:22 Goober5000
* the great big duplicate model removal commit
*
* Revision 2.178.2.27 2007/02/13 01:45:56 Goober5000
* fix a hideous bug with the knossos special warp stuff
* (how did this ever actually work correctly?)
*
* Revision 2.178.2.26 2007/02/09 04:53:27 Goober5000
* merge the WCS screaming feature into the 3.6.9 (3.6.10?) branch
*
* Revision 2.178.2.25 2007/02/08 07:39:34 Goober5000
* fix two bugs:
* --default ship flags in the iff_defs table were not correctly translated from parse flags to ship/object flags
* --ships were created with default allowed comm orders regardless of which team they were on
*
* Revision 2.178.2.24 2007/02/03 23:16:41 Goober5000
* fix an old subtle bug
*
* Revision 2.178.2.23 2007/01/07 12:18:09 taylor
* safety check to make sure that we don't end up with non-player-usable weapons in the weaponselect pool (Mantis bug #1196)
*
* Revision 2.178.2.22 2006/11/25 06:36:59 Goober5000
* holy crap, this had the potential to mark the locked-true sexp as known-false, which would seriously screw a lot of stuff up
*
* Revision 2.178.2.21 2006/11/15 04:02:28 Goober5000
* a much better fix for built-in arrival messages... it needs a new feature to fully work for custom wings though
*
* Revision 2.178.2.20 2006/11/13 23:09:59 karajorma
* Dragoniz3r's fix for builtin messages not being sent when custom named wings arrive.
*
* Revision 2.178.2.19 2006/10/27 21:39:27 taylor
* updated/fixed modelanim code
*
* Revision 2.178.2.18 2006/10/24 13:34:33 taylor
* remove a basically useless debug message
* fix parse_init() so that it doesn't absolutely have to init sexps (doesn't need them when getting basic info, and causes memory fragmentation otherwise)
* fix multiplayer mission info getting to just get basic info (fixes a couple of minor multi issues that were already fixed in single)
*
* Revision 2.178.2.17 2006/10/09 20:50:02 Goober5000
* betterized some things with mission arrivals and departures
*
* Revision 2.178.2.16 2006/10/09 05:25:07 Goober5000
* make sexp nodes dynamic
*
* Revision 2.178.2.15 2006/09/11 01:06:20 taylor
* fixes for stuff_string() bounds checking
* compiler warning and stict compiling fixes
*
* Revision 2.178.2.14 2006/08/27 18:12:41 taylor
* make Species_info[] and Asteroid_info[] dynamic
*
* Revision 2.178.2.13 2006/08/19 21:46:02 Goober5000
* disable duplicate model texture replace
*
* Revision 2.178.2.12 2006/08/19 04:38:46 taylor
* maybe optimize the (PI/2), (PI*2) and (RAND_MAX/2) stuff a little bit
*
* Revision 2.178.2.11 2006/08/06 18:47:12 Goober5000
* add the multiple background feature
* --Goober5000
*
* Revision 2.178.2.10 2006/07/30 20:00:47 Kazan
* resolve 1018 and an interface problem in fred2's ship editor
*
* Revision 2.178.2.9 2006/07/20 00:41:26 Goober5000
* add WCS screaming stuff to RC branch
* --Goober5000
*
* Revision 2.178.2.8 2006/07/17 00:10:05 Goober5000
* stage 2 of animation fix (add base frame time to each ship)
* --Goober5000
*
* Revision 2.178.2.7 2006/07/13 06:11:48 Goober5000
* * better formatting for substitute music options
* * better handling of all special FSO comment tags
* --Goober5000
*
* Revision 2.178.2.6 2006/07/06 06:04:45 Goober5000
* fix subtle change from retail... dunno if it's a bug, but best to stay on the safe side
* --Goober5000
*
* Revision 2.178.2.5 2006/07/06 04:06:00 Goober5000
* 1) complete (almost) changeover to reorganized texture mapping system
* 2) finally fix texture animation; textures now animate at the correct speed
* --Goober5000
*
* Revision 2.178.2.4 2006/07/04 07:42:09 Goober5000
* --in preparation for fixing an annoying animated texture bug, reorganize the various texture structs and glow point structs and clarify several parts of the texture code :P
* --this breaks animated glow maps, and animated regular maps still aren't fixed, but these will be remedied shortly
* --Goober5000
*
* Revision 2.178.2.3 2006/07/01 00:30:51 Goober5000
* fix the "Game of TAG" bug; hopefully this won't unfix something else
* --Goober5000
*
* Revision 2.178.2.2 2006/06/07 20:47:53 karajorma
* Allow Change-Ship-Class to work with ships which haven't arrived yet (For BHX)
*
* Revision 2.178.2.1 2006/06/04 01:03:13 Goober5000
* add fighterbay restriction code
* --Goober5000
*
* Revision 2.178 2006/05/31 03:05:42 Goober5000
* some cosmetic changes in preparation for bay arrival/departure code
* --Goober5000
*
* Revision 2.177 2006/05/21 22:57:30 Goober5000
* fix for Mantis #750
* --Goober5000
*
* Revision 2.176 2006/05/21 03:58:58 Goober5000
* fix Mantis #834
* --Goober5000
*
* Revision 2.175 2006/05/20 02:03:01 Goober5000
* fix for Mantis #755, plus make the missionlog #defines uniform
* --Goober5000
*
* Revision 2.174 2006/05/19 04:47:40 Goober5000
* formatting and clarification, plus fix for Mantis #911
* --Goober5000
*
* Revision 2.173 2006/05/14 15:54:16 karajorma
* Fixes for the Targetable-as-bomb error from Mantis 0000910 (amongst others)
*
* Revision 2.172 2006/05/13 07:29:52 taylor
* OpenGL envmap support
* newer OpenGL extension support
* add GL_ARB_texture_rectangle support for non-power-of-2 textures as interface graphics
* add cubemap reading and writing support to DDS loader
* fix bug in DDS loader that made compressed images with mipmaps use more memory than they really required
* add support for a default envmap named "cubemap.dds"
* new mission flag "$Environment Map:" to use a pre-existing envmap
* minor cleanup of compiler warning messages
* get rid of wasteful math from gr_set_proj_matrix()
* remove extra gr_set_*_matrix() calls from starfield.cpp as there was no longer a reason for them to be there
* clean up bmpman flags in reguards to cubemaps and render targets
* disable D3D envmap code until it can be upgraded to current level of code
* remove bumpmap code from OpenGL stuff (sorry but it was getting in the way, if it was more than copy-paste it would be worth keeping)
* replace gluPerspective() call with glFrustum() call, it's a lot less math this way and saves the extra function call
*
* Revision 2.171 2006/04/20 06:32:07 Goober5000
* proper capitalization according to Volition
*
* Revision 2.170 2006/04/16 11:58:11 karajorma
* Some how managed to fail to commit the primary-locked and secondary-locked ship flags.
* No idea how. Oh well here they are.
*
* Revision 2.169 2006/04/05 16:12:41 karajorma
* Changes to support the new Enable/Disable-Builtin-Messages SEXP
*
* Revision 2.168 2006/03/24 07:38:35 wmcoolmon
* New subobject animation stuff and Lua functions.
*
* Revision 2.167 2006/03/01 00:46:11 Goober5000
* more FS1 import tweaks
*
* Revision 2.166 2006/02/26 23:23:30 wmcoolmon
* Targetable as bomb SEXPs and dialog stuff; made invulnerable an object flag in both FRED and FS2.
*
* Revision 2.165 2006/02/26 08:06:57 taylor
* another non-linear campaign fix, this one affected PVs
*
* Revision 2.164 2006/02/26 05:32:38 Goober5000
* guess this is still needed
*
* Revision 2.163 2006/02/25 22:06:57 Goober5000
* import tweak
*
* Revision 2.162 2006/02/24 07:34:07 taylor
* fix custom loading screens that I manage to break yet again
* add a "MaxFPS" registry/ini option to specify a FPS cap, useful if you can't make use of v-sync for some reason
*
* Revision 2.161 2006/02/17 08:36:16 Goober5000
* bother those variables
*
* Revision 2.160 2006/02/17 08:13:53 Goober5000
* fix subtle bug
* --Goober5000
*
* Revision 2.159 2006/02/16 05:18:48 taylor
* all just getting basic info in parse_mission_info(), this fixes several parsing/warning issues that can show up
* from going in the techroom and some similar FRED2 issues as this stuff doesn't get reset between get_mission_info() calls.
* this also should make the mission simulator screen load a bit faster
*
* Revision 2.158 2006/02/12 05:23:16 Goober5000
* additional fixes and enhancements for substitute music
* --Goober5000
*
* Revision 2.157 2006/02/12 01:27:47 Goober5000
* more cool work on importing, music handling, etc.
* --Goober5000
*
* Revision 2.156 2006/02/11 21:24:05 Goober5000
* this isn't needed now
* --Goober5000
*
* Revision 2.155 2006/02/11 07:31:55 Goober5000
* two things I missed before
* --Goober5000
*
* Revision 2.154 2006/02/11 02:58:23 Goober5000
* yet more various and sundry fixes
* --Goober5000
*
* Revision 2.153 2006/02/11 01:02:28 Goober5000
* more fixage
* --Goober5000
*
* Revision 2.152 2006/02/11 00:13:55 Goober5000
* more FS1 import goodness
* --Goober5000
*
* Revision 2.151 2006/02/10 23:33:39 Goober5000
* fixed an old Volition bug unmasked by my parse changes
* --Goober5000
*
* Revision 2.150 2006/02/06 02:06:01 wmcoolmon
* Various fixes; very beginnings of Directives scripting support
*
* Revision 2.149 2006/02/02 08:12:47 Goober5000
* ugh, more ship/wing fixage
* --Goober5000
*
* Revision 2.148 2006/02/02 05:48:17 Goober5000
* fixed a FRED problem brought on by the parse reordering
* --Goober5000
*
* Revision 2.147 2006/01/31 17:19:20 taylor
* oh the shame (/me imagines the big grin on Goober's face right now :p)
*
* Revision 2.146 2006/01/31 15:44:29 taylor
* we need to skip to the start of the string, not the end
*
* Revision 2.145 2006/01/31 04:24:25 Goober5000
* stuff
*
* Revision 2.144 2006/01/31 02:02:33 Goober5000
* fix a potential problem with replacement range
* --Goober5000
*
* Revision 2.143 2006/01/31 01:53:37 Goober5000
* update FSM import for FSPort v3.0
* --Goober5000
*
* Revision 2.142 2006/01/30 19:35:41 taylor
* fix parsing of reinforcements
*
* Revision 2.141 2006/01/30 06:30:03 taylor
* dynamic starfield bitmaps
*
* Revision 2.140 2006/01/28 02:59:55 Goober5000
* fixed the bug where wings couldn't be initially docked to stuff
* --Goober5000
*
* Revision 2.139 2006/01/26 04:01:58 Goober5000
* spelling
*
* Revision 2.138 2006/01/14 19:54:55 wmcoolmon
* Special shockwave and moving capship bugfix, (even more) scripting stuff, slight rearrangement of level management functions to facilitate scripting access.
*
* Revision 2.137 2006/01/14 06:25:11 taylor
* make sure rship gets initialized before use
*
* Revision 2.136 2006/01/14 05:36:06 Goober5000
* tweaky tweak
* --Goober5000
*
* Revision 2.135 2006/01/13 03:31:09 Goober5000
* übercommit of custom IFF stuff :)
*
* Revision 2.134 2006/01/04 08:19:21 taylor
* fixes for regular texture replacement
*
* Revision 2.133 2005/12/29 08:08:36 wmcoolmon
* Codebase commit, most notably including objecttypes.tbl
*
* Revision 2.132 2005/12/29 00:56:45 phreak
* loading code for flipped briefing icons.
*
* Revision 2.131 2005/12/28 22:31:03 taylor
* unused variable (to make GCC shut up)
*
* Revision 2.130 2005/12/08 15:11:29 taylor
* a few game_busy() changes
*
* Revision 2.129 2005/12/06 02:57:57 taylor
* just to be on the safe side
*
* Revision 2.128 2005/11/24 08:46:10 Goober5000
* * cleaned up mission_do_departure
* * fixed a hidden crash (array index being -1; would only
* be triggered for ships w/o subspace drives under certain conditions)
* * removed finding a new fighterbay target because it might screw up missions
* * improved clarity, code flow, and readability :)
* * added custom AI flag for disabling warpouts if navigation subsystem fails
* --Goober5000
*
* Revision 2.127 2005/11/21 02:43:37 Goober5000
* change from "setting" to "profile"; this way makes more sense
* --Goober5000
*
* Revision 2.126 2005/11/21 00:46:12 Goober5000
* add ai_settings.tbl
* --Goober5000
*
* Revision 2.125 2005/11/18 07:53:27 Goober5000
* made arrival invalidation more efficient
* (probably only a small speedup, but meh)
* --Goober5000
*
* Revision 2.124 2005/10/31 09:09:41 Goober5000
* bit of tweakage
* --Goober5000
*
* Revision 2.123 2005/10/30 20:42:45 Goober5000
* fix the support ship spawning bug
* --Goober5000
*
* Revision 2.122 2005/10/30 20:03:39 taylor
* add a bunch of Assert()'s and NULL checks to either help debug or avoid errors
* fix Mantis bug #381
* fix a small issue with the starfield bitmap removal sexp since it would read one past the array size
*
* Revision 2.121 2005/10/29 22:09:29 Goober5000
* multiple ship docking implemented for initially docked ships
* --Goober5000
*
* Revision 2.120 2005/10/22 22:25:23 Goober5000
* made Cmdline_UseNewAI toggle the mission flag, plus fixed a few other flag-related potential bugs
* --Goober5000
*
* Revision 2.119 2005/10/10 17:21:05 taylor
* remove NO_NETWORK
*
* Revision 2.118 2005/10/09 08:03:20 wmcoolmon
* New SEXP stuff
*
* Revision 2.117 2005/10/02 23:01:17 Goober5000
* removed a redundant action
* --Goober5000
*
* Revision 2.116 2005/09/29 04:26:08 Goober5000
* parse fixage
* --Goober5000
*
* Revision 2.115 2005/09/27 08:09:13 Goober5000
* fix debug warning
* --Goober5000
*
* Revision 2.114 2005/09/27 02:36:57 Goober5000
* clarification
* --Goober5000
*
* Revision 2.113 2005/09/25 18:44:51 taylor
* fix Subsys_status leak, wasn't a problem in game but can be touchy on exit
*
* Revision 2.112 2005/09/25 08:25:15 Goober5000
* Okay, everything should now work again. :p Still have to do a little more with the asteroids.
* --Goober5000
*
* Revision 2.111 2005/09/25 05:13:07 Goober5000
* hopefully complete species upgrade
* --Goober5000
*
* Revision 2.110 2005/09/24 02:24:29 Goober5000
* bah
* --Goober5000
*
* Revision 2.109 2005/09/24 01:50:08 Goober5000
* a bunch of support ship bulletproofing
* --Goober5000
*
* Revision 2.108 2005/08/31 08:09:50 Goober5000
* All wings will now form on their respective leaders, not just certain starting wings.
* If this breaks anything, the old code is commented out. You know who to yell at. ;)
* --Goober5000
*
* Revision 2.107 2005/08/31 07:54:32 Goober5000
* Wings will now form on their leader, not necessarily the player. This is to
* account for cases where the player is not the leader of his wing. It has the
* side effect of causing wings that arrive later in the mission to form on *their*
* leader rather than the player, but this is probably the preferred behavior (at
* least judging from Mike Kulas's comment of 5/9/98). This does create a
* difference from retail AI, but it should be only cosmetic, since ships only form
* on a wing until they are ordered (via comm or sexp) to do something else.
* --Goober5000
*
* Revision 2.106 2005/08/26 00:56:17 taylor
* send out multi pings during level load to prevent timeouts on slower computers (does not break network compatibility with older, or retail, clients/servers)
*
* Revision 2.105 2005/08/23 09:18:07 Goober5000
* ensure init/reset of goals works cleanly
* --Goober5000
*
* Revision 2.104 2005/08/23 07:49:24 Goober5000
* removed a thing I apparently forgot to remove on 2005/07/12
* --Goober5000
*
* Revision 2.103 2005/08/22 22:24:22 Goober5000
* some tweaks to parselo, plus ensure that Unicode files don't crash
* --Goober5000
*
* Revision 2.102 2005/08/21 06:19:31 Goober5000
* clarified the comment on my original move of the wing departure information assignment
* --Goober5000
*
* Revision 2.101 2005/07/25 08:22:00 Goober5000
* more bugs and tweaks
* --Goober5000
*
* Revision 2.100 2005/07/25 05:24:16 Goober5000
* cleaned up some command line and mission flag stuff
* --Goober5000
*
* Revision 2.99 2005/07/25 03:13:25 Goober5000
* various code cleanups, tweaks, and fixes; most notably the MISSION_FLAG_USE_NEW_AI
* should now be added to all places where it is needed (except the turret code, which I still
* have to to review)
* --Goober5000
*
* Revision 2.98 2005/07/22 10:18:39 Goober5000
* CVS header tweaks
* --Goober5000
*
* Revision 2.97 2005/07/13 03:25:59 Goober5000
* remove PreProcDefine #includes in FS2
* --Goober5000
*
* Revision 2.96 2005/07/13 02:32:22 Goober5000
* this seems like it should be handled by the code already...
* let's remove it and see what happens :p
* --Goober5000
*
* Revision 2.95 2005/07/13 02:30:53 Goober5000
* removed autopilot #define
* --Goober5000
*
* Revision 2.94 2005/07/13 02:01:29 Goober5000
* fixed a bunch of "issues" caused by me with the species stuff
* --Goober5000
*
* Revision 2.93 2005/07/13 00:44:22 Goober5000
* improved species support and removed need for #define
* --Goober5000
*
* Revision 2.92 2005/05/12 17:49:14 taylor
* use vm_malloc(), vm_free(), vm_realloc(), vm_strdup() rather than system named macros
* fixes various problems and is past time to make the switch
*
* Revision 2.91 2005/04/28 05:29:30 wmcoolmon
* Removed FS2_DEMO defines that looked like they wouldn't cause the universe to collapse
*
* Revision 2.90 2005/04/28 01:35:26 wmcoolmon
* stuff_byte to stuff_ubyte; does the same thing, but with a better name.
*
* Revision 2.89 2005/04/21 15:59:41 taylor
* sexp to swap the skybox in mission, not tested in FRED, hopefully I didn't mess this up too much
*
* Revision 2.88 2005/04/12 02:07:00 phreak
* ambient_light_level added to the mission structure
*
* Revision 2.87 2005/04/05 05:53:19 taylor
* s/vector/vec3d/g, better support for different compilers (Jens Granseuer)
*
* Revision 2.86 2005/03/27 12:28:33 Goober5000
* clarified max hull/shield strength names and added ship guardian thresholds
* --Goober5000
*
* Revision 2.85 2005/03/25 06:57:35 wmcoolmon
* Big, massive, codebase commit. I have not removed the old ai files as the ones I uploaded aren't up-to-date (But should work with the rest of the codebase)
*
* Revision 2.84 2005/03/08 03:50:22 Goober5000
* edited for language ;)
* --Goober5000
*
* Revision 2.83 2005/03/03 06:05:29 wmcoolmon
* Merge of WMC's codebase. "Features and bugs, making Goober say "Grr!", as release would be stalled now for two months for sure"
*
* Revision 2.82 2005/03/02 21:24:45 taylor
* more network/inferno goodness for Windows, takes care of a few warnings too
*
* Revision 2.81 2005/03/01 06:55:41 bobboau
* oh, hey look I've commited something :D
* animation system, weapon models detail box alt-tab bug, probly other stuff
*
* Revision 2.80 2005/01/31 23:27:54 taylor
* merge with Linux/OSX tree - p0131-2
*
* Revision 2.79 2005/01/26 21:42:19 Goober5000
* important bugfix
* --Goober5000
*
* Revision 2.78 2005/01/21 08:56:50 taylor
* make Subsys_status dynamic but allocate in blocks for speed and to help prevent memory fragmentation
*
* Revision 2.77 2005/01/18 00:14:37 Goober5000
* clarified a bunch of sexp stuff and fixed a bug
* --Goober5000
*
* Revision 2.76 2005/01/11 21:38:50 Goober5000
* multiple ship docking :)
* don't tell anyone yet... check the SCP internal
* --Goober500
*
* Revision 2.75 2005/01/03 01:05:08 phreak
* made custom loading screens work if only one resolution has been specified.
*
* Revision 2.74 2004/12/22 21:48:21 taylor
* safety check against very large missions
*
* Revision 2.73 2004/12/15 17:32:19 Goober5000
* move wing name initialization to ship_level_init
* --Goober5000
*
* Revision 2.72 2004/12/14 14:46:13 Goober5000
* allow different wing names than ABGDEZ
* --Goober5000
*
* Revision 2.71 2004/11/21 11:31:46 taylor
* newline fix (got tired of seeing this)
*
* Revision 2.70 2004/10/31 02:04:34 Goober5000
* added Knossos_warp_ani_used flag for taylor
* --Goober5000
*
* Revision 2.69 2004/10/15 09:21:55 Goober5000
* cleaned up some sexp stuff and added wing capability to kamikaze sexp
* --Goober5000
*
* Revision 2.68 2004/10/12 22:47:14 Goober5000
* added toggle-subsystem-scanning ship flag
* --Goober5000
*
* Revision 2.67 2004/10/12 07:34:45 Goober5000
* added contrail speed threshold
* --Goober5000
*
* Revision 2.66 2004/10/11 22:29:25 Goober5000
* added the no-bank ship flag (which works) and the affected-by-gravity flag
* (which won't work until I implement gravity points)
* --Goober5000
*
* Revision 2.65 2004/09/01 00:22:36 Goober5000
* fixed a small bug in the custom hitpoints mod
* --Goober5000
*
* Revision 2.64 2004/07/27 20:09:12 Kazan
* making another bug a little bit more dead inside :D
*
* Revision 2.63 2004/07/26 20:47:37 Kazan
* remove MCD complete
*
* Revision 2.62 2004/07/12 16:32:54 Kazan
* MCD - define _MCD_CHECK to use memory tracking
*
* Revision 2.61 2004/07/01 01:12:32 bobboau
* implemented index buffered background bitmaps,
* OGL people you realy should get this implemented
*
* Revision 2.60 2004/05/11 02:52:12 Goober5000
* completed the FRED import conversion stuff that I started ages ago
* --Goober5000
*
* Revision 2.59 2004/05/10 10:51:53 Goober5000
* made primary and secondary banks quite a bit more friendly... added error-checking
* and reorganized a bunch of code
* --Goober5000
*
* Revision 2.58 2004/05/10 08:03:30 Goober5000
* fixored the handling of no lasers and no engines... the tests should check the ship,
* not the object
* --Goober5000
*
* Revision 2.57 2004/05/03 21:22:22 Kazan
* Abandon strdup() usage for mod list processing - it was acting odd and causing crashing on free()
* Fix condition where alt_tab_pause() would flipout and trigger failed assert if game minimizes during startup (like it does a lot during debug)
* Nav Point / Auto Pilot code (All disabled via #ifdefs)
*
* Revision 2.56 2004/04/13 05:42:45 Goober5000
* fixed the custom hitpoints subsystem bug
* --Goober5000
*
* Revision 2.55 2004/03/05 09:02:06 Goober5000
* Uber pass at reducing #includes
* --Goober5000
*
* Revision 2.54 2004/02/07 00:48:53 Goober5000
* made FS2 able to account for subsystem mismatches between ships.tbl and the
* model file - e.g. communication vs. communications
* --Goober5000
*
* Revision 2.53 2004/02/05 14:31:44 Goober5000
* fixed a few random bugs
* --Goober5000
*
* Revision 2.52 2004/02/04 08:41:04 Goober5000
* made code more uniform and simplified some things,
* specifically shield percentage and quadrant stuff
* --Goober5000
*
* Revision 2.51 2004/01/30 07:39:08 Goober5000
* whew - I just went through all the code I ever added (or at least, that I could
* find that I commented with a Goober5000 tag) and added a bunch of Asserts
* and error-checking
* --Goober5000
*
* Revision 2.50 2004/01/14 06:34:07 Goober5000
* made set-support-ship number align with general FS convention
* --Goober5000
*
* Revision 2.49 2003/10/23 23:48:03 phreak
* added code for the mission parser to recognize user defined skyboxes
*
* Revision 2.48 2003/10/16 16:38:16 Kazan
* couple more types in species_defs.cpp, also finished up "Da Species Upgrade"
*
* Revision 2.47 2003/10/15 22:03:25 Kazan
* Da Species Update :D
*
* Revision 2.46 2003/10/08 06:32:06 argv
* Negative repeat count means repeat indefinitely. A trivial change, it should not break anything.
*
* Revision 2.45 2003/09/30 04:05:09 Goober5000
* updated FRED to import FS1 default weapons loadouts as well as missions
* --Goober5000
*
* Revision 2.44 2003/09/28 21:22:59 Goober5000
* added the option to import FSM missions, added a replace function, spruced
* up my $player, $rank, etc. code, and fixed encrypt being misspelled as 'encrpyt'
* --Goober5000
*
* Revision 2.43 2003/09/13 06:02:06 Goober5000
* clean rollback of all of argv's stuff
* --Goober5000
*
* Revision 2.40 2003/09/06 20:41:52 wmcoolmon
* Added "+Subsystem Repair Ceiling:" after "+Hull Repair Ceiling:" (formerly "+Support Repair Ceiling:"
*
* Revision 2.39 2003/09/06 19:09:24 wmcoolmon
* Added optional mission parameter "+Support Repair Ceiling", which sets what percentage a support ship can repair a ship's hull to.
*
* Revision 2.38 2003/09/05 10:41:48 Goober5000
* bah - fixed a dumb bug
* --Goober5000
*
* Revision 2.37 2003/09/05 04:25:29 Goober5000
* well, let's see here...
*
* * persistent variables
* * rotating gun barrels
* * positive/negative numbers fixed
* * sexps to trigger whether the player is controlled by AI
* * sexp for force a subspace jump
*
* I think that's it :)
* --Goober5000
*
* Revision 2.36 2003/05/09 23:52:01 phreak
* changed around code in parse_mission_info() to support user-defined loading screens
*
* Revision 2.35 2003/04/29 01:03:23 Goober5000
* implemented the custom hitpoints mod
* --Goober5000
*
* Revision 2.34 2003/03/25 07:03:30 Goober5000
* added beginning functionality for $Texture Replace implementation in FRED
* --Goober5000
*
* Revision 2.33 2003/01/27 07:46:33 Goober5000
* finished up my fighterbay code - ships will not be able to arrive from or depart into
* fighterbays that have been destroyed (but you have to explicitly say in the tables
* that the fighterbay takes damage in order to enable this)
* --Goober5000
*
* Revision 2.32 2003/01/26 00:20:01 Goober5000
* oops - fixed the nebula bug that wasn't completely fixed :)
* --Goober5000
*
* Revision 2.31 2003/01/19 22:20:23 Goober5000
* fixed a bunch of bugs -- the support ship sexp, the "no-subspace-drive" flag,
* and departure into hangars should now all work properly
* --Goober5000
*
* Revision 2.30 2003/01/19 09:14:05 Goober5000
* fixed fiddly code
* --Goober5000
*
* Revision 2.29 2003/01/19 09:10:40 Goober5000
* more tweaky bug fixes
* --Goober5000
*
* Revision 2.28 2003/01/19 08:37:52 Goober5000
* fixed two dumb bugs in the set-support-ship code
* --Goober5000
*
* Revision 2.27 2003/01/19 07:45:38 Goober5000
* actually added the set-support-ship sexp; much of the other commit was
* groundwork (data types and stuff)
* --Goober5000
*
* Revision 2.26 2003/01/19 07:02:16 Goober5000
* fixed a bunch of bugs - "no-subspace-drive" should now work properly for
* all ships, and all ships who have their departure anchor set to a capital ship
* should exit to that ship when told to depart
* --Goober5000
*
* Revision 2.25 2003/01/18 23:25:39 Goober5000
* made "no-subspace-drive" applicable to all ships and fixed a really *STUPID*
* bug that made FRED keep crashing (missing comma, bleagh!)
* --Goober5000
*
* Revision 2.24 2003/01/18 09:25:41 Goober5000
* fixed bug I inadvertently introduced by modifying SIF_ flags with sexps rather
* than SF_ flags
* --Goober5000
*
* Revision 2.23 2003/01/17 04:57:17 Goober5000
* Allowed selection of either $Texture Replace, which keeps track of individual
* replacement textures for a ship, or $Duplicate Model Texture Replace, which
* duplicates a model and reskins it. Use $Duplicate Model Texture Replace
* if you want to substitute an animated texture or a transparent texture.
* --Goober5000
*
* Revision 2.22 2003/01/17 01:48:50 Goober5000
* added capability to the $Texture replace code to substitute the textures
* without needing and extra model, however, this way you can't substitute
* transparent or animated textures
* --Goober5000
*
* Revision 2.21 2003/01/15 08:57:23 Goober5000
* assigning duplicate models to ships now works; committing so I have a base
* to fall back to as I work on texture replacement
* --Goober5000
*
* Revision 2.20 2003/01/15 07:09:09 Goober5000
* changed most references to modelnum to use ship instead of ship_info --
* this will help with the change-model sexp and any other instances of model
* changing
* --Goober5000
*
* Revision 2.19 2003/01/15 05:24:23 Goober5000
* added texture replacement parse - will be implemented later
* --Goober5000
*
* Revision 2.18 2003/01/14 06:13:23 Goober5000
* argh - fixed the stupid message persona bug with <any wingman>
* Bobboau, please check your code!
* --Goober5000
*
* Revision 2.17 2003/01/13 02:09:12 wmcoolmon
* Change mission flag for nebula trails. Also changed code to set flags as necessary
*
* Revision 2.16 2003/01/11 01:00:25 wmcoolmon
* Added code for "Ship Trails override Nebula"
*
* Revision 2.15 2003/01/06 20:29:28 Goober5000
* nasssty bugs, we hates them, yesss we do, preciousssss
* :)
* --Goober5000
*
* Revision 2.14 2003/01/06 17:19:14 Goober5000
* hmm... moved +Squad Logo to immediately beneath $Name
* --Goober5000
*
* Revision 2.13 2003/01/06 17:14:52 Goober5000
* added wing configurable squad logos - put +Squad Logo: filename.pcx as
* the last entry in each wing that you want (but the player's squad logo will
* still be the squad logo for the player's wing)
* --Goober5000
*
* Revision 2.12 2003/01/03 21:58:08 Goober5000
* Fixed some minor bugs, and added a primitive-sensors flag, where if a ship
* has primitive sensors it can't target anything and objects don't appear
* on radar if they're outside a certain range. This range can be modified
* via the sexp primitive-sensors-set-range.
* --Goober5000
*
* Revision 2.11 2003/01/02 00:35:21 Goober5000
* added don't-collide-invisible and collide-invisible sexps
* --Goober5000
*
* Revision 2.10 2003/01/01 23:33:34 Goober5000
* added ship-vaporize and ship-no-vaporize sexps
* --Goober5000
*
* Revision 2.9 2002/12/27 02:57:51 Goober5000
* removed the existing stealth sexps and replaced them with the following...
* ship-stealthy
* ship-unstealthy
* is-ship-stealthy
* friendly-stealth-invisible
* friendly-stealth-visible
* is-friendly-stealth-visible
* --Goober5000
*
* Revision 2.8 2002/12/23 05:18:52 Goober5000
* Squashed some Volition bugs! :O Some of the sexps for dealing with more than
* one ship would return after only dealing with the first ship.
*
* Also added the following sexps:
* is-ship-stealthed
* ship-force-stealth
* ship-force-nostealth
* ship-remove-stealth-forcing
*
* They toggle the stealth flag on and off. If a ship is forced stealthy, it won't even
* show up for friendly ships.
* --Goober5000
*
* Revision 2.7 2002/12/22 22:59:04 Goober5000
* hack: turn on ship trails if in nebula - restores backward compatibility
* --Goober5000
*
* Revision 2.6 2002/12/13 08:13:29 Goober5000
* small tweaks and bug fixes for the ballistic primary conversion
* ~Goober5000~
*
* Revision 2.5 2002/12/10 05:43:34 Goober5000
* Full-fledged ballistic primary support added! Try it and see! :)
*
* Revision 2.4 2002/12/03 23:05:13 Goober5000
* implemented beam-free-all-by-default mission flag
*
* Revision 2.3 2002/12/03 00:00:52 Goober5000
* fixed player entry delay bug
*
* Revision 2.2 2002/11/14 06:15:02 bobboau
* added nameplate code
*
* Revision 2.1 2002/08/01 01:41:07 penguin
* The big include file move
*
* Revision 2.0 2002/06/03 04:02:25 penguin
* Warpcore CVS sync
*
* Revision 1.3 2002/05/10 20:42:44 mharris
* use "ifndef NO_NETWORK" all over the place
*
* Revision 1.2 2002/05/03 22:07:09 mharris
* got some stuff to compile
*
* Revision 1.1 2002/05/02 18:03:10 mharris
* Initial checkin - converted filenames and includes to lower case
*
*
* 63 9/12/99 8:09p Dave
* Fixed problem where skip-training button would cause mission messages
* not to get paged out for the current mission.
*
* 62 9/09/99 3:53a Andsager
* Only reposition HUGE ships to center of knossos device on warp in
*
* 61 8/27/99 9:07p Dave
* LOD explosions. Improved beam weapon accuracy.
*
* 60 8/26/99 8:51p Dave
* Gave multiplayer TvT messaging a heavy dose of sanity. Cheat codes.
*
* 59 8/25/99 10:06a Jefff
* vasudan pilots get a vasudan support ship.
*
* 58 8/24/99 5:27p Andsager
* Make subsystems with zero strength before mission blown off. Protect
* red alert pilot against dying between orders and jump.
*
* 57 8/18/99 10:07p Johnson
* Fix Fred bug in positioning of Knossos device (when trying to warp in
* through self)
*
* 56 8/18/99 3:57p Andsager
* Add warning for invalid alt_name.
*
* 55 8/18/99 3:48p Andsager
* Make support ship take default name and not 0th alt_name.
*
* 54 8/16/99 3:53p Andsager
* Add special warp in interface in Fred and saving / reading.
*
* 53 8/16/99 2:01p Andsager
* Knossos warp-in warp-out.
*
* 52 8/03/99 5:35p Andsager
* Dont draw target dot for instructor in training mission
*
* 51 7/30/99 7:01p Dave
* Dogfight escort gauge. Fixed up laser rendering in Glide.
*
* 50 7/28/99 1:36p Andsager
* Modify cargo1 to include flag CARGO_NO_DEPLETE. Add sexp
* cargo-no-deplete (only for BIG / HUGE). Modify ship struct to pack
* better.
*
* 49 7/26/99 5:50p Dave
* Revised ingame join. Better? We'll see....
*
* 48 7/19/99 3:01p Dave
* Fixed icons. Added single transport icon.
*
* 47 7/15/99 9:20a Andsager
* FS2_DEMO initial checkin
*
* 46 7/13/99 5:03p Alanl
* make sure object sounds get assigned to ships
*
* 45 7/11/99 2:14p Dave
* Added Fred names for the new icon types.
*
* 44 7/02/99 4:\31p Dave
* Much more sophisticated lightning support.
*
* 43 7/01/99 4:23p Dave
* Full support for multiple linked ambient engine sounds. Added "big
* damage" flag.
*
* 42 6/28/99 4:51p Andsager
* Add ship-guardian sexp (does not allow ship to be killed)
*
* 41 6/21/99 1:34p Alanl
* event music tweaks
*
* 40 6/16/99 10:20a Dave
* Added send-message-list sexpression.
*
* 39 6/14/99 2:06p Andsager
* Default load brown asteroids
*
* 38 6/10/99 11:06a Andsager
* Mission designed selection of asteroid types.
*
* 37 6/03/99 6:37p Dave
* More TNT fun. Made perspective bitmaps more flexible.
*
* 36 5/20/99 7:00p Dave
* Added alternate type names for ships. Changed swarm missile table
* entries.
*
* 35 4/26/99 8:49p Dave
* Made all pof based nebula stuff full customizable through fred.
*
* 34 4/26/99 12:49p Andsager
* Add protect object from beam support to Fred
*
* 33 4/16/99 2:34p Andsager
* Second pass on debris fields
*
* 32 4/15/99 5:00p Andsager
* Frist pass on Debris field
*
* 31 4/07/99 6:22p Dave
* Fred and FreeSpace support for multiple background bitmaps and suns.
* Fixed link errors on all subprojects. Moved encrypt_init() to
* cfile_init() and lcl_init(), since its safe to call twice.
*
* 30 3/31/99 9:52a Andsager
* generalization for debris field
*
* 29 3/30/99 5:40p Dave
* Fixed reinforcements for TvT in multiplayer.
*
* 28 3/29/99 6:17p Dave
* More work on demo system. Got just about everything in except for
* blowing ships up, secondary weapons and player death/warpout.
*
* 27 3/24/99 6:23p Dave
* Make sure we only apply squadron changes to the player in single-player
* campaign mode.
*
* 26 3/24/99 4:05p Dave
* Put in support for assigning the player to a specific squadron with a
* specific logo. Preliminary work for doing pos/orient checksumming in
* multiplayer to reduce bandwidth.
*
* 25 3/01/99 7:39p Dave
* Added prioritizing ship respawns. Also fixed respawns in TvT so teams
* don't mix respawn points.
*
* 24 2/26/99 6:01p Andsager
* Add sexp has-been-tagged-delay and cap-subsys-cargo-known-delay
*
* 23 2/24/99 2:25p Dave
* Fixed up chatbox bugs. Made squad war reporting better. Fixed a respawn
* bug for dogfight more.
*
* 22 2/23/99 8:11p Dave
* Tidied up dogfight mode. Fixed TvT ship type problems for alpha wing.
* Small pass over todolist items.
*
* 21 2/17/99 2:10p Dave
* First full run of squad war. All freespace and tracker side stuff
* works.
*
* 20 2/11/99 3:08p Dave
* PXO refresh button. Very preliminary squad war support.
*
* 19 2/11/99 2:15p Andsager
* Add ship explosion modification to FRED
*
* 18 2/07/99 8:51p Andsager
* Add inner bound to asteroid field. Inner bound tries to stay astroid
* free. Wrap when within and don't throw at ships inside.
*
* 17 2/05/99 10:38a Johnson
* Fixed improper object array reference.
*
* 16 2/04/99 1:23p Andsager
* Apply max spark limit to ships created in mission parse
*
* 15 2/03/99 12:42p Andsager
* Add escort priority. Modify ship_flags_dlg to include field. Save and
* Load. Add escort priority field to ship.
*
* 14 1/27/99 9:56a Dave
* Temporary checkin of beam weapons for Dan to make cool sounds.
*
* 13 1/25/99 5:03a Dave
* First run of stealth, AWACS and TAG missile support. New mission type
* :)
*
* 12 1/19/99 3:57p Andsager
* Round 2 of variables
*
* 11 1/07/99 1:52p Andsager
* Initial check in of Sexp_variables
*
* 10 11/14/98 5:32p Dave
* Lots of nebula work. Put in ship contrails.
*
* 9 11/12/98 12:13a Dave
* Tidied code up for multiplayer test. Put in network support for flak
* guns.
*
* 8 11/05/98 5:55p Dave
* Big pass at reducing #includes
*
* 7 11/05/98 4:18p Dave
* First run nebula support. Beefed up localization a bit. Removed all
* conditional compiles for foreign versions. Modified mission file
* format.
*
* 6 10/29/98 9:23p Dave
* Removed minor bug concerning externalization of campaign files.
*
* 5 10/23/98 3:51p Dave
* Full support for tstrings.tbl and foreign languages. All that remains
* is to make it active in Fred.
*
* 4 10/20/98 10:44a Andsager
* Add comment for creating sparks before mission starts
*
* 3 10/07/98 6:27p Dave
* Globalized mission and campaign file extensions. Removed Silent Threat
* special code. Moved \cache \players and \multidata into the \data
* directory.
*
* 2 10/07/98 10:53a Dave
* Initial checkin.
*
* 1 10/07/98 10:49a Dave
*
* 503 9/21/98 8:46p Dave
* Put in special check in fred for identifying unknown ships.
*
* 502 9/14/98 5:09p Dave
* Massive hack to always ignore m-clash
*
* 501 9/14/98 3:40p Allender
* better error checking for invalid number of waves for player wings in a
* multiplayer game. Better popup message in FreeSpace side.
*
* 500 9/11/98 2:05p Allender
* make reinforcements work correctly in multiplayer games. There still
* may be a team vs team issue that I haven't thought of yet :-(
*
* 499 8/07/98 9:48a Allender
* changed how SF_FROM_PLAYER_WING is assigned for team vs. team games
*
* 498 7/16/98 2:22p Allender
* don't do wave check in single player
*
* 497 7/13/98 5:19p Dave
*
* 496 7/13/98 3:15p Allender
* don't allow multiple waves for any player wings
*
* 495 7/10/98 12:11a Allender
* fixed problem where missions files of 0 length would cause game to
* crash
*
* 494 5/21/98 3:31p Allender
* fix bug where Ship_obj_list was getting overwritten by the exited ships
* list
*
* 493 5/20/98 1:04p Hoffoss
* Made credits screen use new artwork and removed rating field usage from
* Fred (a goal struct member).
*
* 492 5/18/98 4:41p Comet
* allender: fix problem where ships in wings were not deleted on clients
* when a new wing create packet came in. A serious hack of all hacks
*
* 491 5/18/98 3:37p Jasen
* move Int3() about too many ships in wing to before where ship actually
* gets created
*
* 490 5/18/98 1:58a Mike
* Make Phoenix not be fired at fighters (but yes bombers).
* Improve positioning of ships in guard mode.
* Make turrets on player ship not fire near end of support ship docking.
*
* 489 5/15/98 9:52a Allender
* added code to give Warning when orders accepted don't match the default
* orders
*
* 488 5/13/98 4:41p Mike
* Make big ships try a tiny bit to avoid collision with each other when
* attacking another big ship. Make ships a little less likely to collide
* into player when in formation, drop off if player flying wacky.
*
* 487 5/13/98 3:07p Mitri
* fixed problem with checking current count of the mission against max
* ships per wing
*
* 486 5/11/98 4:33p Allender
* fixed ingame join problems -- started to work on new object updating
* code (currently ifdef'ed out)
*
* 485 5/08/98 5:05p Dave
* Go to the join game screen when quitting multiplayer. Fixed mission
* text chat bugs. Put mission type symbols on the create game list.
* Started updating standalone gui controls.
*
* $NoKeywords: $
*/
#ifdef _WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>
#include <setjmp.h>
#include "mission/missionparse.h"
#include "parse/parselo.h"
#include "mission/missiongoals.h"
#include "mission/missionlog.h"
#include "mission/missionmessage.h"
#include "globalincs/linklist.h"
#include "io/timer.h"
#include "ship/ship.h"
#include "ai/aigoals.h"
#include "playerman/player.h"
#include "starfield/starfield.h"
#include "bmpman/bmpman.h"
#include "lighting/lighting.h"
#include "gamesnd/eventmusic.h"
#include "mission/missionbriefcommon.h"
#include "mission/missioncampaign.h"
#include "ship/shipfx.h"
#include "debris/debris.h"
#include "starfield/nebula.h"
#include "hud/hudets.h"
#include "mission/missionhotkey.h"
#include "hud/hudescort.h"
#include "asteroid/asteroid.h"
#include "ship/shiphit.h"
#include "math/staticrand.h"
#include "missionui/missioncmdbrief.h"
#include "missionui/redalert.h"
#include "hud/hudwingmanstatus.h"
#include "jumpnode/jumpnode.h"
#include "localization/localize.h"
#include "nebula/neb.h"
#include "demo/demo.h"
#include "nebula/neblightning.h"
#include "math/fvi.h"
#include "weapon/weapon.h"
#include "cfile/cfile.h"
#include "iff_defs/iff_defs.h"
#include "network/multi.h"
#include "network/multiutil.h"
#include "network/multimsgs.h"
#include "network/multi_respawn.h"
#include "network/multi_endgame.h"
#include "object/parseobjectdock.h"
#include "object/waypoint.h"
#include "missionui/fictionviewer.h"
#include "cmdline/cmdline.h"
#include "popup/popup.h"
LOCAL struct {
char docker[NAME_LENGTH];
char dockee[NAME_LENGTH];
char docker_point[NAME_LENGTH];
char dockee_point[NAME_LENGTH];
} Initially_docked[MAX_SHIPS];
int Total_initially_docked;
mission The_mission;
char Mission_filename[80];
int Mission_palette; // index into Nebula_palette_filenames[] of palette file to use for mission
int Nebula_index; // index into Nebula_filenames[] of nebula to use in mission.
int Num_ai_behaviors = MAX_AI_BEHAVIORS;
int Num_cargo = 0;
int Num_status_names = MAX_STATUS_NAMES;
int Num_arrival_names = MAX_ARRIVAL_NAMES;
int Num_goal_type_names = MAX_GOAL_TYPE_NAMES;
int Num_team_names = MAX_TEAM_NAMES;
int Num_parse_goals;
int Player_starts = 1;
int Num_teams;
fix Entry_delay_time = 0;
int Fred_num_texture_replacements = 0;
int Num_unknown_ship_classes;
int Num_unknown_weapon_classes;
ushort Current_file_checksum = 0;
ushort Last_file_checksum = 0;
int Current_file_length = 0;
// alternate ship type names
char Mission_alt_types[MAX_ALT_TYPE_NAMES][NAME_LENGTH];
int Mission_alt_type_count = 0;
#define SHIP_WARP_TIME 5.0f // how many seconds it takes for ship to warp in
// the ship arrival list will contain a list of ships that are yet to arrive. This
// list could also include ships that are part of wings!
p_object Ship_arrival_list; // for linked list of ships to arrive later
// all the ships that we parse
std::vector<p_object> Parse_objects;
// list for arriving support ship
p_object Support_ship_pobj;
p_object *Arriving_support_ship;
char Arriving_repair_targets[MAX_AI_GOALS][NAME_LENGTH];
int Num_arriving_repair_targets;
//subsys_status Subsys_status[MAX_SUBSYS_STATUS]; // it's dynamic now - taylor
#define MIN_SUBSYS_STATUS_SIZE 25
subsys_status *Subsys_status = NULL;
int Subsys_index;
int Subsys_status_size;
char Mission_parse_storm_name[NAME_LENGTH] = "none";
team_data Team_data[MAX_TVT_TEAMS];
// variables for player start in single player
char Player_start_shipname[NAME_LENGTH];
int Player_start_shipnum;
p_object Player_start_pobject;
// name of all ships to use while parsing a mission (since a ship might be referenced by
// something before that ship has even been loaded yet)
char Parse_names[MAX_SHIPS + MAX_WINGS][NAME_LENGTH];
int Num_parse_names;
texture_replace *Fred_texture_replacements = NULL;
int Num_path_restrictions;
path_restriction_t Path_restrictions[MAX_PATH_RESTRICTIONS];
//XSTR:OFF
char *Nebula_filenames[NUM_NEBULAS] = {
"Nebula01",
"Nebula02",
"Nebula03"
};
char *Neb2_filenames[NUM_NEBULAS] = {
"Nebfull01",
"Nebfull02",
"Nebfull03"
};
// Note: Nebula_colors[] and Nebula_palette_filenames are linked via index numbers
char *Nebula_colors[NUM_NEBULA_COLORS] = {
"Red",
"Blue",
"Gold",
"Purple",
"Maroon",
"Green",
"Grey blue",
"Violet",
"Grey Green",
};
char *Ai_behavior_names[MAX_AI_BEHAVIORS] = {
"Chase",
"Evade",
"Get behind",
"Stay Near",
"Still",
"Guard",
"Avoid",
"Waypoints",
"Dock",
"None",
"Big Ship",
"Path",
"Be Rearmed",
"Safety",
"Evade Weapon",
"Strafe",
"Play Dead",
"Bay Emerge",
"Bay Depart",
"Sentry Gun",
"Warp Out",
};
char *Cargo_names[MAX_CARGO];
char Cargo_names_buf[MAX_CARGO][NAME_LENGTH];
char *Ship_class_names[MAX_SHIP_CLASSES]; // to be filled in from Ship_info array
char *Icon_names[MAX_BRIEF_ICONS] = {
"Fighter", "Fighter Wing", "Cargo", "Cargo Wing", "Largeship",
"Largeship Wing", "Capital", "Planet", "Asteroid Field", "Waypoint",
"Support Ship", "Freighter(no cargo)", "Freighter(has cargo)",
"Freighter Wing(no cargo)", "Freighter Wing(has cargo)", "Installation",
"Bomber", "Bomber Wing", "Cruiser", "Cruiser Wing", "Unknown", "Unknown Wing",
"Player Fighter", "Player Fighter Wing", "Player Bomber", "Player Bomber Wing",
"Knossos Device", "Transport Wing", "Corvette", "Gas Miner", "Awacs", "Supercap", "Sentry Gun", "Jump Node", "Transport"
};
char *Status_desc_names[MAX_STATUS_NAMES] = {
"Shields Critical", "Engines Damaged", "Fully Operational",
};
char *Status_type_names[MAX_STATUS_NAMES] = {
"Damaged", "Disabled", "Corroded",
};
char *Status_target_names[MAX_STATUS_NAMES] = {
"Weapons", "Engines", "Cable TV",
};
// definitions for arrival locations for ships/wings
char *Arrival_location_names[MAX_ARRIVAL_NAMES] = {
"Hyperspace", "Near Ship", "In front of ship", "Docking Bay",
};
char *Departure_location_names[MAX_DEPARTURE_NAMES] = {
"Hyperspace", "Docking Bay",
};
char *Goal_type_names[MAX_GOAL_TYPE_NAMES] = {
"Primary", "Secondary", "Bonus",
};
char *Reinforcement_type_names[] = {
"Attack/Protect",
"Repair/Rearm",
};
char *Old_game_types[OLD_MAX_GAME_TYPES] = {
"Single Player Only",
"Multiplayer Only",
"Single/Multi Player",
"Training mission"
};
char *Parse_object_flags[MAX_PARSE_OBJECT_FLAGS] = {
"cargo-known",
"ignore-count",
"protect-ship",
"reinforcement",
"no-shields",
"escort",
"player-start",
"no-arrival-music",
"no-arrival-warp",
"no-departure-warp",
"locked",
"invulnerable",
"hidden-from-sensors",
"scannable",
"kamikaze",
"no-dynamic",
"red-alert-carry",
"beam-protect-ship",
"guardian",
"special-warp",
"vaporize",
"stealth",
"friendly-stealth-invisible",
"don't-collide-invisible",
};
char *Parse_object_flags_2[MAX_PARSE_OBJECT_FLAGS_2] = {
"primitive-sensors",
"no-subspace-drive",
"nav-carry-status",
"affected-by-gravity",
"toggle-subsystem-scanning",
"targetable-as-bomb",
"no-builtin-messages",
"primaries-locked",
"secondaries-locked",
"no-death-scream",
"always-death-scream",
"nav-needslink",
"use-alt-name-as-callsign",
};
//XSTR:ON
int Num_reinforcement_type_names = sizeof(Reinforcement_type_names) / sizeof(char *);
vec3d Parse_viewer_pos;
matrix Parse_viewer_orient;
int Loading_screen_bm_index=-1;
// definitions for timestamps for eval'ing arrival/departure cues
int Mission_arrival_timestamp;
int Mission_departure_timestamp;
fix Mission_end_time;
#define ARRIVAL_TIMESTAMP 2000 // every 2 seconds
#define DEPARTURE_TIMESTAMP 2200 // every 2.2 seconds -- just to be a little different
// calculates a "unique" file signature as a ushort (checksum) and an int (file length)
// the amount of The_mission we're going to checksum
// WARNING : do NOT call this function on the server - it will overwrite goals, etc
#define MISSION_CHECKSUM_SIZE (NAME_LENGTH + NAME_LENGTH + 4 + DATE_TIME_LENGTH + DATE_TIME_LENGTH)
// timers used to limit arrival messages and music
#define ARRIVAL_MUSIC_MIN_SEPARATION 60000
#define ARRIVAL_MESSAGE_MIN_SEPARATION 30000
#define ARRIVAL_MESSAGE_DELAY_MIN 2000
#define ARRIVAL_MESSAGE_DELAY_MAX 3000
static int Allow_arrival_music_timestamp;
static int Allow_arrival_message_timestamp;
static int Arrival_message_delay_timestamp;
// multi TvT
static int Allow_arrival_music_timestamp_m[2];
static int Allow_arrival_message_timestamp_m[2];
static int Arrival_message_delay_timestamp_m[2];
extern fix game_get_overall_frametime(); // for texture animation
// local prototypes
void parse_player_info2(mission *pm);
void post_process_mission();
int allocate_subsys_status();
void parse_common_object_data(p_object *objp);
void parse_asteroid_fields(mission *pm);
int mission_set_arrival_location(int anchor, int location, int distance, int objnum, int path_mask, vec3d *new_pos, matrix *new_orient);
int get_parse_name_index(char *name);
int get_anchor(char *name);
void mission_parse_set_up_initial_docks();
void mission_parse_set_arrival_locations();
void mission_set_wing_arrival_location( wing *wingp, int num_to_set );
int parse_lookup_alt_name(char *name);
void parse_init(bool basic = false);
void parse_object_set_handled_flag_helper(p_object *pobjp, p_dock_function_info *infop);
void parse_object_clear_all_handled_flags();
int parse_object_on_arrival_list(p_object *pobjp);
int add_path_restriction();
// Goober5000
void mission_parse_mark_non_arrival(p_object *p_objp);
void mission_parse_mark_non_arrival(wing *wingp);
void mission_parse_mark_non_arrivals();
// Goober5000 - FRED import
void convertFSMtoFS2();
void restore_default_weapons(char *ships_tbl);
void restore_one_primary_bank(int *ship_primary_weapons, int *default_primary_weapons);
void restore_one_secondary_bank(int *ship_secondary_weapons, int *default_secondary_weapons);
MONITOR(NumShipArrivals)
MONITOR(NumShipDepartures)
void parse_mission_info(mission *pm, bool basic = false)
{
int i;
char game_string[NAME_LENGTH];
// Goober5000
skip_to_start_of_string("#Mission Info");
required_string("#Mission Info");
required_string("$Version:");
stuff_float(&pm->version);
if (pm->version != MISSION_VERSION)
mprintf(("Older mission, should update it (%.2f<-->%.2f)\n", pm->version, MISSION_VERSION));
required_string("$Name:");
stuff_string(pm->name, F_NAME, NAME_LENGTH);
required_string("$Author:");
stuff_string(pm->author, F_NAME, NAME_LENGTH);
required_string("$Created:");
stuff_string(pm->created, F_DATE, DATE_TIME_LENGTH);
required_string("$Modified:");
stuff_string(pm->modified, F_DATE, DATE_TIME_LENGTH);
required_string("$Notes:");
stuff_string(pm->notes, F_NOTES, NOTES_LENGTH);
if (optional_string("$Mission Desc:"))
stuff_string(pm->mission_desc, F_MULTITEXT, MISSION_DESC_LENGTH);
else
strcpy(pm->mission_desc, NOX("No description\n"));
pm->game_type = MISSION_TYPE_SINGLE; // default to single player only
if ( optional_string("+Game Type:")) {
// HACK HACK HACK -- stuff_string was changed to *not* ignore carriage returns. Since the
// old style missions may have carriage returns, deal with it here.
ignore_white_space();
stuff_string(game_string, F_NAME, NAME_LENGTH);
for ( i = 0; i < OLD_MAX_GAME_TYPES; i++ ) {
if ( !stricmp(game_string, Old_game_types[i]) ) {
// this block of code is now old mission compatibility code. We specify game
// type in a different manner than before.
if ( i == OLD_GAME_TYPE_SINGLE_ONLY )
pm->game_type = MISSION_TYPE_SINGLE;
else if ( i == OLD_GAME_TYPE_MULTI_ONLY )
pm->game_type = MISSION_TYPE_MULTI;
else if ( i == OLD_GAME_TYPE_SINGLE_MULTI )
pm->game_type = (MISSION_TYPE_SINGLE | MISSION_TYPE_MULTI );
else if ( i == OLD_GAME_TYPE_TRAINING )
pm->game_type = MISSION_TYPE_TRAINING;
else
Int3();
if ( pm->game_type & MISSION_TYPE_MULTI )
pm->game_type |= MISSION_TYPE_MULTI_COOP;
break;
}
}
}
if ( optional_string("+Game Type Flags:") ) {
stuff_int(&pm->game_type);
}
pm->flags = 0;
if (optional_string("+Flags:")){
stuff_int(&pm->flags);
}
// nebula mission stuff
Neb2_awacs = -1.0f;
if(optional_string("+NebAwacs:")){
stuff_float(&Neb2_awacs);
}
if(optional_string("+Storm:")){
stuff_string(Mission_parse_storm_name, F_NAME, NAME_LENGTH);
if (!basic)
nebl_set_storm(Mission_parse_storm_name);
}
// Goober5000 - ship contrail speed threshold
pm->contrail_threshold = CONTRAIL_THRESHOLD_DEFAULT;
if (optional_string("$Contrail Speed Threshold:")){
stuff_int(&pm->contrail_threshold);
}
// get the number of players if in a multiplayer mission
pm->num_players = 1;
if ( pm->game_type & MISSION_TYPE_MULTI ) {
if ( optional_string("+Num Players:") ) {
stuff_int( &(pm->num_players) );
}
}
// get the number of respawns
pm->num_respawns = 0;
if ( pm->game_type & MISSION_TYPE_MULTI ) {
if ( optional_string("+Num Respawns:") ){
stuff_int( (int*)&(pm->num_respawns) );
}
}
if ( optional_string("+Red Alert:")) {
int temp;
stuff_int(&temp);
if (temp)
pm->flags |= MISSION_FLAG_RED_ALERT;
else
pm->flags &= ~MISSION_FLAG_RED_ALERT;
}
red_alert_invalidate_timestamp();
if ( optional_string("+Scramble:")) {
int temp;
stuff_int(&temp);
if (temp)
pm->flags |= MISSION_FLAG_SCRAMBLE;
else
pm->flags &= ~MISSION_FLAG_SCRAMBLE;
}
// if we are just requesting basic info then skip everything else. the reason
// for this is to make sure that we don't modify things outside of the mission struct
// that might not get reset afterwards (like what can happen in the techroom) - taylor
//
// NOTE: this can be dangerous so be sure that any get_mission_info() call (defaults to basic info) will
// only reference data parsed before this point!! (like current FRED2 and game code does)
if (basic == true)
return;
// set up support ships
pm->support_ships.arrival_location = ARRIVE_AT_LOCATION;
pm->support_ships.arrival_anchor = -1;
pm->support_ships.departure_location = DEPART_AT_LOCATION;
pm->support_ships.departure_anchor = -1;
pm->support_ships.max_hull_repair_val = 100.0f; //ASSUMPTION: full repair capabilities
pm->support_ships.max_subsys_repair_val = 100.0f;
pm->support_ships.max_support_ships = -1; // infinite
pm->support_ships.ship_class = -1;
pm->support_ships.tally = 0;
pm->support_ships.support_available_for_species = 0;
// for each species, store whether support is available
for (int species = 0; species < (int)Species_info.size(); species++)
{
for (int ship_class = 0; ship_class < Num_ship_classes; ship_class++)
{
if ((Ship_info[ship_class].flags & SIF_SUPPORT) && (Ship_info[ship_class].species == species))
{
pm->support_ships.support_available_for_species |= (1 << species);
break;
}
}
}
if ( optional_string("+Disallow Support:"))
{
int temp;
stuff_int(&temp);
pm->support_ships.max_support_ships = (temp > 0) ? 0 : -1;
}
if ( optional_string("+Hull Repair Ceiling:"))
{
float temp;
stuff_float(&temp);
//ONLY set max_hull_repair_val if the value given is valid -C
if (temp <= 100.0f && temp >= 0.0f) {
pm->support_ships.max_hull_repair_val = temp;
}
}
if ( optional_string("+Subsystem Repair Ceiling:"))
{
float temp;
stuff_float(&temp);
//ONLY set max_subsys_repair_val if the value given is valid -C
if (temp <= 100.0f && temp >= 0.0f) {
pm->support_ships.max_subsys_repair_val = temp;
}
}
if (optional_string("+All Teams Attack")){
Mission_all_attack = 1;
} else {
Mission_all_attack = 0;
}
// Maybe delay the player's entry.
if (optional_string("+Player Entry Delay:")) {
float temp;
stuff_float(&temp);
Assert(temp >= 0.0f);
Entry_delay_time = fl2f(temp);
}
else
{
Entry_delay_time = 0;
}
if (optional_string("+Viewer pos:")){
stuff_vector(&Parse_viewer_pos);
}
if (optional_string("+Viewer orient:")){
stuff_matrix(&Parse_viewer_orient);
}
// possible squadron reassignment
strcpy(pm->squad_name, "");
strcpy(pm->squad_filename, "");
if(optional_string("+SquadReassignName:")){
stuff_string(pm->squad_name, F_NAME, NAME_LENGTH);
if(optional_string("+SquadReassignLogo:")){
stuff_string(pm->squad_filename, F_NAME, MAX_FILENAME_LEN);
}
}
// always clear out squad reassignments if not single player
if(Game_mode & GM_MULTIPLAYER){
strcpy(pm->squad_name, "");
strcpy(pm->squad_filename, "");
// mprintf(("Ignoring squadron reassignment in parse_mission_info\n"));
}
// reassign the player
else {
if(!Fred_running && (Player != NULL) && (strlen(pm->squad_name) > 0) && (Game_mode & GM_CAMPAIGN_MODE)){
mprintf(("Reassigning player to squadron %s\n", pm->squad_name));
player_set_squad(Player, pm->squad_name);
player_set_squad_bitmap(Player, pm->squad_filename);
}
}
// wing stuff by Goober5000 ------------------------------------------
// the wing name arrays are initialized in ship_level_init
if (optional_string("$Starting wing names:"))
{
stuff_string_list(Starting_wing_names, MAX_STARTING_WINGS);
}
if (optional_string("$Squadron wing names:"))
{
stuff_string_list(Squadron_wing_names, MAX_SQUADRON_WINGS);
}
if (optional_string("$Team-versus-team wing names:"))
{
stuff_string_list(TVT_wing_names, MAX_TVT_WINGS);
}
// minimal error checking
if (strcmp(Starting_wing_names[0], TVT_wing_names[0]))
{
Error(LOCATION, "The first starting wing and the first team-versus-team wing must have the same wing name.\n");
}
// end of wing stuff -------------------------------------------------
// set up the Num_teams variable accoriding to the game_type variable'
Num_teams = 1; // assume 1
// multiplayer team v. team games have two teams. If we have three teams, we need to use
// a new mission mode!
if ( (pm->game_type & MISSION_TYPE_MULTI) && (pm->game_type & MISSION_TYPE_MULTI_TEAMS) ){
Num_teams = 2;
}
int found640=0, found1024=0;
strcpy(pm->loading_screen[GR_640],"");
strcpy(pm->loading_screen[GR_1024],"");
//custom mission loading background
if (optional_string("$Load Screen 640:"))
{
found640=1;
stuff_string(pm->loading_screen[GR_640], F_NAME, MAX_FILENAME_LEN);
}
if (optional_string("$Load Screen 1024:"))
{
found1024=1;
stuff_string(pm->loading_screen[GR_1024], F_NAME, MAX_FILENAME_LEN);
}
//error testing
if ((found640) && !(found1024))
{
Warning(LOCATION, "Mission: %s\nhas a 640x480 loading screen but no 1024x768 loading screen!",pm->name);
}
if (!(found640) && (found1024))
{
Warning(LOCATION, "Mission: %s\nhas a 1024x768 loading screen but no 640x480 loading screen!",pm->name);
}
strcpy(pm->skybox_model, "");
if (optional_string("$Skybox model:"))
{
stuff_string(pm->skybox_model, F_NAME, MAX_FILENAME_LEN);
}
// Goober5000 - AI on a per-mission basis
The_mission.ai_profile = &Ai_profiles[Default_ai_profile];
if (optional_string("$AI Profile:"))
{
int index;
char temp[NAME_LENGTH];
stuff_string(temp, F_NAME, NAME_LENGTH);
index = ai_profile_lookup(temp);
if (index >= 0)
The_mission.ai_profile = &Ai_profiles[index];
}
Assert( The_mission.ai_profile != NULL );
// Kazan - player use AI at start?
if (pm->flags & MISSION_FLAG_PLAYER_START_AI)
Player_use_ai = 1;
}
void parse_player_info(mission *pm)
{
char alt[NAME_LENGTH];
Assert(pm != NULL);
// alternate type names begin here
mission_parse_reset_alt();
if(optional_string("#Alternate Types:")){
// read them all in
while(!optional_string("#end")){
required_string("$Alt:");
stuff_string(alt, F_NAME, NAME_LENGTH);
// maybe store it
mission_parse_add_alt(alt);
}
}
Player_starts = 0;
required_string("#Players");
while (required_string_either("#Objects", "$")){
parse_player_info2(pm);
}
}
void parse_player_info2(mission *pm)
{
char str[NAME_LENGTH];
int nt, i, total, list[MAX_SHIP_CLASSES * 2], list2[MAX_WEAPON_TYPES * 2], num_starting_wings;
team_data *ptr;
char starting_wings[MAX_STARTING_WINGS][NAME_LENGTH];
// read in a ship/weapon pool for each team.
for ( nt = 0; nt < Num_teams; nt++ ) {
int num_ship_choices;
ptr = &Team_data[nt];
// get the shipname for single player missions
// MWA -- make this required later!!!!
if ( optional_string("$Starting Shipname:") )
stuff_string( Player_start_shipname, F_NAME, NAME_LENGTH );
required_string("$Ship Choices:");
total = stuff_int_list(list, MAX_SHIP_CLASSES * 2, SHIP_INFO_TYPE);
Assert(!(total & 0x01)); // make sure we have an even count
num_ship_choices = 0;
total /= 2; // there are only 1/2 the ships really on the list.
for (i=0; i<total; i++) {
// in a campaign, see if the player is allowed the ships or not. Remove them from the
// pool if they are not allowed
if (Game_mode & GM_CAMPAIGN_MODE || ((Game_mode & GM_MULTIPLAYER) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER))) {
if ( !Campaign.ships_allowed[list[i*2]] )
continue;
}
ptr->ship_list[num_ship_choices] = list[i * 2];
ptr->ship_count[num_ship_choices] = list[i * 2 + 1];
num_ship_choices++;
}
ptr->number_choices = num_ship_choices;
num_starting_wings = 0;
if (optional_string("+Starting Wings:"))
num_starting_wings = stuff_string_list(starting_wings, MAX_STARTING_WINGS);
ptr->default_ship = -1;
if (optional_string("+Default_ship:")) {
stuff_string(str, F_NAME, NAME_LENGTH);
ptr->default_ship = ship_info_lookup(str);
// see if the player's default ship is an allowable ship (campaign only). If not, then what
// do we do? choose the first allowable one?
if (Game_mode & GM_CAMPAIGN_MODE || ((Game_mode & GM_MULTIPLAYER) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER))) {
if ( !(Campaign.ships_allowed[ptr->default_ship]) ) {
for (i = 0; i < MAX_SHIP_CLASSES; i++ ) {
if ( Campaign.ships_allowed[ptr->default_ship] ) {
ptr->default_ship = i;
break;
}
}
Assert( i < MAX_SHIP_CLASSES );
}
}
}
if (ptr->default_ship == -1) // invalid or not specified, make first in list
ptr->default_ship = ptr->ship_list[0];
for (i=0; i<MAX_WEAPON_TYPES; i++)
ptr->weaponry_pool[i] = 0;
required_string("+Weaponry Pool:");
total = stuff_int_list(list2, MAX_WEAPON_TYPES * 2, WEAPON_POOL_TYPE);
Assert( !(total & 0x01) ); // make sure we have an even count
for (i = 0; i < total; i += 2) {
// in a campaign, see if the player is allowed the weapons or not. Remove them from the
// pool if they are not allowed
if (Game_mode & GM_CAMPAIGN_MODE || ((Game_mode & GM_MULTIPLAYER) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER))) {
if ( !Campaign.weapons_allowed[list2[i]] )
continue;
}
if ( (list2[i] >= 0) && (list2[i] < MAX_WEAPON_TYPES) ) {
// always allow the pool to be added in FRED, it is a verbal warning
// to let the mission dev know about the problem
if ( (Weapon_info[list2[i]].wi_flags & WIF_PLAYER_ALLOWED) || Fred_running )
ptr->weaponry_pool[list2[i]] = list2[i+1];
else
mprintf(("WARNING: Weapon '%s' in weapon pool isn't allowed on player loadout! Ignoring it ...\n", Weapon_info[i].name));
}
}
}
if ( nt != Num_teams )
Error(LOCATION, "Not enough ship/weapon pools for mission. There are %d teams and only %d pools.", Num_teams, nt);
}
void parse_plot_info(mission *pm)
{
if (optional_string("#Plot Info"))
{
char dummy_filespec[FILESPEC_LENGTH];
char dummy_name[NAME_LENGTH];
required_string("$Tour:");
stuff_string(dummy_name, F_NAME, NAME_LENGTH);
required_string("$Pre-Briefing Cutscene:");
stuff_string(dummy_filespec, F_FILESPEC, FILESPEC_LENGTH);
required_string("$Pre-Mission Cutscene:");
stuff_string(dummy_filespec, F_FILESPEC, FILESPEC_LENGTH);
required_string("$Next Mission Success:");
stuff_string(dummy_name, F_NAME, NAME_LENGTH);
required_string("$Next Mission Partial:");
stuff_string(dummy_name, F_NAME, NAME_LENGTH);
required_string("$Next Mission Failure:");
stuff_string(dummy_name, F_NAME, NAME_LENGTH);
}
}
void parse_briefing_info(mission *pm)
{
char junk[4096];
if ( !optional_string("#Briefing Info") )
return;
required_string("$Briefing Voice 1:");
stuff_string(junk, F_FILESPEC, sizeof(junk));
required_string("$Briefing Text 1:");
stuff_string(junk, F_MULTITEXTOLD, sizeof(junk));
required_string("$Briefing Voice 2:");
stuff_string(junk, F_FILESPEC, sizeof(junk));
required_string("$Briefing Text 2:");
stuff_string(junk, F_MULTITEXTOLD, sizeof(junk));
required_string("$Briefing Voice 3:");
stuff_string(junk, F_FILESPEC, sizeof(junk));
required_string("$Briefing Text 3:");
stuff_string(junk, F_MULTITEXTOLD, sizeof(junk));
required_string("$Debriefing Voice 1:");
stuff_string(junk, F_FILESPEC, sizeof(junk));
required_string("$Debriefing Text 1:");
stuff_string(junk, F_MULTITEXTOLD, sizeof(junk));
required_string("$Debriefing Voice 2:");
stuff_string(junk, F_FILESPEC, sizeof(junk));
required_string("$Debriefing Text 2:");
stuff_string(junk, F_MULTITEXTOLD, sizeof(junk));
required_string("$Debriefing Voice 3:");
stuff_string(junk, F_FILESPEC, sizeof(junk));
required_string("$Debriefing Text 3:");
stuff_string(junk, F_MULTITEXTOLD, sizeof(junk));
}
// parse the event music and briefing music for the mission
void parse_music(mission *pm, int flags)
{
int i, index, num;
char *ch;
char temp[NAME_LENGTH];
event_music_reset_choices();
if ( !optional_string("#Music") ) {
return;
}
required_string("$Event Music:");
stuff_string(pm->event_music_name, F_NAME, NAME_LENGTH);
// Goober5000
if (optional_string("$Substitute Event Music:"))
stuff_string(pm->substitute_event_music_name, F_NAME, NAME_LENGTH);
required_string("$Briefing Music:");
stuff_string(pm->briefing_music_name, F_NAME, NAME_LENGTH);
// Goober5000
if (optional_string("$Substitute Briefing Music:"))
stuff_string(pm->substitute_briefing_music_name, F_NAME, NAME_LENGTH);
// old stuff, apparently
if (optional_string("$Debriefing Success Music:"))
{
stuff_string(temp, F_NAME, NAME_LENGTH);
event_music_set_score(SCORE_DEBRIEF_SUCCESS, temp);
}
// not old, just added since it makes sense
if (optional_string("$Debriefing Average Music:"))
{
stuff_string(temp, F_NAME, NAME_LENGTH);
event_music_set_score(SCORE_DEBRIEF_AVERAGE, temp);
}
// old stuff
if (optional_string("$Debriefing Fail Music:"))
{
stuff_string(temp, F_NAME, NAME_LENGTH);
event_music_set_score(SCORE_DEBRIEF_FAIL, temp);
}
// new stuff
if (optional_string("$Fiction Viewer Music:"))
{
stuff_string(temp, F_NAME, NAME_LENGTH);
event_music_set_score(SCORE_FICTION_VIEWER, temp);
}
// Goober5000 - if briefing not specified in import, default to BRIEF1
if (!stricmp(pm->briefing_music_name, "none") && (flags & MPF_IMPORT_FSM))
strcpy(pm->briefing_music_name, "BRIEF1");
// Goober5000 - old way of grabbing substitute music, but here for reverse compatibility
if (optional_string("$Substitute Music:"))
{
stuff_string(pm->substitute_event_music_name, F_NAME, NAME_LENGTH, "," );
Mp++;
stuff_string(pm->substitute_briefing_music_name, F_NAME, NAME_LENGTH);
}
// Goober5000 - if the mission is being imported, the substitutes are the specified tracks
// (with FS1 prefixes) and we generate new specified tracks
if (flags & MPF_IMPORT_FSM)
{
// no specified music?
if (!stricmp(pm->event_music_name, "none"))
goto done_event_music;
// set the FS1 equivalent as the substitute
strcpy(pm->substitute_event_music_name, "FS1-");
strcat(pm->substitute_event_music_name, pm->event_music_name);
// if we have Marauder, it's in FS2 as Deuteronomy, so we're done
if (!stricmp(pm->event_music_name, "7: Marauder") && event_music_get_soundtrack_index("5: Deuteronomy") >= 0)
{
strcpy(pm->event_music_name, "5: Deuteronomy");
goto done_event_music;
}
// search for something with the same track number
strcpy(temp, pm->event_music_name);
ch = strchr(temp, ':');
if (ch != NULL)
{
*(ch + 1) = '\0';
for (i = 0; i < Num_soundtracks; i++)
{
if (!strncmp(temp, Soundtracks[i].name, strlen(temp)))
{
strcpy(pm->event_music_name, Soundtracks[i].name);
goto done_event_music;
}
}
}
// last resort: pick a random track out of the 7 FS2 soundtracks
num = (Num_soundtracks < 7) ? Num_soundtracks : 7;
strcpy(pm->event_music_name, Soundtracks[rand() % num].name);
done_event_music:
// no specified music?
if (!stricmp(pm->briefing_music_name, "none"))
goto done_briefing_music;
// set the FS1 equivalent as the substitute
strcpy(pm->substitute_briefing_music_name, "FS1-");
strcat(pm->substitute_briefing_music_name, pm->briefing_music_name);
// Choco Mousse is the FS1 title soundtrack, so use Aquitaine in FS2
if (!stricmp(pm->briefing_music_name, "Choco Mousse") && event_music_get_spooled_music_index("Aquitaine") >= 0)
{
strcpy(pm->briefing_music_name, "Aquitaine");
goto done_briefing_music;
}
// we might have a match with the same track number
if (event_music_get_spooled_music_index(pm->briefing_music_name) >= 0)
goto done_briefing_music;
// last resort: pick a random track out of the first 7 FS2 briefings (the regular ones)...
num = (Num_music_files < 7) ? Num_music_files : 7;
strcpy(pm->briefing_music_name, Spooled_music[rand() % num].name);
done_briefing_music:
/* NO-OP */ ;
}
// set the soundtrack, preferring the substitute in FS2 (not FRED!)
index = event_music_get_soundtrack_index(pm->substitute_event_music_name);
if ((index >= 0) && (Soundtracks[index].flags & EMF_VALID) && !Fred_running)
{
event_music_set_soundtrack(pm->substitute_event_music_name);
}
else
{
event_music_set_soundtrack(pm->event_music_name);
}
// set the briefing, preferring the substitute in FS2 (not FRED!)
index = event_music_get_spooled_music_index(pm->substitute_briefing_music_name);
if ((index >= 0) && (Spooled_music[index].flags & SMF_VALID) && !Fred_running)
{
event_music_set_score(SCORE_BRIEFING, pm->substitute_briefing_music_name);
}
else
{
event_music_set_score(SCORE_BRIEFING, pm->briefing_music_name);
}
}
void parse_fiction(mission *pm)
{
char filename[MAX_FILENAME_LEN];
fiction_viewer_reset();
if (!optional_string("#Fiction Viewer"))
return;
required_string("$File:");
stuff_string(filename, F_FILESPEC, MAX_FILENAME_LEN);
fiction_viewer_load(filename);
}
void parse_cmd_brief(mission *pm)
{
int stage;
Assert(!Cur_cmd_brief->num_stages);
stage = 0;
required_string("#Command Briefing");
while (optional_string("$Stage Text:")) {
Assert(stage < CMD_BRIEF_STAGES_MAX);
Cur_cmd_brief->stage[stage].text = stuff_and_malloc_string(F_MULTITEXT, NULL, CMD_BRIEF_TEXT_MAX);
Assert(Cur_cmd_brief->stage[stage].text);
required_string("$Ani Filename:");
stuff_string(Cur_cmd_brief->stage[stage].ani_filename, F_FILESPEC, MAX_FILENAME_LEN);
if (optional_string("+Wave Filename:"))
stuff_string(Cur_cmd_brief->stage[stage].wave_filename, F_FILESPEC, MAX_FILENAME_LEN);
else
Cur_cmd_brief->stage[stage].wave_filename[0] = 0;
stage++;
}
Cur_cmd_brief->num_stages = stage;
}
void parse_cmd_briefs(mission *pm)
{
int i;
cmd_brief_reset();
// a hack follows because old missions don't have a command briefing
if (required_string_either("#Command Briefing", "#Briefing"))
return;
for (i=0; i<Num_teams; i++) {
Cur_cmd_brief = &Cmd_briefs[i];
parse_cmd_brief(pm);
}
}
// -------------------------------------------------------------------------------------------------
// parse_briefing()
//
// Parse the data required for the mission briefing
//
// NOTE: This updates the global Briefing struct with all the data necessary to drive the briefing
//
void parse_briefing(mission *pm, int flags)
{
int nt, i, j, stage_num = 0, icon_num = 0;
brief_stage *bs;
brief_icon *bi;
briefing *bp;
char not_used_text[MAX_ICON_TEXT_LEN];
brief_reset();
// MWA -- 2/3/98. we can now have multiple briefing and debriefings in a mission
for ( nt = 0; nt < Num_teams; nt++ ) {
if ( !optional_string("#Briefing") )
break;
bp = &Briefings[nt];
required_string("$start_briefing");
required_string("$num_stages:");
stuff_int(&bp->num_stages);
Assert(bp->num_stages <= MAX_BRIEF_STAGES);
stage_num = 0;
while (required_string_either("$end_briefing", "$start_stage")) {
required_string("$start_stage");
Assert(stage_num < MAX_BRIEF_STAGES);
bs = &bp->stages[stage_num++];
required_string("$multi_text");
if ( Fred_running ) {
stuff_string(bs->new_text, F_MULTITEXT, MAX_BRIEF_LEN);
} else {
bs->new_text = stuff_and_malloc_string(F_MULTITEXT, NULL, MAX_BRIEF_LEN);
}
required_string("$voice:");
stuff_string(bs->voice, F_FILESPEC, MAX_FILENAME_LEN);
required_string("$camera_pos:");
stuff_vector(&bs->camera_pos);
required_string("$camera_orient:");
stuff_matrix(&bs->camera_orient);
required_string("$camera_time:");
stuff_int(&bs->camera_time);
if ( optional_string("$num_lines:") ) {
stuff_int(&bs->num_lines);
if ( Fred_running ) {
Assert(bs->lines!=NULL);
} else {
if ( bs->num_lines > 0 ) {
bs->lines = (brief_line *)vm_malloc(sizeof(brief_line)*bs->num_lines);
Assert(bs->lines!=NULL);
}
}
for (i=0; i<bs->num_lines; i++) {
required_string("$line_start:");
stuff_int(&bs->lines[i].start_icon);
required_string("$line_end:");
stuff_int(&bs->lines[i].end_icon);
}
}
else {
bs->num_lines = 0;
}
required_string("$num_icons:");
stuff_int(&bs->num_icons);
if ( Fred_running ) {
Assert(bs->icons!=NULL);
} else {
if ( bs->num_icons > 0 ) {
bs->icons = (brief_icon *)vm_malloc(sizeof(brief_icon)*bs->num_icons);
Assert(bs->icons!=NULL);
}
}
if ( optional_string("$flags:") )
stuff_int(&bs->flags);
else
bs->flags = 0;
if ( optional_string("$formula:") )
bs->formula = get_sexp_main();
else
bs->formula = Locked_sexp_true;
Assert(bs->num_icons <= MAX_STAGE_ICONS );
// static alias stuff - stupid, but it seems to be necessary
static char *temp_team_names[MAX_IFFS];
for (i = 0; i < Num_iffs; i++)
temp_team_names[i] = Iff_info[i].iff_name;
while (required_string_either("$end_stage", "$start_icon"))
{
required_string("$start_icon");
Assert(icon_num < MAX_STAGE_ICONS);
bi = &bs->icons[icon_num++];
required_string("$type:");
stuff_int(&bi->type);
// Goober5000 - import
if (flags & MPF_IMPORT_FSM)
{
// someone changed the jump node icon to a Knossos, so change it back
if (bi->type == ICON_KNOSSOS_DEVICE)
bi->type = ICON_JUMP_NODE;
// change largeship to transport
else if (bi->type == ICON_LARGESHIP)
bi->type = ICON_TRANSPORT;
// ditto
else if (bi->type == ICON_LARGESHIP_WING)
bi->type = ICON_TRANSPORT_WING;
}
find_and_stuff("$team:", &bi->team, F_NAME, temp_team_names, Num_iffs, "team name");
find_and_stuff("$class:", &bi->ship_class, F_NAME, Ship_class_names, Num_ship_classes, "ship class");
// Goober5000 - import
if (flags & MPF_IMPORT_FSM)
{
// the Faustus is a largeship
if (!strnicmp(Ship_info[bi->ship_class].name, "GTSC Faustus", 12))
{
if (bi->type == ICON_CRUISER)
bi->type = ICON_LARGESHIP;
else if (bi->type == ICON_CRUISER_WING)
bi->type = ICON_LARGESHIP_WING;
}
// the Demon is a support ship :p
else if (!strnicmp(Ship_info[bi->ship_class].name, "SD Demon", 8))
{
bi->type = ICON_SUPPORT_SHIP;
}
// the Hades is a supercap
else if (!strnicmp(Ship_info[bi->ship_class].name, "GTD Hades", 9))
{
bi->type = ICON_SUPERCAP;
}
}
required_string("$pos:");
stuff_vector(&bi->pos);
bi->label[0] = 0;
if (optional_string("$label:"))
stuff_string(bi->label, F_MESSAGE, MAX_LABEL_LEN);
if (optional_string("+id:")) {
stuff_int(&bi->id);
if (bi->id >= Cur_brief_id)
Cur_brief_id = bi->id + 1;
} else {
bi->id = -1;
for (i=0; i<stage_num-1; i++)
for (j=0; j < bp->stages[i].num_icons; j++)
{
if (!stricmp(bp->stages[i].icons[j].label, bi->label))
bi->id = bp->stages[i].icons[j].id;
}
if (bi->id < 0)
bi->id = Cur_brief_id++;
}
bi->flags=0;
int val;
required_string("$hlight:");
stuff_int(&val);
if ( val>0 ) {
bi->flags |= BI_HIGHLIGHT;
}
if (optional_string("$mirror:"))
{
stuff_int(&val);
if ( val>0 ) {
bi->flags |= BI_MIRROR_ICON;
}
}
required_string("$multi_text");
// stuff_string(bi->text, F_MULTITEXT, MAX_ICON_TEXT_LEN);
stuff_string(not_used_text, F_MULTITEXT, MAX_ICON_TEXT_LEN);
required_string("$end_icon");
} // end while
Assert(bs->num_icons == icon_num);
icon_num = 0;
required_string("$end_stage");
} // end while
Assert(bp->num_stages == stage_num);
required_string("$end_briefing");
}
if ( nt != Num_teams )
Error(LOCATION, "Not enough briefings in mission file. There are %d teams and only %d briefings.", Num_teams, nt );
}
// -------------------------------------------------------------------------------------------------
// parse_debriefing_old()
//
// Parse the data required for the mission debriefings
void parse_debriefing_old(mission *pm)
{
int junk;
char waste[MAX_DEBRIEF_LEN];
if ( !optional_string("#Debriefing") )
return;
required_string("$num_debriefings:");
stuff_int(&junk);
while (required_string_either("#Players", "$start_debriefing")) {
required_string("$start_debriefing");
required_string("$formula:");
junk = get_sexp_main();
required_string("$num_stages:");
stuff_int(&junk);
while (required_string_either("$end_debriefing", "$start_stage")) {
required_string("$start_stage");
required_string("$multi_text");
stuff_string(waste, F_MULTITEXT, MAX_DEBRIEF_LEN);
required_string("$voice:");
stuff_string(waste, F_FILESPEC, MAX_DEBRIEF_LEN);
required_string("$end_stage");
} // end while
required_string("$end_debriefing");
} // end while
}
// -------------------------------------------------------------------------------------------------
// parse_debriefing_new()
//
// Parse the data required for the mission debriefings
void parse_debriefing_new(mission *pm)
{
int stage_num, nt;
debriefing *db;
debrief_stage *dbs;
debrief_reset();
// next code should be old -- hopefully not called anymore
//if (!optional_string("#Debriefing_info")) {
// parse_debriefing_old(pm);
// return;
//}
// 2/3/98 -- MWA. We can now have multiple briefings and debriefings on a team
for ( nt = 0; nt < Num_teams; nt++ ) {
if ( !optional_string("#Debriefing_info") )
break;
stage_num = 0;
db = &Debriefings[nt];
required_string("$Num stages:");
stuff_int(&db->num_stages);
Assert(db->num_stages <= MAX_DEBRIEF_STAGES);
while (required_string_either("#", "$Formula")) {
Assert(stage_num < MAX_DEBRIEF_STAGES);
dbs = &db->stages[stage_num++];
required_string("$Formula:");
dbs->formula = get_sexp_main();
required_string("$multi text");
if ( Fred_running ) {
stuff_string(dbs->new_text, F_MULTITEXT, MAX_DEBRIEF_LEN);
} else {
dbs->new_text = stuff_and_malloc_string(F_MULTITEXT, NULL, MAX_DEBRIEF_LEN);
}
required_string("$Voice:");
stuff_string(dbs->voice, F_FILESPEC, MAX_FILENAME_LEN);
required_string("$Recommendation text:");
if ( Fred_running ) {
stuff_string( dbs->new_recommendation_text, F_MULTITEXT, MAX_RECOMMENDATION_LEN);
} else {
dbs->new_recommendation_text = stuff_and_malloc_string( F_MULTITEXT, NULL, MAX_RECOMMENDATION_LEN);
}
} // end while
Assert(db->num_stages == stage_num);
}
if ( nt != Num_teams )
Error(LOCATION, "Not enough debriefings for mission. There are %d teams and only %d debriefings;\n", Num_teams, nt );
}
void position_ship_for_knossos_warpin(p_object *p_objp)
{
object *objp = p_objp->created_object;
ship *shipp = &Ships[objp->instance];
object *knossos_objp;
// Assume no valid knossos device
shipp->special_warp_objnum = -1;
// find knossos device
int found = FALSE;
for (ship_obj *so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
{
knossos_objp = &Objects[so->objnum];
if (Ship_info[Ships[knossos_objp->instance].ship_info_index].flags & SIF_KNOSSOS_DEVICE)
{
// be close to the right device (allow multiple knossoses)
if ( vm_vec_dist_quick(&knossos_objp->pos, &p_objp->pos) < 2.0f*(knossos_objp->radius + objp->radius) )
{
found = TRUE;
break;
}
}
}
if (!found)
return;
// set ship special_warp_objnum
shipp->special_warp_objnum = OBJ_INDEX(knossos_objp);
// position self for warp on plane of device
vec3d new_point;
polymodel *pm = model_get(Ship_info[shipp->ship_info_index].model_num);
float dist = fvi_ray_plane(&new_point, &knossos_objp->pos, &knossos_objp->orient.vec.fvec, &p_objp->pos, &p_objp->orient.vec.fvec, 0.0f);
float desired_dist = -pm->mins.xyz.z;
vm_vec_scale_add2(&objp->pos, &objp->orient.vec.fvec, (dist - desired_dist));
// if ship is BIG or HUGE, make it go through the center of the knossos
if (Ship_info[shipp->ship_info_index].flags & SIF_HUGE_SHIP)
{
vec3d offset;
vm_vec_sub(&offset, &knossos_objp->pos, &new_point);
vm_vec_add2(&knossos_objp->pos, &offset);
}
}
// Goober5000
// This is conceptually almost the same as obj_move_one_docked_object and is used in the same way.
void parse_dock_one_docked_object(p_object *pobjp, p_object *parent_pobjp)
{
int dockpoint, parent_dockpoint;
char *dockpoint_name, *parent_dockpoint_name;
object *objp, *parent_objp;
// get the actual in-game objects that will be docked
objp = pobjp->created_object;
parent_objp = parent_pobjp->created_object;
// check valid
if (!objp || !parent_objp)
{
Int3();
return;
}
// get dockpoint names
dockpoint_name = dock_find_dockpoint_used_by_object(pobjp, parent_pobjp);
parent_dockpoint_name = dock_find_dockpoint_used_by_object(parent_pobjp, pobjp);
// check valid
if (!dockpoint_name || !parent_dockpoint_name)
{
Int3();
return;
}
// resolve names to dockpoints
dockpoint = model_find_dock_name_index(Ship_info[Ships[objp->instance].ship_info_index].model_num, dockpoint_name);
parent_dockpoint = model_find_dock_name_index(Ship_info[Ships[parent_objp->instance].ship_info_index].model_num, parent_dockpoint_name);
// check valid
if ((dockpoint < 0) || (parent_dockpoint < 0))
{
Int3();
return;
}
// dock them
nprintf(("AI", "Initially docked: %s to parent %s\n", Ships[objp->instance].ship_name, Ships[parent_objp->instance].ship_name));
ai_dock_with_object(objp, dockpoint, parent_objp, parent_dockpoint, 89, AIDO_DOCK_NOW);
}
int parse_create_object_sub(p_object *objp);
// Goober5000
void parse_create_docked_object_helper(p_object *pobjp, p_dock_function_info *infop)
{
// create the object
parse_create_object_sub(pobjp);
// remove the object from the arrival list
// (don't remove the leader, because he'll be removed the usual way)
if (pobjp != infop->parameter_variables.objp_value)
{
// If an object is present at the beginning of the mission, it may not have been put on the arrival
// list. This is totally dependent on the order the objects are parsed in the mission file because
// that is the order in which they are created. All the objects in a docked group are created
// simultaneously when and only when the leader is created.
// if it's on the list, remove it
if (parse_object_on_arrival_list(pobjp))
list_remove(&Ship_arrival_list, pobjp);
}
}
// Goober5000
// This is a bit tricky because of the way initial docking is now handled.
// Docking groups require special treatment.
int parse_create_object(p_object *pobjp)
{
object *objp;
// if this guy is part of a dock group, create the entire group, starting with the leader
if (object_is_docked(pobjp))
{
p_dock_function_info dfi;
// we should only be calling this for dock leaders, because the dock leader
// governs the creation of his entire group
Assert((pobjp->flags & P_SF_DOCK_LEADER));
// if the leader will be destroyed before the mission starts, then *only* create the leader;
// don't create the rest of the group (this is what retail did)
if (pobjp->destroy_before_mission_time >= 0)
return parse_create_object_sub(pobjp);
// store the leader as a parameter
dfi.parameter_variables.objp_value = pobjp;
// Just as we couldn't create the parse dock trees until we had parsed them all,
// we can't dock the in-game objects until we have created them all. :p
// create all the objects
dock_evaluate_all_docked_objects(pobjp, &dfi, parse_create_docked_object_helper);
// dock all the objects
dock_dock_docked_objects(pobjp);
// now clear the handled flags
parse_object_clear_all_handled_flags();
}
// create normally
else
{
parse_create_object_sub(pobjp);
}
// get the main object
objp = pobjp->created_object;
// warp it in (moved from parse_create_object_sub)
if ((Game_mode & GM_IN_MISSION) && (!Fred_running) && (!Game_restoring))
{
if ((Ships[objp->instance].wingnum < 0) && (pobjp->arrival_location != ARRIVE_FROM_DOCK_BAY))
{
shipfx_warpin_start(objp);
}
}
// return the main object's objnum
return OBJ_INDEX(objp);
}
// Given a stuffed p_object struct, create an object and fill in the necessary fields.
// Return object number.
int parse_create_object_sub(p_object *p_objp)
{
int i, j, k, objnum, shipnum;
ai_info *aip;
ship_subsys *ptr;
ship *shipp;
ship_info *sip;
subsys_status *sssp;
ship_weapon *wp;
// texture replacements
char texture_file[MAX_FILENAME_LEN];
char *p;
polymodel *pm;
MONITOR_INC(NumShipArrivals, 1);
// base level creation - need ship name in case of duplicate textures
objnum = ship_create(&p_objp->orient, &p_objp->pos, p_objp->ship_class, p_objp->name);
Assert(objnum != -1);
shipnum = Objects[objnum].instance;
shipp = &Ships[shipnum];
sip = &Ship_info[shipp->ship_info_index];
// Goober5000 - make the parse object aware of what it was created as
p_objp->created_object = &Objects[objnum];
// if arriving through knossos, adjust objpj->pos to plane of knossos and set flag
// special warp is single player only
if ((p_objp->flags & P_KNOSSOS_WARP_IN) && !(Game_mode & GM_MULTIPLAYER))
{
if (!Fred_running)
position_ship_for_knossos_warpin(p_objp);
}
shipp->group = p_objp->group;
shipp->team = p_objp->team;
strcpy(shipp->ship_name, p_objp->name);
shipp->escort_priority = p_objp->escort_priority;
shipp->special_exp_index = p_objp->special_exp_index;
// Goober5000
shipp->special_hitpoint_index = p_objp->special_hitpoint_index;
shipp->ship_max_shield_strength = p_objp->ship_max_shield_strength;
shipp->ship_max_hull_strength = p_objp->ship_max_hull_strength;
// Goober5000 - ugh, this is really stupid having to do this here; if the
// ship creation code was better organized this wouldn't be necessary
if (shipp->special_hitpoint_index >= 0)
{
float hull_factor = shipp->ship_max_hull_strength / sip->max_hull_strength;
ship_subsys *ss;
for (ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list) && ss != NULL; ss = GET_NEXT(ss))
{
ss->max_hits *= hull_factor;
if (Fred_running)
ss->current_hits = 0.0f;
else
ss->current_hits = ss->max_hits;
}
ship_recalc_subsys_strength(shipp);
}
// Goober5000 - this is also stupid; this is in ship_set but it needs to be done here because of the special hitpoints mod
Objects[objnum].hull_strength = shipp->ship_max_hull_strength;
shipp->respawn_priority = p_objp->respawn_priority;
// if this is a multiplayer dogfight game, and its from a player wing, make it team traitor
if ((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_DOGFIGHT) && (p_objp->wingnum >= 0))
{
for (i = 0; i < MAX_STARTING_WINGS; i++)
{
if (!stricmp(Starting_wing_names[i], Wings[p_objp->wingnum].name))
shipp->team = Iff_traitor;
}
}
if (!Fred_running)
{
ship_assign_sound(&Ships[shipnum]);
}
aip = &(Ai_info[shipp->ai_index]);
aip->behavior = p_objp->behavior;
aip->mode = aip->behavior;
// alternate type name
shipp->alt_type_index = p_objp->alt_type_index;
aip->ai_class = p_objp->ai_class;
shipp->weapons.ai_class = p_objp->ai_class; // Fred uses this instead of above.
// must reset the number of ai goals when the object is created
for (i = 0; i < MAX_AI_GOALS; i++)
{
aip->goals[i].ai_mode = AI_GOAL_NONE;
aip->goals[i].signature = -1;
aip->goals[i].priority = -1;
aip->goals[i].flags = 0;
}
shipp->cargo1 = p_objp->cargo1;
shipp->arrival_location = p_objp->arrival_location;
shipp->arrival_distance = p_objp->arrival_distance;
shipp->arrival_anchor = p_objp->arrival_anchor;
shipp->arrival_path_mask = p_objp->arrival_path_mask;
shipp->arrival_cue = p_objp->arrival_cue;
shipp->arrival_delay = p_objp->arrival_delay;
shipp->departure_location = p_objp->departure_location;
shipp->departure_anchor = p_objp->departure_anchor;
shipp->departure_path_mask = p_objp->departure_path_mask;
shipp->departure_cue = p_objp->departure_cue;
shipp->departure_delay = p_objp->departure_delay;
shipp->determination = p_objp->determination;
shipp->wingnum = p_objp->wingnum;
shipp->hotkey = p_objp->hotkey;
shipp->score = p_objp->score;
shipp->persona_index = p_objp->persona_index;
// reset texture animations
shipp->base_texture_anim_frametime = game_get_overall_frametime();
// handle the replacement textures
if (p_objp->num_texture_replacements > 0)
{
shipp->replacement_textures = (int *) vm_malloc(MAX_MODEL_TEXTURES * sizeof(int));
for (i = 0; i < MAX_MODEL_TEXTURES; i++)
shipp->replacement_textures[i] = -1;
}
// now fill them in
for (i = 0; i < p_objp->num_texture_replacements; i++)
{
pm = model_get(sip->model_num);
// look for textures
for (j = 0; j < pm->n_textures; j++)
{
// get texture file name
bm_get_filename(pm->maps[j].base_map.texture, texture_file);
// get rid of file extension
p = strchr(texture_file, '.');
if (p)
{
mprintf(("ignoring extension on file '%s'\n", texture_file));
*p = 0;
}
// now compare the extension-less texture file names
if (!stricmp(texture_file, p_objp->replacement_textures[i].old_texture))
{
// replace it
shipp->replacement_textures[j] = p_objp->replacement_textures[i].new_texture_id;
break;
}
}
}
// check the parse object's flags for possible things to set on this newly created ship
resolve_parse_flags(&Objects[objnum], p_objp->flags, p_objp->flags2);
// other flag checks
////////////////////////
if (p_objp->ship_max_shield_strength == 0.0f)
Objects[objnum].flags |= OF_NO_SHIELDS;
// don't set the flag if the mission is ongoing in a multiplayer situation. This will be set by the players in the
// game only before the game or during respawning.
// MWA -- changed the next line to remove the !(Game_mode & GM_MULTIPLAYER). We shouldn't be setting
// this flag in single player mode -- it gets set in post process mission.
//if ((p_objp->flags & P_OF_PLAYER_START) && (((Game_mode & GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) || !(Game_mode & GM_MULTIPLAYER)))
if ((p_objp->flags & P_OF_PLAYER_START) && (Fred_running || ((Game_mode & GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION))))
Objects[objnum].flags |= OF_PLAYER_SHIP;
// a couple of ai_info flags. Also, do a reasonable default for the kamikaze damage regardless of
// whether this flag is set or not
if (p_objp->flags & P_AIF_KAMIKAZE)
{
Ai_info[shipp->ai_index].ai_flags |= AIF_KAMIKAZE;
Ai_info[shipp->ai_index].kamikaze_damage = p_objp->kamikaze_damage;
}
if (p_objp->flags & P_AIF_NO_DYNAMIC)
Ai_info[shipp->ai_index].ai_flags |= AIF_NO_DYNAMIC;
if (p_objp->flags & P_SF_RED_ALERT_STORE_STATUS)
{
if (!(Game_mode & GM_MULTIPLAYER)) {
shipp->flags |= SF_RED_ALERT_STORE_STATUS;
}
}
if (p_objp->flags & P_KNOSSOS_WARP_IN)
{
Objects[objnum].flags |= OF_SPECIAL_WARP;
Knossos_warp_ani_used = 1;
}
// set the orders that this ship will accept. It will have already been set to default from the
// ship create code, so only set them if the parse object flags say they are unique
if (p_objp->flags & P_SF_USE_UNIQUE_ORDERS)
{
shipp->orders_accepted = p_objp->orders_accepted;
// MWA 5/15/98 -- Added the following debug code because some orders that ships
// will accept were apparently written out incorrectly with Fred. This Int3() should
// trap these instances.
#ifndef NDEBUG
if (Fred_running)
{
int default_orders, remaining_orders;
default_orders = ship_get_default_orders_accepted(&Ship_info[shipp->ship_info_index]);
remaining_orders = p_objp->orders_accepted & ~default_orders;
if (remaining_orders)
{
Warning(LOCATION, "Ship %s has orders which it will accept that are\nnot part of default orders accepted.\n\nPlease reedit this ship and change the orders again\n", shipp->ship_name);
}
}
#endif
}
if (p_objp->flags & P_SF_DOCK_LEADER)
shipp->flags |= SF_DOCK_LEADER;
if (p_objp->flags & P_SF_WARP_BROKEN)
shipp->flags |= SF_WARP_BROKEN;
if (p_objp->flags & P_SF_WARP_NEVER)
shipp->flags |= SF_WARP_NEVER;
////////////////////////
// if ship is in a wing, and the wing's no_warp_effect flag is set, then set the equivalent
// flag for the ship
if ((shipp->wingnum != -1) && (Wings[shipp->wingnum].flags & WF_NO_ARRIVAL_WARP))
shipp->flags |= SF_NO_ARRIVAL_WARP;
if ((shipp->wingnum != -1) && (Wings[shipp->wingnum].flags & WF_NO_DEPARTURE_WARP))
shipp->flags |= SF_NO_DEPARTURE_WARP;
// ditto for Kazan
if ((shipp->wingnum != -1) && (Wings[shipp->wingnum].flags & WF_NAV_CARRY))
shipp->flags2 |= SF2_NAVPOINT_CARRY;
// if the wing index and wing pos are set for this parse object, set them for the ship. This
// is useful in multiplayer when ships respawn
shipp->wing_status_wing_index = p_objp->wing_status_wing_index;
shipp->wing_status_wing_pos = p_objp->wing_status_wing_pos;
// set up the ai_goals for this object -- all ships created here are AI controlled.
if (p_objp->ai_goals != -1)
{
int sexp;
// set up the ai goals for this object.
for (sexp = CDR(p_objp->ai_goals); sexp != -1; sexp = CDR(sexp))
ai_add_ship_goal_sexp(sexp, AIG_TYPE_EVENT_SHIP, aip);
// free the sexpression nodes only for non-wing ships. wing code will handle its own case
if (p_objp->wingnum < 0)
free_sexp2(p_objp->ai_goals); // free up sexp nodes for reuse, since they aren't needed anymore.
}
Assert(sip->model_num != -1);
// initialize subsystem statii here. The subsystems are given a percentage damaged. So a percent value
// of 20% means that the subsystem is 20% damaged (*not* 20% of max hits). This is opposite the way
// that the initial velocity/hull strength/shields work
i = p_objp->subsys_count;
while (i--)
{
sssp = &Subsys_status[p_objp->subsys_index + i];
if (!stricmp(sssp->name, NOX("Pilot")))
{
wp = &shipp->weapons;
if (sssp->primary_banks[0] != SUBSYS_STATUS_NO_CHANGE)
{
for (j=k=0; j<MAX_SHIP_PRIMARY_BANKS; j++)
{
if ((sssp->primary_banks[j] >= 0) || Fred_running)
{
wp->primary_bank_weapons[k] = sssp->primary_banks[j];
// next
k++;
}
}
if (Fred_running)
wp->num_primary_banks = sip->num_primary_banks;
else
wp->num_primary_banks = k;
}
if (sssp->secondary_banks[0] != SUBSYS_STATUS_NO_CHANGE)
{
for (j = k = 0; j < MAX_SHIP_SECONDARY_BANKS; j++)
{
if ((sssp->secondary_banks[j] >= 0) || Fred_running)
wp->secondary_bank_weapons[k++] = sssp->secondary_banks[j];
}
if (Fred_running)
wp->num_secondary_banks = sip->num_secondary_banks;
else
wp->num_secondary_banks = k;
}
// primary weapons too - Goober5000
for (j = 0; j < wp->num_primary_banks; j++)
{
if (Fred_running)
{
wp->primary_bank_ammo[j] = sssp->primary_ammo[j];
}
else
{
int capacity = fl2i(sssp->primary_ammo[j]/100.0f * sip->primary_bank_ammo_capacity[j] + 0.5f);
wp->primary_bank_ammo[j] = fl2i(capacity / Weapon_info[wp->primary_bank_weapons[j]].cargo_size + 0.5f);
}
}
for (j = 0; j < wp->num_secondary_banks; j++)
{
if (Fred_running)
{
wp->secondary_bank_ammo[j] = sssp->secondary_ammo[j];
}
else
{
int capacity = fl2i(sssp->secondary_ammo[j]/100.0f * sip->secondary_bank_ammo_capacity[j] + 0.5f);
wp->secondary_bank_ammo[j] = fl2i(capacity / Weapon_info[wp->secondary_bank_weapons[j]].cargo_size + 0.5f);
}
}
continue;
}
ptr = GET_FIRST(&shipp->subsys_list);
while (ptr != END_OF_LIST(&shipp->subsys_list))
{
// check the mission flag to possibly free all beam weapons - Goober5000, taken from SEXP.CPP
if (The_mission.flags & MISSION_FLAG_BEAM_FREE_ALL_BY_DEFAULT)
{
// mark all turrets as beam free
if(ptr->system_info->type == SUBSYSTEM_TURRET)
{
ptr->weapons.flags |= SW_FLAG_BEAM_FREE;
ptr->turret_next_fire_stamp = timestamp((int) frand_range(50.0f, 4000.0f));
}
}
if (!subsystem_stricmp(ptr->system_info->subobj_name, sssp->name))
{
if (Fred_running)
{
ptr->current_hits = sssp->percent;
ptr->max_hits = 100.0f;
}
else
{
ptr->max_hits = ptr->system_info->max_subsys_strength * (shipp->ship_max_hull_strength / sip->max_hull_strength);
float new_hits;
new_hits = ptr->max_hits * (100.0f - sssp->percent) / 100.f;
shipp->subsys_info[ptr->system_info->type].current_hits -= (ptr->max_hits - new_hits);
if ((100.0f - sssp->percent) < 0.5)
{
ptr->current_hits = 0.0f;
ptr->submodel_info_1.blown_off = 1;
}
else
{
ptr->current_hits = new_hits;
}
}
if (sssp->primary_banks[0] != SUBSYS_STATUS_NO_CHANGE)
for (j=0; j<MAX_SHIP_PRIMARY_BANKS; j++)
ptr->weapons.primary_bank_weapons[j] = sssp->primary_banks[j];
if (sssp->secondary_banks[0] != SUBSYS_STATUS_NO_CHANGE)
for (j=0; j<MAX_SHIP_SECONDARY_BANKS; j++)
ptr->weapons.secondary_bank_weapons[j] = sssp->secondary_banks[j];
// Goober5000
for (j = 0; j < MAX_SHIP_PRIMARY_BANKS; j++)
ptr->weapons.primary_bank_ammo[j] = sssp->primary_ammo[j];
// AL 3-5-98: This is correct for FRED, but not for FreeSpace... but is this even used?
// As far as I know, turrets cannot run out of ammo
for (j = 0; j < MAX_SHIP_SECONDARY_BANKS; j++)
ptr->weapons.secondary_bank_ammo[j] = sssp->secondary_ammo[j];
ptr->subsys_cargo_name = sssp->subsys_cargo_name;
if (sssp->ai_class != SUBSYS_STATUS_NO_CHANGE)
ptr->weapons.ai_class = sssp->ai_class;
ptr->turret_best_weapon = -1;
ptr->turret_animation_position = 0; // MA_POS_NOT_SET -> model animation position is not set
ptr->turret_animation_done_time = 0;
}
ptr = GET_NEXT(ptr);
}
}
// initial hull strength, shields, and velocity are all expressed as a percentage of the max value/
// so a initial_hull value of 90% means 90% of max. This way is opposite of how subsystems are dealt
// with
if (Fred_running)
{
Objects[objnum].phys_info.speed = (float) p_objp->initial_velocity;
// shipp->hull_hit_points_taken = (float) p_objp->initial_hull;
Objects[objnum].hull_strength = (float) p_objp->initial_hull;
Objects[objnum].shield_quadrant[0] = (float) p_objp->initial_shields;
}
else
{
int max_allowed_sparks, num_sparks, i;
polymodel *pm;
// shipp->hull_hit_points_taken = (float) p_objp->initial_hull * sip->max_hull_hit_points / 100.0f;
Objects[objnum].hull_strength = p_objp->initial_hull * shipp->ship_max_hull_strength / 100.0f;
for (i = 0; i<MAX_SHIELD_SECTIONS; i++)
Objects[objnum].shield_quadrant[i] = (float) (p_objp->initial_shields * get_max_shield_quad(&Objects[objnum]) / 100.0f);
// initial velocities now do not apply to ships which warp in after mission starts
if (!(Game_mode & GM_IN_MISSION))
{
Objects[objnum].phys_info.speed = (float) p_objp->initial_velocity * sip->max_speed / 100.0f;
Objects[objnum].phys_info.vel.xyz.z = Objects[objnum].phys_info.speed;
Objects[objnum].phys_info.prev_ramp_vel = Objects[objnum].phys_info.vel;
Objects[objnum].phys_info.desired_vel = Objects[objnum].phys_info.vel;
}
// recalculate damage of subsystems
ship_recalc_subsys_strength(&Ships[shipnum]);
// create sparks on a ship whose hull is damaged. We will create two sparks for every 20%
// of hull damage done. 100 means no sparks. between 80 and 100 do two sparks. 60 and 80 is
// four, etc.
pm = model_get(sip->model_num);
max_allowed_sparks = get_max_sparks(&Objects[objnum]);
num_sparks = (int)((100.0f - p_objp->initial_hull) / 5.0f);
if (num_sparks > max_allowed_sparks)
num_sparks = max_allowed_sparks;
for (i = 0; i < num_sparks; i++)
{
vec3d v1, v2;
// DA 10/20/98 - sparks must be chosen on the hull and not any submodel
submodel_get_two_random_points(sip->model_num, pm->detail[0], &v1, &v2);
ship_hit_sparks_no_rotate(&Objects[objnum], &v1);
// ship_hit_sparks_no_rotate(&Objects[objnum], &v2);
}
}
// in mission, we add a log entry -- set ship positions for ships not in wings, and then do
// warpin effect
if ((Game_mode & GM_IN_MISSION) && (!Fred_running))
{
mission_log_add_entry(LOG_SHIP_ARRIVED, shipp->ship_name, NULL);
if (!Game_restoring)
{
// if this ship isn't in a wing, determine its arrival location
if (shipp->wingnum == -1)
{
int location;
// multiplayer clients set the arrival location of ships to be at location since their
// position has already been determined. Don't actually set the variable since we
// don't want the warp effect to show if coming from a dock bay.
location = p_objp->arrival_location;
if (MULTIPLAYER_CLIENT)
location = ARRIVE_AT_LOCATION;
mission_set_arrival_location(p_objp->arrival_anchor, location, p_objp->arrival_distance, objnum, p_objp->arrival_path_mask, NULL, NULL);
// Goober5000 - warpin start moved to parse_create_object
}
}
// possibly add this ship to a hotkey set
if ((shipp->wingnum == -1) && (shipp->hotkey != -1))
mission_hotkey_mf_add(shipp->hotkey, shipp->objnum, HOTKEY_MISSION_FILE_ADDED);
else if ((shipp->wingnum != -1) && (Wings[shipp->wingnum].hotkey != -1))
mission_hotkey_mf_add(Wings[shipp->wingnum].hotkey, shipp->objnum, HOTKEY_MISSION_FILE_ADDED);
// possibly add this ship to the hud escort list
if (shipp->flags & SF_ESCORT)
hud_add_remove_ship_escort(objnum, 1);
}
// for multiplayer games, make a call to the network code to assign the object signature
// of the newly created object. The network host of the netgame will always assign a signature
// to a newly created object. The network signature will get to the clients of the game in
// different manners depending on whether or not an individual ship or a wing was created.
if (Game_mode & GM_MULTIPLAYER)
{
Objects[objnum].net_signature = p_objp->net_signature;
if ((Game_mode & GM_IN_MISSION) && MULTIPLAYER_MASTER && (p_objp->wingnum == -1))
send_ship_create_packet(&Objects[objnum], (p_objp == Arriving_support_ship) ? 1 : 0);
}
// if recording a demo, post the event
if(Game_mode & GM_DEMO_RECORD)
demo_POST_obj_create(p_objp->name, Objects[objnum].signature);
return objnum;
}
// Goober5000
void resolve_parse_flags(object *objp, int parse_flags, int parse_flags2)
{
Assert(objp != NULL);
ship *shipp = &Ships[objp->instance];
if (parse_flags & P_SF_CARGO_KNOWN)
shipp->flags |= SF_CARGO_REVEALED;
if (parse_flags & P_SF_IGNORE_COUNT)
shipp->flags |= SF_IGNORE_COUNT;
if (parse_flags & P_OF_PROTECTED)
objp->flags |= OF_PROTECTED;
if (parse_flags & P_SF_REINFORCEMENT)
shipp->flags |= SF_REINFORCEMENT;
if (parse_flags & P_OF_NO_SHIELDS)
objp->flags |= OF_NO_SHIELDS;
if (parse_flags & P_SF_ESCORT)
shipp->flags |= SF_ESCORT;
// P_OF_PLAYER_START is handled in parse_create_object_sub
if (parse_flags & P_SF_NO_ARRIVAL_MUSIC)
shipp->flags |= SF_NO_ARRIVAL_MUSIC;
if (parse_flags & P_SF_NO_ARRIVAL_WARP)
shipp->flags |= SF_NO_ARRIVAL_WARP;
if (parse_flags & P_SF_NO_DEPARTURE_WARP)
shipp->flags |= SF_NO_DEPARTURE_WARP;
if (parse_flags & P_SF_LOCKED)
shipp->flags |= SF_LOCKED;
if (parse_flags & P_OF_INVULNERABLE)
objp->flags |= OF_INVULNERABLE;
if (parse_flags & P_SF_HIDDEN_FROM_SENSORS)
shipp->flags |= SF_HIDDEN_FROM_SENSORS;
if (parse_flags & P_SF_SCANNABLE)
shipp->flags |= SF_SCANNABLE;
// P_AIF_KAMIKAZE, P_AIF_NO_DYNAMIC, and P_SF_RED_ALERT_CARRY are handled in parse_create_object_sub
if (parse_flags & P_OF_BEAM_PROTECTED)
objp->flags |= OF_BEAM_PROTECTED;
if (parse_flags & P_SF_GUARDIAN)
shipp->ship_guardian_threshold = SHIP_GUARDIAN_THRESHOLD_DEFAULT;
if (parse_flags & P_SF_VAPORIZE)
shipp->flags |= SF_VAPORIZE;
if (parse_flags & P_SF2_STEALTH)
shipp->flags2 |= SF2_STEALTH;
if (parse_flags & P_SF2_FRIENDLY_STEALTH_INVIS)
shipp->flags2 |= SF2_FRIENDLY_STEALTH_INVIS;
if (parse_flags & P_SF2_DONT_COLLIDE_INVIS)
shipp->flags2 |= SF2_DONT_COLLIDE_INVIS;
if (parse_flags2 & P2_SF2_PRIMITIVE_SENSORS)
shipp->flags2 |= SF2_PRIMITIVE_SENSORS;
if (parse_flags2 & P2_SF2_NO_SUBSPACE_DRIVE)
shipp->flags2 |= SF2_NO_SUBSPACE_DRIVE;
if (parse_flags2 & P2_SF2_NAV_CARRY_STATUS)
shipp->flags2 |= SF2_NAVPOINT_CARRY;
if (parse_flags2 & P2_SF2_AFFECTED_BY_GRAVITY)
shipp->flags2 |= SF2_AFFECTED_BY_GRAVITY;
if (parse_flags2 & P2_SF2_TOGGLE_SUBSYSTEM_SCANNING)
shipp->flags2 |= SF2_TOGGLE_SUBSYSTEM_SCANNING;
if (parse_flags2 & P2_OF_TARGETABLE_AS_BOMB)
objp->flags |= OF_TARGETABLE_AS_BOMB;
if (parse_flags2 & P2_SF2_NO_BUILTIN_MESSAGES)
shipp->flags2 |= SF2_NO_BUILTIN_MESSAGES;
if (parse_flags2 & P2_SF2_PRIMARIES_LOCKED)
shipp->flags2 |= SF2_PRIMARIES_LOCKED;
if (parse_flags2 & P2_SF2_SECONDARIES_LOCKED)
shipp->flags2 |= SF2_SECONDARIES_LOCKED;
if (parse_flags2 & P2_SF2_NO_DEATH_SCREAM)
shipp->flags2 |= SF2_NO_DEATH_SCREAM;
if (parse_flags2 & P2_SF2_ALWAYS_DEATH_SCREAM)
shipp->flags2 |= SF2_ALWAYS_DEATH_SCREAM;
if (parse_flags2 & P2_SF2_NAV_NEEDSLINK)
shipp->flags2 |= SF2_NAVPOINT_NEEDSLINK;
if (parse_flags2 & P2_SF2_USE_ALT_NAME_AS_CALLSIGN)
shipp->flags2 |= SF2_USE_ALT_NAME_AS_CALLSIGN;
}
// Mp points at the text of an object, which begins with the "$Name:" field.
// Snags all object information. Creating the ship now only happens after everything has been parsed.
//
// flag is parameter that is used to tell what kind information we are retrieving from the mission.
// if we are just getting player starts, then don't create the objects
int parse_object(mission *pm, int flag, p_object *p_objp)
{
int i, j, count, delay;
char name[NAME_LENGTH], flag_strings[MAX_PARSE_OBJECT_FLAGS][NAME_LENGTH];
char flag_strings_2[MAX_PARSE_OBJECT_FLAGS_2][NAME_LENGTH];
Assert(pm != NULL);
// Goober5000
p_objp->created_object = NULL;
p_objp->next = NULL;
p_objp->prev = NULL;
required_string("$Name:");
stuff_string(p_objp->name, F_NAME, NAME_LENGTH);
if (mission_parse_get_parse_object(p_objp->name))
error_display(0, NOX("Redundant ship name: %s\n"), p_objp->name);
find_and_stuff("$Class:", &p_objp->ship_class, F_NAME, Ship_class_names, Num_ship_classes, "ship class");
if (p_objp->ship_class < 0)
{
mprintf(("MISSIONS: Ship \"%s\" has an invalid ship type (ships.tbl probably changed). Making it type 0\n", p_objp->name));
p_objp->ship_class = 0;
Num_unknown_ship_classes++;
}
// if this is a multiplayer dogfight mission, skip support ships
if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_DOGFIGHT) && (Ship_info[p_objp->ship_class].flags & SIF_SUPPORT))
return 0;
// optional alternate name type
p_objp->alt_type_index = -1;
if(optional_string("$Alt:"))
{
// alternate name
stuff_string(name, F_NAME, NAME_LENGTH);
// try and find the alternate name
p_objp->alt_type_index = (char)mission_parse_lookup_alt(name);
Assert(p_objp->alt_type_index >= 0);
if(p_objp->alt_type_index < 0)
mprintf(("Error looking up alternate ship type name!\n"));
else
mprintf(("Using alternate ship type name : %s\n", name));
}
// static alias stuff - stupid, but it seems to be necessary
static char *temp_team_names[MAX_IFFS];
for (i = 0; i < Num_iffs; i++)
temp_team_names[i] = Iff_info[i].iff_name;
find_and_stuff("$Team:", &p_objp->team, F_NAME, temp_team_names, Num_iffs, "team name");
required_string("$Location:");
stuff_vector(&p_objp->pos);
required_string("$Orientation:");
stuff_matrix(&p_objp->orient);
// legacy code, not even used in FS1
if (optional_string("$IFF:"))
{
stuff_string(name, F_NAME, NAME_LENGTH);
}
find_and_stuff("$AI Behavior:", &p_objp->behavior, F_NAME, Ai_behavior_names, Num_ai_behaviors, "AI behavior");
p_objp->ai_goals = -1;
if (optional_string("+AI Class:"))
{
p_objp->ai_class = match_and_stuff(F_NAME, Ai_class_names, Num_ai_classes, "AI class");
if (p_objp->ai_class < 0)
{
Warning(LOCATION, "AI Class for ship %s does not exist in ai.tbl. Setting to first available class.\n", p_objp->name);
p_objp->ai_class = 0;
}
}
else
{
p_objp->ai_class = Ship_info[p_objp->ship_class].ai_class;
}
if (optional_string("$AI Goals:"))
p_objp->ai_goals = get_sexp_main();
if (!required_string_either("$AI Goals:", "$Cargo 1:"))
{
required_string("$AI Goals:");
p_objp->ai_goals = get_sexp_main();
}
p_objp->cargo1 = -1;
int temp;
find_and_stuff_or_add("$Cargo 1:", &temp, F_NAME, Cargo_names, &Num_cargo, MAX_CARGO, "cargo");
p_objp->cargo1 = char(temp);
if (optional_string("$Cargo 2:"))
{
stuff_string(name, F_NAME, NAME_LENGTH);
}
parse_common_object_data(p_objp); // get initial conditions and subsys status
count = 0;
while (required_string_either("$Arrival Location:", "$Status Description:")) {
Assert(count < MAX_OBJECT_STATUS);
find_and_stuff("$Status Description:", &p_objp->status_type[count], F_NAME, Status_desc_names, Num_status_names, "Status Description");
find_and_stuff("$Status:", &p_objp->status[count], F_NAME, Status_type_names, Num_status_names, "Status Type");
find_and_stuff("$Target:", &p_objp->target[count], F_NAME, Status_target_names, Num_status_names, "Target");
count++;
}
p_objp->status_count = count;
p_objp->arrival_anchor = -1;
p_objp->arrival_distance = 0;
p_objp->arrival_path_mask = -1; // -1 only until resolved
find_and_stuff("$Arrival Location:", &p_objp->arrival_location, F_NAME, Arrival_location_names, Num_arrival_names, "Arrival Location");
if (optional_string("+Arrival Distance:"))
{
stuff_int(&p_objp->arrival_distance);
// Goober5000
if ((p_objp->arrival_distance <= 0) && ((p_objp->arrival_location == ARRIVE_NEAR_SHIP) || (p_objp->arrival_location == ARRIVE_IN_FRONT_OF_SHIP)))
{
Warning(LOCATION, "Arrival distance for ship %s cannot be %d. Setting to 1.\n", p_objp->name, p_objp->arrival_distance);
p_objp->arrival_distance = 1;
}
}
if (p_objp->arrival_location != ARRIVE_AT_LOCATION)
{
required_string("$Arrival Anchor:");
stuff_string(name, F_NAME, NAME_LENGTH);
p_objp->arrival_anchor = get_anchor(name);
}
if (optional_string("+Arrival Paths:"))
{
// temporarily use mask to point to the restriction index
p_objp->arrival_path_mask = add_path_restriction();
}
delay = 0;
if (optional_string("+Arrival Delay:"))
{
stuff_int(&delay);
if (delay < 0)
Error(LOCATION, "Cannot have arrival delay < 0 (ship %s)", p_objp->name);
}
if (!Fred_running)
p_objp->arrival_delay = -delay; // use negative numbers to mean we haven't set up a timer yet
else
p_objp->arrival_delay = delay;
required_string("$Arrival Cue:");
p_objp->arrival_cue = get_sexp_main();
if (!Fred_running && (p_objp->arrival_cue >= 0))
{
// eval the arrival cue. if the cue is true, set up the timestamp for the arrival delay
Assert (p_objp->arrival_delay <= 0);
// don't eval arrival_cues when just looking for player information.
// evaluate to determine if sexp is always false.
if (eval_sexp(p_objp->arrival_cue))
p_objp->arrival_delay = timestamp(-p_objp->arrival_delay * 1000);
}
p_objp->departure_anchor = -1;
p_objp->departure_path_mask = -1; // -1 only until resolved
find_and_stuff("$Departure Location:", &p_objp->departure_location, F_NAME, Departure_location_names, Num_arrival_names, "Departure Location");
if (p_objp->departure_location != DEPART_AT_LOCATION)
{
required_string("$Departure Anchor:");
stuff_string(name, F_NAME, NAME_LENGTH);
p_objp->departure_anchor = get_anchor(name);
}
if (optional_string("+Departure Paths:"))
{
// temporarily use mask to point to the restriction index
p_objp->departure_path_mask = add_path_restriction();
}
delay = 0;
if (optional_string("+Departure Delay:"))
{
stuff_int(&delay);
if (delay < 0)
Error(LOCATION, "Cannot have departure delay < 0 (ship %s)", p_objp->name);
}
if (!Fred_running)
p_objp->departure_delay = -delay;
else
p_objp->departure_delay = delay;
required_string("$Departure Cue:");
p_objp->departure_cue = get_sexp_main();
if (optional_string("$Misc Properties:"))
stuff_string(p_objp->misc, F_NAME, NAME_LENGTH);
required_string("$Determination:");
stuff_int(&p_objp->determination);
// set flags
p_objp->flags = 0;
if (optional_string("+Flags:"))
{
count = stuff_string_list(flag_strings, MAX_PARSE_OBJECT_FLAGS);
for (i=0; i<count; i++)
{
for (j=0; j<MAX_PARSE_OBJECT_FLAGS; j++)
{
if (!stricmp(flag_strings[i], Parse_object_flags[j]))
{
p_objp->flags |= (1 << j);
break;
}
}
if (j == MAX_PARSE_OBJECT_FLAGS)
Warning(LOCATION, "Unknown flag in mission file: %s\n", flag_strings[i]);
}
}
// second set - Goober5000
p_objp->flags2 = 0;
if (optional_string("+Flags2:"))
{
count = stuff_string_list(flag_strings_2, MAX_PARSE_OBJECT_FLAGS_2);
for (i=0; i<count; i++)
{
for (j=0; j<MAX_PARSE_OBJECT_FLAGS_2; j++)
{
if (!stricmp(flag_strings_2[i], Parse_object_flags_2[j]))
{
p_objp->flags2 |= (1 << j);
break;
}
}
if (j == MAX_PARSE_OBJECT_FLAGS_2)
Warning(LOCATION, "Unknown flag2 in mission file: %s\n", flag_strings_2[i]);
}
}
// set certain flags that used to be in ship_info - Goober5000
if (Ship_info[p_objp->ship_class].flags & SIF_STEALTH)
p_objp->flags |= P_SF2_STEALTH;
if (Ship_info[p_objp->ship_class].flags & SIF_SHIP_CLASS_DONT_COLLIDE_INVIS)
p_objp->flags |= P_SF2_DONT_COLLIDE_INVIS;
// always store respawn priority, just for ease of implementation
p_objp->respawn_priority = 0;
if(optional_string("+Respawn Priority:"))
stuff_int(&p_objp->respawn_priority);
p_objp->escort_priority = 0;
if (optional_string("+Escort Priority:"))
{
Assert(p_objp->flags & P_SF_ESCORT);
stuff_int(&p_objp->escort_priority);
}
if (p_objp->flags & P_OF_PLAYER_START)
{
p_objp->flags |= P_SF_CARGO_KNOWN; // make cargo known for players
Player_starts++;
}
p_objp->special_exp_index = -1;
if (optional_string("+Special Exp index:"))
stuff_int(&p_objp->special_exp_index);
p_objp->special_hitpoint_index = -1;
if (optional_string("+Special Hitpoint index:"))
stuff_int(&p_objp->special_hitpoint_index);
// get hitpoint values
if (p_objp->special_hitpoint_index != -1)
{
p_objp->ship_max_shield_strength = (float) atoi(Sexp_variables[p_objp->special_hitpoint_index+SHIELD_STRENGTH].text);
p_objp->ship_max_hull_strength = (float) atoi(Sexp_variables[p_objp->special_hitpoint_index+HULL_STRENGTH].text);
}
else
{
p_objp->ship_max_shield_strength = Ship_info[p_objp->ship_class].max_shield_strength;
p_objp->ship_max_hull_strength = Ship_info[p_objp->ship_class].max_hull_strength;
}
Assert(p_objp->ship_max_hull_strength > 0.0f); // Goober5000: div-0 check (not shield because we might not have one)
// if the kamikaze flag is set, we should have the next flag
if (optional_string("+Kamikaze Damage:"))
{
int damage;
stuff_int(&damage);
p_objp->kamikaze_damage = i2fl(damage);
}
p_objp->hotkey = -1;
if (optional_string("+Hotkey:"))
{
stuff_int(&p_objp->hotkey);
Assert((p_objp->hotkey >= 0) && (p_objp->hotkey < 10));
}
// Goober5000
p_objp->dock_list = NULL;
while (optional_string("+Docked With:"))
{
char docked_with[NAME_LENGTH];
char docker_point[NAME_LENGTH];
char dockee_point[NAME_LENGTH];
// grab docking information
// (whoever designed the original docking system
// reversed the dockpoints in the mission file)
stuff_string(docked_with, F_NAME, NAME_LENGTH);
required_string("$Docker Point:");
stuff_string(dockee_point, F_NAME, NAME_LENGTH);
required_string("$Dockee Point:");
stuff_string(docker_point, F_NAME, NAME_LENGTH);
// make sure we don't overflow the limit
if (Total_initially_docked >= MAX_SHIPS)
{
mprintf(("Too many initially docked instances; skipping...\n"));
continue;
}
// put this information into the Initially_docked array
strcpy(Initially_docked[Total_initially_docked].docker, p_objp->name);
strcpy(Initially_docked[Total_initially_docked].dockee, docked_with);
strcpy(Initially_docked[Total_initially_docked].docker_point, docker_point);
strcpy(Initially_docked[Total_initially_docked].dockee_point, dockee_point);
Total_initially_docked++;
}
// check the optional parameter for destroying the ship before the mission starts. If this parameter is
// here, then we need to destroy the ship N seconds before the mission starts (for debris purposes).
// store the time value here. We want to create this object for sure. Set the arrival cue and arrival
// delay to bogus values
p_objp->destroy_before_mission_time = -1;
if (optional_string("+Destroy At:"))
{
stuff_int(&p_objp->destroy_before_mission_time);
Assert (p_objp->destroy_before_mission_time >= 0);
p_objp->arrival_cue = Locked_sexp_true;
p_objp->arrival_delay = timestamp(0);
}
// check for the optional "orders accepted" string which contains the orders from the default
// set that this ship will actually listen to
if (optional_string("+Orders Accepted:"))
{
stuff_int(&p_objp->orders_accepted);
if (p_objp->orders_accepted != -1)
p_objp->flags |= P_SF_USE_UNIQUE_ORDERS;
}
p_objp->group = 0;
if (optional_string("+Group:"))
stuff_int(&p_objp->group);
bool table_score = false;
if (optional_string("+Use Table Score:")) {
table_score = true;
}
if (optional_string("+Score:")) {
if (!table_score) {
stuff_int(&p_objp->score);
}
// throw away the value the mission file has and use the table value.
else {
int temp_score;
stuff_int(&temp_score);
}
}
else {
table_score = true;
}
if (table_score) {
p_objp->score = Ship_info[p_objp->ship_class].score;
}
// parse the persona index if present
p_objp->persona_index = -1;
if (optional_string("+Persona Index:"))
stuff_int(&p_objp->persona_index);
// texture replacement - Goober5000
p_objp->num_texture_replacements = 0;
if (optional_string("$Texture Replace:") || optional_string("$Duplicate Model Texture Replace:"))
{
char *p;
while ((p_objp->num_texture_replacements < MAX_MODEL_TEXTURES) && (optional_string("+old:")))
{
stuff_string(p_objp->replacement_textures[p_objp->num_texture_replacements].old_texture, F_NAME, MAX_FILENAME_LEN);
required_string("+new:");
stuff_string(p_objp->replacement_textures[p_objp->num_texture_replacements].new_texture, F_NAME, MAX_FILENAME_LEN);
// get rid of extensions
p = strchr(p_objp->replacement_textures[p_objp->num_texture_replacements].old_texture, '.');
if (p)
{
mprintf(("Extraneous extension found on replacement texture %s!\n", p_objp->replacement_textures[p_objp->num_texture_replacements].old_texture));
*p = 0;
}
p = strchr(p_objp->replacement_textures[p_objp->num_texture_replacements].new_texture, '.');
if (p)
{
mprintf(("Extraneous extension found on replacement texture %s!\n", p_objp->replacement_textures[p_objp->num_texture_replacements].new_texture));
*p = 0;
}
// load the texture
p_objp->replacement_textures[p_objp->num_texture_replacements].new_texture_id = bm_load(p_objp->replacement_textures[p_objp->num_texture_replacements].new_texture);
if (p_objp->replacement_textures[p_objp->num_texture_replacements].new_texture_id < 0)
{
Warning(LOCATION, "Could not load replacement texture %s for ship %s\n", p_objp->replacement_textures[p_objp->num_texture_replacements].new_texture, p_objp->name);
}
// *** account for FRED
if (Fred_running)
{
Assert( Fred_texture_replacements != NULL );
strcpy(Fred_texture_replacements[Fred_num_texture_replacements].ship_name, p_objp->name);
strcpy(Fred_texture_replacements[Fred_num_texture_replacements].old_texture, p_objp->replacement_textures[p_objp->num_texture_replacements].old_texture);
strcpy(Fred_texture_replacements[Fred_num_texture_replacements].new_texture, p_objp->replacement_textures[p_objp->num_texture_replacements].new_texture);
Fred_texture_replacements[Fred_num_texture_replacements].new_texture_id = -1;
Fred_num_texture_replacements++;
}
// increment
p_objp->num_texture_replacements++;
}
}
p_objp->wingnum = -1; // set the wing number to -1 -- possibly to be set later
p_objp->pos_in_wing = -1; // Goober5000
// for multiplayer, assign a network signature to this parse object. Doing this here will
// allow servers to use the signature with clients when creating new ships, instead of having
// to pass ship names all the time
if (Game_mode & GM_MULTIPLAYER)
p_objp->net_signature = multi_assign_network_signature(MULTI_SIG_SHIP);
// set the wing_status position to be -1 for all objects. This will get set to an appropriate
// value when the wing positions are finally determined.
p_objp->wing_status_wing_index = -1;
p_objp->wing_status_wing_pos = -1;
p_objp->respawn_count = 0;
// if this if the starting player ship, then copy if to Starting_player_pobject (used for ingame join)
if (!stricmp(p_objp->name, Player_start_shipname))
{
Player_start_pobject = *p_objp;
Player_start_pobject.flags |= P_SF_PLAYER_START_VALID;
}
// Goober5000 - preload stuff for certain object flags
// (done after parsing object, but before creating it)
if (p_objp->flags & P_KNOSSOS_WARP_IN)
Knossos_warp_ani_used = 1;
// this is a valid/legal ship to create
return 1;
}
// Goober5000 - I split this because 1) it's clearer; and 2) initially multiple docked ships would have been
// insanely difficult otherwise
//
// Maybe create the object.
// Don't create the new ship blindly. First, check the sexp for the arrival cue
// to determine when this ship should arrive. If not right away, stick this ship
// onto the ship arrival list to be looked at later. Also check to see if it should use the
// wings arrival cue. The ship may get created later depending on whether or not the wing
// is created.
// Always create ships when FRED is running.
void mission_parse_maybe_create_parse_object(p_object *pobjp)
{
// Bail if it was already created. This should only happen when we previously
// created all the objects in a docked group simultaneously.
if (pobjp->created_object != NULL)
{
Assert(object_is_docked(pobjp));
return;
}
// Goober5000
// shunt this guy to the arrival list if he meets one of the following conditions:
// 1) he's docked but not the dock leader
// 2) this is FS2 (i.e. not FRED2) AND he meets one of the following conditions:
// a) he's not cued to arrive yet
// b) his arrival delay hasn't elapsed
// c) he's reinforcement
if ( (object_is_docked(pobjp) && !(pobjp->flags & P_SF_DOCK_LEADER)) || (!Fred_running && (!eval_sexp(pobjp->arrival_cue) || !timestamp_elapsed(pobjp->arrival_delay) || (pobjp->flags & P_SF_REINFORCEMENT))) )
{
// we can't add ships getting destroyed to the arrival list!!!
Assert (pobjp->destroy_before_mission_time < 0);
// add to arrival list
list_append(&Ship_arrival_list, pobjp);
}
// ingame joiners bail here.
else if((Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_INGAME_JOIN))
{
return;
}
// the ship is present at the beginning of the mission, so we create it
else
{
int real_objnum = parse_create_object(pobjp); // this object may later get destroyed depending on wing status!!!!
// if the ship is supposed to be destroyed before the mission, then blow up the ship and mark the pieces
// as last forever. Only call this stuff when you are blowing up the ship
if (pobjp->destroy_before_mission_time >= 0)
{
object *objp = &Objects[real_objnum];
// FreeSpace
if (!Fred_running)
{
int i;
shipfx_blow_up_model(objp, Ship_info[Ships[objp->instance].ship_info_index].model_num, 0, 0, &objp->pos);
objp->flags |= OF_SHOULD_BE_DEAD;
// once the ship is exploded, find the debris pieces belonging to this object, mark them
// as not to expire, and move them forward in time N seconds
for (i = 0; i < MAX_DEBRIS_PIECES; i++)
{
debris *db;
db = &Debris[i];
if (!db->flags & DEBRIS_USED) // not used, move onto the next one.
continue;
if (db->source_objnum != real_objnum) // not from this ship, move to next one
continue;
debris_clear_expired_flag(db); // mark as don't expire
db->lifeleft = -1.0f; // be sure that lifeleft == -1.0 so that it really doesn't expire!
// now move the debris along its path for N seconds
objp = &Objects[db->objnum];
physics_sim(&objp->pos, &objp->orient, &objp->phys_info, (float) pobjp->destroy_before_mission_time);
}
}
// FRED
else
{
// be sure to set the variable in the ships structure for the final death time!!!
Ships[objp->instance].final_death_time = pobjp->destroy_before_mission_time;
Ships[objp->instance].flags |= SF_KILL_BEFORE_MISSION;
}
}
}
}
void parse_common_object_data(p_object *objp)
{
int i;
// set some defaults..
objp->initial_velocity = 0;
objp->initial_hull = 100;
objp->initial_shields = 100;
// now change defaults if present
if (optional_string("+Initial Velocity:")) {
stuff_int(&objp->initial_velocity);
}
if (optional_string("+Initial Hull:"))
stuff_int(&objp->initial_hull);
if (optional_string("+Initial Shields:"))
stuff_int(&objp->initial_shields);
objp->subsys_index = Subsys_index;
objp->subsys_count = 0;
while (optional_string("+Subsystem:")) {
i = allocate_subsys_status();
objp->subsys_count++;
stuff_string(Subsys_status[i].name, F_NAME, NAME_LENGTH);
if (optional_string("$Damage:"))
stuff_float(&Subsys_status[i].percent);
Subsys_status[i].subsys_cargo_name = -1;
if (optional_string("+Cargo Name:")) {
char cargo_name[NAME_LENGTH];
stuff_string(cargo_name, F_NAME, NAME_LENGTH);
int index = string_lookup(cargo_name, Cargo_names, Num_cargo, "cargo", 0);
if (index == -1 && (Num_cargo < MAX_CARGO)) {
index = Num_cargo;
strcpy(Cargo_names[Num_cargo++], cargo_name);
}
Subsys_status[i].subsys_cargo_name = index;
}
if (optional_string("+AI Class:"))
Subsys_status[i].ai_class = match_and_stuff(F_NAME, Ai_class_names, Num_ai_classes, "AI class");
if (optional_string("+Primary Banks:"))
stuff_int_list(Subsys_status[i].primary_banks, MAX_SHIP_PRIMARY_BANKS, WEAPON_LIST_TYPE);
// Goober5000
if (optional_string("+Pbank Ammo:"))
stuff_int_list(Subsys_status[i].primary_ammo, MAX_SHIP_PRIMARY_BANKS, RAW_INTEGER_TYPE);
if (optional_string("+Secondary Banks:"))
stuff_int_list(Subsys_status[i].secondary_banks, MAX_SHIP_SECONDARY_BANKS, WEAPON_LIST_TYPE);
if (optional_string("+Sbank Ammo:"))
stuff_int_list(Subsys_status[i].secondary_ammo, MAX_SHIP_SECONDARY_BANKS, RAW_INTEGER_TYPE);
}
}
extern int Multi_ping_timestamp;
void parse_objects(mission *pm, int flag)
{
Assert(pm != NULL);
required_string("#Objects");
// parse in objects
Parse_objects.clear();
while (required_string_either("#Wings", "$Name:"))
{
p_object pobj;
// parse a single object
int valid = parse_object(pm, flag, &pobj);
// not all objects are always valid or legal
if (!valid)
continue;
// add it
Parse_objects.push_back(pobj);
// send out a ping if we are multi so that psnet2 doesn't kill us off for a long load
// NOTE that we can't use the timestamp*() functions here since they won't increment
// during this loading process
if (Game_mode & GM_MULTIPLAYER)
{
if ((Multi_ping_timestamp == -1) || (Multi_ping_timestamp <= timer_get_milliseconds()))
{
multi_ping_send_all();
Multi_ping_timestamp = timer_get_milliseconds() + 10000; // timeout is 10 seconds between pings
}
}
}
// Goober5000 - I moved the docking stuff to post_process_ships_wings because of interdependencies
// between ships and wings. Neither docking stuff nor ship stuff (for ships present at mission start)
// will be valid until after post_process_ships_wings is run.
}
// Karajorma - Replaces a p_object with a new one based on a Ship_info index.
void swap_parse_object(p_object *p_obj, int new_ship_class)
{
ship_info *new_ship_info = &Ship_info[new_ship_class];
ship_info *old_ship_info = &Ship_info[p_obj->ship_class];
int subsys_ind = p_obj->subsys_index;
subsys_status *ship_subsystems = &Subsys_status[subsys_ind];
// Class
// First things first. Change the class of the p_object
p_obj->ship_class = new_ship_class;
// Hitpoints
// We need to take into account that the ship might have been assigned special hitpoints so we can't
// simply swap old for new.
Assert (p_obj->ship_max_hull_strength > 0);
Assert (old_ship_info->max_hull_strength > 0);
float hp_multiplier = p_obj->ship_max_hull_strength / old_ship_info->max_hull_strength;
p_obj->ship_max_hull_strength = new_ship_info->max_hull_strength * hp_multiplier;
// Shields
// Again we have to watch out for special hitpoints but this time we can't assume that there will be a
// shield. So first lets see if there is one.
if ((p_obj->ship_max_shield_strength != old_ship_info->max_shield_strength) &&
(p_obj->ship_max_shield_strength > 0) &&
(new_ship_info->max_shield_strength > 0))
{
// This ship is using special hitpoints to alter the shield strength
float shield_multiplier = p_obj->ship_max_shield_strength / i2fl(old_ship_info->max_shield_strength);
p_obj->ship_max_shield_strength = new_ship_info->max_shield_strength * shield_multiplier;
}
// Not using special hitpoints or a class which has a shield strength of zero
else
{
p_obj->ship_max_shield_strength = new_ship_info->max_shield_strength;
}
// Primary weapons
// First find out what is the correct number for a ship of this class
int num_pbanks = new_ship_info->num_primary_banks;
// Now cycle through the primary banks looking for banks that were added or removed
for (int i=0; i < MAX_SHIP_PRIMARY_BANKS; i++)
{
// If we're dealing with a primary bank that actually should exist on this ship
if ( i < num_pbanks )
{
// We only care if a weapon hasn't been parsed in for this bank
if (ship_subsystems->primary_banks[i] == -1)
{
// Give the ship the default weapon for this bank.
ship_subsystems->primary_banks[i] = new_ship_info->primary_bank_weapons[i];
}
}
// Any primary banks the ship doesn't have should be set to -1
else
{
ship_subsystems->primary_banks[i] = -1;
}
}
// Secondary weapons
// Again we first have to find out how many we should have
int num_sbanks = new_ship_info->num_secondary_banks;
// Now cycle through the secondary banks looking for banks that were added or removed
for (int j=0; j < MAX_SHIP_SECONDARY_BANKS; j++)
{
// If we're dealing with a primary bank that actually should exist on this ship
if ( j < num_sbanks )
{
// We only care if a weapon hasn't been parsed in for this bank
if (ship_subsystems->secondary_banks[j] == -1){
// Give the ship the default weapon for this bank.
ship_subsystems->secondary_banks[j] = new_ship_info->secondary_bank_weapons[j];
}
}
// Any secondary banks the ship doesn't have should be set to -1
else
{
ship_subsystems->secondary_banks[j] = -1;
}
}
}
p_object *mission_parse_get_parse_object(ushort net_signature)
{
int i;
// look for original ships
for (i = 0; i < (int)Parse_objects.size(); i++)
if(Parse_objects[i].net_signature == net_signature)
return &Parse_objects[i];
// boo
return NULL;
}
// Goober5000 - also get it by name
p_object *mission_parse_get_parse_object(char *name)
{
int i;
// look for original ships
for (i = 0; i < (int)Parse_objects.size(); i++)
if(!stricmp(Parse_objects[i].name, name))
return &Parse_objects[i];
// boo
return NULL;
}
int find_wing_name(char *name)
{
int i;
for (i = 0; i < Num_wings; i++)
{
if (!stricmp(name, Wings[i].name))
return i;
}
return -1;
}
// function to create ships in the wing that need to be created
int parse_wing_create_ships( wing *wingp, int num_to_create, int force, int specific_instance )
{
int wingnum, objnum, num_create_save;
int time_to_arrive;
int pre_create_count;
int i, j;
// we need to send this in multiplayer
pre_create_count = wingp->total_arrived_count;
// force is used to force creation of the wing -- used for multiplayer
if ( !force ) {
// we only want to evaluate the arrival cue of the wing if:
// 1) single player
// 2) multiplayer and I am the host of the game
// can't create any ships if the arrival cue is false or the timestamp has not elapsed.
if ( !eval_sexp(wingp->arrival_cue) ) /* || !timestamp_elapsed(wingp->arrival_delay) ) */
return 0;
// once the sexpressions becomes true, then check the arrival delay on the wing. The first time, the
// arrival delay will be <= 0 meaning that no timer object has been set yet. Set up the timestamp
// which should always give a number >= 0;
if ( wingp->arrival_delay <= 0 ) {
wingp->arrival_delay = timestamp( -wingp->arrival_delay * 1000 );
Assert ( wingp->arrival_delay >= 0 );
}
if ( !timestamp_elapsed( wingp->arrival_delay ) )
return 0;
// if wing is coming from docking bay, then be sure that ship we are arriving from actually exists
// (or will exist).
if ( wingp->arrival_location == ARRIVE_FROM_DOCK_BAY ) {
int shipnum;
char *name;
Assert( wingp->arrival_anchor >= 0 );
name = Parse_names[wingp->arrival_anchor];
// see if ship is yet to arrive. If so, then return -1 so we can evaluate again later.
if ( mission_parse_get_arrival_ship( name ) )
return 0;
// see if ship is in mission. If not, then we can assume it was destroyed or departed since
// it is not on the arrival list (as shown by above if statement).
shipnum = ship_name_lookup( name );
if ( shipnum == -1 ) {
int num_remaining;
// since this wing cannot arrive from this place, we need to mark the wing as destroyed and
// set the wing variables appropriatly. Good for directives.
// set the gone flag
wingp->flags |= WF_WING_GONE;
// if the current wave is zero, it never existed
wingp->flags |= WF_NEVER_EXISTED;
// mark the number of waves and number of ships destroyed equal to the last wave and the number
// of ships yet to arrive
num_remaining = ( (wingp->num_waves - wingp->current_wave) * wingp->wave_count);
wingp->total_arrived_count += num_remaining;
wingp->current_wave = wingp->num_waves;
// replaced following three lines of code with mission log call because of bug with
// the Ships_exited list.
//index = ship_find_exited_ship_by_name( name );
//Assert( index != -1 );
//if (Ships_exited[index].flags & SEF_DESTROYED ) {
if ( mission_log_get_time(LOG_SHIP_DESTROYED, name, NULL, NULL) ) {
wingp->total_destroyed += num_remaining;
} else if ( mission_log_get_time(LOG_SHIP_DEPARTED, name, NULL, NULL) ) {
wingp->total_departed += num_remaining;
} else {
wingp->total_vanished += num_remaining;
}
mission_parse_mark_non_arrival(wingp); // Goober5000
return 0;
}
// Goober5000 - check status of fighterbays - if they're destroyed, we can't launch - but we want to reeval later
if (ship_fighterbays_all_destroyed(&Ships[shipnum]))
return 0;
}
if ( num_to_create == 0 )
return 0;
// check the wave_delay_timestamp field. If it is not valid, make it valid (based on wave delay min
// and max values). If it is valid, and not elapsed, then return. If it is valid and elasped, then
// continue on.
if ( !timestamp_valid(wingp->wave_delay_timestamp) ) {
// if at least one of these is valid, then reset the timestamp. If they are both zero, we will create the
// wave
if ( (wingp->wave_delay_min > 0) || (wingp->wave_delay_max > 0) ) {
Assert ( wingp->wave_delay_min <= wingp->wave_delay_max );
time_to_arrive = wingp->wave_delay_min + (int)(frand() * (wingp->wave_delay_max - wingp->wave_delay_min));
// MWA -- 5/18/98
// HACK HACK -- in the presense of Mike Comet and Mitri, I have introduced one of the most
// serious breaches of coding standards. I'm to lazy to fix this the correct way. Insert
// a delay before the next wave of the wing can arrive to that clients in the game have ample
// time to kill off any ships in the wing before the next wave arrives.
if ( Game_mode & GM_MULTIPLAYER ){
time_to_arrive += 7;
}
wingp->wave_delay_timestamp = timestamp(time_to_arrive * 1000);
return 0;
}
// if we get here, both min and max values are 0; See comments above for a most serious hack
time_to_arrive = 0;
if ( Game_mode & GM_MULTIPLAYER )
time_to_arrive += 7;
time_to_arrive *= 1000;
wingp->wave_delay_timestamp = timestamp(time_to_arrive);
}
// now check to see if the wave_delay_timestamp is elapsed or not
if ( !timestamp_elapsed(wingp->wave_delay_timestamp) )
return 0;
}
// finally we can create the wing.
num_create_save = num_to_create;
wingnum = WING_INDEX(wingp); // get the wing number
// if there are no ships to create, then all ships must be player start ships -- do nothing in this case.
if ( num_to_create == 0 ){
return 0;
}
wingp->current_wave++; // we are creating new ships
// we need to create num_to_create ships. Since the arrival cues for ships in a wing
// are ignored, then *all* ships must be in the Ship_arrival_list.
objnum = -1;
// Goober5000 - we have to do this via the array because we have no guarantee we'll be able to iterate along the list
// (since created objects plus anything they're docked to will be removed from it)
for (i = 0; i < (int)Parse_objects.size(); i++)
{
int index;
ai_info *aip;
p_object *p_objp = &Parse_objects[i];
// ensure on arrival list
if (!parse_object_on_arrival_list(p_objp))
continue;
// compare the wingnums. When they are equal, we can create the ship. In the case of
// wings that have multiple waves, this code implies that we essentially creating clones
// of the ships that were created in Fred for the wing when more ships for a new wave
// arrive. The threshold value of a wing can also make one of the ships in a wing be "cloned"
// more often than other ships in the wing. I don't think this matters much.
if (p_objp->wingnum != wingnum)
continue;
Assert( (p_objp->pos_in_wing >= 0) && (p_objp->pos_in_wing < MAX_SHIPS_PER_WING) );
// when ingame joining, we need to create a specific ship out of the list of ships for a
// wing. specific_instance is a 0 based integer which specified which ship in the wing
// to create. So, only create the ship we actually need to.
if ((Game_mode & GM_MULTIPLAYER) && (specific_instance > 0))
{
specific_instance--;
continue;
}
Assert (!(p_objp->flags & P_SF_CANNOT_ARRIVE)); // get allender
// if we have the maximum number of ships in the wing, we must bail as well
if (wingp->current_count >= MAX_SHIPS_PER_WING)
{
Int3(); // this is bogus -- we should always allow all ships to be created
num_to_create = 0;
break;
}
// bash the ship name to be the name of the wing + some number if there is > 1 wave in this wing
// also, if multiplayer, set the parse object's net signature to be wing's net signature
// base + total_arrived_count (before adding 1)
if (Game_mode & GM_MULTIPLAYER)
{
p_objp->net_signature = (ushort) (wingp->net_signature + wingp->total_arrived_count);
}
wingp->total_arrived_count++;
if (wingp->num_waves > 1)
{
sprintf(p_objp->name, NOX("%s %d"), wingp->name, wingp->total_arrived_count);
}
objnum = parse_create_object(p_objp);
aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
// copy any goals from the wing to the newly created ship
for (index = 0; index < MAX_AI_GOALS; index++)
{
if (wingp->ai_goals[index].ai_mode != AI_GOAL_NONE)
ai_copy_mission_wing_goal(&wingp->ai_goals[index], aip);
}
Ai_info[Ships[Objects[objnum].instance].ai_index].wing = wingnum;
if (wingp->flags & WF_NO_DYNAMIC)
aip->ai_flags |= AIF_NO_DYNAMIC;
// update housekeeping variables
wingp->ship_index[p_objp->pos_in_wing] = Objects[objnum].instance;
// set up wingman status index
hud_wingman_status_set_index(wingp->ship_index[p_objp->pos_in_wing]);
p_objp->wing_status_wing_index = Ships[Objects[objnum].instance].wing_status_wing_index;
p_objp->wing_status_wing_pos = Ships[Objects[objnum].instance].wing_status_wing_pos;
wingp->current_count++;
// keep any player ship on the parse object list -- used for respawns
// 5/8/98 -- MWA -- don't remove ships from the list when you are ingame joining
if (!(p_objp->flags & P_OF_PLAYER_START))
{
if ((Game_mode & GM_NORMAL) || !(Net_player->flags & NETINFO_FLAG_INGAME_JOIN))
{
// only remove ship if one wave in wing
if (wingp->num_waves == wingp->current_wave)
{
// remove p_objp from the list
list_remove(&Ship_arrival_list, p_objp);
// free up sexp nodes for reuse
if (p_objp->ai_goals != -1)
free_sexp2(p_objp->ai_goals);
}
}
}
// flag ship with SF_FROM_PLAYER_WING if a member of player starting wings
if ((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM))
{
// different for tvt -- Goober5000
for (j = 0; j < MAX_TVT_WINGS; j++)
{
if (!stricmp(TVT_wing_names[j], wingp->name))
Ships[Objects[objnum].instance].flags |= SF_FROM_PLAYER_WING;
}
}
else
{
for (j = 0; j < MAX_STARTING_WINGS; j++)
{
if (!stricmp(Starting_wing_names[j], wingp->name))
Ships[Objects[objnum].instance].flags |= SF_FROM_PLAYER_WING;
}
}
// keep track of how many ships to create. Stop when we have done all that we are supposed to do.
num_to_create--;
if (num_to_create == 0)
break;
}
// we should always have enough ships in the list!!!
Assert (num_to_create == 0);
// wing current_count needs to match the end of the ship_index[] list, but there
// is a very off chance it could have holes in it, so make sure to compact the list
for (i = 0; i < (MAX_SHIPS_PER_WING-1); i++) {
if (wingp->ship_index[i] == -1) {
j = i;
while ( j < (MAX_SHIPS_PER_WING-1) ) {
wingp->ship_index[j] = wingp->ship_index[j+1];
// update "special" ship too
if (wingp->special_ship == j+1) {
wingp->special_ship--;
}
j++;
}
}
}
// possibly play some event driven music here. Send a network packet indicating the wing was
// created. Only do this stuff if actually in the mission.
if ( (objnum != -1) && (Game_mode & GM_IN_MISSION) ) { // if true, we have created at least one new ship.
int ship_num;
// Goober5000 - make all wings form on their respective leaders
ai_maybe_add_form_goal( wingp );
/*
// see if this wing is a player starting wing, and if so, call the maybe_add_form_goal
// function to possibly make the wing form on its leader
for (i = 0; i < MAX_STARTING_WINGS; i++ ) {
if ( Starting_wings[i] == wingnum ){
break;
}
}
if ( i < MAX_STARTING_WINGS ){
ai_maybe_add_form_goal( wingp );
}
*/
mission_log_add_entry( LOG_WING_ARRIVED, wingp->name, NULL, wingp->current_wave );
ship_num = wingp->ship_index[0];
if ( !(Ships[ship_num].flags & SF_NO_ARRIVAL_MUSIC) && !(wingp->flags & WF_NO_ARRIVAL_MUSIC) ) {
if ( timestamp_elapsed(Allow_arrival_music_timestamp) ) {
Allow_arrival_music_timestamp = timestamp(ARRIVAL_MUSIC_MIN_SEPARATION);
event_music_arrival(Ships[ship_num].team);
}
}
// possibly change the location where these ships arrive based on the wings arrival location
mission_set_wing_arrival_location( wingp, num_create_save );
// if in multiplayer (and I am the host) and in the mission, send a wing create command to all
// other players
if ( MULTIPLAYER_MASTER ){
send_wing_create_packet( wingp, num_create_save, pre_create_count );
}
#ifndef NDEBUG
// test code to check to be sure that all ships in the wing are ignoring the same types
// of orders from the leader
if ( Fred_running ) {
Assert( wingp->ship_index[wingp->special_ship] != -1 );
int orders = Ships[wingp->ship_index[0]].orders_accepted;
for (i = 0; i < wingp->current_count; i++ ) {
if (i == wingp->special_ship)
continue;
if ( orders != Ships[wingp->ship_index[i]].orders_accepted ) {
Warning(LOCATION, "ships in wing %s are ignoring different player orders. Please find Mark A\nto talk to him about this.", wingp->name );
break;
}
}
}
#endif
}
wingp->wave_delay_timestamp = timestamp(-1); // we will need to set this up properly for the next wave
return num_create_save;
}
void parse_wing(mission *pm)
{
int wingnum, i, wing_goals, delay;
char name[NAME_LENGTH], ship_names[MAX_SHIPS_PER_WING][NAME_LENGTH];
char wing_flag_strings[MAX_WING_FLAGS][NAME_LENGTH];
wing *wingp;
Assert(pm != NULL);
wingp = &Wings[Num_wings];
required_string("$Name:");
stuff_string(wingp->name, F_NAME, NAME_LENGTH);
wingnum = find_wing_name(wingp->name);
if (wingnum != -1)
error_display(0, NOX("Redundant wing name: %s\n"), wingp->name);
wingnum = Num_wings;
wingp->total_arrived_count = 0;
wingp->total_destroyed = 0;
wingp->total_departed = 0; // Goober5000
wingp->total_vanished = 0; // Goober5000
wingp->flags = 0;
// squad logo - Goober5000
if (optional_string("+Squad Logo:"))
{
int flag = -1;
stuff_string(wingp->wing_squad_filename, F_NAME, MAX_FILENAME_LEN);
// load it only if FRED isn't running
if (!Fred_running)
{
// check all previous wings to see if we already loaded it (we want to save memory)
for (i = 0; i < Num_wings; i++)
{
// do we have a previous texture?
if (Wings[i].wing_insignia_texture != -1)
{
// if we have a match
if (!stricmp(Wings[i].wing_squad_filename, wingp->wing_squad_filename))
{
flag = i;
break;
}
}
}
// if we have loaded it already, just use the old bitmap index
if (flag != -1)
{
wingp->wing_insignia_texture = Wings[flag].wing_insignia_texture;
}
else
{
wing_load_squad_bitmap(wingp);
}
}
}
required_string("$Waves:");
stuff_int(&wingp->num_waves);
Assert ( wingp->num_waves >= 1 ); // there must be at least 1 wave
wingp->current_wave = 0;
required_string("$Wave Threshold:");
stuff_int(&wingp->threshold);
required_string("$Special Ship:");
stuff_int(&wingp->special_ship);
wingp->arrival_anchor = -1;
wingp->arrival_distance = 0;
wingp->arrival_path_mask = -1; // -1 only until resolved
find_and_stuff("$Arrival Location:", &wingp->arrival_location, F_NAME, Arrival_location_names, Num_arrival_names, "Arrival Location");
if ( optional_string("+Arrival Distance:") )
{
stuff_int( &wingp->arrival_distance );
// Goober5000
if ((wingp->arrival_distance <= 0) && ((wingp->arrival_location == ARRIVE_NEAR_SHIP) || (wingp->arrival_location == ARRIVE_IN_FRONT_OF_SHIP)))
{
Warning(LOCATION, "Arrival distance for wing %s cannot be %d. Setting to 1.\n", wingp->name, wingp->arrival_distance);
wingp->arrival_distance = 1;
}
}
if ( wingp->arrival_location != ARRIVE_AT_LOCATION )
{
required_string("$Arrival Anchor:");
stuff_string(name, F_NAME, NAME_LENGTH);
wingp->arrival_anchor = get_anchor(name);
}
if (optional_string("+Arrival Paths:"))
{
// temporarily use mask to point to the restriction index
wingp->arrival_path_mask = add_path_restriction();
}
if (optional_string("+Arrival delay:")) {
stuff_int(&delay);
if ( delay < 0 )
Error(LOCATION, "Cannot have arrival delay < 0 on wing %s", wingp->name );
} else
delay = 0;
if ( !Fred_running ){
wingp->arrival_delay = -delay;
} else {
wingp->arrival_delay = delay;
}
required_string("$Arrival Cue:");
wingp->arrival_cue = get_sexp_main();
if ( !Fred_running && (wingp->arrival_cue >= 0) ) {
if ( eval_sexp(wingp->arrival_cue) ) // evaluate to determine if sexp is always false.
wingp->arrival_delay = timestamp( -wingp->arrival_delay * 1000 );
}
wingp->departure_anchor = -1;
wingp->departure_path_mask = -1; // -1 only until resolved
find_and_stuff("$Departure Location:", &wingp->departure_location, F_NAME, Departure_location_names, Num_arrival_names, "Departure Location");
if ( wingp->departure_location != DEPART_AT_LOCATION )
{
required_string("$Departure Anchor:");
stuff_string( name, F_NAME, NAME_LENGTH );
wingp->departure_anchor = get_anchor(name);
}
if (optional_string("+Departure Paths:"))
{
// temporarily use mask to point to the restriction index
wingp->departure_path_mask = add_path_restriction();
}
if (optional_string("+Departure delay:")) {
stuff_int(&delay);
if ( delay < 0 )
Error(LOCATION, "Cannot have departure delay < 0 on wing %s", wingp->name );
} else
delay = 0;
if ( !Fred_running )
wingp->departure_delay = -delay; // use negative numbers to mean that delay timer not yet set
else
wingp->departure_delay = delay;
required_string("$Departure Cue:");
wingp->departure_cue = get_sexp_main();
// stores a list of all names of ships in the wing
required_string("$Ships:");
wingp->wave_count = stuff_string_list( ship_names, MAX_SHIPS_PER_WING );
wingp->current_count = 0;
// get the wings goals, if any
wing_goals = -1;
if ( optional_string("$AI Goals:") )
wing_goals = get_sexp_main();
wingp->hotkey = -1;
if (optional_string("+Hotkey:")) {
stuff_int(&wingp->hotkey);
Assert((wingp->hotkey >= 0) && (wingp->hotkey < 10));
}
if (optional_string("+Flags:")) {
int count;
count = stuff_string_list( wing_flag_strings, MAX_WING_FLAGS );
for (i = 0; i < count; i++ ) {
if ( !stricmp( wing_flag_strings[i], NOX("ignore-count")) )
wingp->flags |= WF_IGNORE_COUNT;
else if ( !stricmp( wing_flag_strings[i], NOX("reinforcement")) )
wingp->flags |= WF_REINFORCEMENT;
else if ( !stricmp( wing_flag_strings[i], NOX("no-arrival-music")) )
wingp->flags |= WF_NO_ARRIVAL_MUSIC;
else if ( !stricmp( wing_flag_strings[i], NOX("no-arrival-message")) )
wingp->flags |= WF_NO_ARRIVAL_MESSAGE;
else if ( !stricmp( wing_flag_strings[i], NOX("no-arrival-warp")) )
wingp->flags |= WF_NO_ARRIVAL_WARP;
else if ( !stricmp( wing_flag_strings[i], NOX("no-departure-warp")) )
wingp->flags |= WF_NO_DEPARTURE_WARP;
else if ( !stricmp( wing_flag_strings[i], NOX("no-dynamic")) )
wingp->flags |= WF_NO_DYNAMIC;
else if ( !stricmp( wing_flag_strings[i], NOX("nav-carry-status")) )
wingp->flags |= WF_NAV_CARRY;
else
Warning(LOCATION, "unknown wing flag\n%s\n\nSkipping.", wing_flag_strings[i]);
}
}
// get the wave arrival delay bounds (if present). Used as lower and upper bounds (in seconds)
// which determine when new waves of a wing should arrive.
wingp->wave_delay_min = 0;
wingp->wave_delay_max = 0;
if ( optional_string("+Wave Delay Min:") )
stuff_int( &(wingp->wave_delay_min) );
if ( optional_string("+Wave Delay Max:") )
stuff_int( &(wingp->wave_delay_max) );
// be sure to set the wave arrival timestamp of this wing to pop right away so that the
// wing could be created if it needs to be
wingp->wave_delay_timestamp = timestamp(0);
// initialize wing goals
for (i=0; i<MAX_AI_GOALS; i++) {
wingp->ai_goals[i].ai_mode = AI_GOAL_NONE;
wingp->ai_goals[i].signature = -1;
wingp->ai_goals[i].priority = -1;
wingp->ai_goals[i].flags = 0;
}
// 7/13/98 -- MWA
// error checking against the player ship wings (i.e. starting & tvt) to be sure that wave count doesn't exceed one for
// these wings.
if ( Game_mode & GM_MULTIPLAYER ) {
for (i = 0; i < MAX_STARTING_WINGS; i++ ) {
if ( !stricmp(Starting_wing_names[i], wingp->name) ) {
if ( wingp->num_waves > 1 ) {
// only end the game if we're the server - clients will eventually find out :)
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_WAVE_COUNT);
}
// Error(LOCATION, "Player wings cannot have more than 1 wave.");
}
}
}
for (i = 0; i < MAX_TVT_WINGS; i++ ) {
if ( !stricmp(TVT_wing_names[i], wingp->name) ) {
if ( wingp->num_waves > 1 ) {
// only end the game if we're the server - clients will eventually find out :)
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_WAVE_COUNT);
}
// Error(LOCATION, "Player wings cannot have more than 1 wave.");
}
}
}
}
// Get the next starting signature for this in this wing. We want to reserve wave_count * num_waves
// of signature. These can be used to construct wings for ingame joiners.
if ( Game_mode & GM_MULTIPLAYER ) {
int next_signature;
wingp->net_signature = multi_assign_network_signature( MULTI_SIG_SHIP );
next_signature = wingp->net_signature + (wingp->wave_count * wingp->num_waves);
if ( next_signature > SHIP_SIG_MAX )
Error(LOCATION, "Too many total ships in mission (%d) for network signature assignment", SHIP_SIG_MAX);
multi_set_network_signature( (ushort)next_signature, MULTI_SIG_SHIP );
}
for (i=0; i<MAX_SHIPS_PER_WING; i++)
wingp->ship_index[i] = -1;
// set up the ai_goals for this wing -- all ships created from this wing will inherit these goals
// goals for the wing are stored slightly differently than for ships. We simply store the index
// into the sexpression array of each goal (max 10). When a ship in this wing is created, each
// goal in the wings goal array is given to the ship.
if ( wing_goals != -1 ) {
int sexp, index;
// this will assign the goals to the wings as well as to any ships in the wing that have been
// already created.
index = 0;
for ( sexp = CDR(wing_goals); sexp != -1; sexp = CDR(sexp) )
ai_add_wing_goal_sexp(sexp, AIG_TYPE_EVENT_WING, wingnum); // used by Fred
//if (Fred_running)
free_sexp2(wing_goals); // free up sexp nodes for reuse, since they aren't needed anymore.
}
// set the wing number for all ships in the wing
for (i = 0; i < wingp->wave_count; i++ ) {
char *ship_name = ship_names[i];
int assigned = 0;
uint j;
// Goober5000 - since the ship/wing creation stuff is reordered to accommodate multiple docking,
// everything is still only in the parse array at this point (in both FRED and FS2)
// find the parse object and assign it the wing number
for (j = 0; j < Parse_objects.size(); j++) {
p_object *p_objp = &Parse_objects[j];
if ( !strcmp(ship_name, p_objp->name) ) {
// get Allender -- ship appears to be in multiple wings
Assert (p_objp->wingnum == -1);
p_objp->wingnum = wingnum;
p_objp->pos_in_wing = i;
assigned++;
}
}
// error checking
if (assigned == 0) {
Error(LOCATION, "Cannot load mission -- for wing %s, ship %s is not present in #Objects section.\n", wingp->name, ship_name);
} else if (assigned > 1) {
Error(LOCATION, "Cannot load mission -- for wing %s, ship %s is specified multiple times in wing.\n", wingp->name, ship_name);
}
}
// Goober5000 - wing creation stuff moved to post_process_ships_wings
}
void parse_wings(mission *pm)
{
required_string("#Wings");
while (required_string_either("#Events", "$Name:"))
{
Assert(Num_wings < MAX_WINGS);
parse_wing(pm);
Num_wings++;
}
}
// Goober5000
void resolve_path_masks(int anchor, int *path_mask)
{
path_restriction_t *prp;
// if we have no restrictions, do a quick out
if (*path_mask < 0)
{
*path_mask = 0;
return;
}
// get path restriction info
prp = &Path_restrictions[*path_mask];
// uninitialized; compute the mask from scratch
if (prp->cached_mask & (1 << MAX_SHIP_BAY_PATHS))
{
int j, bay_path, modelnum;
p_object *parent_pobjp;
// get anchor ship
Assert(!(anchor & SPECIAL_ARRIVAL_ANCHOR_FLAG));
parent_pobjp = mission_parse_get_parse_object(Parse_names[anchor]);
// load model for checking paths
modelnum = model_load(Ship_info[parent_pobjp->ship_class].pof_file, 0, NULL);
// resolve names to indexes
*path_mask = 0;
for (j = 0; j < prp->num_paths; j++)
{
bay_path = model_find_bay_path(modelnum, prp->path_names[j]);
if (bay_path < 0)
continue;
*path_mask |= (1 << bay_path);
}
// unload model
model_unload(modelnum);
// cache the result
prp->cached_mask = *path_mask;
}
// already computed; so reuse it
else
{
*path_mask = prp->cached_mask;
}
}
// Goober5000
// resolve arrival/departure path masks
// NB: between parsing and the time this function is run, the path_mask variables store the index of the path info;
// at all other times, they store the masks of the bay paths as expected
void post_process_path_stuff()
{
int i;
p_object *pobjp;
wing *wingp;
// take care of parse objects (ships)
for (i = 0; i < (int)Parse_objects.size(); i++)
{
pobjp = &Parse_objects[i];
resolve_path_masks(pobjp->arrival_anchor, &pobjp->arrival_path_mask);
resolve_path_masks(pobjp->departure_anchor, &pobjp->departure_path_mask);
}
// take care of wings
for (i = 0; i < Num_wings; i++)
{
wingp = &Wings[i];
resolve_path_masks(wingp->arrival_anchor, &wingp->arrival_path_mask);
resolve_path_masks(wingp->departure_anchor, &wingp->departure_path_mask);
}
}
// Goober5000
void post_process_ships_wings()
{
int i;
// Goober5000 - first, resolve the path masks. Needs to be done first because
// mission_parse_maybe_create_parse_object relies on it.
post_process_path_stuff();
// Goober5000 - now that we've parsed all the objects, resolve the initially docked references.
// This must be done before anything that relies on the dock references but can't be done until
// both ships and wings have been parsed.
mission_parse_set_up_initial_docks();
// Goober5000 - now create all objects that we can. This must be done before any ship stuff
// but can't be done until the dock references are resolved. This was originally done
// in parse_object().
for (i = 0; i < (int)Parse_objects.size(); i++)
{
mission_parse_maybe_create_parse_object(&Parse_objects[i]);
}
// ----------------- at this point the ships have been created -----------------
// Now set up the wings. This must be done after both dock stuff and ship stuff.
// Goober5000 - for FRED, the ships are initialized after the wings, so we must now tell the wings
// where their ships are
if (Fred_running)
{
// even though the ships are already created, only the parse objects know the wing info
for (i = 0; i < (int)Parse_objects.size(); i++)
{
p_object *p_objp = &Parse_objects[i];
// link the ship into the wing's array of ship indices (previously done in parse_wing
// in a rather less backwards way)
if (p_objp->wingnum >= 0)
{
int shipnum = ship_name_lookup(p_objp->name);
Assert(shipnum >= 0 && shipnum < MAX_SHIPS);
Assert(p_objp->pos_in_wing >= 0 && p_objp->pos_in_wing < MAX_SHIPS_PER_WING);
Wings[p_objp->wingnum].ship_index[p_objp->pos_in_wing] = shipnum;
}
}
}
// Goober5000 - for FS2, the wings are initialized first, so all we have to do is create their ships
else
{
for (i = 0; i < Num_wings; i++)
{
wing *wingp = &Wings[i];
// create the wing if is isn't a reinforcement.
if (!(wingp->flags & WF_REINFORCEMENT))
parse_wing_create_ships(wingp, wingp->wave_count);
}
}
}
// mission events are sexpressions which cause things to happen based on the outcome
// of other events in a mission. Essentially scripting the different things that can happen
// in a mission
void parse_event(mission *pm)
{
char buf[NAME_LENGTH];
mission_event *event;
event = &Mission_events[Num_mission_events];
event->chain_delay = -1;
required_string( "$Formula:" );
event->formula = get_sexp_main();
if (optional_string("+Name:")){
stuff_string(event->name, F_NAME, NAME_LENGTH);
} else {
event->name[0] = 0;
}
if ( optional_string("+Repeat Count:")){
stuff_int( &(event->repeat_count) );
} else {
event->repeat_count = 1;
}
event->interval = -1;
if ( optional_string("+Interval:")){
stuff_int( &(event->interval) );
}
event->score = 0;
if ( optional_string("+Score:") ){
stuff_int(&event->score);
}
if ( optional_string("+Chained:") ){
stuff_int(&event->chain_delay);
}
if ( optional_string("+Objective:") ) {
stuff_string(buf, F_NAME, NAME_LENGTH);
event->objective_text = vm_strdup(buf);
} else {
event->objective_text = NULL;
}
if ( optional_string("+Objective key:") ) {
stuff_string(buf, F_NAME, NAME_LENGTH);
event->objective_key_text = vm_strdup(buf);
} else {
event->objective_key_text = NULL;
}
event->team = -1;
if( optional_string("+Team:") ) {
stuff_int(&event->team);
}
event->timestamp = timestamp(-1);
// sanity check on the repeat count variable
// _argv[-1] - negative repeat count is now legal; means repeat indefinitely.
if ( event->repeat_count == 0 ){
Error (LOCATION, "Repeat count for mission event %s is 0.\nMust be >= 1 or negative!", event->name );
}
}
void parse_events(mission *pm)
{
required_string("#Events");
while (required_string_either( "#Goals", "$Formula:")) {
Assert( Num_mission_events < MAX_MISSION_EVENTS );
parse_event(pm);
Num_mission_events++;
}
}
void parse_goal(mission *pm)
{
int dummy;
mission_goal *goalp;
goalp = &Mission_goals[Num_goals++];
Assert(Num_goals < MAX_GOALS);
Assert(pm != NULL);
find_and_stuff("$Type:", &goalp->type, F_NAME, Goal_type_names, Num_goal_type_names, "goal type");
required_string("+Name:");
stuff_string(goalp->name, F_NAME, NAME_LENGTH);
// backwards compatibility for old Fred missions - all new missions should use $MessageNew
if(optional_string("$Message:")){
stuff_string(goalp->message, F_NAME, MAX_GOAL_TEXT);
} else {
required_string("$MessageNew:");
stuff_string(goalp->message, F_MULTITEXT, MAX_GOAL_TEXT);
}
if (optional_string("$Rating:")){
stuff_int(&dummy); // not used
}
required_string("$Formula:");
goalp->formula = get_sexp_main();
goalp->flags = 0;
if ( optional_string("+Invalid:") )
goalp->type |= INVALID_GOAL;
if ( optional_string("+Invalid") )
goalp->type |= INVALID_GOAL;
if ( optional_string("+No music") )
goalp->flags |= MGF_NO_MUSIC;
goalp->score = 0;
if ( optional_string("+Score:") ){
stuff_int(&goalp->score);
}
goalp->team = 0;
if ( optional_string("+Team:") ){
stuff_int( &goalp->team );
}
}
void parse_goals(mission *pm)
{
required_string("#Goals");
while (required_string_either("#Waypoints", "$Type:")){
parse_goal(pm);
}
}
void parse_waypoint_list(mission *pm)
{
waypoint_list *wpl;
Assert(Num_waypoint_lists < MAX_WAYPOINT_LISTS);
Assert(pm != NULL);
wpl = &Waypoint_lists[Num_waypoint_lists];
required_string("$Name:");
stuff_string(wpl->name, F_NAME, NAME_LENGTH);
required_string("$List:");
wpl->count = stuff_vector_list(wpl->waypoints, MAX_WAYPOINTS_PER_LIST);
Num_waypoint_lists++;
}
void parse_waypoints(mission *pm)
{
vec3d pos;
required_string("#Waypoints");
jump_node *jnp;
char file_name[MAX_FILENAME_LEN] = { 0 };
while (optional_string("$Jump Node:")) {
stuff_vector(&pos);
jnp = new jump_node(&pos);
Assert(jnp != NULL);
if (optional_string("$Jump Node Name:") || optional_string("+Jump Node Name:")) {
stuff_string(jnp->get_name_ptr(), F_NAME, NAME_LENGTH);
}
if(optional_string("+Model File:")){
stuff_string(file_name, F_NAME, MAX_FILENAME_LEN);
jnp->set_model(file_name);
}
if(optional_string("+Alphacolor:")) {
ubyte r,g,b,a;
stuff_ubyte(&r);
stuff_ubyte(&g);
stuff_ubyte(&b);
stuff_ubyte(&a);
jnp->set_alphacolor(r, g, b, a);
}
if(optional_string("+Hidden:")) {
int hide;
stuff_boolean(&hide);
jnp->show(!hide);
}
}
while (required_string_either("#Messages", "$Name:"))
parse_waypoint_list(pm);
}
void parse_messages(mission *pm, int flags)
{
required_string("#Messages");
mprintf(("Starting mission message count : %d\n", Num_message_waves));
// the message_parse function can be found in MissionMessage.h. The format in the
// mission file takes the same format as the messages in messages,tbl. Make parsing
// a whole lot easier!!!
while ( required_string_either("#Reinforcements", "$Name")){
message_parse((flags & MPF_IMPORT_FSM) != 0); // call the message parsing system
}
mprintf(("Ending mission message count : %d\n", Num_message_waves));
}
void parse_reinforcement(mission *pm)
{
reinforcements *ptr;
p_object *rforce_obj = NULL;
int instance = -1;
Assert(Num_reinforcements < MAX_REINFORCEMENTS);
Assert(pm != NULL);
ptr = &Reinforcements[Num_reinforcements];
required_string("$Name:");
stuff_string(ptr->name, F_NAME, NAME_LENGTH);
find_and_stuff("$Type:", &ptr->type, F_NAME, Reinforcement_type_names, Num_reinforcement_type_names, "reinforcement type");
required_string("$Num times:");
stuff_int(&ptr->uses);
ptr->num_uses = 0;
// reset the flags to 0
ptr->flags = 0;
if ( optional_string("+Arrival delay:") ){
stuff_int( &(ptr->arrival_delay) );
}
if ( optional_string("+No Messages:") ){
stuff_string_list( ptr->no_messages, MAX_REINFORCEMENT_MESSAGES );
}
if ( optional_string("+Yes Messages:") ){
stuff_string_list( ptr->yes_messages, MAX_REINFORCEMENT_MESSAGES );
}
// sanity check on the names of reinforcements
rforce_obj = mission_parse_get_parse_object(ptr->name);
if (rforce_obj == NULL) {
if ((instance = wing_name_lookup(ptr->name, 1)) == -1) {
Warning(LOCATION, "Reinforcement %s not found as ship or wing", ptr->name);
return;
}
} else {
instance = rforce_obj->wingnum;
}
// now, if the reinforcement is a wing, then set the number of waves of the wing == number of
// uses of the reinforcement
if (instance >= 0) {
Wings[instance].num_waves = ptr->uses;
}
Num_reinforcements++;
}
void parse_reinforcements(mission *pm)
{
Num_reinforcements = 0;
required_string("#Reinforcements");
while (required_string_either("#Background bitmaps", "$Name:"))
parse_reinforcement(pm);
}
void parse_bitmap(mission *pm)
{
/*
char name[NAME_LENGTH];
int z;
starfield_bitmaps *ptr;
Assert(Num_starfield_bitmaps < MAX_STARFIELD_BITMAPS);
Assert(pm != NULL);
ptr = &Starfield_bitmaps[Num_starfield_bitmaps];
required_string("$Bitmap:");
stuff_string(name, F_NAME, NULL);
for (z=0; z<Num_starfield_bitmap_lists; z++) {
if (!stricmp(name, Starfield_bitmap_list[z].name)){
break;
}
}
if ( z >= Num_starfield_bitmap_lists ) {
Warning( LOCATION, "Bitmap specified in mission not in game!\n" );
z = 0;
}
ptr->bitmap_index = z;
required_string("$Orientation:");
stuff_matrix(&ptr->m);
required_string("$Rotation rate:");
stuff_float(&ptr->rot);
required_string("$Distance:");
stuff_float(&ptr->dist);
required_string("$Light:");
stuff_int(&ptr->light);
Num_starfield_bitmaps++;
calculate_bitmap_points(ptr);
*/
Int3();
}
void parse_one_background(background_t *background)
{
// clear here too because this function can be called from more than one place
background->suns.clear();
background->bitmaps.clear();
// parse suns
while (optional_string("$Sun:"))
{
starfield_list_entry sle;
// filename
stuff_string(sle.filename, F_NAME, MAX_FILENAME_LEN);
// angles
required_string("+Angles:");
stuff_float(&sle.ang.p);
stuff_float(&sle.ang.b);
stuff_float(&sle.ang.h);
// scale
required_string("+Scale:");
stuff_float(&sle.scale_x);
sle.scale_y = sle.scale_x;
sle.div_x = 1;
sle.div_y = 1;
// add it
background->suns.push_back(sle);
}
// parse starfields
while (optional_string("$Starbitmap:"))
{
starfield_list_entry sle;
// filename
stuff_string(sle.filename, F_NAME, MAX_FILENAME_LEN);
// angles
required_string("+Angles:");
stuff_float(&sle.ang.p);
stuff_float(&sle.ang.b);
stuff_float(&sle.ang.h);
// scale
if (optional_string("+Scale:"))
{
stuff_float(&sle.scale_x);
sle.scale_y = sle.scale_x;
sle.div_x = 1;
sle.div_y = 1;
}
else
{
required_string("+ScaleX:");
stuff_float(&sle.scale_x);
required_string("+ScaleY:");
stuff_float(&sle.scale_y);
required_string("+DivX:");
stuff_int(&sle.div_x);
required_string("+DivY:");
stuff_int(&sle.div_y);
}
// add it
background->bitmaps.push_back(sle);
}
}
void parse_bitmaps(mission *pm)
{
char str[MAX_FILENAME_LEN];
int z;
required_string("#Background bitmaps");
required_string("$Num stars:");
stuff_int(&Num_stars);
if (Num_stars >= MAX_STARS)
Num_stars = MAX_STARS;
required_string("$Ambient light level:");
stuff_int(&pm->ambient_light_level);
if (pm->ambient_light_level == 0)
{
pm->ambient_light_level = 0x00787878;
}
// This should call light_set_ambient() to
// set the ambient light
Nebula_index = -1;
Mission_palette = 1;
// neb2 info
strcpy(Neb2_texture_name, "Eraseme3");
Neb2_poof_flags = ((1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<4) | (1<<5));
if(optional_string("+Neb2:")){
stuff_string(Neb2_texture_name, F_NAME, MAX_FILENAME_LEN);
required_string("+Neb2Flags:");
stuff_int(&Neb2_poof_flags);
// initialize neb effect. its gross to do this here, but Fred is dumb so I have no choice ... :(
if(Fred_running && (pm->flags & MISSION_FLAG_FULLNEB)){
neb2_post_level_init();
}
}
if(pm->flags & MISSION_FLAG_FULLNEB){
// no regular nebula stuff
nebula_close();
} else {
if (optional_string("+Nebula:")) {
stuff_string(str, F_NAME, MAX_FILENAME_LEN);
// parse the proper nebula type (full or not)
for (z=0; z<NUM_NEBULAS; z++){
if(pm->flags & MISSION_FLAG_FULLNEB){
if (!stricmp(str, Neb2_filenames[z])) {
Nebula_index = z;
break;
}
} else {
if (!stricmp(str, Nebula_filenames[z])) {
Nebula_index = z;
break;
}
}
}
if (optional_string("+Color:")) {
stuff_string(str, F_NAME, MAX_FILENAME_LEN);
for (z=0; z<NUM_NEBULA_COLORS; z++){
if (!stricmp(str, Nebula_colors[z])) {
Mission_palette = z;
break;
}
}
}
if (optional_string("+Pitch:")){
stuff_int(&Nebula_pitch);
} else {
Nebula_pitch = 0;
}
if (optional_string("+Bank:")){
stuff_int(&Nebula_bank);
} else {
Nebula_bank = 0;
}
if (optional_string("+Heading:")){
stuff_int(&Nebula_heading);
} else {
Nebula_heading = 0;
}
}
if (Nebula_index >= 0){
nebula_init(Nebula_filenames[Nebula_index], Nebula_pitch, Nebula_bank, Nebula_heading);
} else {
nebula_close();
}
}
// Goober5000
Num_backgrounds = 0;
while (optional_string("$Bitmap List:") || check_for_string("$Sun:") || check_for_string("$Starbitmap:"))
{
// don't allow overflow; just make sure the last background is the last read
if (Num_backgrounds >= MAX_BACKGROUNDS)
{
Warning(LOCATION, "Too many backgrounds in mission! Max is %d.", MAX_BACKGROUNDS);
Num_backgrounds = MAX_BACKGROUNDS - 1;
}
parse_one_background(&Backgrounds[Num_backgrounds]);
Num_backgrounds++;
}
// Goober5000
stars_load_first_valid_background();
if (optional_string("$Environment Map:")) {
stuff_string(pm->envmap_name, F_NAME, MAX_FILENAME_LEN);
}
// bypass spurious stuff from e.g. FS1 missions
skip_to_start_of_string("#");
}
void parse_asteroid_fields(mission *pm)
{
int i, count, subtype;
Assert(pm != NULL);
for (i=0; i<MAX_ASTEROID_FIELDS; i++)
Asteroid_field.num_initial_asteroids = 0;
i = 0;
count = 0;
if (!optional_string("#Asteroid Fields"))
return;
while (required_string_either("#", "$density:")) {
float speed, density;
int type;
Assert(i < 1);
required_string("$Density:");
stuff_float(&density);
Asteroid_field.num_initial_asteroids = (int) density;
Asteroid_field.field_type = FT_ACTIVE;
if (optional_string("+Field Type:")) {
stuff_int( &type );
Asteroid_field.field_type = (field_type_t)type;
}
Asteroid_field.debris_genre = DG_ASTEROID;
if (optional_string("+Debris Genre:")) {
stuff_int( &type );
Asteroid_field.debris_genre = (debris_genre_t)type;
}
Asteroid_field.field_debris_type[0] = -1;
Asteroid_field.field_debris_type[1] = -1;
Asteroid_field.field_debris_type[2] = -1;
if (Asteroid_field.debris_genre == DG_SHIP) {
if (optional_string("+Field Debris Type:")) {
stuff_int(&Asteroid_field.field_debris_type[0]);
}
if (optional_string("+Field Debris Type:")) {
stuff_int(&Asteroid_field.field_debris_type[1]);
}
if (optional_string("+Field Debris Type:")) {
stuff_int(&Asteroid_field.field_debris_type[2]);
}
} else {
// debris asteroid
if (optional_string("+Field Debris Type:")) {
stuff_int(&subtype);
Asteroid_field.field_debris_type[subtype] = 1;
count++;
}
if (optional_string("+Field Debris Type:")) {
stuff_int(&subtype);
Asteroid_field.field_debris_type[subtype] = 1;
count++;
}
if (optional_string("+Field Debris Type:")) {
stuff_int(&subtype);
Asteroid_field.field_debris_type[subtype] = 1;
count++;
}
}
// backward compatibility
if ( (Asteroid_field.debris_genre == DG_ASTEROID) && (count == 0) ) {
Asteroid_field.field_debris_type[0] = 0;
}
required_string("$Average Speed:");
stuff_float(&speed);
vm_vec_rand_vec_quick(&Asteroid_field.vel);
vm_vec_scale(&Asteroid_field.vel, speed);
Asteroid_field.speed = speed;
required_string("$Minimum:");
stuff_vector(&Asteroid_field.min_bound);
required_string("$Maximum:");
stuff_vector(&Asteroid_field.max_bound);
if (optional_string("+Inner Bound:")) {
Asteroid_field.has_inner_bound = 1;
required_string("$Minimum:");
stuff_vector(&Asteroid_field.inner_min_bound);
required_string("$Maximum:");
stuff_vector(&Asteroid_field.inner_max_bound);
} else {
Asteroid_field.has_inner_bound = 0;
}
i++;
}
}
void parse_variables()
{
int i, j, k, num_variables;
if (! optional_string("#Sexp_variables") ) {
return;
} else {
num_variables = stuff_sexp_variable_list();
}
// yeesh - none of this should be done in FRED :)
// It shouldn't be done for missions in the tecroom either. They should default to whatever FRED set them to
if (!Fred_running && (Game_mode & GM_CAMPAIGN_MODE))
{
// Goober5000 - now set the default value, if it's a campaign-persistent variable
// look through all previous missions (by doing it this way, we will continually
// overwrite the variable with the most recent information)
for (i=0; i<Campaign.num_missions; i++)
{
if (Campaign.missions[i].completed != 1)
continue;
// loop through this particular previous mission's variables
for (j=0; j<Campaign.missions[i].num_saved_variables; j++)
{
// loop through the current mission's variables
for (k=0; k<num_variables; k++)
{
// if the active mission has a variable with the same name as a campaign
// variable AND it is not a block variable, override its initial value
// with the previous mission's value
if (!(stricmp(Sexp_variables[k].variable_name, Campaign.missions[i].saved_variables[j].variable_name))
&& !(Campaign.missions[i].saved_variables[j].type & SEXP_VARIABLE_BLOCK))
{
Sexp_variables[k].type = Campaign.missions[i].saved_variables[j].type;
strcpy(Sexp_variables[k].text, Campaign.missions[i].saved_variables[j].text);
}
}
}
}
// Goober5000 - next, see if any player-persistent variables are set
for (i=0; i<Player->num_variables; i++)
{
// loop through the current mission's variables
for (j=0; j<num_variables; j++)
{
// if the active mission has a variable with the same name as a player
// variable AND it is not a block variable, override its initial value
// with the previous mission's value
if (!(stricmp(Sexp_variables[j].variable_name, Player->player_variables[i].variable_name))
&& !(Player->player_variables[i].type & SEXP_VARIABLE_BLOCK))
{
Sexp_variables[j].type = Player->player_variables[i].type;
strcpy(Sexp_variables[j].text, Player->player_variables[i].text);
}
}
}
}
}
int parse_mission(mission *pm, int flags)
{
int i;
Player_starts = Num_cargo = Num_waypoint_lists = Num_goals = Num_wings = 0;
Player_start_shipnum = -1;
*Player_start_shipname = 0; // make the string 0 length for checking later
memset( &Player_start_pobject, 0, sizeof(Player_start_pobject) );
Fred_num_texture_replacements = 0;
// initialize the initially_docked array.
for ( i = 0; i < MAX_SHIPS; i++ ) {
Initially_docked[i].docker[0] = '\0';
Initially_docked[i].dockee[0] = '\0';
Initially_docked[i].docker_point[0] = '\0';
Initially_docked[i].dockee_point[0] = '\0';
}
Total_initially_docked = 0;
list_init(&Ship_arrival_list); // init list for arrival ships
parse_init();
Subsys_index = 0;
Subsys_status_size = 0;
if (Subsys_status != NULL) {
vm_free( Subsys_status );
Subsys_status = NULL;
}
parse_mission_info(pm);
Current_file_checksum = netmisc_calc_checksum(pm,MISSION_CHECKSUM_SIZE);
if (flags & MPF_ONLY_MISSION_INFO)
return 0;
parse_plot_info(pm);
parse_variables();
parse_briefing_info(pm); // TODO: obsolete code, keeping so we don't obsolete existing mission files
parse_fiction(pm);
parse_cmd_briefs(pm);
parse_briefing(pm, flags);
parse_debriefing_new(pm);
parse_player_info(pm);
parse_objects(pm, flags);
parse_wings(pm);
parse_events(pm);
parse_goals(pm);
parse_waypoints(pm);
parse_messages(pm, flags);
parse_reinforcements(pm);
parse_bitmaps(pm);
parse_asteroid_fields(pm);
parse_music(pm, flags);
// if we couldn't load some mod data
if ((Num_unknown_ship_classes > 0) /*|| (Num_unknown_weapon_classes > 0)*/) {
// don't do this in FRED; we will display a separate popup
if (!Fred_running) {
// build up the prompt...
char text[1024];
sprintf(text, "Warning!\n\nFreeSpace was unable to find %d ship class%s while loading this mission. This can happen if you try to play something that is incompatible with the current mod.\n\n", Num_unknown_ship_classes, (Num_unknown_ship_classes > 1) ? "es" : "");
if (Game_mode & GM_CAMPAIGN_MODE) {
strcat(text, "(The current campaign is \"");
strcat(text, Campaign.name);
} else {
strcat(text, "(The current mission is \"");
strcat(text, pm->name);
}
strcat(text, "\", and the current mod is \"");
if (Cmdline_mod == NULL || *Cmdline_mod == 0) {
strcat(text, "<retail default> ");
} else {
for (char *mod_token = Cmdline_mod; strlen(mod_token) != 0; mod_token += strlen(mod_token) + 1) {
strcat(text, mod_token);
strcat(text, " ");
}
}
strcpy(text + strlen(text) - 1, "\".)\n\n You can continue to load the mission, but it is quite likely that you will encounter a large number of mysterious errors. It is recommended that you either select a campaign that is compatible with your current mod, or else exit FreeSpace and select a different mod.\n\n");
strcat(text, "Do you want to continue to load the mission?");
// now display the popup
int popup_rval = popup(PF_TITLE_BIG | PF_TITLE_RED, 2, POPUP_YES, POPUP_NO, text);
if (popup_rval != 0) {
return -2;
}
}
}
post_process_mission();
// success
return 0;
}
void post_process_mission()
{
int i;
int indices[MAX_SHIPS], objnum;
ship_weapon *swp;
ship_obj *so;
// Goober5000 - must be done before all other post processing
post_process_ships_wings();
// the player_start_shipname had better exist at this point!
Player_start_shipnum = ship_name_lookup( Player_start_shipname );
Assert ( Player_start_shipnum != -1 );
Assert ( Player_start_pobject.flags & P_SF_PLAYER_START_VALID );
// Assign objnum, shipnum, etc. to the player structure
objnum = Ships[Player_start_shipnum].objnum;
Player_obj = &Objects[objnum];
if (!Fred_running){
Player->objnum = objnum;
}
Player_obj->flags |= OF_PLAYER_SHIP; // make this object a player controlled ship.
Player_ship = &Ships[Player_start_shipnum];
Player_ai = &Ai_info[Player_ship->ai_index];
Player_ai->targeted_subsys = NULL;
Player_ai->targeted_subsys_parent = -1;
// determine if player start has initial velocity and set forward cruise percent to relect this
if ( Player_obj->phys_info.vel.xyz.z > 0.0f )
Player->ci.forward_cruise_percent = Player_obj->phys_info.vel.xyz.z / Player_ship->current_max_speed * 100.0f;
// set up wing indexes
for (i = 0; i < MAX_STARTING_WINGS; i++ ) {
Starting_wings[i] = wing_name_lookup(Starting_wing_names[i], 1);
}
for (i = 0; i < MAX_SQUADRON_WINGS; i++ ) {
Squadron_wings[i] = wing_name_lookup(Squadron_wing_names[i], 1);
}
for (i = 0; i < MAX_TVT_WINGS; i++ ) {
TVT_wings[i] = wing_name_lookup(TVT_wing_names[i], 1);
}
// when TVT, hack starting wings to be team wings
if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM)){
Assert(MAX_TVT_WINGS <= MAX_STARTING_WINGS);
for (i=0; i<MAX_STARTING_WINGS; i++)
{
if (i<MAX_TVT_WINGS)
Starting_wings[i] = TVT_wings[i];
else
Starting_wings[i] = -1;
}
}
init_ai_system();
// Goober5000 - this needs to be called only once after parsing of objects and wings is complete
// (for individual invalidation, see mission_parse_mark_non_arrival)
mission_parse_mark_non_arrivals();
// deal with setting up arrival location for all ships. Must do this now after all ships are created
mission_parse_set_arrival_locations();
// clear out information about arriving support ships
Arriving_support_ship = NULL;
Num_arriving_repair_targets = 0;
// convert all ship name indices to ship indices now that mission has been loaded
if (Fred_running) {
for (i=0; i<Num_parse_names; i++) {
indices[i] = ship_name_lookup(Parse_names[i], 1);
if (indices[i] < 0)
Warning(LOCATION, "Ship name \"%s\" referenced, but this ship doesn't exist", Parse_names[i]);
}
for (i=0; i<MAX_SHIPS; i++) {
if ((Ships[i].objnum >= 0) && (Ships[i].arrival_anchor >= 0) && (Ships[i].arrival_anchor < SPECIAL_ARRIVAL_ANCHOR_FLAG))
Ships[i].arrival_anchor = indices[Ships[i].arrival_anchor];
if ( (Ships[i].objnum >= 0) && (Ships[i].departure_anchor >= 0) )
Ships[i].departure_anchor = indices[Ships[i].departure_anchor];
}
for (i=0; i<MAX_WINGS; i++) {
if (Wings[i].wave_count && (Wings[i].arrival_anchor >= 0) && (Wings[i].arrival_anchor < SPECIAL_ARRIVAL_ANCHOR_FLAG))
Wings[i].arrival_anchor = indices[Wings[i].arrival_anchor];
if (Wings[i].wave_count && (Wings[i].departure_anchor >= 0) )
Wings[i].departure_anchor = indices[Wings[i].departure_anchor];
}
}
// before doing anything else, we must validate all of the sexpressions that were loaded into the mission.
// Loop through the Sexp_nodes array and send the top level functions to the check_sexp_syntax parser
for (i = 0; i < Num_sexp_nodes; i++) {
if ( is_sexp_top_level(i) && (!Fred_running || (i != Sexp_clipboard))) {
int result, bad_node, op;
op = get_operator_index(CTEXT(i));
Assert(op != -1); // need to make sure it is an operator before we treat it like one..
result = check_sexp_syntax( i, query_operator_return_type(op), 1, &bad_node);
// entering this if statement will result in program termination!!!!!
// print out an error based on the return value from check_sexp_syntax()
if ( result ) {
char sexp_str[8192], text[8192];
convert_sexp_to_string( i, sexp_str, SEXP_ERROR_CHECK_MODE);
sprintf(text, "%s.\n\nIn sexpression: %s\n(Error appears to be: %s)",
sexp_error_message(result), sexp_str, Sexp_nodes[bad_node].text);
if (!Fred_running)
Error( LOCATION, text );
else
Warning( LOCATION, text );
}
}
}
ai_post_process_mission();
// first we need to clear out the counts for this mission
ship_clear_ship_type_counts();
// we must also count all of the ships of particular types. We count all of the ships that do not have
// their SF_IGNORE_COUNT flag set. We don't count ships in wings when the equivalent wing flag is set.
// in counting ships in wings, we increment the count by the wing's wave count to account for everyone.
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
int siflags, num, shipnum;
shipnum = Objects[so->objnum].instance;
// pass over non-ship objects and player ship objects
if ( Ships[shipnum].objnum == -1 || (Objects[Ships[shipnum].objnum].flags & OF_PLAYER_SHIP) )
continue;
if ( Ships[shipnum].flags & SF_IGNORE_COUNT )
continue;
if ( (Ships[shipnum].wingnum != -1) && (Wings[Ships[shipnum].wingnum].flags & WF_IGNORE_COUNT) )
continue;
siflags = Ship_info[Ships[shipnum].ship_info_index].flags;
// determine the number of times we need to add this ship into the count
// if ( Ships[i].wingnum == -1 )
num = 1;
// else
// num = Wings[Ships[i].wingnum].num_waves;
ship_add_ship_type_count( Ships[shipnum].ship_info_index, num );
}
// now go through the list of ships yet to arrive
for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
{
int siflags, num;
// go through similar motions as above
if ( p_objp->flags & P_SF_IGNORE_COUNT )
continue;
if ( (p_objp->wingnum != -1) && (Wings[p_objp->wingnum].flags & WF_IGNORE_COUNT) )
continue;
siflags = Ship_info[p_objp->ship_class].flags;
if ( p_objp->wingnum == -1 )
num = 1;
else
num = Wings[p_objp->wingnum].num_waves - 1; // subtract one since we already counted the first wave
ship_add_ship_type_count( p_objp->ship_class, num );
}
// set player weapons that are selected by default
// AL 09/17/97: I added this code to select the first primary/secondary weapons,
// since I noticed the player ship sometimes doesn't get default weapons selected
// DB: modified 4/23/98 to take multiplayer into account. Under certain circumstances, multiplayer netplayer ships
// had their current_primary_bank and current_secondary_bank set to -1 (from ship_set()) and left there since
// Player_ship is not the only one we need to need about.
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
ship *shipp = &Ships[Objects[so->objnum].instance];
// don't process non player wing ships
if ( !(shipp->flags & SF_FROM_PLAYER_WING ) )
continue;
swp = &shipp->weapons;
// swp = &Player_ship->weapons;
if ( swp->num_primary_banks > 0 ) {
swp->current_primary_bank = 0; // currently selected primary bank
// ship_primary_changed(shipp);
}
if ( swp->num_secondary_banks > 0 ) {
swp->current_secondary_bank = 0; // currently selected secondary bank
// ship_secondary_changed(shipp);
}
}
ets_init_ship(Player_obj); // init ETS data for the player
// put the timestamp stuff here for now
Mission_arrival_timestamp = timestamp( ARRIVAL_TIMESTAMP );
Mission_departure_timestamp = timestamp( DEPARTURE_TIMESTAMP );
Mission_end_time = -1;
if(Game_mode & GM_MULTIPLAYER){
multi_respawn_build_points();
}
// maybe reset hotkey defaults when loading new mission
if ( Last_file_checksum != Current_file_checksum ){
mission_hotkey_reset_saved();
}
Allow_arrival_music_timestamp=timestamp(0);
Allow_arrival_message_timestamp=timestamp(0);
Arrival_message_delay_timestamp = timestamp(-1);
int idx;
for(idx=0; idx<2; idx++){
Allow_arrival_music_timestamp_m[idx]=timestamp(0);
Allow_arrival_message_timestamp_m[idx]=timestamp(0);
Arrival_message_delay_timestamp_m[idx] = timestamp(-1);
}
Last_file_checksum = Current_file_checksum;
}
int get_mission_info(char *filename, mission *mission_p, bool basic)
{
char real_fname[MAX_FILENAME_LEN];
strcpy(real_fname, filename);
char *p = strrchr(real_fname, '.');
if (p) *p = 0; // remove any extension
strcat(real_fname, FS_MISSION_FILE_EXT); // append mission extension
int rval, filelength;
// if mission_p is NULL, make it point to The_mission
if ( mission_p == NULL )
mission_p = &The_mission;
// open localization
lcl_ext_open();
do {
CFILE *ftemp = cfopen(real_fname, "rt");
if (!ftemp) {
rval = -1;
break;
}
// 7/9/98 -- MWA -- check for 0 length file.
filelength = cfilelength(ftemp);
cfclose(ftemp);
if (filelength == 0) {
rval = -1;
break;
}
if ((rval = setjmp(parse_abort)) != 0) {
mprintf(("MISSIONS: Unable to parse '%s'! Error code = %i.\n", real_fname, rval));
break;
}
read_file_text(real_fname, CF_TYPE_MISSIONS);
memset( mission_p, 0, sizeof(mission) );
parse_init(basic);
parse_mission_info(mission_p, basic);
} while (0);
// close localization
lcl_ext_close();
return rval;
}
// Initialize the mission parse process.
void parse_init(bool basic)
{
reset_parse();
for (int i = 0; i < MAX_CARGO; i++)
Cargo_names[i] = Cargo_names_buf[i]; // make a pointer array for compatibility
Total_goal_ship_names = 0;
// if we are just wanting basic info then we shouldn't need sexps
// (prevents memory fragmentation with the now dynamic Sexp_nodes[])
if ( !basic )
init_sexp();
}
// mai parse routine for parsing a mission. The default parameter flags tells us which information
// to get when parsing the mission. 0 means get everything (default). Other flags just gets us basic
// info such as game type, number of players etc.
int parse_main(char *mission_name, int flags)
{
int rval, i;
// reset parse error stuff
Num_unknown_ship_classes = 0;
Num_unknown_weapon_classes = 0;
// fill in Ship_class_names array with the names from the ship_info struct;
Num_parse_names = 0;
Num_path_restrictions = 0;
Assert(Num_ship_classes <= MAX_SHIP_CLASSES);
for (i = 0; i < Num_ship_classes; i++)
Ship_class_names[i] = Ship_info[i].name;
// open localization
lcl_ext_open();
do {
// don't do this for imports
if (!(flags & MPF_IMPORT_FSM)) {
CFILE *ftemp = cfopen(mission_name, "rt", CFILE_NORMAL, CF_TYPE_MISSIONS);
// fail situation.
if (!ftemp) {
if (!Fred_running)
Error( LOCATION, "Couldn't open mission '%s'\n", mission_name );
Current_file_length = -1;
Current_file_checksum = 0;
rval = -1;
break;
}
Current_file_length = cfilelength(ftemp);
cfclose(ftemp);
}
if ((rval = setjmp(parse_abort)) != 0) {
mprintf(("MISSIONS: Unable to parse '%s'! Error code = %i.\n", mission_name, rval));
break;
}
// import?
if (flags & MPF_IMPORT_FSM) {
read_file_text(mission_name, CF_TYPE_ANY);
convertFSMtoFS2();
} else {
read_file_text(mission_name, CF_TYPE_MISSIONS);
}
memset(&The_mission, 0, sizeof(The_mission));
rval = parse_mission(&The_mission, flags);
display_parse_diagnostics();
} while (0);
// close localization
lcl_ext_close();
if (!Fred_running)
strcpy(Mission_filename, mission_name);
return rval;
}
void mission_parse_close()
{
// free subsystems
if (Subsys_status != NULL)
{
vm_free(Subsys_status);
Subsys_status = NULL;
}
// free parse object dock lists
for (int i = 0; i < (int)Parse_objects.size(); i++)
{
dock_free_instances(&Parse_objects[i]);
}
}
// sets the arrival location of the ships in wingp. pass num_to_set since the threshold value
// for wings may have us create more ships in the wing when there are still some remaining
void mission_set_wing_arrival_location( wing *wingp, int num_to_set )
{
int index;
// get the starting index into the ship_index array of the first ship whose location we need set.
index = wingp->current_count - num_to_set;
if ( (wingp->arrival_location == ARRIVE_FROM_DOCK_BAY) || (wingp->arrival_location == ARRIVE_AT_LOCATION) ) {
while ( index < wingp->current_count ) {
object *objp;
objp = &Objects[Ships[wingp->ship_index[index]].objnum];
mission_set_arrival_location(wingp->arrival_anchor, wingp->arrival_location, wingp->arrival_distance, OBJ_INDEX(objp), wingp->arrival_path_mask, NULL, NULL);
index++;
}
} else {
object *leader_objp;
vec3d pos;
matrix orient;
int wing_index;
// wing is not arriving from a docking bay -- possibly move them based on arriving near
// or in front of some other ship.
index = wingp->current_count - num_to_set;
leader_objp = &Objects[Ships[wingp->ship_index[index]].objnum];
if (mission_set_arrival_location(wingp->arrival_anchor, wingp->arrival_location, wingp->arrival_distance, OBJ_INDEX(leader_objp), wingp->arrival_path_mask, &pos, &orient)) {
// modify the remaining ships created
index++;
wing_index = 1;
while ( index < wingp->current_count ) {
object *objp;
objp = &Objects[Ships[wingp->ship_index[index]].objnum];
// change the position of the next ships in the wing. Use the cool function in AiCode.cpp which
// Mike K wrote to give new positions to the wing members.
get_absolute_wing_pos( &objp->pos, leader_objp, wing_index++, 0);
memcpy( &objp->orient, &orient, sizeof(matrix) );
index++;
}
}
}
// create warp effect if in mission and not arriving from docking bay
if ( (Game_mode & GM_IN_MISSION) && (wingp->arrival_location != ARRIVE_FROM_DOCK_BAY) ) {
for ( index = wingp->current_count - num_to_set; index < wingp->current_count; index ++ ) {
shipfx_warpin_start( &Objects[Ships[wingp->ship_index[index]].objnum] );
}
}
}
// this function is called after a mission is parsed to set the arrival locations of all ships in the
// mission to the apprioriate spot. Mainly needed because ships might be in dock bays to start
// the mission, so their AI mode must be set appropriately.
void mission_parse_set_arrival_locations()
{
int i;
object *objp;
if ( Fred_running )
return;
obj_merge_created_list();
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
ship *shipp;
if ( objp->type != OBJ_SHIP )
continue;
shipp = &Ships[objp->instance];
// if the ship is in a wing -- ignore the info and let the wing info handle it
if ( shipp->wingnum != -1 )
continue;
// call function to set arrival location for this ship.
mission_set_arrival_location( shipp->arrival_anchor, shipp->arrival_location, shipp->arrival_distance, OBJ_INDEX(objp), shipp->arrival_path_mask, NULL, NULL);
}
// do the wings
for ( i = 0; i < Num_wings; i++ ) {
// if wing has no ships, then don't process it.
if ( Wings[i].current_count == 0 )
continue;
mission_set_wing_arrival_location( &Wings[i], Wings[i].current_count );
}
}
// Goober5000
bool sexp_is_locked_false(int node)
{
// dunno why these are different, but they are
if (Fred_running)
return (node == Locked_sexp_false);
else
return (Sexp_nodes[node].value == SEXP_KNOWN_FALSE);
}
// Goober5000
// NOTE - in both retail and SCP, the dock "leader" is defined as the only guy in his
// group with a non-false arrival cue
void parse_object_mark_dock_leader_helper(p_object *pobjp, p_dock_function_info *infop)
{
int cue_to_check;
// if this guy is part of a wing, he uses his wing's arrival cue
if (pobjp->wingnum >= 0)
{
cue_to_check = Wings[pobjp->wingnum].arrival_cue;
}
// check the object's arrival cue
else
{
cue_to_check = pobjp->arrival_cue;
}
// is he a leader (using the definition above)?
if (!sexp_is_locked_false(cue_to_check))
{
p_object *existing_leader;
// increment number of leaders found
infop->maintained_variables.int_value++;
// see if we already found a leader
existing_leader = infop->maintained_variables.objp_value;
if (existing_leader != NULL)
{
// keep existing leader if he has a higher priority than us
if (ship_class_compare(pobjp->ship_class, existing_leader->ship_class) < 0)
{
// set my arrival cue to false
free_sexp2(pobjp->arrival_cue);
pobjp->arrival_cue = Locked_sexp_false;
return;
}
// otherwise, unmark the existing leader and set his arrival cue to false
existing_leader->flags &= ~P_SF_DOCK_LEADER;
free_sexp2(existing_leader->arrival_cue);
existing_leader->arrival_cue = Locked_sexp_false;
}
// mark and save me as the leader
pobjp->flags |= P_SF_DOCK_LEADER;
infop->maintained_variables.objp_value = pobjp;
}
}
// Goober5000
void parse_object_set_handled_flag_helper(p_object *pobjp, p_dock_function_info *infop)
{
pobjp->flags2 |= P2_ALREADY_HANDLED;
}
// Goober5000
void parse_object_clear_handled_flag_helper(p_object *pobjp, p_dock_function_info *infop)
{
pobjp->flags2 &= ~P2_ALREADY_HANDLED;
}
// Goober5000
void parse_object_clear_all_handled_flags()
{
// clear flag for all ships
for (int i = 0; i < (int)Parse_objects.size(); i++)
{
p_object *pobjp = &Parse_objects[i];
p_dock_function_info dfi;
// since we're going through all objects, this object may not be docked
if (!object_is_docked(pobjp))
continue;
// has this object (by extension, this group of docked objects) been cleared already?
if (!(pobjp->flags2 & P2_ALREADY_HANDLED))
continue;
// clear the handled flag for this group
dock_evaluate_all_docked_objects(pobjp, &dfi, parse_object_clear_handled_flag_helper);
}
}
// Goober5000
// This function iterates through the Initially_docked array and builds the dock trees
// for each parse object. This can only be done after all objects and wings have been parsed.
void mission_parse_set_up_initial_docks()
{
int i;
// build trees
for (i = 0; i < Total_initially_docked; i++)
{
char *docker_point, *dockee_point;
p_object *docker, *dockee;
// resolve the docker and dockee
docker = mission_parse_get_parse_object(Initially_docked[i].docker);
dockee = mission_parse_get_parse_object(Initially_docked[i].dockee);
Assert((docker != NULL) && (dockee != NULL));
// resolve the dockpoints
docker_point = Initially_docked[i].docker_point;
dockee_point = Initially_docked[i].dockee_point;
// if they're not already docked, dock them
if (!dock_check_find_direct_docked_object(docker, dockee))
{
dock_dock_objects(docker, docker_point, dockee, dockee_point);
}
}
// now resolve the leader of each tree
for (i = 0; i < (int)Parse_objects.size(); i++)
{
p_object *pobjp = &Parse_objects[i];
p_dock_function_info dfi;
// since we're going through all objects, this object may not be docked
if (!object_is_docked(pobjp))
continue;
// has this object (by extension, this group of docked objects) been handled already?
if (pobjp->flags2 & P2_ALREADY_HANDLED)
continue;
// find the dock leader(s)
dock_evaluate_all_docked_objects(pobjp, &dfi, parse_object_mark_dock_leader_helper);
// display an error if necessary
if (dfi.maintained_variables.int_value == 0)
{
Warning(LOCATION, "No dock leaders found in the docking group containing %s. The group will not appear in-mission!\n", pobjp->name);
}
else if (dfi.maintained_variables.int_value > 1)
{
Warning(LOCATION, "There are multiple dock leaders in the docking group containing the leader %s! Setting %s as the sole leader...\n", dfi.maintained_variables.objp_value->name, dfi.maintained_variables.objp_value->name);
}
// clear dfi stuff
dfi.maintained_variables.int_value = 0;
dfi.maintained_variables.objp_value = NULL;
// set the handled flag for this group
dock_evaluate_all_docked_objects(pobjp, &dfi, parse_object_set_handled_flag_helper);
}
// now clear the handled flags
parse_object_clear_all_handled_flags();
}
// function which returns true or false if the given mission support multiplayers
int mission_parse_is_multi(char *filename, char *mission_name)
{
int rval, game_type;
int filelength;
CFILE *ftemp;
// new way of getting information. Open the file, and just get the name and the game_type flags.
// return the flags if a multiplayer mission
ftemp = cfopen(filename, "rt");
if (!ftemp)
return 0;
// 7/9/98 -- MWA -- check for 0 length file.
filelength = cfilelength(ftemp);
cfclose(ftemp);
if ( filelength == 0 )
return 0;
// open localization
lcl_ext_open();
game_type = 0;
do {
if ((rval = setjmp(parse_abort)) != 0) {
mprintf(("MISSIONS: Unable to parse '%s'! Error code = %i.\n", filename, rval));
break;
}
read_file_text(filename, CF_TYPE_MISSIONS);
reset_parse();
if ( skip_to_string("$Name:") != 1 ) {
nprintf(("Network", "Unable to process %s because we couldn't find $Name:", filename));
break;
}
stuff_string( mission_name, F_NAME, NAME_LENGTH );
if ( skip_to_string("+Game Type Flags:") != 1 ) {
nprintf(("Network", "Unable to process %s because we couldn't find +Game Type Flags:\n", filename));
break;
}
stuff_int(&game_type);
} while (0);
// close localization
lcl_ext_close();
return (game_type & MISSION_TYPE_MULTI) ? game_type : 0;
}
// function which gets called to retrieve useful information about a mission. We will get the
// name, description, and number of players for a mission. Probably used for multiplayer only?
// The calling function can use the information in The_mission to get the name/description of the mission
// if needed.
int mission_parse_get_multi_mission_info( char *filename )
{
if ( get_mission_info(filename, &The_mission) )
return -1;
Assert( The_mission.game_type & MISSION_TYPE_MULTI ); // assume multiplayer only for now?
// return the number of parse_players. later, we might want to include (optionally?) the number
// of other ships in the main players wing (usually wing 'alpha') for inclusion of number of
// players allowed.
return The_mission.num_players;
}
// return the parse object on the ship arrival list associated with the given name
p_object *mission_parse_get_arrival_ship(char *name)
{
p_object *p_objp;
if (name == NULL)
return NULL;
for (p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
{
if (!stricmp(p_objp->name, name))
return p_objp; // still on the arrival list
}
return NULL;
}
// return the parse object on the ship arrival list associated with the given signature
p_object *mission_parse_get_arrival_ship(ushort net_signature)
{
p_object *p_objp;
for (p_objp = GET_FIRST(&Ship_arrival_list); p_objp !=END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
{
if (p_objp->net_signature == net_signature)
return p_objp; // still on the arrival list
}
return NULL;
}
// mission_set_arrival_location() sets the arrival location of a parse object according to the arrival location
// of the object. Returns true if object set to new position, false if not.
int mission_set_arrival_location(int anchor, int location, int dist, int objnum, int path_mask, vec3d *new_pos, matrix *new_orient)
{
int shipnum, anchor_objnum;
vec3d anchor_pos, rand_vec, new_fvec;
matrix orient;
if ( location == ARRIVE_AT_LOCATION )
return 0;
Assert(anchor >= 0);
// this ship might possibly arrive at another location. The location is based on the
// proximity of some ship (and some other special tokens)
if (anchor & SPECIAL_ARRIVAL_ANCHOR_FLAG)
{
bool get_players = (anchor & SPECIAL_ARRIVAL_ANCHOR_PLAYER_FLAG) > 0;
// filter out iff
int iff_index = anchor;
iff_index &= ~SPECIAL_ARRIVAL_ANCHOR_FLAG;
iff_index &= ~SPECIAL_ARRIVAL_ANCHOR_PLAYER_FLAG;
// get ship
shipnum = ship_get_random_team_ship(iff_get_mask(iff_index), get_players ? SHIP_GET_ONLY_PLAYERS : SHIP_GET_ANY_SHIP);
}
// if we didn't find the arrival anchor in the list of special nodes, then do a
// ship name lookup on the anchor
else
{
shipnum = ship_name_lookup(Parse_names[anchor]);
}
// if we didn't get an object from one of the above functions, then make the object
// arrive at its placed location
if (shipnum < 0)
{
Assert ( location != ARRIVE_FROM_DOCK_BAY ); // bogus data somewhere!!! get mwa
nprintf (("allender", "couldn't find ship for arrival anchor -- using location ship created at"));
return 0;
}
// take the shipnum and get the position. once we have positions, we can determine where
// to make this ship appear
Assert ( shipnum != -1 );
anchor_objnum = Ships[shipnum].objnum;
anchor_pos = Objects[anchor_objnum].pos;
// if arriving from docking bay, then set ai mode and call function as per AL's instructions.
if ( location == ARRIVE_FROM_DOCK_BAY ) {
vec3d pos, fvec;
// if we get an error, just let the ship arrive(?)
if ( ai_acquire_emerge_path(&Objects[objnum], anchor_objnum, path_mask, &pos, &fvec) == -1 ) {
Int3(); // get MWA or AL -- not sure what to do here when we cannot acquire a path
return 0;
}
Objects[objnum].pos = pos;
Objects[objnum].orient.vec.fvec = fvec;
} else {
// AL: ensure dist > 0 (otherwise get errors in vecmat)
// TODO: maybe set distance to 2x ship radius of ship appearing in front of?
if ( dist <= 0 )
{
// Goober5000 - default to 100
Error(LOCATION, "Distance of %d is invalid in mission_set_arrival_location. Defaulting to 100.\n", dist);
dist = 100;
//return 0;
}
// get a vector which is the ships arrival position based on the type of arrival
// this ship should have. Arriving near a ship we use a random normalized vector
// scaled by the distance given by the designer. Arriving in front of a ship means
// entering the battle in the view cone.
if ( location == ARRIVE_NEAR_SHIP ) {
// get a random vector -- use static randvec if in multiplayer
if ( Game_mode & GM_NORMAL )
vm_vec_rand_vec_quick(&rand_vec);
else
static_randvec( Objects[objnum].net_signature, &rand_vec );
} else if ( location == ARRIVE_IN_FRONT_OF_SHIP ) {
vec3d t1, t2, t3;
int r1, r2;
float x;
// cool function by MK to give a reasonable random vector "in front" of a ship
// rvec and uvec are the right and up vectors.
// If these are not available, this would be an expensive method.
//x = cos(angle)
x = (float)cos(ANG_TO_RAD(45));
if ( Game_mode & GM_NORMAL ) {
r1 = rand() < RAND_MAX_2 ? -1 : 1;
r2 = rand() < RAND_MAX_2 ? -1 : 1;
} else {
// in multiplayer, use the static rand functions so that all clients can get the
// same information.
r1 = static_rand(Objects[objnum].net_signature) < RAND_MAX_2 ? -1 : 1;
r2 = static_rand(Objects[objnum].net_signature+1) < RAND_MAX_2 ? -1 : 1;
}
vm_vec_copy_scale(&t1, &(Objects[anchor_objnum].orient.vec.fvec), x);
vm_vec_copy_scale(&t2, &(Objects[anchor_objnum].orient.vec.rvec), (1.0f - x) * r1);
vm_vec_copy_scale(&t3, &(Objects[anchor_objnum].orient.vec.uvec), (1.0f - x) * r2);
vm_vec_add(&rand_vec, &t1, &t2);
vm_vec_add2(&rand_vec, &t3);
vm_vec_normalize(&rand_vec);
}
// add in the radius of the two ships involved. This will make the ship arrive further than
// specified, but will appear more accurate since we are pushing the edge of the model to the
// specified distance. large objects appears to be a lot closer without the following line because
// the object centers were at the correct distance, but the model itself was much closer to the
// target ship.
dist += (int)Objects[objnum].radius + (int)Objects[anchor_objnum].radius;
vm_vec_scale_add(&Objects[objnum].pos, &anchor_pos, &rand_vec, (float)dist);
// I think that we will always want to orient the ship that is arriving to face towards
// the ship it is arriving near/in front of. The effect will be cool!
//
// calculate the new fvec of the ship arriving and use only that to get the matrix. isn't a big
// deal not getting bank.
vm_vec_sub(&new_fvec, &anchor_pos, &Objects[objnum].pos );
vm_vector_2_matrix( &orient, &new_fvec, NULL, NULL );
Objects[objnum].orient = orient;
}
// set the new_pos parameter since it might be used outside the function (i.e. when dealing with wings).
if ( new_pos )
memcpy(new_pos, &Objects[objnum].pos, sizeof(vec3d) );
if ( new_orient )
memcpy( new_orient, &Objects[objnum].orient, sizeof(matrix) );
return 1;
}
// mark a reinforcement as available
void mission_parse_mark_reinforcement_available(char *name)
{
int i;
reinforcements *rp;
for (i = 0; i < Num_reinforcements; i++) {
rp = &Reinforcements[i];
if ( !stricmp(rp->name, name) ) {
if ( !(rp->flags & RF_IS_AVAILABLE) ) {
rp->flags |= RF_IS_AVAILABLE;
// tell all of the clients.
if ( MULTIPLAYER_MASTER ) {
send_reinforcement_avail( i );
}
}
return;
}
}
Assert ( i < Num_reinforcements );
}
// mission_did_ship_arrive takes a parse object and checked the arrival cue and delay and
// creates the object if necessary. Returns -1 if not created. objnum of created ship otherwise
int mission_did_ship_arrive(p_object *objp)
{
int should_arrive;
// find out in the arrival cue became true
should_arrive = eval_sexp(objp->arrival_cue);
// we must first check to see if this ship is a reinforcement or not. If so, then don't
// process
if ( objp->flags & P_SF_REINFORCEMENT ) {
// if this ship did arrive, mark the reinforcement as available, and tell clients if in multiplayer
// mode
if ( should_arrive ) {
mission_parse_mark_reinforcement_available(objp->name);
}
return -1;
}
if ( should_arrive ) { // has the arrival criteria been met?
int object_num;
Assert ( !(objp->flags & P_SF_CANNOT_ARRIVE) ); // get allender
// check to see if the delay field <= 0. if so, then create a timestamp and then maybe
// create the object
if ( objp->arrival_delay <= 0 ) {
objp->arrival_delay = timestamp( -objp->arrival_delay * 1000 );
Assert( objp->arrival_delay >= 0 );
}
// if the timestamp hasn't elapsed, move onto the next ship.
if ( !timestamp_elapsed(objp->arrival_delay) )
return -1;
// check to see if this ship is to arrive via a docking bay. If so, and the ship to arrive from
// doesn't exist, don't create.
if ( objp->arrival_location == ARRIVE_FROM_DOCK_BAY ) {
int shipnum;
char *name;
Assert( objp->arrival_anchor >= 0 );
name = Parse_names[objp->arrival_anchor];
// see if ship is yet to arrive. If so, then return -1 so we can evaluate again later.
if ( mission_parse_get_arrival_ship( name ) )
return -1;
// see if ship is in mission. If not, then we can assume it was destroyed or departed since
// it is not on the arrival list (as shown by above if statement).
shipnum = ship_name_lookup( name );
if ( shipnum == -1 ) {
mission_parse_mark_non_arrival(objp); // Goober5000
return -1;
}
// Goober5000: aha - also don't create if fighterbay is destroyed
if (ship_fighterbays_all_destroyed(&Ships[shipnum]))
return -1;
}
// create the ship
object_num = parse_create_object(objp);
// since this ship is not in a wing, create a SHIP_ARRIVE entry
//mission_log_add_entry( LOG_SHIP_ARRIVE, objp->name, NULL );
Assert(object_num >= 0 && object_num < MAX_OBJECTS);
// Play the music track for an arrival
if ( !(Ships[Objects[object_num].instance].flags & SF_NO_ARRIVAL_MUSIC) )
if ( timestamp_elapsed(Allow_arrival_music_timestamp) ) {
Allow_arrival_music_timestamp = timestamp(ARRIVAL_MUSIC_MIN_SEPARATION);
event_music_arrival(Ships[Objects[object_num].instance].team);
}
return object_num;
}
/* Goober5000 - this is redundant, especially with the streamlined marking
else
{
// check to see if the arrival cue of this ship is known false -- if so, then remove
// the parse object from the ship
if ( Sexp_nodes[objp->arrival_cue].value == SEXP_KNOWN_FALSE )
objp->flags |= P_SF_CANNOT_ARRIVE;
}
*/
return -1;
}
// Goober5000
void mission_maybe_make_ship_arrive(p_object *p_objp)
{
// try to create ship
int objnum = mission_did_ship_arrive(p_objp);
if (objnum < 0)
return;
// remove from arrival list
if (p_objp == Arriving_support_ship)
mission_parse_support_arrived(objnum);
else
list_remove(&Ship_arrival_list, p_objp);
}
// Goober5000
void mission_parse_mark_non_arrival(p_object *p_objp)
{
// mark the flag
p_objp->flags |= P_SF_CANNOT_ARRIVE;
}
// Goober5000
void mission_parse_mark_non_arrival(wing *wingp)
{
int wingnum = WING_INDEX(wingp);
// look through all ships yet to arrive...
for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
{
// ...and mark the ones in this wing
if (p_objp->wingnum == wingnum)
p_objp->flags |= P_SF_CANNOT_ARRIVE;
}
}
// Goober5000 - now only called upon mission start
// function to set a flag on all parse objects on ship arrival list which cannot
// arrive in the mission
void mission_parse_mark_non_arrivals()
{
for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
{
if (p_objp->wingnum != -1)
{
if (Sexp_nodes[Wings[p_objp->wingnum].arrival_cue].value == SEXP_KNOWN_FALSE)
p_objp->flags |= P_SF_CANNOT_ARRIVE;
}
else
{
if (Sexp_nodes[p_objp->arrival_cue].value == SEXP_KNOWN_FALSE)
p_objp->flags |= P_SF_CANNOT_ARRIVE;
}
}
}
// function to deal with support ship arrival. objnum is the object number of the arriving support
// ship. This function can get called from either single or multiplayer. Needed to that clients
// can know when to abort rearm.
void mission_parse_support_arrived( int objnum )
{
int i;
// when the support ship arrives, the shipname it is supposed to repair is in the 'misc'
// field of the parse object. If the ship still exists, call ai function which actually
// issues the goal for the repair
for ( i = 0; i < Num_arriving_repair_targets; i++ ) {
int shipnum;
shipnum = ship_name_lookup( Arriving_repair_targets[i] );
if ( shipnum != -1 ) {
object *requester_objp, *support_objp;
support_objp = &Objects[objnum];
requester_objp = &Objects[Ships[shipnum].objnum];
ai_add_rearm_goal( requester_objp, support_objp );
}
}
/* Goober5000 - this is taken care of in mission_bring_in_support_ship
// MK: A bit of a hack. If on player's team and player isn't allowed shields, don't give this ship shields.
if ((Player_obj->flags & OF_NO_SHIELDS) && (Player_ship->team == Ships[Objects[objnum].instance].team))
Objects[objnum].flags |= OF_NO_SHIELDS;
*/
Ships[Objects[objnum].instance].flags |= SF_WARPED_SUPPORT;
Arriving_support_ship = NULL;
Num_arriving_repair_targets = 0;
}
// Goober5000
int parse_object_on_arrival_list(p_object *pobjp)
{
return (pobjp->next != NULL) && (pobjp->prev != NULL);
}
// mission_eval_arrivals will check the lists of arriving ships and wings,
// creating new ships/wings if the arrival criteria have been met
void mission_eval_arrivals()
{
int i;
int rship = -1;
wing *wingp;
// before checking arrivals, check to see if we should play a message concerning arrivals
// of other wings. We use the timestamps to delay the arrival message slightly for
// better effect
if (timestamp_valid(Arrival_message_delay_timestamp) && timestamp_elapsed(Arrival_message_delay_timestamp) && !((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM)))
{
int rship, use_terran_cmd;
// use terran command 25% of time
use_terran_cmd = ((frand() - 0.75) > 0.0f)?1:0;
rship = ship_get_random_player_wing_ship( SHIP_GET_UNSILENCED );
if ((rship < 0) || use_terran_cmd)
message_send_builtin_to_player(MESSAGE_ARRIVE_ENEMY, NULL, MESSAGE_PRIORITY_LOW, MESSAGE_TIME_SOON, 0, 0, -1, -1);
else if (rship >= 0)
message_send_builtin_to_player(MESSAGE_ARRIVE_ENEMY, &Ships[rship], MESSAGE_PRIORITY_LOW, MESSAGE_TIME_SOON, 0, 0, -1, -1);
Arrival_message_delay_timestamp = timestamp(-1); // make the stamp invalid
}
// if ( !timestamp_elapsed(Mission_arrival_timestamp) )
// return;
// check the arrival list
// Goober5000 - we can't run through the list the usual way because we might
// remove a bunch of objects and completely screw up the list linkage
for (i = 0; i < (int)Parse_objects.size(); i++)
{
p_object *pobjp = &Parse_objects[i];
// make sure we're on the arrival list
if (!parse_object_on_arrival_list(pobjp))
continue;
// if this object has a wing, don't create it -- let code for wings determine if it should be created
if (pobjp->wingnum >= 0)
continue;
// make it arrive
mission_maybe_make_ship_arrive(pobjp);
}
// check the support ship arrival list
if (Arriving_support_ship)
{
// make it arrive (support ships are not put on the arrival list)
mission_maybe_make_ship_arrive(Arriving_support_ship);
}
// Goober5000 - it's not very efficient to do it this way; I changed it to use
// mission_parse_mark_non_arrival(object or wing) when something is invalidated
//mission_parse_mark_non_arrivals(); // mark parse objects which can no longer arrive
// we must also check to see if there are waves of a wing that must
// reappear if all the ships of the current wing have been destroyed or
// have departed. If this is the case, then create the next wave.
for (i = 0; i < Num_wings; i++)
{
wingp = &Wings[i];
// should we process this wing anymore
if (wingp->flags & WF_WING_GONE)
continue;
// if we have a reinforcement wing, then don't try to create new ships automatically.
if (wingp->flags & WF_REINFORCEMENT)
{
// check to see in the wings arrival cue is true, and if so, then mark the reinforcement
// as available
if (eval_sexp(wingp->arrival_cue))
mission_parse_mark_reinforcement_available(wingp->name);
// reinforcement wings skip the rest of the loop
continue;
}
// don't do evaluations for departing wings
if (wingp->flags & WF_WING_DEPARTING)
continue;
// must check to see if we are at the last wave. Code above to determine when a wing is gone only
// gets run when a ship is destroyed (not every N seconds like it used to). Do a quick check here.
if (wingp->current_wave == wingp->num_waves)
continue;
// If the current wave of this wing is 0, then we haven't created the ships in the wing yet.
// If the threshold of the wing has been reached, then we need to create more ships.
if ((wingp->current_wave == 0) || (wingp->current_count <= wingp->threshold))
{
// Call parse_wing_create_ships to try and create it. That function will eval the arrival
// cue of the wing and create the ships if necessary.
int created = parse_wing_create_ships(wingp, wingp->wave_count);
// if we didn't create any ships, nothing more to do for this wing
if (created <= 0)
continue;
// If this wing was a reinforcement wing, then we need to reset the reinforcement flag for the wing
// so the user can call in another set if need be.
if (wingp->flags & WF_RESET_REINFORCEMENT)
{
wingp->flags &= ~WF_RESET_REINFORCEMENT;
wingp->flags |= WF_REINFORCEMENT;
}
// probably send a message to the player when this wing arrives.
// if no message, nothing more to do for this wing
if (wingp->flags & WF_NO_ARRIVAL_MESSAGE)
continue;
// multiplayer team vs. team
if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM))
{
// send a hostile wing arrived message
rship = wingp->ship_index[wingp->special_ship];
int multi_team_filter = Ships[rship].team;
// there are two timestamps at work here. One to control how often the player receives
// messages about incoming hostile waves, and the other to control how long after
// the wing arrives does the player actually get the message.
if (timestamp_elapsed(Allow_arrival_message_timestamp_m[multi_team_filter]))
{
if (!timestamp_valid(Arrival_message_delay_timestamp_m[multi_team_filter]))
{
Arrival_message_delay_timestamp_m[multi_team_filter] = timestamp_rand(ARRIVAL_MESSAGE_DELAY_MIN, ARRIVAL_MESSAGE_DELAY_MAX);
}
Allow_arrival_message_timestamp_m[multi_team_filter] = timestamp(ARRIVAL_MESSAGE_MIN_SEPARATION);
// send to the proper team
message_send_builtin_to_player(MESSAGE_ARRIVE_ENEMY, NULL, MESSAGE_PRIORITY_LOW, MESSAGE_TIME_SOON, 0, 0, -1, multi_team_filter);
}
}
// does the player attack this ship?
else if (iff_x_attacks_y(Player_ship->team, Ships[wingp->ship_index[0]].team))
{
// there are two timestamps at work here. One to control how often the player receives
// messages about incoming hostile waves, and the other to control how long after
// the wing arrives does the player actually get the message.
if (timestamp_elapsed(Allow_arrival_message_timestamp))
{
if (!timestamp_valid(Arrival_message_delay_timestamp))
{
Arrival_message_delay_timestamp = timestamp_rand(ARRIVAL_MESSAGE_DELAY_MIN, ARRIVAL_MESSAGE_DELAY_MAX);
}
Allow_arrival_message_timestamp = timestamp(ARRIVAL_MESSAGE_MIN_SEPARATION);
}
}
// everything else
else
{
rship = ship_get_random_ship_in_wing(i, SHIP_GET_UNSILENCED);
if (rship >= 0)
{
int j;
char message_name[NAME_LENGTH + 10];
sprintf(message_name, "%s Arrived", wingp->name);
// see if this wing has an arrival message associated with it
for (j = 0; j < MAX_BUILTIN_MESSAGE_TYPES; j++)
{
if (!stricmp(message_name, Builtin_message_types[j]))
{
message_send_builtin_to_player(j, &Ships[rship], MESSAGE_PRIORITY_LOW, MESSAGE_TIME_SOON, 0, 0, -1, -1);
break;
}
}
}
}
}
}
Mission_arrival_timestamp = timestamp(ARRIVAL_TIMESTAMP);
}
// Goober5000
// this only checks the warp drive; we might be able to depart some other way (e.g. by entering a docking bay)
int ship_can_use_warp_drive(ship *shipp)
{
// must *have* a subspace drive
if (shipp->flags2 & SF2_NO_SUBSPACE_DRIVE)
return 0;
// navigation must work
if (!ship_navigation_ok_to_warp(shipp))
return 0;
return 1;
}
// called to make object objp depart.
int mission_do_departure(object *objp)
{
Assert (objp->type == OBJ_SHIP);
int location, anchor, path_mask;
ship *shipp = &Ships[objp->instance];
// Goober5000 - if this is a ship which has no subspace drive, departs to hyperspace, and belongs to a wing,
// then use the wing departure information
if ((shipp->flags2 & SF2_NO_SUBSPACE_DRIVE) && (shipp->departure_location == DEPART_AT_LOCATION) && (shipp->wingnum >= 0))
{
wing *wingp = &Wings[shipp->wingnum];
location = wingp->departure_location;
anchor = wingp->departure_anchor;
path_mask = wingp->departure_path_mask;
}
else
{
location = shipp->departure_location;
anchor = shipp->departure_anchor;
path_mask = shipp->departure_path_mask;
}
// if departing to a docking bay, try to find the anchor ship to depart to. If not found, then
// just make it warp out like anything else.
if (location == DEPART_AT_DOCK_BAY)
{
int anchor_shipnum;
char *name;
Assert(anchor >= 0);
name = Parse_names[anchor];
// see if ship is yet to arrive. If so, then warp.
if (mission_parse_get_arrival_ship(name))
{
goto try_to_warp;
}
// see if ship is in mission. If not, then we can assume it was destroyed or departed since
// it is not on the arrival list (as shown by above if statement).
anchor_shipnum = ship_name_lookup(name);
if (anchor_shipnum < 0)
{
goto try_to_warp;
}
// if ( ( aip->goal_signature != Objects[aip->goal_objnum].signature) )
// {
// }
// make sure ship not dying or departing
if (Ships[anchor_shipnum].flags & (SF_DYING | SF_DEPARTING))
{
return 0;
}
// make sure fighterbays aren't destroyed
if (ship_fighterbays_all_destroyed(&Ships[anchor_shipnum]))
{
goto try_to_warp;
}
// find a path
if (ai_acquire_depart_path(objp, Ships[anchor_shipnum].objnum, path_mask) >= 0)
{
MONITOR_INC(NumShipDepartures,1);
return 1;
}
}
try_to_warp:
ai_info *aip = &Ai_info[shipp->ai_index];
// Goober5000 - make sure we can actually warp
if (ship_can_use_warp_drive(shipp))
{
ai_set_mode_warp_out(objp, aip);
MONITOR_INC(NumShipDepartures,1);
return 1;
}
else
{
shipp->flags &= ~SF_DEPARTING;
// find something else to do
aip->mode = AIM_NONE;
return 0;
}
}
// put here because mission_eval_arrivals is here. Might move these to a better location
// later -- MWA
void mission_eval_departures()
{
int i, j;
object *objp;
wing *wingp;
// if ( !timestamp_elapsed(Mission_departure_timestamp) )
// return;
// scan through the active ships an evaluate their departure cues. For those
// ships whose time has come, set their departing flag.
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if (objp->type == OBJ_SHIP) {
ship *shipp;
Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
shipp = &Ships[objp->instance];
// don't process a ship that is already departing or dying or disabled
// AL 12-30-97: Added SF_CANNOT_WARP to check
// Goober5000 - fixed so that it WILL eval when SF_CANNOT_WARP if departing to dockbay
if ( (shipp->flags & (SF_DEPARTING | SF_DYING)) || ((shipp->flags & SF_CANNOT_WARP) && (shipp->departure_location != DEPART_AT_DOCK_BAY)) || ship_subsys_disrupted(shipp, SUBSYSTEM_ENGINE) ) {
continue;
}
// don't process ships that are part of a wing -- handled in seperate case
if ( shipp->wingnum != -1 )
continue;
// && (!timestamp_valid(shipp->departure_delay) || timestamp_elapsed(shipp->departure_delay)) )
// when the departure cue becomes true, set off the departure delay timer. We store the
// timer as -seconds in FreeSpace which indicates that the timer has not been set. If the timer
// is not set, then turn it into a valid timer and keep evaluating the timer until it is elapsed
if ( eval_sexp(shipp->departure_cue) ) {
if ( shipp->departure_delay <= 0 )
shipp->departure_delay = timestamp(-shipp->departure_delay * 1000 );
if ( timestamp_elapsed(shipp->departure_delay) )
mission_do_departure( objp );
}
}
}
// now scan through the list of wings and check their departure cues. For wings with
// that cue being true, we must update internal variables to indicate that the wing is
// departed and that no further waves of this wing will appear
for ( i = 0; i < Num_wings; i++ ) {
wingp = &Wings[i];
// should we process this wing anymore
if ( wingp->flags & WF_WING_DEPARTING )
continue;
// evaluate the sexpression. If true, mark all the ships in this wing as departing and increment
// the num departed in the wing structure. Then add number of remaining waves * ships/wave to
// departed count to get total count of ships in the wing which departed. (We are counting ships
// that have not yet arrived as departed if they never arrive -- this may be bad, but for some reason
// seems like the right thing to do).
//&& (!timestamp_valid(wingp->departure_delay) || timestamp_elapsed(wingp->departure_delay)) ) {
if ( eval_sexp(wingp->departure_cue) ) {
// if we haven't set up the departure timer yet (would be <= 0) setup the timer to pop N seconds
// later
if ( wingp->departure_delay <= 0 )
wingp->departure_delay = timestamp( -wingp->departure_delay * 1000 );
if ( !timestamp_elapsed(wingp->departure_delay) )
continue;
wingp->flags |= WF_WING_DEPARTING;
for ( j = 0; j < wingp->current_count; j++ ) {
ship *shipp;
shipp = &Ships[wingp->ship_index[j]];
if ( (shipp->flags & SF_DEPARTING) || (shipp->flags & SF_DYING) )
continue;
// shipp->flags |= SF_DEPARTING;
// shipp->final_depart_time = timestamp(3*1000);
Assert ( shipp->objnum != -1 );
objp = &Objects[shipp->objnum];
// copy the wing's departure information to the ship
shipp->departure_location = Wings[shipp->wingnum].departure_location;
shipp->departure_anchor = Wings[shipp->wingnum].departure_anchor;
shipp->departure_path_mask = Wings[shipp->wingnum].departure_path_mask;
mission_do_departure( objp );
// don't add to wingp->total_departed here -- this is taken care of in ship code.
}
// MWA 2/25/98 -- don't do the follwoing wing member updates. It makes the accurate counts
// sort of messed up and causes problems for the event log. The code in ship_wing_cleanup()
// now keys off of the WF_WING_DEPARTING flag instead of the counts below.
/*
// now be sure that we update wing structure members if there are any remaining waves left
if ( wingp->current_wave < wingp->num_waves ) {
int num_remaining;
num_remaining = ( (wingp->num_waves - wingp->current_wave) * wingp->wave_count);
wingp->total_departed += num_remaining;
wingp->total_arrived_count += num_remaining;
wingp->current_wave = wingp->num_waves;
}
*/
}
}
Mission_departure_timestamp = timestamp(DEPARTURE_TIMESTAMP);
}
// function called from high level game loop to do mission evaluation stuff
void mission_parse_eval_stuff()
{
mission_eval_arrivals();
mission_eval_departures();
}
int allocate_subsys_status()
{
int i;
// set primary weapon ammunition here, but does it actually matter? - Goober5000
Assert(Subsys_index >= 0 /*&& Subsys_index < MAX_SUBSYS_STATUS*/);
// we allocate in blocks of MIN_SUBSYS_STATUS_SIZE so if we need more then make more
if ( (Subsys_status == NULL) || (Subsys_index >= (Subsys_status_size - 1)) ) {
Assert( MIN_SUBSYS_STATUS_SIZE > 0 );
Subsys_status_size += MIN_SUBSYS_STATUS_SIZE;
Subsys_status = (subsys_status*)vm_realloc(Subsys_status, sizeof(subsys_status) * Subsys_status_size );
}
Verify( Subsys_status != NULL );
memset( &Subsys_status[Subsys_index], 0, sizeof(subsys_status) );
Subsys_status[Subsys_index].percent = 0.0f;
Subsys_status[Subsys_index].primary_banks[0] = SUBSYS_STATUS_NO_CHANGE;
Subsys_status[Subsys_index].primary_ammo[0] = 100; // *
for (i=1; i<MAX_SHIP_PRIMARY_BANKS; i++)
{
Subsys_status[Subsys_index].primary_banks[i] = -1; // none
Subsys_status[Subsys_index].primary_ammo[i] = 100; // *
}
Subsys_status[Subsys_index].secondary_banks[0] = SUBSYS_STATUS_NO_CHANGE;
Subsys_status[Subsys_index].secondary_ammo[0] = 100;
for (i=1; i<MAX_SHIP_SECONDARY_BANKS; i++)
{
Subsys_status[Subsys_index].secondary_banks[i] = -1;
Subsys_status[Subsys_index].secondary_ammo[i] = 100;
}
Subsys_status[Subsys_index].ai_class = SUBSYS_STATUS_NO_CHANGE;
return Subsys_index++;
}
// Goober5000
int insert_subsys_status(p_object *pobjp)
{
int i;
// this is not good; we have to allocate another slot, but then bump all the
// slots upward so that this particular parse object's subsystems are contiguous
allocate_subsys_status();
// shift elements upward
for (i = Subsys_index - 1; i > (pobjp->subsys_index + pobjp->subsys_count); i--)
{
memcpy(&Subsys_status[i], &Subsys_status[i-1], sizeof(subsys_status));
}
// return index for new element
return pobjp->subsys_index + pobjp->subsys_count;
}
// Goober5000
subsys_status *parse_get_subsys_status(p_object *pobjp, char *subsys_name)
{
int i;
subsys_status *sssp;
for (i = 0; i < pobjp->subsys_count; i++)
{
sssp = &Subsys_status[pobjp->subsys_index + i];
if (!subsystem_stricmp(sssp->name, subsys_name))
return sssp;
}
return NULL;
}
// find (or add) the name in the list and return an index to it.
int get_parse_name_index(char *name)
{
int i;
for (i=0; i<Num_parse_names; i++)
if (!stricmp(name, Parse_names[i]))
return i;
Assert(i < MAX_SHIPS + MAX_WINGS);
Assert(strlen(name) < NAME_LENGTH);
strcpy(Parse_names[i], name);
return Num_parse_names++;
}
// Goober5000
int add_path_restriction()
{
int i, j;
// parse it
path_restriction_t temp;
temp.cached_mask = (1 << MAX_SHIP_BAY_PATHS); // uninitialized value (too high)
temp.num_paths = stuff_string_list(temp.path_names, MAX_SHIP_BAY_PATHS);
// no restriction?
if (temp.num_paths == 0)
return -1;
// first, see if it's duplicated anywhere
for (i = 0; i < Num_path_restrictions; i++)
{
// must have same number of allowed paths
if (temp.num_paths != Path_restrictions[i].num_paths)
continue;
// see if path names match
for (j = 0; j < temp.num_paths; j++)
{
// no match, so skip this
if (stricmp(temp.path_names[j], Path_restrictions[i].path_names[j]))
goto continue_outer_loop;
}
// match!
return i;
continue_outer_loop:
;
}
// no match, so add a new restriction
// check limit
if (Num_path_restrictions >= MAX_PATH_RESTRICTIONS)
{
Warning(LOCATION, "Maximum number of path restrictions reached");
return -1;
}
// add this restriction at the new index
int index = Num_path_restrictions++;
Path_restrictions[index] = temp;
return index;
}
// Goober5000 - look for <any friendly>, <any hostile player>, etc.
int get_special_anchor(char *name)
{
char tmp[NAME_LENGTH + 15];
char *iff_name;
int iff_index;
if (strnicmp(name, "<any ", 5))
return -1;
strcpy(tmp, name+5);
iff_name = strtok(tmp, " >");
// hack substitute "hostile" for "enemy"
if (!stricmp(iff_name, "enemy"))
iff_name = "hostile";
iff_index = iff_lookup(iff_name);
if (iff_index < 0)
return -1;
// restrict to players?
if (stristr(name+5, "player") != NULL)
return (iff_index | SPECIAL_ARRIVAL_ANCHOR_FLAG | SPECIAL_ARRIVAL_ANCHOR_PLAYER_FLAG);
else
return (iff_index | SPECIAL_ARRIVAL_ANCHOR_FLAG);
}
int get_anchor(char *name)
{
int special_anchor = get_special_anchor(name);
if (special_anchor >= 0)
return special_anchor;
return get_parse_name_index(name);
}
// function to fixup the goals/ai references for player objects in the mission
void mission_parse_fixup_players()
{
object *objp;
// merge created list to have all objects on used list
obj_merge_created_list();
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if ( (objp->type == OBJ_SHIP) && (objp->flags & OF_PLAYER_SHIP) ) {
game_busy( NOX("** fixing up player/ai stuff **") ); // animate the loading screen, doesn't nothing if the screen is not active
ai_clear_ship_goals( &Ai_info[Ships[objp->instance].ai_index] );
init_ai_object( OBJ_INDEX(objp) );
}
}
}
// code to warp in a new support ship. It works by finding the average position of all ships
// in the mission, creating a vector from that position to the player, and scaling out behind the
// player some distance. Should be sufficient.
#define WARP_IN_MIN_DISTANCE 1000.0f
#define WARP_IN_TIME_MIN 3000 // warps in min 3 seconds later
#define WARP_IN_TIME_MAX 6000 // warps in max 6 seconds later
// function which adds requester_objp onto the queue of ships for the arriving support ship to service
void mission_add_to_arriving_support( object *requester_objp )
{
int i;
ship *shipp;
Assert ( Arriving_support_ship );
if ( Num_arriving_repair_targets == MAX_AI_GOALS ) {
// Int3(); // get allender -- ship isn't going to get repair, but I hope they never queue up this far!!!
mprintf(("Reached MAX_AI_GOALS trying to add repair request!\n"));
return;
}
shipp = &Ships[requester_objp->instance];
// check for duplicates before adding
for (i = 0; i < Num_arriving_repair_targets; i++ ) {
if ( !stricmp(Arriving_repair_targets[i], shipp->ship_name) ){
break;
}
}
if ( i != Num_arriving_repair_targets ){ // found the ship before reaching the end -- ignore it!
return;
}
strcpy( Arriving_repair_targets[Num_arriving_repair_targets], Ships[requester_objp->instance].ship_name );
Num_arriving_repair_targets++;
if ( MULTIPLAYER_MASTER ){
multi_maybe_send_repair_info( requester_objp, NULL, REPAIR_INFO_WARP_ADD );
}
}
extern int pp_collide_any(vec3d *curpos, vec3d *goalpos, float radius, object *ignore_objp1, object *ignore_objp2, int big_only_flag);
// Set the warp in position for a support ship relative to an object.
// Caller tries several positions, passing vector in x, y, z.
int get_warp_in_pos(vec3d *pos, object *objp, float x, float y, float z)
{
float rand_val;
if ( Game_mode & GM_NORMAL )
rand_val = frand();
else
rand_val = static_randf(objp->net_signature);
rand_val = 1.0f + (rand_val - 0.5f)*0.2f;
*pos = objp->pos;
vm_vec_scale_add2( pos, &objp->orient.vec.rvec, x*rand_val*800.0f);
vm_vec_scale_add2( pos, &objp->orient.vec.uvec, y*rand_val*800.0f);
vm_vec_scale_add2( pos, &objp->orient.vec.fvec, z*rand_val*800.0f);
return pp_collide_any(&objp->pos, pos, objp->radius, objp, NULL, 1);
}
// modified by Goober5000 to allow more flexibility in support ships
void mission_bring_in_support_ship( object *requester_objp )
{
vec3d center, warp_in_pos;
//float mag;
p_object *pobj;
ship *requester_shipp;
int i, requester_species;
Assert ( requester_objp->type == OBJ_SHIP );
requester_shipp = &Ships[requester_objp->instance]; // MK, 10/23/97, used to be ->type, bogus, no?
// if the support ship is already arriving, add the requester to the list
if ( Arriving_support_ship ) {
mission_add_to_arriving_support( requester_objp );
return;
}
// create a parse object, and put it onto the ship arrival list. This whole thing kind of stinks.
// I want to put it into a parse object since it needs to arrive just a little later than
// this function is called. I have to make some assumptions in the code about values for the parse
// object since I'm no longer working with a mission file. These exceptions will be noted with
// comments
Arriving_support_ship = &Support_ship_pobj;
pobj = Arriving_support_ship;
// get average position of all ships
obj_get_average_ship_pos( &center );
vm_vec_sub( &warp_in_pos, &center, &(requester_objp->pos) );
// be sure to account for case as player being only ship left in mission
/*
if ( !(IS_VEC_NULL( warp_in_pos)) ) {
mag = vm_vec_mag( &warp_in_pos );
if ( mag < WARP_IN_MIN_DISTANCE )
vm_vec_scale( &warp_in_pos, WARP_IN_MIN_DISTANCE/mag);
else
vm_vec_scale( &warp
} else {
// take -player_pos.vec.fvec scaled by 1000.0f;
warp_in_pos = Player_obj->orient.vec.fvec;
vm_vec_scale( &warp_in_pos, -1000.0f );
}
*/
// Choose position to warp in ship.
// Temporary, but changed by MK because it used to be exactly behind the player.
// This could cause an Assert if the player immediately targeted it (before moving).
// Tend to put in front of the player to aid him in flying towards the ship.
if (!get_warp_in_pos(&warp_in_pos, requester_objp, 1.0f, 0.1f, 1.0f))
if (!get_warp_in_pos(&warp_in_pos, requester_objp, 1.0f, 0.2f, -1.0f))
if (!get_warp_in_pos(&warp_in_pos, requester_objp, -1.0f, -0.2f, -1.0f))
if (!get_warp_in_pos(&warp_in_pos, requester_objp, -1.0f, -0.1f, 1.0f))
get_warp_in_pos(&warp_in_pos, requester_objp, 0.1f, 1.0f, 0.2f);
// position for ship if it warps in
pobj->pos = warp_in_pos;
// tally the ship
The_mission.support_ships.tally++;
// create a name for the ship. use "Support #". look for collisions until one isn't found anymore
i = 1;
do {
sprintf(pobj->name, NOX("Support %d"), i);
if ( (ship_name_lookup(pobj->name) == -1) && (ship_find_exited_ship_by_name(pobj->name) == -1) )
break;
i++;
} while(1);
vm_set_identity( &(pobj->orient) );
// *sigh*. Gotta get the ship class. For now, this will amount to finding a ship in the ship_info
// array with the same team as the requester of type SIF_SUPPORT. Might need to be changed, but who knows
// Goober5000 - who knew of the SCP release? ;) only determine ship class if not set by SEXP
pobj->ship_class = The_mission.support_ships.ship_class;
if (pobj->ship_class < 0)
{
requester_species = Ship_info[requester_shipp->ship_info_index].species;
// 5/6/98 -- MWA Don't need to do anything for multiplayer. I think that we always want to use
// the species of the caller ship.
// get index of correct species support ship
for (i=0; i < Num_ship_classes; i++) {
if ( (Ship_info[i].species == requester_species) && (Ship_info[i].flags & SIF_SUPPORT) )
break;
}
if ( i < Num_ship_classes )
pobj->ship_class = i;
else
Int3(); // BOGUS!!!! gotta figure something out here
}
// set support ship hitpoints
pobj->ship_max_hull_strength = Ship_info[i].max_hull_strength;
pobj->ship_max_shield_strength = Ship_info[i].max_shield_strength;
pobj->team = requester_shipp->team;
pobj->behavior = AIM_NONE; // ASSUMPTION: the mission file has the string "None" which maps to AIM_NONE
// set the ai_goals to -1. We will put the requester object shipname in repair target array and then take
// care of setting up the goal when creating the ship!!!!
pobj->ai_goals = -1;
Num_arriving_repair_targets = 0;
mission_add_to_arriving_support( requester_objp );
// need to set ship's cargo to nothing. scan the cargo_names array looking for the string nothing.
// add it if not found
for (i = 0; i < Num_cargo; i++ )
if ( !stricmp(Cargo_names[i], NOX("nothing")) )
break;
if ( i == Num_cargo ) {
strcpy(Cargo_names[i], NOX("Nothing"));
Num_cargo++;
}
pobj->cargo1 = char(i);
pobj->status_count = 0;
// Goober5000 - take some stuff from mission flags
pobj->arrival_location = The_mission.support_ships.arrival_location;
pobj->arrival_anchor = The_mission.support_ships.arrival_anchor;
pobj->departure_location = The_mission.support_ships.departure_location;
pobj->departure_anchor = The_mission.support_ships.departure_anchor;
pobj->arrival_path_mask = 0;
pobj->departure_path_mask = 0;
// pobj->arrival_location = ARRIVE_AT_LOCATION;
pobj->arrival_distance = 0;
// pobj->arrival_anchor = -1;
pobj->arrival_cue = Locked_sexp_true;
pobj->arrival_delay = timestamp_rand(WARP_IN_TIME_MIN, WARP_IN_TIME_MAX);
pobj->subsys_count = 0; // number of elements used in subsys_status array
pobj->initial_velocity = 100; // start at 100% velocity
pobj->initial_hull = 100; // start at 100% hull
pobj->initial_shields = 100; // and 100% shields
// pobj->departure_location = DEPART_AT_LOCATION;
// pobj->departure_anchor = -1;
pobj->departure_cue = Locked_sexp_false;
pobj->departure_delay = 0;
pobj->determination = 10; // ASSUMPTION: mission file always had this number written out
pobj->wingnum = -1;
pobj->flags = 0;
pobj->flags2 = 0;
if ( Player_obj->flags & OF_NO_SHIELDS )
pobj->flags |= P_OF_NO_SHIELDS; // support ships have no shields when player has not shields
if ( Ships[Player_obj->instance].flags2 & SF2_NO_SUBSPACE_DRIVE )
pobj->flags2 |= P2_SF2_NO_SUBSPACE_DRIVE; // support ships have no subspace drive when player has not subspace drive
pobj->ai_class = Ship_info[pobj->ship_class].ai_class;
pobj->hotkey = -1;
pobj->score = 0;
pobj->dock_list = NULL;
pobj->created_object = NULL;
pobj->group = -1;
pobj->persona_index = -1;
pobj->net_signature = multi_assign_network_signature(MULTI_SIG_SHIP);
pobj->wing_status_wing_index = -1;
pobj->wing_status_wing_pos = -1;
pobj->respawn_count = 0;
pobj->alt_type_index = -1;
pobj->num_texture_replacements = 0;
}
// returns true if a support ship is currently in the process of warping in.
int mission_is_support_ship_arriving()
{
if ( Arriving_support_ship )
return 1;
else
return 0;
}
// returns true if the given ship is scheduled to be repaired by the arriving support ship
int mission_is_repair_scheduled( object *objp )
{
char *name;
int i;
if ( !Arriving_support_ship )
return 0;
Assert ( objp->type == OBJ_SHIP );
name = Ships[objp->instance].ship_name;
for (i = 0; i < Num_arriving_repair_targets; i++ ) {
if ( !strcmp( name, Arriving_repair_targets[i]) )
return 1;
}
return 0;
}
// function which removed the given ship from the list of ships that are to get repair
// by arriving support ship
int mission_remove_scheduled_repair( object *objp )
{
char *name;
int i, index;
if ( !Arriving_support_ship )
return 0;
// itereate through the target list looking for this ship name. If not found, we
// can simply return.
Assert ( objp->type == OBJ_SHIP );
name = Ships[objp->instance].ship_name;
for (index = 0; index < Num_arriving_repair_targets; index++ ) {
if ( !strcmp( name, Arriving_repair_targets[index]) )
break;
}
if ( index == Num_arriving_repair_targets )
return 0;
// ship is found -- compress the array
for ( i = index; i < Num_arriving_repair_targets - 1; i++ )
strcpy( Arriving_repair_targets[i], Arriving_repair_targets[i+1] );
Num_arriving_repair_targets--;
if ( MULTIPLAYER_MASTER )
multi_maybe_send_repair_info( objp, NULL, REPAIR_INFO_WARP_REMOVE );
return 1;
}
// alternate name stuff
int mission_parse_lookup_alt(char *name)
{
int idx;
// sanity
if(name == NULL){
return -1;
}
// lookup
for(idx=0; idx<Mission_alt_type_count; idx++){
if(!strcmp(Mission_alt_types[idx], name)){
return idx;
}
}
// could not find
return -1;
}
static int mission_parse_lookup_alt_index_warn = 1;
void mission_parse_lookup_alt_index(int index, char *out)
{
// sanity
if(out == NULL){
return;
}
if((index < 0) || (index > Mission_alt_type_count)){
if (mission_parse_lookup_alt_index_warn) {
Warning(LOCATION, "Ship with invalid alt_name. Get a programmer");
mission_parse_lookup_alt_index_warn = 0;
}
return;
}
// stuff it
strcpy(out, Mission_alt_types[index]);
}
int mission_parse_add_alt(char *name)
{
// sanity
if(name == NULL){
return -1;
}
// maybe add
if(Mission_alt_type_count < MAX_ALT_TYPE_NAMES){
// stuff the name
strncpy(Mission_alt_types[Mission_alt_type_count++], name, NAME_LENGTH);
// done
return Mission_alt_type_count - 1;
}
return -1;
}
void mission_parse_reset_alt()
{
Mission_alt_type_count = 0;
}
int is_training_mission()
{
return (The_mission.game_type & MISSION_TYPE_TRAINING);
}
// Goober5000
// go through all the displayed text in one section and fix "
// the section and text delimiters should all be different
void conv_fix_punctuation_section(char *str, char *section_start, char *section_end, char *text_start, char *text_end)
{
char *s1, *s2, *t1, *t2;
s1 = strstr(str, section_start);
s2 = strstr(s1, section_end);
t1 = s1;
while (1)
{
t1 = strstr(t1+1, text_start);
if (!t1 || t1 > s2) return;
t2 = strstr(t1, text_end);
if (!t2 || t2 > s2) return;
replace_all(t1, "\"", "$quote", MISSION_TEXT_SIZE - (str - Mission_text), (t2 - t1));
}
}
// Goober5000
void conv_fix_punctuation()
{
// command briefings
conv_fix_punctuation_section(Mission_text, "#Command Briefing", "#Briefing", "$Stage Text:", "$end_multi_text");
// briefings
conv_fix_punctuation_section(Mission_text, "#Briefing", "#Debriefing_info", "$multi_text", "$end_multi_text");
// debriefings
conv_fix_punctuation_section(Mission_text, "#Debriefing_info", "#Players", "$Multi text", "$end_multi_text");
// messages
conv_fix_punctuation_section(Mission_text, "#Messages", "#Reinforcements", "$Message:", "\n");
}
// Goober5000
void convertFSMtoFS2()
{
// fix punctuation
conv_fix_punctuation();
}
// Goober5000
void restore_default_weapons(char *ships_tbl)
{
int i, j, si_subsys;
char *ch, *subsys;
char ship_class[NAME_LENGTH];
ship_subsys *ss;
ship_info *sip;
// guesstimate that this actually is a ships.tbl
if (!strstr(ships_tbl, "#Ship Classes"))
{
MessageBox(NULL, "This is not a ships.tbl file. Aborting conversion...", "Error", MB_OK);
return;
}
// for every ship
for (i = 0; i < MAX_SHIPS; i++)
{
// ensure the ship slot is used in this mission
if (Ships[i].objnum >= 0)
{
// get ship_info
sip = &Ship_info[Ships[i].ship_info_index];
// find the ship class
ch = strstr(ships_tbl, ship_class);
if (!ch) continue;
// check pbanks (capital ships have these specified but empty)
Mp = strstr(ch, "$Default PBanks");
Mp = strchr(Mp, '(');
restore_one_primary_bank(Ships[i].weapons.primary_bank_weapons, sip->primary_bank_weapons);
// check sbanks (capital ships have these specified but empty)
Mp = strstr(ch, "$Default SBanks");
Mp = strchr(Mp, '(');
restore_one_secondary_bank(Ships[i].weapons.secondary_bank_weapons, sip->secondary_bank_weapons);
// see if we have any turrets
ch = strstr(ch, "$Subsystem");
for (ss = GET_FIRST(&Ships[i].subsys_list); ss != END_OF_LIST(&Ships[i].subsys_list); ss = GET_NEXT(ss))
{
// we do
if (ss->system_info->type == SUBSYSTEM_TURRET)
{
// find it in the ship_info subsys list
si_subsys = -1;
for (j = 0; j < sip->n_subsystems; j++)
{
if (!subsystem_stricmp(ss->system_info->subobj_name, sip->subsystems[j].subobj_name))
{
si_subsys = j;
break;
}
}
if (si_subsys < 0) continue;
// find it in the file - make sure it belongs to *this* ship
subsys = stristr(ch, ss->system_info->subobj_name);
if (!subsys) continue;
if (subsys > strstr(ch, "$Name")) continue;
// check pbanks - make sure they are *this* subsystem's banks
Mp = strstr(subsys, "$Default PBanks");
if (Mp < strstr(subsys + 1, "$Subsystem"))
{
Mp = strchr(Mp, '(');
restore_one_primary_bank(ss->weapons.primary_bank_weapons, sip->subsystems[si_subsys].primary_banks);
}
// check sbanks - make sure they are *this* subsystem's banks
Mp = strstr(subsys, "$Default SBanks");
if (Mp < strstr(subsys + 1, "$Subsystem"))
{
Mp = strchr(Mp, '(');
restore_one_secondary_bank(ss->weapons.secondary_bank_weapons, sip->subsystems[si_subsys].secondary_banks);
}
}
}
}
}
}
// Goober5000
void restore_one_primary_bank(int *ship_primary_weapons, int *default_primary_weapons)
{
int i, count, original_weapon;
char weapon_list[MAX_SHIP_PRIMARY_BANKS][NAME_LENGTH];
// stuff weapon list
count = stuff_string_list(weapon_list, MAX_SHIP_PRIMARY_BANKS);
// check for default weapons - if same as default, overwrite with the one from the table
for (i = 0; i < count; i++)
{
if (ship_primary_weapons[i] == default_primary_weapons[i])
{
if ((original_weapon = weapon_info_lookup(weapon_list[i])) >= 0)
ship_primary_weapons[i] = original_weapon;
}
}
}
// Goober5000
void restore_one_secondary_bank(int *ship_secondary_weapons, int *default_secondary_weapons)
{
int i, count, original_weapon;
char weapon_list[MAX_SHIP_SECONDARY_BANKS][NAME_LENGTH];
// stuff weapon list
count = stuff_string_list(weapon_list, MAX_SHIP_SECONDARY_BANKS);
// check for default weapons - if same as default, overwrite with the one from the table
for (i = 0; i < count; i++)
{
if (ship_secondary_weapons[i] == default_secondary_weapons[i])
{
if ((original_weapon = weapon_info_lookup(weapon_list[i])) >= 0)
ship_secondary_weapons[i] = original_weapon;
}
}
}