Compare commits

..
Author SHA1 Message Date
SirKnightly 9a07486866 Automated build commit 2021-05-03 02:32:09 +00:00
713 changed files with 23327 additions and 66999 deletions

No files matched your search

-4
View File
@@ -1,8 +1,6 @@
cmake/ @asarium
ci/ @asarium @z64555
code/executor/ @asarium
code/scpui/ @asarium
@@ -10,5 +8,3 @@ code/scpui/ @asarium
code/scripting/ @asarium
code/controlconfig/ @z64555
code/model/modelanimation*.* @bmagnu
+1 -94
View File
@@ -15,7 +15,7 @@ jobs:
configuration: [FastDebug, Release]
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-8a6bbbe
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -210,96 +210,3 @@ jobs:
DATACORDER_USER: ${{ secrets.DATACORDER_USER }}
DATACORDER_SSHPASS: ${{ secrets.DATACORDER_PASSWORD }}
run: $GITHUB_WORKSPACE/ci/linux/upload_dist_package.sh nightly
build_mac:
strategy:
matrix:
configuration: [FastDebug, Release]
compiler: [clang]
arch: [x86_64]
name: Mac
runs-on: macos-latest
steps:
- name: Cache Qt
id: cache-qt-mac
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/../Qt
key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
cached: ${{ steps.cache-qt-mac.outputs.cache-hit }}
setup-python: 'false'
aqtversion: ==1.1.3
- uses: actions/checkout@v1
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Set up test version
shell: bash
run: |
[[ "${{ github.ref }}" =~ ^refs\/heads\/test\/(.*)$ ]]
# Override the revision string so that the builds are named correctly
echo "set(FSO_VERSION_REVISION_STR ${BASH_REMATCH[1]})" > "version_override.cmake"]
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
ARCHITECTURE: ${{ matrix.arch }}
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja all
- name: Run Tests
working-directory: ./build
env:
CONFIGURATION: ${{ matrix.configuration }}
XDG_RUNTIME_DIR: /root
run: $GITHUB_WORKSPACE/ci/linux/run_tests.sh
- name: Package build result
working-directory: ./build
run: cd bin && tar -cvzf macos-build.tar *.app
- name: Upload build result
uses: actions/upload-artifact@v2
with:
name: mac-${{ matrix.configuration }}
path: ${{ github.workspace }}/build/bin/macos-build.tar
mac_zip:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
steps:
- uses: actions/checkout@v2
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Download Release builds
uses: actions/download-artifact@v2
with:
name: mac-Release
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v2
with:
name: mac-FastDebug
path: builds
- name: Create Distribution package
working-directory: ./builds
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Mac
- name: Upload result package
working-directory: ./builds
env:
INDIEGAMES_USER: ${{ secrets.INDIEGAMES_USER }}
INDIEGAMES_SSHPASS: ${{ secrets.INDIEGAMES_PASSWORD }}
DATACORDER_USER: ${{ secrets.DATACORDER_USER }}
DATACORDER_SSHPASS: ${{ secrets.DATACORDER_PASSWORD }}
run: $GITHUB_WORKSPACE/ci/linux/upload_dist_package.sh nightly
+49 -104
View File
@@ -16,17 +16,6 @@ jobs:
- name: Create Release
id: create_release
uses: actions/create-release@v1
# Creates a release package page on github
# create_release.inputs: (notable inputs)
# allowUpdates=false if true, update an existing release if it exists
# artifactErrorsFailBuild=false if true, fails the build should artifact read/write errors occur
# artifacts optional set of paths to artifacts to upload to the release. comma delimited.
# removeArtifacts=false if true, remove existing artifacts
# replaceArtifacts=false if true, replace/overwrite existing artifacts
# create_release.outputs:
# id id of the created release
# html_url HTML URL of the release
# upload_url FTP URL of the release where assets are uploaded
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
@@ -34,53 +23,32 @@ jobs:
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Store upload URL
run: |
echo -n "${{ steps.create_release.outputs.upload_url }}" > upload_url
- name: Persist upload URL
uses: actions/upload-artifact@v2
with:
name: upload_url
path: ${{ github.workspace }}/upload_url
build_linux:
strategy:
matrix:
# Run this job for each configuration
# The current config running is accesible with `matrix.configuration`
configuration: [FastDebug, Release]
name: Linux
needs: create_release # Don't run this job until create_release is done and successful
needs: create_release
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-8a6bbbe
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
steps:
- name: Cache Qt
id: cache-qt-lin
uses: actions/cache@v1
# cache the Qt directory so we don't have to reinstall it every time
# cache.inputs:
# path - A list of files, directories, and wildcard patterns to cache and restore. See @actions/glob for supported patterns.
# key - An explicit key for restoring and saving the cache
# restore-keys - An ordered list of keys to use for restoring the cache if no cache hit occurred for key
# cache.outputs:
# cache-hit true when an exact match was found for the key, false otherwise.
with:
path: ${{ github.workspace }}/../Qt
key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
- name: Install Qt
uses: jurplel/install-qt-action@v2
# installs Qt in the action workflow
# install-qt-action.inputs:
# version desired qt version to install
# host Host platform of Qt, default to platform used by runs-on
# target target platform of Qt, default to desktop
# arch target architecture of Qt, only used for windows or android builds
# dir root dir Qt will be installed to
# install-deps if true, auto-install dependencies on linux. Default true
# modules whitespace delimited list string of additional addon modules to install
# archives whitespace delimited list string of Qt archives to install
# cached if true, Don't download Qt but set ENVARs; use with cache.outputs.cache-hit
# setup-python Set to false to skip using setup-python to find/download a valid python version
# set-env if true, set ENVARs; default = false
# aqtversion version of aqtinstall to use; default = 2.0.0
# py7zrversion version of py7zr to use; default = 0.16.1
# extra additional arguments to pass to aqinstall
# ENVARS set:
# Qt5_DIR / Qt6_DIR
# Qt bin dir appended to PATH
# QT_PLUGIN_PATH, QML2_IMPORT_PATH, PKG_CONFIG_PATH, LD_LIBRARY_PATH
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
@@ -89,10 +57,9 @@ jobs:
aqtversion: ==1.1.3
- uses: actions/checkout@v1
# checks-out your repository under $GITHUB_WORKSPACE, so your workflow can access it.
name: Checkout
with:
submodules: true # `true` to checkout submodules, `recursive` to recursively checkout submodules
submodules: true
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
@@ -117,15 +84,6 @@ jobs:
run: $GITHUB_WORKSPACE/ci/linux/generate_appimage.sh $GITHUB_WORKSPACE/build/install
- name: Upload build result
uses: actions/upload-artifact@v2
# Upload the url file for use with other runners
# upload-artifact.inputs:
# if-no-files-found=warn What to do if path fails to find any files.
# warn Display a warning but do not fail the action
# error Display an error and fail the action
# ignore No display, No fail
# retention-days=0 Days to keep the artifact.
# 0 Use default retention policy of the repo
# 1~90 Min 1, Max 90 days to keep the artifact unless changed by repo settings page
with:
name: linux-${{ matrix.configuration }}
path: ${{ github.workspace }}/build/install/*.AppImage
@@ -139,37 +97,38 @@ jobs:
name: Checkout
with:
submodules: true
fetch-depth: '0' # value 0 to fetch all history and tags for all branches
fetch-depth: '0'
- name: Download upload URL
uses: actions/download-artifact@v1
with:
name: upload_url
path: .
- name: Extract upload URL
id: extractUploadUrl
run: echo "::set-output name=upload_url::$(cat upload_url)"
- name: Download Release builds
# Grab the release builds
uses: actions/download-artifact@v2
uses: actions/download-artifact@v1
with:
name: linux-Release
path: builds
- name: Download FastDebug builds
# Grab the debug builds
uses: actions/download-artifact@v2
with:
name: linux-FastDebug
path: builds
- name: Create Distribution package
# Zip both builds together
id: generate_package
working-directory: ./builds
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Linux
- name: Upload result package
# Stash the result to artifact filespace
uses: actions/upload-artifact@v2
with:
name: ${{ steps.generate_package.outputs.package_name }}
path: ${{ steps.generate_package.outputs.package_path }}
- name: Publish result package
# publish the zipped result
uses: softprops/action-gh-release@v1
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: ${{ steps.generate_package.outputs.package_path }}
upload_url: ${{ steps.extractUploadUrl.outputs.upload_url }}
asset_path: ${{ steps.generate_package.outputs.package_path }}
asset_name: ${{ steps.generate_package.outputs.package_name }}
asset_content_type: ${{ steps.generate_package.outputs.package_mime }}
build_windows:
strategy:
@@ -182,19 +141,16 @@ jobs:
runs-on: windows-2019
steps:
- uses: actions/checkout@v1
# Checkout repo
name: Checkout
with:
submodules: true
- name: Cache Qt
# Cache Qt
id: cache-qt-win
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/../Qt
key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
- name: Install Qt (32 bit)
# Install Qt 32bit if matrix.arch == Win32
uses: jurplel/install-qt-action@v2
if: ${{ matrix.arch == 'Win32' }}
with:
@@ -204,7 +160,6 @@ jobs:
cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
aqtversion: ==0.8
- name: Install Qt (64 bit)
# Install Qt 64bit if matrix.arch == x64
uses: jurplel/install-qt-action@v2
if: ${{ matrix.arch == 'x64' }}
with:
@@ -249,37 +204,39 @@ jobs:
with:
name: windows-${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}
path: ${{ github.workspace }}/build/install/*
windows_zip:
name: Build Windows distribution zip
needs: build_windows
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
strategy:
fail-fast: false # Run the other jobs in the matrix instead of failing them
matrix:
arch: [Win32, x64]
simd: [SSE2, AVX]
steps:
- uses: actions/checkout@v1
name: Checkout
with:
submodules: true
fetch-depth: '0'
- name: Download upload URL
uses: actions/download-artifact@v1
with:
name: upload_url
path: .
- name: Extract upload URL
id: extractUploadUrl
run: echo "::set-output name=upload_url::$(cat upload_url)"
- name: Download Release builds
uses: actions/download-artifact@v2
uses: actions/download-artifact@v1
with:
name: windows-Release-${{ matrix.arch }}-${{ matrix.simd }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v2
with:
name: windows-FastDebug-${{ matrix.arch }}-${{ matrix.simd }}
path: builds
- name: Create Distribution package
id: generate_package
working-directory: ./builds
@@ -288,33 +245,21 @@ jobs:
ARCH: ${{ matrix.arch }}
SIMD: ${{ matrix.simd }}
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Windows
- name: Upload result package
uses: actions/upload-artifact@v2
with:
name: ${{ steps.generate_package.outputs.package_name }}
path: ${{ steps.generate_package.outputs.package_path }}
- name: Upload debug package
uses: actions/upload-artifact@v2
with:
name: ${{ steps.generate_package.outputs.debug_name }}
path: ${{ steps.generate_package.outputs.debug_path }}
- name: Publish packages
uses: softprops/action-gh-release@v1
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
${{ steps.generate_package.outputs.package_path }}
${{ steps.generate_package.outputs.debug_path }}
post_build:
name: Post builds to Nebula and Forum
needs: [linux_zip, windows_zip]
uses: ./.github/workflows/post-build-release.yaml
with:
linux_result: ${{ needs.linux_zip.result }}
windows_result: ${{ needs.windows_zip.result }}
releaseTag: ${{ github.ref_name }}
upload_url: ${{ steps.extractUploadUrl.outputs.upload_url }}
asset_path: ${{ steps.generate_package.outputs.package_path }}
asset_name: ${{ steps.generate_package.outputs.package_name }}
asset_content_type: ${{ steps.generate_package.outputs.package_mime }}
- name: Upload debug symbols
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.extractUploadUrl.outputs.upload_url }}
asset_path: ${{ steps.generate_package.outputs.debug_path }}
asset_name: ${{ steps.generate_package.outputs.debug_name }}
asset_content_type: ${{ steps.generate_package.outputs.debug_mime }}
+1 -93
View File
@@ -15,7 +15,7 @@ jobs:
configuration: [FastDebug, Release]
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-8a6bbbe
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -224,95 +224,3 @@ jobs:
DATACORDER_USER: ${{ secrets.DATACORDER_USER }}
DATACORDER_SSHPASS: ${{ secrets.DATACORDER_PASSWORD }}
run: $GITHUB_WORKSPACE/ci/linux/upload_dist_package.sh test
build_mac:
strategy:
matrix:
configuration: [FastDebug, Release]
compiler: [clang]
arch: [x86_64]
name: Mac
runs-on: macos-latest
steps:
- name: Cache Qt
id: cache-qt-mac
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/../Qt
key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
cached: ${{ steps.cache-qt-mac.outputs.cache-hit }}
setup-python: 'false'
aqtversion: ==1.1.3
- uses: actions/checkout@v1
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Set up test version
shell: bash
run: |
[[ "${{ github.ref }}" =~ ^refs\/heads\/test\/(.*)$ ]]
# Override the revision string so that the builds are named correctly
echo "set(FSO_VERSION_REVISION_STR ${BASH_REMATCH[1]})" > "version_override.cmake"
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
ARCHITECTURE: ${{ matrix.arch }}
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja all
- name: Run Tests
working-directory: ./build
env:
CONFIGURATION: ${{ matrix.configuration }}
XDG_RUNTIME_DIR: /root
run: $GITHUB_WORKSPACE/ci/linux/run_tests.sh
- name: Package build result
working-directory: ./build
run: cd bin && tar -cvzf macos-build.tar *.app
- name: Upload build result
uses: actions/upload-artifact@v2
with:
name: mac-${{ matrix.configuration }}
path: ${{ github.workspace }}/build/bin/macos-build.tar
mac_zip:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
steps:
- uses: actions/checkout@v2
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Download Release builds
uses: actions/download-artifact@v2
with:
name: mac-Release
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v2
with:
name: mac-FastDebug
path: builds
- name: Create Distribution package
working-directory: ./builds
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Mac
- name: Upload result package
working-directory: ./builds
env:
INDIEGAMES_USER: ${{ secrets.INDIEGAMES_USER }}
INDIEGAMES_SSHPASS: ${{ secrets.INDIEGAMES_PASSWORD }}
DATACORDER_USER: ${{ secrets.DATACORDER_USER }}
DATACORDER_SSHPASS: ${{ secrets.DATACORDER_PASSWORD }}
run: $GITHUB_WORKSPACE/ci/linux/upload_dist_package.sh test
-62
View File
@@ -1,62 +0,0 @@
name: Build caches for master
on:
push:
branches:
- master
env:
QT_VERSION: 5.12.9
jobs:
build_linux:
strategy:
matrix:
configuration: [Debug, Release]
compiler: [gcc-5, gcc-10]
cmake_options: [""]
include:
# Also include configurations that check if the code compiles without the graphics backends
- configuration: Debug
compiler: gcc-10
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
- configuration: Release
compiler: gcc-10
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-8a6bbbe
steps:
- name: Cache Qt
id: cache-qt-lin
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/../Qt
key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
setup-python: 'false'
aqtversion: ==1.1.3
- uses: actions/checkout@v1
name: Checkout
with:
submodules: true
- name: ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja -k 20 all
-69
View File
@@ -1,69 +0,0 @@
name: Post Release builds
on:
workflow_call:
# Trigger when called by other workflows
inputs:
linux_result: # job.result; either success, failure, canceled or skipped
required: true
type: string
windows_result: # job.result
required: true
type: string
releaseTag:
required: true
type: string
workflow_dispatch:
# Trigger manually from Github
inputs:
releaseTag:
description: Release Tag
required: true
type: string
env:
GITHUB_REPO: ${{ github.repository }} # repo this workflow was called from
INDIEGAMES_USER: ${{ secrets.INDIEGAMES_USER }}
INDIEGAMES_SSHPASS: ${{ secrets.INDIEGAMES_PASSWORD }}
NEBULA_USER: ${{ secrets.NEBULA_USER }}
NEBULA_PASSWORD: ${{ secrets.NEBULA_PASSWORD }}
HLP_API: ${{ secrets.HLP_API }}
HLP_KEY: ${{ secrets.HLP_KEY }}
RELEASE_TAG: ${{ inputs.releaseTag }}
jobs:
post_builds:
name: Post builds on Nebula and the forums
runs-on: ubuntu-latest
steps:
- name: Caller
# Name of what called this yaml. Used to debug auto/manual step being triggered
run: echo "github.event_name= ${{ github.event_name }}"
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: '0'
ref: ${{ env.RELEASE_TAG }}
- name: Install Python dependencies
run: pip install -r ci/post/requirements.txt
- name: Post Builds (Auto trigger)
if: ${{ (github.event_name == 'workflow_call') || (github.event_name == 'push') }}
env:
LINUX_RESULT: ${{ inputs.linux_result }}
WINDOWS_RESULT: ${{ inputs.windows_result }}
run: python ci/post/main.py release
- name: Post Builds (Manual trigger)
if: ${{ github.event_name == 'workflow_dispatch' }}
# assume user knows what they're doing...
env:
LINUX_RESULT: success
WINDOWS_RESULT: success
run: |
echo "releaseTag= ${{ env.RELEASE_TAG }}"
echo "repository= ${{ github.repository }}"
python ci/post/main.py release
@@ -22,7 +22,7 @@ jobs:
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-8a6bbbe
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -44,11 +44,6 @@ jobs:
with:
submodules: true
fetch-depth: 0
- name: ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
@@ -156,64 +151,3 @@ jobs:
else
"./bin/$CONFIGURATION/unittests" --gtest_shuffle
fi
build_mac:
strategy:
matrix:
configuration: [Debug, Release]
compiler: [clang]
arch: [x86_64]
cmake_options: [""]
name: Mac
runs-on: macos-latest
steps:
- name: Cache Qt
id: cache-qt-mac
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/../Qt
key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
cached: ${{ steps.cache-qt-mac.outputs.cache-hit }}
setup-python: 'false'
aqtversion: ==1.1.3
- uses: actions/checkout@v1
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
ARCHITECTURE: ${{ matrix.arch }}
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja all
- name: Run Tests
working-directory: ./build
env:
CONFIGURATION: ${{ matrix.configuration }}
XDG_RUNTIME_DIR: /root
run: $GITHUB_WORKSPACE/ci/linux/run_tests.sh
- name: Run Clang Tidy
# Clang-tidy reuses the precompiled headers so this only makes sense for the clang compilers
if: startsWith(matrix.compiler, 'clang-')
run: $GITHUB_WORKSPACE/ci/linux/clang_tidy.sh ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}
- name: Process clang-tidy warnings
if: startsWith(matrix.compiler, 'clang-')
uses: asarium/clang-tidy-action@v1
with:
fixesFile: clang-fixes.yaml
@@ -8,7 +8,6 @@ on:
jobs:
build_images:
if: github.repository == 'scp-fs2open/fs2open.github.com'
strategy:
matrix:
configuration: [FastDebug, Release]
+1 -2
View File
@@ -43,8 +43,7 @@ PROJECT(FS2_Open)
find_program(CCACHE_PROGRAM ccache)
# Currently only GCC support ccache properly without generating additional warnings
# Enable ccache on Mac even for clang
if(CCACHE_PROGRAM AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR APPLE))
if(CCACHE_PROGRAM AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
if(NOT CMAKE_C_COMPILER_LAUNCHER)
message(STATUS "Using ccache for C compilation: ${CCACHE_PROGRAM}")
SET(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
-51
View File
@@ -3,52 +3,6 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [22.0.0] - 2022-04-01 ([Thread](https://www.hard-light.net/forums/index.php?topic=98125.0))
### Changed
- Controls5 PR (aka multi-joy)
- Timing system upgrade
- Model animation code upgrades
## [21.4.0] - 2021-09-27 ([Thread](https://www.hard-light.net/forums/index.php?topic=97802.0))
### Changed
- Multiple features; see thread
- Multiple bugfixes; see thread
### Deprecations
- `script-eval` has been deprecated in favor of `script-eval-block`
- renamed `hud-set-retail-gauge-active` to `hud-set-builtin-gauge-active`
## [21.2.0] - 2021-05-02 ([Thread](https://www.hard-light.net/forums/index.php?topic=97561.0))
### Changed
- Added a dynamic action system for more modder control of effects
- Removed fireball limit
- Fixed bomb intercept in multiplayer
- Changed nebula fog distance from a linear to exponential model
- Turrets on moving subobjects enabled
- LZ4 Compression added for game files
## [21.0.0] - 2021-01-27 ([Thread](https://www.hard-light.net/forums/index.php?topic=97293.0))
### Changed
- More OpenGL optimizations
- Full type information output for Lua documentation. Very useful for writing Lua scripts.
- A new -weaponspew command-line option for printing MediaVP-style statistics, plus a weapon comparison spreadsheet, to the debug log.
- Improved ship lookup behavior in SEXPs for improved performance.
- BPTC/BC7 texture compression support
- Refactoring and new features for the ship lab.
- Missile multi-lock. It's finally in an official build!
- Enhancements to the model code in preparation for cool features in 21.2.
- IPv6 support for multiplayer
- A lot of general fixes for multiplayer.
### Deprecated
- Dropped support for Windows XP
- Scripting:
- `ba.getFrametime()`: The parameter value was used incorrectly inside the implementation. To avoid breaking existing scripts and to improve readability this has been split into `ba.getMissionFrametime()` and `ba.getRealFrametime()`.
- `gr.drawMonochromeImage()`: `gr.drawImage()` got a new parameter for drawing monochrome images and is more flexible in general so the monochrome variant is no longer needed.
## [19.0.0] - 2020-01-25 ([Thread](https://www.hard-light.net/forums/index.php?topic=96226.0))
### Meta
- With this release we decided to drop the "`3.Major_revision.Minor_revision`" versioning scheme in favor of a year based scheme since the Major and Minor versions did not have much meaning anymore. Instead the scheme will now be "`<year>.<number that is incremented every release>.0`". The last 0 is still there because some of our systems expect that. It will be gone at some point.
@@ -71,7 +25,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed
- Refactored bitmap slot handling and removed the fixed upper limit on the number of bitmaps. No more bmpman corruption!
## [3.8.0] - 2017-08-22 ([Release Thread](https://www.hard-light.net/forums/index.php?topic=93812.0))
### Added
- Support for APNG animations: [APNG](https://en.wikipedia.org/wiki/APNG) is a variant of the established PNG format which allows to store animations instead of single frames in a PNG file. This change allows to use these animation files in FSO which significantly improves usability for modders since they no longer have to store all individual frames as separate images.
@@ -96,7 +49,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed
- "Tons" of bugfixes
## [3.7.2] - 2015-04-23 ([Release thread](http://www.hard-light.net/forums/index.php?topic=89597.0))
### Added
- MediaVPS 2014 Support
@@ -127,19 +79,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Raised the per-model debris limit to 48 (previously 32).
- Increased the per-frame debris limit to 96 (previously 64)
## [3.6.18] - 2013-03-01 ([Release thread](http://www.hard-light.net/forums/index.php?topic=83889.0))
### Fixed
- Critical damage bug which caused heavy balance issues in the game
## [3.6.16] - 2013-01-31 ([Release thread](http://www.hard-light.net/forums/index.php?topic=83577.0))
### Added
- Diaspora TC support
### Changed
- Performance Improvements
## [3.6.14] - 2012-10-23 - ([Release thread](https://www.hard-light.net/forums/index.php?topic=82648.0))
### Added
- Objecttype tables support for deciding how enemies respond to ship classes when disabled
+1 -1
View File
@@ -19,5 +19,5 @@ BASE_COMMIT=$(git merge-base $1 $2)
echo "Running clang-tidy on changed files"
git diff -U0 --no-color "$BASE_COMMIT..$2" | \
$HERE/clang-tidy-diff.py -path "$(pwd)/build" -p1 \
-regex '(code(?!((\/graphics\/shaders\/compiled)|(\/globalincs\/windebug)))|freespace2|qtfred|test|build|tools)\/.*\.(cpp|h)' \
-regex '(code(?!\/graphics\/shaders\/compiled)|freespace2|qtfred|test|build|tools)\/.*\.(cpp|h)' \
-clang-tidy-binary /usr/bin/clang-tidy-9 -j$(nproc) -export-fixes "$(pwd)/clang-fixes.yaml"
+4 -15
View File
@@ -15,16 +15,8 @@ if [ "$COMPILER" = "clang-9" ]; then
fi
LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH
if [ "$RUNNER_OS" = "macOS" ]; then
CXXFLAGS="-mtune=generic -pipe -Wno-unknown-pragmas"
CFLAGS="-mtune=generic -pipe -Wno-unknown-pragmas"
PLATFORM_CMAKE_OPTIONS=""
export CMAKE_OSX_ARCHITECTURES="$ARCHITECTURE"
else
CXXFLAGS="-m64 -mtune=generic -mfpmath=sse -msse -msse2 -pipe -Wno-unknown-pragmas"
CFLAGS="-m64 -mtune=generic -mfpmath=sse -msse -msse2 -pipe -Wno-unknown-pragmas"
PLATFORM_CMAKE_OPTIONS="-DFSO_BUILD_APPIMAGE=ON"
fi
CXXFLAGS="-m64 -mtune=generic -mfpmath=sse -msse -msse2 -pipe -Wno-unknown-pragmas"
CFLAGS="-m64 -mtune=generic -mfpmath=sse -msse -msse2 -pipe -Wno-unknown-pragmas"
CMAKE_OPTIONS="$JOB_CMAKE_OPTIONS"
if [[ "$COMPILER" =~ ^clang.*$ ]]; then
@@ -38,10 +30,7 @@ export CXXFLAGS
export CFLAGS
export LD_LIBRARY_PATH
if [ "$RUNNER_OS" = "macOS" ]; then
brew install ninja
fi
cmake -G Ninja -DFSO_FATAL_WARNINGS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON $CMAKE_OPTIONS $PLATFORM_CMAKE_OPTIONS \
-DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DCMAKE_BUILD_TYPE=$CONFIGURATION \
cmake -G Ninja -DFSO_FATAL_WARNINGS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON $CMAKE_OPTIONS \
-DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_BUILD_APPIMAGE=ON -DCMAKE_BUILD_TYPE=$CONFIGURATION \
-DFFMPEG_USE_PRECOMPILED=ON -DFSO_BUILD_TESTS=ON -DFSO_BUILD_INCLUDED_LIBS=ON -DFSO_BUILD_QTFRED=ON \
-DSHADERS_ENABLE_COMPILATION=ON ..
-6
View File
@@ -25,12 +25,6 @@ elif [ "$OS" = "Windows" ]; then
echo "::set-output name=debug_path::$(pwd)/$(get_package_name)-debug-$ARCH-$SIMD.7z"
echo "::set-output name=debug_name::$(get_package_name)-debug-$ARCH-$SIMD.7z"
echo "::set-output name=debug_mime::$(file -b --mime-type "$(pwd)/$(get_package_name)-debug-$ARCH-$SIMD.7z")"
elif [ "$OS" = "Mac" ]; then
tar -cvzf "$(get_package_name)-builds-Mac.tar.gz" *
echo "::set-output name=package_path::$(pwd)/$(get_package_name)-builds-Mac.tar.gz"
echo "::set-output name=package_name::$(get_package_name)-builds-Mac.tar.gz"
echo "::set-output name=package_mime::$(file -b --mime-type "$(pwd)/$(get_package_name)-builds-Mac.tar.gz")"
else
echo "Invalid OS: $OS"
fi
+1 -1
View File
@@ -5,7 +5,7 @@ HERE=$(dirname "$SCRIPT")
export LD_LIBRARY_PATH=$(pwd)/bin/lib:$LD_LIBRARY_PATH
if [ "$CONFIGURATION" = "Debug"] && [[ "$RUNNER_OS" != "macOS" ]] ; then
if [ "$CONFIGURATION" = "Debug" ]; then
valgrind --leak-check=full --error-exitcode=1 --gen-suppressions=all \
--suppressions="$HERE/valgrind.supp" ./bin/unittests --gtest_shuffle
else
-169
View File
@@ -1,169 +0,0 @@
# Retrives a log of commit messages and attempts to pretty them into a human-readbile format
import argparse
import os
import re
import requests
import sys
from datetime import datetime
from subprocess import check_output
MAX_PAGES = 100 # maximum number of PR pages to go through
warnings = 0
def get_last_page(response: requests.Response) -> int:
"""
@brief Extract the last page number from the response header
@param[in] response The response from a GET call
@returns The page number of the last page, or MAX_PAGES
"""
# The link header is a dictionary in string form, and for that matter the
# value is in front of the key. It is of the form '<<url1>>; rel=<key1>, <url2>, <key2>'
link = response.headers['link']
link = link.split(',')
for x in link:
y = x.split(';') # <url>; rel=<key>
if "last" not in y[1]: # look for rel='last'
continue
# grab the page number from the URL
# group(0) is `page=123`, group(1) is `123`
last_page = int( re.search(r'page=(\d+)', y[0]).group(1) )
if not last_page:
print("Error: Could not find page number in url! URL: %s", y[0])
sys.exit(1)
if last_page > MAX_PAGES:
return MAX_PAGES
else:
return last_page
def main():
parser = argparse.ArgumentParser(description="Retrieves a log of commit messages and attempts to pretty them into a human-readable format")
parser.add_argument("start_date",
help="Day of the earliest commit to add to the log.")
parser.add_argument("-u", "--user", nargs=2, metavar=("NAME", "TOKEN"), default=["", ""],
help="Github username and token/password")
args = parser.parse_args()
os.chdir(os.path.dirname(__file__)) # Change working directory to this file's directory
start_date = datetime.strptime(args.start_date, "%Y-%m-%d")
print("start_date: {}".format(start_date))
# Retrive the git log for master, since start date, order by date, in ascending order, use YYYY-MM-DD date format,
# each line is of the form "<author short date> | <message>", only grab commits from master,
# output to `fsofordigest.txt`,
# check_output(("git log master", "--after=2021-09-15", "--date-order", "--reverse", "--date=short",
# "--pretty=\"%as | %s\"", "--first-parent",
# "--output fsofordigest.txt"), text=True)
# The commit messages are either of the form "<body> (#<PR_number>)" or "Merge pull request #<PR_number> from <source_repo>"
# For our purposes, we want them be all of the form "<body> (#<PR_number>)"
# Typically, the second form does not have a usable body, so we'll have to pull the PR title from github
# Github's REST API allows us to retrieve PR's, but due to it being HTTP requests it has to page in the data.
# We can loop over the GET request and check for a merge date.
# The GET request doesn't have sorting by merge date, but does have sorting by the last time it was updated... which is usually
# also the merge date but not always.
# Key attributes within the PR JSON objects:
# "merged_at: <date>" # Date the PR was merged in
# "merged: <bool>" # If the PR was merged yet
# "html_url: <string>" # URL of the PR's HEAD commit, used by some clients to pull in the PR branch
# "number: <int>" # The PR id number
# "title: <string>" # Title of the PR
# "body: <string>" # The body paragraph detailing the PR
# "labels: [label_object]" # All labels attached to this PR
changelog = {}
url = "https://api.github.com/repos/scp-fs2open/fs2open.github.com/pulls"
page = 1
last_page = MAX_PAGES
while page <= last_page:
# GET <url>?base=master&sort=updated&direction=desc&state=closed&page=1
response = requests.get(url,
auth=(args.user[0], args.user[1]),
params={
"base": "master",
"direction": "desc",
"page": page,
"state": "closed",
"sort": "updated"
})
# Check if good response
if (response.status_code != 200) :
print("Error: ", response.status_code)
print(response.json()['message'])
sys.exit(1)
else:
print("Request successful: 200")
print("page={}".format(page))
# Update the last_page index at first iteration and possibly last iteration
# This should cover the edge case of a new PR being added to the database before this script is finished
if (page == 1) or (page == last_page):
last_page = get_last_page(response)
print("last_page={}".format(last_page))
# assume pulls are organized from newest to oldest
for pull in response.json():
if (pull['state'] != 'closed'):
# This pull is not merged, so ignore
print("Found irrelevant pull, skipping...")
print("state: {}; number: {}".format(pull['state'], pull['number']))
continue
if (pull['merged_at'] == None):
# This pull wasn't merged, skip
print("Found closed pull #{} ({}), skipping...".format(pull['number'], pull['closed_at']))
continue
merge_date = datetime.strptime(pull['merged_at'], "%Y-%m-%dT%H:%M:%SZ")
updated_date = datetime.strptime(pull['updated_at'], "%Y-%m-%dT%H:%M:%SZ")
if (merge_date < start_date) and (updated_date >= start_date):
# This pull is too old, but was updated within our timeframe. skip
print("Found old pull #{} ({}), skipping...".format(pull['number'], pull['merged_at']))
continue
if (updated_date < start_date):
# This pull is too old
print("Found old pull #{} ({}), stopping loop...".format(pull['number'], pull['updated_at']))
page = last_page + 1
break
# Add the pull to the changelog, indexed by its pull.number
if pull['number'] not in changelog:
# only add if the key isn't there already
changelog[pull['number']] = {
"date": merge_date,
"title": pull['title'],
"labels": pull['labels'],
"url": pull['html_url']
}
print("Added pull #{} ({})".format(pull['number'], merge_date))
# Else, silently ignore duplicate (for now...)
page += 1 # iterate to next page
# Write the changelog to file
# 2021-09-28 | (#3661) Fix homing child weapons in niche circumstances
print("Writing to change.log...")
str = ''
for key, value in changelog.items():
str += "{} | (#{}) {} \n".format(value['date'].strftime("%Y-%m-%d"), key, value['title'])
with open("change.log", "w") as file:
file.write(str)
print("Done!")
main()
-228
View File
@@ -1,228 +0,0 @@
import re # regex module
from ftplib import FTP, error_perm
from itertools import groupby
from typing import List, Tuple, Dict
import requests # HTTP requests module
from util import retry_multi, GLOBAL_TIMEOUT # from util.py
class ReleaseFile:
"""! Class representing a Released file on Nebula
`name`: str
Mod (or build) name,
`url`: str
Primary host URL,
`group`: str
Mod group string,
`subgroup`: str
Mod subgroup string,
`mirrors`: List[str]
List of URL's of FTP mirrors
"""
def __init__(self, name, url, group, subgroup=None, mirrors=None):
if mirrors is None:
mirrors = []
self.mirrors = mirrors
self.subgroup = subgroup
self.group = group
self.url = url
self.name = name
self.base_url = "/".join(url.split('/')[0:-1]) + "/"
self.filename = url.split('/')[-1]
# A list of tuples of (filename, hash)
self.content_hashes = None
self.hash = None
self.size = 0
def __repr__(self):
return repr((self.name))
class SourceFile:
"""! Class represeting a source file
`name`: str
File name,
`url`: str
FTP URL,
`group`
<unknown>
@details More details
"""
def __init__(self, name, url, group):
self.group = group
self.url = url
self.name = name
class FileGroup:
"""! Represents a file group
`name`: str
Name of this group
`files`: List[ReleaseFile]
List of files within this group
`mainFile`: str
If this FileGroup has a subgroup, `mainFile` is the head of that group
`subFiles`: List[ReleaseFile]
Files within a subgroup
"""
def __init__(self, name, files: List[ReleaseFile]):
self.files = files
self.name = name
if len(files) == 1:
self.mainFile = files[0]
self.subFiles = {}
else:
self.mainFile = None
subFiles = []
for file in files:
# We only have subcategories for Windows where SSE2 is the main group
if file.subgroup == "SSE2":
self.mainFile = file
else:
subFiles.append(file)
self.subFiles = dict(((x[0], next(x[1])) for x in groupby(subFiles, lambda f: f.subgroup)))
def get_release_files(tag_name, config) -> Tuple[List[ReleaseFile], Dict[str, SourceFile]]:
"""! Brief Gets the binary and source files from the Github Release server
@param[in] `tag_name` Git tag of the current release
@param[in] `config` confi metadata set in main.py
@returns `List[ReleaseFile]` List of release files
@returns `Dict[str, SourceFile]` Dictionary of source files
@details Sends an `HTTP GET` request to github using their REST API to retrieve metadata. The files are not
actually downloaded here, just their metadata is gathered and organized in their respective container for later
use.
"""
@retry_multi(5) # retry at most 5 times
def execute_request(path):
"""!
@brief Performs a GET request with the given path. To be used with Github's REST API.
@returns If successful, returns a .JSON object
"""
headers = {
"Accept": "application/vnd.github.v3+json"
}
url = "https://api.github.com" + path
# GET https://api.github.com/<path> Accept: "application/vnd.github.v3+json"
response = requests.get(url, headers=headers, timeout=GLOBAL_TIMEOUT)
response.raise_for_status() # Raise a RequestException if we failed, and trigger retry
return response.json()
build_group_regex = re.compile("fs2_open_.*-builds-([^.-]*)(-([^.]*))?.*") # regex for matching binary .zip's and .7z's
source_file_regex = re.compile("fs2_open_.*-source-([^.]*)?.*") # regex for matching source .zip's and .7z's
# Get the github release metadata of the given tag name
response = execute_request(
"/repos/{}/releases/tags/{}".format(config["github"]["repo"], tag_name))
# Extract the binary and source files from the response["asset"] metadata
binary_files = []
source_files = {}
for asset in response["assets"]:
url = asset["browser_download_url"]
name = asset["name"]
group_match = build_group_regex.match(name)
if group_match is not None:
platform = group_match.group(1)
# x64 is the Visual Studio name but for consistency we need Win64
if platform == "x64":
platform = "Win64"
binary_files.append(ReleaseFile(name, url, platform, group_match.group(3)))
else:
group_match = source_file_regex.match(name)
if group_match is None:
continue
group = group_match.group(1)
source_files[group] = SourceFile(name, url, group)
binary_files.sort(key=lambda ReleaseFile: ReleaseFile.name)
return binary_files, source_files
def get_ftp_files(build_type, tag_name, config) -> List[ReleaseFile] :
"""!
@brief Gets file metadata for nightlies hosted on FTP, as determined by config["ftp"] attributes
@param [in] `build_type` Unknown str
@param [in] `tag_name` Github tag name of the release
@param [in] `config` config metadata set in main.py
"""
tag_regex = re.compile("nightly_(.*)")
build_group_regex = re.compile("nightly_.*-builds-([^.]+).*")
files = []
try:
with FTP(config["ftp"]["host"], config["ftp"]["user"], config["ftp"]["pass"]) as ftp:
# extract version
version_str = tag_regex.match(tag_name).group(1)
# extract filepath w/ version
# then list all ftp hits with that path
path_template = config["ftp"]["path"]
path = path_template.format(type=build_type, version=version_str)
file_entries = list(ftp.mlsd(path, ["type"]))
# get all ftp hits of type file
for entry in file_entries:
if entry[1]["type"] == "file":
files.append(entry[0])
except error_perm:
print("Received permanent FTP error!")
return []
out_data = []
for file in files:
# from the file list, extract only nightly files
file_match = build_group_regex.match(file)
if file_match is None:
print("Ignoring non nightly file '{}'".format(file))
continue
group_match = file_match.group(1)
primary_url = None
mirrors = []
# x64 is the name Visual Studio uses but Win64 works better for us since that gets displayed in the nightly post
if "x64" in group_match:
group_match = group_match.replace("x64", "Win64")
# construct the download URL list for all mirrors. The first listed ftp location is taken as the Primary
for mirror in config["ftp"]["mirrors"]:
download_url = mirror.format(type=build_type, version=version_str, file=file)
if primary_url is None:
primary_url = download_url
else:
mirrors.append(download_url)
# Form the List[ReleaseFile] list with the download URL links
out_data.append(ReleaseFile(file, primary_url, group_match, None, mirrors))
return out_data
-103
View File
@@ -1,103 +0,0 @@
from typing import List
import requests
import semantic_version
from mako.template import Template
from file_list import ReleaseFile
class ForumAPI:
POST_SIZE_MAX = 50000
"""!
Maximum number of characters allowed in a post, including BBCode (03/17/2022)
Check with HLP admins occasionally to verify this limit, they can set it in the forum's admin panel
"""
def __init__(self, config):
self.config = config
def create_post(self, title, content, board):
"""!
@brief Create a post on the forums
@param[in] title Title of the post
@param[in] content Body of the post
@param[in] board Which board/forum to post to
@returns { "thread_url": <url_of_created_thread> }
"""
if (self.config["hlp"]["api"] == "") or (self.config["hlp"]["key"] == ""):
print("Post failed! No API or API_KEY given!")
return
resp = requests.post(self.config["hlp"]["api"], data={
"api_key": self.config["hlp"]["key"],
"board": str(board),
"subject": title,
"body": content
})
if resp.text != "OK":
print("Post failed! Response: %s" %resp.text)
return resp.json()
def post_nightly(self, date, revision, files, log, success):
"""!
@brief Post a new Nightly topic on the forums using nightly.mako
@param[in] date The date to use
@param[in] version The build's version
@param[in] groups dict[Any, file_list.FileGroup]; FileGroups of SSE2, AVX, etc.
@param[in] sources URL's of builds on github or fs2net
"""
print("Posting nightly thread...")
title = "Nightly: {} - Revision {}".format(date, revision)
template = Template(filename=self.config["templates"]["nightly"])
rendered = template.render_unicode(**{
"date": date,
"revision": revision,
"files": files,
"log": log,
"success": success
})
print("Creating post...")
self.create_post(title, rendered, self.config["nightly"]["hlp_board"])
def post_release(self, date, version: semantic_version.Version, groups, sources):
"""!
@brief Post a new Release topic on the forums using release.mako.
@param[in] date The date to use
@param[in] version The build's version
@param[in] groups dict[Any, file_list.FileGroup]; FileGroups of SSE2, AVX, etc.
@param[in] sources URL's of builds on github or fs2net
@returns If Successful, the URL of the created post
"""
print("Posting release thread...")
title = "Release: {}".format(version)
template = Template(
filename=self.config["templates"]["release"].format(major=version.major, minor=version.minor),
module_directory='/tmp/mako_modules')
rendered = template.render_unicode(**{
"date": date,
"version": version,
"groups": groups,
"sources": sources
}).strip("\n")
print("Creating post...")
json = self.create_post(title, rendered, self.config["release"]["hlp_board"])
return json["thread_url"]
-85
View File
@@ -1,85 +0,0 @@
# Class for use with interfacing with github via HTTP requests
import requests
from util import retry_multi, GLOBAL_TIMEOUT
class GithubAPI:
headers = {
"Accept": "application/vnd.github.v3+json"
}
url_root = "https://api.github.com"
def __init__(self, config):
self.config = config
@retry_multi(5)
def get(self, path):
"""!
@brief Performs a GET request with the given path. To be used with Github's REST API.
@returns If successful, returns a .JSON object
"""
url = self.url_root + path
# GET https://api.github.com/<path> Accept: "application/vnd.github.v3+json"
response = requests.get(url, headers=self.headers, timeout=GLOBAL_TIMEOUT)
response.raise_for_status() # Raise a RequestException if we failed, and trigger retry
return response.json()
def get_release(self, tag_name):
"""!
@brief Retrieves the Github Release metadata as a JSON object
@see https://docs.github.com/en/rest/reference/releases#get-a-release-by-tag-name
"""
return self.get("/repos/{}/releases/tags/{}".format(self.config["github"]["repo"], tag_name))
def patch(self, path, data):
"""!
@brief Performs a PATCH request with the given path and body data (as dictionary)
@returns If successful, returns a .JSON object
"""
url = self.url_root + path
response = requests.patch(url, headers=self.headers, timeout=GLOBAL_TIMEOUT, data=data)
response.raise_for_status()
return response.json()
def update_release(self, release_id, tag_name="", name="", body="", draft=None, prerelease=None):
"""!
@brief Updates a Github Release
@param[in] release_id The id of the release, as obtained by get_release()
@param[in] tag_name (optional) The git tag this release is associated with
@param[in] name (optional) The name of the release
@param[in] body (optional) Body text describing the release
@param[in] draft (optional) If true, the release is marked as a "draft"
@param[in] prerelease (optional) If true, the release is marked as a "prerelease"
@note If an optional argument is not provided, then its associated data is not changed
"""
path = "/repos/{}/releases/{}".format(self.config["github"]["repo"], release_id)
# Add to the data JSON if options are specified
data = {}
if tag_name != "":
data["tag_name"] = tag_name
if name != "":
data["name"] = name
if body != "":
data["body"] = body
if draft != None:
data["draft"] = data
if prerelease != None:
data["prerelease"] = prerelease
self.patch(path, data)
-88
View File
@@ -1,88 +0,0 @@
import os
import hashlib
import tarfile
import traceback
import zipfile
from tempfile import NamedTemporaryFile
from zipfile import ZipFile
import requests
import sys
from mako.template import Template
from file_list import ReleaseFile
from util import GLOBAL_TIMEOUT
def _download_file(url, dest_file, session):
print("Downloading " + url)
# NOTE the stream=True parameter
r = session.get(url, stream=True, timeout=GLOBAL_TIMEOUT)
for chunk in r.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
dest_file.write(chunk)
dest_file.flush()
def _gen_hash(fileobj, hash_alg):
h = hashlib.new(hash_alg)
while True:
data = fileobj.read(4096)
if not data:
break
h.update(data)
return h.hexdigest()
def get_hashed_file_list(file: ReleaseFile, session: requests.Session = None, hash_alg: str = "sha256"):
if session is None:
session = requests.Session()
with NamedTemporaryFile('w+b', suffix=file.filename) as local_file:
_download_file(file.url, local_file, session)
filename = local_file.name
hash_list = []
local_file.seek(0)
file.hash = _gen_hash(local_file, hash_alg)
file.size = os.stat(filename).st_size
try:
if tarfile.is_tarfile(filename):
with tarfile.open(filename) as archive:
for entry in archive:
if not entry.isfile():
continue
print("Computing hash for " + entry.name)
fileobj = archive.extractfile(entry)
hash_list.append((entry.path, _gen_hash(fileobj, hash_alg)))
elif zipfile.is_zipfile(filename):
with ZipFile(filename) as archive:
for entry in archive.infolist():
# Python 3.6 has is_dir but that version is relatively new so we'll use the "safe" version here
if entry.filename.endswith('/'):
continue
print("Computing hash for " + entry.filename)
with archive.open(entry) as fileobj:
hash_list.append((entry.filename, _gen_hash(fileobj, hash_alg)))
else:
raise NotImplementedError("Unsupported archive type!")
except:
traceback.print_exception(*sys.exc_info())
return
file.content_hashes = hash_list
def render_installer_config(version, groups, config):
template = Template(filename=config["templates"]["installer"], module_directory='/tmp/mako_modules')
return template.render(**{
"version": version,
"groups": groups,
}).strip("\n")
-305
View File
@@ -1,305 +0,0 @@
# Post-build python script
# Uploads build packages to Nebula and creates a forum post on https://www.hard-light.net
# Uses the following environment variables (Set by the github action workflow before execution)
# FSO versioning
# FSO_VERSION_MAJOR
# FSO_VERSION_MINOR
# FSO_VERSION_BUILD
# Credentials into IndieGames file host
# INDIEGAMES_USER:
# INDIEGAMES_SSHPASS:
# Credentials into Nebula Mod Database
# NEBULA_USER:
# NEBULA_PASSWORD:
# Credentials into hard-light.net for forum post
# HLP_API:
# HLP_KEY:
# LINUX_RESULT: bool, if the linux builds were sucessfully uploaded
# WINDOWS_RESULT: bool, if the windows builds were successfully uploaded
import sys
import os
import re
from subprocess import check_output
from datetime import datetime
from itertools import groupby
import semantic_version
# local .py files
import file_list
import installer
import nebula
import forum
# Compile regexes for extracting version components
MAJOR_VERSION_PATTERN = re.compile("(?:set_if_not_defined|set)\(FSO_VERSION_MAJOR (\d+)\)")
MINOR_VERSION_PATTERN = re.compile("(?:set_if_not_defined|set)\(FSO_VERSION_MINOR (\d+)\)")
BUILD_VERSION_PATTERN = re.compile("(?:set_if_not_defined|set)\(FSO_VERSION_BUILD (\d+)\)")
REVISION_VERSION_PATTERN = re.compile("(?:set_if_not_defined|set)\(FSO_VERSION_REVISION (\d+)\)")
REVISION_STR_VERSION_PATTERN = re.compile("(?:set_if_not_defined|set)\(FSO_VERSION_REVISION_STR (\w+)\)")
LOG_FORMAT = """
------------------------------------------------------------------------%n
commit %h%nAuthor: %an <%ad>%n
Commit: %cn <%cd>%n%n %s
"""
DATEFORMAT_VERSION = "%Y%m%d"
DATEFORMAT_FORUM = "%d %B %Y"
def _match_version_number(text, regex):
"""! Extracts the version component represented by `regex` from `text`
@param [in] `text` Filename with version string 'MAJOR.MINOR.BUILD'
@param [in] `regex` Regex pattern of component to look for
@return The version component as integer
"""
match = regex.search(text)
return int(match.group(1))
def _match_version_str(text, regex):
"""! Extracts the version component represented by `regex` from `text`
@param [in] `text` Filename with version string 'MAJOR.MINOR.BUILD'
@param [in] `regex` Regex pattern of component to look for
@return The version component as integer
"""
match = regex.search(text)
return match.group(1)
def get_source_version(date_version: datetime, tag_name: str) -> semantic_version.Version:
"""! Retrieves the version string from version_override.cmake (and version.cmake)
@param[in] `date_version` Date this build is published for release
@param[in] `tag_name` Tag of this release, used to determine if release, rc, or nightly
@return If release: `FSO_VERSION_MAJOR.FSO_VERSION_MINOR.FSO_VERSION_BUILD`
@return If rc: `FSO_VERSION_MAJOR.FSO_VERSION_MINOR.FSO_VERSION_BUILD-FSO_VERSION_REVISION_STR`
@return If nightly: `FSO_VERSION_MAJOR.FSO_VERSION_MINOR.FSO_VERSION_BUILD-FSO_VERSION_REVISION_STR` (uses both version_override.cmake and version.cmake)
@note This is the version used to identify in Nebula and the forums. version_override.cmake is used for the builds
made by CI tools and version.cmake is used for builds made by devs.
"""
with open(os.path.join("..", "..", "version_override.cmake"), "r") as f:
filetext = f.read()
major = _match_version_number(filetext, MAJOR_VERSION_PATTERN)
minor = _match_version_number(filetext, MINOR_VERSION_PATTERN)
build = _match_version_number(filetext, BUILD_VERSION_PATTERN)
# revision = _match_version_number(filetext, REVISION_VERSION_PATTERN)
revision_str = _match_version_str(filetext, REVISION_STR_VERSION_PATTERN)
print("Parsing version_override.cmake...")
if "rc" in tag_name.lower():
version = semantic_version.Version("{}.{}.{}-{}".format(major, minor, build, revision_str))
elif "release" in tag_name.lower():
version = semantic_version.Version("{}.{}.{}".format(major, minor, build))
elif "nightly" in tag_name.lower():
# Nightly's version_override.cmake only has FSO_VERSION_REVISION and FSO_VERSION_REVISION_STR set
# So, we have to pull the rest of the version string from version.cmake
print("Parsing version.cmake...")
with open(os.path.join("..", "..", "cmake", "version.cmake"), "r") as f:
major = _match_version_number(filetext, MAJOR_VERSION_PATTERN)
minor = _match_version_number(filetext, MINOR_VERSION_PATTERN)
build = _match_version_number(filetext, BUILD_VERSION_PATTERN)
version = semantic_version.Version("{}.{}.{}-{}".format(major, minor, build, revision_str))
else:
print(" ERROR: malformed tag_name %s" % tag_name)
sys.exit(1)
print(" version: {}".format(version))
print(" version.prerelease: {}".format(version.prerelease))
return version
def main():
os.chdir(os.path.dirname(__file__)) # Change working directory to this file's directory
# Aggregate configuration data in a dictionary
config = {
"github": {
"repo": os.environ["GITHUB_REPO"],
},
"ftp": {
"host": "scp.indiegames.us",
"user": os.environ["INDIEGAMES_USER"],
"pass": os.environ["INDIEGAMES_SSHPASS"],
"path": "public_html/builds/{type}/{version}/",
"mirrors": (
"https://porphyrion.feralhosting.com/datacorder/builds/{type}/{version}/{file}",
"http://scp.indiegames.us/builds/{type}/{version}/{file}",
),
},
"nebula": {
"user": os.environ["NEBULA_USER"],
"password": os.environ["NEBULA_PASSWORD"],
},
"hlp": {
"api": os.environ["HLP_API"],
"key": os.environ["HLP_KEY"],
},
"templates": {
"nightly": "nightly.mako",
"release": "release.mako",
},
"nightly": {
"hlp_board": 173,
},
"release": {
"hlp_board": 50,
},
}
# bail if this script is run from a workflow that doesn't identify as a release or nightly
if sys.argv[1] not in ("release", "nightly"):
print("ERROR: Invalid release mode %s passed. Expected release or nightly!" % sys.argv[1])
sys.exit(1)
linux_success = os.environ["LINUX_RESULT"] == "success"
windows_success = os.environ["WINDOWS_RESULT"] == "success"
if (sys.argv[1] == "release"):
if not (linux_success and windows_success):
# bail if this is a release and either build workflow failed
print("ERROR: One or more builds failed to publish! Cannot post release.")
sys.exit(1)
else:
if not (linux_success or windows_success):
# bail if this is a nightly build and both build workflows failed
print("ERROR: All builds failed to publish! Cannot post nightly.")
sys.exit(1)
# bail if tag_name doesn't match with sys.argv[1]
tag_name = os.environ["RELEASE_TAG"] ##!< commit tag string
if (sys.argv[1] == "release") and (not tag_name.startswith("release_")):
print("ERROR: Invalid tag name detected. Expected release_ prefix for release builds but found %s (full ref = %s)" % tag_name, os.environ["GITHUB_REF"])
sys.exit(1)
if (sys.argv[1] == "nightly") and (not tag_name.startswith("nightly_")):
print("ERROR: Invalid tag name detected. Expected nightly_ prefix for nightly builds but found %s (full ref = %s)" % tag_name, os.environ["GITHUB_REF"])
sys.exit(1)
# bail if tag_name does not exist in the repo
if not check_output(("git", "show-ref", "--tags", "refs/tags/%s" % tag_name), text=True):
print("ERROR: Couldn't find tag %s in repo (git show-ref)!" % tag_name)
sys.exit(1)
# Populate version and date
date = datetime.now() ##!< current date
version = get_source_version(date.strftime(DATEFORMAT_VERSION), tag_name) ##!< form full version string
# find the previous tag release (used by nightly)
tags = check_output(("git", "for-each-ref", "--sort=-taggerdate", "--format='%(tag)'", "refs/tags"), text=True).splitlines() # retrieve all tags in the repo by using git on the shell
# NOTE: check_output returns canonical string representation. use repr() on strings its being compared to
previous_tag = None
found = False
for tag in tags:
if found:
# Look for the "previous" tag with the same configuration ('release_' or 'nightly_')
# Assumes older tags have a higher index than newer
if tag.startswith("\'" + sys.argv[1] + "_"):
previous_tag = tag
break
elif tag == repr(tag_name):
# Ok, found the tag
found = True
if not found:
# couldn't find tag, bail
print("ERROR: Couldn't find tag %s in repo! (git for-each-ref)" % tag_name)
sys.exit(1)
if (sys.argv[1] == "nightly") and (not previous_tag):
# couldn't find previous tag, bail
print("ERROR: Could not find a previous tag for %s!" % tag_name)
sys.exit(1)
if sys.argv[1] == "release":
# Release specific action
print("Retrieving file list...")
files, sources = file_list.get_release_files(tag_name, config)
print("Hashing files...")
for file in files:
installer.get_hashed_file_list(file)
# Construct the file groups
# split the file list into a dictionary of {group: files} pairs using `groupby()`
# Since `groupby()` returns a iterator reference to the original files list, we have to do a weird hand toss
# to save it as a dictionary
## NOTE z64: files need to be sorted or else key duplication will occur!
groups = {key: file_list.FileGroup(key, list(group)) for key, group in groupby(files, lambda g: g.group)}
# Error check for all group keys and subkeys to be used in the mako. Do this here for better debugging QoL
if "Win32" in groups.keys():
if "AVX" not in groups["Win32"].subFiles.keys():
print("ERROR: No Win32-AVX builds were detected!")
sys.exit(1)
else:
print("ERROR: No Win32 builds were detected!")
sys.exit(1)
if "Win64" in groups.keys():
if "AVX" not in groups["Win64"].subFiles.keys():
print("ERROR: No x64-AVX builds were detected!")
sys.exit(1)
else:
print("ERROR: No x64 builds were detected!")
sys.exit(1)
if "Linux" not in groups.keys():
print("ERROR: No Linux builds were detected!")
sys.exit(1)
# z64: What dose this do???
# print(installer.render_installer_config(version, groups, config))
# Publish release builds to Nebula
print("Publishing to Nebula...")
nebula.submit_release(
nebula.render_nebula_release(version, "rc" if version.prerelease else "stable", files, config),
config)
# Publish forum post, using the release.mako template
print("Publishing to Hard-light.net...")
fapi = forum.ForumAPI(config)
fapi.post_release(date.strftime(DATEFORMAT_FORUM), version, groups, sources)
else:
# Dead for now, nightly publish is done elsewhere
# Nightly specific action
print("Retrieving file list...")
files = file_list.get_ftp_files("nightly", tag_name, config)
print("Hashing files...")
for file in files:
installer.get_hashed_file_list(file)
print("Publishing to Nebula...")
nebula.submit_release(
nebula.render_nebula_release(version, "nightly", files, config),
config)
# Compile commit messages into a log with all commits between this nightly and the previous nightly
log = check_output(("git", "log", "%s^..%s^" % (previous_tag, tag_name), "--no-merges", "--stat", "--pretty=format:\"%s\"" % LOG_FORMAT.strip()))
# Publish forum post, using the nighty.mako template
print("Publishing to Hard-light.net...")
fapi = forum.ForumAPI(config)
fapi.post_nightly(date.strftime(DATEFORMAT_FORUM), os.environ["GITHUB_SHA"], files, log, (linux_success and windows_success))
print("Done!")
if __name__ == "__main__":
main()
-241
View File
@@ -1,241 +0,0 @@
import os.path
import sys
import traceback
import requests
from file_list import ReleaseFile
from util import retry_multi, GLOBAL_TIMEOUT
metadata = {
'type': 'engine',
'title': 'FSO',
'notes': '',
'banner': 'https://fsnebula.org/storage/0d/e7/bf64bcdea9a9c115969cfb784e1ca457d24a7c2da4fc6f213521c3bb6abb.png',
'screenshots': [],
'videos': [],
'first_release': '2017-09-24',
'cmdline': '',
'mod_flag': ['FSO'],
'tile': 'https://fsnebula.org/storage/ec/cc/0bf23e028c26d5175ff52d003bff85b0a17b0ddfc1130d65bdf6d36f6324.png',
'logo': None,
'release_thread': None,
'description': '',
'id': 'FSO',
'packages': [],
}
platforms = {
'Linux': 'linux',
'MacOSX': 'macosx',
'Win32-SSE2': 'windows',
'Win64-SSE2': 'windows',
'Win32-AVX': 'windows',
'Win64-AVX': 'windows'
}
envs = {
'Linux': 'linux && x86_64', # Linux only has 64bit builds
'MacOSX': 'macosx',
'Win32-SSE2': 'windows',
'Win64-SSE2': 'windows && x86_64',
'Win32-AVX': 'windows && avx',
'Win64-AVX': 'windows && avx && x86_64'
}
subdirs = {
'Win32-SSE2': 'x86',
'Win64-SSE2': 'x64',
'Win32-AVX': 'x86_avx',
'Win64-AVX': 'x64_avx'
}
def render_nebula_release(version, stability, files, config):
meta = metadata.copy()
meta['version'] = str(version)
meta['stability'] = stability # This can be one of ('stable', 'rc', 'nightly')
for file in files:
if file.content_hashes is None:
# The extraction probably failed which is why we don't have any hashes
continue
group = file.group
# release.py sets subgroup but nightly.py doesn't which means we have to normalize group here.
if file.subgroup:
group += '-' + file.subgroup
pkg = {
'name': group,
'notes': '',
'is_vp': False,
'files': [{
'dest': platforms[group] + '/' + subdirs.get(group, ''),
'filesize': file.size,
'checksum': ['sha256', file.hash],
'urls': [file.url] + file.mirrors,
'filename': file.name
}],
'environment': envs.get(group),
'filelist': [],
'status': 'required',
'folder': None,
'dependencies': [],
'executables': []
}
for fn, checksum in file.content_hashes:
if group in subdirs:
dest_fn = subdirs[group] + '/' + fn
else:
dest_fn = fn
# If the group is a known platform then we put the binaries into a separate subfolder for each platform
if group in platforms:
dest_fn = platforms[group] + '/' + dest_fn
pkg['filelist'].append({
'orig_name': fn,
'checksum': ('sha256', checksum),
'archive': file.name,
'filename': dest_fn
})
if group == 'Linux' and fn.endswith('.AppImage'):
if 'qtfred' in fn:
if '-FASTDBG' in fn:
label = 'QtFRED Debug'
else:
label = 'QtFRED'
else:
if '-FASTDBG' in fn:
label = 'Fast Debug'
else:
label = None
props = {
"x64": True, # All Linux builds are 64-bit
"sse2": True, # Linux builds are forced to compile with SSE2 but not AVX
"avx": False,
"avx2": False,
}
pkg['executables'].append({
'file': dest_fn,
'label': label,
'properties': props,
})
elif group == 'MacOSX' and fn.startswith(os.path.basename(fn) + '.app/'):
if 'qtfred' in fn:
if '-FASTDBG' in fn:
label = 'QtFRED Debug'
else:
label = 'QtFRED'
else:
if '-FASTDBG' in fn:
label = 'Fast Debug'
else:
label = None
props = {
"x64": True, # All Mac builds are 64-bit
"sse2": True, # There are no forced options on mac but 64-bit always implies SSE2 so we set that here
"avx": False,
"avx2": False,
}
pkg['executables'].append({
'file': dest_fn,
'label': label,
'properties': props,
})
elif group.startswith('Win') and fn.endswith('.exe'):
if 'fred2' in fn:
if '-FASTDBG' in fn:
label = 'FRED2 Debug'
else:
label = 'FRED2'
elif 'qtfred' in fn:
if '-FASTDBG' in fn:
label = 'QtFRED Debug'
else:
label = 'QtFRED'
else:
if '-FASTDBG' in fn:
label = 'Fast Debug'
else:
label = None
props = {
"x64": "x64" in fn,
"sse2": "SSE2" in fn or "AVX" in fn, # AVX implies SSE2
"avx": "AVX" in fn, # This conveniently also covers the AVX2 case since AVX2 implies AVX
"avx2": "AVX2" in fn,
}
pkg['executables'].append({
'file': dest_fn,
'label': label,
'properties': props,
})
meta["packages"].append(pkg)
return meta
@retry_multi(5)
def nebula_request(session, kind, path, **kwargs):
uri = "https://fsnebula.org/api/1/{}".format(path)
request_args = {
"timeout": GLOBAL_TIMEOUT
}
request_args.update(kwargs)
return session.request(kind, uri, **request_args)
def submit_release(meta, config):
try:
with requests.Session() as session:
print('Logging into Nebula...')
result = nebula_request(session, 'post', 'login', data={
'user': config['nebula']['user'],
'password': config['nebula']['password']
})
if result.status_code != 200:
print('Login failed!')
return False
data = result.json()
if not data['result']:
print('Login failed!')
return False
print('Submitting release...')
result = nebula_request(session, 'post', 'mod/release', headers={
'X-KN-TOKEN': data['token']
}, json=meta)
if result.status_code != 200:
print('Request failed!')
return False
data = result.json()
if not data['result']:
print('ERROR: ' + data['reason'])
return False
print('Success!')
return True
except Exception:
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback.print_exception(exc_type, exc_value, exc_traceback)
return False
-27
View File
@@ -1,27 +0,0 @@
Here is the nightly for ${date} - Revision ${revision}
% if not success:
[b][color=red]At least one of the nightly builds failed![/color][/b]
% endif
% for file in files:
Group: ${file.group}
[url=${file.url}]${file.name}[/url] \
%for mirror in file.mirrors:
%if loop.first:
(\
%endif
[url=${mirror}]Mirror[/url]\
%if loop.last:
)\
%else:
, \
%endif
%endfor
% endfor
[code]
${log}
[/code]
-68
View File
@@ -1,68 +0,0 @@
<%def name="build(file)">[url=${file.url}]${file.filename}[/url]</%def>
[b]Change log:[/b] (chronologically ordered)
PLACEHOLDER
[b]Deprecations:[/b]
PLACEHOLDER
Deprecations are a mechanism in FSO where a certain feature or aspect of the engine has changed or is no longer supported. Since this would normally break existing mods we have the mod table feature "[tt]$Target Version:[/tt]" with which a mod can specify what version of FSO it was developed with. The features listed above will be removed or changed when the target version of a mod is at least the version released in this post.
[size=5pt]Previous [url=https://www.hard-light.net/forums/index.php?topic=97293.0]21.0 Release Thread[/url][/size]
Launchers, if you don't have one already:
[b]All platforms: [/b] For every day use, we recommend [url=https://www.hard-light.net/forums/index.php?topic=94068.0]Knossos[/url], an integrated solution for downloading and launching mods.
[hidden=Alternative Launchers]
Cross-platform: [url=http://www.hard-light.net/forums/index.php?topic=89162]wxLauncher 0.12.x Test Build[/url] (ongoing project for a unified launcher, you should upgrade to the latest RC/test build if you have not yet)
[b]Important:[/b] For best compatibility with FSO 3.8 and later you should use at least wxLauncher 0.12.
Windows: [url=http://scp.fsmods.net/files/Launcher55g.zip]Launcher 5.5g[/url] ([url=http://scp.indiegames.us/builds/Launcher55g.zip]Mirror[/url]) ([url=http://www.mediafire.com/?wdvzn7hhhzh418m]Mirror[/url]) Not compatible with Windows 8+, use wxLauncher above
OS X: Soulstorm's [url=http://www.hard-light.net/forums/index.php/topic,51391.0.html]OS X Launcher 3.0[/url]
Linux: [url=http://www.hard-light.net/forums/index.php/topic,53206.0.html]YAL[/url] or [url=http://www.hard-light.net/wiki/index.php/Fs2_open_on_Linux/Graphics_Settings]by hand[/url] or whatever you can figure out.[/hidden]
[img]http://scp.indiegames.us/img/windows-icon.png[/img] [color=green][size=12pt]Windows (32/64-bit)[/size][/color]
[size=8pt]Compiled using GitHub Actions on Windows Server 2019 (10.0.17763), Visual Studio Enterprise 2019[/size]
[b]64-bit:[/b] ${build(groups["Win64"].mainFile)}
[b]32-bit:[/b] ${build(groups["Win32"].mainFile)}
[size=8pt]This one is based on the SSE2 Optimizations from the MSVC Compiler.[/size]
[hidden=Alternative builds]
[b]64-bit AVX:[/b] ${build(groups["Win64"].subFiles["AVX"])}
[size=8pt]This one is based on the AVX Optimizations from the MSVC Compiler (fastest build if your CPU supports AVX instructions).[/size]
[b]32-bit AVX:[/b] ${build(groups["Win32"].subFiles["AVX"])}
[size=8pt]This one is based on the AVX Optimizations from the MSVC Compiler.[/size]
[b]What are those SSE, SSE2 and AVX builds I keep seeing everywhere?[/b]
[url=http://www.hard-light.net/forums/index.php?topic=65628.0]Your answer is in this topic.[/url]
Don't want to deal with that? Use [url=https://www.hard-light.net/forums/index.php?topic=94068.0]Knossos[/url] and it will download the best build specifically for your PC!
[/hidden]
[img]http://scp.indiegames.us/img/linux-icon.png[/img] [color=green][size=12pt]Linux 64-bit[/size][/color]
[size=8pt]Compiled with Ubuntu 16.04 LTS 64-bit, GCC 5[/size]
${build(groups["Linux"].mainFile)}
These builds use a mechanism called [url=http://appimage.org/]AppImage[/url] which should allow these builds to run on most Linux distributions. However, we recommend that you compile your own builds which will result in less issues.
Alternatively, if there is a package in your software repository then you should use that. If you are the maintainer of such a package for a distribution then let us know and we will include that here.
[img]http://scp.indiegames.us/img/mac-icon.png[/img] [color=green][size=12pt]OS X[/size][/color]
[b][color=red]Not available[/color][/b] We recently lost access to our Mac CI environment which we usually used for compiling these builds so for the time being, there will be no builds for this OS.
[hidden=TrackIR Users]
[size=12pt]Important!![/size]
An external DLL is required for FSO to use TrackIR functions. The following DLL is simply unpacked in to your main FreeSpace2 root dir.
TrackIR is only supported on Windows.
[url=http://www.mediafire.com/download.php?4zw024zrh44etse]TrackIR SCP DLL[/url] ([url=http://scp.fsmods.net/builds/scptrackir.zip]Mirror[/url]) ([url=http://scp.indiegames.us/builds/scptrackir.zip]Mirror[/url])[/hidden]
Known issues:
[list]
[li][url=https://github.com/scp-fs2open/fs2open.github.com/issues]Github issues[/url] and [url=https://github.com/scp-fs2open/fs2open.github.com/pulls]pending pull requests[/url][/li]
[li]See the list of [url=http://scp.indiegames.us/mantis/search.php?project_id=1&status_id%5B%5D=10&status_id%5B%5D=20&status_id%5B%5D=30&status_id%5B%5D=40&status_id%5B%5D=50&priority_id%5B%5D=40&priority_id%5B%5D=50&priority_id%5B%5D=60&sticky_issues=on&sortby=last_updated&dir=DESC&per_page=200&hide_status_id=-2]Fix for next release[/url] bugs - mark a bug as an elevated priority (high, urgent, immediate) to get it included in that filter.[/li]
[/list]
-3
View File
@@ -1,3 +0,0 @@
Mako==1.1.5
requests==2.26.0
semantic-version==2.8.5
-46
View File
@@ -1,46 +0,0 @@
# miscellaneous utilities
import os
import time
from functools import wraps
from requests import RequestException
GLOBAL_TIMEOUT = 30
def retry_multi(max_retries):
"""!
@brief Decorator - Retry a function `max_retries` times.
@param [in] `max_retries` Maximum number of times to retry the decorated function before giving up
@details Intended for use with functions from the `request` module, only catches `RequestException`'s
@note This has been copied from https://stackoverflow.com/a/23892489
"""
def retry(func):
@wraps(func)
def wrapper(*args, **kwargs):
num_retries = 0
while num_retries <= max_retries:
try:
ret = func(*args, **kwargs)
break
except RequestException:
if num_retries == max_retries:
raise
num_retries += 1
time.sleep(5)
return ret
return wrapper
return retry
def expand_config_vars(config):
"""
@brief Expands the shell variable for the repo string from `config`
"""
config["git"]["repo"] = os.path.expandvars(config["git"]["repo"])
+12
View File
@@ -1,3 +1,15 @@
include(WriteCompilerDetectionHeader)
set(CMAKE_CXX_STANDARD_REQUIRED 11)
set(CMAKE_CXX_STANDARD 11)
get_property(CMAKE_CXX_KNOWN_FEATURES GLOBAL PROPERTY CMAKE_CXX_KNOWN_FEATURES)
write_compiler_detection_header(
FILE "${GENERATED_SOURCE_DIR}/scp_compiler_detection.h"
PREFIX SCP
COMPILERS AppleClang Clang GNU MSVC
FEATURES ${CMAKE_CXX_KNOWN_FEATURES}
VERSION 3.2
)
+1 -1
View File
@@ -8,7 +8,7 @@ if(DOXYGEN_FOUND)
FILE(TO_NATIVE_PATH ${OUTPUT_DIR} OUTPUT_DIR)
SET(OUTPUT_DIR \"${OUTPUT_DIR}\")
SET(INPUTS "${CMAKE_CURRENT_SOURCE_DIR}/code" "${CMAKE_CURRENT_SOURCE_DIR}/freespace2" "${CMAKE_CURRENT_SOURCE_DIR}/fred2" "${CMAKE_CURRENT_SOURCE_DIR}/wxfred2" "${CMAKE_CURRENT_SOURCE_DIR}/qtfred")
SET(INPUTS "${CMAKE_CURRENT_SOURCE_DIR}/code" "${CMAKE_CURRENT_SOURCE_DIR}/freespace2" "${CMAKE_CURRENT_SOURCE_DIR}/fred2" "${CMAKE_CURRENT_SOURCE_DIR}/wxfred2")
SET(INPUT_DIRS)
FOREACH(DIR IN LISTS INPUTS)
+2
View File
@@ -21,6 +21,8 @@ endif()
set(CMAKE_EXTRA_INCLUDE_FILES "cstddef")
CHECK_TYPE_SIZE("std::max_align_t" STD_MAX_ALIGN_T LANGUAGE CXX)
set(CMAKE_EXTRA_INCLUDE_FILES "type_traits")
CHECK_TYPE_SIZE("std::is_trivially_copyable<int>" STD_IS_TRIVIALLY_COPYABLE LANGUAGE CXX)
set(CMAKE_EXTRA_INCLUDE_FILES)
CHECK_TYPE_SIZE("char32_t" CHAR32_T LANGUAGE CXX)
+2
View File
@@ -8,6 +8,8 @@
#cmakedefine SCP_HAVE_MAX_ALIGN_T
#cmakedefine SCP_HAVE_STD_MAX_ALIGN_T
#cmakedefine01 HAVE_STD_IS_TRIVIALLY_COPYABLE
#cmakedefine01 IS_64BIT
#cmakedefine01 SCP_HAVE_STRCASECMP
-4
View File
@@ -100,10 +100,6 @@ set(CMAKE_C_FLAGS_DEBUG ${COMPILER_FLAGS_DEBUG})
set(CMAKE_EXE_LINKER_FLAGS "")
if(DEFINED ENV{LDFLAGS})
set(CMAKE_EXE_LINKER_FLAGS $ENV{LDFLAGS})
endif()
if (CLANG_USE_LIBCXX)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lc++abi")
endif()
-5
View File
@@ -27,11 +27,6 @@ endif()
set(COMPILER_FLAGS "")
set(LINKER_FLAGS "")
# Don't ignore user-set LDFLAGS
if(DEFINED ENV{LDFLAGS})
set(LINKER_FLAGS $ENV{LDFLAGS})
endif()
if (GCC_USE_GOLD)
OPTION(GCC_INCREMENTAL_LINKING "Use incremental linking" OFF)
set(LINKER_FLAGS "${LINKER_FLAGS} -fuse-ld=gold")
+1 -1
View File
@@ -6,7 +6,7 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
include("${PROJECT_SOURCE_DIR}/version_override.cmake")
endif()
set_if_not_defined(FSO_VERSION_MAJOR 22)
set_if_not_defined(FSO_VERSION_MAJOR 21)
set_if_not_defined(FSO_VERSION_MINOR 1)
set_if_not_defined(FSO_VERSION_BUILD 0)
-5
View File
@@ -66,8 +66,6 @@ TARGET_LINK_LIBRARIES(code PUBLIC jansson)
target_link_libraries(code PUBLIC variant)
target_link_libraries(code PUBLIC any)
# Headers for standalone "gui" on UNIX
IF(UNIX)
TARGET_LINK_LIBRARIES(code PUBLIC ${MONGOOSE_LIBS})
@@ -106,6 +104,3 @@ set_target_properties(code PROPERTIES XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYM
set_target_properties(code PROPERTIES XCODE_ATTRIBUTE_COPY_PHASE_STRIP[variant=Debug] "NO")
set_target_properties(code PROPERTIES XCODE_ATTRIBUTE_STRIP_INSTALLED_PRODUCT[variant=Debug] "NO")
set_target_properties(code PROPERTIES XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN[variant=Debug] "NO")
# Prevent GCC warnings in third-party BitOp... see GitHub #4366
suppress_file_warnings(scripting/lua/bitop/bit.c)
+2 -2
View File
@@ -1,11 +1,11 @@
#pragma once
#include "actions/expression/nodes/AbstractExpression.h"
#include "globalincs/pstypes.h"
#include "ParseContext.h"
#include "actions/expression/nodes/AbstractExpression.h"
namespace actions {
namespace expression {
@@ -1,11 +1,11 @@
#pragma once
#include <Parser.h>
#include <Token.h>
#include "actions/expression/ParseContext.h"
#include "actions/expression/Value.h"
#include <Parser.h>
#include <Token.h>
namespace actions {
namespace expression {
namespace nodes {
+4 -4
View File
@@ -43,13 +43,13 @@ ActionResult ParticleEffectAction::execute(ProgramLocals& locals) const
auto pmi = model_get_instance(instance);
auto pm = model_get(pmi->model_num);
model_instance_local_to_global_point_orient(&local_pos,
find_submodel_instance_point_orient(&local_pos,
&local_orient,
&locals.localPosition,
&locals.localOrient,
pm,
pmi,
locals.hostSubobject);
locals.hostSubobject,
&locals.localPosition,
&locals.localOrient);
} else {
local_pos = locals.localPosition;
local_orient = locals.localOrient;
+4 -4
View File
@@ -32,13 +32,13 @@ ActionResult PlaySoundAction::execute(ProgramLocals& locals) const
auto pmi = model_get_instance(instance);
auto pm = model_get(pmi->model_num);
model_instance_local_to_global_point_orient(&local_pos,
find_submodel_instance_point_orient(&local_pos,
&local_orient,
&locals.localPosition,
&locals.localOrient,
pm,
pmi,
locals.hostSubobject);
locals.hostSubobject,
&locals.localPosition,
&locals.localOrient);
} else {
local_pos = locals.localPosition;
local_orient = locals.localOrient;
+6 -26
View File
@@ -17,7 +17,6 @@
#include "globalincs/pstypes.h"
#include "globalincs/systemvars.h"
#include "object/waypoint.h"
#include "parse/sexp.h"
#include "physics/physics.h"
#include "ship/ship_flags.h"
@@ -72,7 +71,6 @@ extern ai_flag_name Ai_flag_names[];
#define AITTV_VIA_SEXP (1<<1) // Goober5000 - via sexp
#define AITTV_IGNORE_BANK (1<<2) // Goober5000 - ignore bank when turning
#define AITTV_SLOW_BANK_ACCEL (1<<3) // Asteroth - used by formation flying
#define AITTV_FORCE_DELTA_BANK (1<<4) // Lafiel - Always use the provided delta bank and override calculated values
#define KAMIKAZE_HULL_ON_DEATH -1000.0f // Hull strength ship gets set to if it crash-dies.
@@ -105,13 +103,11 @@ typedef struct ai_goal {
int index;
} dockee;
object_ship_wing_point_team lua_ai_target;
} ai_goal;
#define AIM_CHASE 0
#define AIM_EVADE 1
#define AIM_GET_BEHIND 2 // This mode is not actually implemented.
#define AIM_GET_BEHIND 2
#define AIM_STAY_NEAR 3 // Stay near another ship.
#define AIM_STILL 4 // Sit still. Don't move. Hold your breath. Don't blink.
#define AIM_GUARD 5 // Guard an object
@@ -131,9 +127,8 @@ typedef struct ai_goal {
#define AIM_SENTRYGUN 19 // AI mode for sentry guns only (floating turrets)
#define AIM_WARP_OUT 20 // Commence warp out sequence. Point in legal direction. Then call John's code.
#define AIM_FLY_TO_SHIP 21 // [Kazan] Fly to a ship, doesn't matter if it's hostile or friendly -- for Autopilot usage
#define AIM_LUA 22 // Generic Lua-based AI mode
#define MAX_AI_BEHAVIORS 23 // Number of AIM_xxxx types
#define MAX_AI_BEHAVIORS 22 // Number of AIM_xxxx types
#define MAX_WAYPOINTS_PER_LIST 20
#define MAX_ENEMY_DISTANCE 2500.0f // Maximum distance from which a ship will pursue an enemy.
@@ -272,8 +267,6 @@ extern pnode *Ppfp; // Free pointer in path points.
// Goober5000 (based on the "you can only remember 7 things in short-term memory" assumption)
#define MAX_IGNORE_NEW_OBJECTS 7
#define AI_DYNAMIC_PATH_RECHECK_DELAY 1*1000
typedef struct ai_info {
flagset<AI::AI_Flags> ai_flags; // Special flags for AI behavior.
int shipnum; // Ship using this slot, -1 means none.
@@ -337,7 +330,7 @@ typedef struct ai_info {
fix path_next_check_time; // Last time checked to see if would collide with model.
int path_goal_dist; // minimum distance to first path point to consider path reached
int path_subsystem_next_check; // timestamp to next check if subsystem is still visible
vec3d path_depart_rvec; //Rotational orientation associated with the path
vec3d path_depart_orient; //Rotational orientation associated with the path
int submode;
int previous_submode; // previous submode, get it?
@@ -350,9 +343,6 @@ typedef struct ai_info {
int submode_parm1; // SUSHI: Another optional parameter
fix next_predict_pos_time; // Next time to predict position.
int next_dynamic_path_check_time; //ETP: Time of next path check in ai_new_maybe_reposition_attack_subsys
vec3d last_dynamic_path_goal; //ETP: Remebers last target location in ai_new_maybe_reposition_attack_subsys
//SUSHI: like last_predicted_enemy_pos, but for aiming (which currently ignores predicted position)
//Unlike the predicted position stuff, also takes into account velocity
//Only used against small ships
@@ -415,6 +405,7 @@ typedef struct ai_info {
union {
float lead_scale; // Amount to lead current opponent by.
float stay_near_distance; // Distance to stay within for AIM_STAY_NEAR mode.
};
@@ -500,11 +491,6 @@ typedef struct ai_info {
int ai_override_rot_timestamp; // mark for when to end the current rotational maneuver override
int form_obj_slotnum; // for flying in formation object mode, the position in the formation
int multilock_check_timestamp; // when to check for multilock next
SCP_vector<std::pair<int, ship_subsys*>> ai_missile_locks_firing; // a list of missile locks (locked objnum, locked subsys) the ai is currently firing
object_ship_wing_point_team lua_ai_target;
} ai_info;
// Goober5000
@@ -548,8 +534,6 @@ extern int Ai_firing_enabled;
extern const char *Skill_level_names(int skill_level, int translate = 1);
extern int Ai_goal_signature;
extern control_info AI_ci;
// need access to following data in AiBig.cpp
extern object *Pl_objp;
extern object *En_objp;
@@ -587,7 +571,6 @@ extern void ai_do_default_behavior(object *obj);
extern void ai_start_waypoints(object *objp, waypoint_list *wp_list, int wp_flags);
extern void ai_ship_hit(object *objp_ship, object *hit_objp, vec3d *hit_normal);
extern void ai_ship_destroy(int shipnum);
extern vec3d ai_get_acc_limit(vec3d* vel_limit, const object* objp);
extern void ai_turn_towards_vector(vec3d *dest, object *objp, vec3d *slide_vec, vec3d *rel_pos, float bank_override, int flags, vec3d *rvec = nullptr, vec3d* turnrate_mod = nullptr);
extern void init_ai_object(int objnum);
extern void ai_init(void); // Call this one to parse ai.tbl.
@@ -622,7 +605,7 @@ extern int ai_find_goal_index( ai_goal* aigp, int mode, int submode = -1, int pr
extern void ai_do_safety(object *objp);
// used to get path info for fighter bay emerging and departing
int ai_acquire_emerge_path(object *pl_objp, int parent_objnum, int path_mask);
int ai_acquire_emerge_path(object *pl_objp, int parent_objnum, int path_mask, vec3d *pos, vec3d *fvec);
int ai_acquire_depart_path(object *pl_objp, int parent_objnum, int path_mask);
// used by AiBig.cpp
@@ -674,7 +657,7 @@ void ai_start_fly_to_ship(object *objp, int shipnum);
void ai_fly_to_ship();
//Moved declaration here for player ship -WMC
void ai_process_subobjects(int objnum);
void process_subobjects(int objnum);
//SUSHI: Setting ai_info stuff from both ai class and ai profile
void init_aip_from_class_and_profile(ai_info *aip, ai_class *aicp, ai_profile_t *profile);
@@ -686,7 +669,4 @@ void do_random_sidethrust(ai_info *aip, ship_info *sip);
void ai_formation_object_recalculate_slotnums(int form_objnum, int exiting_objnum = -1);
bool test_line_of_sight(vec3d* from, vec3d* to, std::unordered_set<const object*>&& excluded_objects = {}, float threshold = 10.0f, bool test_for_shields = false, bool test_for_hull = true, float* first_intersect_dist = nullptr);
#endif
+1 -9
View File
@@ -5,7 +5,7 @@
namespace AI {
FLAG_LIST(AI_Flags) {
Formation_wing, // Fly in formation as part of wing. Also used when flying waypoints.
Formation_wing, // Fly in formation as part of wing.
Awaiting_repair, // Awaiting a repair ship.
Being_repaired, // Currently docked with repair ship.
Repairing, // Repairing a ship (or going to repair a ship)
@@ -137,14 +137,6 @@ namespace AI {
Improved_missile_avoidance,
Friendlies_use_countermeasure_firechance,
Improved_subsystem_attack_pathing,
Fixed_ship_weapon_collision,
No_shield_damage_from_ship_collisions,
Reset_last_hit_target_time_for_player_hits,
Fighterbay_arrivals_use_carrier_orient,
Fighterbay_departures_use_carrier_orient,
Prevent_negative_turret_ammo,
Fix_keep_safe_distance,
Ignore_aspect_when_leading,
NUM_VALUES
};
+7 -78
View File
@@ -58,26 +58,6 @@ int ai_path_type_match(char *p)
return -1;
}
const char* AI_secondary_range_aware_select_modes[] = {
"retail",
"aware",
};
int Num_ai_secondary_range_aware_select_modes = sizeof(AI_secondary_range_aware_select_modes) / sizeof(char*);
int ai_secondary_range_select_mode_match(char* p)
{
int i;
for (i = 0; i < Num_ai_secondary_range_aware_select_modes; i++)
{
if (!stricmp(AI_secondary_range_aware_select_modes[i], p))
return i;
}
return -1;
}
void parse_ai_profiles_tbl(const char *filename)
{
int i;
@@ -468,10 +448,7 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$firing requires exact los:", AI::Profile_Flags::Require_exact_los);
set_flag(profile, "$fighterbay arrivals use carrier orientation:", AI::Profile_Flags::Fighterbay_arrivals_use_carrier_orient);
set_flag(profile, "$fighterbay departures use carrier orientation:", AI::Profile_Flags::Fighterbay_departures_use_carrier_orient);
profile->ai_path_mode = AI_PATH_MODE_NORMAL;
if (optional_string("$ai path mode:"))
{
stuff_string(buf, F_NAME, NAME_LENGTH);
@@ -504,12 +481,14 @@ void parse_ai_profiles_tbl(const char *filename)
if (path_radii >= Minimum_subsystem_path_pt_dist) {
profile->subsystem_path_radii = path_radii;
} else {
mprintf(("Warning: \"$override radius for subsystem path points:\" should be >= %i (read %i). Value will not be used.\n", Minimum_subsystem_path_pt_dist, path_radii));
mprintf(("Warning: \"$override radius for subsystem path points:\" should be >= %i (read %i). Value will not be used. ", Minimum_subsystem_path_pt_dist, path_radii));
}
}
set_flag(profile, "$use POF radius for subsystem path points:", AI::Profile_Flags::Use_subsystem_path_point_radii);
profile->bay_arrive_speed_mult = 1.0f;
profile->bay_depart_speed_mult = 1.0f;
if (optional_string("$bay arrive speed multiplier:")) {
stuff_float(&profile->bay_arrive_speed_mult);
}
@@ -565,42 +544,6 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$improved subsystem attack pathing:", AI::Profile_Flags::Improved_subsystem_attack_pathing);
set_flag(profile, "$fixed ship-weapon collisions:", AI::Profile_Flags::Fixed_ship_weapon_collision);
//Intention is to expand this feature to include a preference for close or long range weapons
//hence using something besides a simple flag.
if (optional_string("$AI secondary range awareness:"))
{
stuff_string(buf, F_NAME, NAME_LENGTH);
int j = ai_secondary_range_select_mode_match(buf);
if (j >= 0) {
profile->ai_range_aware_secondary_select_mode = j;
}
else {
Warning(LOCATION, "Invalid ai secondary range awareness mode '%s' specified", buf);
}
}
set_flag(profile, "$no shield damage from ship collisions:", AI::Profile_Flags::No_shield_damage_from_ship_collisions);
set_flag(profile, "$reset last_hit_target_time for player hits:", AI::Profile_Flags::Reset_last_hit_target_time_for_player_hits);
if (optional_string("$turret target recheck time:"))
{
stuff_float(&profile->turret_target_recheck_time);
if (profile->turret_target_recheck_time < 0) {
Warning(LOCATION, "Turret target recheck time must be positive.");
profile->turret_target_recheck_time = 2000.0f;
}
}
set_flag(profile, "$prevent negative turret ammo:", AI::Profile_Flags::Prevent_negative_turret_ammo);
set_flag(profile, "$fix keep-safe-distance:", AI::Profile_Flags::Fix_keep_safe_distance);
set_flag(profile, "$AI ignores aspect lock for leading:", AI::Profile_Flags::Ignore_aspect_when_leading);
// if we've been through once already and are at the same place, force a move
if (saved_Mp && (saved_Mp == Mp))
{
@@ -672,13 +615,11 @@ void ai_profile_t::reset()
flags.reset();
ai_path_mode = AI_PATH_MODE_NORMAL;
ai_path_mode = 0;
subsystem_path_radii = 0;
bay_arrive_speed_mult = 1.0f;
bay_depart_speed_mult = 1.0f;
bay_arrive_speed_mult = 0;
bay_depart_speed_mult = 0;
second_order_lead_predict_factor = 0;
ai_range_aware_secondary_select_mode = AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL;
turret_target_recheck_time = 2000.0f;
for (int i = 0; i < NUM_SKILL_LEVELS; ++i) {
max_incoming_asteroids[i] = 0;
@@ -756,16 +697,4 @@ void ai_profile_t::reset()
if (mod_supports_version(3, 7, 2)) {
flags.set(AI::Profile_Flags::Fix_ramming_stationary_targets_bug);
}
// and this flag has been enabled ever since 3.6.10
if (mod_supports_version(3, 6, 10)) {
flags.set(AI::Profile_Flags::Reset_last_hit_target_time_for_player_hits);
}
if (mod_supports_version(21, 4, 0)) {
flags.set(AI::Profile_Flags::Fixed_ship_weapon_collision);
}
if (mod_supports_version(22, 0, 0)) {
flags.set(AI::Profile_Flags::Fighterbay_arrivals_use_carrier_orient);
flags.set(AI::Profile_Flags::Prevent_negative_turret_ammo);
flags.set(AI::Profile_Flags::Fix_keep_safe_distance);
}
}
-9
View File
@@ -17,9 +17,6 @@
// AI Path types
#define AI_PATH_MODE_NORMAL 0
#define AI_PATH_MODE_ALT1 1
#define AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL 0
#define AI_RANGE_AWARE_SEC_SEL_MODE_AWARE 1
#define MAX_AI_PROFILES 5
@@ -110,12 +107,6 @@ public:
// How much 0-1 of a second-order lead prediction factor to add to lead indicators. Affects only the HUD indicator, and autoaim.
float second_order_lead_predict_factor;
//Controls if the AI is dumb enough to keep trying to use out-of-range secondaries.
int ai_range_aware_secondary_select_mode;
// how often turrets shoulds check for new targets, milliseconds
float turret_target_recheck_time;
void reset();
};
+65 -86
View File
@@ -17,12 +17,12 @@
#include "io/timer.h"
#include "math/staticrand.h"
#include "mission/missionparse.h"
#include "object/objcollide.h"
#include "object/object.h"
#include "ship/afterburner.h"
#include "ship/ship.h"
#include "utils/Random.h"
#include "weapon/weapon.h"
#include "object/objcollide.h"
#ifdef _MSC_VER
#pragma optimize("", off)
@@ -407,7 +407,7 @@ int ai_big_maybe_follow_subsys_path(int do_dot_check)
// get world pos of eye (stored in geye)
ep = &(pm->view_positions[shipp->current_viewpoint]);
model_instance_local_to_global_point( &geye, &ep->pnt, pm, pmi, 0, &Pl_objp->orient, &Pl_objp->pos );
model_instance_find_world_point( &geye, &ep->pnt, pm, pmi, 0, &Pl_objp->orient, &Pl_objp->pos );
// get world pos of subsystem
vm_vec_unrotate(&gsubpos, &aip->targeted_subsys->system_info->pnt, &En_objp->orient);
@@ -549,7 +549,7 @@ bool ai_new_maybe_reposition_attack_subsys() {
// get world pos of eye (stored in geye)
ep = &(pm->view_positions[Ships[Pl_objp->instance].current_viewpoint]);
model_local_to_global_point(&geye, &ep->pnt, pm->id, 0, &Pl_objp->orient, &Pl_objp->pos);
model_find_world_point(&geye, &ep->pnt, pm->id, 0, &Pl_objp->orient, &Pl_objp->pos);
// get world pos of subsystem
vm_vec_unrotate(&gsubpos, &aip->targeted_subsys->system_info->pnt, &En_objp->orient);
@@ -560,69 +560,61 @@ bool ai_new_maybe_reposition_attack_subsys() {
return false;
// not in sight, gotta get there
vec3d tgt2pl = Pl_objp->pos - target_objp->pos;
vec3d tgt2subsys = gsubpos - target_objp->pos;
float pl_dist = vm_vec_mag(&tgt2pl);
float subsys_dist = vm_vec_mag(&tgt2subsys) * 1.3f; // add a bit to this, we don't want to go directly at the subsys, we want to go above it
float angle2subsys = vm_vec_delta_ang(&tgt2pl, &tgt2subsys, nullptr);
float angle2goal = angle2subsys;
if (angle2goal > 1.2f) // let's not try to go more than about 70 degrees around the ship
angle2goal = 1.2f;
// we'll start by travelling along the sphere at our current distance around the ship
// remember that goal_pos will be relative to our target_ship (so we can scale in and out easily)
vec3d goal_pos, cross;
vm_vec_cross(&cross, &tgt2pl, &tgt2subsys);
vm_vec_normalize(&cross);
vm_vec_negate(&cross);
vm_rot_point_around_line(&goal_pos, &tgt2pl, angle2goal, &vmd_zero_vector, &cross);
// then scale in (or out) towards our subsys
vec3d world_goal_pos;
float new_radius = ((subsys_dist - pl_dist) * (angle2goal / angle2subsys)) + pl_dist;
vm_vec_copy_scale(&world_goal_pos, &goal_pos, new_radius / pl_dist);
world_goal_pos += target_objp->pos;
vec3d* good_pos = nullptr;
// For performance reasons we only recheck once a second, otherwise we reuse the last found target.
if (timestamp_elapsed(aip->next_dynamic_path_check_time)) {
vec3d tgt2pl = Pl_objp->pos - target_objp->pos;
vec3d tgt2subsys = gsubpos - target_objp->pos;
float pl_dist = vm_vec_mag(&tgt2pl);
float subsys_dist = vm_vec_mag(&tgt2subsys) * 1.3f; // add a bit to this, we don't want to go directly at the subsys, we want to go above it
float angle2subsys = vm_vec_delta_ang(&tgt2pl, &tgt2subsys, nullptr);
float angle2goal = angle2subsys;
if (angle2goal > 1.2f) // let's not try to go more than about 70 degrees around the ship
angle2goal = 1.2f;
// we'll start by travelling along the sphere at our current distance around the ship
// remember that goal_pos will be relative to our target_ship (so we can scale in and out easily)
vec3d goal_pos, cross;
vm_vec_cross(&cross, &tgt2pl, &tgt2subsys);
vm_vec_normalize(&cross);
vm_vec_negate(&cross);
vm_rot_point_around_line(&goal_pos, &tgt2pl, angle2goal, &vmd_zero_vector, &cross);
// then scale in (or out) towards our subsys
vec3d world_goal_pos;
float new_radius = ((subsys_dist - pl_dist) * (angle2goal / angle2subsys)) + pl_dist;
vm_vec_copy_scale(&world_goal_pos, &goal_pos, new_radius / pl_dist);
world_goal_pos += target_objp->pos;
if (pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f)) {
// ok best case won't work
if (pl_dist > subsys_dist) {
//we're farther away than the subsys, let's try at our current distance
world_goal_pos = goal_pos + target_objp->pos;
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
good_pos = &world_goal_pos;
}
// ok, let's start moving it away and see if that's better
for (float i = 1.0f; i < 4.0f; i += 1.0f) {
if (good_pos)
break;
vm_vec_copy_scale(&world_goal_pos, &goal_pos, (new_radius / pl_dist) * powf(1.3f,i));
world_goal_pos += target_objp->pos;
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
good_pos = &world_goal_pos;
}
// welp this sucks, just run away i guess
if (!good_pos) {
vm_vec_copy_normalize(&world_goal_pos, &tgt2pl);
world_goal_pos *= target_objp->radius + pl_dist;
world_goal_pos += target_objp->pos;
if (pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f)) {
// ok best case won't work
if (pl_dist > subsys_dist) {
//we're farther away than the subsys, let's try at our current distance
world_goal_pos = goal_pos + target_objp->pos;
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
good_pos = &world_goal_pos;
}
}
else
good_pos = &world_goal_pos;
aip->next_dynamic_path_check_time = timestamp( AI_DYNAMIC_PATH_RECHECK_DELAY );
// ok, let's start moving it away and see if that's better
for (float i = 1.0f; i < 4.0f; i += 1.0f) {
if (good_pos)
break;
vm_vec_copy_scale(&world_goal_pos, &goal_pos, (new_radius / pl_dist) * powf(1.3f,i));
world_goal_pos += target_objp->pos;
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
good_pos = &world_goal_pos;
}
// welp this sucks, just run away i guess
if (!good_pos) {
vm_vec_copy_normalize(&world_goal_pos, &tgt2pl);
world_goal_pos *= target_objp->radius + pl_dist;
world_goal_pos += target_objp->pos;
good_pos = &world_goal_pos;
}
}
else{ //Not enough time has passed till recheck.
good_pos = &aip->last_dynamic_path_goal;
}
else
good_pos = &world_goal_pos;
ai_turn_towards_vector(good_pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
float subsys_distance = vm_vec_dist(&Pl_objp->pos, &gsubpos);
@@ -785,28 +777,14 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
ai_turn_towards_vector(enemy_pos, Pl_objp, nullptr, rel_pos, 0.0f, 0, rvec);
// set an optimal range if applicable
float optimal_range = 0.0f;
ship_weapon* weapons = &Ships[Pl_objp->instance].weapons;
weapon_info* wip = nullptr;
ship* shipp = &Ships[Pl_objp->instance];
if (weapons->num_primary_banks >= 1 && weapons->current_primary_bank >= 0 && !shipp->flags[Ship::Ship_Flags::Primaries_locked]) {
wip = &Weapon_info[weapons->primary_bank_weapons[weapons->current_primary_bank]];
} else if (weapons->num_secondary_banks >= 1 && weapons->current_secondary_bank >= 0 && !shipp->flags[Ship::Ship_Flags::Secondaries_locked]) {
wip = &Weapon_info[weapons->secondary_bank_weapons[weapons->current_secondary_bank]];
}
if (wip != nullptr && wip->optimum_range > 0)
optimal_range = wip->optimum_range;
// calc range of primary weapon
weapon_travel_dist = ai_get_weapon_dist(&Ships[Pl_objp->instance].weapons);
if ( aip->targeted_subsys != NULL ) {
if (dist_to_enemy > (optimal_range > 0.0f ? optimal_range : (weapon_travel_dist-20))) {
if (dist_to_enemy > (weapon_travel_dist-20)) {
accelerate_ship(aip, 1.0f);
ship *shipp = &Ships[Pl_objp->instance];
if ((aip->ai_flags[AI::AI_Flags::Free_afterburner_use] || aip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use]) && !(shipp->flags[Ship::Ship_Flags::Afterburner_locked]) && (dot_to_enemy > 0.75f)) {
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
afterburners_start(Pl_objp);
@@ -831,7 +809,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
} else if (dot_to_enemy < 0.75f) {
accel = 0.75f;
} else {
if (dist_to_enemy > (optimal_range > 0.0f ? optimal_range : (weapon_travel_dist/2.0f))) {
if (dist_to_enemy > weapon_travel_dist/2.0f) {
accel = 1.0f;
} else {
accel = 1.0f - dot_to_enemy;
@@ -848,6 +826,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
} else {
accelerate_ship(aip, accel);
ship *shipp = &Ships[Pl_objp->instance];
if ((aip->ai_flags[AI::AI_Flags::Free_afterburner_use] || aip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use]) && !(shipp->flags[Ship::Ship_Flags::Afterburner_locked]) && (accel > 0.95f)) {
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
afterburners_start(Pl_objp);
@@ -935,7 +914,7 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
}
}
if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank]) && weapon_target_satisfies_lock_restrictions(swip, En_objp)) {
if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank])) {
float firing_range;
if (swip->wi_flags[Weapon::Info_Flags::Local_ssm])
firing_range=swip->lssm_lock_range;
@@ -965,11 +944,11 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
} else {
t = swip->fire_wait;
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
} else {
swp->burst_counter[current_bank_adjusted] = 0;
}
swp->burst_seed[current_bank_adjusted] = Random::next();
swp->burst_seed[current_bank_adjusted] = rand32();
}
} else {
if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
@@ -978,11 +957,11 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
} else {
t = set_secondary_fire_delay(aip, temp_shipp, swip, false);
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
} else {
swp->burst_counter[current_bank_adjusted] = 0;
}
swp->burst_seed[current_bank_adjusted] = Random::next();
swp->burst_seed[current_bank_adjusted] = rand32();
}
}
swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
@@ -1492,7 +1471,7 @@ void ai_big_strafe_attack()
if ( t > 0.0f && t < 1.5f ) {
// set up goal_point for avoid path to turn towards
aip->goal_point = Pl_objp->pos;
switch(Random::next(4)) {
switch(rand()%4) {
case 0:
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, 2.0f);
break;
@@ -1724,7 +1703,7 @@ void ai_big_strafe_retreat1()
if (Missiontime - aip->submode_start_time > fl2f(1.50f)) {
// set up goal_point for avoid path to turn towards
aip->prev_goal_point = Pl_objp->pos;
switch(Random::next(4)) {
switch(rand()%4) {
case 0:
vm_vec_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec);
break;
+537 -957
View File
File diff suppressed because it is too large. Load diff
+87 -142
View File
@@ -11,7 +11,6 @@
#include "ai/aigoals.h"
#include "ai/ailua.h"
#include "globalincs/linklist.h"
#include "mission/missionlog.h"
#include "mission/missionparse.h"
@@ -24,7 +23,6 @@
#include "playerman/player.h"
#include "scripting/scripting.h"
#include "ship/ship.h"
#include "ship/awacs.h"
#include "weapon/weapon.h"
// Just a reference to this struct being for looking up a ship's team in TVT
@@ -54,6 +52,12 @@ struct ship_registry_entry;
// goals given from the player to other ships in the game are also handled in this
// code
#define AI_GOAL_ACHIEVABLE 1
#define AI_GOAL_NOT_ACHIEVABLE 2
#define AI_GOAL_NOT_KNOWN 3
#define AI_GOAL_SATISFIED 4
int Ai_goal_signature;
int Num_ai_dock_names = 0;
char Ai_dock_names[MAX_AI_DOCK_NAMES][NAME_LENGTH];
@@ -95,7 +99,7 @@ int Num_ai_goals = sizeof(Ai_goal_names) / sizeof(ai_goal_list);
// HUD what a ship's current orders are. If the AI goal doesn't correspond to something that
// ought to be printable, then NULL is used.
// JAS: Converted to a function in order to externalize the strings
const char *Ai_goal_text(int goal, int submode)
const char *Ai_goal_text(int goal)
{
switch(goal) {
case AI_GOAL_CHASE:
@@ -126,15 +130,10 @@ const char *Ai_goal_text(int goal, int submode)
return XSTR( "rearm ", 484);
case AI_GOAL_FLY_TO_SHIP:
return XSTR( "rendezvous with ", 1597);
case AI_GOAL_LUA:
auto mode = ai_lua_find_mode(submode);
if (mode == nullptr)
return nullptr;
return mode->hudText;
}
// Avoid compiler warning
return nullptr;
return NULL;
}
void ai_maybe_add_form_goal(wing* wingp)
@@ -188,11 +187,6 @@ void ai_post_process_mission()
object *objp;
int i;
// make sure team visibility is updated first
if ( !Fred_running ) {
awacs_process();
}
// Check ships in player starting wings. Those ships should follow these rules:
// (1) if they have no orders, they should get a form on my wing order
// (2) if they have an order, they are free to act on it.
@@ -209,6 +203,7 @@ void ai_post_process_mission()
wingp = &Wings[Starting_wings[i]];
ai_maybe_add_form_goal( wingp );
}
}
}
@@ -240,8 +235,8 @@ int ai_query_goal_valid( int ship, int ai_goal_type )
{
int accepted;
if (ai_goal_type == AI_GOAL_NONE || ai_goal_type == AI_GOAL_LUA)
return 1; // anything can have no orders or Lua'd orders.
if (ai_goal_type == AI_GOAL_NONE)
return 1; // anything can have no orders.
accepted = 0;
@@ -266,20 +261,14 @@ void ai_remove_ship_goal( ai_info *aip, int index )
// ai goal code look
Assert ( index >= 0 ); // must have a valid goal
if (index == aip->active_goal)
{
// rearm/repair needs a bit of extra cleanup
if (aip->goals[index].ai_mode == AI_GOAL_REARM_REPAIR)
ai_abort_rearm_request(&Objects[Ships[aip->shipnum].objnum]);
aip->active_goal = AI_GOAL_NONE;
}
aip->goals[index].ai_mode = AI_GOAL_NONE;
aip->goals[index].signature = -1;
aip->goals[index].priority = -1;
aip->goals[index].flags.reset(); // must reset the flags since not doing so will screw up goal sorting.
if ( index == aip->active_goal )
aip->active_goal = AI_GOAL_NONE;
// mwa -- removed this line 8/5/97. Just because we remove a goal doesn't mean to do the default
// behavior. We will make the call commented out below in a more reasonable location
//ai_do_default_behavior( &Objects[Ships[aip->shipnum].objnum] );
@@ -304,11 +293,9 @@ void ai_clear_ship_goals( ai_info *aip )
}
// add scripting hook for 'On Goals Cleared' --wookieejedi
if (Script_system.IsActiveAction(CHA_ONGOALSCLEARED)) {
Script_system.SetHookObject("Ship", &Objects[Ships[aip->shipnum].objnum]);
Script_system.RunCondition(CHA_ONGOALSCLEARED, &Objects[Ships[aip->shipnum].objnum]);
Script_system.RemHookVars({"Ship"});
}
Script_system.SetHookObject("Ship", &Objects[Ships[aip->shipnum].objnum]);
Script_system.RunCondition(CHA_ONGOALSCLEARED);
Script_system.RemHookVars({"Ship"});
}
void ai_clear_wing_goals( wing *wingp )
@@ -659,7 +646,7 @@ void ai_goal_fixup_dockpoints(ai_info *aip, ai_goal *aigp)
// from the mission goals (i.e. those goals which come from events) in that we don't
// use sexpressions for goals from the player...so we enumerate all the parameters
void ai_add_goal_sub_player(int type, int mode, int submode, char *target_name, ai_goal *aigp, const object_ship_wing_point_team& lua_target )
void ai_add_goal_sub_player(int type, int mode, int submode, char *target_name, ai_goal *aigp )
{
Assert ( (type == AIG_TYPE_PLAYER_WING) || (type == AIG_TYPE_PLAYER_SHIP) );
@@ -667,7 +654,6 @@ void ai_add_goal_sub_player(int type, int mode, int submode, char *target_name,
aigp->type = type; // from player for sure -- could be to ship or to wing
aigp->ai_mode = mode; // major mode for this goal
aigp->ai_submode = submode; // could mean different things depending on mode
aigp->lua_ai_target = lua_target;
if ( mode == AI_GOAL_WARP ) {
if (submode >= 0) {
@@ -813,7 +799,7 @@ void ai_add_ship_goal_scripting(int mode, int submode, int priority, char *shipn
// is issued to ship or wing (from player), mode is AI_GOAL_*. submode is the submode the
// ship should go into. shipname is the object of the action. aip is the ai_info pointer
// of the ship receiving the order
void ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, ai_info *aip, const object_ship_wing_point_team& lua_target)
void ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, ai_info *aip )
{
int empty_index;
ai_goal *aigp;
@@ -822,7 +808,7 @@ void ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, a
// get a pointer to the goal structure
aigp = &aip->goals[empty_index];
ai_add_goal_sub_player( type, mode, submode, shipname, aigp, lua_target );
ai_add_goal_sub_player( type, mode, submode, shipname, aigp );
// if the goal is to dock, then we must determine which dock points on the two ships to use.
// If the target of the dock is a cargo type container, then we should use DOCK_TYPE_CARGO
@@ -839,7 +825,7 @@ void ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, a
// adds a goal from the player to the given wing (which in turn will add it to the proper
// ships in the wing
void ai_add_wing_goal_player( int type, int mode, int submode, char *shipname, int wingnum, const object_ship_wing_point_team& lua_target)
void ai_add_wing_goal_player( int type, int mode, int submode, char *shipname, int wingnum )
{
int i, empty_index;
wing *wingp = &Wings[wingnum];
@@ -850,7 +836,7 @@ void ai_add_wing_goal_player( int type, int mode, int submode, char *shipname, i
int num = wingp->ship_index[i];
if ( num == -1 ) // ship must have been destroyed or departed
continue;
ai_add_ship_goal_player( type, mode, submode, shipname, &Ai_info[Ships[num].ai_index], lua_target );
ai_add_ship_goal_player( type, mode, submode, shipname, &Ai_info[Ships[num].ai_index] );
}
}
@@ -858,7 +844,7 @@ void ai_add_wing_goal_player( int type, int mode, int submode, char *shipname, i
// there are more waves to come. We use the same method here as when adding a goal to
// a ship -- find the first empty entry. If none exists, take the oldest entry and replace it.
empty_index = ai_goal_find_empty_slot( wingp->ai_goals, -1 );
ai_add_goal_sub_player( type, mode, submode, shipname, &wingp->ai_goals[empty_index], lua_target );
ai_add_goal_sub_player( type, mode, submode, shipname, &wingp->ai_goals[empty_index] );
}
@@ -1042,24 +1028,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_goal *aigp, char *actor_name )
break;
default:
const ai_mode_lua* luaAIMode = ai_lua_find_mode(op);
if(luaAIMode != nullptr){
//Found a LuaAI mode sexp for this
int localnode = CDR(node);
aigp->ai_mode = AI_GOAL_LUA;
aigp->ai_submode = op;
if(luaAIMode->needsTarget) {
eval_object_ship_wing_point_team(&aigp->lua_ai_target, localnode);
localnode = CDR(localnode);
}
aigp->priority = atoi( CTEXT(localnode) );
}
else {
UNREACHABLE("Invalid SEXP-OP number %d for an AI goal!", op);
}
Int3(); // get ALLENDER -- invalid ai-goal specified for ai object!!!!
}
if ( aigp->priority > MAX_GOAL_PRIORITY ) {
@@ -1235,28 +1204,8 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp )
priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
goalmode = AI_GOAL_IGNORE_NEW;
break;
case OP_AI_FORM_ON_WING:
priority = 99;
goalmode = AI_GOAL_FORM_ON_WING;
break;
default:
const ai_mode_lua* luaAIMode = ai_lua_find_mode(op);
if(luaAIMode != nullptr){
//Found a LuaAI mode sexp for this
int localnode = CDR(node);
goalmode = AI_GOAL_LUA;
goalsubmode = op;
if(luaAIMode->needsTarget) {
localnode = CDR(localnode);
}
priority = localnode >= 0 ? atoi( CTEXT(localnode) ) : -1;
}
else {
UNREACHABLE("Invalid SEXP-OP %s (number %d) for an AI goal!", Sexp_nodes[node].text, op);
}
Int3( );
break;
};
@@ -1276,13 +1225,13 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp )
return goalindex;
}
// code to remove ai goals from wings.
// code to add ai goals to wings.
void ai_remove_wing_goal_sexp(int sexp, wing *wingp)
{
int i;
int goalindex = -1;
// remove the ai goal for any ship that is currently arrived in the game (only if fred isn't running)
// add the ai goal for any ship that is currently arrived in the game (only if fred isn't running)
if ( !Fred_running ) {
for (i = 0; i < wingp->current_count; i++) {
int num = wingp->ship_index[i];
@@ -1294,8 +1243,6 @@ void ai_remove_wing_goal_sexp(int sexp, wing *wingp)
}
}
// remove the sexpression index from the wing's list of goal sexpressions if
// there are more waves to come
if ((wingp->num_waves - wingp->current_wave > 0) || Fred_running)
{
ai_remove_goal_sexp_sub( sexp, wingp->ai_goals );
@@ -1449,10 +1396,10 @@ void ai_copy_mission_wing_goal( ai_goal *aigp, ai_info *aip )
// function to determine if an ai goal is achieveable or not. Will return
// one of the AI_GOAL_* values. Also determines is a goal was successful.
ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
int ai_mission_goal_achievable( int objnum, ai_goal *aigp )
{
int status;
ai_achievability return_val;
int return_val;
object *objp;
ai_info *aip;
int index = -1, sindex = -1;
@@ -1465,11 +1412,8 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
// these orders are always achievable.
if ( (aigp->ai_mode == AI_GOAL_KEEP_SAFE_DISTANCE)
|| (aigp->ai_mode == AI_GOAL_CHASE_ANY) || (aigp->ai_mode == AI_GOAL_STAY_STILL)
|| (aigp->ai_mode == AI_GOAL_PLAY_DEAD) || (aigp->ai_mode == AI_GOAL_PLAY_DEAD_PERSISTENT))
return ai_achievability::ACHIEVABLE;
if (aigp->ai_mode == AI_GOAL_LUA)
return ai_lua_is_achievable(aigp, objnum);
|| (aigp->ai_mode == AI_GOAL_PLAY_DEAD) || (aigp->ai_mode == AI_GOAL_PLAY_DEAD_PERSISTENT) )
return AI_GOAL_ACHIEVABLE;
// warp (depart) only achievable if there's somewhere to depart to
if (aigp->ai_mode == AI_GOAL_WARP)
@@ -1478,14 +1422,14 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
// always valid if has working subspace drive, not disabled, and not limited by subsystem strength
if ( ship_can_warp_full_check(shipp) )
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
// if we can't warp, we can only depart if the ship (or its wing) departs to a fighter bay and the mothership is present
if (ship_can_bay_depart(shipp)) {
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
}
else {
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
}
}
@@ -1497,10 +1441,10 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
sindex = ship_name_lookup( aigp->target_name );
if (sindex < 0)
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
aigp->flags.set(AI::Goal_Flags::Goal_override);
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
}
// check to see if we have a valid list. If not, then try to set one up. If that
@@ -1511,10 +1455,10 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
if ( aigp->wp_list == NULL ) {
Warning(LOCATION, "Unknown waypoint list %s - not found in mission file. Killing ai goal", aigp->target_name );
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
}
}
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
}
// chasing all ships of a certain ship class is achievable if there are ships of that class present;
@@ -1523,14 +1467,14 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
if ( aigp->ai_mode == AI_GOAL_CHASE_SHIP_CLASS ) {
for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
if ((objp->type == OBJ_SHIP) && !strcmp(aigp->target_name, Ship_info[Ships[objp->instance].ship_info_index].name)) {
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
}
}
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
}
return_val = ai_achievability::SATISFIED;
return_val = AI_GOAL_SATISFIED;
// next, determine if the goal has been completed successfully
switch ( aigp->ai_mode )
@@ -1632,7 +1576,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
}
if ( status )
return_val = ai_achievability::NOT_ACHIEVABLE;
return_val = AI_GOAL_NOT_ACHIEVABLE;
} else {
status = 0;
}
@@ -1647,7 +1591,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
if ( !status ) {
status = mission_log_get_time( LOG_WING_DESTROYED, aigp->target_name, NULL, NULL);
if ( status )
return_val = ai_achievability::NOT_ACHIEVABLE;
return_val = AI_GOAL_NOT_ACHIEVABLE;
}
break;
@@ -1661,14 +1605,14 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
Assert( aigp->target_instance >= 0 );
if ( Weapons[aigp->target_instance].objnum == -1 )
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
// if the signatures don't match, then goal isn't achievable.
if ( Objects[Weapons[aigp->target_instance].objnum].signature != aigp->target_signature )
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
// otherwise, we should be good to go
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
break;
}
@@ -1692,22 +1636,22 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
sindex = wing_name_lookup( aigp->target_name );
if (sindex < 0)
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
wing *wingp = &Wings[sindex];
if ( wingp->flags[Ship::Wing_Flags::Gone] )
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
else if ( wingp->total_arrived_count == 0 )
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
else
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
}
// Goober5000 - undocking from an unspecified object is always achievable;
// undocking from a specified object is handled below
else if ( (aigp->ai_mode == AI_GOAL_UNDOCK) && (aigp->target_name == NULL) )
{
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
}
else
{
@@ -1748,7 +1692,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
{
// if the ship has no turrets, we can't disarm it!
if (Ships[ship_name_lookup(aigp->target_name)].subsys_info[SUBSYSTEM_TURRET].type_count == 0)
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
}
// if the goal is an ignore/disable/disarm goal, then
@@ -1790,12 +1734,12 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
if ( (aigp->dockee.index == -1) || (aigp->docker.index == -1) ) {
Int3(); // for now, allender wants to know about these things!!!!
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
}
// if ship is disabled, don't know if it can dock or not
if ( Ships[objp->instance].flags[Ship::Ship_Flags::Disabled] )
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
// we must also determine if we're prevented from docking for any reason
sindex = ship_name_lookup(aigp->target_name);
@@ -1817,7 +1761,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
else if (obstacle_objp != objp)
{
// return NOT_KNOWN which will place the goal on hold until the dockpoint is clear
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
}
}
@@ -1837,7 +1781,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
ai_add_goal_ship_internal( aip, AI_GOAL_UNDOCK, Ships[obstacle_objp->instance].ship_name, -1, -1, 0 );
// return NOT_KNOWN which will place the goal on hold until the undocking is complete.
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
}
}
@@ -1851,7 +1795,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
{
// only put it on hold if it's someone other than the guy we're undocking from right now!!
if (aip->goal_objnum != Ships[ship_name_lookup(aigp->target_name)].objnum)
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
}
} else if ( (aigp->ai_mode == AI_GOAL_DESTROY_SUBSYSTEM) && (status == SHIP_STATUS_ARRIVED) ) {
@@ -1862,11 +1806,11 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
sindex = ship_name_lookup( aigp->target_name );
if ( sindex != -1 ) {
aigp->ai_submode = ship_find_subsys( &Ships[sindex], aigp->docker.name );
aigp->ai_submode = ship_get_subsys_index( &Ships[sindex], aigp->docker.name );
aigp->flags.remove(AI::Goal_Flags::Subsys_needs_fixup);
} else {
Int3();
return ai_achievability::NOT_ACHIEVABLE; // force this goal to be invalid
return AI_GOAL_NOT_ACHIEVABLE; // force this goal to be invalid
}
}
} else if ( ((aigp->ai_mode == AI_GOAL_IGNORE) || (aigp->ai_mode == AI_GOAL_IGNORE_NEW)) && (status == SHIP_STATUS_ARRIVED) ) {
@@ -1879,7 +1823,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
ai_ignore_object(objp, ignored, (aigp->ai_mode == AI_GOAL_IGNORE_NEW));
return ai_achievability::SATISFIED;
return AI_GOAL_SATISFIED;
}
switch ( aigp->ai_mode )
@@ -1900,13 +1844,13 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
case AI_GOAL_FLY_TO_SHIP:
{
if ( status == SHIP_STATUS_ARRIVED )
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
else if ( status == SHIP_STATUS_NOT_ARRIVED )
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
else if ( status == SHIP_STATUS_GONE )
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
else if ( status == SHIP_STATUS_UNKNOWN )
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
Int3(); // get allender -- bad logic
break;
@@ -1921,61 +1865,66 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
// we mark the goal as not achievable.
if ( status == SHIP_STATUS_NOT_ARRIVED ) {
Int3(); // get Allender. this shouldn't happen!!!
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
}
if ( status == SHIP_STATUS_GONE )
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
sindex = ship_name_lookup( aigp->target_name );
if ( sindex < 0 ) {
Int3();
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
}
// if desitnation currently being repaired, then goal is stil active
if ( Ai_info[Ships[sindex].ai_index].ai_flags[AI::AI_Flags::Being_repaired] )
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
// if the destination ship is dying or departing (but not completed yet), the mark goal as
// not achievable.
if ( Ships[sindex].is_dying_or_departing())
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
// if the destination object is no longer awaiting repair, then remove the item
if ( !(Ai_info[Ships[sindex].ai_index].ai_flags[AI::AI_Flags::Awaiting_repair]) )
return ai_achievability::NOT_ACHIEVABLE;
return AI_GOAL_NOT_ACHIEVABLE;
// not repairing anything means that he can do this goal!!!
if ( !(aip->ai_flags[AI::AI_Flags::Repairing]) )
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
// test code!!!
if ( aip->goal_objnum == -1 ) {
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
}
// if he is repairing something, he can satisfy his repair goal (his goal_objnum)
// return GOAL_NOT_KNOWN which is kind of a hack which puts the goal on hold until it can be
// satisfied.
if ( aip->goal_objnum != Ships[sindex].objnum )
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
return ai_achievability::ACHIEVABLE;
return AI_GOAL_ACHIEVABLE;
}
default:
Int3(); // invalid case in switch:
}
return ai_achievability::NOT_KNOWN;
return AI_GOAL_NOT_KNOWN;
}
// Compare function for sorting ai_goals based on priority.
// Return values set to sort array in _decreasing_ order.
int ai_goal_priority_compare(const ai_goal *ga, const ai_goal *gb)
int ai_goal_priority_compare(const void *a, const void *b)
{
ai_goal *ga, *gb;
ga = (ai_goal *) a;
gb = (ai_goal *) b;
// first, sort based on whether or not the ON_HOLD flag is set for the goal.
// If the flag is set, don't push the goal higher in the list even if priority
// is higher since goal cannot currently be achieved.
@@ -2023,7 +1972,7 @@ int ai_goal_priority_compare(const ai_goal *ga, const ai_goal *gb)
void prioritize_goals(ai_info *aip)
{
// First sort based on priority field.
insertion_sort(aip->goals, MAX_AI_GOALS, ai_goal_priority_compare);
insertion_sort(aip->goals, MAX_AI_GOALS, sizeof(ai_goal), ai_goal_priority_compare);
}
// Scan the list of goals at aip->goals.
@@ -2037,7 +1986,7 @@ void validate_mission_goals(int objnum, ai_info *aip)
// loop through all of the goals to determine which goal should be followed.
// This determination will be based on priority, and the time at which it was issued.
for ( i = 0; i < MAX_AI_GOALS; i++ ) {
ai_achievability state;
int state;
ai_goal *aigp;
aigp = &aip->goals[i];
@@ -2056,18 +2005,18 @@ void validate_mission_goals(int objnum, ai_info *aip)
state = ai_mission_goal_achievable( objnum, aigp );
// if this order is no longer a valid one, remove it
if ( (state == ai_achievability::NOT_ACHIEVABLE) || (state == ai_achievability::SATISFIED) ) {
if ( (state == AI_GOAL_NOT_ACHIEVABLE) || (state == AI_GOAL_SATISFIED) ) {
ai_remove_ship_goal( aip, i );
continue;
}
// if the status is achievable, and the on_hold flag is set, clear the flagb
if ( (state == ai_achievability::ACHIEVABLE) && (aigp->flags[AI::Goal_Flags::Goal_on_hold]) )
if ( (state == AI_GOAL_ACHIEVABLE) && (aigp->flags[AI::Goal_Flags::Goal_on_hold]) )
aigp->flags.remove(AI::Goal_Flags::Goal_on_hold);
// if the goal is not known, then set the ON_HOLD flag so that it doesn't get counted as
// a goal to be pursued
if (state == ai_achievability::NOT_KNOWN)
if (state == AI_GOAL_NOT_KNOWN)
aigp->flags.set(AI::Goal_Flags::Goal_on_hold); // put this goal on hold until it becomes true
}
@@ -2435,17 +2384,13 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
other_obj = &Objects[Ships[shipnum].objnum];
ai_rearm_repair( objp, current_goal->docker.index, other_obj, current_goal->dockee.index );
break;
case AI_GOAL_LUA:
ai_lua_start(current_goal, objp);
break;
default:
Int3();
break;
}
if (old_form_objnum >= 0)
if (old_form_objnum != -1)
ai_formation_object_recalculate_slotnums(old_form_objnum);
}
+4 -8
View File
@@ -14,7 +14,6 @@
#include "globalincs/globals.h"
#include "globalincs/pstypes.h"
#include "parse/sexp.h"
struct wing;
struct ai_info;
@@ -60,7 +59,6 @@ struct ai_goal;
#define AI_GOAL_IGNORE_NEW (1<<24) // 0x01000000
#define AI_GOAL_CHASE_SHIP_CLASS (1<<25) // 0x02000000
#define AI_GOAL_PLAY_DEAD_PERSISTENT (1<<26) // 0x03000000
#define AI_GOAL_LUA (1<<27) // 0x04000000
// now the masks for ship types
@@ -84,8 +82,6 @@ struct ai_goal;
#define MAX_AI_DOCK_NAMES 25
enum class ai_achievability { ACHIEVABLE, NOT_ACHIEVABLE, NOT_KNOWN, SATISFIED };
typedef flag_def_list ai_goal_list;
extern ai_goal_list Ai_goal_names[];
@@ -94,7 +90,7 @@ extern int Num_ai_goals;
extern int Num_ai_dock_names;
extern char Ai_dock_names[MAX_AI_DOCK_NAMES][NAME_LENGTH];
extern const char *Ai_goal_text(int goal, int submode);
extern const char *Ai_goal_text(int goal);
// extern function definitions
extern void ai_post_process_mission();
@@ -107,14 +103,14 @@ extern int ai_goal_num(ai_goal *goals);
extern void ai_add_ship_goal_scripting(int mode, int submode, int priority, char *shipname, ai_info *aip);
extern void ai_add_ship_goal_sexp( int sexp, int type, ai_info *aip );
extern void ai_add_wing_goal_sexp( int sexp, int type, wing *wingp );
extern void ai_add_goal_sub_sexp( int sexp, int type, ai_goal *aigp, char *actor_name);
extern void ai_add_goal_sub_sexp( int sexp, int type, ai_goal *aigp, char *actor_name );
extern int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp );
extern void ai_remove_wing_goal_sexp( int sexp, wing *wingp );
// adds goals to ships/sings through player orders
extern void ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, ai_info *aip, const object_ship_wing_point_team& lua_target = object_ship_wing_point_team());
extern void ai_add_wing_goal_player( int type, int mode, int submode, char *shipname, int wingnum, const object_ship_wing_point_team& lua_target = object_ship_wing_point_team());
extern void ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, ai_info *aip );
extern void ai_add_wing_goal_player( int type, int mode, int submode, char *shipname, int wingnum );
extern void ai_remove_ship_goal( ai_info *aip, int index );
extern void ai_clear_ship_goals( ai_info *aip );
+1 -1
View File
@@ -32,6 +32,6 @@ int check_ok_to_fire(int objnum, int target_objnum, weapon_info *wip, int second
bool check_los(int objnum, int target_objnum, float threshold, int primary_bank, int secondary_bank, ship_subsys* turret);
//Does all the stuff needed to aim and fire a turret.
void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss);
void ai_fire_from_turret(ship *shipp, ship_subsys *ss);
#endif
-239
View File
@@ -1,239 +0,0 @@
#include "ai/ailua.h"
#include "hud/hudsquadmsg.h"
#include "iff_defs/iff_defs.h"
#include "parse/sexp/sexp_lookup.h"
#include "parse/sexp/LuaAISEXP.h"
#include "scripting/api/objs/ai_helper.h"
#include "scripting/api/objs/enums.h"
#include "scripting/api/objs/oswpt.h"
#include "scripting/api/objs/ship.h"
#include "scripting/scripting.h"
static std::unordered_map<int, ai_mode_lua> Lua_ai_modes;
static std::unordered_map<int, player_order_lua> Lua_player_orders;
void ai_lua_add_mode(int sexp_op, const ai_mode_lua& mode) {
Lua_ai_modes.emplace(sexp_op, mode);
}
bool ai_lua_add_order(int sexp_op, player_order_lua order) {
Player_orders.emplace_back(vm_strdup(order.parseText.c_str()), vm_strdup(order.displayText.c_str()), -1, sexp_op);
Player_orders.back().localize();
Lua_player_orders.emplace(sexp_op, std::move(order));
return true;
}
bool ai_lua_has_mode(int sexp_op){
return Lua_ai_modes.count(sexp_op) > 0;
}
const ai_mode_lua* ai_lua_find_mode(int sexp_op){
auto aiLuaMode = Lua_ai_modes.find(sexp_op);
if(aiLuaMode == Lua_ai_modes.end())
return nullptr;
else
return &aiLuaMode->second;
}
const player_order_lua* ai_lua_find_player_order(int sexp_op) {
auto aiLuaOrder = Lua_player_orders.find(sexp_op);
if (aiLuaOrder == Lua_player_orders.end())
return nullptr;
else
return &aiLuaOrder->second;
}
void run_ai_lua_action(const luacpp::LuaFunction& action, const ai_mode_lua& lua_ai, ai_info* aip) {
if (!action.isValid()) {
Error(LOCATION,
"Lua AI SEXP called without a valid action function! A script probably failed to set the action for some reason.");
return;
}
luacpp::LuaValueList luaParameters;
luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_AI_Helper.Set(object_h(&Objects[Ships[aip->shipnum].objnum]))));
if (lua_ai.needsTarget) {
luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_OSWPT.Set(aip->lua_ai_target)));
}
auto retVals = action.call(Script_system.GetLuaSession(), luaParameters);
if (!retVals.empty() && retVals[0].getValueType() == luacpp::ValueType::BOOLEAN) {
if (retVals[0].getValue<bool>())
ai_mission_goal_complete(aip);
}
}
void ai_lua(ai_info* aip){
Assertion(aip->mode == AIM_LUA, "Tried to invoke LuaAI on a ship not in LuaAI-Mode");
const auto& lua_ai = Lua_ai_modes.at(aip->submode);
auto dynamicSEXP = sexp::get_dynamic_sexp(aip->submode);
if (dynamicSEXP != nullptr && typeid(*dynamicSEXP) == typeid(sexp::LuaAISEXP)) {
auto lua_ai_sexp = static_cast<sexp::LuaAISEXP*>(dynamicSEXP);
const auto& action = lua_ai_sexp->getActionFrame();
run_ai_lua_action(action, lua_ai, aip);
}
}
void ai_lua_start(ai_goal* aigp, object* objp){
Assert(objp->type == OBJ_SHIP);
Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
aip->mode = AIM_LUA;
aip->submode = aigp->ai_submode;
aip->lua_ai_target = aigp->lua_ai_target;
aip->submode_start_time = Missiontime;
const auto& lua_ai = Lua_ai_modes.at(aigp->ai_submode);
auto dynamicSEXP = sexp::get_dynamic_sexp(aigp->ai_submode);
if (dynamicSEXP != nullptr && typeid(*dynamicSEXP) == typeid(sexp::LuaAISEXP)) {
auto lua_ai_sexp = static_cast<sexp::LuaAISEXP*>(dynamicSEXP);
const auto& action = lua_ai_sexp->getActionEnter();
run_ai_lua_action(action, lua_ai, aip);
}
}
bool ai_lua_is_valid_target_intrinsic(int sexp_op, int target_objnum, ship* self) {
ship* target = &Ships[Objects[target_objnum].instance];
const player_order_lua& order = *ai_lua_find_player_order(sexp_op);
switch (order.targetRestrictions) {
case player_order_lua::target_restrictions::TARGET_ALL:
return true;
case player_order_lua::target_restrictions::TARGET_OWN:
return target->team == self->team;
case player_order_lua::target_restrictions::TARGET_ALLIES:
return !iff_x_attacks_y(self->team, target->team);
case player_order_lua::target_restrictions::TARGET_ENEMIES:
return iff_x_attacks_y(self->team, target->team);
case player_order_lua::target_restrictions::TARGET_SAME_WING:
return target->wingnum != -1 && self->wingnum == target->wingnum;
case player_order_lua::target_restrictions::TARGET_PLAYER_WING:
return target->wingnum != -1 && Ships[Player_obj->instance].wingnum == target->wingnum;
case player_order_lua::target_restrictions::TARGET_ALL_CAPS:
return Ship_info[target->ship_info_index].is_big_ship();
case player_order_lua::target_restrictions::TARGET_ALLIED_CAPS:
return !iff_x_attacks_y(self->team, target->team) && Ship_info[target->ship_info_index].is_big_ship();
case player_order_lua::target_restrictions::TARGET_ENEMY_CAPS:
return iff_x_attacks_y(self->team, target->team) && Ship_info[target->ship_info_index].is_big_ship();
case player_order_lua::target_restrictions::TARGET_NOT_SELF:
return self->objnum != target_objnum;
}
return false;
}
bool ai_lua_is_valid_target_lua(const ai_mode_lua& mode, int sexp_op, int target_objnum, ship* self) {
auto dynamicSEXP = sexp::get_dynamic_sexp(sexp_op);
if (dynamicSEXP != nullptr && typeid(*dynamicSEXP) == typeid(sexp::LuaAISEXP)) {
auto lua_ai_sexp = static_cast<sexp::LuaAISEXP*>(dynamicSEXP);
const auto& action = lua_ai_sexp->getTargetRestrict();
if (!action.isValid())
return true;
luacpp::LuaValueList luaParameters;
luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_Ship.Set(object_h(&Objects[self->objnum]))));
if (mode.needsTarget) {
luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_OSWPT.Set(object_ship_wing_point_team(&Ships[Objects[target_objnum].instance]))));
}
auto retVals = action.call(Script_system.GetLuaSession(), luaParameters);
if (!retVals.empty() && retVals[0].getValueType() == luacpp::ValueType::BOOLEAN) {
return retVals[0].getValue<bool>();
}
else {
Error(LOCATION, "LuaAI target restriction function did not return a valid boolean!");
}
}
return true;
}
bool ai_lua_is_valid_target(int sexp_op, int target_objnum, ship* self) {
const ai_mode_lua& mode = *ai_lua_find_mode(sexp_op);
//All targetless AI modes are fine
if (mode.needsTarget) {
//No target is then not valid
if (target_objnum == -1)
return false;
//As of now, only accept ships
if (Objects[target_objnum].type != OBJ_SHIP)
return false;
if (!ai_lua_is_valid_target_intrinsic(sexp_op, target_objnum, self))
return false;
}
//If we haven't bailed yet, query the custom callback
return ai_lua_is_valid_target_lua(mode, sexp_op, target_objnum, self);
}
ai_achievability ai_lua_is_achievable(const ai_goal* aigp, int objnum){
const auto& lua_ai = Lua_ai_modes.at(aigp->ai_submode);
auto dynamicSEXP = sexp::get_dynamic_sexp(aigp->ai_submode);
if (dynamicSEXP != nullptr && typeid(*dynamicSEXP) == typeid(sexp::LuaAISEXP)) {
auto lua_ai_sexp = static_cast<sexp::LuaAISEXP*>(dynamicSEXP);
const auto& action = lua_ai_sexp->getAchievable();
if (!action.isValid())
return ai_achievability::ACHIEVABLE;
luacpp::LuaValueList luaParameters;
luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_Ship.Set(object_h(&Objects[objnum]))));
if (lua_ai.needsTarget) {
luaParameters.push_back(luacpp::LuaValue::createValue(action.getLuaState(), scripting::api::l_OSWPT.Set(aigp->lua_ai_target)));
}
auto retVals = action.call(Script_system.GetLuaSession(), luaParameters);
if (!retVals.empty() && retVals[0].getValueType() == luacpp::ValueType::USERDATA) {
scripting::api::enum_h enumData;
retVals[0].getValue(scripting::api::l_Enum.Get(&enumData));
if (!enumData.IsValid()) {
Error(LOCATION, "LuaAI SEXP achievability hook returned an invalid avilability enum.");
return ai_achievability::ACHIEVABLE;
}
switch (enumData.index) {
case scripting::api::LE_LUAAI_ACHIEVABLE:
return ai_achievability::ACHIEVABLE;
case scripting::api::LE_LUAAI_NOT_YET_ACHIEVABLE:
return ai_achievability::NOT_KNOWN;
case scripting::api::LE_LUAAI_UNACHIEVABLE:
return ai_achievability::NOT_ACHIEVABLE;
default:
Error(LOCATION, "LuaAI SEXP achievability hook returned an invalid avilability enum.");
return ai_achievability::ACHIEVABLE;
}
}
else {
Error(LOCATION, "LuaAI SEXP achievability hook did not return any avilability enum!");
}
}
return ai_achievability::ACHIEVABLE;
}
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "ai/ai.h"
#include "ai/aigoals.h"
#include "mission/missionmessage.h"
#include "ship/ship.h"
#include "globalincs/globals.h"
struct ai_mode_lua {
bool needsTarget;
const char* hudText;
};
struct player_order_lua {
int ai_message = MESSAGE_YESSIR;
SCP_string parseText = "";
SCP_string displayText = "";
enum class target_restrictions : int { TARGET_ALLIES, TARGET_ALL, TARGET_OWN, TARGET_ENEMIES, TARGET_SAME_WING, TARGET_PLAYER_WING, TARGET_ALL_CAPS, TARGET_ALLIED_CAPS, TARGET_ENEMY_CAPS, TARGET_NOT_SELF } targetRestrictions = target_restrictions::TARGET_ALL;
};
void ai_lua_add_mode(int sexp_op, const ai_mode_lua& mode);
bool ai_lua_add_order(int sexp_op, player_order_lua order);
bool ai_lua_has_mode(int sexp_op);
const ai_mode_lua* ai_lua_find_mode(int sexp_op);
const player_order_lua* ai_lua_find_player_order(int sexp_op);
void ai_lua(ai_info* aip);
void ai_lua_start(ai_goal* aigp, object* objp);
bool ai_lua_is_valid_target(int sexp_op, int target_objnum, ship* self);
ai_achievability ai_lua_is_achievable(const ai_goal* aigp, int objnum);
+47 -95
View File
@@ -18,7 +18,6 @@
#include "render/3d.h"
#include "ship/ship.h"
#include "ship/shipfx.h"
#include "utils/Random.h"
#include "weapon/beam.h"
#include "weapon/flak.h"
#include "weapon/muzzleflash.h"
@@ -95,8 +94,8 @@ void turret_instance_find_world_orient(matrix* out_mat, int model_instance_num,
auto pmi = model_get_instance(model_instance_num);
auto pm = model_get(pmi->model_num);
vec3d fvec, uvec;
model_instance_local_to_global_dir(&fvec, &pm->submodel[submodel_num].frame_of_reference.vec.fvec, pm, pmi, pm->submodel[submodel_num].parent, objorient);
model_instance_local_to_global_dir(&uvec, &pm->submodel[submodel_num].frame_of_reference.vec.uvec, pm, pmi, pm->submodel[submodel_num].parent, objorient);
model_instance_find_world_dir(&fvec, &pm->submodel[submodel_num].frame_of_reference.vec.fvec, pm, pmi, pm->submodel[submodel_num].parent, objorient);
model_instance_find_world_dir(&uvec, &pm->submodel[submodel_num].frame_of_reference.vec.uvec, pm, pmi, pm->submodel[submodel_num].parent, objorient);
vm_vector_2_matrix_norm(out_mat, &fvec, &uvec);
}
@@ -434,7 +433,7 @@ int valid_turret_enemy(object *objp, object *turret_parent)
}
// don't shoot at ships without collision check
if (!(objp->flags[Object::Object_Flags::Collides]) && !(objp->flags[Object::Object_Flags::Attackable_if_no_collide])) {
if (!(objp->flags[Object::Object_Flags::Collides])) {
return 0;
}
@@ -1190,9 +1189,9 @@ void ship_get_global_turret_info(const object *objp, const model_subsystem *tp,
{
auto model_instance_num = Ships[objp->instance].model_instance_num;
if (gpos)
model_instance_local_to_global_point(gpos, &vmd_zero_vector, model_instance_num, tp->subobj_num, &objp->orient, &objp->pos);
model_instance_find_world_point(gpos, &vmd_zero_vector, model_instance_num, tp->subobj_num, &objp->orient, &objp->pos);
if (gvec)
model_instance_local_to_global_dir(gvec, &tp->turret_norm, model_instance_num, tp->subobj_num, &objp->orient, true);
model_instance_find_world_dir(gvec, &tp->turret_norm, model_instance_num, tp->subobj_num, &objp->orient, true);
}
void turret_ai_update_aim(ai_info *aip, object *En_Objp, ship_subsys *ss);
@@ -1220,15 +1219,15 @@ void ship_get_global_turret_gun_info(object *objp, ship_subsys *ssp, vec3d *gpos
gun_pos = &tp->turret_firing_point[ssp->turret_next_fire_pos % tp->turret_num_firing_points];
model_instance_local_to_global_point(gpos, gun_pos, pm, pmi, tp->turret_gun_sobj, &objp->orient, &objp->pos);
model_instance_find_world_point(gpos, gun_pos, pm, pmi, tp->turret_gun_sobj, &objp->orient, &objp->pos);
if (use_angles) {
model_instance_local_to_global_dir(gvec, &tp->turret_norm, pm, pmi, tp->turret_gun_sobj, &objp->orient);
model_instance_find_world_dir(gvec, &tp->turret_norm, pm, pmi, tp->turret_gun_sobj, &objp->orient);
} else if (tp->flags[Model::Subsystem_Flags::Share_fire_direction]) {
vec3d avg, tmp_pos, tmp_target, enemy_point, turret_norm;
vm_vec_avg_n(&avg, tp->turret_num_firing_points, tp->turret_firing_point);
model_instance_local_to_global_point(&tmp_pos, &avg, pm, pmi, tp->turret_gun_sobj, &objp->orient, &objp->pos);
model_instance_find_world_point(&tmp_pos, &avg, pm, pmi, tp->turret_gun_sobj, &objp->orient, &objp->pos);
if (targetp == nullptr) {
ship* shipp = &Ships[Objects[ssp->parent_objnum].instance];
@@ -1253,7 +1252,7 @@ void ship_get_global_turret_gun_info(object *objp, ship_subsys *ssp, vec3d *gpos
} else {
if ((lep->type == OBJ_SHIP) && (Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge())) {
// the turret norm here is from the perspective of the base submodel, not the gun submodel
model_instance_local_to_global_dir(&turret_norm, &tp->turret_norm, pm, pmi, pm->submodel[tp->subobj_num].parent, &objp->orient);
model_instance_find_world_dir(&turret_norm, &tp->turret_norm, pm, pmi, pm->submodel[tp->subobj_num].parent, &objp->orient);
ai_big_pick_attack_point_turret(lep, ssp, &tmp_pos, &turret_norm, &enemy_point, MIN(wip->max_speed * wip->lifetime, wip->weapon_range), tp->turret_fov);
}
else {
@@ -1422,7 +1421,7 @@ float aifft_compute_turret_dot(object *objp, object *enemy_objp, vec3d *abs_gunp
if (ship_subsystem_in_sight(enemy_objp, enemy_subsysp, abs_gunposp, &subobj_pos, 1, &dot_out, &vector_out)) {
vec3d turret_norm;
model_instance_local_to_global_dir(&turret_norm, &turret_subsysp->system_info->turret_norm, Ships[objp->instance].model_instance_num, turret_subsysp->system_info->subobj_num, &objp->orient, true);
model_instance_find_world_dir(&turret_norm, &turret_subsysp->system_info->turret_norm, Ships[objp->instance].model_instance_num, turret_subsysp->system_info->subobj_num, &objp->orient, true);
float dot_return = vm_vec_dot(&turret_norm, &vector_out);
if (Ai_info[Ships[objp->instance].ai_index].ai_profile_flags[AI::Profile_Flags::Smart_subsystem_targeting_for_turrets]) {
@@ -1605,10 +1604,8 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
{
Assert(weapon_num < MAX_SHIP_WEAPONS);
float wait = 1000.0f;
// should subtract 1 from firepoints to match behavior of wip->burst_shots
int burst_shots = (wip->burst_flags[Weapon::Burst_Flags::Num_firepoints_burst_shots] ? turret->system_info->turret_num_firing_points - 1 : wip->burst_shots);
if (burst_shots > turret->weapons.burst_counter[weapon_num]) {
if (wip->burst_shots > turret->weapons.burst_counter[weapon_num]) {
wait *= wip->burst_delay;
turret->weapons.burst_counter[weapon_num]++;
} else {
@@ -1618,12 +1615,12 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
} else {
wait *= wip->fire_wait;
}
if ((burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Random_length])) {
turret->weapons.burst_counter[weapon_num] = Random::next(burst_shots);
turret->weapons.burst_seed[weapon_num] = Random::next();
if ((wip->burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Random_length])) {
turret->weapons.burst_counter[weapon_num] = (myrand() % wip->burst_shots);
turret->weapons.burst_seed[weapon_num] = rand32();
} else {
turret->weapons.burst_counter[weapon_num] = 0;
turret->weapons.burst_seed[weapon_num] = Random::next();
turret->weapons.burst_seed[weapon_num] = rand32();
}
}
@@ -1634,7 +1631,7 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
fs_dest = &turret->weapons.next_secondary_fire_stamp[weapon_num - MAX_SHIP_PRIMARY_BANKS];
//Check for the new cooldown flag
if(!((wip->wi_flags[Weapon::Info_Flags::Same_turret_cooldown]) || ((burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Fast_firing]))))
if(!((wip->wi_flags[Weapon::Info_Flags::Same_turret_cooldown]) || ((wip->burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Fast_firing]))))
{
// make side even for team vs. team
@@ -1782,8 +1779,6 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
#endif
if (check_ok_to_fire(parent_objnum, turret->turret_enemy_objnum, wip, -1, turret)) {
auto turret_enemy_objp = (turret->turret_enemy_objnum >= 0) ? &Objects[turret->turret_enemy_objnum] : nullptr;
ship_weapon* swp = &turret->weapons;
turret->turret_last_fire_direction = *firing_vec;
@@ -1792,19 +1787,8 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
vm_vector_2_matrix(&turret_orient, firing_vec, nullptr, nullptr);
// grab and set some burst data before turret_set_next_fire_timestamp wipes it
int old_burst_seed = swp->burst_seed[weapon_num];
int old_burst_counter = swp->burst_counter[weapon_num];
// only used by type 5 beams
if (turret->weapons.burst_counter[weapon_num] == 0) {
swp->per_burst_rot += wip->b_info.t5info.per_burst_rot;
if (swp->per_burst_rot > PI2)
swp->per_burst_rot -= PI2;
else if (swp->per_burst_rot < -PI2)
swp->per_burst_rot += PI2;
}
// set next fire timestamp for the turret
int old_burst_seed = swp->burst_seed[weapon_num];
if (last_shot_in_salvo)
turret_set_next_fire_timestamp(weapon_num, wip, turret, parent_aip);
@@ -1820,18 +1804,16 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
memset(&fire_info, 0, sizeof(beam_fire_info));
fire_info.accuracy = 1.0f;
fire_info.beam_info_index = turret_weapon_class;
fire_info.beam_info_override = nullptr;
fire_info.beam_info_override = NULL;
fire_info.shooter = &Objects[parent_objnum];
fire_info.target = turret_enemy_objp;
fire_info.target = &Objects[turret->turret_enemy_objnum];
if (wip->wi_flags[Weapon::Info_Flags::Antisubsysbeam])
fire_info.target_subsys = turret->targeted_subsys;
else
fire_info.target_subsys = nullptr;
fire_info.target_subsys = NULL;
fire_info.turret = turret;
fire_info.burst_seed = old_burst_seed;
fire_info.fire_method = BFM_TURRET_FIRED;
fire_info.per_burst_rotation = swp->per_burst_rot;
fire_info.burst_index = old_burst_counter;
// fire a beam weapon
weapon_objnum = beam_fire(&fire_info);
@@ -1842,11 +1824,9 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
parent_ship->last_fired_turret = turret;
turret->last_fired_weapon_info_index = turret_weapon_class;
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", nullptr, "Beam", objp, "Target", turret_enemy_objp);
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum], objp);
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
}
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", nullptr, "Beam", objp, "Target", &Objects[turret->turret_enemy_objnum]);
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum]);
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
}
turret->flags.set(Ship::Subsystem_Flags::Has_fired); //set fired flag for scripting -nike
@@ -1858,7 +1838,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
swp->current_secondary_bank = 0;
}
int bank_to_fire = swp->current_secondary_bank;
if ((turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) && !ship_secondary_has_ammo(swp, bank_to_fire)) {
if ((turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) && (swp->secondary_bank_ammo[bank_to_fire] < 0)) {
if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns])) {
swp->current_secondary_bank++;
if (swp->current_secondary_bank >= swp->num_secondary_banks) {
@@ -1906,10 +1886,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
points = 1;
}
if (swp->primary_bank_ammo[bank_to_fire] >= points) {
swp->primary_bank_ammo[bank_to_fire] -= points;
} else if ((swp->primary_bank_ammo[bank_to_fire] >= 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
// default behavior allowed ammo to be negative
if (swp->primary_bank_ammo[bank_to_fire] >= 0) {
swp->primary_bank_ammo[bank_to_fire] -= points;
} else if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]) && (swp->primary_bank_ammo[bank_to_fire] < 0)) {
swp->current_primary_bank++;
@@ -1941,17 +1918,11 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
for (int j = start_slot; j <= end_slot; j++) {
swp->secondary_next_slot[bank_to_fire]++;
if (Weapon_info[swp->secondary_bank_weapons[bank_to_fire]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo])
continue;
if (swp->secondary_next_slot[bank_to_fire] > (num_slots - 1)){
swp->secondary_next_slot[bank_to_fire] = 0;
}
if (swp->secondary_bank_ammo[bank_to_fire] > 0) {
swp->secondary_bank_ammo[bank_to_fire]--;
} else if ((swp->secondary_bank_ammo[bank_to_fire] == 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
// default behavior allowed ammo to be negative
if (swp->secondary_bank_ammo[bank_to_fire] >= 0) {
swp->secondary_bank_ammo[bank_to_fire]--;
} else if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]) && (swp->secondary_bank_ammo[bank_to_fire] < 0)) {
swp->current_secondary_bank++;
@@ -1985,11 +1956,9 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
// AL 1-6-97: Store pointer to turret subsystem
wp->turret_subsys = turret;
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", objp, "Beam", nullptr, "Target", turret_enemy_objp);
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum], objp);
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
}
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", objp, "Beam", nullptr, "Target", &Objects[turret->turret_enemy_objnum]);
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum]);
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
// if the gun is a flak gun
if (wip->wi_flags[Weapon::Info_Flags::Flak]) {
@@ -2017,7 +1986,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
if ( MULTIPLAYER_MASTER && (weapon_objnum != -1) ) {
int subsys_index;
subsys_index = ship_get_subsys_index(turret);
subsys_index = ship_get_index_from_subsys(turret, parent_objnum );
Assert( subsys_index != -1 );
if(wip->wi_flags[Weapon::Info_Flags::Flak]){
send_flak_fired_packet( parent_objnum, subsys_index, weapon_objnum, flak_range );
@@ -2052,7 +2021,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
turret->flags.set(Ship::Subsystem_Flags::Has_fired); //set has fired flag for scriptng - nuke
//Fire animation stuff
Ship_info[parent_ship->ship_info_index].animations.start(model_get_instance(parent_ship->model_instance_num), animation::ModelAnimationTriggerType::TurretFired, animation::anim_name_from_subsys(turret->system_info), animation::ModelAnimationDirection::FWD, true);
model_anim_start_type(turret, AnimationTriggerType::TurretFired, ANIMATION_SUBTYPE_ALL, 1);
return true;
}
@@ -2103,11 +2072,9 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
Ships[Objects[tsi->parent_objnum].instance].last_fired_turret = tsi->turret;
tsi->turret->last_fired_weapon_info_index = tsi->weapon_class;
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
Script_system.SetHookObjects(4, "Ship", &Objects[tsi->parent_objnum], "Weapon", &Objects[weapon_objnum], "Beam", nullptr, "Target", &Objects[tsi->turret->turret_enemy_objnum]);
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[tsi->parent_objnum], &Objects[weapon_objnum]);
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
}
Script_system.SetHookObjects(4, "Ship", &Objects[tsi->parent_objnum], "Weapon", &Objects[weapon_objnum], "Beam", nullptr, "Target", &Objects[tsi->turret->turret_enemy_objnum]);
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[tsi->parent_objnum]);
Script_system.RemHookVars({"Ship", "Weapon", "Beam", "Target"});
// muzzle flash?
if (Weapon_info[tsi->weapon_class].muzzle_flash >= 0) {
@@ -2126,7 +2093,7 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
if ( MULTIPLAYER_MASTER && (weapon_objnum != -1) ) {
int subsys_index;
subsys_index = ship_get_subsys_index(tsi->turret);
subsys_index = ship_get_index_from_subsys(tsi->turret, tsi->parent_objnum );
Assert( subsys_index != -1 );
send_turret_fired_packet( tsi->parent_objnum, subsys_index, weapon_objnum );
}
@@ -2138,15 +2105,13 @@ int Num_find_turret_enemy = 0;
int Num_turrets_fired = 0;
/**
* Previously called ai_fire_from_turret()
* Given a ship and a turret subsystem, handle all its behavior, mostly targeting and shooting but also
* all turret movement and resetting when idle
* Given a turret tp and its parent parent_objnum, fire from the turret at its enemy.
*/
extern int Nebula_sec_range;
void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
{
float weapon_firing_range;
float weapon_min_range; // *Weapon minimum firing range -Et1
float WeaponMinRange; // *Weapon minimum firing range -Et1
vec3d v2e;
object *lep; // Last enemy pointer
model_subsystem *tp = ss->system_info;
@@ -2321,12 +2286,12 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
{
weapon_firing_range = wip->lssm_lock_range;
weapon_min_range = 0.0f;
WeaponMinRange = 0.0f;
}
else
{
weapon_firing_range = MIN(wip->lifetime * wip->max_speed, wip->weapon_range);
weapon_min_range = wip->weapon_min_range;
WeaponMinRange = wip->WeaponMinRange;
}
// if beam weapon in nebula and target not tagged, decrease firing range
@@ -2339,7 +2304,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
}
// Don't try to fire beyond weapon_limit_range (or within min range)
if (dist_to_enemy < weapon_min_range || dist_to_enemy > weapon_firing_range) {
if (dist_to_enemy < WeaponMinRange || dist_to_enemy > weapon_firing_range) {
// it's possible another weapon is in range, but if not,
// we will end up selecting a new target
continue;
@@ -2406,11 +2371,6 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
tentative_return = true;
continue;
}
if (!weapon_target_satisfies_lock_restrictions(wip, lep)) {
tentative_return = true;
continue;
}
}
else {
// check tagged-only for bombs only if the flag is set; see Mantis #3114
@@ -2477,10 +2437,9 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
if (( lep->type == OBJ_SHIP ) && !(ss->flags[Ship::Subsystem_Flags::No_SS_targeting])) {
ss->targeted_subsys = aifft_find_turret_subsys(objp, ss, lep, &dot);
}
// recheck in 2-3 seconds
ss->turret_next_enemy_check_stamp = timestamp((int)((MAX(dot, 0.5f) * The_mission.ai_profile->turret_target_recheck_time) + (The_mission.ai_profile->turret_target_recheck_time / 2.0f)));
ss->turret_next_enemy_check_stamp = timestamp((int) (MAX(dot, 0.5f)*2000.0f) + 1000);
} else {
ss->turret_next_enemy_check_stamp = timestamp((int)(The_mission.ai_profile->turret_target_recheck_time * frand_range(0.9f, 1.1f))); // Check every two seconds
ss->turret_next_enemy_check_stamp = timestamp((int) (2000.0f * frand_range(0.9f, 1.1f))); // Check every two seconds
}
}
@@ -2669,15 +2628,8 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
{
// starting animation checks
if (ss->turret_animation_position == MA_POS_NOT_SET) {
bool started = false;
//For legacy animations using subtype for turret number
started |= Ship_info[shipp->ship_info_index].animations.startAll(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::TurretFiring, animation::ModelAnimationDirection::FWD, false, false, false, ss->system_info->subobj_num, true);
//For modern animations using proper triggered-by-subsys name
started |= Ship_info[shipp->ship_info_index].animations.start(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::TurretFiring, animation::anim_name_from_subsys(ss->system_info), animation::ModelAnimationDirection::FWD);
if (started) {
int duration = Ship_info[shipp->ship_info_index].animations.getTimeAll(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::TurretFiring, ss->system_info->subobj_num, true);
int duration2 = Ship_info[shipp->ship_info_index].animations.getTime(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::TurretFiring, animation::anim_name_from_subsys(ss->system_info));
ss->turret_animation_done_time = timestamp(duration > duration2 ? duration : duration2);
if ( model_anim_start_type(shipp, AnimationTriggerType::TurretFiring, ss->system_info->subobj_num, 1) ) {
ss->turret_animation_done_time = model_anim_get_time_type(shipp, AnimationTriggerType::TurretFiring, ss->system_info->subobj_num);
ss->turret_animation_position = MA_POS_SET;
}
}
@@ -2742,7 +2694,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
{
//something did fire, get the lowest valid timestamp
// don't do this if we already set a turret timestamp previously in the function
if (timestamp_since(ss->turret_next_fire_stamp) > 0)
if (timestamp_until(ss->turret_next_fire_stamp) <= 0)
{
int minimum_stamp = -1;
@@ -2751,7 +2703,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
int stamp = (i < MAX_SHIP_PRIMARY_BANKS) ? swp->next_primary_fire_stamp[i] : swp->next_secondary_fire_stamp[i - MAX_SHIP_PRIMARY_BANKS];
// valid timestamps start at 2; stamp must be in the future
if (stamp < 2 || timestamp_since(stamp) > 0)
if (stamp < 2 || timestamp_until(stamp) <= 0)
continue;
// find minimum
+146 -212
View File
@@ -32,7 +32,7 @@
#include "object/objcollide.h"
#include "object/object.h"
#include "parse/parselo.h"
#include "scripting/global_hooks.h"
#include "scripting/scripting.h"
#include "particle/particle.h"
#include "render/3d.h"
#include "ship/ship.h"
@@ -55,6 +55,8 @@ const float LARGE_DEBRIS_WEIGHT = 1.0f;
int Asteroids_enabled = 1;
int Num_asteroids = 0;
int Asteroid_throw_objnum = -1; // Object index of ship to throw asteroids at.
int Next_asteroid_throw;
SCP_vector< asteroid_info > Asteroid_info;
asteroid Asteroids[MAX_ASTEROIDS];
@@ -71,55 +73,6 @@ float Asteroid_icon_closeup_zoom;
#define ASTEROID_UPDATE_COLLIDE_TIMESTAMP 2000 // how often asteroid is checked for impending collisions with escort ships
#define ASTEROID_MIN_COLLIDE_TIME 24 // time in seconds to check for asteroid colliding
SCP_vector<SCP_string> Asteroid_target_ships;
bool Default_asteroid_throwing_behavior = true;
typedef struct asteroid_target {
int objnum;
int signature;
int throw_stamp;
int incoming_asteroids;
} asteroid_target;
// if default throwing behavior is enabled, then this should always have at most 1 target or possibly none, determined the retail way
// if not, then this is whatever number of mission-specified ships (after they arrive, list is sanitized when they exit)
SCP_vector<asteroid_target> Asteroid_targets;
/**
* Return number of asteroids expected to collide with a ship.
*/
static int count_incident_asteroids(int target_objnum)
{
object* asteroid_objp;
int count;
count = 0;
for (asteroid_objp = GET_FIRST(&obj_used_list); asteroid_objp != END_OF_LIST(&obj_used_list); asteroid_objp = GET_NEXT(asteroid_objp)) {
if (asteroid_objp->type == OBJ_ASTEROID) {
asteroid* asp = &Asteroids[asteroid_objp->instance];
if (asp->target_objnum == target_objnum) {
count++;
}
}
}
return count;
}
// add a ship as a new asteroid target
void asteroid_add_target(object* objp) {
asteroid_target new_target;
new_target.objnum = OBJ_INDEX(objp);
new_target.signature = objp->signature;
new_target.throw_stamp = timestamp(Random::next(500, 2000));
new_target.incoming_asteroids = count_incident_asteroids(OBJ_INDEX(objp)); // this *should* normally be 0
Asteroid_targets.push_back(new_target);
}
/**
* Force updating of pair stuff for asteroid *objp.
*/
@@ -205,28 +158,43 @@ static float asteroid_cap_speed(int asteroid_info_index, float speed)
return speed;
}
/**
* Returns whether position is inside inner bounding volume
*
* Sum together the following: 1 inside x, 2 inside y, 4 inside z
* inside only when sum = 7
*/
static int asteroid_in_inner_bound_with_axes(asteroid_field *asfieldp, vec3d *pos, float delta)
{
Assert(asfieldp->has_inner_bound);
int rval = 0;
if ( (pos->xyz.x > asfieldp->inner_min_bound.xyz.x - delta) && (pos->xyz.x < asfieldp->inner_max_bound.xyz.x + delta) ) {
rval += 1;
}
if ( (pos->xyz.y > asfieldp->inner_min_bound.xyz.y - delta) && (pos->xyz.y < asfieldp->inner_max_bound.xyz.y + delta) ) {
rval += 2;
}
if ( (pos->xyz.z > asfieldp->inner_min_bound.xyz.z - delta) && (pos->xyz.z < asfieldp->inner_max_bound.xyz.z + delta) ) {
rval += 4;
}
return rval;
}
/**
* Check if asteroid is within inner bound
* @return 0 if not inside or no inner bound, 1 if inside inner bound
*/
static bool asteroid_in_inner_bound(asteroid_field *asfieldp, vec3d *pos, float delta) {
static int asteroid_in_inner_bound(asteroid_field *asfieldp, vec3d *pos, float delta) {
if (!asfieldp->has_inner_bound)
return false;
if (!asfieldp->has_inner_bound) {
return 0;
}
return (pos->xyz.x > asfieldp->inner_min_bound.xyz.x - delta) && (pos->xyz.x < asfieldp->inner_max_bound.xyz.x + delta) &&
(pos->xyz.y > asfieldp->inner_min_bound.xyz.y - delta) && (pos->xyz.y < asfieldp->inner_max_bound.xyz.y + delta) &&
(pos->xyz.z > asfieldp->inner_min_bound.xyz.z - delta) && (pos->xyz.z < asfieldp->inner_max_bound.xyz.z + delta);
}
static bool asteroid_is_ship_inside_field(asteroid_field* asfieldp, vec3d* pos, float radius) {
radius *= 2.0f;
return pos->xyz.x + radius > Asteroid_field.min_bound.xyz.x && pos->xyz.x - radius < Asteroid_field.max_bound.xyz.x &&
pos->xyz.y + radius > Asteroid_field.min_bound.xyz.y && pos->xyz.y - radius < Asteroid_field.max_bound.xyz.y &&
pos->xyz.z + radius > Asteroid_field.min_bound.xyz.z && pos->xyz.z - radius < Asteroid_field.max_bound.xyz.z &&
!asteroid_in_inner_bound(asfieldp, pos, radius);
return (asteroid_in_inner_bound_with_axes(asfieldp, pos, delta) == 7);
}
/**
@@ -356,7 +324,7 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi
asteroid_default_flagset += Object::Object_Flags::Physics;
asteroid_default_flagset += Object::Object_Flags::Collides;
objnum = obj_create(OBJ_ASTEROID, -1, n, &orient, &pos, radius, asteroid_default_flagset, false);
objnum = obj_create(OBJ_ASTEROID, -1, n, &orient, &pos, radius, asteroid_default_flagset);
if ( (objnum == -1) || (objnum >= MAX_OBJECTS) ) {
mprintf(("Couldn't create asteroid -- out of object slots\n"));
@@ -366,8 +334,8 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi
asp->objnum = objnum;
asp->model_instance_num = -1;
if (model_get(asip->model_num[asteroid_subtype])->flags & PM_FLAG_HAS_INTRINSIC_MOTION) {
asp->model_instance_num = model_create_instance(objnum, asip->model_num[asteroid_subtype]);
if (model_get(asip->model_num[asteroid_subtype])->flags & PM_FLAG_HAS_INTRINSIC_ROTATE) {
asp->model_instance_num = model_create_instance(false, asip->model_num[asteroid_subtype]);
}
// Add to Asteroid_used_list
@@ -528,7 +496,7 @@ void asteroid_create_all()
// ship_debris_odds_table keeps track of debris type of the next debris piece
// each different type (size) of debris piece has a diffenent weight, smaller weighted more heavily than larger.
// choose next type from table ship_debris_odds_table by Random::next()%max_weighted_range,
// choose next type from table ship_debris_odds_table by rand()%max_weighted_range,
// the threshold *below* which the debris type is selected.
struct {
float random_threshold;
@@ -544,11 +512,6 @@ void asteroid_create_all()
return;
}
if (Asteroid_field.num_used_field_debris_types <= 0) {
Warning(LOCATION, "An asteroid field is enabled, but no asteroid types were enabled.");
return;
}
int max_asteroids = Asteroid_field.num_initial_asteroids; // * (1.0f - 0.1f*(MAX_DETAIL_LEVEL-Detail.asteroid_density)));
int num_debris_types = 0;
@@ -601,20 +564,12 @@ void asteroid_create_all()
// For asteroid, load only large asteroids
// get a valid subtype
int counter = Random::next(Asteroid_field.num_used_field_debris_types);
int subtype = -1;
for (int j = 0; j < NUM_DEBRIS_POFS; j++) {
if (Asteroid_field.field_debris_type[j] >= 0) {
if (counter == 0) {
subtype = j;
break;
} else
counter--;
}
int subtype = rand() % NUM_DEBRIS_POFS;
while (Asteroid_field.field_debris_type[subtype] == -1) {
subtype = (subtype + 1) % NUM_DEBRIS_POFS;
}
if (subtype >= 0)
asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
} else {
Assert(num_debris_types > 0);
@@ -638,13 +593,11 @@ void asteroid_level_init()
{
Asteroid_field.num_initial_asteroids=0;
Num_asteroids = 0;
Asteroid_targets.clear();
Default_asteroid_throwing_behavior = true;
Next_asteroid_throw = timestamp(1);
asteroid_obj_list_init();
SCP_vector<asteroid_info>::iterator ast;
for (ast = Asteroid_info.begin(); ast != Asteroid_info.end(); ++ast)
ast->damage_type_idx = ast->damage_type_idx_sav;
Asteroid_field.num_used_field_debris_types = 0;
}
/**
@@ -780,75 +733,52 @@ static void asteroid_aim_at_target(object *objp, object *asteroid_objp, float de
}
/**
* Sanitizes the Asteroid targets list removing ships that have died/become invalid etc
*/
static void sanitize_asteroid_targets_list() {
for (size_t i = 0; i < Asteroid_targets.size();) {
object* target_objp = &Objects[Asteroid_targets[i].objnum];
if (target_objp->type != OBJ_SHIP || target_objp->signature != Asteroid_targets[i].signature) {
Asteroid_targets[i] = Asteroid_targets.back();
Asteroid_targets.pop_back();
}
else // if we needed to cull we should not advance because we just moved a new asteroid target into this spot
i++;
}
}
/**
* Call once per frame to maybe throw some asteroids at one or more ships.
* Call once per frame to maybe throw an asteroid at a ship.
*
* @param count asteroids already targeted on
*/
static void maybe_throw_asteroid()
static void maybe_throw_asteroid(int count)
{
if (!timestamp_elapsed(Next_asteroid_throw)) {
return;
}
for (asteroid_target& target : Asteroid_targets) {
if (!timestamp_elapsed(target.throw_stamp))
continue;
if (Asteroid_throw_objnum == -1) {
return;
}
object* target_objp = &Objects[target.objnum];
if (!asteroid_is_ship_inside_field(&Asteroid_field, &target_objp->pos, target_objp->radius))
continue;
// This should've been greater equal, but alas...
if (target.incoming_asteroids > The_mission.ai_profile->max_incoming_asteroids[Game_skill_level])
continue;
nprintf(("AI", "Incoming asteroids: %i\n", count));
nprintf(("AI", "Incoming asteroids to %s: %i\n", Ships[target_objp->instance].ship_name, target.incoming_asteroids));
if (count > The_mission.ai_profile->max_incoming_asteroids[Game_skill_level])
return;
target.throw_stamp = timestamp(1000 + 1200 * target.incoming_asteroids /(Game_skill_level+1));
Next_asteroid_throw = timestamp(1000 + 1200 * count/(Game_skill_level+1));
int counter = Random::next(Asteroid_field.num_used_field_debris_types);
int subtype = -1;
for (int i = 0; i < NUM_DEBRIS_POFS; i++) {
if (Asteroid_field.field_debris_type[i] >= 0) {
if (counter == 0) {
subtype = i;
break;
ship_obj *so;
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
object *A = &Objects[so->objnum];
if (so->objnum == Asteroid_throw_objnum) {
int subtype = rand() % NUM_DEBRIS_POFS;
while (Asteroid_field.field_debris_type[subtype] == -1) {
subtype = (subtype + 1) % NUM_DEBRIS_POFS;
}
object *objp = asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
if (objp != NULL) {
asteroid_aim_at_target(A, objp, ASTEROID_MIN_COLLIDE_TIME + frand() * 20.0f);
// if asteroid is inside inner bound, kill it
if (asteroid_in_inner_bound(&Asteroid_field, &objp->pos, 0.0f)) {
objp->flags.set(Object::Object_Flags::Should_be_dead);
} else {
Asteroids[objp->instance].target_objnum = so->objnum;
if ( MULTIPLAYER_MASTER ) {
send_asteroid_throw( objp );
}
}
else
counter--;
}
}
// this really shouldn't happen but just in case...
if (subtype < 0)
return;
object *objp = asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
if (objp != nullptr) {
asteroid_aim_at_target(target_objp, objp, ASTEROID_MIN_COLLIDE_TIME + frand() * 20.0f);
// if asteroid is inside inner bound, kill it
if (asteroid_in_inner_bound(&Asteroid_field, &objp->pos, 0.0f)) {
objp->flags.set(Object::Object_Flags::Should_be_dead);
} else {
Asteroids[objp->instance].target_objnum = target.objnum;
target.incoming_asteroids++;
if ( MULTIPLAYER_MASTER ) {
send_asteroid_throw( objp );
}
}
}
}
@@ -870,13 +800,6 @@ void asteroid_delete( object * obj )
if (asp->model_instance_num >= 0)
model_delete_instance(asp->model_instance_num);
if (asp->target_objnum >= 0) {
for (asteroid_target& target : Asteroid_targets) {
if (asp->target_objnum == target.objnum)
target.incoming_asteroids--;
}
}
asp->flags = 0;
Num_asteroids--;
Assert(Num_asteroids >= 0);
@@ -915,14 +838,7 @@ static void asteroid_maybe_reposition(object *objp, asteroid_field *asfieldp)
} else {
// check to ensure player won't see asteroid appear either
asteroid_wrap_pos(objp, asfieldp);
asteroid* astp = &Asteroids[objp->instance];
// this doesnt count as a thrown asteroid anymore
for (asteroid_target& target : Asteroid_targets)
if (target.objnum == astp->target_objnum)
target.incoming_asteroids--;
astp->target_objnum = -1;
Asteroids[objp->instance].target_objnum = -1;
dist = vm_vec_normalized_dir(&vec_to_asteroid, &objp->pos, &Eye_position);
dot = vm_vec_dot(&Eye_matrix.vec.fvec, &vec_to_asteroid);
@@ -1002,9 +918,9 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
vec3d normal;
if (mc.model_instance_num >= 0)
model_instance_local_to_global_dir(&normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, mc.orient);
model_instance_find_world_dir(&normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, mc.orient);
else
model_local_to_global_dir(&normal, &mc.hit_normal, mc.model_num, mc.hit_submodel, mc.orient);
model_find_world_dir(&normal, &mc.hit_normal, mc.model_num, mc.hit_submodel, mc.orient);
*hitnormal = normal;
}
@@ -1034,11 +950,11 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
// find the light object's position in the heavy object's reference frame at last frame and also in this frame.
vec3d p0_temp, p0_rotated;
// Collision detection from rotation enabled if at rotation is less than 30 degree in frame
// Collision detection from rotation enabled if at rotaion is less than 30 degree in frame
// This should account for all ships
if ( (vm_vec_mag_squared( &heavy->phys_info.rotvel ) * flFrametime*flFrametime) < (PI*PI/36) ) {
// collide_rotate calculate (1) start position and (2) relative velocity
asteroid_hit_info->collide_rotate = true;
asteroid_hit_info->collide_rotate = 1;
vm_vec_rotate( &p0_temp, &p0, &heavy->last_orient );
vm_vec_unrotate( &p0_rotated, &p0_temp, &heavy->orient );
mc.p0 = &p0_rotated; // Point 1 of ray to check
@@ -1046,11 +962,11 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
vm_vec_scale( &asteroid_hit_info->light_rel_vel, 1/flFrametime );
// HACK - this applies to big ships warping in/out of asteroid fields - not sure what it does
if (vm_vec_mag(&asteroid_hit_info->light_rel_vel) > 300) {
asteroid_hit_info->collide_rotate = false;
asteroid_hit_info->collide_rotate = 0;
vm_vec_sub( &asteroid_hit_info->light_rel_vel, &lighter->phys_info.vel, &heavy->phys_info.vel );
}
} else {
asteroid_hit_info->collide_rotate = false;
asteroid_hit_info->collide_rotate = 0;
vm_vec_sub( &asteroid_hit_info->light_rel_vel, &lighter->phys_info.vel, &heavy->phys_info.vel );
}
@@ -1082,18 +998,18 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
// Do collision the cool new way
if ( asteroid_hit_info->collide_rotate ) {
// We collide with the sphere, find the list of moving submodels and test one at a time
// We collide with the sphere, find the list of rotating submodels and test one at a time
SCP_vector<int> submodel_vector;
model_get_moving_submodel_list(submodel_vector, heavy_obj);
model_get_rotating_submodel_list(&submodel_vector, heavy_obj);
// turn off all moving submodels, collide against only 1 at a time.
// turn off collision detection for all moving submodels
// turn off all rotating submodels, collide against only 1 at a time.
// turn off collision detection for all rotating submodels
for (auto submodel: submodel_vector) {
pmi->submodel[submodel].collision_checked = true;
}
// Only check single submodel now, since children of moving submodels are handled as moving as well
mc.flags = copy_flags | MC_SUBMODEL;
// reset flags to check MC_CHECK_MODEL | MC_CHECK_SPHERELINE and maybe MC_CHECK_INVISIBLE_FACES and MC_SUBMODEL_INSTANCE
mc.flags = copy_flags | MC_SUBMODEL_INSTANCE;
// check each submodel in turn
for (auto submodel: submodel_vector) {
@@ -1102,9 +1018,15 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
// turn on just one submodel for collision test
smi->collision_checked = false;
// set angles for last frame (need to set to prev to get p0)
matrix copy_matrix = smi->canonical_orient;
// find the start and end positions of the sphere in submodel RF
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
model_instance_global_to_local_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
smi->canonical_orient = smi->canonical_prev_orient;
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
smi->canonical_orient = copy_matrix;
world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
mc.p0 = &p0;
mc.p1 = &p1;
@@ -1117,18 +1039,18 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
mc_ret_val = 1;
// set up asteroid_hit_info common
set_hit_struct_info(asteroid_hit_info, &mc, true);
set_hit_struct_info(asteroid_hit_info, &mc, SUBMODEL_ROT_HIT);
// set up asteroid_hit_info for rotating submodel
if (!asteroid_hit_info->edge_hit) {
model_instance_local_to_global_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
if (asteroid_hit_info->edge_hit == 0) {
model_instance_find_world_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
vec3d int_light_pos, diff;
vm_vec_sub(&diff, mc.p1, mc.p0);
vm_vec_scale_add(&int_light_pos, mc.p0, &diff, mc.hit_dist);
model_instance_local_to_global_point(&asteroid_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
model_instance_find_world_point(&asteroid_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
}
}
// Don't look at this submodel again
@@ -1149,11 +1071,11 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
if (mc.hit_dist < asteroid_hit_info->hit_time) {
mc_ret_val = 1;
set_hit_struct_info(asteroid_hit_info, &mc, false);
set_hit_struct_info(asteroid_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
// get collision normal if not edge hit
if (!asteroid_hit_info->edge_hit) {
model_instance_local_to_global_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
if (asteroid_hit_info->edge_hit == 0) {
model_instance_find_world_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
@@ -1178,7 +1100,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
mc_ret_val = model_collide(&mc);
if (mc_ret_val) {
set_hit_struct_info(asteroid_hit_info, &mc, false);
set_hit_struct_info(asteroid_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
// set normal if not edge hit
if ( !asteroid_hit_info->edge_hit ) {
@@ -1402,7 +1324,7 @@ static void asteroid_do_area_effect(object *asteroid_objp)
if ( weapon_area_calc_damage(ship_objp, &asteroid_objp->pos, asip->inner_rad, asip->outer_rad, asip->blast, asip->damage, &blast, &damage, asip->outer_rad) == -1 )
continue;
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage, asip->damage_type_idx);
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage );
weapon_area_apply_blast(NULL, ship_objp, &asteroid_objp->pos, blast, 0);
} // end for
}
@@ -1510,15 +1432,9 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
if ( timestamp_elapsed(asp->final_death_time) ) {
vec3d relvec, vfh, tvec;
bool skip = false;
bool hooked = scripting::hooks::OnDeath->isActive();
if (hooked) {
skip = scripting::hooks::OnDeath->isOverride(
scripting::hook_param_list(scripting::hook_param("Self", 'o', pasteroid_obj)),
pasteroid_obj);
}
if (!skip)
Script_system.SetHookObject("Self", pasteroid_obj);
if(!Script_system.IsConditionOverride(CHA_DEATH, pasteroid_obj))
{
pasteroid_obj->flags.set(Object::Object_Flags::Should_be_dead);
@@ -1568,7 +1484,7 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
int num_roids_var = split->max - split->min;
if (num_roids_var > 0)
num_roids += Random::next(num_roids_var);
num_roids += rand() % num_roids_var;
if (num_roids > 0)
for (int i=0; i<num_roids; i++)
@@ -1623,11 +1539,8 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
}
asp->final_death_time = timestamp(-1);
}
if (hooked) {
scripting::hooks::OnDeath->run(
scripting::hook_param_list(scripting::hook_param("Self", 'o', pasteroid_obj)),
pasteroid_obj);
}
Script_system.RunCondition(CHA_DEATH, pasteroid_obj);
Script_system.RemHookVar("Self");
}
}
@@ -2092,6 +2005,29 @@ static void asteroid_parse_tbl()
}
}
/**
* Return number of asteroids expected to collide with a ship.
*/
static int count_incident_asteroids()
{
object *asteroid_objp;
int count;
count = 0;
for ( asteroid_objp = GET_FIRST(&obj_used_list); asteroid_objp !=END_OF_LIST(&obj_used_list); asteroid_objp = GET_NEXT(asteroid_objp) ) {
if (asteroid_objp->type == OBJ_ASTEROID ) {
asteroid *asp = &Asteroids[asteroid_objp->instance];
if ( asp->target_objnum >= 0 ) {
count++;
}
}
}
return count;
}
/**
* Pick object to throw asteroids at.
* Pick any capital or big ship inside the bounds of the asteroid field.
@@ -2106,10 +2042,17 @@ static int set_asteroid_throw_objnum()
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
ship_objp = &Objects[so->objnum];
float radius = ship_objp->radius*2.0f;
if (Ship_info[Ships[ship_objp->instance].ship_info_index].is_big_or_huge()) {
if (asteroid_is_ship_inside_field(&Asteroid_field, &ship_objp->pos, ship_objp->radius))
return so->objnum;
if (ship_objp->pos.xyz.x + radius > Asteroid_field.min_bound.xyz.x)
if (ship_objp->pos.xyz.y + radius > Asteroid_field.min_bound.xyz.y)
if (ship_objp->pos.xyz.z + radius > Asteroid_field.min_bound.xyz.z)
if (ship_objp->pos.xyz.x - radius < Asteroid_field.max_bound.xyz.x)
if (ship_objp->pos.xyz.y - radius < Asteroid_field.max_bound.xyz.y)
if (ship_objp->pos.xyz.z - radius < Asteroid_field.max_bound.xyz.z)
if (!asteroid_in_inner_bound(&Asteroid_field, &ship_objp->pos, radius))
return so->objnum;
}
}
return -1;
@@ -2126,18 +2069,9 @@ void asteroid_frame()
return;
}
if (Default_asteroid_throwing_behavior) {
int objnum = set_asteroid_throw_objnum();
if (Asteroid_targets.empty() || Asteroid_targets[0].objnum != objnum) {
Asteroid_targets.clear();
if (objnum >= 0)
asteroid_add_target(&Objects[objnum]);
}
}
Asteroid_throw_objnum = set_asteroid_throw_objnum();
sanitize_asteroid_targets_list();
maybe_throw_asteroid();
maybe_throw_asteroid(count_incident_asteroids());
}
/**
-7
View File
@@ -36,11 +36,6 @@ class model_draw_list;
// Goober5000 - currently same as MAX_SHIP_DETAIL_LEVELS (put here to avoid an #include)
#define MAX_ASTEROID_DETAIL_LEVELS 5
// whether to do retail behavior, just throw at the first big ship in the field
// must be explicitly opted out of by the mission
extern bool Default_asteroid_throwing_behavior;
extern SCP_vector<SCP_string> Asteroid_target_ships;
// Data structure to track the active asteroids
typedef struct asteroid_obj {
@@ -143,7 +138,6 @@ typedef struct asteroid_field {
field_type_t field_type; // active throws and wraps, passive does not
debris_genre_t debris_genre; // type of debris (ship or asteroid) [generic type]
int field_debris_type[MAX_ACTIVE_DEBRIS_TYPES]; // one of the debris type defines above
int num_used_field_debris_types; // how many of the above are used
} asteroid_field;
extern SCP_vector< asteroid_info > Asteroid_info;
@@ -171,7 +165,6 @@ int asteroid_collide_objnum(object *asteroid_objp);
float asteroid_time_to_impact(object *asteroid_objp);
void asteroid_show_brackets();
void asteroid_target_closest_danger();
void asteroid_add_target(object* objp);
// need to extern for multiplayer
void asteroid_sub_create(object *parent_objp, int asteroid_type, vec3d *relvec);
+2 -18
View File
@@ -716,7 +716,7 @@ bool StartAutopilot()
vm_vec_scale(&pos, 5*speed_cap*tc_factor); // pos is now scaled by 5 times the speed (5 seconds ahead)
vm_vec_add(&pos, &pos, &Autopilot_flight_leader->pos); // pos is now 5*speed cap in front of player ship
switch (Random::next(24))
switch (myrand()%24)
// 8 different ways of getting perp points
// 4 of which will not be used when capships are present (anything below, or straight above)
{
@@ -795,8 +795,7 @@ bool StartAutopilot()
vm_vec_scale(&perp, Autopilot_flight_leader->radius+radius);
// randomly scale up/down by up to 20%
// jg18 - original comment said [-20,20] but preserving existing behavior
j = Random::next(-19, 20);
j = 20-myrand()%40; // [-20,20]
vm_vec_scale(&perp, 1.0f+(float(j)/100.0f));
vm_vec_add(&cameraPos, &pos, &perp);
@@ -1581,18 +1580,3 @@ bool IsVisited(int nav)
return 1;
return 0;
}
//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
void Nav_SetColor(const char *nav, bool visited, ubyte r, ubyte g, ubyte b)
{
int n = FindNav(nav);
if (n >= 0 && n < MAX_NAVPOINTS)
{
auto rgb = visited ? Navs[n].visited_color : Navs[n].normal_color;
rgb[0] = r;
rgb[1] = g;
rgb[2] = b;
}
}
-12
View File
@@ -31,8 +31,6 @@ class NavPoint
public:
char m_NavName[32];
int flags;
ubyte normal_color[3];
ubyte visited_color[3];
const void *target_obj;
int waypoint_num; //only used when flags & NP_WAYPOINT
@@ -44,14 +42,6 @@ public:
m_NavName[0] = 0;
flags = 0;
normal_color[0] = 0x80;
normal_color[1] = 0x80;
normal_color[2] = 0xFF;
visited_color[0] = 0xFF;
visited_color[1] = 0xFF;
visited_color[2] = 0x00;
target_obj = nullptr;
waypoint_num = -1;
}
@@ -165,8 +155,6 @@ unsigned int DistanceTo(int nav);
bool IsVisited(const char *nav);
bool IsVisited(int nav);
void Nav_SetColor(const char *nav, bool visited, ubyte r, ubyte g, ubyte b);
void send_autopilot_msg(const char *msg, const char *snd=nullptr);
void send_autopilot_msgID(int msgid);
#endif
+19 -19
View File
@@ -592,9 +592,12 @@ void bm_convert_format(bitmap *bmp, ushort flags) {
return;
if (Is_standalone) {
Assert(bmp->bpp == 8);
return;
} else {
if(!(flags & BMP_AABITMAP))
if (flags & BMP_AABITMAP)
Assert(bmp->bpp == 8);
else
Assert((bmp->bpp == 16) || (bmp->bpp == 32));
}
@@ -1117,7 +1120,7 @@ static int bm_load_info(BM_TYPE type, const char *filename, CFILE *img_cfp, int
return 0;
}
int bm_load(const char *real_filename, int dir_type) {
int bm_load(const char *real_filename) {
int free_slot = -1;
int w, h, bpp = 8;
int rc = 0;
@@ -1149,7 +1152,6 @@ int bm_load(const char *real_filename, int dir_type) {
if (Is_standalone) {
char standalone_filename[MAX_FILENAME_LEN] = "right_bracket";
strcpy_s(filename, standalone_filename);
dir_type = CF_TYPE_INTERFACE;
}
// safety catch for strcat...
@@ -1162,11 +1164,11 @@ int bm_load(const char *real_filename, int dir_type) {
// Lets find out what type it is
{
// see if it's already loaded (checks for any type with filename)
if (bm_load_sub_fast(filename, &handle, dir_type))
if (bm_load_sub_fast(filename, &handle))
return handle;
// if we are still here then we need to fall back to a file-based search
int rval = bm_load_sub_slow(filename, BM_NUM_TYPES, bm_ext_list, &img_cfp, dir_type);
int rval = bm_load_sub_slow(filename, BM_NUM_TYPES, bm_ext_list, &img_cfp);
if (rval < 0)
return -1;
@@ -1222,7 +1224,7 @@ int bm_load(const char *real_filename, int dir_type) {
entry->bm.palette = nullptr;
entry->num_mipmaps = mm_lvl;
entry->mem_taken = (size_t)bm_size;
entry->dir_type = dir_type;
entry->dir_type = CF_TYPE_ANY;
entry->handle = handle;
entry->load_count++;
@@ -1233,8 +1235,8 @@ int bm_load(const char *real_filename, int dir_type) {
return handle;
}
int bm_load(const SCP_string& filename, int dir_type) {
return bm_load(filename.c_str(), dir_type);
int bm_load(const SCP_string& filename) {
return bm_load(filename.c_str());
}
bool bm_load_and_parse_eff(const char *filename, int dir_type, int *nframes, int *nfps, int *key, BM_TYPE *type) {
@@ -1422,7 +1424,7 @@ static int bm_load_image_data(int handle, int bpp, ushort flags, bool nodebug)
return 0;
}
int bm_load_animation(const char *real_filename, int *nframes, int *fps, int *keyframe, float *total_time, bool can_drop_frames, int dir_type, bool rethrow_exceptions) {
int bm_load_animation(const char *real_filename, int *nframes, int *fps, int *keyframe, float *total_time, bool can_drop_frames, int dir_type) {
int i, n;
anim the_anim;
CFILE *img_cfp = nullptr;
@@ -1464,7 +1466,6 @@ int bm_load_animation(const char *real_filename, int *nframes, int *fps, int *ke
if (Is_standalone) {
char standalone_filename[MAX_FILENAME_LEN] = "cursorweb";
strcpy_s(filename, standalone_filename);
dir_type = CF_TYPE_INTERFACE;
}
// safety catch for strcat...
@@ -1593,15 +1594,10 @@ int bm_load_animation(const char *real_filename, int *nframes, int *fps, int *ke
img_size = the_apng.imgsize();
}
catch (const apng::ApngException& e) {
mprintf(("Failed to load apng: %s\n", e.what() ));
if (img_cfp != nullptr)
cfclose(img_cfp);
if (rethrow_exceptions) {
throw e;
}
else {
mprintf(("Failed to load apng: %s\n", e.what()));
return -1;
}
return -1;
}
}
else {
@@ -1793,7 +1789,7 @@ int bm_load_either(const char *filename, int *nframes, int *fps, int *keyframe,
*fps = 0;
int tidx = bm_load_animation(filename, nframes, fps, keyframe, nullptr, can_drop_frames, dir_type);
if (tidx == -1) {
tidx = bm_load(filename, dir_type);
tidx = bm_load(filename);
if (tidx != -1 && nframes != NULL)
*nframes = 1;
}
@@ -1872,7 +1868,9 @@ bitmap * bm_lock(int handle, int bpp, ushort flags, bool nodebug) {
}
// otherwise do it as normal
else {
if ((flags & BMP_TEX_NONCOMP) && (!(flags & BMP_TEX_COMP))) {
if (flags & BMP_AABITMAP) {
Assert(bpp == 8);
} else if ((flags & BMP_TEX_NONCOMP) && (!(flags & BMP_TEX_COMP))) {
Assert(bpp >= 16); // cheating but bpp passed isn't what we normally end up with
} else if ((flags & BMP_TEX_DXT1) || (flags & BMP_TEX_DXT3) || (flags & BMP_TEX_DXT5) || (flags & BMP_TEX_BC7)) {
Assert(bpp >= 16); // cheating but bpp passed isn't what we normally end up with
@@ -1882,6 +1880,8 @@ bitmap * bm_lock(int handle, int bpp, ushort flags, bool nodebug) {
(be->type == BM_TYPE_CUBEMAP_DXT3) ||
(be->type == BM_TYPE_CUBEMAP_DXT5));
Assert(bpp >= 16);
} else {
Assert(0); //?
}
}
+3 -10
View File
@@ -177,7 +177,7 @@ void clear_bm_lookup_cache();
* @returns The bitmap number if successful, else
* @returns a negative value if not
*/
int bm_load(const char* filename, int dir_type = CF_TYPE_ANY);
int bm_load(const char* filename);
/**
* @brief Loads a bitmap so we can draw with it later. (Preferred version)
@@ -187,7 +187,7 @@ int bm_load(const char* filename, int dir_type = CF_TYPE_ANY);
* @returns The bitmap number if successful, else
* @returns a negative value if not
*/
int bm_load(const SCP_string& filename, int dir_type = CF_TYPE_ANY);
int bm_load(const SCP_string& filename);
/**
* @brief Reloads a bitmap as a duplicate.
@@ -289,14 +289,7 @@ bool bm_release_rendertarget(int handle);
* @returns The bm number of the first bitmap in the sequence if successful, or
* @returns A negative value if unsuccessful
*/
int bm_load_animation(const char *filename,
int *nframes = nullptr,
int *fps = nullptr,
int *keyframe = nullptr,
float *total_time = nullptr,
bool can_drop_frames = false,
int dir_type = CF_TYPE_ANY,
bool rethrow_exceptions = false);
int bm_load_animation(const char *filename, int *nframes = nullptr, int *fps = nullptr, int *keyframe = nullptr, float *total_time = nullptr, bool can_drop_frames = 0, int dir_type = CF_TYPE_ANY);
/**
* @brief Loads either animation (bm_load_animation) or still image (bm_load)
+62 -169
View File
@@ -9,13 +9,11 @@
#include "model/model.h" //polymodel, model_get
#include "options/Option.h"
#include "parse/parselo.h"
#include "parse/sexp_container.h"
#include "playerman/player.h" //player_get_padlock_orient
#include "ship/ship.h" //compute_slew_matrix
//*************************IMPORTANT GLOBALS*************************
float VIEWER_ZOOM_DEFAULT = 0.75f; // Default viewer zoom, 0.625 as per multi-lateral agreement on 3/24/97
float COCKPIT_ZOOM_DEFAULT = 0.75f;
float Sexp_fov = 0.0f;
warp_camera Warp_camera;
@@ -45,7 +43,7 @@ auto FovOption = options::OptionBuilder<float>("Graphics.FOV", "Field Of View",
.finish();
//*************************CLASS: camera*************************
//This is where the camera class begins! :D
//This is where the camera class beings! :D
camera::camera(const char *in_name, int in_signature)
{
set_name(in_name);
@@ -239,24 +237,12 @@ void camera::set_rotation_facing(vec3d *in_target, float in_rotation_time, float
{
matrix temp_matrix = IDENTITY_MATRIX;
if (in_target != nullptr)
if(in_target != NULL)
{
vec3d position;
matrix orient_buf;
matrix *orient = nullptr;
vec3d position = vmd_zero_vector;
this->get_info(&position, NULL);
if (Use_host_orientation_for_set_camera_facing)
{
// we want to retrieve the camera's orientation, so provide a buffer for it
orient = &orient_buf;
// reset the camera's orientation state because if we have no host object, that is what the camera will default to
c_ori = vmd_identity_matrix;
}
this->get_info(&position, orient, false);
if (vm_vec_same(in_target, &position))
if(in_target->xyz.x == position.xyz.x && in_target->xyz.y == position.xyz.y && in_target->xyz.z == position.xyz.z)
{
Warning(LOCATION, "Camera tried to point to self");
return;
@@ -264,25 +250,7 @@ void camera::set_rotation_facing(vec3d *in_target, float in_rotation_time, float
vec3d targetvec;
vm_vec_normalized_dir(&targetvec, in_target, &position);
if (Use_host_orientation_for_set_camera_facing)
{
// point along the target vector, but using the host orient's roll
vm_vector_2_matrix(&temp_matrix, &targetvec, &orient->vec.uvec, nullptr);
// if we have a host, we need the difference between the camera's current orient and the orient we want
// if not, we will later set the absolute orientation, rather than the orientation relative to the host
if (object_host.IsValid())
{
vm_transpose(orient);
temp_matrix = temp_matrix * *orient;
}
}
else
{
// point directly along the target vector
vm_vector_2_matrix(&temp_matrix, &targetvec, nullptr, nullptr);
}
vm_vector_2_matrix(&temp_matrix, &targetvec, NULL, NULL);
}
set_rotation(&temp_matrix, in_rotation_time, in_rotation_acceleration_time, in_rotation_deceleration_time);
@@ -330,16 +298,13 @@ float camera::get_fov()
}
eye* get_submodel_eye(polymodel *pm, int submodel_num);
void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_orientation)
void camera::get_info(vec3d *position, matrix *orientation)
{
if(position == NULL && orientation == NULL)
return;
eye* eyep = NULL;
vec3d host_normal;
matrix host_orient = vmd_identity_matrix;
bool use_host_orient = false;
//POSITION
if(!(flags & CAM_STATIONARY_POS) || object_host.IsValid())
{
@@ -377,19 +342,16 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or
Assertion(objp->type == OBJ_SHIP, "This part of the code expects the object to be a ship");
vec3d c_pos_in;
model_instance_local_to_global_point_dir(&c_pos_in, &host_normal, &eyep->pnt, &eyep->norm, pm, pmi, eyep->parent);
find_submodel_instance_point_normal(&c_pos_in, &host_normal, pm, pmi, eyep->parent, &eyep->pnt, &eyep->norm);
vm_vec_unrotate(&c_pos, &c_pos_in, &objp->orient);
vm_vec_add2(&c_pos, &objp->pos);
}
else
{
if (pmi != nullptr)
{
model_instance_local_to_global_point_orient(&c_pos, &host_orient, &pt, &vmd_identity_matrix, pm, pmi, object_host_submodel, &objp->orient, &objp->pos);
use_host_orient = true;
}
model_instance_find_world_point(&c_pos, &pt, pm, pmi, object_host_submodel, &objp->orient, &objp->pos);
else
model_local_to_global_point( &c_pos, &pt, pm, object_host_submodel, &objp->orient, &objp->pos );
model_find_world_point( &c_pos, &pt, pm, object_host_submodel, &objp->orient, &objp->pos );
}
}
}
@@ -405,7 +367,7 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or
}
}
if (position != nullptr)
if(position != NULL)
*position = c_pos;
//ORIENTATION
@@ -416,32 +378,32 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or
{
if(object_target.IsValid())
{
object *target_objp = object_target.objp;
int model_num = object_get_model(target_objp);
polymodel *target_pm = nullptr;
polymodel_instance *target_pmi = nullptr;
object *objp = object_target.objp;
int model_num = object_get_model(objp);
polymodel *pm = nullptr;
polymodel_instance *pmi = nullptr;
vec3d target_pos = vmd_zero_vector;
//See if we can get the model
if(model_num >= 0)
{
target_pm = model_get(model_num);
if (target_objp->type == OBJ_SHIP)
target_pmi = model_get_instance(Ships[target_objp->instance].model_instance_num);
pm = model_get(model_num);
if (objp->type == OBJ_SHIP)
pmi = model_get_instance(Ships[objp->instance].model_instance_num);
}
//If we don't have a submodel or don't have the model use object pos
//Otherwise, find the submodel pos as it is rotated
if (object_target_submodel < 0 || target_pm == nullptr)
if(object_target_submodel < 0 || pm == NULL)
{
target_pos = target_objp->pos;
target_pos = objp->pos;
}
else
{
if (target_pmi != nullptr)
model_instance_local_to_global_point(&target_pos, &vmd_zero_vector, target_pm, target_pmi, object_target_submodel, &target_objp->orient, &target_objp->pos);
if (pmi != nullptr)
model_instance_find_world_point(&target_pos, &vmd_zero_vector, pm, pmi, object_target_submodel, &objp->orient, &objp->pos);
else
model_local_to_global_point( &target_pos, &vmd_zero_vector, target_pm, object_target_submodel, &target_objp->orient, &target_objp->pos );
model_find_world_point( &target_pos, &vmd_zero_vector, pm, object_target_submodel, &objp->orient, &objp->pos );
}
vec3d targetvec;
@@ -456,10 +418,6 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or
vm_vector_2_matrix(&c_ori, &host_normal, vm_vec_same(&host_normal, &object_host.objp->orient.vec.uvec)?NULL:&object_host.objp->orient.vec.uvec, NULL);
target_set = true;
}
else if (use_host_orient)
{
c_ori = host_orient;
}
else
{
c_ori = object_host.objp->orient;
@@ -470,30 +428,23 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or
c_ori = vmd_identity_matrix;
}
//Do custom orientation stuff, if needed
if (func_custom_orientation != nullptr && !target_set)
matrix mtxA = c_ori;
matrix mtxB = IDENTITY_MATRIX;
float pos = 0.0f;
for(int i = 0; i < 9; i++)
{
func_custom_orientation(this, &c_ori);
ori[i].get(&pos, NULL);
mtxB.a1d[i] = pos;
}
vm_matrix_x_matrix(&c_ori, &mtxA, &mtxB);
// only do this if we want to find where the camera is actually pointing;
// skip this if we just want the orientation of the host
if (apply_camera_orientation)
{
matrix mtxA = c_ori;
matrix mtxB = IDENTITY_MATRIX;
float pos = 0.0f;
for (int i = 0; i < 9; i++)
{
ori[i].get(&pos, nullptr);
mtxB.a1d[i] = pos;
}
vm_matrix_x_matrix(&c_ori, &mtxA, &mtxB);
vm_orthogonalize_matrix(&c_ori);
}
vm_orthogonalize_matrix(&c_ori);
}
//Do custom orientation stuff, if needed
if(func_custom_orientation != NULL && !target_set)
{
func_custom_orientation(this, &c_ori);
}
*orientation = c_ori;
}
}
@@ -608,46 +559,11 @@ void warp_camera::get_info(vec3d *position, matrix *orientation)
#define MAX_SUBTITLE_LINES 64
subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char* in_imageanim, float in_display_time,
float in_fade_time, const color* in_text_color, int in_text_fontnum, bool center_x, bool center_y, int in_width,
int in_height, bool in_post_shaded, int in_line_height_modifier, bool in_adjust_wh)
: display_time(-1.0f), fade_time(-1.0f), text_fontnum(-1), line_height_modifier(0),
image_id(-1), time_displayed(-1.0f), time_displayed_end(-1.0f), post_shaded(false), do_screen_scaling(false)
float in_fade_time, const color *in_text_color, int in_text_fontnum, bool center_x, bool center_y, int in_width,
int in_height, bool in_post_shaded)
:display_time(-1.0f), fade_time(-1.0f), text_fontnum(-1), time_displayed(-1.0f), time_displayed_end(-1.0f),
post_shaded(false)
{
if ((in_text == nullptr || *in_text == '\0') && (in_imageanim == nullptr || *in_imageanim == '\0'))
return;
auto screen_w = gr_screen.center_w;
auto screen_h = gr_screen.center_h;
// we might be doing screen scaling
if (Show_subtitle_screen_base_res[0] > 0 && Show_subtitle_screen_base_res[1] > 0)
{
// The way this works is we specify our subtitle coordinates relative to a base resolution, e.g. 1024x768.
// The base resolution is adjusted behind the scenes to match the aspect ratio of the current resolution
// (and the subtitle coordinates are adjusted as well). Then the adjusted subtitles are resized by the
// graphics code when they are drawn to the screen.
do_screen_scaling = true;
// we might need to adjust the coordinates for aspect ratio
if (in_adjust_wh)
{
if (Show_subtitle_screen_base_res[0] != Show_subtitle_screen_adjusted_res[0])
{
in_x_pos = in_x_pos * Show_subtitle_screen_adjusted_res[0] / Show_subtitle_screen_base_res[0];
in_width = in_width * Show_subtitle_screen_adjusted_res[0] / Show_subtitle_screen_base_res[0];
}
if (Show_subtitle_screen_base_res[1] != Show_subtitle_screen_adjusted_res[1])
{
in_y_pos = in_y_pos * Show_subtitle_screen_adjusted_res[1] / Show_subtitle_screen_base_res[1];
in_height = in_height * Show_subtitle_screen_adjusted_res[1] / Show_subtitle_screen_base_res[1];
}
}
// use these because we will be scaling the subtitle when it is displayed
screen_w = Show_subtitle_screen_adjusted_res[0];
screen_h = Show_subtitle_screen_adjusted_res[1];
}
// Initialize color
gr_init_color(&text_color, 0, 0, 0);
@@ -657,15 +573,19 @@ subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char*
memset( imageanim, 0, sizeof(imageanim) );
memset( &text_pos, 0, 2*sizeof(int) );
memset( &image_pos, 0, 4*sizeof(int) );
image_id = -1;
if ( ((in_text != NULL) && (strlen(in_text) <= 0)) && ((in_imageanim != NULL) && (strlen(in_imageanim) <= 0)) )
return;
if (in_text != NULL && in_text[0] != '\0')
{
text_buf = in_text;
sexp_replace_variable_names_with_values(text_buf);
sexp_container_replace_refs_with_values(text_buf);
in_text = text_buf.c_str();
}
int num_text_lines = 0;
const char *text_line_ptrs[MAX_SUBTITLE_LINES];
int text_line_lens[MAX_SUBTITLE_LINES];
@@ -688,7 +608,6 @@ subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char*
else
gr_init_alphacolor(&text_color, 255, 255, 255, 255);
text_fontnum = in_text_fontnum;
line_height_modifier = in_line_height_modifier;
//Setup display and fade time
display_time = fl_abs(in_display_time);
@@ -727,14 +646,6 @@ subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char*
if(w > tw)
tw = w;
if (line_height_modifier != 0.0f)
{
if (Show_subtitle_uses_pixels)
h = line_height_modifier;
else
h = fl2i(h * line_height_modifier / 100.0f);
}
th += h;
}
@@ -760,20 +671,20 @@ subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char*
//Center it?
if(center_x)
image_pos.x = (screen_w - tw)/2;
image_pos.x = (gr_screen.center_w - tw)/2 + gr_screen.center_offset_x;
if(center_y)
image_pos.y = (screen_h - th)/2;
image_pos.y = (gr_screen.center_h - th)/2 + gr_screen.center_offset_y;
}
if(in_x_pos < 0 && !center_x)
image_pos.x += screen_w + in_x_pos;
image_pos.x += gr_screen.center_offset_x + gr_screen.center_w + in_x_pos;
else if(!center_x)
image_pos.x += in_x_pos;
image_pos.x += gr_screen.center_offset_x + in_x_pos;
if(in_y_pos < 0 && !center_y)
image_pos.y += screen_h + in_y_pos;
image_pos.y += gr_screen.center_offset_y + gr_screen.center_h + in_y_pos;
else if(!center_y)
image_pos.y += in_y_pos;
image_pos.y += gr_screen.center_offset_y + in_y_pos;
image_pos.w = in_width;
image_pos.h = in_height;
@@ -829,32 +740,24 @@ void subtitle::do_frame(float frametime)
int x = text_pos.x;
int y = text_pos.y;
auto sizing_mode = do_screen_scaling ? GR_RESIZE_FULL : GR_RESIZE_NONE;
if (do_screen_scaling)
gr_set_screen_scale(Show_subtitle_screen_adjusted_res[0], Show_subtitle_screen_adjusted_res[1]);
// do the actual drawing ---------------------
for(SCP_vector<SCP_string>::iterator line = text_lines.begin(); line != text_lines.end(); ++line)
{
gr_string(x, y, (char*)line->c_str(), sizing_mode);
if (line_height_modifier != 0.0f)
{
if (Show_subtitle_uses_pixels)
font_height = line_height_modifier;
else
font_height = fl2i(font_height * line_height_modifier / 100.0f);
}
gr_string(x, y, (char*)line->c_str(), GR_RESIZE_NONE);
y += font_height;
}
// restore old font
if (old_fontnum >= 0)
{
font::set_font(old_fontnum);
}
if(image_id >= 0)
{
gr_set_bitmap(image_id, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, text_color.alpha/255.0f);
// image scaling?
// scaling?
if (image_pos.w > 0 || image_pos.h > 0)
{
int orig_w, orig_h;
@@ -866,25 +769,16 @@ void subtitle::do_frame(float frametime)
scale.xyz.z = 1.0f;
gr_push_scale_matrix(&scale);
gr_bitmap(fl2i(image_pos.x / scale.xyz.x), fl2i(image_pos.y / scale.xyz.y), sizing_mode);
gr_bitmap(fl2i(image_pos.x / scale.xyz.x), fl2i(image_pos.y / scale.xyz.y), GR_RESIZE_NONE);
gr_pop_scale_matrix();
}
// no scaling
else
{
gr_bitmap(image_pos.x, image_pos.y, sizing_mode);
gr_bitmap(image_pos.x, image_pos.y, GR_RESIZE_NONE);
}
}
// finished the actual drawing ---------------
if (do_screen_scaling)
gr_reset_screen_scale();
// restore old font
if (old_fontnum >= 0)
font::set_font(old_fontnum);
time_displayed += frametime;
}
@@ -899,9 +793,9 @@ subtitle::~subtitle()
void subtitle::clone(const subtitle &sub)
{
text_lines = sub.text_lines;
text_fontnum = sub.text_fontnum;
line_height_modifier = sub.line_height_modifier;
// copy the structs
text_pos = sub.text_pos;
@@ -924,7 +818,6 @@ void subtitle::clone(const subtitle &sub)
time_displayed_end = sub.time_displayed_end;
post_shaded = sub.post_shaded;
do_screen_scaling = sub.do_screen_scaling;
}
subtitle& subtitle::operator=(const subtitle &sub)
+2 -7
View File
@@ -75,7 +75,7 @@ public:
object *get_object_target();
int get_object_target_submodel();
float get_fov();
void get_info(vec3d *position, matrix *orientation, bool apply_camera_orientation = true);
void get_info(vec3d *position, matrix *orientation);
//Is
bool is_empty(){return sig < 0;}
@@ -123,7 +123,6 @@ private:
float fade_time;
color text_color;
int text_fontnum;
int line_height_modifier;
//Done with set
char imageanim[MAX_FILENAME_LEN];
@@ -137,13 +136,10 @@ private:
float time_displayed_end;
bool post_shaded;
bool do_screen_scaling;
public:
subtitle(int in_x_pos, int in_y_pos, const char* in_text = NULL, const char* in_imageanim = NULL,
float in_display_time = 0, float in_fade_time = 0.0f, const color *in_text_color = NULL, int in_text_fontnum = -1,
bool center_x = false, bool center_y = false, int in_width = 0, int in_height = 0, bool post_shaded = false,
int in_line_height_modifier = 0, bool in_adjust_wh = true);
bool center_x = false, bool center_y = false, int in_width = 0, int in_height = 0, bool post_shaded=false);
~subtitle();
subtitle(const subtitle &sub) { clone(sub); }
@@ -156,7 +152,6 @@ public:
//Some global stuff
extern SCP_vector<subtitle> Subtitles;
extern float VIEWER_ZOOM_DEFAULT;
extern float COCKPIT_ZOOM_DEFAULT;
extern float Sexp_fov;
//Helpful functions
+5 -6
View File
@@ -74,24 +74,23 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
{ CF_TYPE_FONT, "data" DIR_SEPARATOR_STR "fonts", ".vf .ttf .otf", CF_TYPE_DATA },
{ CF_TYPE_EFFECTS, "data" DIR_SEPARATOR_STR "effects", ".ani .eff .pcx .neb .tga .jpg .png .dds .sdr", CF_TYPE_DATA },
{ CF_TYPE_HUD, "data" DIR_SEPARATOR_STR "hud", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_PLAYERS, "data" DIR_SEPARATOR_STR "players", ".hcf", /* DON'T add pilot files here!! */ CF_TYPE_DATA },
{ CF_TYPE_PLAYERS, "data" DIR_SEPARATOR_STR "players", ".hcf", CF_TYPE_DATA },
{ CF_TYPE_PLAYER_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "images", ".pcx .png .dds", CF_TYPE_PLAYERS },
{ CF_TYPE_SQUAD_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "squads", ".pcx .png .dds", CF_TYPE_PLAYERS },
{ CF_TYPE_SINGLE_PLAYERS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "single", ".pl2 .cs2 .plr .csg .css .json", CF_TYPE_PLAYERS },
{ CF_TYPE_MULTI_PLAYERS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "multi", ".plr .json", CF_TYPE_PLAYERS },
{ CF_TYPE_CACHE, "data" DIR_SEPARATOR_STR "cache", ".clr .tmp .bx", CF_TYPE_DATA }, //clr=cached color
{ CF_TYPE_MULTI_CACHE, "data" DIR_SEPARATOR_STR "multidata", ".pcx .png .jpg .dds .fs2 .txt", CF_TYPE_DATA },
{ CF_TYPE_MULTI_CACHE, "data" DIR_SEPARATOR_STR "multidata", ".pcx .png .dds .fs2 .txt", CF_TYPE_DATA },
{ CF_TYPE_MISSIONS, "data" DIR_SEPARATOR_STR "missions", ".fs2 .fc2 .ntl .ssv", CF_TYPE_DATA },
{ CF_TYPE_CONFIG, "data" DIR_SEPARATOR_STR "config", ".cfg .tbl .tbm .xml .csv", CF_TYPE_DATA },
{ CF_TYPE_DEMOS, "data" DIR_SEPARATOR_STR "demos", ".fsd", CF_TYPE_DATA },
{ CF_TYPE_CBANIMS, "data" DIR_SEPARATOR_STR "cbanims", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_INTEL_ANIMS, "data" DIR_SEPARATOR_STR "intelanims", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_SCRIPTS, "data" DIR_SEPARATOR_STR "scripts", ".lua .lc .fnl", CF_TYPE_DATA },
{ CF_TYPE_SCRIPTS, "data" DIR_SEPARATOR_STR "scripts", ".lua .lc", CF_TYPE_DATA },
{ CF_TYPE_FICTION, "data" DIR_SEPARATOR_STR "fiction", ".txt", CF_TYPE_DATA },
{ CF_TYPE_FREDDOCS, "data" DIR_SEPARATOR_STR "freddocs", ".html", CF_TYPE_DATA },
{ CF_TYPE_INTERFACE_MARKUP, "data" DIR_SEPARATOR_STR "interface" DIR_SEPARATOR_STR "markup", ".rml", CF_TYPE_INTERFACE },
{ CF_TYPE_INTERFACE_CSS, "data" DIR_SEPARATOR_STR "interface" DIR_SEPARATOR_STR "css", ".rcss", CF_TYPE_INTERFACE },
{ CF_TYPE_PLAYER_BINDS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "presets", ".json", CF_TYPE_PLAYERS },
};
// clang-format on
@@ -635,7 +634,7 @@ void cf_create_directory(int dir_type, uint32_t location_flags)
//
// parameters: *filepath ==> name of file to open (may be path+name)
// *mode ==> specifies how file should be opened (eg "rb" for read binary)
// passing NULL to mode triggers an assert and returns NULL
// passing NULL to mode deletes the file if it exists and returns NULL
// type ==> one of: CFILE_NORMAL
// CFILE_MEMORY_MAPPED
// dir_type => override extension check, value is one of CF_TYPE* #defines
@@ -782,7 +781,7 @@ CFILE* _cfopen(const char* source, int line, const char* file_path, const char*
//
// parameters: *filepath ==> name of file to open (may be path+name)
// *mode ==> specifies how file should be opened (eg "rb" for read binary)
// passing NULL to mode triggers an assert and returns NULL
// passing NULL to mode deletes the file if it exists and returns NULL
// dir_type => override extension check, value is one of CF_TYPE* #defines
//
+1 -2
View File
@@ -81,10 +81,9 @@ typedef struct {
#define CF_TYPE_FREDDOCS 37
#define CF_TYPE_INTERFACE_MARKUP 38
#define CF_TYPE_INTERFACE_CSS 39
#define CF_TYPE_PLAYER_BINDS 40
#define CF_MAX_PATH_TYPES \
41 // Can be as high as you'd like //DTP; yeah but beware alot of things uses CF_MAX_PATH_TYPES
40 // Can be as high as you'd like //DTP; yeah but beware alot of things uses CF_MAX_PATH_TYPES
// TRUE if type is specified and valid
#define CF_TYPE_SPECIFIED(path_type) (((path_type)>CF_TYPE_INVALID) && ((path_type)<CF_MAX_PATH_TYPES))
+757 -478
View File
File diff suppressed because it is too large. Load diff
+145 -116
View File
@@ -19,7 +19,6 @@
#include "hud/hudconfig.h"
#include "io/joy.h"
#include "network/multi.h"
#include "network/multi_log.h"
#include "options/OptionsManager.h"
#include "osapi/osapi.h"
#include "parse/sexp.h"
@@ -43,7 +42,6 @@
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sstream>
#include <jansson.h>
@@ -197,7 +195,6 @@ Flag exe_params[] =
{ "-no_enhanced_sound", "Disable enhanced sound", false, 0, EASY_DEFAULT, "Audio", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-no_enhanced_sound", },
{ "-portable_mode", "Store config in portable location", false, 0, EASY_DEFAULT, "Launcher", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-portable_mode", },
{ "-joy_info", "Outputs SDL joystick info", true, 0, EASY_DEFAULT, "Launcher", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-joy_info",},
{ "-standalone", "Run as standalone server", false, 0, EASY_DEFAULT, "Multiplayer", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-standalone", },
{ "-startgame", "Skip mainhall and start hosting", false, 0, EASY_DEFAULT, "Multiplayer", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-startgame", },
@@ -223,9 +220,6 @@ Flag exe_params[] =
{ "-set_cpu_affinity", "Sets processor affinity to config value", true, 0, EASY_DEFAULT, "Troubleshoot", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-set_cpu_affinity", },
{ "-nograb", "Disables mouse grabbing", true, 0, EASY_DEFAULT, "Troubleshoot", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-nograb", },
{ "-noshadercache", "Disables the shader cache", true, 0, EASY_DEFAULT, "Troubleshoot", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-noshadercache", },
{ "-prefer_ipv4", "Prefer IPv4 DNS lookups", true, 0, EASY_DEFAULT, "Troubleshoot", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-prefer_ipv4", },
{ "-prefer_ipv6", "Prefer IPv6 DNS lookups", true, 0, EASY_DEFAULT, "Troubleshoot", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-prefer_ipv6", },
{ "-log_multi_packet", "Log multi packet types ", true, 0, EASY_DEFAULT, "Troubleshoot", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-log_multi_packet",},
#ifdef WIN32
{ "-fix_registry", "Use a different registry path", true, 0, EASY_DEFAULT, "Troubleshoot", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-fix_registry", },
#endif
@@ -246,7 +240,6 @@ Flag exe_params[] =
{ "-output_sexps", "Output SEXPs to sexps.html", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-output_sexps", },
{ "-output_scripting", "Output scripting to scripting.html", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-output_scripting", },
{ "-output_script_json", "Output scripting doc to scripting.json", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-output_script_json", },
{ "-controlconfig_tbl", "Save control presets to table", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-controlconfig_tbl", },
{ "-save_render_target", "Save render targets to file", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-save_render_target", },
{ "-verify_vps", "Spew VP CRCs to vp_crcs.txt", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-verify_vps", },
{ "-reparse_mainhall", "Reparse mainhall.tbl when loading halls", false, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-reparse_mainhall", },
@@ -258,8 +251,6 @@ Flag exe_params[] =
{ "-json_profiling", "Generate JSON profiling output", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-json_profiling", },
{ "-debug_window", "Enable the debug window", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-debug_window", },
{ "-gr_debug", "Output graphics debug information", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-gr_debug", },
{ "-stdout_log", "Output log file to stdout", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-stdout_log", },
{ "-slow_frames_ok", "Don't adjust timestamps for slow frames", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-slow_frames_ok", },
};
// clang-format on
@@ -315,7 +306,6 @@ int Cmdline_use_last_pilot = 0;
// Graphics related
cmdline_parm fov_arg("-fov", "Vertical field-of-view factor", AT_FLOAT); // Cmdline_fov -- comand line FOV -Bobboau
cmdline_parm fov_cockpit_arg("-fov_cockpit", "Vertical field-of-view factor for Cockpits", AT_FLOAT);
cmdline_parm clip_dist_arg("-clipdist", "Changes the distance from the viewpoint for the near-clipping plane", AT_FLOAT); // Cmdline_clip_dist
cmdline_parm spec_static_arg("-spec_static", "Adjusts suns contribution to specular highlights", AT_FLOAT);
cmdline_parm spec_point_arg("-spec_point", "Adjusts laser weapons contribution to specular highlights", AT_FLOAT);
@@ -385,23 +375,21 @@ int Cmdline_rearm_timer = 0;
int Cmdline_targetinfo = 0;
// Gameplay related
cmdline_parm use_3dwarp("-3dwarp", nullptr, AT_NONE); // Is now Fireball_use_3d_warp
cmdline_parm ship_choice_3d_arg("-ship_choice_3d", nullptr, AT_NONE); // Cmdline_ship_choice_3d
cmdline_parm weapon_choice_3d_arg("-weapon_choice_3d", nullptr, AT_NONE); // Cmdline_weapon_choice_3d
cmdline_parm use_warp_flash("-warp_flash", nullptr, AT_NONE); // Cmdline_warp_flash
cmdline_parm allow_autpilot_interrupt("-no_ap_interrupt", nullptr, AT_NONE);
cmdline_parm stretch_menu("-stretch_menu", nullptr, AT_NONE); // Cmdline_stretch_menu
cmdline_parm use_3dwarp("-3dwarp", NULL, AT_NONE); // Cmdline_3dwarp
cmdline_parm ship_choice_3d_arg("-ship_choice_3d", NULL, AT_NONE); // Cmdline_ship_choice_3d
cmdline_parm weapon_choice_3d_arg("-weapon_choice_3d", NULL, AT_NONE); // Cmdline_weapon_choice_3d
cmdline_parm use_warp_flash("-warp_flash", NULL, AT_NONE); // Cmdline_warp_flash
cmdline_parm allow_autpilot_interrupt("-no_ap_interrupt", NULL, AT_NONE);
cmdline_parm stretch_menu("-stretch_menu", NULL, AT_NONE); // Cmdline_stretch_menu
cmdline_parm no_screenshake("-no_screenshake", nullptr, AT_NONE); // Cmdline_no_screenshake
cmdline_parm deadzone("-deadzone",
"Sets the joystick deadzone. Integer value from 0 to 100 as a percentage of the joystick's range (100% would make the stick do nothing). Disables deadzone slider in the in-game Options menu.", AT_INT); //Cmdline_deadzone
int Cmdline_3dwarp = 0;
int Cmdline_ship_choice_3d = 0;
int Cmdline_weapon_choice_3d = 0;
int Cmdline_warp_flash = 0;
int Cmdline_autopilot_interruptable = 1;
int Cmdline_stretch_menu = 0;
int Cmdline_no_screenshake = 0;
int Cmdline_deadzone = -1;
// Audio related
cmdline_parm voice_recognition_arg("-voicer", NULL, AT_NONE); // Cmdline_voice_recognition
@@ -429,7 +417,6 @@ char *Cmdline_gateway_ip = nullptr;
// Launcher related options
cmdline_parm portable_mode("-portable_mode", NULL, AT_NONE);
cmdline_parm joy_info("-joy_info", "Outputs SDL joystick info", AT_NONE);
bool Cmdline_portable_mode = false;
@@ -449,9 +436,6 @@ cmdline_parm no_geo_sdr_effects("-no_geo_effects", NULL, AT_NONE);
cmdline_parm set_cpu_affinity("-set_cpu_affinity", NULL, AT_NONE);
cmdline_parm nograb_arg("-nograb", NULL, AT_NONE);
cmdline_parm noshadercache_arg("-noshadercache", NULL, AT_NONE);
cmdline_parm prefer_ipv4_arg("-prefer_ipv4", nullptr, AT_NONE);
cmdline_parm prefer_ipv6_arg("-prefer_ipv6", nullptr, AT_NONE);
cmdline_parm log_multi_packet_arg("-log_multi_packet", nullptr, AT_NONE);
#ifdef WIN32
cmdline_parm fix_registry("-fix_registry", NULL, AT_NONE);
#endif
@@ -470,9 +454,6 @@ bool Cmdline_no_geo_sdr_effects = false;
bool Cmdline_set_cpu_affinity = false;
bool Cmdline_nograb = false;
bool Cmdline_noshadercache = false;
bool Cmdline_prefer_ipv4 = false;
bool Cmdline_prefer_ipv6 = false;
bool Cmdline_dump_packet_type = false;
#ifdef WIN32
bool Cmdline_alternate_registry_path = false;
#endif
@@ -506,7 +487,6 @@ cmdline_parm frame_profile_arg("-profile_frame_time", NULL, AT_NONE); //Cmdline_
cmdline_parm debug_window_arg("-debug_window", NULL, AT_NONE); // Cmdline_debug_window
cmdline_parm graphics_debug_output_arg("-gr_debug", nullptr, AT_NONE); // Cmdline_graphics_debug_output
cmdline_parm log_to_stdout_arg("-stdout_log", nullptr, AT_NONE); // Cmdline_log_to_stdout
cmdline_parm slow_frames_ok_arg("-slow_frames_ok", nullptr, AT_NONE); // Cmdline_slow_frames_ok
char *Cmdline_start_mission = NULL;
@@ -539,14 +519,12 @@ bool Cmdline_show_video_info = false;
bool Cmdline_debug_window = false;
bool Cmdline_graphics_debug_output = false;
bool Cmdline_log_to_stdout = false;
bool Cmdline_slow_frames_ok = false;
// Other
cmdline_parm get_flags_arg(GET_FLAGS_STRING, "Output the launcher flags file", AT_STRING);
cmdline_parm output_sexp_arg("-output_sexps", NULL, AT_NONE); //WMC - outputs all SEXPs to sexps.html
cmdline_parm output_scripting_arg("-output_scripting", NULL, AT_NONE); //WMC
cmdline_parm output_script_json_arg("-output_script_json", nullptr, AT_NONE); // m!m
cmdline_parm generate_controlconfig_arg("-controlconfig_tbl", nullptr, AT_NONE);
// Deprecated flags - CommanderDJ
cmdline_parm deprecated_spec_arg("-spec", "Deprecated", AT_NONE);
@@ -563,6 +541,18 @@ cmdline_parm deprecated_cache_bitmaps_arg("-cache_bitmaps", "Deprecated", AT_NON
cmdline_parm deprecated_no_emissive_arg("-no_emissive_light", "Deprecated", AT_NONE);
cmdline_parm deprecated_postprocess_arg("-post_process", "Deprecated", AT_NONE);
int Cmdline_deprecated_spec = 0;
int Cmdline_deprecated_glow = 0;
int Cmdline_deprecated_normal = 0;
int Cmdline_deprecated_env = 0;
int Cmdline_deprecated_tbp = 0;
int Cmdline_deprecated_jpgtga = 0;
int Cmdline_deprecated_nohtl = 0;
bool Cmdline_deprecated_brief_lighting = 0;
bool Cmdline_deprecated_missile_lighting = false;
bool Cmdline_deprecated_cache_bitmaps = false;
bool Cmdline_deprecated_postprocess = false;
#ifndef NDEBUG
// NOTE: this assumes that os_init() has already been called but isn't a fatal error if it hasn't
void cmdline_debug_print_cmdline()
@@ -588,41 +578,61 @@ void cmdline_debug_print_cmdline()
mprintf(("\n"));
//Print log messages about any deprecated flags we found - CommanderDJ
//Do it programmatically, rather than enumerating them by hand - MageKing17
for (parmp = GET_FIRST(&Parm_list); parmp != END_OF_LIST(&Parm_list); parmp = GET_NEXT(parmp)) {
if (parmp->name_found && !stricmp("deprecated", parmp->help)) {
mprintf(("Deprecated flag '%s' found. Please remove from your cmdline.\n", parmp->name));
}
if(Cmdline_deprecated_spec == 1)
{
mprintf(("Deprecated flag '-spec' found. Please remove from your cmdline.\n"));
}
if(Cmdline_deprecated_glow == 1)
{
mprintf(("Deprecated flag '-glow' found. Please remove from your cmdline.\n"));
}
if(Cmdline_deprecated_normal == 1)
{
mprintf(("Deprecated flag '-normal' found. Please remove from your cmdline.\n"));
}
if(Cmdline_deprecated_env == 1)
{
mprintf(("Deprecated flag '-env' found. Please remove from your cmdline.\n"));
}
if(Cmdline_deprecated_tbp == 1)
{
mprintf(("Deprecated flag '-tbp' found. Please remove from your cmdline.\n"));
}
if(Cmdline_deprecated_jpgtga == 1)
{
mprintf(("Deprecated flag '-jpgtga' found. Please remove from your cmdline.\n"));
}
if(Cmdline_deprecated_nohtl == 1)
{
mprintf(("Deprecated flag '-nohtl' found. Please remove from your cmdline.\n"));
}
if(Cmdline_deprecated_brief_lighting == 1)
{
mprintf(("Deprecated flag '-brief_lighting' found. Please remove from your cmdline.\n"));
}
if (Cmdline_deprecated_missile_lighting)
{
mprintf(("Deprecated flag '-missile_lighting' found. Please remove from your cmdline.\n"));
}
if (Cmdline_deprecated_cache_bitmaps) {
mprintf(("Deprecated flag '-cache_bitmaps' found. Please remove from your cmdline.\n"));
}
if (Cmdline_deprecated_postprocess) {
mprintf(("Deprecated flag '-post_process' found. Please remove from your cmdline.\n"));
}
}
#endif
// prints simple cmdline to multi.log
void cmdline_print_cmdline_multi()
{
cmdline_parm *parmp;
int found = 0;
std::ostringstream cmdline;
for (parmp = GET_FIRST(&Parm_list); parmp !=END_OF_LIST(&Parm_list); parmp = GET_NEXT(parmp) ) {
if ( parmp->name_found ) {
cmdline << " " << parmp->name;
if (parmp->args) {
cmdline << " " << parmp->args;
}
found++;
}
}
if ( !found ) {
cmdline << " <none>";
}
ml_printf("Command line:%s", cmdline.str().c_str());
}
// Return true if this character is an extra char (white space and quotes)
int is_extra_space(char ch)
{
@@ -1309,7 +1319,6 @@ static json_t* json_get_v1() {
json_array_append_new(caps_array, json_string("No D3D"));
json_array_append_new(caps_array, json_string("New Sound"));
json_array_append_new(caps_array, json_string("SDL"));
json_array_append_new(caps_array, json_string("Multijoy"));
json_object_set_new(root, "caps", caps_array);
}
@@ -1401,7 +1410,24 @@ static json_t* json_get_v1() {
json_object_set_new(root, "openal", openal_obj);
}
{
auto joystick_array = io::joystick::getJsonArray();
auto joystick_array = json_array();
auto joysticks = io::joystick::getJoystickInformations();
for (auto& info : joysticks) {
auto joystick_obj = json_object();
json_object_set_new(joystick_obj, "name", json_string(info.name.c_str()));
json_object_set_new(joystick_obj, "guid", json_string(info.guid.c_str()));
json_object_set_new(joystick_obj, "num_axes", json_integer(info.num_axes));
json_object_set_new(joystick_obj, "num_balls", json_integer(info.num_balls));
json_object_set_new(joystick_obj, "num_buttons", json_integer(info.num_buttons));
json_object_set_new(joystick_obj, "num_hats", json_integer(info.num_hats));
json_object_set_new(joystick_obj, "is_haptic", json_boolean(info.is_haptic));
json_array_append_new(joystick_array, joystick_obj);
}
json_object_set_new(root, "joysticks", joystick_array);
}
@@ -1504,11 +1530,6 @@ bool SetCmdlineParams()
return false;
}
if (joy_info.found()) {
io::joystick::printJoyJSON();
return false;
}
if (no_fpscap.found())
{
Cmdline_NoFPSCap = 1;
@@ -1540,9 +1561,6 @@ bool SetCmdlineParams()
// is this a standalone server??
if (standalone_arg.found()) {
Is_standalone = 1;
// also enable non-interactive by default here
Cmdline_noninteractive = true;
}
}
@@ -1601,7 +1619,7 @@ bool SetCmdlineParams()
Cmdline_game_name = gamename_arg.str();
// be sure that this string fits in our limits
if ( strlen(Cmdline_game_name) >= MAX_GAMENAME_LEN ) {
if ( strlen(Cmdline_game_name) > MAX_GAMENAME_LEN ) {
Cmdline_game_name[MAX_GAMENAME_LEN-1] = '\0';
}
}
@@ -1611,9 +1629,8 @@ bool SetCmdlineParams()
Cmdline_game_password = gamepassword_arg.str();
// be sure that this string fits in our limits
if ( strlen(Cmdline_game_password) >= MAX_PASSWD_LEN ) {
ReleaseWarning(LOCATION, "Multi game password is longer than max of %d charaters and will be trimmed to fit!", MAX_PASSWD_LEN-1);
Cmdline_game_password[MAX_PASSWD_LEN-1] = '\0';
if ( strlen(Cmdline_game_name) > MAX_PASSWD_LEN ) {
Cmdline_game_name[MAX_PASSWD_LEN-1] = '\0';
}
}
@@ -1698,7 +1715,7 @@ bool SetCmdlineParams()
sprintf(override, "{\"width\":%d,\"height\":%d}", width, height);
options::OptionsManager::instance()->setOverride("Graphics.Resolution", override);
} else {
mprintf(("Failed to parse -res parameter \"%s\". Must be in format \"<width>x<height>\".\n", Cmdline_res));
mprintf(("Failed to parse -res parameter \"%s\". Must be in format \"<width>x<height>\".", Cmdline_res));
}
}
if(center_res_arg.found()){
@@ -1799,16 +1816,6 @@ bool SetCmdlineParams()
}
}
if ( fov_cockpit_arg.found() ) {
auto val = fov_cockpit_arg.get_float();
if (val > 0.1) {
COCKPIT_ZOOM_DEFAULT = val;
}
else {
COCKPIT_ZOOM_DEFAULT = VIEWER_ZOOM_DEFAULT;
}
}
if( clip_dist_arg.found() ) {
Min_draw_distance = Cmdline_clip_dist = clip_dist_arg.get_float();
}
@@ -1834,10 +1841,6 @@ bool SetCmdlineParams()
Cmdline_no_screenshake = 1;
}
if ( deadzone.found() ) {
Cmdline_deadzone = deadzone.get_int();
}
if ( stretch_menu.found() ) {
Cmdline_stretch_menu = 1;
}
@@ -1937,9 +1940,6 @@ bool SetCmdlineParams()
Cmdline_output_sexp_info = true;
}
if (generate_controlconfig_arg.found())
Generate_controlconfig_table = true;
if ( no_pbo_arg.found() )
{
Cmdline_no_pbo = 1;
@@ -2157,20 +2157,69 @@ bool SetCmdlineParams()
Cmdline_log_to_stdout = true;
}
if (slow_frames_ok_arg.found()) {
Cmdline_slow_frames_ok = true;
}
if (show_video_info.found())
{
Cmdline_show_video_info = true;
}
//Deprecated flags - CommanderDJ
if( deprecated_spec_arg.found() )
{
Cmdline_deprecated_spec = 1;
}
if( deprecated_glow_arg.found() )
{
Cmdline_deprecated_glow = 1;
}
if( deprecated_normal_arg.found() )
{
Cmdline_deprecated_normal = 1;
}
if( deprecated_env_arg.found() )
{
Cmdline_deprecated_env = 1;
}
if( deprecated_tbp_arg.found() )
{
Cmdline_deprecated_tbp = 1;
}
if( deprecated_jpgtga_arg.found() )
{
Cmdline_deprecated_jpgtga = 1;
}
if ( deprecated_htl_arg.found() )
{
Cmdline_deprecated_nohtl = 1;
}
if ( deprecated_brieflighting_arg.found() )
{
Cmdline_deprecated_brief_lighting = 1;
}
if (deprecated_missile_lighting_arg.found())
{
Cmdline_deprecated_missile_lighting = true;
}
if (deprecated_cache_bitmaps_arg.found()) {
Cmdline_deprecated_cache_bitmaps = true;
}
if (deprecated_no_emissive_arg.found()) {
Cmdline_emissive = 0;
}
if (deprecated_postprocess_arg.found()) {
Cmdline_deprecated_postprocess = true;
}
if (deprecated_fxaa_arg.found() ) {
Gr_aa_mode = AntiAliasMode::FXAA_Medium;
@@ -2209,27 +2258,7 @@ bool SetCmdlineParams()
}
}
}
if (prefer_ipv4_arg.found()) {
Cmdline_prefer_ipv4 = true;
}
if (prefer_ipv6_arg.found()) {
Cmdline_prefer_ipv6 = true;
// Rule 2: Not both
if (Cmdline_prefer_ipv4) {
ReleaseWarning(LOCATION, "Cannot set preference for both IPv4 and IPv6! Reverting to default behavior...\n");
Cmdline_prefer_ipv4 = false;
Cmdline_prefer_ipv6 = false;
}
}
if (log_multi_packet_arg.found()) {
Cmdline_dump_packet_type = true;
}
return true;
}
+1 -5
View File
@@ -83,13 +83,13 @@ extern int Cmdline_rearm_timer;
extern int Cmdline_targetinfo;
// Gameplay related
extern int Cmdline_3dwarp;
extern int Cmdline_ship_choice_3d;
extern int Cmdline_weapon_choice_3d;
extern int Cmdline_warp_flash;
extern int Cmdline_autopilot_interruptable;
extern int Cmdline_stretch_menu;
extern int Cmdline_no_screenshake;
extern int Cmdline_deadzone;
// Audio related
extern int Cmdline_voice_recognition;
@@ -123,9 +123,6 @@ extern bool Cmdline_no_geo_sdr_effects;
extern bool Cmdline_set_cpu_affinity;
extern bool Cmdline_nograb;
extern bool Cmdline_noshadercache;
extern bool Cmdline_prefer_ipv4;
extern bool Cmdline_prefer_ipv6;
extern bool Cmdline_dump_packet_type;
#ifdef WIN32
extern bool Cmdline_alternate_registry_path;
#endif
@@ -154,7 +151,6 @@ extern bool Cmdline_show_video_info;
extern bool Cmdline_debug_window;
extern bool Cmdline_graphics_debug_output;
extern bool Cmdline_log_to_stdout;
extern bool Cmdline_slow_frames_ok;
enum class WeaponSpewType { NONE = 0, STANDARD, ALL };
extern WeaponSpewType Cmdline_spew_weapon_stats;
File diff suppressed because it is too large. Load diff
+99 -470
View File
@@ -12,67 +12,71 @@
#define CONTROLS_CONFIG_H
#include "globalincs/pstypes.h"
#include "scripting/scripting.h"
#define CONTROL_CONFIG_XSTR 507
#define CCF_AXIS_BTN 0x40 //!< axis is a button on positive side. If also inverted, is on negative side instead
#define CCF_RELATIVE 0x20 //!< axis is relative
#define CCF_INVERTED 0x10 //!< axis is inverted
#define CCF_AXIS 0x08 //!< btn is an axis
#define CCF_HAT 0x04 //!< btn is a hat
#define CCF_BALL 0x02 //!< btn is a ball
#define CCF_BUTTON 0x01 //!< btn is actually a button
/*!
* These are used to index a corresponding axis value from an array.
* These are used to index a corresponding joystick axis value from an array.
* Currently only used by ::Axis_map_to[] and ::Axis_map_to_defaults[]
*/
enum Joy_axis_index : short {
enum Joy_axis_index {
JOY_X_AXIS =0,
JOY_Y_AXIS,
JOY_Z_AXIS,
JOY_RX_AXIS,
JOY_RY_AXIS,
JOY_RZ_AXIS,
JOY_NUM_AXES // Number of axes a joystick may have. Must be last enum in Joy_axis_index.
JOY_RZ_AXIS
};
// Aliases for mouse axes. Really should unify this...
const short MOUSE_X_AXIS = JOY_X_AXIS;
const short MOUSE_Y_AXIS = JOY_Y_AXIS;
const short MOUSE_Z_AXIS = JOY_Z_AXIS;
const short MOUSE_NUM_AXES = 3;
const short MOUSE_NUM_BUTTONS = 9; // Keep this up to date with mouse.h until z64555 stops being lazy and does it right
/*!
* Controller index enumeration
* @details For use with hardcoded bindings and scripting API to allow human-readable translation
* @note These enums are hardcoded so that an int value of 0 would be JOY0
*/
enum CID : int8_t {
CID_NONE = -3, //!< No device bound
CID_KEYBOARD = -2, //!< belongs to keyboard
CID_MOUSE = -1, //!< to mouse
CID_JOY0 = 0, //!< to Joy0
CID_JOY1 = 1, //!< to Joy1 (Throttle?)
CID_JOY2 = 2, //!< to Joy2 (Pedals?)
CID_JOY3 = 3, //!< to Joy3 (Head tracker?)
CID_JOY_MAX //!< Maximum supported joysticks, must be last in the enum definition
enum CID : short {
CID_NONE = -3, // No device bound
CID_KEYBOARD = -2, // button belongs to keyboard
CID_MOUSE = -1, // to mouse
CID_JOY0 = 0, // to Joy0
CID_JOY1 = 1, // to Joy1 (Throttle?)
CID_JOY2 = 2, // to Joy2 (Pedals?)
CID_JOY3 = 3 // to Joy3 (Head tracker?)
// Any additional joystick is an int >3
};
/*!
* These are used to index a corresponding (analog) action, namely controlling the orientation angles and throttle.
*/
enum Joy_axis_action_index {
JOY_HEADING_AXIS =0, //
JOY_PITCH_AXIS,
JOY_BANK_AXIS,
JOY_ABS_THROTTLE_AXIS,
JOY_REL_THROTTLE_AXIS,
/*!
* This must always be below the last defined item
*/
NUM_JOY_AXIS_ACTIONS //!< The total number of actions an axis may map to
};
/*!
* Joystick action modes
* note: should this be in joy.h?
*/
enum Joy_axis_action_mode {
JAAM_ABS, //!< Absolute mode. Axis position = output value
JAAM_REL, //!< Relative mode. Axis postion away from its center adds or subtracts an output value register.
JAAM_BTN_NEG, //!< Button mode, negative side. Axis position in the negative side will trigger a button action
JAAM_BTN_POS, //!< Button mode, positive side. Axis position in the positive side will trigger a button action
};
/*!
* Control Configuration Types. Namely differ in how the control is activated
*/
enum CC_type {
CC_TYPE_TRIGGER = 0, //!< A normal, one-shot type control that is activated when a key is or button is pressed
CC_TYPE_CONTINUOUS, //!< A continous control that is activated as long as the key or button is held down
CC_TYPE_AXIS_ABS, //!< Absolute Axis mode. Axis position = output value
CC_TYPE_AXIS_REL, //!< Relative Axis mode. Axis postion away from its center adds or subtracts an output value register
CC_TYPE_AXIS_BTN_NEG, //!< Axis Button mode, negative side. Axis position in the negative side will trigger a button action
CC_TYPE_AXIS_BTN_POS //!< Axis Button mode, positive side. Axis position in the positive side will trigger a button action
CC_TYPE_TRIGGER =0, //!< A normal, one-shot type control that is activated when a key is or button is pressed
CC_TYPE_CONTINUOUS //!< A continous control that is activated as long as the key or button is held down
};
/*!
@@ -82,7 +86,7 @@ enum CC_type {
*
* Note: Do not adjust the order or numeric value
*/
enum IoActionId : int {
enum IoActionId {
TARGET_NEXT =0, //!< target next
TARGET_PREV =1, //!< target previous
TARGET_NEXT_CLOSEST_HOSTILE =2, //!< target the next hostile target
@@ -284,86 +288,52 @@ enum IoActionId : int {
CUSTOM_CONTROL_4 = 122,
CUSTOM_CONTROL_5 = 123,
//!< @n
//!< Analog controls
//!<-----------------------------
//!< All axis actions must be kept together
JOY_HEADING_AXIS = 124,
JOY_PITCH_AXIS = 125,
JOY_BANK_AXIS = 126,
JOY_ABS_THROTTLE_AXIS = 127,
JOY_REL_THROTTLE_AXIS = 128,
/*!
* This must always be below the last defined item
*/
CCFG_MAX //!< The total number of defined control actions (or last define + 1)
};
// Constants for array-based checking (within Control_config) for the axis actions.
// z64: Yes, this is hackish. No I don't really like it.
const int JOY_AXIS_BEGIN = JOY_HEADING_AXIS;
const int JOY_AXIS_END = JOY_REL_THROTTLE_AXIS + 1;
const int NUM_JOY_AXIS_ACTIONS = JOY_AXIS_END - JOY_AXIS_BEGIN;
/*!
* @brief Enum for indexing into axis action arrays.
* @note Keep this up to date with JOY_HEADING_AXIS and friends. Pay attention to their order and copy to here
*/
namespace Action {
enum {
HEADING = 0,
PITCH,
BANK,
ABS_THROTTLE,
REL_THROTTLE,
NUM_VALUES
};
}
/*!
* Where the preset is located in memory
*/
enum class Preset_t {
hardcode, // Preset is defined hardcode
tbl, // Preset is defined in controlconfigdefaults.tbl
pst // Preset is defined in a .json
};
class CCB;
/*!
* A singular button binding
*/
class CC_bind {
public:
CID cid = CID_NONE; //!< Which controller this belongs to
short btn = -1; //!< Which button to index; If cid == CID_KEYBOARD, this is a key hash.
public:
CC_bind() = default;
CC_bind(CID _cid, short _btn) : cid(_cid), btn(_btn) { validate(); };
CC_bind(CID _cid, short _btn, char _flags) : cid(_cid), flags(_flags), btn(_btn) { validate(); };
CC_bind(const CC_bind &A) = default;
CC_bind& operator=(const CC_bind &A) = default;
CC_bind(CID _cid, short _btn) : cid(_cid), btn(_btn) {};
CC_bind& operator=(const CC_bind &A);
/*!
* Checks if this CC_bind is equal to the given CC_bind
*
* @note EQUAL means EQUAL, this will return FALSE if the flags differ by a single bit
*/
bool operator==(const CC_bind &B) const;
/*!
* Checks if this CC_bind is equal to either in the pair
*/
bool operator==(const CCB &pair) const;
/*!
* Checks if this CC_bind is not equal to the given CC_bind
*/
bool operator!=(const CC_bind &B) const;
/*!
* Checks if this CC_bind is equal to the given CC_bind, disregarding the inversion flag
* Checks if this CC_bind is not equal to either in the pair
*/
bool invert_agnostic_equals(const CC_bind &B) const;
bool operator!=(const CCB &pair) const;
/*!
* Clears the binding and flags. Some flags are retained.
* Clears the binding
*/
void clear();
@@ -372,62 +342,10 @@ public:
*/
bool empty() const;
/**
* Takes the given binding.
*/
void take(CID _cid, short _btn, char _flags = 0);
/**
* Validates the binding, clearing it if necassary
*/
void validate();
/*!
* Returns a copy of ::btn
*/
short get_btn() const;
/*!
* Returns a copy of ::cid
*/
CID get_cid() const;
/*!
* Returns a copy of ::flags
*/
char get_flags() const;
/*!
* Returns a human-readable string of this binding
*/
SCP_string textify() const;
/*!
* Sets the inversion state of this binding according to the passed bool. True = inverted, false = normal
*/
void invert(bool);
/*!
* Toggles the inversion state
*/
void invert_toggle();
/*!
* Is true if inverted, false otherwise
*/
bool is_inverted() const;
/*!
* Checks if this CC_bind conflicts with the given argument
*
* @note Similar to operator==, but ignores certain combinations of flags.
*/
bool conflicts_with(const CC_bind&) const;
private:
CID cid = CID_NONE; //!< Which controller this belongs to
char flags = 0; //!< mask to determine various additional attributes of btn
short btn = -1; //!< The button, key combo, or axis that's bound.
};
/*!
@@ -474,57 +392,17 @@ public:
/*!
* Assigns the contents of the given CCB to this CCB
*/
CCB& operator=(const CCB&) = default;
CCB& operator=(const CCB&);
/*!
* Checks if the given CCB is exactly equal to this
* Returns True if this CCB's first isn't empty and the given CCB has a binding equal to it
*/
bool operator==(const CCB&) const;
bool has_first(const CCB&) const;
/*!
* Checks if the given CCB differs from this
* Returns True if this CCB's second isn't empty and the given CCB has a binding equal to it
*/
bool operator!=(const CCB&) const;
/*!
* Returns True if this CCB's first isn't empty and the given CCB has a binding that can conflict with it
*/
bool has_first_conflict(const CCB&) const;
/*!
* Returns True if this CCB's second isn't empty and the given CCB has a binding that can conflict with it
*/
bool has_second_conflict(const CCB&) const;
/*!
* Returns a pointer to first, or second, whichever has a binding equal to the given CC_bind, or nullptr if neither
*/
CC_bind* find(const CC_bind&);
/*!
* Returns a pointer to first, or second, whichever has a CID equal to the given CID, or nullptr if neither
*/
CC_bind* find(CID);
/*!
* Returns a pointer to first, or second, whichever has *all* flags in the given mask
*/
CC_bind* find_flags(const char);
/*!
* Sets the inversion state of both bindings
*/
void invert(bool);
/*!
* Toggles the inversion state of Primary and copies it to Secondary
*/
void invert_toggle();
/*!
* Is true if both bindings are inverted, false otherwise
*/
bool is_inverted() const;
bool has_second(const CCB&) const;
};
/*!
@@ -534,17 +412,6 @@ class CC_preset {
public:
SCP_vector<CCB> bindings;
SCP_string name;
Preset_t type;
public:
CC_preset() = default;
CC_preset(const CC_preset& A) = default;
CC_preset& operator=(const CC_preset&);
/*!
* Checks if the given preset is a duplicate of this one
*/
bool is_duplicate_of(CC_preset&);
};
/*!
@@ -565,10 +432,8 @@ public:
CC_type type; //!< manner control should be checked in
// Items used during gameplay
int used; //!< has control been used yet in mission? If so, this is the timestamp. For axes, this denotes the last frame's value
int used; //!< has control been used yet in mission? If so, this is the timestamp
bool disabled = true; //!< whether this action should be available at all
bool locked = false; //!< whether this action will be triggered by the respectively bound key
bool scriptEnabledByDefault = false; //!< whether this binding will execute it's registered hooks if triggered. Resets each mission
bool continuous_ongoing; //!< whether this action is a continuous one and is currently ongoing
public:
@@ -584,11 +449,6 @@ public:
* @brief Takes the bindings of the given CCB, but leaves all other members alone
*/
CCI& operator=(const CCB&);
/*!
* Returns true if this item is analogue or an axis
*/
bool is_axis();
};
/*!
@@ -613,23 +473,9 @@ public:
/*!
* Assigns the hardcoded binding to the given action
*
* @note This differs from the original hardcode from :V: in the hopes of it being more intuitive to future IoAction additions
*
* param[in] action_id The IoActionId enum associated with the control
* param[in] primary The btn, key combo, or axis for the default primary binding
* param[in] secondary The btn, key combo, or axis for the default secondary binding
* param[in] tab Category tab this control should show up under on the config menu
* param[in] indexXSTR ID for the XSTR localization utility. 0 for no localization, 1 for automatic lookup, all other values are index for item in strings.tbl
* param[in] text Label text displayed in the config menu.
* param[in] type How the control should be treated. see doxy on CC_type for details
* param[in] disabled If true, this control will not be available in the game. Controlsconfigdefaults.tbl may override this. Don't use this to temporarily disable controls in mission.
*
* @details The CID for the default primary and secondary is determined by the type parameter.
* If the type is CC_TYPE_TRIGGER or CC_TYPE_CONTINOUS, the primary is assumed to be a key combo and secondary is assumed to be a joy button.
* If the type is CC_TYPE_AXIS_ABS, CC_TYPE_AXIS_REL, CC_TYPE_AXIS_BTN_POS, or CC_TYPE_AXIS_BTN_NEG, the primary is a Joy0 axis and the secondary is a Mouse axis
*/
CCI_builder& operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false);
CCI_builder& operator()(IoActionId action_id, short key_default, short joy_default, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false);
private:
CCI_builder(); // Only one builder per Control Config, so a default constructor is useless
@@ -639,6 +485,11 @@ private:
extern int Failed_key_index;
extern int Axis_map_to[]; // Array to map an axis action to a joy axis. size() = NUM_JOY_AXIS_ACTIONS
extern int Axis_map_to_defaults[];
extern int Invert_axis[]; // Array to hold inversion bools for a joy axis. size() = JOY_NUM_AXES
extern int Invert_axis_defaults[];
extern int Joy_dead_zone_size;
extern int Joy_sensitivity;
@@ -646,16 +497,8 @@ extern int Control_config_overlay_id;
extern SCP_vector<CCI> Control_config; //!< Stores the keyboard configuration
extern SCP_vector<CC_preset> Control_config_presets; // tabled control presets; pointers to config_item arrays
extern const char **Joy_button_text; // String table of button labels. XSTR'd on init.
extern bool Generate_controlconfig_table;
// string table constants for labels and stuff.
#define NUM_AXIS_TEXT JOY_NUM_AXES
#define NUM_MOUSE_TEXT MOUSE_NUM_BUTTONS
extern char *Axis_text[NUM_AXIS_TEXT]; // String table of axis labels (joystick and mice). XSTR'd on init.
extern char *Mouse_button_text[NUM_MOUSE_TEXT]; // String table of mouse button labels. XSTR'd on init.
extern const char **Scan_code_text;
extern const char **Joy_button_text;
/*!
* @brief Checks if either binding in the CCB has the given cid
@@ -666,11 +509,6 @@ extern char *Mouse_button_text[NUM_MOUSE_TEXT]; // String table of mouse button
*/
int is_cid_either(CID, const CCB);
/*!
* Allows CID to be assigned by a short. Has Assert to check values
*/
void cid_assign(CID&, const short);
/*!
* @brief initialize common control config stuff - call at game startup after localization has been initialized
*/
@@ -702,87 +540,49 @@ void control_config_close();
void control_config_cancel_exit();
/*!
* @brief Copies all bindings within preset into Control_config
* @brief Resets all controls to values within the currently selected preset
*/
void control_config_use_preset(CC_preset &preset);
void control_config_reset_defaults();
/*!
* @brief Search for a given preset by name and use it, if it exists.
*
* @returns TRUE if the preset was found and selected
* @returns FALSE if the preset was not found, and the defaults were selected
*
* @details If the preset does not exist, then defaults are used instead.
* @see control_config_use_preset()
*/
bool control_config_use_preset_by_name(const SCP_string &name);
/**
* @brief Gets the currently used preset
*
* @param[in] invert_agnostic If true, check the bindings in Control_config against the preset, ignoring inversion flags
*
* @returns an iterator to the current preset, or
* @returns ::iterator Control_config_presets.end() if current bindings are not in a preset
*
* @note Similar to preset_find_duplicate, this function has additional logic in its search to ignore disabled controls
*/
SCP_vector<CC_preset>::iterator control_config_get_current_preset(bool invert_agnostic = false);
/*!
* Returns the IoActionId (index within Control_config[]) of a control bound to the given key
*
* @param[in] key The key combo to look for
* @param[in] find_override If true, return the IoActionId of a control that has this key as its default
*
* @return -1 if the given key is NULL or not bound.
* Returns the IoActionId of a control bound to the given key
*
* @param[in] key The key combo to look for
* @param[in] find_override If true, return the IoActionId of a control that has this key as its default
* @details If find_override is set to true, then this returns the index of the action
*/
int translate_key_to_index(const char *key, bool find_override=true);
/*!
* @brief Given the system default key 'key', return the current control input(s) that is bound to that function.
* @brief Given the system default key 'key', return the current key that is bound to that function.
*
* @param[in] key The default key combo (as cstring) to a certain control
* @param[in] key The default key combo (as string) to a certain control
*
* @return The key combo (as cstring) currently bound to the control, or
* @return nullptr if the given key is not a system default key, or
* @return "None" if there is nothing bound to the control
* @returns The key combo (as string) currently bound to the control
*
* @details Both the 'key' and the return value are descriptive strings that can be displayed
* directly to the user. If 'key' isn't a real key, is not normally bound to anything,
* or there is no key currently bound to the function, nullptr is returned.
*
* @note Not thread safe. Has an internal buffer for the return value which is overwritten on each call.
*
* @note Uses CC_bind::textify for translations
* or there is no key currently bound to the function, NULL is returned.
*/
const char *translate_key(char *key);
char *translate_key(char *key);
/**
* @brief Converts the specified key code to a human readable string, according to the selected locale and keyboard layout
* @brief Converts the specified key code to a human readable string
*
* @param[in] code The key scancode to convert
* @note The returned value is localized to the current language
*
* @return The text representation of the code.
*
* @note Not thread safe. Has an internal buffer for the return value which is overwritten on each call.
*
* @note The return value is translated according to localization settings
* @param code The key code to convert
* @return The text representation of the code. The returned value is stored in a temporary location, copy it to your
* own buffer if you want to continue using it.
*/
const char *textify_scancode(int code);
/**
* @brief Converts the specified key code to a human readable string. Uses the English locale and QWERTY layout.
*
* @param[in] code The key scancode to convert
*
* @return The text representation of the code
*
* @note Not thread safe. Has an internal buffer for the return value which is overwritten on each call.
* @note Same as textify_scancode but always returns the same value regardless of current language
* @param code The key code to convert
* @return The name of the key
* @see textify_scancode
*/
const char *textify_scancode_universal(int code);
@@ -800,29 +600,26 @@ float check_control_timef(int id);
/**
* @brief Wrapper for check_control_used. Allows the game to ignore the key if told to do so by the ignore-key SEXP.
*
* @param[in] id The IoActionId of the control to check
* @param[in] key The key combo to check against the control. If -1, re-check the last key passed to this function
* @brief id The IoActionId of the control to check
* @brief key The key combo to check against the control.
*
* @returns 0 If the control wasn't used, or
* @returns 1 If the control was used
*
* @details If the given control is CC_CONTINOUS, param key is effectively ignored and instead keyd_press[] is checked.
* This function also checks against joy and mouse presses.
*/
int check_control(int id, int key = -1);
/**
* @brief Gets the scaled reading for all control axes.
*
* @param[in] frametime Frametime used to scale the mouse axes
* @param[out] axis_v Output array of all control axes, must have size of JOY_NUM_AXIS_ACTIONS
* @param[out] h heading
* @param[out] p pitch
* @param[out] b bank
* @param[out] ta Throttle - Absolute
* @param[out] tr Throttle - Relative
*
* @details
* * Clamps output to stay within +/-JOY_AXIS_RANGE.
* * Should multiple axes be bound to a control, their values are blended with a simple sum. This is to save a bit of
* processing for speed.
* @note None of the input pointers may be nullptr
*/
void control_get_axes_readings(int *axis_v, float frame_time);
void control_get_axes_readings(int *h, int *p, int *b, int *ta, int *tr);
/**
* @brief Markes the given control (by IoActionId) as used
@@ -831,35 +628,6 @@ void control_get_axes_readings(int *axis_v, float frame_time);
*/
void control_used(int id);
/**
* @brief Runs a given controlbindings lua hook added in ccd.tbl
*
* @return Whether the control should be overridden
*/
bool control_run_lua(IoActionId id, int value);
/**
* @brief Resets the cache for script evaluations for continuous buttons. Should be called once per frame.
*
*/
void control_reset_lua_cache();
/**
* @brief Registers a new hook for the keybinding action system.
* Parameters set if it is a normal or an override function, as well as if it is enabled by default or needs to be enabled manually each mission
*/
void control_register_hook(IoActionId id, const luacpp::LuaFunction& hook, bool is_override, bool enabledByDefault);
/**
* @brief Enables or disables the respective Lua hook
*/
void control_enable_hook(IoActionId id, bool enable);
/**
* @brief Resets every hooks enable state to the tabled value. Call on Mission start
*/
void control_reset_hook();
/**
* @brief Clears the bindings of all controls
*/
@@ -884,143 +652,4 @@ void control_config_clear_used_status();
*/
int joy_get_scaled_reading(int raw);
/**
* Lookups the given stringified enum to find its value
* @return -1 if not found, or
* @return IoActionId if found
*/
int ActionToVal(const char * str);
/**
* Reverse lookups the IoActionId to get its stringified name
* @return Pointer to the stringified name of the action, or
* @return nullptr if not found
*/
const char * ValToAction(IoActionId id);
/**
* Reverse lookups the IoActionId to get its stringified name
* @return Pointer to the stringified name of the action, or
* @return nullptr if not found, or invalid id
*/
const char * ValToAction(int id);
/**
* Lookups the given stringified enum to find its value
*/
CID CIDToVal(const char * str);
/**
* Reverse lookups the CID to get its stringified name
* @return The stringified name of the CID, or
* @return "NONE" if not found
*/
SCP_string ValToCID(CID id);
/**
* Reverse lookups the CID to get its stringified name
* @return The stringified name of the CID, or
* @return "NONE" if not found, or invalid id
*/
SCP_string ValToCID(int id);
/**
* Lookups the given stringified enum to find its value
*/
char CCFToVal(const char * str);
/**
* Constructs a enum string from the CCF_FLAGS
* @return The stringified name of the CCF, or
* @return "NONE" if not found, or invalid id
*/
SCP_string ValToCCF(char id);
/**
* Lookups the given stringified enum to find its value
*/
short InputToVal(CID cid, const char * str);
/**
* Constructs a enumstring from the input binding, depending on the CID
*
* @return The stringified name of the input, or
* @return "NONE" if not found, or invalid CC_bind
*
* @note This requires a CCB due to the way things are encoded
*/
SCP_string ValToInput(const CC_bind &bind);
/**
* Lookups the given stringified enum to find its btn value
*/
short MouseToVal(const char * str);
/**
* Constructs a enum string from the mouse input
*
* @return The stringified name of the input, or
* @return "NONE" if not found, or invalid CC_bind
* TODO XSTR
*
* @note This requires a CCB due to the way things are encoded
*/
SCP_string ValToMouse(const CC_bind &bind);
/**
* Lookups the given stringified enum to find its btn value
*/
short KeyboardToVal(const char * str);
/**
* Constructs an enum string from the key binding
*
* @return The stringified name of the input, or
* @return "NONE" f not found, or invalid CC_bind
*
* @note This requires a CCB due to the way things are encoded
* TODO XSTR
*/
SCP_string ValToKeyboard(const CC_bind &bind);
/**
* Lookups the given stringified enum to find its value
*/
short JoyToVal(const char * str);
/**
* Constructs a enum string from the Joystick input
*
* @return The stringified name of the input, or
* @return "NONE" if not found, or invalid CC_bind
*
* @note This requires a CCB due to the way things are encoded
* TODO XSTR
*/
SCP_string ValToJoy(const CC_bind &bind);
/**
* Lookups the given string to find its tab value
*/
char CCTabToVal(const char *str);
/**
* Reverse lookups the given tab in mCCTabToVal to retrieve the string name
*
* @return The stringified name of the input, or
* @return nullptr if not found, or invalid tab
*
*/
SCP_string ValToCCTab(char tab);
/**
* Lookups the given string to find its CC_type value. =
*/
CC_type CCTypeToVal(const char *str);
/**
* Reverse lookups the given CC_type to retrieve the string name
*/
SCP_string ValToCCType(CC_type type);
#endif
File diff suppressed because it is too large. Load diff
-499
View File
@@ -1,499 +0,0 @@
#include "controlconfig/controlsconfig.h"
#include "controlconfig/presets.h"
#include "libs/jansson.h"
#include "parse/parselo.h"
#include <jansson.h>
using namespace io::presets;
const SCP_vector<std::pair<Section, const char*>> SectionMapping {
std::pair<Section, const char*>(Section::Unnamed, nullptr),
std::pair<Section, const char*>(Section::Actions, "actions"),
std::pair<Section, const char*>(Section::Primary, "primary"),
std::pair<Section, const char*>(Section::Secondary, "secondary"),
};
const char * lookupSectionName(Section id) {
for (const auto& pair: SectionMapping) {
if (pair.first == id) {
return pair.second;
}
}
return nullptr;
}
Section lookupSectionValue(const char* name) {
Assertion(name != nullptr, "Key name must be a valid pointer!");
for (auto& pair : SectionMapping) {
if (!strcmp(pair.second, name)) {
return pair.first;
}
}
return Section::Invalid;
}
void load_preset_files() {
SCP_vector<SCP_string> filelist;
cf_get_file_list(filelist, CF_TYPE_PLAYER_BINDS, NOX("*.json"), CF_SORT_NAME, nullptr,
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
std::unique_ptr<PresetFileHandler> handler = nullptr;
for (const auto &file : filelist) {
CFILE* fp = cfopen((file + ".json").c_str(), "r", CFILE_NORMAL, CF_TYPE_PLAYER_BINDS, false,
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
if (!fp) {
mprintf(("PST => Unable to open '%s' for loading!\n", file.c_str()));
// try next
continue;
}
try {
handler.reset(new PresetFileHandler(fp, true));
} catch (const std::exception& e) {
mprintf(("PST => Failed to open JSON: `%s`\n", e.what()));
continue;
}
// Check the signature
int pst_id = handler->readInt("signature");
if (pst_id != PST_FILE_ID) {
mprintf(("PST => Invalid header id for `%s`, skipping...\n", file.c_str()));
continue;
}
// Version
int version = handler->readInt("version");
mprintf(("PST => Loading `%s` with version %i\n", file.c_str(), version));
// Start reading in data
CC_preset preset;
preset.name = file;
preset.type = Preset_t::pst;
preset.bindings.resize(Control_config.size());
size_t size;
handler->beginArrayRead(Section::Actions, size);
for (size_t i = 0; i < size; ++i, handler->nextArrayElement()) {
SCP_string str;
int id;
//handler->beginSectionRead(Section::Unnamed);
str = handler->readString("bind");
id = ActionToVal(str.c_str());
if (id < 0) {
// Unknown bind, possibly custom. Ignore for now
handler->endSectionRead(); // Unnamed
continue;
}
auto &item = preset.bindings[id];
handler->beginSectionRead(Section::Primary);
str = handler->readString("cid");
auto cid = CIDToVal(str.c_str());
str = handler->readString("flags");
auto flags = CCFToVal(str.c_str());
str = handler->readString("input");
auto btn = InputToVal(cid, str.c_str());
item.first.take(cid, btn, flags);
handler->endSectionRead(); // Primary
handler->beginSectionRead(Section::Secondary);
str = handler->readString("cid");
cid = CIDToVal(str.c_str());
str = handler->readString("flags");
flags = CCFToVal(str.c_str());
str = handler->readString("input");
btn = InputToVal(cid, str.c_str());
item.second.take(cid, btn, flags);
handler->endSectionRead(); // Secondary
//handler->endSectionRead(); // Unnamed
}
handler->endArrayRead(); // Actions
// Done with the file
auto it = preset_find_duplicate(preset);
if (it == Control_config_presets.end()) {
Control_config_presets.push_back(preset);
} else if ((it->name != preset.name) || (it->type != Preset_t::pst)) {
// Complain and ignore if the preset names or the type differs
Warning(LOCATION, "PST => Preset '%s' is a duplicate of an existing preset, ignoring", preset.name.c_str());
} // else, silent ignore
}
}
bool preset_file_exists(SCP_string name) {
if (name.find(".json") == std::string::npos) {
name.append(".json");
}
return cf_exists(name.c_str(), CF_TYPE_PLAYER_BINDS) != 0;
}
bool save_preset_file(CC_preset preset, bool overwrite) {
// Must have a name
if (preset.name.empty()) {
mprintf(("PST => Unable to save preset, missing name!\n"));
return false;
}
SCP_string filename = preset.name + ".json";
std::unique_ptr<PresetFileHandler> handler = nullptr;
// Check if there's a file already
CFILE* fp = cfopen(filename.c_str(), "r", CFILE_NORMAL, CF_TYPE_PLAYER_BINDS, false,
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
if ((fp != nullptr) && !overwrite) {
mprintf(("PST => Unable to save '%s', file already exists!\n", filename.c_str()));
return false;
}
// Try opening file for write
fp = cfopen(filename.c_str(), "w", CFILE_NORMAL, CF_TYPE_PLAYER_BINDS, false,
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
if (!fp) {
mprintf(("PST => Unable to save '%s', unknown error!\n", filename.c_str()));
}
try {
handler.reset(new PresetFileHandler(fp, false));
} catch (const std::exception& e) {
mprintf(("PST => Failed to open JSON: %s\n", e.what()));
return false;
}
// Write header and version
handler->writeInt("signature", PST_FILE_ID);
handler->writeInt("version", PST_VERSION);
mprintf(("PST => Saving %s with version %d...\n", filename.c_str(), (int)PST_VERSION));
handler->beginArrayWrite(Section::Actions);
for (int i = 0; static_cast<size_t>(i) < preset.bindings.size(); ++i) {
const auto& item = preset.bindings[i];
const auto& first = item.first;
const auto& second = item.second;
handler->beginSectionWrite(Section::Unnamed);
handler->writeString("bind", ValToAction(i));
handler->beginSectionWrite(Section::Primary);
handler->writeString("cid", ValToCID(first.get_cid()));
handler->writeString("flags", ValToCCF(first.get_flags()));
handler->writeString("input", ValToInput(first));
handler->endSectionWrite(); // Primary
handler->beginSectionWrite(Section::Secondary);
handler->writeString("cid", ValToCID(second.get_cid()));
handler->writeString("flags", ValToCCF(second.get_flags()));
handler->writeString("input", ValToInput(second));
handler->endSectionWrite(); // Secondary
handler->endSectionWrite(); // Control_config[i]
}
handler->endArrayWrite(); // Actions
handler->flush();
return true;
}
SCP_vector<CC_preset>::iterator preset_find_duplicate(const CC_preset& new_preset) {
SCP_vector<CC_preset>::iterator it = Control_config_presets.begin(); // NOLINT(modernize-use-auto)
for (; it != Control_config_presets.end(); ++it) {
size_t i;
for (i = 0; i < it->bindings.size(); ++i) {
if (new_preset.bindings[i] != it->bindings[i]) {
// These two differ, check the next in the vector
break;
}
}
if (i == it->bindings.size()) {
// These two are equal
break;
}
}
return it;
}
PresetFileHandler::PresetFileHandler(CFILE* cfp, bool reading) : _cfp(cfp) {
Assertion(cfp != nullptr, "File pointer must be valid!");
if (reading) {
json_error_t error;
_rootObj = json_load_cfile(cfp, 0, &error);
if (!_rootObj) {
SCP_string errorStr;
sprintf(errorStr, "Error while reading pilot file! %d: %s", error.line, error.text);
throw std::runtime_error(errorStr);
}
} else {
_rootObj = json_object();
}
_currentEl = _rootObj;
_elementStack.push_back(_currentEl);
};
PresetFileHandler::~PresetFileHandler() {
json_decref(_rootObj);
cfclose(_cfp);
_cfp = nullptr;
};
bool PresetFileHandler::beginSectionRead(Section s) {
const char * sectionName = lookupSectionName(s);
ensureExists(sectionName);
auto section = json_object_get(_currentEl, sectionName);
if (json_typeof(section) != JSON_OBJECT) {
Error(LOCATION, "Section must be a JSON object!");
return false;
}
pushElement(section);
return true;
};
void PresetFileHandler::beginSectionWrite(Section s) {
auto key_name = lookupSectionName(s);
json_t* obj = json_object();
if (json_is_array(_currentEl)) {
// Currently in an array, section must be unnamed
Assertion(key_name == nullptr, "Elements of array may not be named sections!");
json_array_append_new(_currentEl, obj);
} else {
Assertion(key_name != nullptr, "Section outside of arrays must be named!");
json_object_set_new(_currentEl, key_name, obj);
}
pushElement(obj);
};
bool PresetFileHandler::beginArrayRead(Section s, size_t &size) {
Assertion(_arrayIndex == INVALID_SIZE, "Array nesting is not supported yet!");
const char * name = lookupSectionName(s);
ensureExists(name);
auto array = json_object_get(_currentEl, name);
if (json_typeof(array) != JSON_ARRAY) {
Error(LOCATION, "Expected an array for '%s' but it was a different type!", name);
return false;
}
pushElement(array);
size = json_array_size(array);
if (size == 0) {
// Nothing to do here, avoid calling nextArraySection since that assumes that there is at least one element
return true;
} // Else, advance to the first element in the array
_arrayIndex = 0;
nextArrayElement();
return true;
}
void PresetFileHandler::beginArrayWrite(Section s) {
const char * name = lookupSectionName(s);
auto array = json_array();
if (json_is_array(_currentEl)) {
// We are in an array, section must be unnamed
Assertion(name == nullptr, "Inside an array there can be no named section!");
json_array_append_new(_currentEl, array);
} else {
Assertion(name != nullptr, "Section outside of arrays must be named!");
json_object_set_new(_currentEl, name, array);
}
pushElement(array);
}
void PresetFileHandler::endSectionRead() {
Assertion(json_typeof(_currentEl) == JSON_OBJECT, "Current element for section reading is not an object!");
popElement();
};
void PresetFileHandler::endSectionWrite() {
Assertion(json_is_object(_currentEl), "Section ended while not in a section!");
popElement();
};
void PresetFileHandler::endArrayRead() {
// First, remove the element we are currently in if it exists
if (json_typeof(_currentEl) != JSON_ARRAY) {
popElement();
}
Assertion(json_typeof(_currentEl) == JSON_ARRAY, "Current element must be an array!");
// TODO Sections are straightforward, but Arrays may need special consideration re: index.
popElement();
_arrayIndex = INVALID_SIZE;
};
void PresetFileHandler::endArrayWrite() {
Assertion(json_is_array(_currentEl), "Array ended while not in an array!");
popElement();
};
void PresetFileHandler::ensureExists(const char* name) {
if (json_typeof(_currentEl) != JSON_OBJECT) {
Error(LOCATION, "JSON reading requires a value with name '%s' but the current element is not an object!", name);
}
if (json_object_get(_currentEl, name) == nullptr) {
Error(LOCATION, "JSON reading requires a value with name '%s' but there is no such value!", name);
}
}
void PresetFileHandler::ensureNotExists(const char* name) {
// Stuff debug checks into booleans, otherwise clang will trigger a false positive for static method with just the asserts
// Make sure we're in an element that can support keys
bool supports_keys = json_is_object(_currentEl);
// Make sure we don't overwrite previous values
bool element_is_unique = json_object_get(_currentEl, name) == nullptr;
Assertion(supports_keys, "Currently not in an element that supports keys!");
Assertion(element_is_unique, "Entry with name %s already exists!", name);
}
bool PresetFileHandler::exists(const char* name) {
if (json_typeof(_currentEl) != JSON_OBJECT) {
return false;
}
if (json_object_get(_currentEl, name) == nullptr) {
return false;
}
return true;
}
void PresetFileHandler::flush() {
Assertion(_elementStack.size() == 1, "Not all sections or arrays have been ended!");
json_dump_cfile(_rootObj, _cfp, JSON_INDENT(4) | JSON_PRESERVE_ORDER);
}
void PresetFileHandler::nextArrayElement() {
Assertion(_arrayIndex != INVALID_SIZE, "Array index must be valid for this function!");
bool in_section = json_is_object(_currentEl);
if (in_section) {
// We have to pop the previous section first
popElement();
Assertion(json_typeof(_currentEl) == JSON_ARRAY, "The previous element should have been an array!");
}
auto max = json_array_size(_currentEl);
Assertion(_arrayIndex <= max, "Invalid array index detected!");
// Silently ignore if we are one past the last element since this function is used in a for loop where this function
// is executed one time after the index is incremented past the last element
if (_arrayIndex == max) {
// Increment the index so we catch usage errors the next time someone tries to call this function
++_arrayIndex;
return;
}
// We use the current array index and then increment it to avoid skipping the first element
auto el = json_array_get(_currentEl, _arrayIndex);
++_arrayIndex;
pushElement(el);
}
json_t * PresetFileHandler::popElement() {
Assertion(_elementStack.size() > 1, "Element stack may not get smaller than one element!");
_elementStack.pop_back();
_currentEl = _elementStack.back();
return _currentEl;
}
void PresetFileHandler::pushElement(json_t* el) {
Assertion(el != nullptr, "Invalid JSON element pointer passed!");
_currentEl = el;
_elementStack.push_back(_currentEl);
}
SCP_string PresetFileHandler::readString(const char* key) {
auto el = json_object_get(_currentEl, key);
if (el == nullptr || json_typeof(el) != JSON_STRING) {
Error(LOCATION, "JSON element %s must be a string but it is not valid!", key);
return SCP_string();
}
auto json_str = json_string_value(el);
SCP_string val;
val.assign(json_str, json_str + json_string_length(el));
return val;
}
int PresetFileHandler::readInt(const char* key) {
auto el = json_object_get(_currentEl, key);
if (el == nullptr || json_typeof(el) != JSON_INTEGER) {
Error(LOCATION, "JSON element %s must be an integer but it is not valid!", key);
return 0;
}
return static_cast<int>(json_integer_value(el));
}
void PresetFileHandler::writeString(const char* key, SCP_string s) {
ensureNotExists(key);
json_t *jstr = nullptr;
if (!s.empty()) {
// if this string isn't proper UTF-8, try to convert it
if (utf8::find_invalid(s.begin(), s.end()) != s.end()) {
SCP_string buffer;
coerce_to_utf8(buffer, s.c_str());
jstr = json_string(buffer.c_str());
} else {
jstr = json_string(s.c_str());
}
}
json_object_set_new(_currentEl, key, jstr);
};
void PresetFileHandler::writeInt(const char* key, int val) {
ensureNotExists(key);
json_object_set_new(_currentEl, key, json_integer(val));
}
-230
View File
@@ -1,230 +0,0 @@
#pragma once
#include "cfile/cfile.h"
#include "controlconfig/controlsconfig.h"
#include "globalincs/pstypes.h"
#include "globalincs/vmallocator.h"
#include <jansson.h>
/** API for saving and loading player custom bindings, known as Presets
Init. If the player/preset dir doesn't exist. Create it, and print a help.txt with all enums (as strings) to label
the button names and action names.
Example output:
{
"actions": {
"TARGET_NEXT": {
"first": {
"cid": 0,
"flags": 0,
"btn": 0
},
"second": {
"cid": 0,
"flags": 0,
"btn": 0
}
},
"TARGET_PREV" : {
"first": {
"cid": 0,
"flags": 0,
"btn": 0
},
"second": {
"cid": 0,
"flags": 0,
"btn": 0
}
},
},
"version": 0
}
*/
namespace io {
namespace presets {
static const unsigned int PST_VERSION = 0;
static const unsigned int PST_FILE_ID = 0x5f545350; // "PST_" in file
enum class Section {
Invalid = -1,
Unnamed = 0,
// arrays
Actions,
// subsections
Primary,
Secondary
};
class PresetFileHandler {
public:
PresetFileHandler(CFILE* cfp, bool reading);
~PresetFileHandler();
/**
* begins writing to a section using the presets::Section as its name
* @param[in] s id of the section
*/
void beginSectionWrite(presets::Section s);
/**
* begins reading a section, if it exists
* @param[in] s id of the section
*/
bool beginSectionRead(presets::Section s);
/**
* Begins writing to an array using the presets::Section as its name
*
* @param[in] s id of the array
*/
void beginArrayWrite(presets::Section s);
/**
* Begins reading from an array
* @param[int] s id of the array
* @param[out] size the size of the array
*/
bool beginArrayRead(presets::Section s, size_t &size);
/**
* ends writing to a section
*/
void endSectionWrite();
/**
* ends reading a section
*/
void endSectionRead();
/**
* ends writing to an array.
*/
void endArrayWrite();
/**
* ends reading from an array
*/
void endArrayRead();
/**
* Writes a string with the given key name
*/
void writeString(const char* key, SCP_string s);
/**
* Reads a string with the given key name
*/
SCP_string readString(const char* key);
/**
* Writes an int with the given key name
*/
void writeInt(const char* key, int val);
/**
* Reads an int with the given key name
*/
int readInt(const char* key);
/**
* For writing, Flushes the buffers and dumps to file
*/
void flush();
/**
* Advances the array parser to the next element
*/
void nextArrayElement();
private:
CFILE* _cfp = nullptr;
json_t* _rootObj = nullptr;
json_t* _currentEl = nullptr;
/**
* BufferStack of all json elements being read/written
*/
SCP_vector<json_t*> _elementStack;
/**
* Stack to keep track of the heirachy/nesting. Back is immediate parent, Front is root
*/
SCP_vector<void*> _parentStack;
size_t _arrayIndex = INVALID_SIZE;
//void* _sectionIterator = nullptr; //!< Pointer to the current section/array we're in
private:
/**
* Pushes an element onto the elementStack
*/
void pushElement(json_t* el);
/**
* Pops an element off the elementStack
*/
json_t * popElement();
/**
* Used for optionals, checks if an element exists with the given name
* @return True if the element exists
* @return False otherwise
*/
bool exists(const char* name);
/**
* Throws an error if an element with the given name does not exist
*/
void ensureExists(const char* name);
/**
* Throws an error if an element with the given name exists
*/
void ensureNotExists(const char* name);
};
} // namespace presets
} // namespace io
/**
* @brief Saves the given preset to file.
* @param[in] preset The preset to save
* @param[in] overwrite If true, overwrite existing preset file which have the same name as the given preset
*
* @returns True if successful, or
* @returns False Preset does not have a name, or
* @returns False Preset file exists and overwrite == false, or
* @returns False Preset file .JSON could not be written
*/
bool save_preset_file(CC_preset preset, bool overwrite);
/**
* @brief Loads in all preset .json files from 'players/presets'
* @details If a preset file is unique, it is loaded into the game and available for use by the player. However, if
* a preset is a duplicate of another preset, it is ignored, and the player is warned of it
*/
void load_preset_files();
/**
* @brief Checks if the given preset is a duplicate within Control_config_presets vector
* @returns iterator to the duplicate if found, or
* @returns iterator to Control_config_presets.end() otherwise
*/
SCP_vector<CC_preset>::iterator preset_find_duplicate(const CC_preset& new_preset);
/**
* @brief Returns true if a preset file with the given name exists.
*/
bool preset_file_exists(SCP_string name);
+3 -2
View File
@@ -93,12 +93,13 @@ class Decoder {
bool m_decoding;
size_t m_queueSize = 0;
Decoder(const Decoder&) SCP_DELETED_FUNCTION;
Decoder& operator=(const Decoder&) SCP_DELETED_FUNCTION;
protected:
Decoder();
public:
Decoder(const Decoder&) = delete;
Decoder& operator=(const Decoder&) = delete;
virtual ~Decoder();
+1 -1
View File
@@ -613,7 +613,7 @@ void cutscenes_screen_do_frame()
if (src)
{
Text_size = split_str(src, Cutscene_desc_coords[gr_screen.res][2], Text_line_size, Text_lines,
Cutscene_max_text_lines[gr_screen.res], MAX_TEXT_LINE_LEN);
Cutscene_max_text_lines[gr_screen.res]);
Assert(Text_size >= 0 && Text_size < Cutscene_max_text_lines[gr_screen.res]);
}
}
+25 -35
View File
@@ -229,7 +229,7 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
auto ctx = stream->m_ctx->ctx();
auto videoStream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
auto videoStream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &status->videoCodec, 0);
if (videoStream < 0) {
if (videoStream == AVERROR_STREAM_NOT_FOUND) {
mprintf(("FFmpeg: No video stream found in file!\n"));
@@ -242,26 +242,10 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
return nullptr;
}
status->videoStreamIndex = videoStream;
status->videoStream = ctx->streams[videoStream];
status->videoCodecPars = getCodecParameters(status->videoStream);
status->videoCodec = avcodec_find_decoder(status->videoCodecPars.codec_id);
if ( !status->videoCodec ) {
mprintf(("FFmpeg: Cannot find decoder for video stream!\n"));
return nullptr;
}
if (properties.looping && status->videoCodecPars.codec_id == AV_CODEC_ID_INTERPLAY_VIDEO) {
mprintf(("FFmpeg: Looping is not supported for inteplay (MVE) movies!\n"));
return nullptr;
}
int audioStream = -1;
if (properties.with_audio) {
audioStream = av_find_best_stream(ctx, AVMEDIA_TYPE_AUDIO, -1, videoStream, nullptr, 0);
audioStream = av_find_best_stream(ctx, AVMEDIA_TYPE_AUDIO, -1, videoStream, &status->audioCodec, 0);
if (audioStream < 0) {
if (audioStream == AVERROR_STREAM_NOT_FOUND) {
mprintf(("FFmpeg: No audio stream found in file!\n"));
@@ -270,21 +254,17 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
} else {
mprintf(("FFmpeg: Unknown error while finding audio stream!\n"));
}
} else {
status->audioStreamIndex = audioStream;
status->audioStream = ctx->streams[audioStream];
status->audioCodecPars = getCodecParameters(status->audioStream);
status->audioCodec = avcodec_find_decoder(status->audioCodecPars.codec_id);
if ( !status->audioCodec ) {
mprintf(("FFmpeg: Cannot find decoder for audio stream!\n"));
status->audioStreamIndex = -1;
audioStream = -1;
}
}
}
status->videoStreamIndex = videoStream;
status->videoStream = ctx->streams[videoStream];
if (audioStream >= 0) {
status->audioStreamIndex = audioStream;
status->audioStream = ctx->streams[audioStream];
}
if (subt) {
auto subtCtx = subt->m_ctx->ctx();
@@ -298,13 +278,9 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
mprintf(("FFmpeg: Unknown error while finding subtitle stream!\n"));
}
} else {
status->subtitleStreamIndex = subtStream;
status->subtitleStream = subtCtx->streams[subtStream];
status->externalSubtitles = true;
// Not really sure if this makes sense for subtitles but it shouldn't break anything...
status->subtitleCodecPars = getCodecParameters(status->subtitleStream);
status->subtitleCodec = avcodec_find_decoder(status->subtitleCodecPars.codec_id);
status->subtitleStreamIndex = subtStream;
}
}
@@ -336,6 +312,13 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
}
}
status->videoCodecPars = getCodecParameters(status->videoStream);
if (properties.looping && status->videoCodecPars.codec_id == AV_CODEC_ID_INTERPLAY_VIDEO) {
mprintf(("FFmpeg: Looping is not supported for inteplay (MVE) movies!\n"));
return nullptr;
}
int err;
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
status->videoCodecCtx = avcodec_alloc_context3(status->videoCodec);
@@ -365,6 +348,8 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
// Now initialize audio, if this fails it's not a fatal error
if (audioStream >= 0) {
status->audioCodecPars = getCodecParameters(status->audioStream);
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
status->audioCodecCtx = avcodec_alloc_context3(status->audioCodec);
@@ -391,6 +376,11 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
}
if (status->subtitleStream != nullptr) {
// Not really sure if this makes sense for subtitles but it shouldn't break anything...
status->subtitleCodecPars = getCodecParameters(status->subtitleStream);
status->subtitleCodec = avcodec_find_decoder(status->subtitleCodecPars.codec_id);
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
status->subtitleCodecCtx = avcodec_alloc_context3(status->subtitleCodec);
+9 -7
View File
@@ -24,13 +24,13 @@ struct DecoderStatus {
int videoStreamIndex = -1;
AVStream* videoStream = nullptr;
CodecContextParameters videoCodecPars;
const AVCodec* videoCodec = nullptr;
AVCodec* videoCodec = nullptr;
AVCodecContext* videoCodecCtx = nullptr;
int audioStreamIndex = -1;
AVStream* audioStream = nullptr;
CodecContextParameters audioCodecPars;
const AVCodec* audioCodec = nullptr;
AVCodec* audioCodec = nullptr;
AVCodecContext* audioCodecCtx = nullptr;
// Subtitles are a bit different since they max come from a different file
@@ -38,15 +38,17 @@ struct DecoderStatus {
int subtitleStreamIndex = -1;
AVStream* subtitleStream = nullptr;
CodecContextParameters subtitleCodecPars;
const AVCodec* subtitleCodec = nullptr;
AVCodec* subtitleCodec = nullptr;
AVCodecContext* subtitleCodecCtx = nullptr;
DecoderStatus();
~DecoderStatus();
DecoderStatus(const DecoderStatus&) = delete;
DecoderStatus& operator=(const DecoderStatus&) = delete;
private:
DecoderStatus(const DecoderStatus&) SCP_DELETED_FUNCTION;
DecoderStatus& operator=(const DecoderStatus&) SCP_DELETED_FUNCTION;
};
template<typename Frame>
@@ -65,8 +67,8 @@ class FFMPEGStreamDecoder {
explicit FFMPEGStreamDecoder(DecoderStatus* status);
virtual ~FFMPEGStreamDecoder();
FFMPEGStreamDecoder(const FFMPEGStreamDecoder&) = delete;
FFMPEGStreamDecoder& operator=(const FFMPEGStreamDecoder&) = delete;
FFMPEGStreamDecoder(const FFMPEGStreamDecoder&) SCP_DELETED_FUNCTION;
FFMPEGStreamDecoder& operator=(const FFMPEGStreamDecoder&) SCP_DELETED_FUNCTION;
virtual void decodePacket(AVPacket* packet) = 0;
+4 -5
View File
@@ -51,7 +51,6 @@ void processEvents()
{
io::mouse::CursorManager::get()->showCursor(false);
os_remove_deferred_cutscene_key_events();
os_poll();
}
@@ -206,14 +205,14 @@ void movie_display_loop(Player* player, PlaybackState* state) {
auto fpsCapSave = Cmdline_NoFPSCap;
Cmdline_NoFPSCap = 1;
auto lastDisplayTime = timer_get_microseconds();
auto lastDisplayTimestamp = timer_get_microseconds();
while (state->playing) {
TRACE_SCOPE(tracing::CutsceneStep);
auto now = timer_get_microseconds();
auto timestamp = timer_get_microseconds();
auto passed = now - lastDisplayTime;
lastDisplayTime = now;
auto passed = timestamp - lastDisplayTimestamp;
lastDisplayTimestamp = timestamp;
if (player->isPlaybackReady()) {
// Play as long as the player reports that there is more to display
+89 -47
View File
@@ -30,9 +30,8 @@
#include "ship/ship.h"
#include "ship/shipfx.h"
#include "species_defs/species_defs.h"
#include "tracing/Monitor.h"
#include "utils/Random.h"
#include "weapon/weapon.h"
#include "tracing/Monitor.h"
#define MIN_RADIUS_FOR_PERSISTANT_DEBRIS 50 // ship radius at which debris from it becomes persistant
#define DEBRIS_SOUND_DELAY 2000 // time to start debris sound after created
@@ -52,6 +51,8 @@ int Debris_model = -1;
int Debris_vaporize_model = -1;
int Debris_num_submodels = 0;
#define MAX_DEBRIS_DIST 10000.0f // Debris goes away if it's this far away.
#define DEBRIS_DISTANCE_CHECK_TIME (10*1000) // Check every 10 seconds.
#define DEBRIS_INDEX(dp) (int)(dp-Debris.data())
const auto OnDebrisCreatedHook = scripting::Hook::Factory(
@@ -74,7 +75,7 @@ static void debris_start_death_roll(object *debris_obj, debris *debris_p)
int fireball_type = fireball_ship_explosion_type(&Ship_info[debris_p->ship_info_index]);
if(fireball_type < 0) {
fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
fireball_type = FIREBALL_EXPLOSION_LARGE1 + rand()%FIREBALL_NUM_LARGE_EXPLOSIONS;
}
fireball_create( &debris_obj->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(debris_obj), debris_obj->radius*1.75f);
@@ -195,6 +196,34 @@ void debris_delete( object * obj )
db->objnum = -1;
}
/**
* If debris piece *db is far away from all players, make it go away very soon.
* In single player game, delete if MAX_DEBRIS_DIST from player.
* In multiplayer game, delete if MAX_DEBRIS_DIST from all players.
*/
void maybe_delete_debris(debris *db)
{
object *objp;
if (Player_obj != nullptr && timestamp_elapsed(db->next_distance_check) && timestamp_elapsed(db->must_survive_until)) {
if (!(Game_mode & GM_MULTIPLAYER)) { // In single player game, just check against player.
if (vm_vec_dist_quick(&Player_obj->pos, &Objects[db->objnum].pos) > MAX_DEBRIS_DIST)
db->lifeleft = 0.1f;
else
db->next_distance_check = timestamp(DEBRIS_DISTANCE_CHECK_TIME);
} else {
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if (objp->flags[Object::Object_Flags::Player_ship]) {
if (vm_vec_dist_quick(&objp->pos, &Objects[db->objnum].pos) < MAX_DEBRIS_DIST) {
db->next_distance_check = timestamp(DEBRIS_DISTANCE_CHECK_TIME);
return;
}
}
}
db->lifeleft = 0.1f;
}
}
}
/**
* Do various updates to debris: check if time to die, start fireballs
@@ -217,7 +246,7 @@ void debris_process_post(object * obj, float frame_time)
radar_plot_object( obj );
if ( timestamp_elapsed(db->sound_delay) ) {
obj_snd_assign(objnum, db->ambient_sound, &vmd_zero_vector);
obj_snd_assign(objnum, db->ambient_sound, &vmd_zero_vector, 0);
db->sound_delay = 0;
}
}
@@ -231,6 +260,8 @@ void debris_process_post(object * obj, float frame_time)
}
}
maybe_delete_debris(db); // Make this debris go away if it's very far away.
// ================== DO THE ELECTRIC ARCING STUFF =====================
if ( db->arc_frequency <= 0 ) {
return; // If arc_frequency <= 0, this piece has no arcs on it
@@ -243,7 +274,7 @@ void debris_process_post(object * obj, float frame_time)
if (db->is_hull) {
int n, n_arcs = Random::next(1, 3); // Create 1-3 sparks
int n, n_arcs = ((rand()>>5) % 3)+1; // Create 1-3 sparks
vec3d v1, v2, v3, v4;
@@ -252,7 +283,8 @@ void debris_process_post(object * obj, float frame_time)
n = 0;
int lifetime = Random::next(100, 1000);
int a = 100, b = 1000;
int lifetime = (myrand()%((b)-(a)+1))+(a);
// Create the spark effects
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
@@ -319,8 +351,8 @@ void debris_process_post(object * obj, float frame_time)
db->arc_timestamp[i] = timestamp(-1);
} else {
// Maybe move a vertex.... 20% of the time maybe?
int mr = Random::next();
if ( mr < Random::MAX_VALUE/5 ) {
int mr = myrand();
if ( mr < RAND_MAX/5 ) {
vec3d v1, v2;
submodel_get_two_random_points_better(db->model_num, db->submodel_num, &v1, &v2);
@@ -417,15 +449,15 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
if (!hull_flag) {
if (model_num >= 0) {
db->model_num = model_num;
db->submodel_num = Random::next(sip->generic_debris_num_submodels);
db->submodel_num = (myrand() >> 4) % sip->generic_debris_num_submodels;
}
else if (vaporize) {
db->model_num = Debris_vaporize_model;
db->submodel_num = Random::next(Debris_num_submodels);
db->submodel_num = (myrand() >> 4) % Debris_num_submodels;
}
else {
db->model_num = Debris_model;
db->submodel_num = Random::next(Debris_num_submodels);
db->submodel_num = (myrand() >> 4) % Debris_num_submodels;
}
}
else {
@@ -433,6 +465,8 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
db->submodel_num = submodel_num;
}
db->model_instance_num = model_create_instance(false, db->model_num);
float radius = submodel_get_radius(db->model_num, db->submodel_num);
// if its generic, maybe cull it if its too small and far
@@ -447,6 +481,9 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
}
}
//WMC - We must survive until now, at least.
db->must_survive_until = timestamp();
if(hull_flag && sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
{
db->lifeleft = (( sip->debris_max_lifetime - sip->debris_min_lifetime ) * frand()) + sip->debris_min_lifetime;
@@ -455,7 +492,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
{
// Create Debris piece n!
if ( hull_flag ) {
if (Random::next() < (Random::MAX_VALUE/6)) // Make some pieces blow up shortly after explosion.
if (rand() < RAND_MAX/6) // Make some pieces blow up shortly after explosion.
db->lifeleft = 2.0f * (frand()) + 0.5f;
else {
db->flags.set(Debris_Flags::DoNotExpire);
@@ -473,10 +510,12 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
{
if(sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
{
db->must_survive_until = timestamp(fl2i(sip->debris_min_lifetime * 1000.0f));
db->lifeleft = (( sip->debris_max_lifetime - sip->debris_min_lifetime ) * frand()) + sip->debris_min_lifetime;
}
else if(sip->debris_min_lifetime >= 0.0f)
{
db->must_survive_until = timestamp(fl2i(sip->debris_min_lifetime * 1000.0f));
if(db->lifeleft < sip->debris_min_lifetime)
db->lifeleft = sip->debris_min_lifetime;
}
@@ -501,6 +540,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
db->fire_timeout = 0; // if not changed, timestamp_elapsed() will return false
db->time_started = Missiontime;
db->species = Ship_info[shipp->ship_info_index].species;
db->next_distance_check = (myrand() % 2000) + 4*DEBRIS_DISTANCE_CHECK_TIME;
db->parent_alt_name = shipp->alt_type_index;
db->damage_mult = 1.0f;
@@ -537,7 +577,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
vm_vector_2_matrix(&orient, &rand);
}
objnum = obj_create(OBJ_DEBRIS, parent_objnum, n, &orient, pos, radius, default_flags, false);
objnum = obj_create( OBJ_DEBRIS, parent_objnum, n, &orient, pos, radius, default_flags);
if ( objnum == -1 ) {
mprintf(("Couldn't create debris object -- out of object slots\n"));
return nullptr;
@@ -548,8 +588,6 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
obj = &Objects[objnum];
pi = &obj->phys_info;
db->model_instance_num = model_create_instance(objnum, db->model_num);
// assign the network signature. The signature will be 0 for non-hull pieces, but since that
// is our invalid signature, it should be okay.
obj->net_signature = 0;
@@ -592,7 +630,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
if ( hull_flag ) {
float t;
scale = exp_force * i2fl(Random::next(10, 29)); // for radial_vel away from location of blast center
scale = exp_force * i2fl((myrand()%20) + 10); // for radial_vel away from location of blast center
db->sound_delay = timestamp(DEBRIS_SOUND_DELAY);
// set up physics mass and I_inv for hull debris pieces
@@ -604,7 +642,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
// limit the amount of time that fireballs appear
// let fireball length be linked to radius of ship. Range is .33 radius => 3.33 radius seconds.
t = 1000*Objects[db->source_objnum].radius/3 + Random::next(fl2i(1000*3*Objects[db->source_objnum].radius));
t = 1000*Objects[db->source_objnum].radius/3 + myrand()%(fl2i(1000*3*Objects[db->source_objnum].radius));
db->fire_timeout = timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
if ( Objects[db->source_objnum].radius < MIN_RADIUS_FOR_PERSISTANT_DEBRIS ) {
@@ -615,7 +653,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
}
}
else {
scale = exp_force * i2fl(Random::next(10, 29)); // for radial_vel away from blast center (non-hull)
scale = exp_force * i2fl((myrand()%20) + 10); // for radial_vel away from blast center (non-hull)
}
if ( vm_vec_mag_squared( &to_center ) < 0.1f ) {
@@ -642,7 +680,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
radius = 1.0f;
}
scale = (6.0f + i2fl(Random::next(4))) / radius;
scale = ( 6.0f + i2fl(myrand()%4) ) / radius;
vm_vec_rand_vec_quick(&rotvel);
vm_vec_scale(&rotvel, scale);
@@ -668,11 +706,9 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
// ensure vel is valid
Assert( !vm_is_vec_nan(&obj->phys_info.vel) );
if (OnDebrisCreatedHook->isActive()) {
OnDebrisCreatedHook->run(scripting::hook_param_list(
scripting::hook_param("Debris", 'o', obj),
scripting::hook_param("Source", 'o', source_obj)));
}
OnDebrisCreatedHook->run(scripting::hook_param_list(
scripting::hook_param("Debris", 'o', obj),
scripting::hook_param("Source", 'o', source_obj)));
if (db->is_hull) {
MONITOR_INC(NumHullDebris,1);
@@ -777,9 +813,9 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
vec3d normal;
if (mc.model_instance_num >= 0)
model_instance_local_to_global_dir(&normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, mc.orient);
model_instance_find_world_dir(&normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, mc.orient);
else
model_local_to_global_dir(&normal, &mc.hit_normal, mc.model_num, mc.hit_submodel, mc.orient);
model_find_world_dir(&normal, &mc.hit_normal, mc.model_num, mc.hit_submodel, mc.orient);
*hitNormal = normal;
}
@@ -813,18 +849,18 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
// find the light object's position in the heavy object's reference frame at last frame and also in this frame.
vec3d p0_temp, p0_rotated;
// Collision detection from rotation enabled if at rotation is less than 30 degree in frame
// Collision detection from rotation enabled if at rotaion is less than 30 degree in frame
// This should account for all ships
if ( (vm_vec_mag_squared(&heavy->phys_info.rotvel) * flFrametime*flFrametime) < (PI*PI/36) ) {
// collide_rotate calculate (1) start position and (2) relative velocity
debris_hit_info->collide_rotate = true;
debris_hit_info->collide_rotate = 1;
vm_vec_rotate(&p0_temp, &p0, &heavy->last_orient);
vm_vec_unrotate(&p0_rotated, &p0_temp, &heavy->orient);
mc.p0 = &p0_rotated; // Point 1 of ray to check
vm_vec_sub(&debris_hit_info->light_rel_vel, &p1, &p0_rotated);
vm_vec_scale(&debris_hit_info->light_rel_vel, 1/flFrametime);
} else {
debris_hit_info->collide_rotate = false;
debris_hit_info->collide_rotate = 0;
vm_vec_sub(&debris_hit_info->light_rel_vel, &lighter->phys_info.vel, &heavy->phys_info.vel);
}
@@ -856,18 +892,18 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
// Do collision the cool new way
if ( debris_hit_info->collide_rotate ) {
// We collide with the sphere, find the list of moving submodels and test one at a time
// We collide with the sphere, find the list of rotating submodels and test one at a time
SCP_vector<int> submodel_vector;
model_get_moving_submodel_list(submodel_vector, heavy_obj);
model_get_rotating_submodel_list(&submodel_vector, heavy_obj);
// turn off all moving submodels, collide against only 1 at a time.
// turn off collision detection for all moving submodels
// turn off all rotating submodels, collide against only 1 at a time.
// turn off collision detection for all rotating submodels
for (auto submodel : submodel_vector) {
pmi->submodel[submodel].collision_checked = true;
}
// Only check single submodel now, since children of moving submodels are handled as moving as well
mc.flags = copy_flags | MC_SUBMODEL;
// reset flags to check MC_CHECK_MODEL | MC_CHECK_SPHERELINE and maybe MC_CHECK_INVISIBLE_FACES and MC_SUBMODEL_INSTANCE
mc.flags = copy_flags | MC_SUBMODEL_INSTANCE;
if (Ship_info[Ships[pship_obj->instance].ship_info_index].collision_lod > -1) {
mc.lod = Ship_info[Ships[pship_obj->instance].ship_info_index].collision_lod;
@@ -880,9 +916,15 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
// turn on just one submodel for collision test
smi->collision_checked = false;
// set angles for last frame (need to set to prev to get p0)
matrix copy_matrix = smi->canonical_orient;
// find the start and end positions of the sphere in submodel RF
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
model_instance_global_to_local_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
smi->canonical_orient = smi->canonical_prev_orient;
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
smi->canonical_orient = copy_matrix;
world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
mc.p0 = &p0;
mc.p1 = &p1;
@@ -895,19 +937,19 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
mc_ret_val = 1;
// set up debris_hit_info common
set_hit_struct_info(debris_hit_info, &mc, true);
model_instance_local_to_global_point(&debris_hit_info->hit_pos, &mc.hit_point, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
set_hit_struct_info(debris_hit_info, &mc, SUBMODEL_ROT_HIT);
model_instance_find_world_point(&debris_hit_info->hit_pos, &mc.hit_point, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
// set up debris_hit_info for rotating submodel
if (!debris_hit_info->edge_hit) {
model_instance_local_to_global_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
if (debris_hit_info->edge_hit == 0) {
model_instance_find_world_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
vec3d int_light_pos, diff;
vm_vec_sub(&diff, mc.p1, mc.p0);
vm_vec_scale_add(&int_light_pos, mc.p0, &diff, mc.hit_dist);
model_instance_local_to_global_point(&debris_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
model_instance_find_world_point(&debris_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
}
}
@@ -928,11 +970,11 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
if (mc.hit_dist < debris_hit_info->hit_time) {
mc_ret_val = 1;
set_hit_struct_info(debris_hit_info, &mc, false);
set_hit_struct_info(debris_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
// get collision normal if not edge hit
if (!debris_hit_info->edge_hit) {
model_instance_local_to_global_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
if (debris_hit_info->edge_hit == 0) {
model_instance_find_world_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
@@ -958,7 +1000,7 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
mc_ret_val = model_collide(&mc);
if (mc_ret_val) {
set_hit_struct_info(debris_hit_info, &mc, false);
set_hit_struct_info(debris_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
// set normal if not edge hit
if ( !debris_hit_info->edge_hit ) {
@@ -1092,7 +1134,7 @@ void debris_render(object * obj, model_draw_list *scene)
if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f ) {
for (i=0; i<MAX_DEBRIS_ARCS; i++ ) {
if ( timestamp_valid( db->arc_timestamp[i] ) ) {
model_instance_add_arc( pm, pmi, db->submodel_num, &db->arc_pts[i][0], &db->arc_pts[i][1], MARC_TYPE_DAMAGED );
model_instance_add_arc( pm, pmi, db->submodel_num, &db->arc_pts[i][0], &db->arc_pts[i][1], MARC_TYPE_NORMAL );
}
}
}
+2
View File
@@ -40,6 +40,7 @@ typedef struct debris {
gamesnd_id ambient_sound; // Ambient looping sound
int objnum = -1; // What object this is linked to
float lifeleft; // When 0 or less object dies
int must_survive_until; //WMC - timestamp of earliest point that it can be murthered.
int model_num; // What model this uses
int model_instance_num; // What model instance this uses - needed for arcs
int submodel_num; // What submodel this uses
@@ -49,6 +50,7 @@ typedef struct debris {
int fire_timeout; // timestamp that holds time for fireballs to stop appearing
int sound_delay; // timestamp to signal when sound should start
fix time_started; // time when debris was created
int next_distance_check; // timestamp to determine whether to delete this piece of debris.
vec3d arc_pts[MAX_DEBRIS_ARCS][2]; // The endpoints of each arc
int arc_timestamp[MAX_DEBRIS_ARCS]; // When this times out, the spark goes away. -1 is not used
+5 -24
View File
@@ -488,21 +488,6 @@ void dc_putc(char c)
line_str->push_back(c);
}
bool handle_textInputEvent(const SDL_Event& event) {
if (event.text.text[0] != '\0' && event.text.text[0] != '\1') {
for (char c : event.text.text) {
if (c < 32)
break;
if (dc_command_buf.size() < MAX_CLI_LEN) {
dc_command_buf.push_back(c);
}
}
}
return true;
}
void debug_console(void (*_func)(void))
{
int done = 0;
@@ -515,9 +500,6 @@ void debug_console(void (*_func)(void))
dc_init();
}
auto textListener = os::events::addEventListener(SDL_TEXTINPUT, 10000, &handle_textInputEvent);
SDL_StartTextInput();
dc_draw(TRUE);
while (!done) {
@@ -604,9 +586,11 @@ void debug_console(void (*_func)(void))
break;
default:
// Leave handling of key input to SDL
break;
// Not any of the control key codes, so it's probably a letter or number.
ubyte c = (ubyte)key_to_ascii(k);
if ((c != 255) && (dc_command_buf.size() < MAX_CLI_LEN)) {
dc_command_buf.push_back(c);
}
}
// Do the passed function
@@ -618,9 +602,6 @@ void debug_console(void (*_func)(void))
dc_draw(TRUE);
}
SDL_StopTextInput();
os::events::removeEventListener(textListener);
while( key_inkey() ) {
os_poll();
}
+208 -189
View File
@@ -9,158 +9,179 @@
#include "tracing/tracing.h"
#include "ship/ship.h"
namespace decals {
namespace {
DecalDefinition::DecalDefinition(SCP_string name) : _name(std::move(name)) {
}
class DecalDefinition {
SCP_string _name;
DecalDefinition::~DecalDefinition() {
if (_diffuseBitmap >= 0) {
bm_release(_diffuseBitmap);
SCP_string _diffuseFilename;
bool _loopDiffuse = true;
SCP_string _glowFilename;
bool _loopGlow = true;
SCP_string _normalMapFilename;
bool _loopNormal = true;
int _diffuseBitmap = -1;
int _glowBitmap = -1;
int _normalBitmap = -1;
public:
explicit DecalDefinition(SCP_string name) : _name(std::move(name)) {
}
if (_glowBitmap >= 0) {
bm_release(_glowBitmap);
}
if (_normalBitmap >= 0) {
bm_release(_normalBitmap);
}
}
DecalDefinition::DecalDefinition(DecalDefinition&& other) noexcept {
*this = std::move(other); // Use operator implementation
}
DecalDefinition& DecalDefinition::operator=(DecalDefinition&& other) noexcept {
std::swap(_name, other._name);
std::swap(_diffuseFilename, other._diffuseFilename);
std::swap(_glowFilename, other._glowFilename);
std::swap(_normalMapFilename, other._normalMapFilename);
std::swap(_diffuseBitmap, other._diffuseBitmap);
std::swap(_glowBitmap, other._glowBitmap);
std::swap(_normalBitmap, other._normalBitmap);
std::swap(_loopDiffuse, other._loopDiffuse);
std::swap(_loopGlow, other._loopGlow);
std::swap(_loopNormal, other._loopNormal);
return *this;
}
void DecalDefinition::parse() {
if (optional_string("+Diffuse:")) {
stuff_string(_diffuseFilename, F_FILESPEC);
if (!bm_validate_filename(_diffuseFilename, true, true)) {
error_display(0, "Animation '%s' is not valid!", _diffuseFilename.c_str());
_diffuseFilename = "";
~DecalDefinition() {
if (_diffuseBitmap >= 0) {
bm_release(_diffuseBitmap);
}
if (optional_string("+Loop:")) {
stuff_boolean(&_loopDiffuse);
if (_glowBitmap >= 0) {
bm_release(_glowBitmap);
}
if (_normalBitmap >= 0) {
bm_release(_normalBitmap);
}
}
if (optional_string("+Glow:")) {
stuff_string(_glowFilename, F_FILESPEC);
if (!bm_validate_filename(_glowFilename, true, true)) {
error_display(0, "Animation '%s' is not valid!", _glowFilename.c_str());
_glowFilename = "";
// Disallow copying
DecalDefinition(const DecalDefinition&) = delete;
DecalDefinition& operator=(const DecalDefinition&) = delete;
// Move constructor and operator
DecalDefinition(DecalDefinition&& other) noexcept {
*this = std::move(other); // Use operator implementation
}
DecalDefinition& operator=(DecalDefinition&& other) noexcept {
std::swap(_name, other._name);
std::swap(_diffuseFilename, other._diffuseFilename);
std::swap(_glowFilename, other._glowFilename);
std::swap(_normalMapFilename, other._normalMapFilename);
std::swap(_diffuseBitmap, other._diffuseBitmap);
std::swap(_glowBitmap, other._glowBitmap);
std::swap(_normalBitmap, other._normalBitmap);
std::swap(_loopDiffuse, other._loopDiffuse);
std::swap(_loopGlow, other._loopGlow);
std::swap(_loopNormal, other._loopNormal);
return *this;
}
void parse() {
if (optional_string("+Diffuse:")) {
stuff_string(_diffuseFilename, F_FILESPEC);
if (!bm_validate_filename(_diffuseFilename, true, true)) {
error_display(0, "Animation '%s' is not valid!", _diffuseFilename.c_str());
_diffuseFilename = "";
}
if (optional_string("+Loop:")) {
stuff_boolean(&_loopDiffuse);
}
}
if (optional_string("+Glow:")) {
stuff_string(_glowFilename, F_FILESPEC);
if (optional_string("+Loop:")) {
stuff_boolean(&_loopGlow);
if (!bm_validate_filename(_glowFilename, true, true)) {
error_display(0, "Animation '%s' is not valid!", _glowFilename.c_str());
_glowFilename = "";
}
if (optional_string("+Loop:")) {
stuff_boolean(&_loopGlow);
}
}
if (optional_string("+Normal:")) {
stuff_string(_normalMapFilename, F_FILESPEC);
if (!bm_validate_filename(_normalMapFilename, true, true)) {
error_display(0, "Animation '%s' is not valid!", _normalMapFilename.c_str());
_normalMapFilename = "";
}
if (optional_string("+Loop:")) {
stuff_boolean(&_loopNormal);
}
}
}
if (optional_string("+Normal:")) {
stuff_string(_normalMapFilename, F_FILESPEC);
if (!bm_validate_filename(_normalMapFilename, true, true)) {
error_display(0, "Animation '%s' is not valid!", _normalMapFilename.c_str());
_normalMapFilename = "";
void loadBitmaps() {
if (_diffuseBitmap == -1 && VALID_FNAME(_diffuseFilename)) {
_diffuseBitmap = bm_load_either(_diffuseFilename.c_str());
if (_diffuseBitmap == -1) {
Warning(LOCATION,
"Bitmap '%s' failed to load for decal definition %s!",
_diffuseFilename.c_str(),
_name.c_str());
}
}
if (optional_string("+Loop:")) {
stuff_boolean(&_loopNormal);
if (_glowBitmap == -1 && VALID_FNAME(_glowFilename)) {
_glowBitmap = bm_load_either(_glowFilename.c_str());
if (_glowBitmap == -1) {
Warning(LOCATION,
"Bitmap '%s' failed to load for decal definition %s!",
_glowFilename.c_str(),
_name.c_str());
}
}
if (_normalBitmap == -1 && VALID_FNAME(_normalMapFilename)) {
_normalBitmap = bm_load_either(_normalMapFilename.c_str());
if (_normalBitmap == -1) {
Warning(LOCATION,
"Bitmap '%s' failed to load for decal definition %s!",
_normalMapFilename.c_str(),
_name.c_str());
}
}
}
}
void DecalDefinition::loadBitmaps() {
if (_diffuseBitmap == -1 && VALID_FNAME(_diffuseFilename)) {
_diffuseBitmap = bm_load_either(_diffuseFilename.c_str());
if (_diffuseBitmap == -1) {
Warning(LOCATION,
"Bitmap '%s' failed to load for decal definition %s!",
_diffuseFilename.c_str(),
_name.c_str());
void pageIn() {
if (_diffuseBitmap >= 0) {
bm_page_in_texture(_diffuseBitmap);
}
if (_glowBitmap >= 0) {
bm_page_in_texture(_glowBitmap);
}
if (_normalBitmap >= 0) {
bm_page_in_texture(_normalBitmap);
}
}
if (_glowBitmap == -1 && VALID_FNAME(_glowFilename)) {
_glowBitmap = bm_load_either(_glowFilename.c_str());
if (_glowBitmap == -1) {
Warning(LOCATION,
"Bitmap '%s' failed to load for decal definition %s!",
_glowFilename.c_str(),
_name.c_str());
}
}
if (_normalBitmap == -1 && VALID_FNAME(_normalMapFilename)) {
_normalBitmap = bm_load_either(_normalMapFilename.c_str());
if (_normalBitmap == -1) {
Warning(LOCATION,
"Bitmap '%s' failed to load for decal definition %s!",
_normalMapFilename.c_str(),
_name.c_str());
}
}
}
void DecalDefinition::pageIn() {
if (_diffuseBitmap >= 0) {
bm_page_in_texture(_diffuseBitmap);
bool bitmapsLoaded() {
// Since both bitmap types are optional we need to check if either is loaded to determine if any bitmap is loaded
return _diffuseBitmap >= 0 || _glowBitmap >= 0 || _normalBitmap >= 0;
}
if (_glowBitmap >= 0) {
bm_page_in_texture(_glowBitmap);
const SCP_string& getName() const {
return _name;
}
if (_normalBitmap >= 0) {
bm_page_in_texture(_normalBitmap);
int getDiffuseBitmap() const {
return _diffuseBitmap;
}
}
int getGlowBitmap() const {
return _glowBitmap;
}
int getNormalBitmap() const {
return _normalBitmap;
}
bool isDiffuseLooping() const {
return _loopDiffuse;
}
bool isGlowLooping() const {
return _loopGlow;
}
bool isNormalLooping() const {
return _loopNormal;
}
};
bool DecalDefinition::bitmapsLoaded() {
// Since both bitmap types are optional we need to check if either is loaded to determine if any bitmap is loaded
return _diffuseBitmap >= 0 || _glowBitmap >= 0 || _normalBitmap >= 0;
}
const SCP_string& DecalDefinition::getName() const {
return _name;
}
int DecalDefinition::getDiffuseBitmap() const {
return _diffuseBitmap;
}
int DecalDefinition::getGlowBitmap() const {
return _glowBitmap;
}
int DecalDefinition::getNormalBitmap() const {
return _normalBitmap;
}
bool DecalDefinition::isDiffuseLooping() const {
return _loopDiffuse;
}
bool DecalDefinition::isGlowLooping() const {
return _loopGlow;
}
bool DecalDefinition::isNormalLooping() const {
return _loopNormal;
}
SCP_vector<DecalDefinition> DecalDefinitions;
SCP_vector<DecalDefinition> decalDefinitions;
// Variable to indicate if the system is able to work correctly on the current system
bool Decal_system_active = true;
bool decal_system_active = true;
void parse_decals_table(const char* filename) {
try {
@@ -176,7 +197,7 @@ void parse_decals_table(const char* filename) {
DecalDefinition def(name);
def.parse();
DecalDefinitions.push_back(std::move(def));
decalDefinitions.push_back(std::move(def));
}
required_string("#End");
@@ -187,17 +208,17 @@ void parse_decals_table(const char* filename) {
if (!gr_is_capable(CAPABILITY_DEFERRED_LIGHTING)) {
// we need deferred lighting
Decal_system_active = false;
decal_system_active = false;
mprintf(("Note: Decal system has been disabled due to lack of deferred lighting.\n"));
}
if (!gr_is_capable(CAPABILITY_NORMAL_MAP)) {
// We need normal mapping for the full feature range
Decal_system_active = false;
decal_system_active = false;
mprintf(("Note: Decal system has been disabled due to lack of normal mapping.\n"));
}
if (!gr_is_capable(CAPABILITY_SEPARATE_BLEND_FUNCTIONS)) {
// WWe need separate blending functions for different color buffers
Decal_system_active = false;
decal_system_active = false;
mprintf(("Note: Decal system has been disabled due to lack of separate color buffer blend functions.\n"));
}
}
@@ -223,9 +244,6 @@ struct Decal {
if (!object.IsValid()) {
return false;
}
if (object.objp->flags[Object::Object_Flags::Should_be_dead]) {
return false;
}
if (orig_obj_type != object.objp->type) {
mprintf(("Decal object type for object %d has changed from %s to %s. Please let m!m know about this\n",
@@ -252,9 +270,6 @@ struct Decal {
if (smi->blown_off) {
return false;
}
} else {
Assertion(false, "Only ships are currently supported for decals!");
return false;
}
return true;
@@ -293,41 +308,35 @@ float smoothstep(float edge0, float edge1, float x) {
namespace decals {
void initialize() {
if (gr_screen.mode == GR_STUB) {
Decal_system_active = false;
return;
}
DecalDefinitions.clear();
decalDefinitions.clear();
graphics::decal_draw_list::globalInit();
parse_modular_table(NOX("*-dcl.tbm"), parse_decals_table);
}
void shutdown() {
graphics::decal_draw_list::globalShutdown();
// Free allocated resources
DecalDefinitions.clear();
decalDefinitions.clear();
}
int findDecalDefinition(const SCP_string& name) {
auto ii = std::find_if(DecalDefinitions.begin(), DecalDefinitions.end(), [&](const DecalDefinition& def)
{
return def.getName() == name;
});
int getDecalConfig(const SCP_string& name) {
int index = 0;
for (auto& def : decalDefinitions) {
if (!stricmp(def.getName().c_str(), name.c_str())) {
return index;
}
++index;
}
if (ii == DecalDefinitions.end())
return -1;
return static_cast<int>(std::distance(DecalDefinitions.begin(), ii));
return -1;
}
void parseDecalReference(creation_info& dest_info, bool is_new_entry) {
SCP_string name;
stuff_string(name, F_NAME);
auto decalRef = findDecalDefinition(name);
auto decalRef = getDecalConfig(name);
if (decalRef < 0) {
error_display(0, "Decal definition '%s' is unknown!", name.c_str());
@@ -352,9 +361,8 @@ void parseDecalReference(creation_info& dest_info, bool is_new_entry) {
}
}
}
void loadBitmaps(const creation_info& info) {
if (!Decal_system_active) {
if (!decal_system_active) {
return;
}
// Silently ignore invalid definition handle since weapons use the default values if the decal option is not present
@@ -362,16 +370,15 @@ void loadBitmaps(const creation_info& info) {
return;
}
Assertion(info.definition_handle >= 0 && info.definition_handle < (int) DecalDefinitions.size(),
Assertion(info.definition_handle >= 0 && info.definition_handle < (int) decalDefinitions.size(),
"Invalid decal handle detected!");
auto& def = DecalDefinitions[info.definition_handle];
auto& def = decalDefinitions[info.definition_handle];
def.loadBitmaps();
}
void pageInDecal(const creation_info& info) {
if (!Decal_system_active) {
if (!decal_system_active) {
return;
}
// Silently ignore invalid definition handle since weapons use the default values if the decal option is not present
@@ -379,10 +386,10 @@ void pageInDecal(const creation_info& info) {
return;
}
Assertion(info.definition_handle >= 0 && info.definition_handle < (int) DecalDefinitions.size(),
Assertion(info.definition_handle >= 0 && info.definition_handle < (int) decalDefinitions.size(),
"Invalid decal handle detected!");
DecalDefinitions[info.definition_handle].pageIn();
decalDefinitions[info.definition_handle].pageIn();
}
void initializeMission() {
@@ -398,25 +405,43 @@ matrix4 getDecalTransform(Decal& decal) {
auto pm = model_get(pmi->model_num);
vec3d worldPos;
matrix worldOrient;
model_instance_local_to_global_point_orient(&worldPos,
&worldOrient,
model_instance_find_world_point(&worldPos,
&decal.position,
&decal.orientation,
pm,
pmi,
decal.submodel,
&objp->orient,
&objp->pos);
// The decal API sees the "direction" of a decal to be along the normal of the surface it is attached to. However,
// this will lead to a situation where we would look at the decal texture "from behind" causing the texture to
// appear flipped. We fix that here for the graphics transform by inverting the Z scaling.
vec3d worldDir;
model_instance_find_world_dir(&worldDir,
&decal.orientation.vec.fvec,
pm,
pmi,
decal.submodel,
&objp->orient);
vec3d worldUp;
model_instance_find_world_dir(&worldUp,
&decal.orientation.vec.fvec,
pm,
pmi,
decal.submodel,
&objp->orient);
matrix worldOrient;
vm_vector_2_matrix(&worldOrient, &worldDir, &worldUp);
// Apply scaling
vm_vec_scale(&worldOrient.vec.rvec, decal.scale.xyz.x);
vm_vec_scale(&worldOrient.vec.uvec, decal.scale.xyz.y);
vm_vec_scale(&worldOrient.vec.fvec, -decal.scale.xyz.z);
worldOrient.a2d[0][0] *= decal.scale.xyz.x;
worldOrient.a2d[0][1] *= decal.scale.xyz.x;
worldOrient.a2d[0][2] *= decal.scale.xyz.x;
worldOrient.a2d[1][0] *= decal.scale.xyz.y;
worldOrient.a2d[1][1] *= decal.scale.xyz.y;
worldOrient.a2d[1][2] *= decal.scale.xyz.y;
worldOrient.a2d[2][0] *= decal.scale.xyz.z;
worldOrient.a2d[2][1] *= decal.scale.xyz.z;
worldOrient.a2d[2][2] *= decal.scale.xyz.z;
matrix4 mat4;
vm_matrix4_set_transform(&mat4, &worldOrient, &worldPos);
@@ -425,7 +450,7 @@ matrix4 getDecalTransform(Decal& decal) {
}
void renderAll() {
if (!Decal_system_active) {
if (!decal_system_active) {
return;
}
@@ -456,9 +481,9 @@ void renderAll() {
int glow_bm = -1;
int normal_bm = -1;
Assertion(decal.definition_handle >= 0 && decal.definition_handle < (int) DecalDefinitions.size(),
Assertion(decal.definition_handle >= 0 && decal.definition_handle < (int) decalDefinitions.size(),
"Invalid decal handle detected!");
auto& decalDef = DecalDefinitions[decal.definition_handle];
auto& decalDef = decalDefinitions[decal.definition_handle];
auto decal_time = mission_time - decal.creation_time;
auto progress = decal_time / decal.lifetime;
@@ -489,9 +514,9 @@ void renderAll() {
draw_list.render();
}
void addDecal(creation_info& info, object* host, int submodel, const vec3d& local_pos, const matrix& local_orient) {
if (!Decal_system_active) {
void
addDecal(creation_info& info, object* host, int submodel, const vec3d& local_pos, const matrix& local_orient) {
if (!decal_system_active) {
return;
}
// Silently ignore invalid definition handle since weapons use the default values if the decal option is not present
@@ -499,9 +524,9 @@ void addDecal(creation_info& info, object* host, int submodel, const vec3d& loca
return;
}
Assertion(info.definition_handle >= 0 && info.definition_handle < (int) DecalDefinitions.size(),
Assertion(info.definition_handle >= 0 && info.definition_handle < (int) decalDefinitions.size(),
"Invalid decal handle detected!");
auto& def = DecalDefinitions[info.definition_handle];
auto& def = decalDefinitions[info.definition_handle];
if (!def.bitmapsLoaded()) {
// If this decal was never used before then the bitmaps are not loaded so we need to do that here.
@@ -518,15 +543,9 @@ void addDecal(creation_info& info, object* host, int submodel, const vec3d& loca
newDecal.position = local_pos;
newDecal.orientation = local_orient;
if (info.width < 0.0f || info.height < 0.0f) {
newDecal.scale.xyz.x = info.radius;
newDecal.scale.xyz.y = info.radius;
newDecal.scale.xyz.z = info.radius;
} else {
newDecal.scale.xyz.x = info.width;
newDecal.scale.xyz.y = info.height;
newDecal.scale.xyz.z = MIN(info.width, info.height);
}
newDecal.scale.xyz.x = info.radius;
newDecal.scale.xyz.y = info.radius;
newDecal.scale.xyz.z = info.radius;
active_decals.push_back(newDecal);
}
-53
View File
@@ -8,52 +8,6 @@
namespace decals {
class DecalDefinition {
SCP_string _name;
SCP_string _diffuseFilename;
bool _loopDiffuse = true;
SCP_string _glowFilename;
bool _loopGlow = true;
SCP_string _normalMapFilename;
bool _loopNormal = true;
int _diffuseBitmap = -1;
int _glowBitmap = -1;
int _normalBitmap = -1;
public:
explicit DecalDefinition(SCP_string name);
~DecalDefinition();
// Disallow copying
DecalDefinition(const DecalDefinition&) = delete;
DecalDefinition& operator=(const DecalDefinition&) = delete;
// Move constructor and operator
DecalDefinition(DecalDefinition&& other) noexcept;
DecalDefinition& operator=(DecalDefinition&& other) noexcept;
void parse();
void loadBitmaps();
void pageIn();
bool bitmapsLoaded();
const SCP_string& getName() const;
int getDiffuseBitmap() const;
int getGlowBitmap() const;
int getNormalBitmap() const;
bool isDiffuseLooping() const;
bool isGlowLooping() const;
bool isNormalLooping() const;
};
extern SCP_vector<DecalDefinition> DecalDefinitions;
extern bool Decal_system_active;
/**
* @brief A reference to a decal definition
*/
@@ -65,8 +19,6 @@ typedef int DecalReference;
struct creation_info {
DecalReference definition_handle = -1;
float radius = -1.0f;
float width = -1.0f;
float height = -1.0f;
util::UniformFloatRange lifetime = ::util::UniformFloatRange(-1.0f);
};
@@ -76,11 +28,6 @@ struct creation_info {
void initialize();
/**
* Returns the index of this decal, or -1 if not found
*/
int findDecalDefinition(const SCP_string& name);
/**
* @brief Parses the information for a decal reference
*
+3 -3
View File
@@ -56,7 +56,7 @@ void GetLightInfo(vec3 position, out vec3 lightDir, out float attenuation)
// Positional light source
lightDir = lightPosition - position.xyz;
float dist = length(lightDir);
attenuation = 1.0 - clamp(sqrt(dist / lightRadius), 0.0, 1.0);
attenuation = 1.0 - clamp(dist / lightRadius, 0.0, 1.0);
if(dist > lightRadius && lightType != LT_TUBE) {
discard;
@@ -81,7 +81,7 @@ void GetLightInfo(vec3 position, out vec3 lightDir, out float attenuation)
if(dist > lightRadius) {
discard;
}
attenuation = 1.0 - clamp(sqrt(dist / lightRadius), 0.0, 1.0);
attenuation = 1.0 - clamp(dist / lightRadius, 0.0, 1.0);
} else if (lightType == LT_CONE) {
float coneDot = dot(normalize(-lightDir), coneDir);
if(dualCone) {
@@ -98,7 +98,7 @@ void GetLightInfo(vec3 position, out vec3 lightDir, out float attenuation)
}
}
}
attenuation *= attenuation;
lightDir = normalize(lightDir);
}
}
@@ -1,20 +0,0 @@
#define M_PI 3.1415926535897932384626433832795
uniform samplerCube envmap;
in vec4 fragTexCoord;
out vec4 fragOut0;
layout (location = 0) out vec4 outColor;
void main() {
const float theta = -fragTexCoord.x * (2.0 * M_PI);
const float phi = fragTexCoord.y * (M_PI) - (0.5 * M_PI);
const float st = sin(theta);
const float ct = cos(theta);
const float sp = sin(phi);
const float cp = cos(phi);
const vec3 sampVec = vec3(cp * ct, sp, cp * st);
fragOut0 = vec4(texture(envmap, sampVec).rgb, 1.0);
}
+6
View File
@@ -4,8 +4,14 @@ const int LT_POINT = 1; // A point light, like an explosion
const int LT_TUBE = 2; // A tube light, like a fluorescent light
const int LT_CONE = 3; // A cone light, like a flood light
const float SPEC_INTENSITY_POINT = 5.3 ;// Point light
const float SPEC_INTENSITY_DIRECTIONAL = 3.0 ;// Directional light
const float SPECULAR_FACTOR = 1.75;
const float SPECULAR_ALPHA = 0.1;
const float SPEC_FACTOR_NO_SPEC_MAP = 0.1;
const float ENV_ALPHA_FACTOR = 0.3;
const float GLOW_MAP_INTENSITY = 1.5;
const float AMBIENT_LIGHT_BOOST = 1.0;
const float GLOW_MAP_SRGB_MULTIPLIER = 3.0;
vec3 FresnelSchlick(vec3 specColor, vec3 light, vec3 halfVec)
+1 -16
View File
@@ -94,8 +94,6 @@ layout (std140) uniform modelData {
int sNormalmapIndex;
int sAmbientmapIndex;
int sMiscmapIndex;
float alphaMult;
};
in VertexOutput {
@@ -263,11 +261,6 @@ void main()
}
#endif
baseColor = texture(sBasemap, vec3(diffuseTexCoord, float(sBasemapIndex)));
#ifdef FLAG_ALPHA_MULT
baseColor.a *= alphaMult;
#endif
if ( blend_alpha == 0 && baseColor.a < 0.95 ) discard; // if alpha blending is not on, discard transparent pixels
// premultiply alpha if blend_alpha is 1. assume that our blend function is srcColor + (1-Alpha)*destColor.
// if blend_alpha is 2, assume blend func is additive and don't modify color
@@ -436,15 +429,7 @@ void main()
fragOut0 = baseColor;
#ifdef FLAG_DEFERRED
fragOut1 = vec4(vertIn.position.xyz, 1.0);
// Anti-glint "trick" based on Valve's "Advanced VR Rendering" talk at GDC2015:
// http://media.steampowered.com/apps/valve/2015/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf Page 43
// basically make surfaces rougher if local normals change too fast in screenspace
vec2 normDx = dFdx(normal.xy);
vec2 normDy = dFdy(normal.xy);
float glossGeo = 1.0f - pow(clamp(max(dot(normDx,normDx), dot(normDy,normDy)),0.0,1.0),0.33);
fragOut2 = vec4(normal, min(glossData, glossGeo));
fragOut2 = vec4(normal, glossData);
fragOut3 = vec4(specColor.rgb, fresnelFactor);
fragOut4 = emissiveColor;
#endif
-2
View File
@@ -103,8 +103,6 @@ layout (std140) uniform modelData {
int sNormalmapIndex;
int sAmbientmapIndex;
int sMiscmapIndex;
float alphaMult;
};
in VertexOutput {
-2
View File
@@ -97,8 +97,6 @@ layout (std140) uniform modelData {
int sNormalmapIndex;
int sAmbientmapIndex;
int sMiscmapIndex;
float alphaMult;
};
#ifdef FLAG_TRANSFORM
+6 -192
View File
@@ -7,25 +7,9 @@ uniform sampler2D tex;
layout (std140) uniform genericData {
float exposure;
int tonemapper;
float x0; //from here on these are for the PPC tonemappers
float y0;
float x1;
float toe_B;
float toe_lnA;
float sh_B;
float sh_lnA;
float sh_offsetX;
float sh_offsetY;
};
//Tonemapping options, aside from the previous standard UC2, pulled from wookiejedi and qazwsxal's
//work on testing them for FSO, which itself was based on these references:
//https://64.github.io/tonemapping/ Delta - Blog by 64: Tone Mapping
//http://filmicworlds.com/blog/filmic-tonemapping-operators/ Filmic Worlds: Filmic Tonemapping Operators by John Hable
vec3 Uncharted2ToneMapping(vec3 hdr_color) // John Hable's filmic
vec3 Uncharted2Tonemapping(vec3 hdr_color)
{
float A = 0.15;
float B = 0.50;
@@ -33,185 +17,15 @@ vec3 Uncharted2ToneMapping(vec3 hdr_color) // John Hable's filmic
float D = 0.20;
float E = 0.02;
float F = 0.30;
float W = 11.2;
hdr_color = ((hdr_color * (A * hdr_color + C * B) + D * E) / (hdr_color * (A * hdr_color + B) + D * F)) - E / F;
float white = ((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F;
hdr_color /= white;
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
return hdr_color;
return ((hdr_color*(A*hdr_color+C*B)+D*E)/(hdr_color*(A*hdr_color+B)+D*F))-E/F;
}
vec3 rtt_and_odt_fit(vec3 v)
{
vec3 a = v * (v + 0.0245786f) - 0.000090537f;
vec3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f;
return a / b;
}
const mat3 aces_input = mat3(vec3(0.59719, 0.07600, 0.02840),
vec3(0.35458, 0.90834, 0.13383),
vec3(0.04823, 0.01566, 0.83777)
);
const mat3 aces_output = mat3(vec3( 1.60475, -0.10208, -0.00327),
vec3(-0.53108, 1.10813, -0.07276),
vec3(-0.07367, -0.00605, 1.07602)
);
vec3 aces(vec3 hdr_color)
{
hdr_color = aces_input * hdr_color;
hdr_color = rtt_and_odt_fit(hdr_color);
hdr_color = aces_output * hdr_color;
hdr_color = linear_to_srgb(hdr_color);
return hdr_color;
}
vec3 aces_approx(vec3 hdr_color) // also filmic, higher contrast then Uncharted2ToneMapping
{
const float a = 2.51f;
const float b = 0.03f;
const float c = 2.43f;
const float d = 0.59f;
const float e = 0.14f;
hdr_color *= 0.6f;
hdr_color = clamp((hdr_color*(a*hdr_color+b))/(hdr_color*(c*hdr_color+d)+e), 0.0f, 1.0f);
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
return hdr_color;
}
vec3 OptimizedCineonToneMapping( vec3 color ) {
// optimized filmic operator by Jim Hejl and Richard Burgess-Dawson
color = max( vec3( 0.0 ), color - 0.004 );
color = (color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 );
// linear to sRGB conversion embedded in shader
return color;
}
vec3 rhj_lumaToneMapping(vec3 hdr_color) // reinhard_jodie
{
float luma = dot(hdr_color, vec3(0.2126, 0.7152, 0.0722)); //dot products are a bit more expensive
float toneMappedLuma = luma / (1.0 + luma);
hdr_color *= toneMappedLuma / luma;
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
return hdr_color;
}
vec3 reinhard_extended(vec3 hdr_color)
{
float max_white = 1.0;
vec3 numerator = hdr_color * (1.0f + (hdr_color / vec3(max_white * max_white)));
hdr_color = numerator / (1.0f + hdr_color);
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
return hdr_color;
}
vec3 linearToneMapping(vec3 hdr_color)
{
hdr_color = clamp(hdr_color, 0., 1.);
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
return hdr_color;
}
float toe( float x ) {
return exp(toe_lnA + toe_B * log(x));
}
float linear( float x ) {
// Slope is 1 by definition
return y0 + (x - x0);
}
float shoulder ( float x ) {
// Scale is -1 so reverse subtraction to save a mult
x = sh_offsetX - x;
x = exp(sh_lnA + sh_B * log(x));
x = sh_offsetY - x;
return x;
}
float funswitch (float x) {
float x_tone;
if (x <= x0) {
x_tone = toe(x);
}
else if (x <= x1) {
x_tone = linear(x);
}
else if ( x < sh_offsetX ){
x_tone = shoulder(x);
}
else {
x_tone = sh_offsetY;
}
return x_tone;
}
vec3 PPC_RGB( vec3 color ) {
color = vec3( funswitch(color.r),
funswitch(color.g),
funswitch(color.b));
return linear_to_srgb(color);
}
vec3 PPC( vec3 color ) {
float luma = dot(color, vec3(0.2126, 0.7152, 0.0722));
float luma_tone;
if ( luma <= x0 ) {
luma_tone = toe(luma);
}
else if ( luma <= x1 ) {
luma_tone = linear(luma);
}
else if ( luma < sh_offsetX ){
luma_tone = shoulder(luma);
}
else {
luma_tone = sh_offsetY;
}
return linear_to_srgb(color * luma_tone / luma);
}
void main()
{
vec4 color = texture(tex, fragTexCoord.xy);
color *= exposure;
switch(tonemapper){
case 0:
color.rgb = linearToneMapping(color.rgb);
break;
case 1:
color.rgb = Uncharted2ToneMapping(color.rgb);
break;
case 2:
color.rgb = aces(color.rgb);
break;
case 3:
color.rgb = aces_approx(color.rgb);
break;
case 4:
color.rgb = OptimizedCineonToneMapping(color.rgb);
break;
case 5:
color.rgb = rhj_lumaToneMapping(color.rgb);
break;
case 6:
color.rgb = reinhard_extended(color.rgb);
break;
case 7:
color.rgb = PPC(color.rgb);
break;
case 8:
color.rgb = PPC_RGB(color.rgb);
break;
}
// Tone mapping using John Hable's Uncharted 2 tonemapping algorithm.
float whitepoint = 11.2f; // hardcoded whitepoint value from Hable's algo
color.rgb = Uncharted2Tonemapping(color.rgb * exposure) / Uncharted2Tonemapping(vec3(whitepoint));
color.rgb = linear_to_srgb(color.rgb); // return from linear color space to SRGB color space
fragOut0 = vec4(color.rgb, 1.0);
}
+11 -33
View File
@@ -26,7 +26,7 @@
#include <cstdlib>
bool Knossos_warp_ani_used;
int Knossos_warp_ani_used;
#define WARPHOLE_GROW_TIME (2.35f) // time for warphole to reach max size (also time to shrink to nothing once it begins to shrink)
@@ -203,10 +203,6 @@ static void fireball_set_default_warp_attributes(int idx)
strcpy_s(Fireball_info[idx].warp_glow, "warpglow01");
strcpy_s(Fireball_info[idx].warp_ball, "warpball01");
strcpy_s(Fireball_info[idx].warp_model, "warp.pof");
if (Fireball_use_3d_warp)
Fireball_info[idx].use_3d_warp = true;
break;
}
}
@@ -370,13 +366,7 @@ static void parse_fireball_tbl(const char *table_filename)
// check for custom warp model
if (optional_string("$Warp model:"))
{
stuff_string(fi->warp_model, F_NAME, NAME_LENGTH);
// if we are explicitly specifying a model, then we'll want to use it,
// rather than just having the default model that might or might not be used
fi->use_3d_warp = true;
}
}
required_string("#End");
@@ -407,21 +397,10 @@ void fireball_parse_tbl()
{
if (fi.lod_count > 1)
{
Assertion(fi.lod_count <= MAX_FIREBALL_LOD, "Fireball LOD (%d) greater than MAX_FIREBALL_LOD %d.", fi.lod_count, MAX_FIREBALL_LOD);
static_assert(MAX_FIREBALL_LOD < 10, "The fireball LOD naming scheme needs to be changed for LODs > 9");
auto lod0 = fi.lod[0].filename;
constexpr int MAX_BASENAME_LEN = MAX_FILENAME_LEN - 3; // ensure base file name + _*lod* fits in filename field
if (strlen(lod0) > MAX_BASENAME_LEN)
{
Warning(LOCATION, "A fireball base file name may not be longer than %d characters due to the LOD naming scheme.\nLODs other than LOD0 will be skipped for fireball %s", MAX_BASENAME_LEN, lod0);
fi.lod_count = 1;
continue;
}
for (int j = 1; j < fi.lod_count; ++j)
sprintf(fi.lod[j].filename, "%.*s_%d", MAX_BASENAME_LEN, lod0, j % MAX_FIREBALL_LOD /*to show gcc12 format string is safe*/);
sprintf(fi.lod[j].filename, "%s_%d", lod0, j);
}
}
@@ -481,7 +460,7 @@ void fireball_init()
Unused_fireball_indices.reserve(INTITIAL_FIREBALL_CONTAINTER_SIZE);
// Goober5000 - reset Knossos warp flag
Knossos_warp_ani_used = false;
Knossos_warp_ani_used = 0;
}
MONITOR( NumFireballsRend )
@@ -859,7 +838,7 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
flagset<Object::Object_Flags> default_flags;
default_flags.set(Object::Object_Flags::Renders);
objnum = obj_create(OBJ_FIREBALL, parent_obj, n, &orient, pos, size, default_flags, false);
objnum = obj_create(OBJ_FIREBALL, parent_obj, n, &orient, pos, size, default_flags);
obj = &Objects[objnum];
@@ -872,11 +851,11 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
switch( new_fireball->fireball_render_type ) {
case FIREBALL_MEDIUM_EXPLOSION:
new_fireball->orient = Random::next() & 7; // 0 - 7
new_fireball->orient = (myrand()>>8) & 7; // 0 - 7
break;
case FIREBALL_LARGE_EXPLOSION:
new_fireball->orient = Random::next(360); // 0 - 359
new_fireball->orient = (myrand()>>8) % 360; // 0 - 359
break;
case FIREBALL_WARP_EFFECT:
@@ -1007,9 +986,9 @@ int fireball_ship_explosion_type(ship_info *sip)
}
if(ship_fireballs > 0){
index = sip->explosion_bitmap_anims[Random::next(ship_fireballs)];
index = sip->explosion_bitmap_anims[rand()%ship_fireballs];
} else if(objecttype_fireballs > 0){
index = Ship_types[sip->class_type].explosion_bitmap_anims[Random::next(objecttype_fireballs)];
index = Ship_types[sip->class_type].explosion_bitmap_anims[rand()%objecttype_fireballs];
}
return index;
@@ -1026,7 +1005,7 @@ int fireball_asteroid_explosion_type(asteroid_info *aip)
int roid_fireballs = (int)aip->explosion_bitmap_anims.size();
if (roid_fireballs > 0) {
index = aip->explosion_bitmap_anims[Random::next(roid_fireballs)];
index = aip->explosion_bitmap_anims[rand()%roid_fireballs];
}
return index;
@@ -1070,8 +1049,7 @@ void fireball_render(object* obj, model_draw_list *scene)
float alpha = 1.0f;
if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_fireballs)
alpha *= neb2_get_fog_visibility(&obj->pos,
Neb2_fog_visibility_fireball_const + (obj->radius * Neb2_fog_visibility_fireball_scaled_factor));
alpha *= neb2_get_fog_visibility(&obj->pos, NEB_FOG_VISIBILITY_MULT_FIREBALL(obj->radius));
g3_transfer_vertex(&p, &obj->pos);
@@ -1093,7 +1071,7 @@ void fireball_render(object* obj, model_draw_list *scene)
float rad = obj->radius * fireball_wormhole_intensity(fb);
fireball_info *fi = &Fireball_info[fb->fireball_info_index];
warpin_queue_render(scene, obj, &obj->orient, &obj->pos, fb->current_bitmap, rad, percent_life, obj->radius, fi->use_3d_warp || (fb->flags & FBF_WARP_3D) != 0, fi->warp_glow_bitmap, fi->warp_ball_bitmap, fi->warp_model_id);
warpin_queue_render(scene, obj, &obj->orient, &obj->pos, fb->current_bitmap, rad, percent_life, obj->radius, (fb->flags & FBF_WARP_3D) != 0, fi->warp_glow_bitmap, fi->warp_ball_bitmap, fi->warp_model_id);
}
break;
+1 -3
View File
@@ -58,8 +58,6 @@ typedef struct fireball_info {
fireball_lod lod[MAX_FIREBALL_LOD];
float exp_color[3]; // red, green, blue
bool use_3d_warp;
char warp_glow[NAME_LENGTH];
int warp_glow_bitmap;
char warp_ball[NAME_LENGTH];
@@ -143,7 +141,7 @@ int fireball_asteroid_explosion_type(asteroid_info *aip);
float fireball_wormhole_intensity( fireball *fb );
// Goober5000
extern bool Knossos_warp_ani_used;
extern int Knossos_warp_ani_used;
extern bool Fireball_use_3d_warp;
+2 -2
View File
@@ -91,14 +91,14 @@ void warpin_queue_render(model_draw_list *scene, object *obj, matrix *orient, ve
g3_transfer_vertex( &verts[4], &vecs[4] );
float alpha = (The_mission.flags[Mission::Mission_Flags::Fullneb]) ? neb2_get_fog_visibility(&obj->pos, 1.0f) : 1.0f;
float alpha = (The_mission.flags[Mission::Mission_Flags::Fullneb]) ? neb2_get_fog_visibility(obj) : 1.0f;
//batch_add_bitmap(warp_glow_bitmap, TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT, &verts[4], 0, r, alpha);
batching_add_bitmap(warp_glow_bitmap, &verts[4], 0, r, alpha);
}
}
if ( (warp_model_id >= 0) && (warp_3d) ) {
if ( (warp_model_id >= 0) && (warp_3d || Fireball_use_3d_warp) ) {
model_render_params render_info;
float scale = radius / 25.0f;
+1 -4
View File
@@ -239,10 +239,7 @@ bool GameState_Stack_Valid()
// returns one of the GS_STATE_ macros
int gameseq_get_state(int depth)
{
if (!GameState_Stack_Valid())
return GS_STATE_INVALID;
{
Assert(depth <= gs_current_stack);
return gs[gs_current_stack - depth].current_state;
+69 -64
View File
@@ -68,8 +68,8 @@ typedef struct tagSNDPATTERN {
SNDPATTERN Patterns[MAX_PATTERNS]; // holds data on sections of a SoundTrack
// Holds filenames for the different sections of a soundtrack
SCP_vector<SOUNDTRACK_INFO> Soundtracks;
SOUNDTRACK_INFO Soundtracks[MAX_SOUNDTRACKS];
int Num_soundtracks;
int Current_soundtrack_num; // Active soundtrack for the current mission.. index into Soundtracks[]
int Current_pattern = -1; // currently playing part of track
@@ -87,6 +87,12 @@ static int Victory2_music_played;
static int Next_arrival_timestamp;
static int Check_for_battle_music;
// stores the number of measures for the different patterns (data from music.tbl)
float Pattern_num_measures[MAX_SOUNDTRACKS][MAX_PATTERNS];
// stores the number of bytes per measure (data from music.tbl)
int Pattern_samples_per_measure[MAX_SOUNDTRACKS][MAX_PATTERNS];
typedef struct pattern_info
{
const char *pattern_name;
@@ -250,10 +256,11 @@ extern int hud_target_invalid_awacs(object *objp);
// Holds file names of spooled music that is played at menus, briefings, credits etc.
// Indexed into by a #define enumeration of the different kinds of spooled music
SCP_vector<menu_music> Spooled_music;
menu_music Spooled_music[MAX_SPOOLED_MUSIC];
int Num_music_files; // Number of spooled music files
// Array that holds indicies into Spooled_music[], these specify which music is played in briefing/debriefing
int Mission_music[NUM_SCORES];
int Mission_music[NUM_SCORES];
// -------------------------------------------------------------------------------------------------
@@ -278,11 +285,23 @@ void event_music_init()
return;
}
int i, j;
//MUST be called before parsing stuffzors.
Soundtracks.clear();
Spooled_music.clear();
Num_music_files = 0;
Num_soundtracks = 0; // Global
event_music_reset_choices();
// Goober5000
// set all the filenames to "none" so we're compatible with the extra NRMLs in FS1 music
memset(Soundtracks, 0, MAX_SOUNDTRACKS * sizeof(SOUNDTRACK_INFO));
for (i = 0; i < MAX_SOUNDTRACKS; i++)
for (j = 0; j < MAX_PATTERNS; j++)
strcpy_s(Soundtracks[i].pattern_fnames[j], NOX("none.wav"));
// Goober5000
memset(Spooled_music, 0, MAX_SPOOLED_MUSIC * sizeof(menu_music));
//Do teh parsing
event_music_parse_musictbl("music.tbl");
@@ -548,9 +567,9 @@ void event_music_level_start(int force_soundtrack)
return;
}
Assert(Current_soundtrack_num >= 0 && Current_soundtrack_num < (int)Soundtracks.size());
Assert(Current_soundtrack_num >= 0 && Current_soundtrack_num < Num_soundtracks);
if (Current_soundtrack_num < 0 || Current_soundtrack_num > (int)Soundtracks.size())
if (Current_soundtrack_num < 0 || Current_soundtrack_num > Num_soundtracks)
return;
strack = &Soundtracks[Current_soundtrack_num];
@@ -563,13 +582,13 @@ void event_music_level_start(int force_soundtrack)
// tracks because their patterns weren't -1
for (i = 0; i < MAX_PATTERNS; i++)
{
if (!strnicmp(strack->patterns[i].fname, NOX("none.wav"), 4))
if (!strnicmp(strack->pattern_fnames[i], NOX("none.wav"), 4))
{
Patterns[i].handle = -1;
continue;
}
Patterns[i].handle = audiostream_open( strack->patterns[i].fname, ASF_EVENTMUSIC );
Patterns[i].handle = audiostream_open( strack->pattern_fnames[i], ASF_EVENTMUSIC );
if (Patterns[i].handle >= 0)
{
@@ -590,8 +609,8 @@ void event_music_level_start(int force_soundtrack)
Patterns[i].loop_for = Patterns[i].default_loop_for;
Patterns[i].force_pattern = FALSE;
Patterns[i].can_force = pip->pattern_can_force;
Patterns[i].samples_per_measure = strack->patterns[i].samples_per_measure;
Patterns[i].num_measures = strack->patterns[i].num_measures;
Patterns[i].samples_per_measure = Pattern_samples_per_measure[Current_soundtrack_num][i];
Patterns[i].num_measures = Pattern_num_measures[Current_soundtrack_num][i];
}
Num_enemy_arrivals = 0;
@@ -640,12 +659,7 @@ void event_music_first_pattern()
audiostream_stop( Patterns[Current_pattern].handle );
}
// if we really are initializing the level
if (Missiontime == 0) {
Pattern_timer_id = timestamp(-1); // don't let this start quite yet; see event_music_set_start_delay()
} else {
Pattern_timer_id = timestamp(0); // start immediately
}
Pattern_timer_id = 2000; // start music delay
Event_music_begun = FALSE;
if ( Event_Music_battle_started == TRUE ) {
@@ -656,25 +670,6 @@ void event_music_first_pattern()
}
}
// Event music was originally hard-coded to start at 2 seconds, regardless of the timestamp. Since the default player entry delay is 1 second,
// this yields a relative start time of 1 second. Mission start will now use relative rather than absolute delay.
void event_music_set_start_delay()
{
if (Event_music_inited == FALSE) {
return;
}
if (Event_music_enabled == FALSE) {
return;
}
if (Event_music_level_inited == FALSE) {
return;
}
Pattern_timer_id = timestamp(1000);
}
// -------------------------------------------------------------------------------------------------
// event_music_level_close()
//
@@ -687,8 +682,11 @@ void event_music_level_close()
if ( Event_music_level_inited == FALSE )
return;
if ( Current_soundtrack_num >= 0 && Current_soundtrack_num < (int)Soundtracks.size() ) {
auto strack = &Soundtracks[Current_soundtrack_num];
if ( Current_soundtrack_num >= 0 && Current_soundtrack_num < MAX_SOUNDTRACKS ) {
SOUNDTRACK_INFO *strack;
Assert( Current_soundtrack_num >= 0 && Current_soundtrack_num < MAX_SOUNDTRACKS );
strack = &Soundtracks[Current_soundtrack_num];
// close the pattern files
for ( i = 0; i < strack->num_patterns; i++ ) {
@@ -1146,16 +1144,16 @@ bool parse_soundtrack_line(int strack_idx, int pattern_idx)
}
if (count == 0) {
Soundtracks[strack_idx].patterns[pattern_idx].num_measures = (float)atof(token); //Num_measures
} else if (count == 1) {
Soundtracks[strack_idx].patterns[pattern_idx].samples_per_measure = atoi(token); //Samples per measure
if ( count == 0 ) {
Pattern_num_measures[strack_idx][pattern_idx] = (float)atof(token); //Num_measures
} else if(count == 1) {
Pattern_samples_per_measure[strack_idx][pattern_idx] = atoi(token); //Samples per measure
}
count++;
} // end while
strcpy_s(Soundtracks[strack_idx].patterns[pattern_idx].fname, fname);
strcpy_s(Soundtracks[strack_idx].pattern_fnames[pattern_idx], fname);
return true;
}
@@ -1179,27 +1177,28 @@ void parse_soundtrack()
}
//Get a valid strack_idx
if (strack_idx < 0 && nocreate)
if(strack_idx < 0 && (nocreate || Num_soundtracks >= MAX_SOUNDTRACKS))
{
if(Num_soundtracks >= MAX_SOUNDTRACKS) {
Warning(LOCATION, "Maximum number of soundtracks reached after '%s'; max is '%d'", Soundtracks[MAX_SOUNDTRACKS-1].name, MAX_SOUNDTRACKS);
}
//Track doesn't exist and has nocreate, so don't create it
if ( !skip_to_start_of_string_either("#SoundTrack Start", "#Menu Music Start") && !skip_to_string("#SoundTrack End")) {
error_display(1, "Couldn't find #Soundtrack Start, #Menu Music Start or #Soundtrack End.");
}
return;
}
else if (strack_idx < 0)
else if(strack_idx < 0)
{
//If we don't have this soundtrack already, create it
strack_idx = (int)Soundtracks.size();
Soundtracks.emplace_back();
strack_idx = Num_soundtracks;
Soundtracks[strack_idx].flags = 0;
Soundtracks[strack_idx].num_patterns = 0;
strcpy_s(Soundtracks[strack_idx].name, namebuf);
Soundtracks[strack_idx].num_patterns = 0;
// set all the filenames to "none" so we're compatible with the extra NRMLs in FS1 music
for (auto &pattern : Soundtracks[strack_idx].patterns)
strcpy_s(pattern.fname, NOX("none.wav"));
Num_soundtracks++;
}
// Goober5000
@@ -1290,11 +1289,11 @@ void parse_soundtrack()
for (i = 0; i < Soundtracks[strack_idx].num_patterns; i++)
{
// check for "none"
if (!strlen(Soundtracks[strack_idx].patterns[i].fname) || !strnicmp(Soundtracks[strack_idx].patterns[i].fname, "none", 4))
if (!strlen(Soundtracks[strack_idx].pattern_fnames[i]) || !strnicmp(Soundtracks[strack_idx].pattern_fnames[i], "none", 4))
continue;
// check for file
if (!cf_exists_full_ext(Soundtracks[strack_idx].patterns[i].fname, CF_TYPE_MUSIC, NUM_AUDIO_EXT, audio_ext_list))
if (!cf_exists_full(Soundtracks[strack_idx].pattern_fnames[i], CF_TYPE_MUSIC))
return;
}
@@ -1317,19 +1316,22 @@ void parse_menumusic()
int idx = event_music_get_spooled_music_index(spoolname);
if (idx < 0 && nocreate)
if(idx < 0 && (nocreate || Num_music_files >= MAX_SPOOLED_MUSIC))
{
if(Num_music_files >= MAX_SPOOLED_MUSIC) {
Warning(LOCATION, "Could not load spooled music file after '%s' as maximum number of spooled music was reached (Max is %d)", Spooled_music[Num_music_files - 1].name, MAX_SPOOLED_MUSIC);
}
if (!skip_to_start_of_string_either("$Name:", "#Menu Music End")) {
Error(LOCATION, "Couldn't find $Name or #Menu Music End. Music.tbl or -mus.tbm is invalid.\n");
}
return;
}
else if (idx < 0)
else if(idx < 0)
{
idx = (int)Spooled_music.size();
Spooled_music.emplace_back();
idx = Num_music_files;
Spooled_music[idx].flags = 0;
strcpy_s( Spooled_music[idx].name, spoolname );
strcpy_s( Spooled_music[idx].filename, "");
}
@@ -1352,6 +1354,9 @@ void parse_menumusic()
// chief1983 - use type list defined in ffmpeg.h
if ( cf_exists_full_ext(Spooled_music[idx].filename, CF_TYPE_MUSIC, NUM_AUDIO_EXT, audio_ext_list) )
Spooled_music[idx].flags |= SMF_VALID;
if (!nocreate)
Num_music_files++;
}
// -------------------------------------------------------------------------------------------------
@@ -1600,7 +1605,7 @@ int event_music_next_soundtrack(int delta)
}
new_soundtrack = Current_soundtrack_num + delta;
if ( new_soundtrack >= (int)Soundtracks.size() )
if ( new_soundtrack >= Num_soundtracks )
new_soundtrack = 0;
event_music_level_close();
@@ -1616,7 +1621,7 @@ void event_sexp_change_soundtrack(const char *name)
int i, new_soundtrack = -1;
for (i = 0; i < (int)Soundtracks.size(); i++) {
for (i = 0; i < Num_soundtracks; i++) {
if ( !stricmp(name, Soundtracks[i].name) ) {
new_soundtrack = i;
}
@@ -1660,7 +1665,7 @@ void event_music_set_soundtrack(const char *name)
int event_music_get_soundtrack_index(const char *name)
{
// find the correct index for the event music
for ( int i = 0; i < (int)Soundtracks.size(); i++ ) {
for ( int i = 0; i < Num_soundtracks; i++ ) {
if ( !stricmp(name, Soundtracks[i].name) ) {
return i;
}
@@ -1672,7 +1677,7 @@ int event_music_get_soundtrack_index(const char *name)
int event_music_get_spooled_music_index(const char *name)
{
// find the correct index for the event music
for ( int i = 0; i < (int)Spooled_music.size(); i++ ) {
for ( int i = 0; i < Num_music_files; i++ ) {
if ( !stricmp(name, Spooled_music[i].name) ) {
return i;
}
+10 -9
View File
@@ -67,30 +67,32 @@ typedef struct menu_music {
char filename[MAX_FILENAME_LEN]; // name music is stored on disk as
} menu_music;
#define MAX_SPOOLED_MUSIC 50 // max number of briefing/mainhall/credits tracks
// Goober5000 - spooled music flags
#define SMF_VALID (1 << 0)
extern SCP_vector<menu_music> Spooled_music;
extern menu_music Spooled_music[MAX_SPOOLED_MUSIC];
extern int Num_music_files;
// event music soundtrack storage
typedef struct tagSOUNDTRACK_INFO {
int flags;
int num_patterns;
char name[NAME_LENGTH];
struct {
char fname[MAX_FILENAME_LEN];
float num_measures;
int samples_per_measure;
} patterns[MAX_PATTERNS];
char name[NAME_LENGTH];
char pattern_fnames[MAX_PATTERNS][MAX_FILENAME_LEN];
} SOUNDTRACK_INFO;
#define MAX_SOUNDTRACKS 30 // max number of battle tracks
// Goober5000 - event music flags
#define EMF_VALID (1 << 0)
#define EMF_ALLIED_ARRIVAL_OVERLAY (1 << 1)
#define EMF_CYCLE_FS1 (1 << 2)
extern SCP_vector<SOUNDTRACK_INFO> Soundtracks;
extern SOUNDTRACK_INFO Soundtracks[MAX_SOUNDTRACKS];
extern int Num_soundtracks;
void event_music_init();
@@ -105,7 +107,6 @@ void event_music_parse_musictbl(const char *filename);
void event_music_change_pattern(int new_pattern);
int event_music_return_current_pattern();
void event_music_first_pattern();
void event_music_set_start_delay();
int event_music_battle_start();
int event_music_enemy_arrival();
int event_music_friendly_arrival();
+25 -45
View File
@@ -526,7 +526,7 @@ void parse_iface_sound_list(const char* tag, SCP_vector<interface_snd_id>& desti
//if we're using the old format, double check the size)
if(!scp_list && (destination.size() != (unsigned)check))
{
mprintf(("%s in '%s' has " SIZE_T_ARG " entries. This does not match entered size of %i.\n", tag, object_name, destination.size(), check));
mprintf(("%s in '%s' has " SIZE_T_ARG " entries. This does not match entered size of %i.", tag, object_name, destination.size(), check));
}
}
}
@@ -544,12 +544,12 @@ void gamesnd_preload_common_sounds()
return;
Assert( Snds.size() <= INT_MAX );
for (auto& gs: Snds) {
if ( gs.preload ) {
for (auto& entry : gs.sound_entries) {
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
if ( gs->preload ) {
for (auto& entry : gs->sound_entries) {
if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0 ) {
game_busy( NOX("** preloading common game sounds **") ); // Animate loading cursor... does nothing if loading screen not active.
entry.id = snd_load(&entry, &gs.flags);
entry.id = snd_load(&entry, gs->flags);
}
}
}
@@ -565,12 +565,12 @@ void gamesnd_load_gameplay_sounds()
return;
Assert( Snds.size() <= INT_MAX );
for (auto& gs: Snds) {
if ( !gs.preload ) { // don't try to load anything that's already preloaded
for (auto& entry : gs.sound_entries) {
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
if ( !gs->preload ) { // don't try to load anything that's already preloaded
for (auto& entry : gs->sound_entries) {
if (entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0) {
game_busy(NOX("** preloading gameplay sounds **")); // Animate loading cursor... does nothing if loading screen not active.
entry.id = snd_load(&entry, &gs.flags);
entry.id = snd_load(&entry, gs->flags);
}
}
}
@@ -583,8 +583,8 @@ void gamesnd_load_gameplay_sounds()
void gamesnd_unload_gameplay_sounds()
{
Assert( Snds.size() <= INT_MAX );
for (auto& gs: Snds) {
for (auto& entry : gs.sound_entries) {
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
for (auto& entry : gs->sound_entries) {
if (entry.id.isValid()) {
snd_unload(entry.id);
entry.id = sound_load_id::invalid();
@@ -602,10 +602,10 @@ void gamesnd_load_interface_sounds()
return;
Assert( Snds_iface.size() < INT_MAX );
for (auto& gs: Snds) {
for (auto& entry : gs.sound_entries) {
for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
for (auto& entry : si->sound_entries) {
if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0 ) {
entry.id = snd_load(&entry, &gs.flags);
entry.id = snd_load(&entry, si->flags);
}
}
}
@@ -642,11 +642,10 @@ void parse_gamesnd_old(game_snd* gs)
stuff_string(entry.filename, F_NAME, MAX_FILENAME_LEN, ",");
if (!stricmp(entry.filename, NOX("empty")) || !stricmp(entry.filename, NOX("none")))
if (!stricmp(entry.filename, NOX("empty")))
{
entry.filename[0] = 0;
advance_to_eoln(nullptr);
gs->flags |= GAME_SND_NOT_VALID;
advance_to_eoln(NULL);
return;
}
Mp++;
@@ -752,14 +751,14 @@ bool required_string_no_create(const char* token, bool no_create)
static GameSoundCycleType parse_cycle_type() {
if (optional_string("Sequential")) {
return GameSoundCycleType::SequentialCycle;
return GameSoundCycleType::Sequential;
} else if (optional_string("Random")) {
return GameSoundCycleType::RandomCycle;
return GameSoundCycleType::Random;
} else {
error_display(0, "Failed to parse sound cycle type. Expected 'sequential' or 'random'. Got [%.32s]", next_tokens());
// Ignore everything until the end of the line. That should hopefully skip the bad token.
advance_to_eoln(nullptr);
return GameSoundCycleType::SequentialCycle;
return GameSoundCycleType::Sequential;
}
}
@@ -823,7 +822,7 @@ void parse_gamesnd_soundset(game_snd* gs, bool no_create) {
int temp_limit;
stuff_int(&temp_limit);
if (temp_limit > 0)
if ((temp_limit > 0) && (static_cast<uint>(temp_limit) <= SND_ENHANCED_MAX_LIMIT))
{
gs->enhanced_sound_data.limit = (unsigned int)temp_limit;
}
@@ -861,10 +860,9 @@ void parse_gamesnd_new(game_snd* gs, bool no_create)
// Default pitch is 1.0. This is set here in case we don't have a valid file name
gs->pitch_range = util::UniformFloatRange(1.0f);
if (!stricmp(name, NOX("empty")) || !stricmp(name, NOX("none")))
if (!stricmp(name, NOX("empty")))
{
entry->filename[0] = 0;
gs->flags |= GAME_SND_NOT_VALID;
return;
}
@@ -927,7 +925,7 @@ void parse_gamesnd_new(game_snd* gs, bool no_create)
int temp_limit;
stuff_int(&temp_limit);
if (temp_limit > 0)
if ((temp_limit > 0) && (static_cast<uint>(temp_limit) <= SND_ENHANCED_MAX_LIMIT))
{
gs->enhanced_sound_data.limit = (unsigned int)temp_limit;
}
@@ -1345,31 +1343,13 @@ bool gamesnd_interface_sound_valid(interface_snd_id sound) {
return sound.isValid() && sound.value() < (int) Snds_iface.size();
}
bool gamesnd_game_sound_try_load(gamesnd_id sound)
{
if (!gamesnd_game_sound_valid(sound)) {
return false;
}
auto gs = gamesnd_get_game_sound(sound);
for (auto& entry : gs->sound_entries) {
if (!entry.id.isValid()) {
// Lazily load unloaded sound entries when required
entry.id = snd_load(&entry, &gs->flags);
}
}
// check flag
return (gs->flags & GAME_SND_NOT_VALID) == 0;
}
float gamesnd_get_max_duration(game_snd* gs) {
int max_length = 0;
for (auto& entry : gs->sound_entries) {
if (!entry.id.isValid()) {
// Lazily load unloaded sound entries when required
entry.id = snd_load(&entry, &gs->flags);
entry.id = snd_load(&entry, gs->flags);
}
max_length = std::max(max_length, snd_get_duration(entry.id));
@@ -1382,14 +1362,14 @@ game_snd_entry* gamesnd_choose_entry(game_snd* gs) {
size_t index = 0;
switch(gs->cycle_type) {
case GameSoundCycleType::RandomCycle:
case GameSoundCycleType::Random:
if (gs->sound_entries.size() == 1) {
index = 0;
} else {
index = util::UniformRange<size_t>(0, gs->sound_entries.size() - 1).next();
}
break;
case GameSoundCycleType::SequentialCycle:
case GameSoundCycleType::Sequential:
if (gs->last_entry_index == std::numeric_limits<size_t>::max()) {
// If this is the first time we must return the first sound to keep everything consistent
index = 0;
-5
View File
@@ -401,11 +401,6 @@ game_snd* gamesnd_get_interface_sound(interface_snd_id handle);
*/
bool gamesnd_game_sound_valid(gamesnd_id sound);
/**
* @brief Checks if the given sound handle can be loaded
*/
bool gamesnd_game_sound_try_load(gamesnd_id sound);
/**
* @brief Checks if the given sound handle is a valid interface sound handle
* @param sound The handle to check
+2
View File
@@ -31,6 +31,8 @@
#define CALLSIGN_LEN (MAX_FILENAME_LEN - 4 - 1) // shortened from 32 to allow .json to be attached without exceeding MAX_FILENAME_LEN
#define SHORT_CALLSIGN_PIXEL_W 80 // max width of short_callsign[] in pixels
#define MAX_IFFS 10
// from ship.h
#define MAX_SHIPS 400 // max number of ship instances there can be.DTP; bumped from 200 to 400
#define SHIPS_LIMIT 400 // what MAX_SHIPS will be at release time (for error checking in debug mode); dtp Bumped from 200 to 400
+1 -161
View File
@@ -7,7 +7,7 @@
*
*/
#include <utility> // for std::move
#ifndef _LINKLIST_H
#define _LINKLIST_H
@@ -83,164 +83,4 @@ do { \
#define NOT_EMPTY(head) ((head)->next != (head))
#define EMPTY(head) ((head) == nullptr || (head)->next == (head))
// Note that since these iterators operate on pointer collections, the dereference of an iterator using operator* returns a pointer.
template <class T>
class volition_linked_list
{
T* head;
public:
class iterator
{
T* ptr;
public:
iterator(T* x)
: ptr(x)
{}
iterator(const iterator& it)
: ptr(it.ptr)
{}
iterator& operator++()
{
ptr = GET_NEXT(ptr);
return *this;
}
iterator operator++(int)
{
iterator tmp(*this);
operator++();
return tmp;
}
iterator& operator--()
{
ptr = GET_PREV(ptr);
return *this;
}
iterator operator--(int)
{
iterator tmp(*this);
operator--();
return tmp;
}
bool operator==(const iterator& rhs) const
{
return ptr == rhs.ptr;
}
bool operator!=(const iterator& rhs) const
{
return ptr != rhs.ptr;
}
T*& operator*()
{
return ptr;
}
T* operator->()
{
return ptr;
}
};
class const_iterator
{
const T* ptr;
public:
const_iterator(const T* x)
: ptr(x)
{}
const_iterator(const const_iterator& it)
: ptr(it.ptr)
{}
const_iterator& operator++()
{
ptr = GET_NEXT(ptr);
return *this;
}
const_iterator operator++(int)
{
const_iterator tmp(*this);
operator++();
return tmp;
}
const_iterator& operator--()
{
ptr = GET_PREV(ptr);
return *this;
}
const_iterator operator--(int)
{
const_iterator tmp(*this);
operator--();
return tmp;
}
bool operator==(const const_iterator& rhs) const
{
return ptr == rhs.ptr;
}
bool operator!=(const const_iterator& rhs) const
{
return ptr != rhs.ptr;
}
const T*& operator*()
{
return ptr;
}
const T* operator->()
{
return ptr;
}
};
iterator begin() const
{
return iterator(GET_FIRST(head));
}
iterator end() const
{
return iterator(END_OF_LIST(head));
}
const_iterator cbegin() const
{
return const_iterator(GET_FIRST(head));
}
const_iterator cend() const
{
return const_iterator(END_OF_LIST(head));
}
volition_linked_list(T* ptr)
: head(ptr)
{}
};
template <class T>
volition_linked_list<T> list_range(T* head)
{
return volition_linked_list<T>(head);
}
#endif
+13 -5
View File
@@ -113,9 +113,6 @@ typedef struct vec2d {
float x, y;
} vec2d;
// Euler angles for a rotation: h=heading, b=bank and p=pitch; angles are around the y, z and x axes
// respectively and are performed in that order. (Strictly speaking these are Tait-Bryan angles,
// not classic Euler angles.)
typedef struct angles {
float p, b, h;
} angles_t;
@@ -131,7 +128,6 @@ typedef struct matrix {
};
} matrix;
// You might think this is also row-major. You fool! It is actually column-major.
typedef struct matrix4 {
union {
struct {
@@ -307,6 +303,8 @@ constexpr bool LoggingEnabled = false;
const float PI2 = (PI*2.0f);
// half values
const float PI_2 = (PI/2.0f);
const int RAND_MAX_2 = (RAND_MAX/2);
const float RAND_MAX_1f = (1.0f / RAND_MAX);
extern int Fred_running; // Is Fred running, or FreeSpace?
@@ -352,6 +350,14 @@ const size_t INVALID_SIZE = static_cast<size_t>(-1);
#define TRUE 1
#define FALSE 0
int myrand();
// Returns a random number between 0 and 0x7fffffff
int rand32();
// Returns a random integer from low to high, inclusive
int rand32(int low, int high);
// lod checker for (modular) table parsing
typedef struct lod_checker {
@@ -457,6 +463,7 @@ SCP_string dump_stacktrace();
// DEBUG compile time catch for dangerous uses of memset/memcpy/memmove
// This is disabled for VS2013 and lower since that doesn't support the necessary features
#if !defined(NDEBUG) && !defined(USING_THIRD_PARTY_LIBS) && (!defined(_MSC_VER) || _MSC_VER >= 1900)
#if SCP_COMPILER_CXX_AUTO_TYPE && SCP_COMPILER_CXX_STATIC_ASSERT && HAVE_STD_IS_TRIVIALLY_COPYABLE
// feature support seems to be: gcc clang msvc
// auto 4.4 2.9 2010
// std::is_trivial 4.5 ? 2012 (2010 only duplicates std::is_pod)
@@ -556,6 +563,7 @@ using std::memcpy_if_trivial_else_error;
using std::memmove_if_trivial_else_error;
using std::memset_if_trivial_else_error;
#endif // HAVE_CXX11
#endif // NDEBUG
#endif // _PSTYPES_H
#endif // PS_TYPES_H
+2 -5
View File
@@ -3,14 +3,11 @@
#define SCP_DEFINES_H
#pragma once
#include "scp_compiler_detection.h"
#define SCP_TOKEN_CONCAT1(x, y) x ## y
#define SCP_TOKEN_CONCAT(x, y) SCP_TOKEN_CONCAT1(x, y)
// these are useful for embedding numbers in strings
// see https://stackoverflow.com/questions/5459868/concatenate-int-to-string-using-c-preprocessor
#define SCP_TOKEN_TO_STR1(x) #x
#define SCP_TOKEN_TO_STR(x) SCP_TOKEN_TO_STR1(x)
#define SCP_UNUSED(x) (void)(x)
#endif // SCP_DEFINES_H
+87 -2
View File
@@ -57,6 +57,7 @@ vci Viewer_chase_info; // View chase camera information
vec3d leaning_position;
int Is_standalone;
int Rand_count;
int Interface_last_tick = -1; // last timer tick on flip
@@ -108,6 +109,47 @@ float Noise[NOISE_NUM_FRAMES] = {
0.000000f
};
int myrand()
{
int rval;
rval = rand();
Rand_count++;
// nprintf(("Alan","RAND: %d\n", rval));
return rval;
}
// returns a random number between 0 and 0x7fffffff, or something close to it.
int rand32()
{
if (RAND_MAX == 0x7fff) {
int random32;
// this gets two random 16 bit numbers and stuffs them into a 32 bit number
random32 = (rand() << 16) | rand();
//since rand() returns between 0 and 0x7fff, there needs to be a thing to randomly generate the 16th bit
random32 |= ((rand() & 1) << 15);
return random32;
}
else {
return rand();
}
}
int rand32(int low, int high)
{
int diff;
// get diff - don't allow negative or zero
diff = high - low;
if (diff < 0)
diff = 0;
// To get a range of values between min and max, inclusive:
// random value = min + random number % (max - min + 1)
return (low + rand32() % (diff + 1));
}
// Variables for the loading callback hooks
static int cf_timestamp = -1;
static void (*cf_callback)(int count) = NULL;
@@ -243,8 +285,8 @@ detail_levels Detail_defaults[NUM_DEFAULT_DETAIL_LEVELS] = {
{ // Highest level
3, // setting
// ===== Analogs (0-MAX_DETAIL_LEVEL) ====
4, // nebula_detail; // 0=lowest detail, MAX_DETAIL_LEVEL=highest detail
4, // detail_distance; // 0=lowest MAX_DETAIL_LEVEL=highest
3, // nebula_detail; // 0=lowest detail, MAX_DETAIL_LEVEL=highest detail
3, // detail_distance; // 0=lowest MAX_DETAIL_LEVEL=highest
4, // hardware_textures; // 0=max culling, MAX_DETAIL_LEVEL=no culling
4, // num_small_debris; // 0=min number, MAX_DETAIL_LEVEL=max number
4, // num_particles; // 0=min number, MAX_DETAIL_LEVEL=max number
@@ -328,6 +370,9 @@ void detail_level_set(int level)
Assert( level < NUM_DEFAULT_DETAIL_LEVELS );
Detail = Detail_defaults[level];
// reset nebula stuff
neb2_set_detail_level(level);
}
// Returns the current detail level or -1 if custom.
@@ -427,6 +472,46 @@ DCF(detail, "Turns on/off parts of the game for speed testing" )
}
#endif
// Goober5000
// (Taylor says that for optimization purposes malloc/free should be used rather than vm_malloc/vm_free here)
// NOTE: Because this uses memcpy, it should only be used to sort POD elements!
void insertion_sort(void *array_base, size_t array_size, size_t element_size, int (*fncompare)(const void *, const void *))
{
int i, j;
void *current;
char *array_byte_base;
// this is used to avoid having to cast array_base to (char *) all the time
array_byte_base = (char *) array_base;
// allocate space for the element being moved
current = malloc(element_size);
if (current == NULL)
{
Int3();
return;
}
// loop
for (i = 1; (unsigned) i < array_size; i++)
{
// grab the current element
memcpy(current, array_byte_base + (i * element_size), element_size);
// bump other elements toward the end of the array
for (j = i - 1; (j >= 0) && (fncompare(array_byte_base + (j * element_size), current) > 0); j--)
{
memcpy(array_byte_base + ((j + 1) * element_size), array_byte_base + (j * element_size), element_size);
}
// insert the current element at the correct place
memcpy(array_byte_base + ((j + 1) * element_size), current, element_size);
}
// free the allocated space
free(current);
}
// Stuff that can't be included in vmallocator.h
std::locale SCP_default_locale("");
+18 -68
View File
@@ -31,23 +31,23 @@
#define GM_CAMPAIGN_MODE (1 << 10) // are we currently in a campaign.
#define GM_LAB (1 << 11) // We are currently in the F3 lab
#define VM_EXTERNAL (1 << 0) // Set if not viewing from player position.
#define VM_TRACK (1 << 1) // Set if viewer is tracking target.
#define VM_DEAD_VIEW (1 << 2) // Set if viewer is watching from dead view.
#define VM_CHASE (1 << 3) // Chase view.
#define VM_OTHER_SHIP (1 << 4) // View from another ship.
#define VM_CAMERA_LOCKED (1 << 5) // Set if player does not have control of the camera
#define VM_WARP_CHASE (1 << 6) // View while warping out (form normal view mode)
#define VM_PADLOCK_UP (1 << 7) // Set when player is looking up
#define VM_PADLOCK_REAR (1 << 8) // Set when player is looking behind
#define VM_PADLOCK_LEFT (1 << 9) // Set when player is looking left
#define VM_PADLOCK_RIGHT (1 << 10) // Set when player is looking right
#define VM_WARPIN_ANCHOR (1 << 11) // special warpin camera mode
#define VM_TOPDOWN (1 << 12) // Camera is looking down on ship
#define VM_FREECAMERA (1 << 13) // Camera is not attached to any particular object, probably under SEXP control
#define VM_CENTERING (1 << 14) // View is springing to center
#define VM_EXTERNAL (1 << 0) // Set if not viewing from player position.
#define VM_TRACK (1 << 1) // Set if viewer is tracking target.
#define VM_DEAD_VIEW (1 << 2) // Set if viewer is watching from dead view.
#define VM_CHASE (1 << 3) // Chase view.
#define VM_OTHER_SHIP (1 << 4) // View from another ship.
#define VM_CAMERA_LOCKED (1 << 5) // Set if player does not have control of the camera
#define VM_WARP_CHASE (1 << 6) // View while warping out (form normal view mode)
#define VM_PADLOCK_UP (1 << 7)
#define VM_PADLOCK_REAR (1 << 8)
#define VM_PADLOCK_LEFT (1 << 9)
#define VM_PADLOCK_RIGHT (1 << 10)
#define VM_WARPIN_ANCHOR (1 << 11) // special warpin camera mode
#define VM_TOPDOWN (1 << 12) //Camera is looking down on ship
#define VM_FREECAMERA (1 << 13) //Camera is not attached to any particular object, probably under SEXP control
#define VM_CENTERING (1 << 14) // View is springing to center
#define VM_PADLOCK_ANY (VM_PADLOCK_UP | VM_PADLOCK_REAR | VM_PADLOCK_LEFT | VM_PADLOCK_RIGHT)
#define VM_PADLOCK_ANY (VM_PADLOCK_UP|VM_PADLOCK_REAR|VM_PADLOCK_LEFT|VM_PADLOCK_RIGHT)
//-----Cutscene stuff
//No bars
@@ -94,6 +94,7 @@ extern int Game_mode;
#define SINGLEPLAYER !(Game_mode & GM_MULTIPLAYER)
extern int Viewer_mode;
extern int Rand_count;
extern int Game_restoring; // If set, this means we are restoring data from disk
@@ -213,58 +214,7 @@ int current_detail_level();
// Goober5000
// A sort for use with small or almost-sorted lists. Iteration time is O(n) for a fully-sorted list.
// This uses a type-safe version of the function prototype for stdlib's qsort, although the size is an int rather than a size_t (for the reasons that j is an int).
// The fncompare function should return <0, 0, or >0 as the left item is less than, equal to, or greater than the right item.
template <typename T>
void insertion_sort(T* array_base, int array_size, int (*fncompare)(const T*, const T*))
{
// NOTE: j *must* be a signed type because j reaches -1 and j+1 must be 0.
int i, j;
T *current, *current_buf;
void insertion_sort(void *array, size_t array_size, size_t element_size, int (*fncompare)(const void *, const void *));
// allocate space for the element being moved
// (Taylor says that for optimization purposes malloc/free should be used rather than vm_malloc/vm_free here)
current_buf = (T*)malloc(sizeof(T));
if (current_buf == nullptr)
{
UNREACHABLE("Malloc failed!");
return;
}
// loop
for (i = 1; i < array_size; i++)
{
// grab the current element
// this does a lazy move/copy because if the array is mostly sorted,
// there's no sense copying sorted items to their own places
bool lazily_copied = false;
current = &array_base[i];
// bump other elements toward the end of the array
for (j = i - 1; (j >= 0) && (fncompare(&array_base[j], current) > 0); j--)
{
if (!lazily_copied)
{
// this may look strange but it is just copying the data
// into the buffer, then pointing to the buffer
*current_buf = std::move(*current);
current = current_buf;
lazily_copied = true;
}
array_base[j + 1] = std::move(array_base[j]);
}
if (lazily_copied)
{
// insert the current element at the correct place
array_base[j + 1] = std::move(*current);
}
}
// free the allocated space
free(current_buf);
}
#endif
+1 -1
View File
@@ -67,7 +67,7 @@
#define USED_VARIABLE __attribute__((used))
#if __GNUC__ >= 7
#if SCP_COMPILER_VERSION_MAJOR >= 7
#define FALLTHROUGH [[fallthrough]]
#else
#define FALLTHROUGH
+1 -1
View File
@@ -64,7 +64,7 @@
#define USED_VARIABLE __attribute__((used))
#if __GNUC__ >= 7
#if SCP_COMPILER_VERSION_MAJOR >= 7
#define FALLTHROUGH __attribute__((fallthrough))
#else
#define FALLTHROUGH
Loaded 100 of 713 files, more files were not shown because too many files have changed in this diff. Show more