mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-12 12:11:36 -10:00
* Shadow overhaul round 2: PCSS, dynamic cascades, cockpit/instanced shadows Squashed snapshot of lafiel/shadow_overhaul_2's continuation work (d3922aa87..010f9d2d9), which has no separate upstream PR of its own. * Add Vulkan rendering backend Squashed snapshot of the Vulkan backend as vendored into this branch (ce2d1e85f..6ce539463, authored by Mara van der Laan with build/CI fixes by Taylor Richards). This work has its own upstream PR #7553 (notimaginative:vulkan-pr-new) and is not otherwise original to true-hdr. * Add true HDR output, RT shadows, and PCSS shadows for Vulkan Squashed snapshot of true-hdr's own integration work: true HDR framebuffer output, wiring the shadow overhaul into the Vulkan backend (raytraced + cascaded shadow maps), logging refactors, log filter UI, and CI/build fixes. * refactor: update Vulkan logging calls to use `nprintf` for scoped logging * fix: correct shadow cascade count in Vulkan logging calls * fix: fall back to PATH-resolved ccache when CCACHE_PATH is stale CI hardcodes /usr/local/bin/ccache, but macOS runners on Apple Silicon hosts install Homebrew (and ccache) under /opt/homebrew instead, causing all four Mac configs to fail with "No such file or directory". * Fix vulkan texture readback to correctly handle transparent pipelines * Put the pixel swizzles into a util file * OOB safety check * Fix fog handling * Fix MSAA in Vulkan * fix: enable SMAA for vulkan * adapt the post-processing AA shaders to hdr * fix clang-tidy warnings: concatenate nested namespaces, static methods pixel_swizzle.{h,cpp}: collapse `namespace graphics { namespace util {` into `namespace graphics::util {` (modernize-concat-nested-namespaces). VulkanState.h/VulkanDraw.h: getClearColor()/setClearColor() only touch gr_screen and other globals, no instance state, so make them static (readability-convert-member-functions-to-static) and drop the top-level const on getClearColor()'s return type (readability-const-return-type). * fix clang-tidy warning * Extend raytraced shadows to all directional lights RT shadows previously only shadowed the first directional light in the scene, even though multi-sun missions push more than one directional light into the engine. The TLAS raytraced shadows query against is already light-agnostic, so this extends both the deferred lighting pass and the forward MODEL shader to shadow every directional light (capped by a new Max Raytraced Shadow Lights option, configurable via the in-game Options menu or the Lab's Scene rendering options). Cascaded shadow maps are untouched: they're built for a single light only, so the deferred pass still limits itself to one shadowed directional light whenever CSM (not raytracing) is the active method. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_016DA6JHJ3twz2oHGQcZ1Udc * refactor: improve shader flag handling and render target finalization in Vulkan backend - Consolidate shader flag setup logic in VulkanPostProcessingLighting to simplify configuration. - Fix dead field usage in VulkanDraw by ensuring correct image view selection for environment maps. - Add mip chain regeneration in VulkanRenderer::endRenderTarget to ensure correct mip levels for cubemaps. * fix: ensure correct mip chain initialization in cubemap rendering - Add mip chain generation to VulkanDraw for cubemaps with multiple mip levels. - Fix potential issue with uninitialized mip levels above level 0 in deferred-f.sdr sampling paths. * fix: don't crash on startup when environment locale is unavailable std::locale("") pulls in the process's environment locale categories (LC_CTYPE, LC_MONETARY, LC_PAPER, etc.), and throws std::runtime_error if any of them names a locale not installed on the host (e.g. mismatched regional-format overrides). Since SCP_default_locale is constructed during static initialization, before main() and before any of our own exception handling exists, an uncaught throw here aborted the whole process before the game even started. Fall back to the classic "C" locale instead, which is sufficient since this locale is only ever used for ASCII case-folding (SCP_toupper/tolower/isdigit). Co-Authored-By: Claude Sonnet 5 <[email protected]> * `feat: add raytraced shadows for local lights and optimize TLAS management` * Make the Commandline authoritative for -res and -vulkan / -opengl * address BMagnu's comments on the original PR * update prebuilt version hash to 62c89ef6 * refactor: standardize graphics API handling and document Vulkan limitations in qtFRED - Updated `AboutDialogModel.cpp` to use `GraphicsAPI` enum for better clarity. - Ensured qtFRED falls back to OpenGL when Vulkan is requested. - Added detailed documentation in `qtfred/README.md` regarding Vulkan backend limitations and requirements. * chore: include cmdline library in main and mission management modules * docs: add known issue for blank render viewport under Wayland in README.md * fix CI issues * chore: add `GR_DEBUG_SCOPE` markers across Vulkan post-processing and rendering paths for improved debugging * feat: improve Vulkan HDR pipeline with separate encode paths for SDR and HDR10 - Added an HDR10-specific output encode pass in `VulkanRenderer` and `VulkanPostProcessor` that applies PQ/BT.2020 transfer. - Introduced an optimized SDR fallback path with direct blit support for devices that can skip shader draws. - Updated `m_sceneLdrCompressed` to provide [0,1]-clamped proxies for HDR edge detection. - Refactored `copyImageToImage` and added `blitImageToImage` to allow format conversions and optimized transfers. - Replaced `hdr_mode` with explicit HDR10 flags and data structures for clearer state handling. * refactor: encapsulate SMAA processing and refactor VulkanPostProcessor - Moved SMAA-specific logic into `VulkanSMAA` class for better separation of concerns. - Simplified `initSMAA`, `shutdownSMAA`, and `executeSMAA` logic by leveraging `VulkanLDR` and `PostProcessContext` abstractions. - Improved initialization and cleanup code with clearer resource lifecycle management. - Updated debug markers and messages to reference `VulkanSMAA`. - Enhanced flexibility of fullscreen triangle rendering API with additional parameters (`sampleCount`, `bindGlobalSet`). * Update Vulkan porting credits in AboutDialogModel * fix CI issues again * fix: correct Y-flip in viewport setup for Vulkan light volumes - Adjusted viewport height to negative value to account for G-buffer render pass's negative Y-axis convention. - Ensured proper alignment of point and tube light volumes with actual light positions. * feat: add user-adjustable gamma correction to SDR and HDR10 output paths - Added gamma correction to HDR10 path, applying user settings before PQ/BT.2020 encode. - Replaced the SDR blit-only path with an encode pass that applies gamma correction, ensuring consistent behavior across devices. - Refactored VulkanPostProcessor and associated shaders to support gamma slider adjustments for both SDR and HDR output. * fix: ensure proper uniform and cull mode setup for Vulkan sphere rendering - Added uniform buffer setup and default material uniforms in `vulkan_draw_sphere`. - Disabled face culling in material definition for sphere rendering to align behavior with OpenGL. * Ensure memory flushes after UBO updates and add explicit pipeline barrier for MoltenVK compatibility. * fix: add clear pass for SMAA edge detection to prevent persistent artifacts * feat: add Vulkan debug-utils messenger, opt-in sync validation, frame diagnostics Groundwork for the renderer-review fix series (.agents/plans/ vulkan-renderer-review-fixes.md, Phase 0): - Register a VK_EXT_debug_utils messenger (preferred) instead of only the deprecated VK_EXT_debug_report callback, which is kept as a fallback for loaders without debug-utils. The messenger config is also chained into instance creation so vkCreateInstance-time messages are captured. Validation errors/warnings now go to the main log via mprintf. - New -gr_sync_validation cmdline flag (implies -gr_debug): chains VkValidationFeaturesEXT with synchronization validation into instance creation when VK_LAYER_KHRONOS_validation is available. This is the regression net for the synchronization fixes in the following commits. - Frame stats now report descriptor sets/writes allocated per frame and the total pipeline count (descriptor-manager counters; DescriptorWriter::flush moved out-of-line to report into the manager). Co-Authored-By: Claude Fable 5 <[email protected]> * refactor: unify Vulkan render-pass begins, clamp in-pass clears, add per-frame UBO ring Phase 1 of the renderer-review fix series: shared helpers that the correctness fixes build on. - New VulkanRenderer::beginTrackedRenderPass(PassBeginDesc): every render-pass begin on the frame command buffer now records vkCmdBeginRenderPass and updates the state tracker (pass, color attachment count, sample count, render area, viewport) in one place. Converted: setupFrame, beginSceneRendering (both branches), resumeSceneRendering, endSceneRendering, beginRenderTarget (viewport kept for gr_set_viewport), resumeSwapChainPass, plus new resumeScenePassAfterCopy() shared by copyEffectTexture/copySceneDepthForParticles and rebeginSwapChainPassAfterReadback() shared by both readbackFramebuffer exits (~200 lines of duplicated begin/clear/viewport blocks removed). - The state tracker records the active render area; all four vkCmdClearAttachments sites clamp their rects against it and skip empty results. Previously gr_screen-sized clear rects could exceed a smaller off-screen render target's render area, which is invalid API usage. - New PerFrameUboRing utility (VulkanPerFrameUbo.h/.cpp): a persistently mapped uniform buffer with one slot region per frame in flight and a self-flushing alloc(). Used by the next commit to fix the cross-frame UBO races. Deliberate behavior deltas (all safe-direction): the helper always sets colorAttachmentCount/sampleCount (some sites previously inherited stale values), and resumeSwapChainPass/resumeSceneRendering now also reset the standard flipped viewport instead of leaving the previous one. Co-Authored-By: Claude Fable 5 <[email protected]> * fix: eliminate Vulkan cross-frame UBO/buffer races, make gr_sync honest Phase 2 of the renderer-review fix series: with two frames in flight, several host-visible resources were rewritten by the CPU while the previous frame's GPU work could still be reading them. - The post-processing scratch UBO is now a PerFrameUboRing (per-frame-in- flight slot regions, persistently mapped, self-flushing alloc). All the per-pass map/unmap/cursor bookkeeping in bloom/LDR/fog/SMAA is deleted; the cursor resets exactly once per frame via the new VulkanPostProcessor::beginFrame() hook in setupFrame. This also fixes two latent bugs: (a) fog consumed scratch slots mid-scene and bloom's later same-frame cursor reset overwrote them before submission whenever fog+bloom were both active; (b) volumetric_fog_data (288 bytes) overflowed the 256-byte slots - the memcpy spilled into the neighboring slot and the shader's bound range ended before the aspect/fov/noise fields. Slots are now 512 bytes (static_assert at the fog call site) and the per-frame budget is 32 slots. - The dedicated tonemap, output-encode, and MSAA-resolve UBOs (each a single instance rewritten per frame, i.e. racy; the MSAA one also never flushed) are deleted entirely - those passes build their structs on the stack and allocate from the scratch ring. - Static-buffer rewrites orphan instead of racing: getVkBuffer() stamps a last-used frame number, and a full-content updateBufferData() on a Static buffer used within the last MAX_FRAMES_IN_FLIGHT frames defers the old VkBuffer to the deletion queue and writes a fresh one. Offset writes (GPU-heap appends to fresh regions) and PersistentMapping buffers (the engine holds their mapped pointer and syncs via gr_sync) are exempt. - gr_sync_wait now honors its contract: VulkanRenderFrame::waitForFinish takes a timeout and reports expiry without consuming the fence; VulkanRenderer::waitForFrame returns false for fences taken during the still-recording frame (previously it waited on a stale or idle slot and reported success) and resolves the frame->slot mapping correctly. This makes UniformBufferManager's segment-fence throttling actually protective on Vulkan for the first time. Co-Authored-By: Claude Fable 5 <[email protected]> * Refactor framebuffer and target lifecycle: centralized creation/resizing logic, improved resource management across Vulkan post-processing subsystems. * Improve Vulkan cross-frame sync with explicit access masks and validation updates - Unified Vulkan validation flag handling in setup, separating engine debug output from synchronization validation. - Specified access masks and stage flags to address cross-frame WRITE_AFTER_WRITE vulnerabilities, notably in G-buffer, post-processing, and MSAA resolve paths. - Adjusted subpass dependencies and image layout transitions to prevent read-after-write and write-after-write hazards, detected using `-gr_sync_validation`. - Fixed uniform buffer and barrier transitions to ensure proper ordering in render passes across frames. * Refactor Vulkan render-pass dependencies and sync stages for clarity and future testing - Clarified dependency logic for cross-pass and cross-frame synchronization, ensuring explicit stage and access flag comments match observed behavior. - Adjusted subpass dependencies to decouple render-pass compatibility definitions from dependency assumptions. - Highlighted MoltenVK-specific issues and retained workarounds with notes for re-testing on macOS hardware. - Updated synchronization barriers and validation notes to address historical inconsistencies across platforms. * Remove transfer queue support; consolidate all operations into the graphics queue - Removed transfer queue handling, simplifying buffer and queue logic to use the graphics queue for all transfer operations. - Updated buffer sharing modes and removed related code paths and parameters for transfer queue families. - Introduced exclusive buffer access, noting async transfer and queue ownership transfers are future work. - Limited enabled device features to only those actively used by the renderer, reducing unnecessary feature requests (e.g., robustBufferAccess). * Refactor frame advancement and descriptor management in VulkanRenderer - Removed `endFrame()` from `VulkanDescriptorManager`, replacing it with `setCurrentFrame(frameIndex)` for explicit frame synchronization. - Centralized frame index advancement within `VulkanRenderer` to ensure consistency across components. - Invalidated cached descriptor set bindings at render-pass boundaries to handle raw recorder staleness correctly. - Optimized pipeline descriptor rebind logic by removing redundant clears during pipeline changes. * Refactor `PipelineConfig::hash` and Global descriptor set management for improved hashing and memoization efficiency - Replaced inefficient shift-based hashing in `PipelineConfig::hash` with a robust `hashCombine` strategy to minimize bucket collisions. - Introduced `maskBits` helper to streamline bitmask creation for boolean fields. - Implemented memoization for Global (Set 0) descriptor sets, reducing redundant rebuilds across frames and improving performance. - Added dirty state tracking (`m_globalSetDirty`) and invalidation logic for Global set updates following relevant input changes (e.g., TLAS or UBO modifications). * Refactor Vulkan post-processing output logic: extracted shared `encodeToSwapChainPass` method to streamline HDR and SDR output paths. * Optimize Vulkan descriptor set management with per-frame memoization - Added memoization for Material (Set 1) and PerDraw (Set 2) descriptor sets to reuse cached sets across frames if inputs remain unchanged. - Implemented precise input tracking for Material and PerDraw descriptor sets to detect and rebuild only when necessary. - Reduced redundant Vulkan descriptor allocations and writes, improving performance on repeated or batched draw calls. * Fix Vulkan access masks for G-buffer and scene pass transitions - Added `eColorAttachmentRead` to access masks in multiple barriers to ensure proper synchronization for load operations (e.g., `loadOp=eLoad`) across render passes. - Prevented `READ_AFTER_WRITE` synchronization validation issues in deferred and multi-pass rendering scenarios. - Updated comments to clarify the reasoning behind the access mask changes and load dependencies. * Refactor Vulkan resource management and indexed draw logic - Consolidated destruction queue methods in `VulkanDeletionQueue` using a shared `queueHandle` template to reduce redundancy. - Introduced `ModelDrawParams` structure and `computeModelDrawParams` function to unify indexed draw parameters for models and shadows. - Simplified draw logic by eliminating repeated calculation of indexed draw parameters and improving code reuse. * Add `releaseAnimationSlotRef` for ref-count handling of shared animation texture arrays - Introduced `releaseAnimationSlotRef` to manage reference counting for shared animation texture-array slots, preventing premature destruction of shared resources. - Updated `flushTextures` and related methods to integrate `releaseAnimationSlotRef` for shared slot cleanup. - Refactored staging buffer creation into `createStagingBuffer` for 3D texture uploads and shared path consistency. - Enhanced texture upload logic with improved tracking and logging for re-upload cases, aiding preloading gap analysis. - Added instrumentation for on-demand mid-frame texture uploads in `FrameStats`. * Refactor Vulkan resource and texture management, extend debug logging - Updated smart pointer usage for consistency across Vulkan resource managers (`std::make_unique`). - Removed redundant `cubeImageView` member and related cleanup logic; unified sampling view management. - Added per-instance debug log counters (avoiding static state) for Vulkan texture uploads and bindings, improving visibility during initialization and run-time. - Optimized `VulkanRenderer` property/feature getters by caching physical device properties and features during device creation. - Improved memory region flushing logic for out-of-date offsets, aligning with Vulkan Memory Allocator behavior. - Refactored render pass state tracking to reapply dynamic state reliably after mid-frame render passes. - Enhanced validation and error handling for mismatched update texture formats. * fix CI build warning * Fix on-demand YUV texture locking and set alpha uniform for movie rendering in Vulkan - Corrected bitmap lock type from `BMP_TEX_OTHER` to `BMP_AABITMAP` for YUV textures during on-demand loading. - Updated movie rendering to bind alpha from the `MovieData` UBO (set 2, binding 4) for Vulkan, aligning behavior with OpenGL implementation. * revert inadvertent changes introduced by a squash commit * address ci issues * remove wip stuff from code comments that make no sense to people who aren't me * Remove unused LD_LIBRARY_PATH export and fix shader shadow map sampling. * Refactor batched rendering uniform setup in Vulkan - Removed unused fields from `genericData` uniform block, simplifying shader definitions. - Updated Vulkan material uniform handling to align with OpenGL's batched rendering logic. - Introduced `batched_data` struct for streamlined UBO updates, improving maintainability and clarity. * Add `-hdr` command-line option for HDR nits configuration - Introduced `-hdr` parameter to set paper white and peak luminance in nits, overriding in-game HDR settings. - Added input validation with error feedback for incorrect format. * Remove unused `MODEL_SDR_FLAG_SHADOW_MAP` macro from shader flags * clean up unrelated changes * Invalidate Vulkan state tracker bindings after external command buffer modifications - Added `invalidateExternalBindings()` to `VulkanStateTracker` to handle stale pipelines, descriptors, and dynamic states caused by external Vulkan commands (e.g., ImGui renders mid-pass). - Updated ImGui rendering flow to call `invalidateExternalBindings()` post-draw, ensuring consistent pipeline state for subsequent engine renders. - Adjusted image usage flags to account for `eTransferSrc` in texture readbacks. - Fixed barrier `dstAccessMask` and pipeline stage flags to ensure proper synchronization for image transitions. * Add a few more generically useful lines to the log * Simplify Qt deployment logic and improve handling for system-installed Qt on Linux * add cmdline headers to make qtFred compile * use VK_KHR_synchronization2 for sync and barrier purposes * Simplify and clean up Qt deployment logic in CMake for qtfred * update get_bitmap_from_texture to match the new API * Enhance `-hdr` command-line option to force HDR output and support optional nits configuration with improved input validation and overrides. * Improve Vulkan robustness, add device feature checks, and refine memory/logging. - Added depth clamping support gated by device feature checks, with fallback where unsupported. - Improved deferred lighting UBO overflow handling with user-accessible warnings and buffer size adjustments. - Enhanced Vulkan logging for validation, allocation, and debugging: - Automatic logging for descriptor pool exhaustion/fragmentation. - Vertex format and resource destruction checks. - Notifications for generic layout transitions and volumetric texture uploads. - Consolidated shader logic to ensure Vulkan and OpenGL parity. * Remove HDR10 encode shader and consolidate gamma correction logic for SDR and HDR10. Replace `Verify` assertions with `Assert` * Add braces to conditional in object lighting logic for clarity * Refine Vulkan memory allocation checks with `Verification` for improved error handling and messaging. * Refine VulkanBuffer resize logic to preserve contents with CPU-side copy and add safeguards for non-coherent memory scenarios. * Add detailed comments explaining descriptor array capacity constants in VulkanDescriptorManager * Gate shadow support on depth clamp feature availability and clarify its necessity with improved comments. --------- Co-authored-by: BMagnu <[email protected]> Co-authored-by: Mara van der Laan <[email protected]> Co-authored-by: Claude <[email protected]>
2342 lines
66 KiB
C++
2342 lines
66 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.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "asteroid/asteroid.h"
|
|
#include "cmeasure/cmeasure.h"
|
|
#include "debris/debris.h"
|
|
#include "debugconsole/console.h"
|
|
#include "fireball/fireballs.h"
|
|
#include "freespace.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "globalincs/pstypes.h"
|
|
#include "globalincs/vmallocator.h"
|
|
#include "iff_defs/iff_defs.h"
|
|
#include "io/timer.h"
|
|
#include "jumpnode/jumpnode.h"
|
|
#include "lighting/lighting.h"
|
|
#include "lighting/lighting_profiles.h"
|
|
#include "mission/missionparse.h" //For 2D Mode
|
|
#include "network/multi.h"
|
|
#include "network/multiutil.h"
|
|
#include "network//multi_obj.h"
|
|
#include "object/deadobjectdock.h"
|
|
#include "object/objcollide.h"
|
|
#include "object/object.h"
|
|
#include "object/objectdock.h"
|
|
#include "object/objectshield.h"
|
|
#include "object/objectsnd.h"
|
|
#include "observer/observer.h"
|
|
#include "prop/prop.h"
|
|
#include "scripting/global_hooks.h"
|
|
#include "scripting/api/libs/graphics.h"
|
|
#include "scripting/scripting.h"
|
|
#include "playerman/player.h"
|
|
#include "radar/radar.h"
|
|
#include "radar/radarsetup.h"
|
|
#include "render/3d.h"
|
|
#include "ship/afterburner.h"
|
|
#include "ship/ship.h"
|
|
#include "starfield/starfield.h"
|
|
#include "tracing/tracing.h"
|
|
#include "weapon/beam.h"
|
|
#include "weapon/shockwave.h"
|
|
#include "weapon/swarm.h"
|
|
#include "weapon/weapon.h"
|
|
#include "tracing/Monitor.h"
|
|
#include "graphics/light.h"
|
|
#include "graphics/color.h"
|
|
#include "math/curve.h"
|
|
|
|
extern void ship_reset_disabled_physics(object *objp, int ship_class);
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
|
|
object obj_free_list;
|
|
object obj_used_list;
|
|
object obj_create_list;
|
|
|
|
object *Player_obj = NULL;
|
|
object *Viewer_obj = NULL;
|
|
|
|
//Data for objects
|
|
object Objects[MAX_OBJECTS];
|
|
SCP_map<int, raw_pof_obj> Pof_objects;
|
|
|
|
#ifdef OBJECT_CHECK
|
|
checkobject CheckObjects[MAX_OBJECTS];
|
|
#endif
|
|
|
|
int Num_objects=-1;
|
|
int Highest_object_index=-1;
|
|
int Highest_ever_object_index=0;
|
|
int Object_next_signature = 1; //0 is bogus, start at 1
|
|
int Object_inited = 0;
|
|
int Show_waypoints = 0;
|
|
|
|
object_h::object_h(int in_objnum)
|
|
: objnum(in_objnum)
|
|
{
|
|
if (objnum >= 0 && objnum < MAX_OBJECTS)
|
|
sig = Objects[objnum].signature;
|
|
else
|
|
objnum = -1;
|
|
}
|
|
|
|
object_h::object_h(const object* in_objp)
|
|
{
|
|
if (in_objp)
|
|
{
|
|
objnum = OBJ_INDEX(in_objp);
|
|
sig = in_objp->signature;
|
|
}
|
|
}
|
|
|
|
object_h::object_h()
|
|
{}
|
|
|
|
bool object_h::isValid() const
|
|
{
|
|
// a signature of 0 is invalid, per obj_init()
|
|
if (objnum < 0 || sig <= 0 || objnum >= MAX_OBJECTS)
|
|
return false;
|
|
return Objects[objnum].signature == sig;
|
|
}
|
|
|
|
object* object_h::objp() const
|
|
{
|
|
return &Objects[objnum];
|
|
}
|
|
|
|
object* object_h::objp_or_null() const
|
|
{
|
|
return isValid() ? &Objects[objnum] : nullptr;
|
|
}
|
|
|
|
//WMC - Made these prettier
|
|
const char *Object_type_names[MAX_OBJECT_TYPES] = {
|
|
//XSTR:OFF
|
|
"None",
|
|
"Ship",
|
|
"Weapon",
|
|
"Fireball",
|
|
"Start",
|
|
"Waypoint",
|
|
"Debris",
|
|
"Countermeasure",
|
|
"Ghost",
|
|
"Point",
|
|
"Shockwave",
|
|
"Wing",
|
|
"Observer",
|
|
"Asteroid",
|
|
"Jump Node",
|
|
"Beam",
|
|
"Raw Pof"
|
|
//XSTR:ON
|
|
};
|
|
|
|
obj_flag_name Object_flag_names[] = {
|
|
{ Object::Object_Flags::Invulnerable, "invulnerable", },
|
|
{ Object::Object_Flags::Protected, "protect-ship", },
|
|
{ Object::Object_Flags::Beam_protected, "beam-protect-ship", },
|
|
{ Object::Object_Flags::No_shields, "no-shields", },
|
|
{ Object::Object_Flags::Targetable_as_bomb, "targetable-as-bomb", },
|
|
{ Object::Object_Flags::Flak_protected, "flak-protect-ship", },
|
|
{ Object::Object_Flags::Laser_protected, "laser-protect-ship", },
|
|
{ Object::Object_Flags::Missile_protected, "missile-protect-ship", },
|
|
{ Object::Object_Flags::Immobile, "immobile", },
|
|
{ Object::Object_Flags::Dont_change_position, "don't-change-position", },
|
|
{ Object::Object_Flags::Dont_change_orientation, "don't-change-orientation", },
|
|
{ Object::Object_Flags::Collides, "collides", },
|
|
{ Object::Object_Flags::Attackable_if_no_collide, "ai-attackable-if-no-collide", },
|
|
};
|
|
|
|
obj_flag_description Object_flag_descriptions[] = {
|
|
{ Object::Object_Flags::Invulnerable, "Stops this object from taking any damage."},
|
|
{ Object::Object_Flags::Protected, "Ship and Turret AI will ignore and not attack this object."},
|
|
{ Object::Object_Flags::Beam_protected, "Turrets with beam weapons will ignore and not attack this object."},
|
|
{ Object::Object_Flags::No_shields, "This object will have no shields. (If this object can otherwise have shields, its shield energy will be fully reallocated to other ETS systems.)"},
|
|
{ Object::Object_Flags::Targetable_as_bomb, "Allows this object to be targeted with the bomb targeting key."},
|
|
{ Object::Object_Flags::Flak_protected, "Turrets with flak weapons will ignore and not attack this object."},
|
|
{ Object::Object_Flags::Laser_protected, "Turrets with laser weapons will ignore and not attack this object."},
|
|
{ Object::Object_Flags::Missile_protected, "Turrets with missile weapons will ignore and not attack this object."},
|
|
{ Object::Object_Flags::Immobile, "Will not let an object change position or orientation. Upon destruction it will still do the death roll and explosion."},
|
|
{ Object::Object_Flags::Dont_change_position, "Will not let an object change position. Upon destruction it will still do the death roll and explosion."},
|
|
{ Object::Object_Flags::Dont_change_orientation, "Will not let an object change orientation. Upon destruction it will still do the death roll and explosion."},
|
|
{ Object::Object_Flags::Collides, "This object will collide with other objects."},
|
|
{ Object::Object_Flags::Attackable_if_no_collide, "Allows the AI to attack this object, even if no-collide is set. (Normally an object that does not collide is also not attacked.)"},
|
|
{ Object::Object_Flags::Player_ship, "Ship is a player ship."},
|
|
{ Object::Object_Flags::Special_warpin, "Ship uses the special Knossos warp-in animation."},
|
|
};
|
|
|
|
extern const int Num_object_flag_names = sizeof(Object_flag_names) / sizeof(obj_flag_name);
|
|
extern const size_t Num_object_flag_descriptions = sizeof(Object_flag_descriptions) / sizeof(obj_flag_description);
|
|
|
|
#ifdef OBJECT_CHECK
|
|
checkobject::checkobject()
|
|
: type(0), signature(0), parent_sig(0)
|
|
{
|
|
flags.reset();
|
|
}
|
|
#endif
|
|
|
|
// all we need to set are the pointers, but type, parent, and instance are useful to set as well
|
|
object::object()
|
|
: next(nullptr), prev(nullptr), signature(0), type(OBJ_NONE), parent(-1), parent_sig(0), instance(-1), pos(vmd_zero_vector), orient(vmd_identity_matrix),
|
|
radius(0.0f), last_pos(vmd_zero_vector), last_orient(vmd_identity_matrix), hull_strength(0.0f), sim_hull_strength(0.0f), net_signature(0), num_pairs(0),
|
|
dock_list(nullptr), dead_dock_list(nullptr), collision_group_id(0)
|
|
{
|
|
memset(&(this->phys_info), 0, sizeof(physics_info));
|
|
}
|
|
|
|
object::~object()
|
|
{
|
|
dock_free_dock_list(this);
|
|
dock_free_dead_dock_list(this);
|
|
}
|
|
|
|
// DO NOT set next and prev to NULL because they keep the object on the free and used lists
|
|
void object::clear()
|
|
{
|
|
signature = num_pairs = collision_group_id = 0;
|
|
parent = parent_sig = instance = -1;
|
|
type = OBJ_NONE;
|
|
flags.reset();
|
|
pos = last_pos = vmd_zero_vector;
|
|
orient = last_orient = vmd_identity_matrix;
|
|
radius = hull_strength = sim_hull_strength = 0.0f;
|
|
physics_init( &phys_info );
|
|
shield_quadrant.clear();
|
|
objsnd_num.clear();
|
|
net_signature = 0;
|
|
|
|
pre_move_event.clear();
|
|
post_move_event.clear();
|
|
|
|
// just in case nobody called obj_delete last mission
|
|
dock_free_dock_list(this);
|
|
dock_free_dead_dock_list(this);
|
|
}
|
|
|
|
/**
|
|
* Scan the object list, freeing down to num_used objects
|
|
*
|
|
* @param target_num_used Number of used objects to free down to
|
|
* @return Returns number of slots freed
|
|
*/
|
|
int free_object_slots(int target_num_used)
|
|
{
|
|
int i, olind, deleted_weapons;
|
|
int obj_list[MAX_OBJECTS];
|
|
int num_already_free, num_to_free, original_num_to_free;
|
|
object *objp;
|
|
|
|
olind = 0;
|
|
|
|
// calc num_already_free by walking the obj_free_list
|
|
num_already_free = 0;
|
|
for ( objp = GET_FIRST(&obj_free_list); objp != END_OF_LIST(&obj_free_list); objp = GET_NEXT(objp) )
|
|
num_already_free++;
|
|
|
|
if (MAX_OBJECTS - num_already_free < target_num_used)
|
|
return 0;
|
|
|
|
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead]) {
|
|
num_already_free++;
|
|
if (MAX_OBJECTS - num_already_free < target_num_used)
|
|
return num_already_free;
|
|
} else
|
|
switch (objp->type) {
|
|
case OBJ_NONE:
|
|
num_already_free++;
|
|
if (MAX_OBJECTS - num_already_free < target_num_used)
|
|
return 0;
|
|
break;
|
|
case OBJ_FIREBALL:
|
|
case OBJ_WEAPON:
|
|
case OBJ_DEBRIS:
|
|
// case OBJ_CMEASURE:
|
|
obj_list[olind++] = OBJ_INDEX(objp);
|
|
break;
|
|
|
|
case OBJ_GHOST:
|
|
case OBJ_SHIP:
|
|
case OBJ_START:
|
|
case OBJ_WAYPOINT:
|
|
case OBJ_POINT:
|
|
case OBJ_SHOCKWAVE:
|
|
case OBJ_WING:
|
|
case OBJ_OBSERVER:
|
|
case OBJ_ASTEROID:
|
|
case OBJ_JUMP_NODE:
|
|
case OBJ_BEAM:
|
|
case OBJ_RAW_POF:
|
|
case OBJ_PROP:
|
|
break;
|
|
default:
|
|
Int3(); // Hey, what kind of object is this? Unknown!
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
num_to_free = MAX_OBJECTS - target_num_used - num_already_free;
|
|
original_num_to_free = num_to_free;
|
|
|
|
if (num_to_free > olind) {
|
|
nprintf(("allender", "Warning: Asked to free %i objects, but can only free %i.\n", num_to_free, olind));
|
|
num_to_free = olind;
|
|
}
|
|
|
|
for (i=0; i<num_to_free; i++)
|
|
if ( (Objects[obj_list[i]].type == OBJ_DEBRIS) && (!Debris[Objects[obj_list[i]].instance].flags[Debris_Flags::DoNotExpire]) ) {
|
|
num_to_free--;
|
|
nprintf(("allender", "Freeing DEBRIS object %3i\n", obj_list[i]));
|
|
Objects[obj_list[i]].flags.set(Object::Object_Flags::Should_be_dead);
|
|
}
|
|
|
|
if (num_to_free <= 0) {
|
|
return original_num_to_free;
|
|
}
|
|
|
|
for (i=0; i<num_to_free; i++) {
|
|
object *tmp_obj = &Objects[obj_list[i]];
|
|
if ( (tmp_obj->type == OBJ_FIREBALL) && (fireball_is_perishable(tmp_obj)) ) {
|
|
num_to_free--;
|
|
if (num_to_free <= 0) {
|
|
return original_num_to_free;
|
|
}
|
|
nprintf(("allender", "Freeing FIREBALL object %3i\n", obj_list[i]));
|
|
tmp_obj->flags.set(Object::Object_Flags::Should_be_dead);
|
|
}
|
|
}
|
|
|
|
deleted_weapons = collide_remove_weapons();
|
|
|
|
num_to_free -= deleted_weapons;
|
|
if ( num_to_free <= 0){
|
|
return original_num_to_free;
|
|
}
|
|
|
|
for (i=0; i<num_to_free; i++){
|
|
if ( Objects[obj_list[i]].type == OBJ_WEAPON ) {
|
|
num_to_free--;
|
|
Objects[obj_list[i]].flags.set(Object::Object_Flags::Should_be_dead);
|
|
}
|
|
}
|
|
|
|
if (!num_to_free){
|
|
return original_num_to_free;
|
|
}
|
|
|
|
return original_num_to_free - num_to_free;
|
|
}
|
|
|
|
// Goober5000
|
|
// This helper function does not check the object type and is not intended as a public API.
|
|
float get_hull_or_sim_hull_pct_helper(const object *objp, const float object::*strength_field, const ship *shipp, bool allow_negative)
|
|
{
|
|
Assert(objp && shipp);
|
|
if (!objp || !shipp)
|
|
return 0.0f;
|
|
|
|
float total_strength = shipp->ship_max_hull_strength;
|
|
Assert(total_strength > 0.0f); // unlike shield, no ship can have 0 hull
|
|
if (total_strength == 0.0f)
|
|
return 0.0f;
|
|
|
|
if (!allow_negative && objp->*strength_field < 0.0f) // this sometimes happens when a ship is being destroyed
|
|
return 0.0f;
|
|
|
|
return objp->*strength_field / total_strength;
|
|
}
|
|
|
|
float get_hull_pct(const object *objp, bool allow_negative)
|
|
{
|
|
Assert(objp && objp->type == OBJ_SHIP);
|
|
if (!objp || objp->type != OBJ_SHIP)
|
|
return 0.0f;
|
|
return get_hull_or_sim_hull_pct_helper(objp, &object::hull_strength, &Ships[objp->instance], allow_negative);
|
|
}
|
|
|
|
float get_hull_pct(const ship_registry_entry *ship_entry, bool allow_negative)
|
|
{
|
|
return get_hull_or_sim_hull_pct_helper(ship_entry->objp(), &object::hull_strength, ship_entry->shipp(), allow_negative);
|
|
}
|
|
|
|
float get_sim_hull_pct(const object *objp, bool allow_negative)
|
|
{
|
|
Assert(objp && objp->type == OBJ_SHIP);
|
|
if (!objp || objp->type != OBJ_SHIP)
|
|
return 0.0f;
|
|
return get_hull_or_sim_hull_pct_helper(objp, &object::sim_hull_strength, &Ships[objp->instance], allow_negative);
|
|
}
|
|
|
|
float get_sim_hull_pct(const ship_registry_entry *ship_entry, bool allow_negative)
|
|
{
|
|
return get_hull_or_sim_hull_pct_helper(ship_entry->objp(), &object::sim_hull_strength, ship_entry->shipp(), allow_negative);
|
|
}
|
|
|
|
// Goober5000
|
|
// This helper function does not check the object type and is not intended as a public API.
|
|
float get_shield_pct_helper(const object *objp, const ship *shipp)
|
|
{
|
|
Assert(objp && shipp);
|
|
if (!objp || !shipp)
|
|
return 0.0f;
|
|
|
|
float total_strength = shield_get_max_strength(shipp);
|
|
if (total_strength == 0.0f)
|
|
return 0.0f;
|
|
|
|
return shield_get_strength(objp) / total_strength;
|
|
}
|
|
|
|
float get_shield_pct(const object *objp)
|
|
{
|
|
Assert(objp);
|
|
if (!objp)
|
|
return 0.0f;
|
|
|
|
// bah - we might have asteroids
|
|
if (objp->type != OBJ_SHIP)
|
|
return 0.0f;
|
|
|
|
return get_shield_pct_helper(objp, &Ships[objp->instance]);
|
|
}
|
|
|
|
float get_shield_pct(const ship_registry_entry *ship_entry)
|
|
{
|
|
return get_shield_pct_helper(ship_entry->objp(), ship_entry->shipp());
|
|
}
|
|
|
|
static void on_script_state_destroy(lua_State*) {
|
|
// Since events are mostly used for scripting, we clear the event handlers when the Lua state is destroyed
|
|
for (auto& obj : Objects) {
|
|
obj.pre_move_event.clear();
|
|
obj.post_move_event.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets up the free list & init player & whatever else
|
|
*/
|
|
void obj_init()
|
|
{
|
|
int i;
|
|
object *objp;
|
|
|
|
Object_inited = 1;
|
|
for (i = 0; i < MAX_OBJECTS; ++i)
|
|
Objects[i].clear();
|
|
Viewer_obj = NULL;
|
|
|
|
list_init( &obj_free_list );
|
|
list_init( &obj_used_list );
|
|
list_init( &obj_create_list );
|
|
|
|
// Link all object slots into the free list
|
|
objp = Objects;
|
|
for (i=0; i<MAX_OBJECTS; i++) {
|
|
list_append(&obj_free_list, objp);
|
|
objp++;
|
|
}
|
|
|
|
Object_next_signature = 1; //0 is invalid, others start at 1
|
|
Num_objects = 0;
|
|
Highest_object_index = 0;
|
|
|
|
obj_reset_colliders();
|
|
|
|
Script_system.OnStateDestroy.add(on_script_state_destroy);
|
|
}
|
|
|
|
void obj_shutdown()
|
|
{
|
|
for (auto& obj : Objects) {
|
|
obj.clear();
|
|
}
|
|
}
|
|
|
|
static int num_objects_hwm = 0;
|
|
|
|
/**
|
|
* Allocates an object
|
|
*
|
|
* Generally, obj_create() should be called to get an object, since it
|
|
* fills in important fields and does the linking.
|
|
*
|
|
* @return the number of a free object, updating Highest_object_index
|
|
* @return -1 if no free objects
|
|
*/
|
|
int obj_allocate(bool essential)
|
|
{
|
|
int objnum;
|
|
object *objp;
|
|
|
|
if (!Object_inited) {
|
|
mprintf(("Why hasn't obj_init() been called yet?\n"));
|
|
obj_init();
|
|
}
|
|
|
|
if ( (Num_objects >= MAX_OBJECTS-10) && essential ) {
|
|
int num_freed;
|
|
|
|
num_freed = free_object_slots(MAX_OBJECTS-10);
|
|
nprintf(("warning", " *** Freed %i objects\n", num_freed));
|
|
}
|
|
|
|
if (Num_objects >= MAX_OBJECTS) {
|
|
mprintf(("Object creation failed - too many objects!\n" ));
|
|
return -1;
|
|
}
|
|
|
|
// Find next available object
|
|
objp = GET_FIRST(&obj_free_list);
|
|
Assert ( objp != &obj_free_list ); // shouldn't have the dummy element
|
|
|
|
// remove objp from the free list
|
|
list_remove( &obj_free_list, objp );
|
|
|
|
// insert objp onto the end of create list
|
|
list_append( &obj_create_list, objp );
|
|
|
|
// increment counter
|
|
Num_objects++;
|
|
|
|
if (Num_objects > num_objects_hwm) {
|
|
num_objects_hwm = Num_objects;
|
|
}
|
|
|
|
// get objnum
|
|
objnum = OBJ_INDEX(objp);
|
|
|
|
if (objnum > Highest_object_index) {
|
|
Highest_object_index = objnum;
|
|
if (Highest_object_index > Highest_ever_object_index)
|
|
Highest_ever_object_index = Highest_object_index;
|
|
}
|
|
|
|
return objnum;
|
|
}
|
|
|
|
/**
|
|
* Frees up an object
|
|
*
|
|
* Generally, obj_delete() should be called to get rid of an object.
|
|
* This function deallocates the object entry after the object has been unlinked
|
|
*/
|
|
void obj_free(int objnum)
|
|
{
|
|
object *objp;
|
|
|
|
if (!Object_inited) {
|
|
mprintf(("Why hasn't obj_init() been called yet?\n"));
|
|
obj_init();
|
|
}
|
|
|
|
Assert( objnum >= 0 ); // Trying to free bogus object!!!
|
|
|
|
// get object pointer
|
|
objp = &Objects[objnum];
|
|
|
|
// remove objp from the used list
|
|
list_remove( &obj_used_list, objp );
|
|
|
|
// add objp to the end of the free
|
|
list_append( &obj_free_list, objp );
|
|
|
|
// decrement counter
|
|
Num_objects--;
|
|
|
|
Objects[objnum].type = OBJ_NONE;
|
|
|
|
Assert(Num_objects >= 0);
|
|
|
|
if (objnum == Highest_object_index) {
|
|
while (Highest_object_index >= 0 && Objects[Highest_object_index].type == OBJ_NONE) {
|
|
--Highest_object_index;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a raw pof item
|
|
* @return the objnum of this raw POF
|
|
*/
|
|
int obj_raw_pof_create(const char* pof_filename, const matrix* orient, const vec3d* pos)
|
|
{
|
|
static int next_raw_pof_id = 0;
|
|
|
|
// Unlikely this would ever be hit.. but just in case
|
|
if (next_raw_pof_id >= INT_MAX || next_raw_pof_id < 0) {
|
|
Error(LOCATION, "Too many RAW_POF objects created!");
|
|
return -1;
|
|
}
|
|
|
|
if (!VALID_FNAME(pof_filename)) {
|
|
Warning(LOCATION, "Invalid tech model POF: %s", pof_filename);
|
|
return -1;
|
|
}
|
|
|
|
int model_num = model_load(pof_filename);
|
|
if (model_num < 0) {
|
|
Warning(LOCATION, "Failed to load tech model: %s", pof_filename);
|
|
return -1;
|
|
}
|
|
|
|
int id = next_raw_pof_id++;
|
|
Pof_objects[id] = {model_num, -1, {}};
|
|
|
|
flagset<Object::Object_Flags> flags;
|
|
flags.set(Object::Object_Flags::Renders);
|
|
|
|
int objnum = obj_create(OBJ_RAW_POF, -1, id, orient, pos, model_get_radius(model_num), flags);
|
|
|
|
Pof_objects[id].model_instance = model_create_instance(objnum, model_num);
|
|
|
|
return objnum;
|
|
}
|
|
|
|
/**
|
|
* Initialize a new object. Adds to the list for the given segment.
|
|
*
|
|
* The object will be a non-rendering, non-physics object. Pass -1 if no parent.
|
|
* @return the object number
|
|
*/
|
|
int obj_create(ubyte type, int parent_obj, int instance, const matrix *orient,
|
|
const vec3d *pos, float radius, const flagset<Object::Object_Flags> &flags, bool essential)
|
|
{
|
|
int objnum;
|
|
object *obj;
|
|
|
|
// Find next free object
|
|
objnum = obj_allocate(essential);
|
|
|
|
if (objnum == -1) //no free objects
|
|
return -1;
|
|
|
|
obj = &Objects[objnum];
|
|
Assert(obj->type == OBJ_NONE); //make sure unused
|
|
|
|
// clear object in preparation for setting of custom values
|
|
obj->clear();
|
|
|
|
Assert(Object_next_signature > 0); // 0 is bogus!
|
|
obj->signature = Object_next_signature++;
|
|
|
|
obj->type = type;
|
|
obj->instance = instance;
|
|
obj->parent = parent_obj;
|
|
if (obj->parent != -1) {
|
|
obj->parent_sig = Objects[parent_obj].signature;
|
|
} else {
|
|
obj->parent_sig = obj->signature;
|
|
}
|
|
|
|
obj->flags = flags;
|
|
obj->flags.set(Object::Object_Flags::Not_in_coll);
|
|
if (pos) {
|
|
obj->pos = *pos;
|
|
obj->last_pos = *pos;
|
|
}
|
|
|
|
if (orient) {
|
|
obj->orient = *orient;
|
|
obj->last_orient = *orient;
|
|
}
|
|
obj->radius = radius;
|
|
|
|
obj->shield_quadrant.resize(DEFAULT_SHIELD_SECTIONS); // Might be changed by the ship creation code
|
|
|
|
return objnum;
|
|
}
|
|
|
|
void obj_delete_all()
|
|
{
|
|
int counter = 0;
|
|
for (int i = 0; i < MAX_OBJECTS; ++i)
|
|
{
|
|
if (Objects[i].type == OBJ_NONE)
|
|
continue;
|
|
++counter;
|
|
obj_delete(i);
|
|
}
|
|
|
|
mprintf(("Cleanup: Deleted %i objects\n", counter));
|
|
}
|
|
|
|
/**
|
|
* Remove object from the world
|
|
* If Player_obj, don't remove it!
|
|
*
|
|
* @param objnum Object number to remove
|
|
*/
|
|
void obj_delete(int objnum)
|
|
{
|
|
object *objp;
|
|
|
|
Assert(objnum >= 0 && objnum < MAX_OBJECTS);
|
|
objp = &Objects[objnum];
|
|
if (objp->type == OBJ_NONE) {
|
|
mprintf(("obj_delete() called for already deleted object %d.\n", objnum));
|
|
return;
|
|
};
|
|
|
|
// Remove all object pairs
|
|
obj_remove_collider(objnum);
|
|
|
|
switch( objp->type ) {
|
|
case OBJ_WEAPON:
|
|
weapon_delete( objp );
|
|
break;
|
|
case OBJ_SHIP:
|
|
if ((objp == Player_obj) && !Fred_running) {
|
|
objp->type = OBJ_GHOST;
|
|
objp->flags.remove(Object::Object_Flags::Should_be_dead);
|
|
|
|
// we have to traverse the ship_obj list and remove this guy from it as well
|
|
ship_obj *moveup = GET_FIRST(&Ship_obj_list);
|
|
while(moveup != END_OF_LIST(&Ship_obj_list)){
|
|
if(OBJ_INDEX(objp) == moveup->objnum){
|
|
list_remove(&Ship_obj_list,moveup);
|
|
break;
|
|
}
|
|
moveup = GET_NEXT(moveup);
|
|
}
|
|
|
|
physics_init(&objp->phys_info);
|
|
obj_snd_delete_type(OBJ_INDEX(objp));
|
|
|
|
return;
|
|
} else
|
|
ship_delete( objp );
|
|
break;
|
|
case OBJ_FIREBALL:
|
|
fireball_delete( objp );
|
|
break;
|
|
case OBJ_SHOCKWAVE:
|
|
shockwave_delete( objp );
|
|
break;
|
|
case OBJ_START:
|
|
case OBJ_WAYPOINT:
|
|
case OBJ_POINT:
|
|
break; // requires no action, handled by the Fred code.
|
|
case OBJ_JUMP_NODE:
|
|
jumpnode_delete(objp);
|
|
break;
|
|
case OBJ_DEBRIS:
|
|
debris_delete( objp );
|
|
break;
|
|
case OBJ_ASTEROID:
|
|
asteroid_delete(objp);
|
|
break;
|
|
/* case OBJ_CMEASURE:
|
|
cmeasure_delete( objp );
|
|
break;*/
|
|
case OBJ_GHOST:
|
|
if(!(Game_mode & GM_MULTIPLAYER)){
|
|
mprintf(("Warning: Tried to delete a ghost!\n"));
|
|
objp->flags.remove(Object::Object_Flags::Should_be_dead);
|
|
return;
|
|
} else {
|
|
// we need to be able to delete GHOST objects in multiplayer to allow for player respawns.
|
|
nprintf(("Network","Deleting GHOST object\n"));
|
|
objp->net_signature = 0;
|
|
}
|
|
break;
|
|
case OBJ_OBSERVER:
|
|
observer_delete(objp);
|
|
break;
|
|
case OBJ_BEAM:
|
|
break;
|
|
case OBJ_RAW_POF:
|
|
model_delete_instance(Pof_objects[objp->instance].model_instance);
|
|
Pof_objects.erase(objp->instance);
|
|
break;
|
|
case OBJ_PROP:
|
|
prop_delete(objp);
|
|
break;
|
|
case OBJ_NONE:
|
|
Int3();
|
|
break;
|
|
default:
|
|
Error( LOCATION, "Unhandled object type %d in obj_delete_all_that_should_be_dead", objp->type );
|
|
}
|
|
|
|
// this avoids include issues from physics state, multi interpolate and object code
|
|
extern void multi_interpolate_clear_helper(int objnum);
|
|
|
|
// clean up interpolation info
|
|
multi_interpolate_clear_helper(objnum);
|
|
|
|
// delete any dock information we still have
|
|
dock_free_dock_list(objp);
|
|
dock_free_dead_dock_list(objp);
|
|
|
|
// if a persistant sound has been created, delete it
|
|
obj_snd_delete_type(OBJ_INDEX(objp));
|
|
|
|
objp->type = OBJ_NONE; //unused!
|
|
objp->signature = 0;
|
|
|
|
obj_free(objnum);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------------------
|
|
void obj_delete_all_that_should_be_dead()
|
|
{
|
|
object *objp, *temp;
|
|
|
|
if (!Object_inited) {
|
|
mprintf(("Why hasn't obj_init() been called yet?\n"));
|
|
obj_init();
|
|
}
|
|
|
|
// Move all objects
|
|
objp = GET_FIRST(&obj_used_list);
|
|
while( objp !=END_OF_LIST(&obj_used_list) ) {
|
|
// Goober5000 - HACK HACK HACK - see obj_move_all
|
|
objp->flags.remove(Object::Object_Flags::Docked_already_handled);
|
|
|
|
temp = GET_NEXT(objp);
|
|
if ( objp->flags[Object::Object_Flags::Should_be_dead] )
|
|
obj_delete( OBJ_INDEX(objp) ); // MWA says that john says that let obj_delete handle everything because of the editor
|
|
objp = temp;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Add all newly created objects to the end of the used list and create their
|
|
* object pairs for collision detection
|
|
*/
|
|
void obj_merge_created_list(void)
|
|
{
|
|
// The old way just merged the two. This code takes one out of the create list,
|
|
// creates object pairs for it, and then adds it to the used list.
|
|
// OLD WAY: list_merge( &obj_used_list, &obj_create_list );
|
|
object *objp = GET_FIRST(&obj_create_list);
|
|
while( objp !=END_OF_LIST(&obj_create_list) ) {
|
|
list_remove( obj_create_list, objp );
|
|
|
|
// Add it to the object pairs array
|
|
obj_add_collider(OBJ_INDEX(objp));
|
|
|
|
// Then add it to the object used list
|
|
list_append( &obj_used_list, objp );
|
|
|
|
objp = GET_FIRST(&obj_create_list);
|
|
}
|
|
|
|
// Make sure the create list is empty.
|
|
list_init(&obj_create_list);
|
|
}
|
|
|
|
int physics_paused = 0, ai_paused = 0;
|
|
|
|
|
|
// Goober5000
|
|
extern void call_doa(object *child, object *parent);
|
|
void obj_move_one_docked_object(object *objp, object *parent_objp)
|
|
{
|
|
// in FRED, just move and return
|
|
if (Fred_running)
|
|
{
|
|
call_doa(objp, parent_objp);
|
|
return;
|
|
}
|
|
|
|
// support ships (and anyone else, for that matter) don't keep up if they're undocking
|
|
ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
|
|
if ( (aip->mode == AIM_DOCK) && (aip->submode >= AIS_UNDOCK_1) )
|
|
{
|
|
if (aip->goal_objnum == OBJ_INDEX(parent_objp))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check the guy that I'm docked with and don't move if he's undocking from me
|
|
ai_info *other_aip = &Ai_info[Ships[parent_objp->instance].ai_index];
|
|
if ( (other_aip->mode == AIM_DOCK) && (other_aip->submode >= AIS_UNDOCK_1) )
|
|
{
|
|
if (other_aip->goal_objnum == OBJ_INDEX(objp))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// we're here, so we move with our parent object
|
|
call_doa(objp, parent_objp);
|
|
}
|
|
|
|
/**
|
|
* Deals with firing player things like lasers, missiles, etc.
|
|
*
|
|
* Separated out because of multiplayer issues.
|
|
*/
|
|
void obj_player_fire_stuff( object *objp, control_info ci )
|
|
{
|
|
ship *shipp;
|
|
|
|
Assert( objp->flags[Object::Object_Flags::Player_ship]);
|
|
|
|
// try and get the ship pointer
|
|
shipp = NULL;
|
|
if((objp->type == OBJ_SHIP) && (objp->instance >= 0) && (objp->instance < MAX_SHIPS)){
|
|
shipp = &Ships[objp->instance];
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
ship_weapon* swp = &shipp->weapons;
|
|
|
|
// single player pilots, and all players in multiplayer take care of firing their own primaries
|
|
if(!(Game_mode & GM_MULTIPLAYER) || (objp == Player_obj))
|
|
{
|
|
if ( ci.fire_primary_count ) {
|
|
// flag the ship as having the trigger down
|
|
if(shipp != NULL){
|
|
shipp->flags.set(Ship::Ship_Flags::Trigger_down);
|
|
swp->flags.set(Ship::Weapon_Flags::Primary_trigger_down);
|
|
}
|
|
|
|
// fire non-streaming primaries here
|
|
// Cyborg17, this is where the inaccurate multi shots are being shot...
|
|
// so let's let the new system take over instead by excluding client player shots
|
|
// on the server.
|
|
if (!(MULTIPLAYER_MASTER) || (objp == Player_obj)) {
|
|
ship_fire_primary(objp);
|
|
}
|
|
|
|
} else {
|
|
// unflag the ship as having the trigger down
|
|
if(shipp != NULL){
|
|
shipp->flags.remove(Ship::Ship_Flags::Trigger_down);
|
|
swp->flags.remove(Ship::Weapon_Flags::Primary_trigger_down);
|
|
ship_stop_fire_primary(objp); //if it hasn't fired do the "has just stoped fireing" stuff
|
|
}
|
|
}
|
|
|
|
if ( ci.fire_countermeasure_count ) {
|
|
ship_launch_countermeasure( objp );
|
|
}
|
|
}
|
|
|
|
// single player and multiplayer masters do all of the following
|
|
if ( !MULTIPLAYER_CLIENT
|
|
// Cyborg17 - except clients now fire dumbfires for rollback on the server
|
|
|| !(Weapon_info[swp->secondary_bank_weapons[shipp->weapons.current_secondary_bank]].is_homing())) {
|
|
if (ci.fire_secondary_count) {
|
|
if ( !ship_start_secondary_fire(objp) ) {
|
|
ship_fire_secondary( objp );
|
|
}
|
|
|
|
// kill the secondary count
|
|
ci.fire_secondary_count = 0;
|
|
} else {
|
|
if ( ship_stop_secondary_fire(objp) ) {
|
|
ship_fire_secondary( objp );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( MULTIPLAYER_CLIENT && objp == Player_obj ) {
|
|
if (Weapon_info[swp->secondary_bank_weapons[shipp->weapons.current_secondary_bank]].trigger_lock) {
|
|
if (ci.fire_secondary_count) {
|
|
ship_start_secondary_fire(objp);
|
|
} else {
|
|
ship_stop_secondary_fire(objp);
|
|
}
|
|
}
|
|
}
|
|
|
|
// everyone does the following for their own ships.
|
|
if ( ci.afterburner_start ){
|
|
if (Ships[objp->instance].flags[Ship::Ship_Flags::Maneuver_despite_engines] || !ship_subsystems_blown(&Ships[objp->instance], SUBSYSTEM_ENGINE)) {
|
|
afterburners_start( objp );
|
|
}
|
|
}
|
|
|
|
if ( ci.afterburner_stop ){
|
|
afterburners_stop( objp, 1 );
|
|
}
|
|
|
|
}
|
|
|
|
void obj_move_call_physics(object *objp, float frametime)
|
|
{
|
|
TRACE_SCOPE(tracing::Physics);
|
|
|
|
// Do physics for objects with OF_PHYSICS flag set and with some engine strength remaining.
|
|
if ( objp->flags[Object::Object_Flags::Physics] ) {
|
|
// only set phys info if ship is not dead
|
|
if ((objp->type == OBJ_SHIP) && !(Ships[objp->instance].flags[Ship::Ship_Flags::Dying])) {
|
|
ship *shipp = &Ships[objp->instance];
|
|
|
|
if (!shipp->flags[Ship::Ship_Flags::Maneuver_despite_engines]) {
|
|
bool engines_blown = ship_subsystems_blown(shipp, SUBSYSTEM_ENGINE);
|
|
if ( ship_subsys_disrupted(shipp, SUBSYSTEM_ENGINE) ) {
|
|
engines_blown = true;
|
|
}
|
|
|
|
if (engines_blown) { // All this is necessary to make ship gradually come to a stop after engines are blown.
|
|
vm_vec_zero(&objp->phys_info.desired_vel);
|
|
vm_vec_zero(&objp->phys_info.desired_rotvel);
|
|
vm_mat_zero(&objp->phys_info.ai_desired_orient);
|
|
objp->phys_info.flags |= (PF_REDUCED_DAMP | PF_DEAD_DAMP);
|
|
objp->phys_info.side_slip_time_const = Ship_info[shipp->ship_info_index].damp * 4.0f;
|
|
}
|
|
}
|
|
// recover if we *are* maneuvering but the flag was added
|
|
else if ((objp->phys_info.flags & PF_DEAD_DAMP) && !shipp->flags[Ship::Ship_Flags::Dying]) {
|
|
ship_reset_disabled_physics(objp, shipp->ship_info_index);
|
|
}
|
|
}
|
|
|
|
// if a weapon is flagged as dead, kill its engines just like a ship
|
|
if((objp->type == OBJ_WEAPON) && (Weapons[objp->instance].weapon_flags[Weapon::Weapon_Flags::Dead_in_water])){
|
|
vm_vec_zero(&objp->phys_info.desired_vel);
|
|
vm_vec_zero(&objp->phys_info.desired_rotvel);
|
|
objp->phys_info.flags |= (PF_REDUCED_DAMP | PF_DEAD_DAMP);
|
|
objp->phys_info.side_slip_time_const = 1.0f; // FIXME? originally indexed into Ship_info[], which was a bug...
|
|
}
|
|
|
|
if (physics_paused) {
|
|
if (objp==Player_obj){
|
|
physics_sim(&objp->pos, &objp->orient, &objp->phys_info, &The_mission.gravity, frametime ); // simulate the physics
|
|
}
|
|
} else {
|
|
// Hack for dock mode.
|
|
// If docking with a ship, we don't obey the normal ship physics, we can slew about.
|
|
if (objp->type == OBJ_SHIP) {
|
|
ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
|
|
|
|
// Note: This conditional for using PF_USE_VEL (instantaneous acceleration) is probably too loose.
|
|
// A ships awaiting support will fly towards the support ship with instantaneous acceleration.
|
|
// But we want to have ships in the process of docking have quick acceleration, or they overshoot their goals.
|
|
// Probably can not key off objnum_I_am_docked_or_docking_with, but then need to add some other condition. Live with it for now. -- MK, 2/19/98
|
|
|
|
// Goober5000 - no need to key off objnum; other conditions get it just fine
|
|
|
|
if (/* (objnum_I_am_docked_or_docking_with != -1) || */
|
|
((aip->mode == AIM_DOCK) && ((aip->submode == AIS_DOCK_2) || (aip->submode == AIS_DOCK_3) || (aip->submode == AIS_UNDOCK_0))) ||
|
|
((aip->mode == AIM_WARP_OUT) && (aip->submode >= AIS_WARP_3))) {
|
|
if (Ships[objp->instance].flags[Ship::Ship_Flags::Maneuver_despite_engines] || !ship_subsystems_blown(&Ships[objp->instance], SUBSYSTEM_ENGINE)){
|
|
objp->phys_info.flags |= PF_USE_VEL;
|
|
} else {
|
|
objp->phys_info.flags &= ~PF_USE_VEL; // If engine blown, don't PF_USE_VEL, or ships stop immediately
|
|
}
|
|
} else {
|
|
objp->phys_info.flags &= ~PF_USE_VEL;
|
|
}
|
|
}
|
|
|
|
// simulate the physics
|
|
physics_sim(&objp->pos, &objp->orient, &objp->phys_info, &The_mission.gravity, frametime);
|
|
|
|
// if the object is the player object, do things that need to be done after the ship
|
|
// is moved (like firing weapons, etc). This routine will get called either single
|
|
// or multiplayer. We must find the player object to get to the control info field
|
|
if ( (objp->flags[Object::Object_Flags::Player_ship]) && (objp->type != OBJ_OBSERVER) && (objp == Player_obj)) {
|
|
player *pp;
|
|
if(Player != NULL){
|
|
pp = Player;
|
|
obj_player_fire_stuff( objp, pp->ci );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//2D MODE
|
|
//THIS IS A FREAKIN' HACK
|
|
//Do not let ship change position on Y axis
|
|
if(The_mission.flags[Mission::Mission_Flags::Mission_2d])
|
|
{
|
|
angles old_angles, new_angles;
|
|
objp->pos.xyz.y = objp->last_pos.xyz.y;
|
|
vm_extract_angles_matrix(&old_angles, &objp->last_orient);
|
|
vm_extract_angles_matrix(&new_angles, &objp->orient);
|
|
new_angles.p = old_angles.p;
|
|
new_angles.b = old_angles.b;
|
|
vm_angles_2_matrix(&objp->orient, &new_angles);
|
|
|
|
//Phys stuff hack
|
|
new_angles.h = old_angles.h;
|
|
vm_angles_2_matrix(&objp->phys_info.last_rotmat, &new_angles);
|
|
objp->phys_info.vel.xyz.y = 0.0f;
|
|
objp->phys_info.desired_rotvel.xyz.x = 0;
|
|
objp->phys_info.desired_rotvel.xyz.z = 0;
|
|
objp->phys_info.desired_vel.xyz.y = 0.0f;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef OBJECT_CHECK
|
|
|
|
void obj_check_object( object *obj )
|
|
{
|
|
int objnum = OBJ_INDEX(obj);
|
|
|
|
// PROGRAMMERS: If one of these Int3() gets hit, then someone
|
|
// is changing a value in the object structure that might cause
|
|
// collision detection to not work. See John for more info if
|
|
// you are hitting one of these.
|
|
|
|
if ( CheckObjects[objnum].type != obj->type ) {
|
|
if ( (obj->type==OBJ_WAYPOINT) && (CheckObjects[objnum].type==OBJ_SHIP) ) {
|
|
// We know about ships changing into waypoints and that is
|
|
// ok.
|
|
CheckObjects[objnum].type = OBJ_WAYPOINT;
|
|
} else if ( (obj->type==OBJ_SHIP) && (CheckObjects[objnum].type==OBJ_GHOST) ) {
|
|
// We know about player changing into a ghost after dying and that is
|
|
// ok.
|
|
CheckObjects[objnum].type = OBJ_GHOST;
|
|
} else if ( (obj->type==OBJ_GHOST) && (CheckObjects[objnum].type==OBJ_SHIP) ) {
|
|
// We know about player changing into a ghost after dying and that is
|
|
// ok.
|
|
CheckObjects[objnum].type = OBJ_SHIP;
|
|
} else {
|
|
mprintf(( "Object type changed! Old: %i, Current: %i\n", CheckObjects[objnum].type, obj->type ));
|
|
Int3();
|
|
}
|
|
}
|
|
if ( CheckObjects[objnum].signature != obj->signature ) {
|
|
mprintf(( "Object signature changed!\n" ));
|
|
Int3();
|
|
}
|
|
if ( (CheckObjects[objnum].flags[Object::Object_Flags::Collides]) != (obj->flags[Object::Object_Flags::Collides]) ) {
|
|
mprintf(( "Object flags changed!\n" ));
|
|
Int3();
|
|
}
|
|
if ( CheckObjects[objnum].parent_sig != obj->parent_sig ) {
|
|
mprintf(( "Object parent sig changed!\n" ));
|
|
Int3();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Call this if you want to change an object flag so that the
|
|
* object code knows what's going on. For instance if you turn
|
|
* off OF_COLLIDES, the object code needs to know this in order to
|
|
* actually turn the object collision detection off. By calling
|
|
* this you shouldn't get Int3's in the checkobject code. If you
|
|
* do, then put code in here to correctly handle the case.
|
|
*/
|
|
void obj_set_flags( object *obj, const flagset<Object::Object_Flags>& new_flags )
|
|
{
|
|
int objnum = OBJ_INDEX(obj);
|
|
|
|
// turning collision detection off
|
|
if ( (obj->flags[Object::Object_Flags::Collides]) && (!(new_flags[Object::Object_Flags::Collides]))) {
|
|
// Remove all object pairs
|
|
obj_remove_collider(objnum);
|
|
|
|
// update object flags properly
|
|
obj->flags = new_flags;
|
|
obj->flags.set(Object::Object_Flags::Not_in_coll);
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[objnum].flags = new_flags;
|
|
CheckObjects[objnum].flags.set(Object::Object_Flags::Not_in_coll);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
|
|
// turning collision detection on
|
|
if ( (!(obj->flags[Object::Object_Flags::Collides])) && (new_flags[Object::Object_Flags::Collides]) ) {
|
|
|
|
// observers can't collide or be hit, and they therefore have no hit or collide functions
|
|
// So, don't allow this bit to be set
|
|
if(obj->type == OBJ_OBSERVER){
|
|
mprintf(("Illegal to set collision bit for OBJ_OBSERVER!!\n"));
|
|
Int3();
|
|
}
|
|
|
|
obj->flags.set(Object::Object_Flags::Collides);
|
|
|
|
// Turn on collision detection
|
|
obj_add_collider(objnum);
|
|
|
|
obj->flags = new_flags;
|
|
obj->flags.remove(Object::Object_Flags::Not_in_coll);
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[objnum].flags = new_flags;
|
|
CheckObjects[objnum].flags.remove(Object::Object_Flags::Not_in_coll);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// for a multiplayer host -- use this debug code to help trap when non-player ships are getting
|
|
// marked as OF_COULD_BE_PLAYER
|
|
// this code is pretty much debug code and shouldn't be relied on to always do the right thing
|
|
// for flags other than
|
|
if ( MULTIPLAYER_MASTER && !(obj->flags[Object::Object_Flags::Could_be_player]) && (new_flags[Object::Object_Flags::Could_be_player]) ) {
|
|
ship *shipp;
|
|
int team, slot;
|
|
|
|
// this flag sometimes gets set for observers.
|
|
if ( obj->type == OBJ_OBSERVER ) {
|
|
return;
|
|
}
|
|
|
|
// sanity checks
|
|
if ( (obj->type != OBJ_SHIP) || (obj->instance < 0) ) {
|
|
return; // return because we really don't want to set the flag
|
|
}
|
|
|
|
// see if this ship is really a player ship (or should be)
|
|
shipp = &Ships[obj->instance];
|
|
extern void multi_ts_get_team_and_slot(char *, int *, int *);
|
|
multi_ts_get_team_and_slot(shipp->ship_name,&team,&slot);
|
|
if ( (shipp->wingnum == -1) || (team == -1) || (slot==-1) ) {
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
// set the flag
|
|
obj->flags = new_flags;
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[objnum].flags = new_flags;
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
// Check for unhandled flag changing
|
|
if ( (new_flags[Object::Object_Flags::Collides]) != (obj->flags[Object::Object_Flags::Collides]) ) {
|
|
mprintf(( "Unhandled flag changing in obj_set_flags!!\n" ));
|
|
mprintf(( "Add code to support it, see John for questions!!\n" ));
|
|
Int3();
|
|
} else {
|
|
// Since it wasn't an important flag, just bash it.
|
|
obj->flags = new_flags;
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[objnum].flags = new_flags;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
void obj_move_all_pre(object *objp, float frametime)
|
|
{
|
|
TRACE_SCOPE(tracing::PreMove);
|
|
|
|
switch( objp->type ) {
|
|
case OBJ_WEAPON:
|
|
if (!physics_paused){
|
|
weapon_process_pre( objp, frametime );
|
|
}
|
|
break;
|
|
case OBJ_SHIP:
|
|
if (!physics_paused || (objp==Player_obj )){
|
|
ship_process_pre( objp, frametime );
|
|
}
|
|
break;
|
|
case OBJ_FIREBALL:
|
|
// all fireballs are moved via fireball_process_post()
|
|
break;
|
|
case OBJ_SHOCKWAVE:
|
|
// all shockwaves are moved via shockwave_move_all()
|
|
break;
|
|
case OBJ_DEBRIS:
|
|
// all debris are moved via debris_process_post()
|
|
break;
|
|
case OBJ_ASTEROID:
|
|
if (!physics_paused){
|
|
asteroid_process_pre(objp);
|
|
}
|
|
break;
|
|
/* case OBJ_CMEASURE:
|
|
if (!physics_paused){
|
|
cmeasure_process_pre(objp, frametime);
|
|
}
|
|
break;*/
|
|
case OBJ_WAYPOINT:
|
|
break; // waypoints don't move..
|
|
case OBJ_GHOST:
|
|
break;
|
|
case OBJ_OBSERVER:
|
|
case OBJ_JUMP_NODE:
|
|
break;
|
|
case OBJ_BEAM:
|
|
break;
|
|
case OBJ_RAW_POF:
|
|
break;
|
|
case OBJ_PROP:
|
|
break;
|
|
case OBJ_NONE:
|
|
Int3();
|
|
break;
|
|
default:
|
|
Error(LOCATION, "Unhandled object type %d in obj_move_all_pre\n", objp->type);
|
|
}
|
|
|
|
objp->pre_move_event(objp);
|
|
}
|
|
|
|
// Used to tell if a particular group of lasers has cast light yet.
|
|
ubyte Obj_weapon_group_id_used[WEAPON_MAX_GROUP_IDS];
|
|
|
|
/**
|
|
* Called once a frame to mark all weapon groups as not having cast light yet.
|
|
*/
|
|
void obj_clear_weapon_group_id_list()
|
|
{
|
|
memset( Obj_weapon_group_id_used, 0, sizeof(Obj_weapon_group_id_used) );
|
|
}
|
|
|
|
int Arc_light = 1; // If set, electrical arcs on debris cast light
|
|
DCF_BOOL(arc_light, Arc_light)
|
|
|
|
void obj_move_all_post(object *objp, float frametime)
|
|
{
|
|
switch (objp->type)
|
|
{
|
|
case OBJ_WEAPON:
|
|
{
|
|
TRACE_SCOPE(tracing::WeaponPostMove);
|
|
|
|
if ( !physics_paused )
|
|
weapon_process_post( objp, frametime );
|
|
|
|
// Cast light
|
|
if ( (Detail.lighting > 3) && light_deferred_enabled() ) {
|
|
// Weapons cast light
|
|
|
|
int group_id = Weapons[objp->instance].group_id;
|
|
int cast_light = 1;
|
|
|
|
if (group_id >= 0) {
|
|
if (Obj_weapon_group_id_used[group_id] == 0) {
|
|
// Mark this group as done
|
|
Obj_weapon_group_id_used[group_id]++;
|
|
}
|
|
else {
|
|
// This group has already done its light casting
|
|
cast_light = 0;
|
|
}
|
|
}
|
|
if (cast_light) {
|
|
weapon* wp = &Weapons[objp->instance];
|
|
weapon_info* wi = &Weapon_info[wp->weapon_info_index];
|
|
auto lp = lighting_profiles::current();
|
|
hdr_color light_color;
|
|
|
|
float intensity_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_INTENSITY_MULT, *wp, &wp->modular_curves_instance);
|
|
float radius_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_RADIUS_MULT, *wp, &wp->modular_curves_instance);
|
|
float r_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_R_MULT, *wp, &wp->modular_curves_instance);
|
|
float g_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_G_MULT, *wp, &wp->modular_curves_instance);
|
|
float b_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_B_MULT, *wp, &wp->modular_curves_instance);
|
|
|
|
float source_radius = objp->radius;
|
|
float light_radius;
|
|
float light_brightness;
|
|
|
|
// Handle differing adjustments depending on weapon type.
|
|
if (wi->render_type == WRT_LASER) {
|
|
light_radius = lp->laser_light_radius.handle(wi->light_radius) * radius_mult;
|
|
// intensity is stored in the light color even if no user setting is done.
|
|
light_brightness = lp->laser_light_brightness.handle(wi->light_color.i());
|
|
} else {
|
|
// Missiles should typically not be treated as lights for their whole radius. TODO: make configurable.
|
|
source_radius *= 0.05f;
|
|
light_radius = lp->missile_light_radius.handle(wi->light_radius) * radius_mult;
|
|
light_brightness = lp->missile_light_brightness.handle(wi->light_color.i());
|
|
}
|
|
|
|
// If there is no specific color set in the table, laser render weapons have a dynamic color.
|
|
if (!wi->light_color_set && wi->render_type == WRT_LASER) {
|
|
// Classic dynamic laser color is handled with an old color object
|
|
color c;
|
|
weapon_get_laser_color(&c, objp);
|
|
light_color.set_rgbai((c.red/255.f), (c.green/255.f), (c.blue/255.f), 1.f, light_brightness);
|
|
} else {
|
|
// If not a laser then all default information needed is stored in the weapon light color
|
|
light_color.set_rgbai(wi->light_color.r(), wi->light_color.g(), wi->light_color.b(), 1.f, light_brightness);
|
|
}
|
|
|
|
light_color.multiply_rgbai(r_mult, g_mult, b_mult, 1.f, intensity_mult);
|
|
|
|
if(light_radius > 0.0f && intensity_mult > 0.0f && light_color.i() > 0.0f) {
|
|
// LASER-rendered weapons (the common case: primary bolts) fire far more often
|
|
// than missiles, so excluding them from raytraced-shadow candidacy is a large,
|
|
// cheap cut to the number of lights that pass through that selection each frame.
|
|
light_add_point(&objp->pos, light_radius, light_radius, &light_color, source_radius, wi->render_type == WRT_LASER);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_SHIP:
|
|
{
|
|
TRACE_SCOPE(tracing::ShipPostMove);
|
|
|
|
if ( !physics_paused || (objp==Player_obj) ) {
|
|
ship_process_post( objp, frametime );
|
|
}
|
|
|
|
// Make any electrical arcs on ships cast light
|
|
if (Arc_light) {
|
|
if ( (Detail.lighting > 3) && (objp != Viewer_obj) ) {
|
|
auto shipp = &Ships[objp->instance];
|
|
|
|
for (auto &arc: shipp->electrical_arcs) {
|
|
if ( arc.timestamp.isValid() ) {
|
|
// Move arc endpoints into world coordinates
|
|
vec3d tmp1, tmp2;
|
|
vm_vec_unrotate(&tmp1,&arc.endpoint_1,&objp->orient);
|
|
vm_vec_add2(&tmp1,&objp->pos);
|
|
|
|
vm_vec_unrotate(&tmp2,&arc.endpoint_2,&objp->orient);
|
|
vm_vec_add2(&tmp2,&objp->pos);
|
|
|
|
light_add_point( &tmp1, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f);
|
|
light_add_point( &tmp2, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Check for changing team colors
|
|
ship* shipp = &Ships[objp->instance];
|
|
if (Ship_info[shipp->ship_info_index].uses_team_colors && stricmp(shipp->secondary_team_name.c_str(), "none") != 0) {
|
|
if (f2fl(Missiontime) * 1000 > f2fl(shipp->team_change_timestamp) * 1000 + shipp->team_change_time) {
|
|
shipp->team_name = shipp->secondary_team_name;
|
|
shipp->team_change_timestamp = 0;
|
|
shipp->team_change_time = 0;
|
|
shipp->secondary_team_name = "none";
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_FIREBALL:
|
|
{
|
|
TRACE_SCOPE(tracing::FireballPostMove);
|
|
|
|
if ( !physics_paused )
|
|
fireball_process_post(objp,frametime);
|
|
|
|
if (Detail.lighting > 2) {
|
|
float r = 0.0f, g = 0.0f, b = 0.0f;
|
|
// Make sure the new system works fine.
|
|
Assert(objp->instance > -1);
|
|
Assert(static_cast<int>(Fireballs.size()) > objp->instance);
|
|
|
|
fireball_get_color(Fireballs[objp->instance].fireball_info_index, &r, &g, &b);
|
|
|
|
// we don't cast black light, so just bail in that case
|
|
if ( (r == 0.0f) && (g == 0.0f) && (b == 0.0f) )
|
|
break;
|
|
|
|
// Make explosions cast light
|
|
float p = fireball_lifeleft_percent(objp);
|
|
if (p > 0.0f) {
|
|
if (p > 0.5f)
|
|
p = 1.0f - p;
|
|
|
|
p *= 2.0f;
|
|
float rad = p * (1.0f + frand() * 0.05f) * objp->radius;
|
|
|
|
float intensity = 1.0f;
|
|
if (fireball_is_warp(objp))
|
|
{
|
|
// Make sure the new system works fine.
|
|
Assert(static_cast<int>(Fireballs.size()) > objp->instance);
|
|
Assert(objp->instance > -1);
|
|
intensity = fireball_wormhole_intensity(&Fireballs[objp->instance]); // Valathil: Get wormhole radius for lighting
|
|
rad = objp->radius;
|
|
}
|
|
// P goes from 0 to 1 to 0 over the life of the explosion
|
|
// Only do this if rad is > 0.0000001f
|
|
// TODO: Make fireball source radius configurable, currently sized based on modern subspace portal textures as that will be a very prominent case of it
|
|
if (rad > 0.0001f)
|
|
light_add_point( &objp->pos, rad * 2.0f, rad * 5.0f, intensity, r, g, b,rad * 0.3f);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_SHOCKWAVE:
|
|
// all shockwaves are moved via shockwave_move_all()
|
|
break;
|
|
|
|
case OBJ_DEBRIS:
|
|
{
|
|
TRACE_SCOPE(tracing::DebrisPostMove);
|
|
|
|
if ( !physics_paused )
|
|
debris_process_post(objp, frametime);
|
|
|
|
// Make any electrical arcs on debris cast light
|
|
if (Arc_light) {
|
|
if ( Detail.lighting > 3 ) {
|
|
auto db = &Debris[objp->instance];
|
|
|
|
if (db->arc_frequency > 0) {
|
|
for (auto &arc: db->electrical_arcs) {
|
|
if ( arc.timestamp.isValid() ) {
|
|
// Move arc endpoints into world coordinates
|
|
vec3d tmp1, tmp2;
|
|
vm_vec_unrotate(&tmp1,&arc.endpoint_1,&objp->orient);
|
|
vm_vec_add2(&tmp1,&objp->pos);
|
|
|
|
vm_vec_unrotate(&tmp2,&arc.endpoint_2,&objp->orient);
|
|
vm_vec_add2(&tmp2,&objp->pos);
|
|
|
|
light_add_point( &tmp1, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f );
|
|
light_add_point( &tmp2, 10.0f, 20.0f, frand(), 1.0f, 1.0f, 1.0f );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_ASTEROID:
|
|
{
|
|
TRACE_SCOPE(tracing::AsteroidPostMove);
|
|
|
|
if ( !physics_paused )
|
|
asteroid_process_post(objp);
|
|
|
|
break;
|
|
}
|
|
|
|
case OBJ_WAYPOINT:
|
|
break; // waypoints don't move..
|
|
|
|
case OBJ_GHOST:
|
|
break;
|
|
|
|
case OBJ_OBSERVER:
|
|
void observer_process_post(object *objp);
|
|
observer_process_post(objp);
|
|
break;
|
|
|
|
case OBJ_JUMP_NODE:
|
|
radar_plot_object(objp);
|
|
break;
|
|
|
|
case OBJ_BEAM:
|
|
break;
|
|
|
|
case OBJ_RAW_POF:
|
|
break;
|
|
|
|
case OBJ_PROP:
|
|
break;
|
|
|
|
case OBJ_NONE:
|
|
Int3();
|
|
break;
|
|
|
|
default:
|
|
Error(LOCATION, "Unhandled object type %d in obj_move_all_post\n", objp->type);
|
|
}
|
|
|
|
objp->post_move_event(objp);
|
|
}
|
|
|
|
|
|
int Collisions_enabled = 1;
|
|
|
|
DCF_BOOL( collisions, Collisions_enabled )
|
|
|
|
MONITOR( NumObjects )
|
|
|
|
/**
|
|
* Move all objects for the current frame
|
|
*/
|
|
void obj_move_all(float frametime)
|
|
{
|
|
TRACE_SCOPE(tracing::MoveObjects);
|
|
|
|
object *objp;
|
|
SCP_vector<object*> cmeasure_list;
|
|
const bool global_cmeasure_timer = (Cmeasures_homing_check > 0);
|
|
|
|
Assertion(Cmeasures_homing_check >= 0, "Cmeasures_homing_check is %d in obj_move_all(); it should never be negative. Get a coder!\n", Cmeasures_homing_check);
|
|
|
|
if (global_cmeasure_timer)
|
|
Cmeasures_homing_check--;
|
|
|
|
// Goober5000 - HACK HACK HACK
|
|
// this function also resets the OF_DOCKED_ALREADY_HANDLED flag, to save trips
|
|
// through the used object list
|
|
obj_delete_all_that_should_be_dead();
|
|
|
|
obj_merge_created_list();
|
|
|
|
// Clear the table that tells which groups of weapons have cast light so far.
|
|
if(!(Game_mode & GM_MULTIPLAYER) || (MULTIPLAYER_MASTER)) {
|
|
obj_clear_weapon_group_id_list();
|
|
}
|
|
|
|
MONITOR_INC( NumObjects, Num_objects );
|
|
|
|
for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
|
|
// skip objects which should be dead
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead]) {
|
|
continue;
|
|
}
|
|
|
|
// if this is an observer object, skip it
|
|
if (objp->type == OBJ_OBSERVER) {
|
|
continue;
|
|
}
|
|
|
|
// Compile a list of active countermeasures during an existing traversal of obj_used_list
|
|
if (objp->type == OBJ_WEAPON) {
|
|
weapon *wp = &Weapons[objp->instance];
|
|
weapon_info *wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
if (wip->wi_flags[Weapon::Info_Flags::Cmeasure]) {
|
|
if ((wip->cmeasure_timer_interval > 0 && timestamp_elapsed(wp->cmeasure_timer)) // If it's timer-based and ready to pulse...
|
|
|| (wip->cmeasure_timer_interval <= 0 && global_cmeasure_timer)) { // ...or it's not and the global counter is active...
|
|
// ...then it's actively pulsing and we need to add objp to cmeasure_list.
|
|
cmeasure_list.push_back(objp);
|
|
if (wip->cmeasure_timer_interval > 0) {
|
|
// Reset the timer
|
|
wp->cmeasure_timer = timestamp(wip->cmeasure_timer_interval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
vec3d cur_pos = objp->pos; // Save the current position
|
|
|
|
#ifdef OBJECT_CHECK
|
|
obj_check_object( objp );
|
|
#endif
|
|
|
|
// pre-move
|
|
obj_move_all_pre(objp, frametime);
|
|
|
|
bool interpolation_object = multi_oo_is_interp_object(objp);
|
|
|
|
// store last pos and orient, but only for non-interpolation objects
|
|
// interpolation objects will need to to work backwards from the last good position
|
|
// to prevent collision issues
|
|
if (!interpolation_object){
|
|
objp->last_pos = cur_pos;
|
|
objp->last_orient = objp->orient;
|
|
}
|
|
|
|
// Goober5000 - accommodate objects that aren't supposed to move in some way (at least until they're destroyed)
|
|
bool dont_change_position = objp->flags[Object::Object_Flags::Dont_change_position, Object::Object_Flags::Immobile] && objp->hull_strength > 0.0f;
|
|
bool dont_change_orientation = objp->flags[Object::Object_Flags::Dont_change_orientation, Object::Object_Flags::Immobile] && objp->hull_strength > 0.0f;
|
|
|
|
// skip the physics if we're totally immobile
|
|
if (!dont_change_position || !dont_change_orientation) {
|
|
// if this is an object which should be interpolated in multiplayer, do so
|
|
if (interpolation_object) {
|
|
extern void interpolate_main_helper(int objnum, vec3d* pos, matrix* ori, physics_info* pip, vec3d* last_pos, matrix* last_orient, vec3d* gravity, bool player_ship);
|
|
|
|
interpolate_main_helper(OBJ_INDEX(objp), &objp->pos, &objp->orient, &objp->phys_info, &objp->last_pos, &objp->last_orient, &The_mission.gravity, objp->flags[Object::Object_Flags::Player_ship]);
|
|
} else {
|
|
// physics
|
|
obj_move_call_physics(objp, frametime);
|
|
}
|
|
}
|
|
|
|
// If the object isn't supposed to move, roll back any movement that occurred. Most of the movement should already have been skipped, but this ensures complete immobility.
|
|
if (dont_change_position) {
|
|
objp->pos = objp->last_pos;
|
|
|
|
// make sure velocity is always 0
|
|
vm_vec_zero(&objp->phys_info.vel);
|
|
vm_vec_zero(&objp->phys_info.desired_vel);
|
|
objp->phys_info.speed = 0.0f;
|
|
objp->phys_info.fspeed = 0.0f;
|
|
}
|
|
if (dont_change_orientation) {
|
|
objp->orient = objp->last_orient;
|
|
|
|
// make sure velocity is always 0
|
|
vm_vec_zero(&objp->phys_info.rotvel);
|
|
vm_vec_zero(&objp->phys_info.desired_rotvel);
|
|
}
|
|
|
|
// Submodel movement now happens here, right after physics movement. It's not excluded by the "immobile", "don't-change-position", or "don't-change-orientation" flags.
|
|
|
|
// this flag only affects ship subsystems, not any other type of submodel movement
|
|
if (objp->type == OBJ_SHIP && !Ships[objp->instance].flags[Ship::Ship_Flags::Subsystem_movement_locked])
|
|
ship_move_subsystems(objp);
|
|
|
|
// do animation on this object
|
|
int model_instance_num = object_get_model_instance_num(objp);
|
|
if (model_instance_num >= 0) {
|
|
polymodel_instance* pmi = model_get_instance(model_instance_num);
|
|
animation::ModelAnimation::stepAnimations(frametime, pmi);
|
|
}
|
|
|
|
// finally, do intrinsic motion on this object
|
|
// (this happens last because look_at is a type of intrinsic rotation,
|
|
// and look_at needs to happen last or the angle may be off by a frame)
|
|
model_do_intrinsic_motions(objp);
|
|
|
|
// Future TODO: Props will need a version of this when submodel animation support is added.
|
|
// For ships, we now have to make sure that all the submodel detail levels remain consistent.
|
|
if (objp->type == OBJ_SHIP)
|
|
ship_model_replicate_submodels(objp);
|
|
|
|
// move post
|
|
obj_move_all_post(objp, frametime);
|
|
|
|
// Equipment script processing
|
|
if (objp->type == OBJ_SHIP) {
|
|
ship* shipp = &Ships[objp->instance];
|
|
object* target;
|
|
|
|
if (Ai_info[shipp->ai_index].target_objnum != -1)
|
|
target = &Objects[Ai_info[shipp->ai_index].target_objnum];
|
|
else
|
|
target = NULL;
|
|
if (objp == Player_obj && Player_ai->target_objnum != -1)
|
|
target = &Objects[Player_ai->target_objnum];
|
|
|
|
if (scripting::hooks::OnWeaponEquipped->isActive()) {
|
|
scripting::hooks::OnWeaponEquipped->run(scripting::hooks::WeaponEquippedConditions{ shipp, target },
|
|
scripting::hook_param_list(
|
|
scripting::hook_param("User", 'o', objp),
|
|
scripting::hook_param("Target", 'o', target)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now apply intrinsic motion to things that aren't objects (like skyboxes). This technically doesn't belong in the object code,
|
|
// but there isn't really a good place to put this, it doesn't hurt to have this here, and it's conceptually related to what's here.
|
|
model_do_intrinsic_motions(nullptr);
|
|
|
|
// After all objects have been moved, move all docked objects.
|
|
for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
|
|
// skip objects which should be dead
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead]) {
|
|
continue;
|
|
}
|
|
|
|
dock_move_docked_objects(objp);
|
|
|
|
//Valathil - Move the screen rotation calculation for billboards here to get the updated orientation matrices caused by docking interpolation
|
|
vec3d tangles;
|
|
|
|
tangles.xyz.x = -objp->phys_info.rotvel.xyz.x*frametime;
|
|
tangles.xyz.y = -objp->phys_info.rotvel.xyz.y*frametime;
|
|
tangles.xyz.z = objp->phys_info.rotvel.xyz.z*frametime;
|
|
|
|
// If this is the viewer_object, keep track of the
|
|
// changes in banking so that rotated bitmaps look correct.
|
|
// This is used by the g3_draw_rotated_bitmap function.
|
|
extern physics_info *Viewer_physics_info;
|
|
if ( &objp->phys_info == Viewer_physics_info ) {
|
|
vec3d tangles_r;
|
|
vm_vec_unrotate(&tangles_r, &tangles, &Eye_matrix);
|
|
vm_vec_rotate(&tangles, &tangles_r, &objp->orient);
|
|
|
|
if(objp->dock_list && objp->dock_list->docked_objp->type == OBJ_SHIP && Ai_info[Ships[objp->dock_list->docked_objp->instance].ai_index].submode == AIS_DOCK_4) {
|
|
Physics_viewer_bank -= tangles.xyz.z*0.65f;
|
|
} else {
|
|
Physics_viewer_bank -= tangles.xyz.z;
|
|
}
|
|
|
|
if ( Physics_viewer_bank < 0.0f ){
|
|
Physics_viewer_bank += 2.0f * PI;
|
|
}
|
|
|
|
if ( Physics_viewer_bank > 2.0f * PI ){
|
|
Physics_viewer_bank -= 2.0f * PI;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cmeasure_list.empty())
|
|
find_homing_object_cmeasures(cmeasure_list); // If any cmeasures are active, maybe steer away homing missiles
|
|
|
|
// do pre-collision stuff for beam weapons
|
|
beam_move_all_pre();
|
|
|
|
if ( Collisions_enabled ) {
|
|
TRACE_SCOPE(tracing::CollisionDetection);
|
|
obj_sort_and_collide();
|
|
}
|
|
|
|
turret_swarm_check_validity();
|
|
|
|
// do post-collision stuff for beam weapons
|
|
beam_move_all_post();
|
|
|
|
// Cyborg17 - Update the multi record on multi with these new positions. Clients need to get updated, too.
|
|
if (MULTIPLAYER_MASTER) {
|
|
multi_ship_record_update_all();
|
|
}
|
|
|
|
// update artillery locking info now
|
|
ship_update_artillery_lock();
|
|
|
|
if (Nmodel_instance_num >= 0) {
|
|
animation::ModelAnimation::stepAnimations(frametime, model_get_instance(Nmodel_instance_num));
|
|
}
|
|
|
|
if (Viewer_obj && Viewer_obj->type == OBJ_SHIP && Viewer_obj->instance >= 0) {
|
|
ship* shipp = &Ships[Viewer_obj->instance];
|
|
if (shipp->cockpit_model_instance >= 0) {
|
|
animation::ModelAnimation::stepAnimations(frametime, model_get_instance(shipp->cockpit_model_instance));
|
|
}
|
|
}
|
|
|
|
// mprintf(("moved all objects\n"));
|
|
}
|
|
|
|
|
|
MONITOR( NumObjectsRend )
|
|
|
|
/**
|
|
* Render an object. Calls one of several routines based on type
|
|
*/
|
|
extern int Cmdline_dis_weapons;
|
|
|
|
void obj_render(object *obj)
|
|
{
|
|
model_draw_list render_list;
|
|
|
|
obj_queue_render(obj, &render_list);
|
|
|
|
render_list.init_render();
|
|
render_list.render_all();
|
|
|
|
gr_zbias(0);
|
|
gr_set_cull(0);
|
|
gr_zbuffer_set(ZBUFFER_TYPE_READ);
|
|
gr_set_fill_mode(GR_FILL_MODE_SOLID);
|
|
|
|
gr_clear_states();
|
|
|
|
gr_reset_lighting();
|
|
}
|
|
|
|
void raw_pof_render(object* obj, model_draw_list* scene) {
|
|
model_render_params render_info;
|
|
|
|
auto pof_obj = Pof_objects[obj->instance];
|
|
|
|
uint64_t render_flags = MR_NORMAL | MR_IS_MISSILE | MR_NO_BATCH;
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_light])
|
|
render_flags |= MR_NO_LIGHTING;
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Glowmaps_disabled]) {
|
|
render_flags |= MR_NO_GLOWMAPS;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Draw_as_wireframe]) {
|
|
render_flags |= MR_SHOW_OUTLINE_HTL | MR_NO_POLYS | MR_NO_TEXTURING;
|
|
render_info.set_color(Wireframe_color);
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_full_detail]) {
|
|
render_flags |= MR_FULL_DETAIL;
|
|
}
|
|
|
|
uint debug_flags = render_info.get_debug_flags();
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_diffuse]) {
|
|
debug_flags |= MR_DEBUG_NO_DIFFUSE;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_glowmap]) {
|
|
debug_flags |= MR_DEBUG_NO_GLOW;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_normalmap]) {
|
|
debug_flags |= MR_DEBUG_NO_NORMAL;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_ambientmap]) {
|
|
debug_flags |= MR_DEBUG_NO_AMBIENT;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_specmap]) {
|
|
debug_flags |= MR_DEBUG_NO_SPEC;
|
|
}
|
|
|
|
if (pof_obj.flags[Object::Raw_Pof_Flags::Render_without_reflectmap]) {
|
|
debug_flags |= MR_DEBUG_NO_REFLECT;
|
|
}
|
|
|
|
render_info.set_object_number(OBJ_INDEX(obj));
|
|
|
|
render_info.set_flags(render_flags);
|
|
render_info.set_debug_flags(debug_flags);
|
|
|
|
model_render_queue(&render_info, scene, Pof_objects[obj->instance].model_num, &obj->orient, &obj->pos);
|
|
}
|
|
|
|
void obj_queue_render(object* obj, model_draw_list* scene)
|
|
{
|
|
TRACE_SCOPE(tracing::QueueRender);
|
|
|
|
if ( obj->flags[Object::Object_Flags::Should_be_dead] ) return;
|
|
|
|
if (scripting::hooks::OnObjectRender->isActive()) {
|
|
scripting::api::Current_scene = scene;
|
|
|
|
auto param_list = scripting::hook_param_list(scripting::hook_param("Self", 'o', obj));
|
|
|
|
// Always execute the hook content
|
|
bool skip_render = scripting::hooks::OnObjectRender->isOverride(scripting::hooks::ObjectDrawConditions{ obj }, param_list);
|
|
scripting::hooks::OnObjectRender->run(scripting::hooks::ObjectDrawConditions{ obj }, std::move(param_list));
|
|
|
|
// Clear the render scene context
|
|
scripting::api::Current_scene = nullptr;
|
|
|
|
if (skip_render) {
|
|
// Script said that it want's to skip rendering
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch ( obj->type ) {
|
|
case OBJ_NONE:
|
|
#ifndef NDEBUG
|
|
mprintf(( "ERROR!!!! Bogus obj " PTRDIFF_T_ARG " is rendering!\n", obj-Objects ));
|
|
Int3();
|
|
#endif
|
|
break;
|
|
case OBJ_WEAPON:
|
|
if ( Cmdline_dis_weapons ) return;
|
|
weapon_render(obj, scene);
|
|
break;
|
|
case OBJ_SHIP:
|
|
ship_render(obj, scene);
|
|
break;
|
|
case OBJ_FIREBALL:
|
|
fireball_render(obj, scene);
|
|
break;
|
|
case OBJ_SHOCKWAVE:
|
|
shockwave_render(obj, scene);
|
|
break;
|
|
case OBJ_DEBRIS:
|
|
debris_render(obj, scene);
|
|
break;
|
|
case OBJ_ASTEROID:
|
|
asteroid_render(obj, scene);
|
|
break;
|
|
case OBJ_JUMP_NODE: {
|
|
int objnum = OBJ_INDEX(obj);
|
|
for ( auto &jnp : Jump_nodes ) {
|
|
if ( jnp.GetSCPObjectNumber() != objnum ) {
|
|
continue;
|
|
}
|
|
|
|
jnp.Render(scene, &obj->pos, &Eye_position);
|
|
}
|
|
break;
|
|
}
|
|
case OBJ_WAYPOINT:
|
|
// if (Show_waypoints) {
|
|
// gr_set_color( 128, 128, 128 );
|
|
// g3_draw_sphere_ez( &obj->pos, 5.0f );
|
|
// }
|
|
break;
|
|
case OBJ_GHOST:
|
|
break;
|
|
case OBJ_BEAM:
|
|
break;
|
|
case OBJ_RAW_POF:
|
|
raw_pof_render(obj, scene);
|
|
break;
|
|
case OBJ_PROP:
|
|
prop_render(obj, scene);
|
|
break;
|
|
default:
|
|
Error( LOCATION, "Unhandled obj type %d in obj_render", obj->type );
|
|
}
|
|
}
|
|
|
|
void obj_init_all_ships_physics()
|
|
{
|
|
object *objp;
|
|
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if (objp->type == OBJ_SHIP)
|
|
physics_ship_init(objp);
|
|
}
|
|
|
|
}
|
|
|
|
void obj_observer_move(float frame_time)
|
|
{
|
|
object *objp;
|
|
float ft;
|
|
|
|
// if i'm not in multiplayer, or not an observer, bail
|
|
if(!(Game_mode & GM_MULTIPLAYER) || (Net_player == NULL) || !(Net_player->flags & NETINFO_FLAG_OBSERVER) || (Player_obj->type != OBJ_OBSERVER)){
|
|
return;
|
|
}
|
|
|
|
objp = Player_obj;
|
|
|
|
objp->last_pos = objp->pos;
|
|
objp->last_orient = objp->orient; // save the orientation -- useful in multiplayer.
|
|
|
|
ft = flFrametime;
|
|
obj_move_call_physics( objp, ft );
|
|
obj_move_all_post(objp, frame_time);
|
|
}
|
|
|
|
/**
|
|
* Returns a vector of the average position of all ships in the mission.
|
|
*/
|
|
void obj_get_average_ship_pos( vec3d *pos )
|
|
{
|
|
int count;
|
|
|
|
vm_vec_zero( pos );
|
|
|
|
// average up all ship positions
|
|
count = 0;
|
|
for (auto so: list_range(&Ship_obj_list)) {
|
|
auto objp = &Objects[so->objnum];
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
vm_vec_add2( pos, &objp->pos );
|
|
count++;
|
|
}
|
|
|
|
if ( count )
|
|
vm_vec_scale( pos, 1.0f/(float)count );
|
|
}
|
|
|
|
/**
|
|
* Return the team for the object passed as a parameter
|
|
*
|
|
* @param objp Pointer to object that you want team for
|
|
*
|
|
* @return enumerated team on success
|
|
* @return -1 on failure (for objects that don't have teams)
|
|
*/
|
|
int obj_team(object *objp)
|
|
{
|
|
Assert( objp != NULL );
|
|
int team = -1;
|
|
|
|
switch ( objp->type ) {
|
|
case OBJ_SHIP:
|
|
Assert( objp->instance >= 0 && objp->instance < MAX_SHIPS );
|
|
team = Ships[objp->instance].team;
|
|
break;
|
|
|
|
case OBJ_DEBRIS:
|
|
team = debris_get_team(objp);
|
|
Assertion(team != -1, "Obj_team called for a debris object with no team.");
|
|
break;
|
|
|
|
/* case OBJ_CMEASURE:
|
|
Assert( objp->instance >= 0 && objp->instance < MAX_CMEASURES);
|
|
team = Cmeasures[objp->instance].team;
|
|
break;
|
|
*/
|
|
case OBJ_WEAPON:
|
|
Assert( objp->instance >= 0 && objp->instance < MAX_WEAPONS );
|
|
team = Weapons[objp->instance].team;
|
|
break;
|
|
|
|
case OBJ_JUMP_NODE:
|
|
team = Player_ship->team;
|
|
break;
|
|
|
|
case OBJ_FIREBALL:
|
|
case OBJ_WAYPOINT:
|
|
case OBJ_START:
|
|
case OBJ_NONE:
|
|
case OBJ_GHOST:
|
|
case OBJ_SHOCKWAVE:
|
|
case OBJ_BEAM:
|
|
case OBJ_RAW_POF:
|
|
case OBJ_PROP:
|
|
team = -1;
|
|
break;
|
|
|
|
case OBJ_ASTEROID:
|
|
team = Iff_traitor;
|
|
break;
|
|
|
|
default:
|
|
Int3(); // can't happen
|
|
break;
|
|
} // end switch
|
|
|
|
Assertion(team != -1, "Obj_team called for a object of type %s with no team.", Object_type_names[objp->type]);
|
|
return team;
|
|
}
|
|
|
|
/**
|
|
* Removes any occurances of object 'a' from
|
|
* the pairs list.
|
|
*/
|
|
extern int Num_pairs;
|
|
|
|
/**
|
|
* Reset all collisions
|
|
*/
|
|
void obj_reset_all_collisions()
|
|
{
|
|
// clear checkobjects
|
|
#ifndef NDEBUG
|
|
for (int i = 0; i < MAX_OBJECTS; ++i) {
|
|
CheckObjects[i] = checkobject();
|
|
}
|
|
#endif
|
|
|
|
// clear object pairs
|
|
obj_reset_colliders();
|
|
|
|
// now add every object back into the object collision pairs
|
|
for (auto moveup: list_range(&obj_used_list)) {
|
|
if (moveup->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
// he's not in the collision list
|
|
moveup->flags.set(Object::Object_Flags::Not_in_coll);
|
|
|
|
// recalc pairs for this guy
|
|
obj_add_collider(OBJ_INDEX(moveup));
|
|
}
|
|
}
|
|
|
|
// Goober5000
|
|
int object_is_docked(object *objp)
|
|
{
|
|
return (objp->dock_list != NULL);
|
|
}
|
|
|
|
// Goober5000
|
|
int object_is_dead_docked(object *objp)
|
|
{
|
|
return (objp->dead_dock_list != NULL);
|
|
}
|
|
|
|
/**
|
|
* Makes an object start 'gliding'
|
|
*
|
|
* It will continue on the same velocity that it was going,
|
|
* regardless of orientation -WMC
|
|
*/
|
|
void object_set_gliding(object *objp, bool enable, bool force)
|
|
{
|
|
Assert(objp != NULL);
|
|
|
|
if(enable) {
|
|
if (!force) {
|
|
objp->phys_info.flags |= PF_GLIDING;
|
|
} else {
|
|
objp->phys_info.flags |= PF_FORCE_GLIDE;
|
|
}
|
|
} else {
|
|
if (!force) {
|
|
objp->phys_info.flags &= ~PF_GLIDING;
|
|
} else {
|
|
objp->phys_info.flags &= ~PF_FORCE_GLIDE;
|
|
}
|
|
vm_vec_rotate(&objp->phys_info.prev_ramp_vel, &objp->phys_info.vel, &objp->orient); //Backslash
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return whether an object is gliding -WMC
|
|
*/
|
|
bool object_get_gliding(object *objp)
|
|
{
|
|
Assert(objp != NULL);
|
|
|
|
return ( ((objp->phys_info.flags & PF_GLIDING) != 0) || ((objp->phys_info.flags & PF_FORCE_GLIDE) != 0));
|
|
}
|
|
|
|
bool object_glide_forced(object *objp)
|
|
{
|
|
return (objp->phys_info.flags & PF_FORCE_GLIDE) != 0;
|
|
}
|
|
|
|
/**
|
|
* Quickly finds an object by its signature
|
|
*/
|
|
int obj_get_by_signature(int sig)
|
|
{
|
|
Assert(sig > 0);
|
|
|
|
for (auto objp: list_range(&obj_used_list))
|
|
{
|
|
// don't skip over should-be-dead objects, since we assume we know what we're doing
|
|
if (objp->signature == sig)
|
|
return OBJ_INDEX(objp);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Gets the model number for this object, or -1 if none
|
|
*/
|
|
int object_get_model_num(const object *objp)
|
|
{
|
|
switch(objp->type)
|
|
{
|
|
case OBJ_ASTEROID:
|
|
{
|
|
asteroid *asp = &Asteroids[objp->instance];
|
|
return Asteroid_info[asp->asteroid_type].subtypes[asp->asteroid_subtype].model_number;
|
|
}
|
|
case OBJ_DEBRIS:
|
|
{
|
|
debris *debrisp = &Debris[objp->instance];
|
|
return debrisp->model_num;
|
|
}
|
|
case OBJ_SHIP:
|
|
{
|
|
ship *shipp = &Ships[objp->instance];
|
|
return Ship_info[shipp->ship_info_index].model_num;
|
|
}
|
|
case OBJ_WEAPON:
|
|
{
|
|
weapon *wp = &Weapons[objp->instance];
|
|
return Weapon_info[wp->weapon_info_index].model_num;
|
|
}
|
|
case OBJ_RAW_POF:
|
|
return Pof_objects[objp->instance].model_num;
|
|
case OBJ_PROP:
|
|
return Prop_info[objp->instance].model_num;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Gets the model for this object, or nullptr if none
|
|
*/
|
|
polymodel *object_get_model(const object *objp)
|
|
{
|
|
int model_num = object_get_model_num(objp);
|
|
return model_num >= 0 ? model_get(model_num) : nullptr;
|
|
}
|
|
|
|
/**
|
|
* Gets the model instance number for this object, or -1 if none
|
|
*/
|
|
int object_get_model_instance_num(const object *objp)
|
|
{
|
|
if (objp == nullptr)
|
|
return -1;
|
|
|
|
switch (objp->type)
|
|
{
|
|
case OBJ_ASTEROID:
|
|
{
|
|
asteroid *asp = &Asteroids[objp->instance];
|
|
return asp->model_instance_num;
|
|
}
|
|
case OBJ_DEBRIS:
|
|
{
|
|
debris *debrisp = &Debris[objp->instance];
|
|
return debrisp->model_instance_num;
|
|
}
|
|
case OBJ_SHIP:
|
|
{
|
|
ship *shipp = &Ships[objp->instance];
|
|
return shipp->model_instance_num;
|
|
}
|
|
case OBJ_WEAPON:
|
|
{
|
|
weapon *wp = &Weapons[objp->instance];
|
|
return wp->model_instance_num;
|
|
}
|
|
case OBJ_JUMP_NODE:
|
|
{
|
|
CJumpNode* jnp = jumpnode_get_by_objnum(OBJ_INDEX(objp));
|
|
Assertion(jnp != nullptr, "Could not find jump node!");
|
|
return jnp->GetPolymodelInstanceNum();
|
|
}
|
|
case OBJ_RAW_POF:
|
|
return Pof_objects[objp->instance].model_instance;
|
|
case OBJ_PROP:
|
|
return prop_id_lookup(objp->instance)->model_instance_num;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Gets the model instance for this object, or nullptr if none
|
|
*/
|
|
polymodel_instance *object_get_model_instance(const object *objp)
|
|
{
|
|
int model_instance_num = object_get_model_instance_num(objp);
|
|
return model_instance_num >= 0 ? model_get_instance(model_instance_num) : nullptr;
|
|
}
|
|
|
|
bool obj_compare(object* left, object* right) {
|
|
if (left == right) {
|
|
// Same pointer
|
|
return true;
|
|
}
|
|
if (left == nullptr || right == nullptr) {
|
|
// Only one is nullptr and the other is not (since they are not equal)
|
|
return false;
|
|
}
|
|
|
|
return OBJ_INDEX(left) == OBJ_INDEX(right);
|
|
}
|
|
|
|
void physics_populate_snapshot(physics_snapshot& snapshot, const object* objp)
|
|
{
|
|
Assertion(objp != nullptr, "Bad object (nullptr) passed to physics_overwrite_snapshot, please report to the SCP!");
|
|
|
|
snapshot.position = objp->pos;
|
|
snapshot.orientation = objp->orient;
|
|
snapshot.velocity = objp->phys_info.vel;
|
|
snapshot.desired_velocity = objp->phys_info.desired_vel;
|
|
snapshot.rotational_velocity = objp->phys_info.rotvel;
|
|
snapshot.desired_rotational_velocity = objp->phys_info.desired_rotvel;
|
|
}
|
|
|
|
void physics_apply_pstate_to_object(object* objp, const physics_snapshot& source)
|
|
{
|
|
Assertion(objp != nullptr, "Bad object passed to phsyics snapshot application code. This is a coder mistake, please report!");
|
|
|
|
objp->pos = source.position;
|
|
objp->orient = source.orientation;
|
|
objp->phys_info.vel = source.velocity;
|
|
objp->phys_info.desired_vel = source.desired_velocity;
|
|
objp->phys_info.rotvel = source.rotational_velocity;
|
|
objp->phys_info.desired_rotvel = source.desired_rotational_velocity;
|
|
}
|
|
|