Compare commits

..
Author SHA1 Message Date
asarium 1588644fec Add variables concept to expressions
This also includes the necessary setup work for using "position" and
"direction" in action expressions.
2021-01-10 22:47:49 +01:00
asarium 0abd592038 Rework actions to use definitions
This introduces action definitions which can then be instantiated to
form actual actions with expressions as their parameters.
2021-01-10 12:22:00 +01:00
asarium 909e799bdd Allow implicit conversion from int to float
This is generally a safe operation and will make the system a bit easier
to use.
2021-01-07 21:36:28 +01:00
asarium 5a9f181aff Rework function handling 2021-01-07 13:34:10 +01:00
asarium b0ba22a43d Add Action expressions
This is a parser for a very simple language to allow for execution of
dynamic expressions with return values for use as action parameters. The
only "dynamic" part this currently supports are random ranges but more
features are planned for the future.
2021-01-06 19:13:12 +01:00
asarium c89faebe4e Update GTest 2021-01-06 18:49:48 +01:00
asarium 059e149590 Add dynamic action programming system
This system adds the code necessary for letting content creators specify
complex events by specifying a simple, program-like sequence of
operations that will be executed by the engine when a certain event is
triggered.
2021-01-05 18:26:10 +01:00
799 changed files with 28123 additions and 87072 deletions

No files matched your search

-6
View File
@@ -1,14 +1,8 @@
cmake/ @asarium
ci/ @asarium @z64555
code/executor/ @asarium
code/scpui/ @asarium
code/scripting/ @asarium
code/controlconfig/ @z64555
code/model/modelanimation*.* @bmagnu
+1 -96
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-a8ca321
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -29,8 +29,6 @@ jobs:
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
@@ -210,96 +208,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 -106
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,65 +23,41 @@ 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-a8ca321
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 }}/..
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
setup-python: 'false'
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 +82,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 +95,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 +139,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 +158,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 +202,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 +243,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 }}
-53
View File
@@ -1,53 +0,0 @@
name: Publish Standalone docker image
on:
push:
tags:
- 'nightly_*'
- 'release_*'
jobs:
build_images:
if: github.repository == 'scp-fs2open/fs2open.github.com'
strategy:
matrix:
configuration: [FastDebug, Release]
include:
- configuration: FastDebug
tag_suffix: -debug
- configuration: Release
tag_suffix: ""
fail-fast: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
name: Checkout
with:
submodules: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: ghcr.io/scp-fs2open/standalone_server
tag-latest: false
- name: Login to GitHub Docker Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
file: ./docker/standalone/Dockerfile
push: true
tags: ${{ steps.docker_meta.outputs.tags }}${{ matrix.tag_suffix }}
labels: ${{ steps.docker_meta.outputs.labels }}
build-args: |
build_type=${{ matrix.configuration }}
+1 -95
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-a8ca321
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -29,8 +29,6 @@ jobs:
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
@@ -224,95 +222,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
@@ -8,6 +8,7 @@ env:
jobs:
build_linux:
strategy:
fail-fast: false
matrix:
configuration: [Debug, Release]
compiler: [gcc-5, gcc-10, clang-9]
@@ -22,7 +23,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-e977d36
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -37,18 +38,12 @@ jobs:
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
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
@@ -0,0 +1,48 @@
name: Publish Standalone docker image
on:
push:
tags:
- '*'
jobs:
build_images:
strategy:
matrix:
configuration: [FastDebug, Release]
include:
- configuration: FastDebug
latest_tag: latest_debug
nightly_tag: nightly_debug
release_tag: release_debug
- configuration: Release
latest_tag: latest
nightly_tag: nightly
release_tag: release
fail-fast: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
name: Checkout
with:
submodules: true
- name: Docker login
run: echo ${{ secrets.DOCKER_ACCESSTOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
- name: Docker build
run: docker build --build-arg build_type=${{ matrix.configuration }} -f docker/standalone/Dockerfile -t ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }} .
- name: Push latest tag
run: |
docker push ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }}
- name: Push nightly tag
if: contains(github.ref, 'refs/tags/nightly_')
run: |
docker tag ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }} ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.nightly_tag }}
docker push ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.nightly_tag }}
- name: Push release tag
if: contains(github.ref, 'refs/tags/release_')
run: |
docker tag ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }} ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.release_tag }}
docker push ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.release_tag }}
+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 20)
set_if_not_defined(FSO_VERSION_MINOR 1)
set_if_not_defined(FSO_VERSION_BUILD 0)
-6
View File
@@ -26,7 +26,6 @@ TARGET_LINK_LIBRARIES(code PUBLIC openal)
TARGET_LINK_LIBRARIES(code PUBLIC ${LUA_LIBS})
TARGET_LINK_LIBRARIES(code PUBLIC ${PNG_LIBS})
TARGET_LINK_LIBRARIES(code PUBLIC ${JPEG_LIBS})
TARGET_LINK_LIBRARIES(code PUBLIC lz4)
TARGET_LINK_LIBRARIES(code PUBLIC sdl2)
@@ -66,8 +65,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 +103,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)
-1
View File
@@ -7,7 +7,6 @@ ActionDefinition::ActionDefinition(SCP_string name, SCP_vector<ActionParameter>
: m_name(std::move(name)), m_parameters(std::move(parameters))
{
}
ActionDefinition::~ActionDefinition() = default;
const SCP_string& ActionDefinition::getName() const
{
-1
View File
@@ -15,7 +15,6 @@ struct ActionParameter {
class ActionDefinition {
public:
ActionDefinition(SCP_string name, SCP_vector<ActionParameter> parameters);
virtual ~ActionDefinition();
const SCP_string& getName() const;
const SCP_vector<actions::ActionParameter>& getParameters() const;
+18 -15
View File
@@ -47,7 +47,24 @@ void parseActionParameters(const SCP_vector<ActionParameter>& params,
const ActionDefinitionManager& ActionDefinitionManager::instance()
{
static const ActionDefinitionManager manager;
static const ActionDefinitionManager manager = []() {
ActionDefinitionManager newManager;
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::MoveToSubmodel>("Move To Subobject")));
newManager.addDefinition(std::unique_ptr<ActionDefinition>(
new BuiltinActionDefinition<types::ParticleEffectAction>("Start Particle Effect")));
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::PlaySoundAction>("Play 3D Sound")));
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetDirectionAction>("Set Direction")));
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetPositionAction>("Set Position")));
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::WaitAction>("Wait")));
return newManager;
}();
return manager;
}
void ActionDefinitionManager::addDefinition(std::unique_ptr<ActionDefinition> def)
@@ -127,19 +144,5 @@ std::unique_ptr<Action> ActionDefinitionManager::parseAction(const flagset<Progr
return std::unique_ptr<Action>();
}
ActionDefinitionManager::ActionDefinitionManager()
{
addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::MoveToSubmodel>("Move To Subobject")));
addDefinition(std::unique_ptr<ActionDefinition>(
new BuiltinActionDefinition<types::ParticleEffectAction>("Start Particle Effect")));
addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::PlaySoundAction>("Play 3D Sound")));
addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetDirectionAction>("Set Direction")));
addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetPositionAction>("Set Position")));
addDefinition(std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::WaitAction>("Wait")));
}
} // namespace actions
+1 -1
View File
@@ -19,7 +19,7 @@ class ActionDefinitionManager {
static const ActionDefinitionManager& instance();
private:
ActionDefinitionManager();
ActionDefinitionManager() = default;
void addDefinition(std::unique_ptr<ActionDefinition> def);
+2
View File
@@ -0,0 +1,2 @@
#include "BuiltinActionDefinition.h"
-1
View File
@@ -15,7 +15,6 @@ class BuiltinActionDefinition : public ActionDefinition {
: ActionDefinition(std::move(name), {{"parameter", expression::ValueTypeTraits<ActionValueType>::type}})
{
}
~BuiltinActionDefinition() override = default;
flagset<ProgramContextFlags> getRequiredContext() const override
{
+2 -2
View File
@@ -123,9 +123,9 @@ Program& Program::operator=(const Program& other)
Program& Program::operator=(Program&&) noexcept = default;
Program::Program(Program&&) noexcept = default;
bool ProgramSet::isEmpty() const
const SCP_vector<actions::Program>& ProgramSet::getPrograms() const
{
return _programs.empty();
return _programs;
}
ProgramSet::ProgramSet() = default;
+1 -1
View File
@@ -84,7 +84,7 @@ class ProgramSet {
ProgramSet();
explicit ProgramSet(const flagset<ProgramContextFlags>& context_flags);
bool isEmpty() const;
const SCP_vector<actions::Program>& getPrograms() const;
/**
* @brief Starts the execution of all programs within this set with the specified parameters
@@ -14,12 +14,6 @@ class AbstractExpression;
template <typename T>
class TypedActionExpression;
/**
* @brief An expression that can be executed to compute a value at runtime
*
* This is the basis of having dynamic values in actions by allowing mods to specify complex expressions that make use
* of variables and functions for computing their values at runtime.
*/
class ActionExpression {
public:
ActionExpression() = default;
@@ -42,13 +36,6 @@ class ActionExpression {
std::shared_ptr<nodes::AbstractExpression> m_expression;
};
/**
* @brief An action expression with a specific type specified at compile time
*
* This is most useful for built-in actions that have the required type fixed at compile time.
*
* @tparam T The type of the expression value.
*/
template <typename T>
class TypedActionExpression {
public:
+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 {
+14 -11
View File
@@ -22,7 +22,19 @@ Value value_minus(const SCP_vector<Value>& parameters)
const FunctionManager& FunctionManager::instance()
{
static const FunctionManager manager;
static const FunctionManager manager = []() {
FunctionManager newManager;
newManager.addOperator("+", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_plus<int>);
newManager.addOperator("+", {ValueType::Float, ValueType::Float}, ValueType::Float, value_plus<float>);
newManager.addOperator("+", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_plus<vec3d>);
newManager.addOperator("-", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_minus<int>);
newManager.addOperator("-", {ValueType::Float, ValueType::Float}, ValueType::Float, value_minus<float>);
newManager.addOperator("-", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_minus<vec3d>);
return newManager;
}();
return manager;
}
void FunctionManager::addOperator(const SCP_string& name,
@@ -75,16 +87,7 @@ const FunctionDefinition* FunctionManager::findOperator(const SCP_string& name,
return nullptr;
}
FunctionManager::FunctionManager()
{
addOperator("+", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_plus<int>);
addOperator("+", {ValueType::Float, ValueType::Float}, ValueType::Float, value_plus<float>);
addOperator("+", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_plus<vec3d>);
addOperator("-", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_minus<int>);
addOperator("-", {ValueType::Float, ValueType::Float}, ValueType::Float, value_minus<float>);
addOperator("-", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_minus<vec3d>);
}
FunctionManager::FunctionManager() = default;
} // namespace expression
} // namespace actions
@@ -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;
+7 -27
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;
@@ -577,7 +561,7 @@ const char *ai_get_goal_target_name(const char *name, int *index);
void ai_clear_goal_target_names();
extern void init_ai_system(void);
extern void ai_attack_object(object *attacker, object *attacked, int ship_info_index = -1);
extern void ai_attack_object(object *attacker, object *attacked, ship_subsys *ssp, int ship_info_index = -1);
extern void ai_evade_object(object *evader, object *evaded);
extern void ai_ignore_object(object *ignorer, object *ignored, int ignore_new);
extern void ai_ignore_wing(object *ignorer, int wingnum);
@@ -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
+2 -14
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)
@@ -130,21 +130,9 @@ namespace AI {
Use_only_single_fov_for_turrets,
No_turning_directional_bias,
Use_axial_turnrate_differences,
all_nonshielded_ships_can_manage_ets,
fightercraft_nonshielded_ships_can_manage_ets,
nonshielded_ships_can_manage_ets,
Better_collision_avoidance,
Require_exact_los,
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
};
+9 -95
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);
}
@@ -541,66 +520,15 @@ void parse_ai_profiles_tbl(const char *filename)
profile->second_order_lead_predict_factor = 0;
}
}
if (optional_string("$ships with no shields can manage ETS:"))
{
mprintf(("Warning: \"$ships with no shields can manage ETS\" flag is deprecated in favor of \"$any ship with no shields can manage ETS\"\n"));
bool temp;
stuff_boolean(&temp);
profile->flags.set(AI::Profile_Flags::all_nonshielded_ships_can_manage_ets, temp);
}
set_flag(profile, "$no directional bias for missile and ship turning:", AI::Profile_Flags::No_turning_directional_bias);
set_flag(profile, "$respect ship axial turnrate differences:", AI::Profile_Flags::Use_axial_turnrate_differences);
set_flag(profile, "$any ship with no shields can manage ETS:", AI::Profile_Flags::all_nonshielded_ships_can_manage_ets);
set_flag(profile, "$fighters/bombers with no shields can manage ETS:", AI::Profile_Flags::fightercraft_nonshielded_ships_can_manage_ets);
set_flag(profile, "$ships with no shields can manage ETS:", AI::Profile_Flags::nonshielded_ships_can_manage_ets);
set_flag(profile, "$better combat collision avoidance for fightercraft:", AI::Profile_Flags::Better_collision_avoidance);
set_flag(profile, "$improved missile avoidance for fightercraft:", AI::Profile_Flags::Improved_missile_avoidance);
set_flag(profile, "$friendly ships use AI profile countermeasure chance:", AI::Profile_Flags::Friendlies_use_countermeasure_firechance);
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 +600,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;
@@ -750,22 +676,10 @@ void ai_profile_t::reset()
flags.set(AI::Profile_Flags::Fix_ai_path_order_bug);
flags.set(AI::Profile_Flags::Aspect_invulnerability_fix);
flags.set(AI::Profile_Flags::Use_actual_primary_range);
flags.set(AI::Profile_Flags::fightercraft_nonshielded_ships_can_manage_ets);
flags.set(AI::Profile_Flags::nonshielded_ships_can_manage_ets);
}
// this flag has been enabled ever since 3.7.2
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();
};
+19 -168
View File
@@ -17,13 +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"
#ifdef _MSC_VER
#pragma optimize("", off)
#pragma auto_inline(off)
@@ -407,7 +406,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);
@@ -530,120 +529,6 @@ int ai_big_maybe_follow_subsys_path(int do_dot_check)
return 0;
}
bool ai_new_maybe_reposition_attack_subsys() {
ai_info* aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
if (aip->target_objnum < 0 || aip->targeted_subsys == nullptr)
return false;
object* target_objp = &Objects[aip->target_objnum];
// dont bother with this if you're still far away
if (vm_vec_dist(&Pl_objp->pos, &target_objp->pos) > target_objp->radius * 2.0f + 2000.0f)
return false;
vec3d geye, gsubpos;
eye* ep;
polymodel* pm = model_get(Ship_info[Ships[Pl_objp->instance].ship_info_index].model_num);
// 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);
// get world pos of subsystem
vm_vec_unrotate(&gsubpos, &aip->targeted_subsys->system_info->pnt, &En_objp->orient);
vm_vec_add2(&gsubpos, &En_objp->pos);
// you're in sight! shoot it!
if (ship_subsystem_in_sight(En_objp, aip->targeted_subsys, &geye, &gsubpos, 0))
return false;
// not in sight, gotta get there
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;
good_pos = &world_goal_pos;
}
}
else
good_pos = &world_goal_pos;
aip->next_dynamic_path_check_time = timestamp( AI_DYNAMIC_PATH_RECHECK_DELAY );
}
else{ //Not enough time has passed till recheck.
good_pos = &aip->last_dynamic_path_goal;
}
ai_turn_towards_vector(good_pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
float subsys_distance = vm_vec_dist(&Pl_objp->pos, &gsubpos);
// full speed until you're close, then slow down a bit
accelerate_ship(aip, subsys_distance > 200.0f ? 1.0f : subsys_distance / 200.0f);
if (!(Ships[Pl_objp->instance].flags[Ship::Ship_Flags::Afterburner_locked])) {
if (subsys_distance > 300.0f) {
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
afterburners_start(Pl_objp);
aip->afterburner_stop_time = Missiontime + 2 * F1_0;
}
}
else
afterburners_stop(Pl_objp);
}
return true;
}
// This function is only called from ai_big_chase_attack() when a ship is flying very slowly and
// attacking a big ship. The ship should scan for enemy fighter/bombers... if any are close, then
// return 1, otherwise return 0;
@@ -752,10 +637,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
}
// see if Pl_objp needs to reposition to get a good shot at subsystem which is being attacked
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
if (ai_new_maybe_reposition_attack_subsys())
return;
} else if ( ai_big_maybe_follow_subsys_path() ) {
if ( ai_big_maybe_follow_subsys_path() ) {
if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze])) {
afterburners_stop(Pl_objp);
}
@@ -785,28 +667,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 +699,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 +716,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);
@@ -888,14 +757,7 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
swp = &Ships[Pl_objp->instance].weapons;
float target_radius = En_objp->radius;
// normal subsystem attacking uses whether or not its on a path and skips this function entirely to stop "over-eager" firing
// since the improved method doesnt use paths, refine the target radius to stop over-eager shooting
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing] && aip->targeted_subsys != nullptr)
target_radius = aip->targeted_subsys->system_info->radius;
if (dot_to_enemy > 0.95f - 0.5f * target_radius/MAX(1.0f, target_radius + dist_to_enemy)) {
if (dot_to_enemy > 0.95f - 0.5f * En_objp->radius/MAX(1.0f, En_objp->radius + dist_to_enemy)) {
aip->time_enemy_in_range += flFrametime;
// Chance of hitting ship is based on dot product of firing ship's forward vector with vector to ship
@@ -935,7 +797,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 +827,10 @@ 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();
}
} else {
if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
@@ -978,11 +839,10 @@ 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->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
@@ -1179,10 +1039,7 @@ void ai_big_chase()
float dist_normal_to_enemy;
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
else // I'm fairly sure this retail code VVV meant to use / instead of *
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
} else {
// don;t have normal so use a conservative value here
dist_normal_to_enemy = 0.3f * dist_to_enemy;
@@ -1459,17 +1316,13 @@ void ai_big_strafe_attack()
ai_info *aip;
vec3d target_pos;
vec3d rand_vec;
float accel, t;
float target_dist = 0.0f;
float target_dot = 0.0f;
float target_dist, target_dot, accel, t;
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
if (ai_new_maybe_reposition_attack_subsys())
return;
} else if (ai_big_maybe_follow_subsys_path(0))
if ( ai_big_maybe_follow_subsys_path(0) ) {
return;
}
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
if ( ai_big_strafe_maybe_retreat(&target_pos) )
@@ -1492,7 +1345,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;
@@ -1586,11 +1439,9 @@ void ai_big_strafe_glide_attack()
flight_dot_to_enemy = vm_vec_dot(&target_objp->pos, &norm_vel_vec); //Angle between flight path and target ship
//If we're following a path, then we shouldn't be doing glide strafe
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
if (ai_new_maybe_reposition_attack_subsys())
return;
} else if (ai_big_maybe_follow_subsys_path(0))
if ( ai_big_maybe_follow_subsys_path(0) ) {
return;
}
//Gets a point on the target ship to attack, as well as distance and the angle between the nose of the attacker and that point.
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
@@ -1724,7 +1575,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;
+624 -1103
View File
File diff suppressed because it is too large. Load diff
+92 -147
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
}
@@ -2198,14 +2147,14 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
// Mike -- debug code!
// If a ship has a subobject on it, attack that instead of the main ship!
ai_attack_object( objp, other_obj);
ai_attack_object( objp, other_obj, NULL);
break;
case AI_GOAL_CHASE_WEAPON:
Assert( Weapons[current_goal->target_instance].objnum >= 0 );
other_obj = &Objects[Weapons[current_goal->target_instance].objnum];
Assert( other_obj->signature == current_goal->target_signature );
ai_attack_object( objp, other_obj);
ai_attack_object( objp, other_obj, NULL );
break;
case AI_GOAL_GUARD:
@@ -2330,7 +2279,7 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
shipnum = ship_name_lookup( current_goal->target_name );
Assert( shipnum >= 0 );
other_obj = &Objects[Ships[shipnum].objnum];
ai_attack_object( objp, other_obj);
ai_attack_object( objp, other_obj, NULL);
ai_set_attack_subsystem( objp, current_goal->ai_submode ); // submode stored the subsystem type
if (current_goal->ai_mode != AI_GOAL_DESTROY_SUBSYSTEM) {
if (aip->target_objnum != -1) {
@@ -2352,14 +2301,14 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
break;
case AI_GOAL_CHASE_ANY:
ai_attack_object( objp, nullptr);
ai_attack_object( objp, NULL, NULL );
break;
// chase-ship-class is chase-any but restricted to a subset of ships
case AI_GOAL_CHASE_SHIP_CLASS:
shipnum = ship_info_lookup( current_goal->target_name );
Assertion( shipnum >= 0, "The target of AI_GOAL_CHASE_SHIP_CLASS must refer to a valid ship class!" );
ai_attack_object( objp, nullptr, shipnum );
ai_attack_object( objp, NULL, NULL, shipnum );
break;
case AI_GOAL_WARP: {
@@ -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);
+211 -211
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"
@@ -88,18 +87,6 @@ typedef struct eval_enemy_obj_struct {
int nearest_objnum;
} eval_enemy_obj_struct;
// the current world orientation of the turret matrix, corresponding to its fvec and uvec defined in the model
// is NOT affected by the turret's current aiming
void turret_instance_find_world_orient(matrix* out_mat, int model_instance_num, int submodel_num, const matrix* objorient)
{
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);
vm_vector_2_matrix_norm(out_mat, &fvec, &uvec);
}
/**
* Is object in turret field of view?
*
@@ -111,53 +98,117 @@ void turret_instance_find_world_orient(matrix* out_mat, int model_instance_num,
*
* @return 1 if objp is in fov of the specified turret. Otherwise return 0.
*/
bool object_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec, vec3d *tpos, float dist)
int object_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec, vec3d *tpos, float dist)
{
vec3d v2e;
float size_mod;
bool in_fov = false;
if (ss->flags[Ship::Subsystem_Flags::FOV_edge_check]) {
int model_num;
switch (objp->type) {
case OBJ_SHIP:
model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num;
break;
case OBJ_ASTEROID:
model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype];
break;
default:
vm_vec_normalized_dir(&v2e, &objp->pos, tpos);
size_mod = objp->radius / (dist + objp->radius);
in_fov = turret_fov_test(ss, tvec, &v2e, size_mod);
return in_fov;
}
auto pm = model_get(model_num);
for (int i = 0; i < 8; i++) {
vec3d bbox_point;
vm_vec_unrotate(&bbox_point, &pm->bounding_box[i], &objp->orient);
bbox_point += objp->pos;
vm_vec_normalized_dir(&v2e, &bbox_point, tpos);
in_fov = turret_fov_test(ss, tvec, &v2e, -0.2f);
if (in_fov)
return true;
}
}
// if the bbox points method didn't work (or fov_edge_checks isn't on)
// try the normal method
bool in_fov;
vm_vec_normalized_dir(&v2e, &objp->pos, tpos);
size_mod = objp->radius / (dist + objp->radius);
in_fov = turret_fov_test(ss, tvec, &v2e, size_mod);
if ( in_fov ) {
return 1;
}
return in_fov;
return 0;
}
bool is_object_radius_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec, vec3d *tpos, vec3d *v2e, vec3d *predicted_pos, float distance)
{
float target_dist = distance;
if (distance == 0.0f)
target_dist = vm_vec_dist(predicted_pos,tpos);
if (object_in_turret_fov(objp, ss, tvec, tpos, target_dist + objp->radius)) {
// so the targeted spot in not in fov but the enemy + radius is
// lets align the darn gun and try shooting there
vec3d temp_vec;
float multiplier = 0;
model_subsystem *tp = ss->system_info;
// project v2e_from_turret to turret normal
// substract resultant vector from the temp_vec (v2e_from_turret)
// adjust z component as necessary
// calculate multiplier for the resultant vector
// use multiplier and the z component and compose a new vector
float dot = vm_vec_dot(v2e, tvec);
vm_vec_scale_add(&temp_vec, v2e, tvec, -dot);
if (IS_VEC_NULL_SQ_SAFE(&temp_vec)) {
// return false, target is perfectly aligned over or below the turret
// safe bet is to allow turret to reacquire better target
return false;
}
// normalize the vec, it needs to be done regardless
vm_vec_normalize(&temp_vec);
bool fix_elevation = false;
bool fix_base_rot = false;
if (dot < tp->turret_fov) {
dot = tp->turret_fov;
fix_elevation = true;
}
if (dot > tp->turret_max_fov) {
dot = tp->turret_max_fov;
fix_elevation = true;
}
if (tp->flags[Model::Subsystem_Flags::Turret_alt_math]) {
vec3d temp_vec2;
vm_vec_rotate(&temp_vec2, &temp_vec, &ss->world_to_turret_matrix);
// now in turrets frame of reference
// check if math is actually possible
if (!((temp_vec2.xyz.x == 0) && (temp_vec2.xyz.y == 0))) {
float temp_z = temp_vec2.xyz.z;
temp_vec2.xyz.z = 0.0f;
// make sure null vecs wont happen
if (!IS_VEC_NULL_SQ_SAFE(&temp_vec2)) {
vm_vec_normalize(&temp_vec2);
// only do this if it actually is required
if (-temp_vec2.xyz.y < tp->turret_y_fov) {
float check_pos = 1;
fix_base_rot = true;
temp_vec2.xyz.y = -tp->turret_y_fov;
if (temp_vec2.xyz.x < 0)
check_pos = -1;
temp_vec2.xyz.x = check_pos * sqrtf(1 - (temp_vec2.xyz.y*temp_vec2.xyz.y));
// restore z component
float scaler = sqrtf(1 - (temp_z*temp_z));
vm_vec_scale(&temp_vec2,scaler);
temp_vec2.xyz.z = temp_z;
// back to world frame
vm_vec_unrotate(&temp_vec, &temp_vec2, &ss->world_to_turret_matrix);
}
}
}
}
if (fix_elevation || fix_base_rot) {
if (fix_elevation) {
multiplier = sqrtf(1 - (dot*dot));
// keep the temp_vec scaled with the tweaked vector
vm_vec_scale(&temp_vec, multiplier);
}
vm_vec_scale_add(v2e, &temp_vec, tvec, dot);
// and we are done with v2e...
vm_vec_scale_add(predicted_pos, tpos, v2e, target_dist);
// and we are done with predicted position
return true;
} else {
mprintf(("Warning: Function 'is_object_radius_in_turret_fov' was called\nwithout need to fix turret alignments\n"));
return false;
}
}
// outside of the expanded radii, unable to align, return false
return false;
}
/**
@@ -434,7 +485,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;
}
@@ -504,7 +555,6 @@ void evaluate_obj_as_target(object *objp, eval_enemy_obj_struct *eeo)
if ( objp->type == OBJ_SHIP ) {
shipp = &Ships[objp->instance];
ship_info* sip = &Ship_info[shipp->ship_info_index];
// check on enemy team
if ( !iff_matches_mask(shipp->team, eeo->enemy_team_mask) ) {
@@ -544,18 +594,16 @@ void evaluate_obj_as_target(object *objp, eval_enemy_obj_struct *eeo)
}
}
// don't shoot at big ships with huge weapons unless they have the flag
// don't shoot at small ships if we shouldn't
if (eeo->eeo_flags & EEOF_BIG_ONLY) {
if (sip->class_type == -1 || !(Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
if (!(Ship_info[shipp->ship_info_index].is_big_or_huge())) {
return;
}
}
// ^ Note the difference between these checks
// V "small only" EXCLUDES only big ships, "huge" INCLUDES only big ships
// don't shoot at ships ignored by small weapons if this is a small weapon
// don't shoot at big ships if we shouldn't
if (eeo->eeo_flags & EEOF_SMALL_ONLY) {
if (sip->class_type >= 0 && (Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
if ((Ship_info[shipp->ship_info_index].is_big_or_huge())) {
return;
}
}
@@ -1131,18 +1179,23 @@ int find_turret_enemy(ship_subsys *turret_subsys, int objnum, vec3d *tpos, vec3d
if (!skip) {
if ( Objects[aip->target_objnum].type == OBJ_SHIP ) {
ship_info* esip = &Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index];
// check for huge weapon and huge ship
if ( !big_only_flag || (esip->class_type >= 0 && Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only]) ) {
if ( !big_only_flag || (Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index].is_big_or_huge()) ) {
// check for tagged only and tagged ship
if ( tagged_only_flag && ship_is_tagged(&Objects[aip->target_objnum]) ) {
// select new target if aip->target_objnum is out of field of view
vec3d v2e;
float dist;
bool in_fov;
vm_vec_normalized_dir(&v2e, &Objects[aip->target_objnum].pos, tpos);
dist = vm_vec_normalized_dir(&v2e, &Objects[aip->target_objnum].pos, tpos);
in_fov = turret_fov_test(turret_subsys, tvec, &v2e);
if (turret_subsys->flags[Ship::Subsystem_Flags::FOV_edge_check]) {
if (in_fov == false)
if (object_in_turret_fov(&Objects[aip->target_objnum], turret_subsys, tvec, tpos, dist + Objects[aip->target_objnum].radius))
in_fov = true;
}
// MODIFY FOR ATTACKING BIG SHIP
// dot += (0.5f * Objects[aip->target_objnum].radius / dist);
if (in_fov) {
@@ -1177,22 +1230,20 @@ int find_turret_enemy(ship_subsys *turret_subsys, int objnum, vec3d *tpos, vec3d
* of the turret.
*
* @param objp Pointer to object
* @param tp Turret model system on that object
* @param tp Turrent model system on that object
* @param gpos [Output] Global absolute position of gun firing point
* @param gvec [Output] Global vector
*
* @note The gun normal is the unrotated gun normal, (the center of the FOV cone), not
* the actual gun normal given using the current turret heading. But it _is_ rotated into the model's orientation
* in global space.
* @note2 Because of this, both single-part and multi-part turrets are treated the same way; no need to find the multi-part's gun submodel.
*/
void ship_get_global_turret_info(const object *objp, const model_subsystem *tp, vec3d *gpos, vec3d *gvec)
void ship_get_global_turret_info(object *objp, model_subsystem *tp, vec3d *gpos, vec3d *gvec)
{
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);
if (gvec)
model_instance_local_to_global_dir(gvec, &tp->turret_norm, model_instance_num, tp->subobj_num, &objp->orient, true);
// vm_vec_unrotate(gpos, &tp->turret_avg_firing_point, &objp->orient);
vm_vec_unrotate(gpos, &tp->pnt, &objp->orient);
vm_vec_add2(gpos, &objp->pos);
vm_vec_unrotate(gvec, &tp->turret_norm, &objp->orient);
}
void turret_ai_update_aim(ai_info *aip, object *En_Objp, ship_subsys *ss);
@@ -1220,15 +1271,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];
@@ -1252,8 +1303,7 @@ void ship_get_global_turret_gun_info(object *objp, ship_subsys *ssp, vec3d *gpos
vm_vec_add2(&enemy_point, &ssp->last_aim_enemy_pos);
} 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);
vm_vec_unrotate(&turret_norm, &tp->turret_norm, &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 {
@@ -1384,13 +1434,18 @@ int aifft_rotate_turret(object *objp, ship *shipp, ship_subsys *ss, object *lep,
in_fov = turret_fov_test(ss, gvec, &v2e);
if (ss->flags[Ship::Subsystem_Flags::FOV_edge_check]) {
if (in_fov == false)
in_fov = is_object_radius_in_turret_fov(&Objects[ss->turret_enemy_objnum], ss, gvec, &gun_pos, &v2e, predicted_enemy_pos, 0.0f);
}
if (in_fov) {
ret_val = model_rotate_gun(objp, pm, pmi, ss, predicted_enemy_pos);
ret_val = model_rotate_gun(objp, pm, pmi, tp, predicted_enemy_pos);
} else if ((tp->flags[Model::Subsystem_Flags::Turret_reset_idle]) &&(timestamp_elapsed(ss->rotation_timestamp))) {
ret_val = model_rotate_gun(objp, pm, pmi, ss, predicted_enemy_pos, true);
ret_val = model_rotate_gun(objp, pm, pmi, tp, predicted_enemy_pos, true);
}
} else if ((ss->system_info->flags[Model::Subsystem_Flags::Turret_reset_idle]) && (timestamp_elapsed(ss->rotation_timestamp))) {
ret_val = model_rotate_gun(objp, pm, pmi, ss, predicted_enemy_pos, true);
ret_val = model_rotate_gun(objp, pm, pmi, ss->system_info, predicted_enemy_pos, true);
}
// by default "ret_val" should be set to 1 for multi-part turrets, and 0 for single-part turrets
@@ -1422,7 +1477,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);
vm_vec_unrotate(&turret_norm, &turret_subsysp->system_info->turret_norm, &objp->orient);
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]) {
@@ -1492,7 +1547,8 @@ ship_subsys *aifft_find_turret_subsys(object *objp, ship_subsys *ssp, object *en
// Compute absolute gun position.
vec3d abs_gun_pos;
ship_get_global_turret_info(objp, ssp->system_info, &abs_gun_pos, nullptr);
vm_vec_unrotate(&abs_gun_pos, &ssp->system_info->pnt, &objp->orient);
vm_vec_add2(&abs_gun_pos, &objp->pos);
// Only pick a turret to attack on large ships.
if (!esip->is_big_or_huge())
@@ -1605,10 +1661,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 +1672,10 @@ 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);
} else {
turret->weapons.burst_counter[weapon_num] = 0;
turret->weapons.burst_seed[weapon_num] = Random::next();
}
}
@@ -1634,7 +1686,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
@@ -1717,7 +1769,7 @@ void turret_update_enemy_in_range(ship_subsys *turret, float seconds)
/**
* Fire a weapon from a turret
*/
bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, vec3d *turret_pos, vec3d *firing_vec, vec3d *predicted_pos = nullptr, float flak_range_override = 100.0f)
bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, vec3d *turret_pos, vec3d *turret_fvec, vec3d *predicted_pos = NULL, float flak_range_override = 100.0f)
{
matrix turret_orient;
int weapon_objnum;
@@ -1782,27 +1834,8 @@ 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;
if (turret->turret_inaccuracy > 0.0f)
vm_vec_random_cone(firing_vec, firing_vec, turret->turret_inaccuracy);
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;
}
vm_vector_2_matrix(&turret_orient, turret_fvec, NULL, NULL);
turret->turret_last_fire_direction = *turret_fvec;
// set next fire timestamp for the turret
if (last_shot_in_salvo)
@@ -1820,18 +1853,15 @@ 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 +1872,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
@@ -1854,11 +1882,12 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
}
// don't fire swam, but set up swarm info instead
else if ((wip->wi_flags[Weapon::Info_Flags::Swarm]) || (wip->wi_flags[Weapon::Info_Flags::Corkscrew])) {
ship_weapon *swp = &turret->weapons;
if (swp->current_secondary_bank < 0) {
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) {
@@ -1879,6 +1908,8 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
else {
for (int i = 0; i < wip->shots; i++) {
if (turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) {
ship_weapon *swp;
swp = &turret->weapons;
int bank_to_fire, num_slots = turret->system_info->turret_num_firing_points;
if (wip->subtype == WP_LASER) {
int points;
@@ -1906,10 +1937,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 +1969,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++;
@@ -1967,7 +1989,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
}
// zookeeper - Firepoints should cycle normally between shots,
// so we need to get the position info separately for each shot
ship_get_global_turret_gun_info(&Objects[parent_objnum], turret, turret_pos, firing_vec, 1, nullptr);
ship_get_global_turret_gun_info(&Objects[parent_objnum], turret, turret_pos, turret_fvec, 1, NULL);
weapon_objnum = weapon_create( turret_pos, &turret_orient, turret_weapon_class, parent_objnum, -1, 1, 0,0.0f, turret);
weapon_set_tracking_info(weapon_objnum, parent_objnum, turret->turret_enemy_objnum, 1, turret->targeted_subsys);
@@ -1985,14 +2007,14 @@ 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]) {
if (wip->wi_flags[Weapon::Info_Flags::Flak]) {
// show a muzzle flash
flak_muzzle_flash(turret_pos, turret_fvec, &Objects[parent_ship->objnum].phys_info, turret_weapon_class);
if(predicted_pos != NULL)
{
@@ -2007,17 +2029,16 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
flak_set_range(objp, flak_range_override);
}
}
// do mflash if the weapon has it
if (wip->muzzle_flash >= 0) {
mflash_create(turret_pos, firing_vec, &Objects[parent_ship->objnum].phys_info, wip->muzzle_flash);
// otherwise just do mflash if the weapon has it
else if (wip->muzzle_flash >= 0) {
mflash_create(turret_pos, turret_fvec, &Objects[parent_ship->objnum].phys_info, wip->muzzle_flash);
}
// in multiplayer (and the master), then send a turret fired packet.
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 +2073,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 +2124,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 +2145,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 +2157,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;
@@ -2218,8 +2235,6 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
{
ss->turret_enemy_objnum = -1;
lep = nullptr;
ss->flags.remove(Ship::Subsystem_Flags::Forced_target);
ss->flags.remove(Ship::Subsystem_Flags::Forced_subsys_target);
}
Assert((shipp->objnum >= 0) && (shipp->objnum < MAX_OBJECTS));
@@ -2227,10 +2242,25 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
objp = &Objects[shipp->objnum];
Assert(objp->type == OBJ_SHIP);
// Wanderer - make sure turrets already have all the data
if ( !(tp->flags[Model::Subsystem_Flags::Turret_matrix]) )
{
if (!(tp->turret_gun_sobj == tp->subobj_num))
{
auto pmi = model_get_instance(shipp->model_instance_num);
auto pm = model_get(pmi->model_num);
model_make_turret_matrix(pm, pmi, tp);
}
}
// Use the turret info for all guns, not one gun in particular.
vec3d gvec, gpos;
ship_get_global_turret_info(objp, tp, &gpos, &gvec);
if (tp->flags[Model::Subsystem_Flags::Turret_alt_math]) {
vm_matrix_x_matrix( &ss->world_to_turret_matrix, &objp->orient, &tp->turret_matrix );
}
// Rotate the turret even if time hasn't elapsed, since it needs to turn to face its target.
int use_angles = aifft_rotate_turret(objp, shipp, ss, lep, &predicted_enemy_pos, &gvec);
@@ -2321,12 +2351,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,21 +2369,19 @@ 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;
}
ship_info* esip = lep->type != OBJ_SHIP ? nullptr : &Ship_info[Ships[lep->instance].ship_info_index];
// If targeted a small ship and have a huge weapon, don't fire. But this shouldn't happen, as a small ship should not get selected.
if ( wip->wi_flags[Weapon::Info_Flags::Huge] ) {
if ( lep->type != OBJ_SHIP ) {
tentative_return = true;
continue;
}
if ( esip->class_type >= 0 && !(Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
if ( !(Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge()) ) {
tentative_return = true;
continue;
}
@@ -2361,8 +2389,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
// Similar check for small weapons
if ( wip->wi_flags[Weapon::Info_Flags::Small_only] ) {
if ( (lep->type == OBJ_SHIP) && esip->class_type >= 0 &&
(Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
if ( (lep->type == OBJ_SHIP) && Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge() ) {
tentative_return = true;
continue;
}
@@ -2406,11 +2433,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
@@ -2431,10 +2453,8 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
//none of our guns can hit the enemy, so find a new enemy
if (num_valid == 0) {
if (!ss->flags[Ship::Subsystem_Flags::Forced_target]) {
ss->turret_enemy_objnum = -1;
ss->turret_time_enemy_in_range = 0.0f;
}
ss->turret_enemy_objnum = -1;
ss->turret_time_enemy_in_range = 0.0f;
// in the original code, we returned where the "tentative return" variable was set,
// so return for real now, since we don't have a reason to remain in the function
@@ -2442,16 +2462,8 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
return;
}
// if we've got Forced_subsys_target targeted_subsys *really* shouldnt be null but just in case...
if (ss->flags[Ship::Subsystem_Flags::Forced_subsys_target] && ss->targeted_subsys != nullptr) {
if (ss->targeted_subsys->current_hits <= 0.0f) {
ss->flags.remove(Ship::Subsystem_Flags::Forced_subsys_target);
ss->flags.remove(Ship::Subsystem_Flags::Forced_target);
}
}
// Maybe pick a new enemy, unless targeting has been taken over by scripting
if ( turret_should_pick_new_target(ss) && !ss->scripting_target_override && !(ss->flags[Ship::Subsystem_Flags::Forced_target])) {
if ( turret_should_pick_new_target(ss) && !ss->scripting_target_override ) {
Num_find_turret_enemy++;
int objnum = find_turret_enemy(ss, parent_objnum, &gpos, &gvec, ss->turret_enemy_objnum);
@@ -2477,10 +2489,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 +2680,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;
}
}
@@ -2732,7 +2736,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
{
// If nothing is OK to fire (lost track of the target?)
// reset the target (so we don't continue to track what we can't hit)
if (tp->flags[Model::Subsystem_Flags::Turret_only_target_if_can_fire] && !(ss->flags[Ship::Subsystem_Flags::Forced_target]))
if (tp->flags[Model::Subsystem_Flags::Turret_only_target_if_can_fire])
{
ss->turret_enemy_objnum = -1; // Reset enemy objnum, find a new one next frame.
ss->turret_time_enemy_in_range = 0.0f;
@@ -2742,7 +2746,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 +2755,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
@@ -2767,7 +2771,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
}
}
}
else if (!(ss->flags[Ship::Subsystem_Flags::Forced_target]))
else
{
// Lost him!
ss->turret_enemy_objnum = -1; // Reset enemy objnum, find a new one next frame.
@@ -2790,20 +2794,16 @@ bool turret_adv_fov_test(ship_subsys *ss, vec3d *gvec, vec3d *v2e, float size_mo
model_subsystem *tp = ss->system_info;
float dot = vm_vec_dot(v2e, gvec);
if (((dot + size_mod) >= tp->turret_fov) && ((dot - size_mod) <= tp->turret_max_fov)) {
object* objp = &Objects[ss->parent_objnum];
matrix turret_matrix;
turret_instance_find_world_orient(&turret_matrix, Ships[objp->instance].model_instance_num, tp->subobj_num, &objp->orient);
vec3d of_dst;
vm_vec_rotate(&of_dst, v2e, &turret_matrix);
if ((of_dst.xyz.x == 0) && (of_dst.xyz.z == 0))
return true;
else {
of_dst.xyz.y = 0;
vm_vec_rotate( &of_dst, v2e, &ss->world_to_turret_matrix );
if ((of_dst.xyz.x == 0) && (of_dst.xyz.y == 0)) {
return true;
} else {
of_dst.xyz.z = 0;
if (!IS_VEC_NULL_SQ_SAFE(&of_dst)) {
vm_vec_normalize(&of_dst);
// now we have 2d vector with lenght of 1 that points at the targets direction after being rotated to turrets FOR
if ((of_dst.xyz.z + size_mod) >= tp->turret_base_fov)
if ((-of_dst.xyz.y + size_mod) >= tp->turret_y_fov)
return true;
}
}
@@ -2814,7 +2814,7 @@ bool turret_adv_fov_test(ship_subsys *ss, vec3d *gvec, vec3d *v2e, float size_mo
bool turret_fov_test(ship_subsys *ss, vec3d *gvec, vec3d *v2e, float size_mod)
{
bool in_fov = false;
if (ss->system_info->flags[Model::Subsystem_Flags::Turret_restricted_fov])
if (ss->system_info->flags[Model::Subsystem_Flags::Turret_alt_math])
in_fov = turret_adv_fov_test(ss, gvec, v2e, size_mod);
else
in_fov = turret_std_fov_test(ss, gvec, v2e, size_mod);
+270 -322
View File
@@ -32,12 +32,13 @@
#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"
#include "ship/shipfx.h"
#include "ship/shiphit.h"
#include "species_defs/species_defs.h"
#include "stats/scoring.h"
#include "weapon/weapon.h"
@@ -49,12 +50,14 @@ asteroid_obj Asteroid_objs[MAX_ASTEROID_OBJS]; // array used to store asteroid o
asteroid_obj Asteroid_obj_list; // head of linked list of asteroid_obj structs
// used for randomly generating debris type when there are multiple sizes.
const float SMALL_DEBRIS_WEIGHT = 8.0f;
const float MEDIUM_DEBRIS_WEIGHT = 4.0f;
const float LARGE_DEBRIS_WEIGHT = 1.0f;
#define SMALL_DEBRIS_WEIGHT 8
#define MEDIUM_DEBRIS_WEIGHT 4
#define LARGE_DEBRIS_WEIGHT 1
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 +74,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 +159,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);
}
/**
@@ -288,7 +257,7 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi
return NULL;
}
if((asteroid_type < 0) || (asteroid_type >= (int)Asteroid_info.size())) {
if((asteroid_type < 0) || (asteroid_type >= (((int)Species_info.size() + 1) * NUM_DEBRIS_SIZES))) {
return NULL;
}
@@ -356,7 +325,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 +335,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
@@ -519,6 +488,31 @@ static void asteroid_load(int asteroid_info_index, int asteroid_subtype)
}
}
/**
* Returns a weight that depends on asteroid size.
*
* The weight is then used to determine the frequencty of different sizes of ship debris
*/
static int get_debris_weight(int ship_debris_index)
{
int size = ship_debris_index % NUM_DEBRIS_SIZES;
switch (size)
{
case ASTEROID_TYPE_SMALL:
return SMALL_DEBRIS_WEIGHT;
case ASTEROID_TYPE_MEDIUM:
return MEDIUM_DEBRIS_WEIGHT;
case ASTEROID_TYPE_LARGE:
return LARGE_DEBRIS_WEIGHT;
default:
Int3();
return 1;
}
}
/**
* Create all the asteroids for the mission
*/
@@ -528,14 +522,14 @@ 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;
int random_threshold;
int debris_type;
} ship_debris_odds_table[MAX_ACTIVE_DEBRIS_TYPES];
float max_weighted_range = 0.0f;
int max_weighted_range = 0;
if (!Asteroids_enabled)
return;
@@ -544,11 +538,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;
@@ -563,7 +552,7 @@ void asteroid_create_all()
// Calculate the odds table
for (idx=0; idx<num_debris_types; idx++) {
float debris_weight = Asteroid_info[Asteroid_field.field_debris_type[idx]].spawn_weight;
int debris_weight = get_debris_weight(Asteroid_field.field_debris_type[idx]);
ship_debris_odds_table[idx].random_threshold = max_weighted_range + debris_weight;
ship_debris_odds_table[idx].debris_type = Asteroid_field.field_debris_type[idx];
max_weighted_range += debris_weight;
@@ -601,24 +590,16 @@ 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);
float rand_choice = frand() * max_weighted_range;
int rand_choice = rand() % max_weighted_range;
for (idx=0; idx<MAX_ACTIVE_DEBRIS_TYPES; idx++) {
// for ship debris, choose type according to odds table
@@ -638,13 +619,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 +759,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 +826,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 +864,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 +944,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 +976,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 +988,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 +1024,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 +1044,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)
angles copy_angles = smi->angs;
// 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->angs = smi->prev_angs;
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
smi->angs = copy_angles;
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 +1065,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_obj_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 +1097,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_obj_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 +1126,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 +1350,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 +1458,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 +1510,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 +1565,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");
}
}
@@ -1849,7 +1788,7 @@ static void asteroid_parse_section(asteroid_info *asip)
}
required_string("$Detail distance:");
asip->num_detail_levels = (int)stuff_int_list(asip->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, RAW_INTEGER_TYPE);
asip->num_detail_levels = stuff_int_list(asip->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, RAW_INTEGER_TYPE);
required_string("$Max Speed:");
stuff_float(&asip->max_speed);
@@ -1863,7 +1802,7 @@ static void asteroid_parse_section(asteroid_info *asip)
if(optional_string("$Explosion Animations:")){
int temp[MAX_FIREBALL_TYPES];
auto parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
int parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
asip->explosion_bitmap_anims.clear();
asip->explosion_bitmap_anims.insert(asip->explosion_bitmap_anims.begin(), temp, temp+parsed_ints);
}
@@ -1910,81 +1849,20 @@ static void asteroid_parse_section(asteroid_info *asip)
} else
Warning(LOCATION, "Invalid asteroid reference %i used for $Split in asteroids table, ignoring.", split_type);
}
if (optional_string("$Spawn Weight:")) {
stuff_float(&asip->spawn_weight);
if (asip->spawn_weight <= 0.0f) {
Warning(LOCATION, "Spawn weight for asteroid '%s' must be greater than 0", asip->name);
asip->spawn_weight = 1.0f;
}
} else {
switch (Asteroid_info.size() % NUM_DEBRIS_SIZES)
{
case ASTEROID_TYPE_SMALL:
asip->spawn_weight = SMALL_DEBRIS_WEIGHT;
break;
case ASTEROID_TYPE_MEDIUM:
asip->spawn_weight = MEDIUM_DEBRIS_WEIGHT;
break;
case ASTEROID_TYPE_LARGE:
asip->spawn_weight = LARGE_DEBRIS_WEIGHT;
break;
default:
UNREACHABLE("Here be dragons");
}
}
}
// changes the name to "[species] Debris" if it had a name like "[species] debris #"
void maybe_change_asteroid_name(asteroid_info* asip) {
SCP_string name = asip->name;
size_t split = std::string::npos;
for (species_info species : Species_info) {
if (name.compare(0, strlen(species.species_name), species.species_name) == 0) {
split = strlen(species.species_name);
break;
}
}
if (split == std::string::npos)
return;
SCP_string remaining_name = name.substr(split + 1, name.length());
split = remaining_name.find(' ');
if (split == std::string::npos)
return;
SCP_string debris = remaining_name.substr(0, split);
SCP_string num = remaining_name.substr(split + 1, remaining_name.length());
if (stricmp(debris.c_str(), "Debris") != 0)
return;
if (num.empty() || std::find_if(num.begin(), num.end(), [](char c) {
return !std::isdigit(c, SCP_default_locale);
}) != num.end())
return;
// make sure this asteroid would correspond the 'species section' of the old style retail asteroids
if (Asteroid_info.size() < NUM_DEBRIS_SIZES + Species_info.size() * NUM_DEBRIS_SIZES && Asteroid_info.size() >= NUM_DEBRIS_SIZES) {
int idx = (int)(Asteroid_info.size()) / NUM_DEBRIS_SIZES - 1;
strcpy_s(asip->name, Species_info[idx].species_name);
strcat(asip->name, " ");
strcat(asip->name, XSTR("debris", 348));
}
}
/**
Read in data from asteroid.tbl into Asteroid_info[] array.
After this function is complete, the Asteroid_info[] array
will have at least 3 generic asteroids plus whatever else
special ones were added in the table. The generic asteroids are
a set of small, medium, and large asteroids in that exact order.
After this function is completes, the Asteroid_info[] array will
contain exactly three asteroids per species plus exactly three
generic asteroids. These three asteroids are a set
of small, medium, and large asteroids in that exact order.
If asteroid.tbl contains too many or too few asteroids as per
the rules above this function will abort the engine with an
error message stating the number expected and it will write
to the debug.log how everything was parsed.
Note that by saying "asteroid" this code is talking about
the asteroids and the debris that make up asteroid fields
@@ -1996,6 +1874,12 @@ that is being protected.
static void asteroid_parse_tbl()
{
char impact_ani_file[MAX_FILENAME_LEN];
// How did we get here without having any species defined?
Assertion(!Species_info.empty(),
"Cannot parse asteroids/debris if there "
"are no species for them to belong to."
);
try
{
@@ -2005,6 +1889,8 @@ static void asteroid_parse_tbl()
required_string("#Asteroid Types");
size_t tally = 0;
size_t max_asteroids =
NUM_DEBRIS_SIZES + Species_info.size() * NUM_DEBRIS_SIZES;
#ifndef NDEBUG
SCP_vector<SCP_string> parsed_asteroids;
@@ -2017,41 +1903,82 @@ static void asteroid_parse_tbl()
asteroid_parse_section(&new_asteroid);
maybe_change_asteroid_name(&new_asteroid);
#ifndef NDEBUG
SCP_string msg;
msg.append("Parsing asteroid: '");
msg.append(new_asteroid.name);
msg.append("' as a '");
msg.append((tally >= NUM_DEBRIS_SIZES) ? "generic" : "special");
msg.append("'");
switch (tally % NUM_DEBRIS_SIZES) {
case ASTEROID_TYPE_SMALL:
msg.append(" small\n");
break;
case ASTEROID_TYPE_MEDIUM:
msg.append(" medium\n");
break;
case ASTEROID_TYPE_LARGE:
msg.append(" large\n");
break;
default:
Error(LOCATION, "Get a coder! Math has broken!\n"
"Important numbers:\n"
"\ttally: " SIZE_T_ARG "\n"
"\tNUM_DEBRIS_SIZES: %d\n",
tally, NUM_DEBRIS_SIZES
);
msg.append(" unknown\n");
}
parsed_asteroids.push_back(msg);
size_t species = tally / NUM_DEBRIS_SIZES;
if (tally >= max_asteroids)
{
#ifdef NDEBUG
// Bump the warning count in release so they get something
// even if the message never gets displayed.
Warning(LOCATION, "Ignoring extra asteroid/debris");
#else
SCP_string msg("Ignoring extra asteroid/debris '");
msg.append(new_asteroid.name);
msg.append("'\n");
Warning(LOCATION, "%s", msg.c_str());
parsed_asteroids.push_back(msg);
#endif
Asteroid_info.push_back(new_asteroid);
}
else
{
#ifndef NDEBUG
SCP_string msg;
msg.append("Parsing asteroid: '");
msg.append(new_asteroid.name);
msg.append("' as a '");
msg.append((species == 0) ? "generic" : Species_info[species - 1].species_name);
msg.append("'");
switch (tally % NUM_DEBRIS_SIZES) {
case ASTEROID_TYPE_SMALL:
msg.append(" small\n");
break;
case ASTEROID_TYPE_MEDIUM:
msg.append(" medium\n");
break;
case ASTEROID_TYPE_LARGE:
msg.append(" large\n");
break;
default:
Error(LOCATION, "Get a coder! Math has broken!\n"
"Important numbers:\n"
"\ttally: " SIZE_T_ARG "\n"
"\tNUM_DEBRIS_SIZES: %d\n",
tally, NUM_DEBRIS_SIZES
);
msg.append(" unknown\n");
}
parsed_asteroids.push_back(msg);
#endif
Asteroid_info.push_back(new_asteroid);
}
tally++;
}
required_string("#End");
if (tally != max_asteroids)
{
#ifndef NDEBUG
mprintf(("Asteroid.tbl as parsed:\n"));
for (SCP_vector<SCP_string>::iterator iter = parsed_asteroids.begin();
iter != parsed_asteroids.end(); ++iter)
{
mprintf(("%s\n", iter->c_str()));
}
#endif
Error(LOCATION,
"Found " SIZE_T_ARG " asteroids/debris when " SIZE_T_ARG " expected\n\n"
"<Number expected> = <Number of species> * %d + %d generic asteroids\n"
SIZE_T_ARG " = " SIZE_T_ARG "*%d + %d\n\n"
#ifdef NDEBUG
"Run a debug build to see a list of all parsed asteroids\n",
#else
"See the debug.log for a listing of all parsed asteroids\n",
#endif
tally, max_asteroids,
NUM_DEBRIS_SIZES, NUM_DEBRIS_SIZES,
max_asteroids, Species_info.size(), NUM_DEBRIS_SIZES, NUM_DEBRIS_SIZES
);
}
Asteroid_impact_explosion_ani = -1;
required_string("$Impact Explosion:");
stuff_string(impact_ani_file, F_NAME, MAX_FILENAME_LEN);
@@ -2092,6 +2019,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 +2056,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 +2083,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());
}
/**
-9
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 {
@@ -78,8 +73,6 @@ public:
int model_num[NUM_DEBRIS_POFS];
SCP_vector<int> explosion_bitmap_anims;
float fireball_radius_multiplier; // the model radius is multiplied by this to determine the fireball size
SCP_string display_name; // only used for hud targeting display and for 'ship' asteroids
float spawn_weight; // ship asteroids only, relative proportion to spawn compared to other types in its asteroid field
asteroid_info( )
: num_detail_levels( 0 ), max_speed( 0 ), damage_type_idx( 0 ),
@@ -143,7 +136,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 +163,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
+10 -13
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
@@ -288,7 +287,7 @@ static int _cfile_chdir(const char *new_dir, const char *cur_dir __UNUSED)
const char *colon = strchr(new_dir, ':');
if (colon) {
if (!cfile_chdrive(SCP_tolower(*(colon - 1)) - 'a' + 1, 1))
if (!cfile_chdrive(tolower(*(colon - 1)) - 'a' + 1, 1))
return 1;
path = colon + 1;
@@ -306,7 +305,7 @@ static int _cfile_chdir(const char *new_dir, const char *cur_dir __UNUSED)
status = _chdir(path);
if (status != 0) {
#ifdef _WIN32
cfile_chdrive(SCP_tolower(cur_dir[0]) - 'a' + 1, 1);
cfile_chdrive(tolower(cur_dir[0]) - 'a' + 1, 1);
#endif /* _WIN32 */
return 2;
}
@@ -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
//
@@ -867,7 +866,6 @@ static int cfget_cfile_block()
cfile->data = nullptr;
cfile->fp = nullptr;
cfile->type = CFILE_BLOCK_USED;
cf_clear_compression_info(cfile);
return i;
}
}
@@ -923,7 +921,7 @@ int cfclose( CFILE * cfile )
} else {
// VP do nothing
}
cf_clear_compression_info(cfile);
cfile->type = CFILE_BLOCK_UNUSED;
return result;
}
@@ -975,7 +973,7 @@ static CFILE *cf_open_fill_cfblock(const char* source, int line, const char* ori
if(pos == -1L)
pos = 0;
cf_init_lowlevel_read_code(cfp,0,filelength(fileno(fp)), 0 );
cf_check_compression(cfp);
return cfp;
}
}
@@ -1010,7 +1008,7 @@ static CFILE *cf_open_packed_cfblock(const char* source, int line, const char* o
cfp->line_num = line;
cf_init_lowlevel_read_code(cfp,offset, size, 0 );
cf_check_compression(cfp);
return cfp;
}
@@ -1054,7 +1052,6 @@ static CFILE *cf_open_mapped_fill_cfblock(const char* source, int line, const ch
cfp->line_num = line;
cf_init_lowlevel_read_code(cfp, 0, 0, 0 );
#if defined _WIN32
cfp->hMapFile = CreateFileMapping(cfp->hInFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (cfp->hMapFile == NULL) {
+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))
+8 -47
View File
@@ -24,7 +24,6 @@
#include "cfile/cfile.h"
#include "cfile/cfilearchive.h"
#include "cfile/cfilecompression.h"
#include "luaconf.h"
#include <sstream>
@@ -34,6 +33,7 @@
#define CHECK_POSITION
// Called once to setup the low-level reading code.
void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size, size_t pos )
{
Assert(cfile != nullptr);
@@ -54,33 +54,6 @@ void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size,
}
}
//This function checks the file header to see if the file is compressed, if so it fills the correct compression info.
void cf_check_compression(CFILE* cfile)
{
if (cfile->size <= 16)
return;
int header=cfread_int(cfile);
cfseek(cfile, 0, SEEK_SET);
if (comp_check_header(header) == COMP_HEADER_MATCH)
comp_create_ci(cfile, header);
}
//This function is called to cleanup compression info when the cfile handle is reused.
void cf_clear_compression_info(CFILE* cfile)
{
if (cfile->compression_info.header != 0)
{
free(cfile->compression_info.offsets);
free(cfile->compression_info.decoder_buffer);
cfile->compression_info.offsets = nullptr;
cfile->compression_info.decoder_buffer = nullptr;
cfile->compression_info.header = 0;
cfile->compression_info.block_size = 0;
cfile->compression_info.last_decoded_block_pos = 0;
cfile->compression_info.last_decoded_block_bytes = 0;
cfile->compression_info.num_offsets = 0;
}
}
// cfeof() Tests for end-of-file on a stream
@@ -88,11 +61,11 @@ void cf_clear_compression_info(CFILE* cfile)
// returns a nonzero value after the first read operation that attempts to read
// past the end of the file. It returns 0 if the current position is not end of file.
// There is no error return.
int cfeof(CFILE *cfile)
{
Assert(cfile != NULL);
if (cfile->compression_info.header != 0)
return comp_feof(cfile);
int result = 0;
#if defined(CHECK_POSITION) && !defined(NDEBUG)
@@ -119,8 +92,6 @@ int cfeof(CFILE *cfile)
int cftell( CFILE * cfile )
{
Assert(cfile != NULL);
if (cfile->compression_info.header != 0)
return (int)comp_ftell(cfile);
#if defined(CHECK_POSITION) && !defined(NDEBUG)
if (cfile->fp) {
@@ -146,9 +117,6 @@ int cfseek( CFILE *cfile, int offset, int where )
Assert(cfile != NULL);
if (cfile->compression_info.header != 0)
return comp_fseek(cfile, offset, where);
// TODO: seek to offset in memory mapped file
Assert( !cfile->mem_mapped );
@@ -234,31 +202,23 @@ int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
// This is a file from memory
bytes_read = size;
memcpy(buf, reinterpret_cast<const char*>(cfile->data) + cfile->raw_position, size);
} else if (cfile->compression_info.header != 0) {
bytes_read = comp_fread(cfile, reinterpret_cast<char*>(buf),size);
if (bytes_read != size)
{
mprintf(("\nFile: %s decompression error. Result was: %d expected: %d\n", cfile->original_filename.c_str(), (int)bytes_read, (int)size));
Assertion(bytes_read == size, "File decompression error!");
}
} else {
bytes_read = fread(buf, 1, size, cfile->fp);
bytes_read = fread( buf, 1, size, cfile->fp );
}
if ( bytes_read > 0 ) {
cfile->raw_position += bytes_read;
Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!");
}
#if defined(CHECK_POSITION) && !defined(NDEBUG)
//Raw position is not the fp position with compressed files
if (cfile->fp && cfile->compression_info.header == 0) {
if (cfile->fp) {
auto tmp_offset = ftell(cfile->fp) - cfile->lib_offset;
Assert(tmp_offset == cfile->raw_position);
Assert(tmp_offset==cfile->raw_position);
}
#endif
return (int)(bytes_read / elsize);
}
int cfread_lua_number(double *buf, CFILE *cfile)
@@ -306,3 +266,4 @@ int cfread_lua_number(double *buf, CFILE *cfile)
return items_read;
}
+2 -19
View File
@@ -7,6 +7,7 @@
*
*/
#ifndef _CFILEARCHIVE_H
#define _CFILEARCHIVE_H
@@ -22,17 +23,6 @@
#define CFILE_BLOCK_UNUSED 0
#define CFILE_BLOCK_USED 1
struct COMPRESSION_INFO {
int header = 0;
size_t compressed_size = 0;
int block_size = 0;
int num_offsets = 0;
int* offsets = nullptr;
char* decoder_buffer = nullptr;
int last_decoded_block_pos = 0;
int last_decoded_block_bytes = 0;
};
struct CFILE {
int type = CFILE_BLOCK_UNUSED; // CFILE_BLOCK_UNUSED, CFILE_BLOCK_USED
int dir_type; // directory location
@@ -54,19 +44,12 @@ struct CFILE {
SCP_string original_filename;
const char* source_file;
int line_num;
COMPRESSION_INFO compression_info;
};
#define MAX_CFILE_BLOCKS 64
extern std::array<CFILE, MAX_CFILE_BLOCKS> Cfile_block_list;
// Called once to setup the low-level reading code.
void cf_init_lowlevel_read_code(CFILE* cfile, size_t lib_offset, size_t size, size_t pos);
// This checks if the file is compressed or not, and creates the proper compression info if so.
void cf_check_compression(CFILE* cfile);
// Used to clear compression info data and free dynamic memory used by compression
void cf_clear_compression_info(CFILE* cfile);
void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size, size_t pos );
#endif
-206
View File
@@ -1,206 +0,0 @@
#define _CFILE_INTERNAL
#include <cstdlib>
#include <cstring>
#include <cstdio>
#ifdef _WIN32
#include <io.h>
#include <direct.h>
#include <windows.h>
#include <winbase.h>
#endif
#include "lz4.h"
#include "cfilecompression.h"
#include "cfilearchive.h"
/*INTERNAL FUNCTIONS*/
/*LZ41*/
void lz41_load_offsets(CFILE* cf);
size_t lz41_stream_random_access(CFILE* cf, char* bytes_out, size_t offset, size_t length);
void lz41_create_ci(CFILE* cf, int header);
/*MISC*/
int fso_fseek(CFILE* cfile, int offset, int where);
/*END OF INTERNAL FUNCTIONS*/
int comp_check_header(int header)
{
if (LZ41_FILE_HEADER == header)
return COMP_HEADER_MATCH;
return COMP_HEADER_MISMATCH;
}
void comp_create_ci(CFILE* cf, int header)
{
//mprintf(("(CI)Compressed File Opened: %s \n", cf->original_filename.c_str()));
if (LZ41_FILE_HEADER == header)
lz41_create_ci(cf, header);
//mprintf(("(CI)Uncompressed FileSize: %d \n", cf->size));
//mprintf(("(CI)Compressed FileSize: %d \n", cf->compression_info.compressed_size));
//mprintf(("(CI)Block Size: %d \n", cf->compression_info.block_size));
}
size_t comp_fread(CFILE* cf, char* buffer, size_t length)
{
/* Check the request to be at least 1 byte */
Assertion(length > 0, "Invalid length requested.");
/* Check that we are not requesting to read beyond end of file */
Assertion(cf->raw_position + length <= cf->size, "Invalid length requested.");
if (LZ41_FILE_HEADER == cf->compression_info.header)
return lz41_stream_random_access(cf, buffer, cf->raw_position, length);
return 0;
}
size_t comp_ftell(CFILE* cf)
{
return cf->raw_position;
}
int comp_feof(CFILE* cf)
{
if (cf->raw_position >= cf->size)
return 1;
else
return 0;
}
int comp_fseek(CFILE* cf, int offset, int where)
{
size_t goal_position;
switch (where) {
case SEEK_SET: goal_position = offset; break;
case SEEK_CUR: goal_position = cf->raw_position + offset; break;
case SEEK_END: goal_position = cf->size + offset; break;
default: return 1;
}
// Make sure we don't seek beyond the end of the file
CAP(goal_position,(size_t)0, cf->size);
cf->raw_position = goal_position;
return 0;
}
//Special fseek for compressed files handled by FSO, only SEEK_SET and SEEK_END is supported.
int fso_fseek(CFILE* cfile, int offset, int where)
{
size_t goal_position;
switch (where) {
case SEEK_SET: goal_position = offset + cfile->lib_offset; break;
case SEEK_END: goal_position = cfile->compression_info.compressed_size + offset + cfile->lib_offset; break;
default: return 0;
}
// Make sure we don't seek beyond the end of the file
CAP(goal_position, cfile->lib_offset, cfile->lib_offset + cfile->compression_info.compressed_size);
int result = 0;
if (cfile->fp)
{
result = fseek(cfile->fp, (long)goal_position, SEEK_SET);
Assertion(goal_position >= cfile->lib_offset, "Invalid offset values detected while seeking! Goal was " SIZE_T_ARG ", lib_offset is " SIZE_T_ARG ".", goal_position, cfile->lib_offset);
}
return result;
}
void lz41_create_ci(CFILE* cf, int header)
{
cf->compression_info.header = header;
cf->compression_info.compressed_size = cf->size;
fso_fseek(cf, (int)sizeof(int)*-3, SEEK_END);
auto fNumoffsets = fread(&cf->compression_info.num_offsets, sizeof(int), 1, cf->fp);
auto fSize = fread(&cf->size, sizeof(int), 1, cf->fp);
auto fBsize = fread(&cf->compression_info.block_size, sizeof(int), 1, cf->fp);
Assertion(cf->compression_info.num_offsets > 0, "Invalid number of offsets, compressed file is possibly in the wrong format or corrupted.");
#if !defined(NDEBUG)
Assertion(cf->size > 4, "Invalid filesize, compressed file is possibly in the wrong format or corrupted.");
Assertion(cf->compression_info.block_size > 16, "Invalid block size, compressed file is possibly in the wrong format or corrupted.");
Assertion(fNumoffsets == 1, "Error while reading the number of offsets, compressed file is possibly in the wrong format or corrupted.");
Assertion(fSize == 1, "Error while reading original filesize, compressed file is possibly in the wrong format or corrupted.");
Assertion(fBsize == 1, "Error while reading block size, compressed file is possibly in the wrong format or corrupted.");
#endif
cf->compression_info.decoder_buffer = (char*)malloc(LZ4_compressBound(cf->compression_info.block_size));
cf->compression_info.last_decoded_block_pos = 0;
cf->compression_info.last_decoded_block_bytes = 0;
lz41_load_offsets(cf);
}
void lz41_load_offsets(CFILE* cf)
{
cf->compression_info.offsets = (int*)malloc(cf->compression_info.num_offsets * sizeof(int));
int block;
int* offsets_ptr = cf->compression_info.offsets;
/* Seek to the first offset position, remember to consider the trailing ints */
fso_fseek(cf, ( ( sizeof(int) * cf->compression_info.num_offsets ) * -1 ) - (sizeof(int)*3 ), SEEK_END);
for (block = 0; block < cf->compression_info.num_offsets; ++block)
{
auto bytes_read = fread(offsets_ptr++, sizeof(int), 1, cf->fp);
Assertion(bytes_read == 1, "Error reading offset list.");
}
}
size_t lz41_stream_random_access(CFILE* cf, char* bytes_out, size_t offset, size_t length)
{
LZ4_streamDecode_t lz4_stream_decode_body;
LZ4_streamDecode_t* lz4_stream_decode = &lz4_stream_decode_body;
/* The blocks (current_block to end_block) contain the data we want */
size_t current_block = offset / cf->compression_info.block_size;
size_t end_block = ((offset + length - 1) / cf->compression_info.block_size) + 1;
size_t written_bytes = 0;
if (cf->compression_info.num_offsets <= (int)end_block)
return (size_t)LZ41_OFFSETS_MISMATCH;
/* Seek to the first block to read, if it matches the cached block, search the next one instead */
if (cf->compression_info.last_decoded_block_pos == cf->compression_info.offsets[current_block] && current_block + 1 <= end_block)
fso_fseek(cf, cf->compression_info.offsets[current_block+1], SEEK_SET);
else
fso_fseek(cf, cf->compression_info.offsets[current_block], SEEK_SET);
offset = offset % cf->compression_info.block_size;
char* cmp_buf = (char*)malloc(LZ4_compressBound(cf->compression_info.block_size));
/* Start decoding */
for (; current_block < end_block; ++current_block)
{
/* Only read and decode if the requested block is not the cached one */
if (cf->compression_info.last_decoded_block_pos != cf->compression_info.offsets[current_block])
{
cf->compression_info.last_decoded_block_pos = cf->compression_info.offsets[current_block];
/* The difference in offsets is the size of the block */
int cmp_bytes = cf->compression_info.offsets[current_block + 1] - cf->compression_info.offsets[current_block];
auto bytes_read = fread(cmp_buf, cmp_bytes, 1, cf->fp);
Assertion(bytes_read == 1, "Error reading from compressed file.");
cf->compression_info.last_decoded_block_bytes = LZ4_decompress_safe_continue(lz4_stream_decode, cmp_buf, cf->compression_info.decoder_buffer, cmp_bytes, cf->compression_info.block_size);
if (cf->compression_info.last_decoded_block_bytes <= 0)
{
free(cmp_buf);
return (size_t)LZ41_DECOMPRESSION_ERROR;
}
}
/* Write out the part of the data we care about from buffer */
size_t block_length = (length < (cf->compression_info.last_decoded_block_bytes - offset) ? length : (cf->compression_info.last_decoded_block_bytes - offset));
memcpy(bytes_out + written_bytes, cf->compression_info.decoder_buffer + offset, (size_t)block_length);
written_bytes += block_length;
offset = 0;
length -= block_length;
}
free(cmp_buf);
return written_bytes;
}
-66
View File
@@ -1,66 +0,0 @@
/*
ShivanSpS - Compressed files support for FSO. Many thanks to ngld, taylor and everyone elsewho helped me in getting this done.
-The file header is a version, this is used to tell FSO how to decompress that file, always use 4 chars to mantain alignment.
-The header ID can be used to add diferent revisions to LZ41 decompression system or to add other compression format supports whiout breaking compatibility.
-The system uses a offset list to record the position of every block in file, this list, along with the number of offsets, original filesize,
and block size, must be written by the compressor app.
-A decoder cache is used to store the last decoded block, this ensures each block is read and decoded only once in sequential reads. This cache
is a little bit bigger than the block size, a higher block size means less overhead added to the file, but it also means a little more ram will
be used during decompression.
-All this dynamic memory is assigned at cfopen() and it is cleared on cfclose().
................................char[4]..........(n ints)...(int)..........(int)..........(int)
-COMPRESSED FILE DATA STUCTURE: HEADER|N BLOCKS|N OFFSETS|NUM_OFFSETS|ORIGINAL_FILESIZE|BLOCK_SIZE
*/
#ifndef _CFILECOMPRESSION_H
#define _CFILECOMPRESSION_H
struct CFILE;
/*LZ41*/
#define LZ41_FILE_HEADER 0x31345A4C // "14ZL" -> "LZ41", inverted
#define LZ41_DECOMPRESSION_ERROR -1
#define LZ41_MAX_BLOCKS_OVERFLOW -2
#define LZ41_HEADER_MISMATCH -3
#define LZ41_OFFSETS_MISMATCH -4
/******/
#define COMP_HEADER_MATCH 1
#define COMP_HEADER_MISMATCH 0
/*
Returns COMP_HEADER_MATCH if header is a valid compressed file header,
returns COMP_HEADER_MISMATCH if it dosent.
*/
int comp_check_header(int header);
/*
This is called to generate the correct compression_info data
after the file has been indentified as a compressed file.
This must be done before calling any other function.
*/
void comp_create_ci(CFILE* cf, int header);
/*
Read X bytes from the uncompressed file starting from X offset.
Returns the amount of bytes read, and 0 or lower to indicate errors.
*/
size_t comp_fread(CFILE* cf, char* buffer, size_t length);
/*
Returns the current uncompressed file position.
*/
size_t comp_ftell(CFILE* cf);
/*
Returns 1 of the uncompressed file has been completely read, otherwise it returns a 0.
*/
int comp_feof(CFILE* cf);
/*
Used to move the uncompressed file current position.
*/
int comp_fseek(CFILE* cf, int offset, int where);
#endif
+757 -478
View File
File diff suppressed because it is too large. Load diff
+155 -125
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,20 +240,17 @@ 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", },
{ "-noninteractive", "Disables interactive dialogs", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-noninteractive", },
{ "-no_unfocused_pause","Don't pause if the window isn't focused", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-no_unfocused_pause", },
{ "-benchmark_mode", "Puts the game into benchmark mode", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-benchmark_mode", },
{ "-profile_frame_time","Profile frame time", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-profile_frame_time", },
{ "-profile_frame_time","Profile frame time", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-profile_frame_time", },
{ "-profile_write_file", "Write profiling information to file", true, 0, EASY_DEFAULT, "Dev Tool", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-profile_write_file", },
{ "-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
@@ -283,6 +274,7 @@ cmdline_parm gameclosed_arg("-closed", NULL, AT_NONE); // Cmdline_closed_game
cmdline_parm gamerestricted_arg("-restricted", NULL, AT_NONE); // Cmdline_restricted_game
cmdline_parm port_arg("-port", "Multiplayer network port", AT_INT);
cmdline_parm multilog_arg("-multilog", NULL, AT_NONE); // Cmdline_multi_log
cmdline_parm client_dodamage("-clientdamage", NULL, AT_NONE); // Cmdline_client_dodamage
cmdline_parm pof_spew("-pofspew", NULL, AT_NONE); // Cmdline_spew_pof_info
cmdline_parm weapon_spew("-weaponspew", nullptr, AT_STRING); // Cmdline_spew_weapon_stats
cmdline_parm mouse_coords("-coords", NULL, AT_NONE); // Cmdline_mouse_coords
@@ -295,6 +287,7 @@ char *Cmdline_game_password = NULL;
char *Cmdline_rank_above = NULL;
char *Cmdline_rank_below = NULL;
int Cmdline_cd_check = 1;
int Cmdline_client_dodamage = 0;
int Cmdline_closed_game = 0;
int Cmdline_freespace_no_music = 0;
int Cmdline_freespace_no_sound = 0;
@@ -315,7 +308,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 +377,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 +419,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 +438,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 +456,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
@@ -498,7 +481,6 @@ cmdline_parm reparse_mainhall_arg("-reparse_mainhall", NULL, AT_NONE); //Cmdline
cmdline_parm frame_profile_write_file("-profile_write_file", NULL, AT_NONE); // Cmdline_profile_write_file
cmdline_parm no_unfocused_pause_arg("-no_unfocused_pause", NULL, AT_NONE); //Cmdline_no_unfocus_pause
cmdline_parm benchmark_mode_arg("-benchmark_mode", NULL, AT_NONE); //Cmdline_benchmark_mode
cmdline_parm pilot_arg("-pilot", nullptr, AT_STRING); //Cmdline_pilot
cmdline_parm noninteractive_arg("-noninteractive", NULL, AT_NONE); //Cmdline_noninteractive
cmdline_parm json_profiling("-json_profiling", NULL, AT_NONE); //Cmdline_json_profiling
cmdline_parm show_video_info("-show_video_info", NULL, AT_NONE); //Cmdline_show_video_info
@@ -506,7 +488,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;
@@ -531,7 +512,6 @@ int Cmdline_reparse_mainhall = 0;
bool Cmdline_profile_write_file = false;
bool Cmdline_no_unfocus_pause = false;
bool Cmdline_benchmark_mode = false;
const char *Cmdline_pilot = nullptr;
bool Cmdline_noninteractive = false;
bool Cmdline_json_profiling = false;
bool Cmdline_frame_profile = false;
@@ -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)
{
@@ -802,7 +812,7 @@ void os_validate_parms(int argc, char *argv[])
if (!stricmp(token, "-help") || !stricmp(token, "--help") || !stricmp(token, "-h") || !stricmp(token, "-?")) {
printf("FreeSpace 2 Open, version %s\n", FS_VERSION_FULL);
printf("Website: http://scp.indiegames.us\n");
printf("Github (bug reporting): https://github.com/scp-fs2open/fs2open.github.com/issues\n\n");
printf("Mantis (bug reporting): http://scp.indiegames.us/mantis/\n\n");
printf("Usage: fs2_open [options]\n");
// not the prettiest thing but the job gets done
@@ -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';
}
}
@@ -1646,6 +1663,12 @@ bool SetCmdlineParams()
Cmdline_multi_log = 1;
}
// maybe use old-school client damage
if(client_dodamage.found()){
Cmdline_client_dodamage = 1;
}
// spew pof info
if(pof_spew.found()){
Cmdline_spew_pof_info = 1;
@@ -1698,7 +1721,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 +1822,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 +1847,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 +1946,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;
@@ -2125,11 +2131,6 @@ bool SetCmdlineParams()
Cmdline_benchmark_mode = true;
}
if (pilot_arg.found())
{
Cmdline_pilot = pilot_arg.str();
}
if (noninteractive_arg.found())
{
Cmdline_noninteractive = true;
@@ -2157,20 +2158,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 +2259,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;
}
+2 -6
View File
@@ -30,6 +30,7 @@ extern char *Cmdline_game_password;
extern char *Cmdline_rank_above;
extern char *Cmdline_rank_below;
extern int Cmdline_cd_check;
extern int Cmdline_client_dodamage;
extern int Cmdline_closed_game;
extern int Cmdline_freespace_no_music;
extern int Cmdline_freespace_no_sound;
@@ -83,13 +84,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 +124,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
@@ -146,7 +144,6 @@ extern int Cmdline_reparse_mainhall;
extern bool Cmdline_profile_write_file;
extern bool Cmdline_no_unfocus_pause;
extern bool Cmdline_benchmark_mode;
extern const char *Cmdline_pilot;
extern bool Cmdline_noninteractive;
extern bool Cmdline_json_profiling;
extern bool Cmdline_frame_profile;
@@ -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
+61 -740
View File
@@ -12,77 +12,72 @@
#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
* These are used to index a corresponding (analog) action, namely controlling the orientation angles and throttle.
*/
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 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
};
/*!
* 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
};
/*!
* Control configuration item type.
*/
typedef struct config_item {
short key_default; //!< default key bound to action
short joy_default; //!< default joystick button bound to action
char tab; //!< what tab (category) it belongs in
int indexXSTR; //!< what string index we should use to translate this with an XSTR
const char *text; //!< describes the action in the config screen
char type; //!< manner control should be checked in
short key_id; //!< actual key bound to action
short joy_id; //!< joystick button bound to action
int used; //!< has control been used yet in mission? If so, this is the timestamp
bool disabled; //!< whether this action should be available at all
bool continuous_ongoing;//!< whether this action is a continuous one and is currently ongoing
} config_item;
/*!
* All available actions
* This is the value of the id field in config_item
* The first group of items are ship targeting.
*
* 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
@@ -257,7 +252,7 @@ enum IoActionId : int {
DOWN_SLIDE_THRUST =111, //!< DOWN_SLIDE_THRUST
HUD_TARGETBOX_TOGGLE_WIREFRAME =112, //!< HUD_TARGETBOX_TOGGLE_WIREFRAME
VIEW_TOPDOWN =113, //!< VIEW_TOPDOWN
VIEW_TRACK_TARGET =114, //!< VIEW_TRACK_TARGET (Switfty) Toggle for VM_Track
VIEW_TRACK_TARGET =114, //!< VIEW_TRACK_TARGET
//!< @n
//!< AutoPilot - Kazan
@@ -284,743 +279,69 @@ 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:
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;
/*!
* 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 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
*/
bool invert_agnostic_equals(const CC_bind &B) const;
/*!
* Clears the binding and flags. Some flags are retained.
*/
void clear();
/*!
* True if not bound
*/
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.
};
/*!
* A pair of bindings.
* @note Please don't set the bindings directly, use ::take() instead.
*/
class CCB {
public:
CC_bind first; // The primary binding
CC_bind second; // The secondary binding
public:
CCB() = default;
CCB(const CCB& A) = default;
/*!
* Returns true if nothing is bound
*/
bool empty() const;
/*!
* Takes a given binding
*
* @param[in] A The bind to take
* @param[in] order 0 is primary, 1 is secondary, -1 is overwrite existing bind (if any)
*
* @note Should there be a binding to the same controller as the given binding, it is cleared
*/
void take(CC_bind A, int order);
/*!
* Clears both bindings
*/
void clear();
/*!
* Gets the value of btn if either CC_bind has the given CID
*
* @returns the btn of the bound controller, or
* @returns -1 if that controller is not bound
*/
short get_btn(CID) const;
/*!
* Assigns the contents of the given CCB to this CCB
*/
CCB& operator=(const CCB&) = default;
/*!
* Checks if the given CCB is exactly equal to this
*/
bool operator==(const CCB&) const;
/*!
* Checks if the given CCB differs from this
*/
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;
};
/*!
* A preset, a collection of bindings for use in Control_config with an associated name
*/
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&);
};
/*!
* Control configuration item type.
* @detail Contains binding info, documentation, behavior, etc. for a single control
*/
class CCI : public CCB {
public:
// Inherited from CCB
// CC_bind first;
// CC_bind second;
// Items Set in menu
char tab; //!< what tab (category) it belongs in
int indexXSTR; //!< what string index we should use to translate this with an XSTR 0 = None, 1= Use item index + CONTROL_CONFIG_XSTR, 2 <= use CCI::indexXSTR directly
SCP_string text; //!< describes the action in the config screen
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
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:
CCI() = default;
CCI(const CCI& A) = default;
/*!
* Assigns the contents of the given CCI to this CCI
*/
CCI& operator=(const CCI&);
/*!
* @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();
};
/*!
* Builder predicate to populate a ControlConfig vector with hardcoded default bindings.
*/
class CCI_builder {
public:
/*!
* Initilizes the given ControlConfig vector
*/
CCI_builder(SCP_vector<CCI>& _ControlConfig);
/*!
* Start a chain of factory methods. If there are any pre-init work to be done, that's done here.
*/
CCI_builder& start();
/*!
* End a chain of factory methods. If there any post-init work to be done, that's done here.
*/
void end();
/*!
* 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);
private:
CCI_builder(); // Only one builder per Control Config, so a default constructor is useless
SCP_vector<CCI>& ControlConfig;
};
extern int Failed_key_index;
extern int Axis_map_to[];
extern int Invert_axis[];
extern int Invert_axis_defaults[];
extern int Joy_dead_zone_size;
extern int Joy_sensitivity;
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 config_item Control_config[]; //!< Stores the keyboard configuration
extern SCP_vector<config_item*> Control_config_presets; // tabled control presets; pointers to config_item arrays
extern SCP_vector<SCP_string> Control_config_preset_names; // names for Control_config_presets (identical order of items)
extern const char **Scan_code_text;
extern const char **Joy_button_text;
extern bool Generate_controlconfig_table;
void control_config_common_init(); //!< initialize common control config stuff - call at game startup after localization has been initialized
void control_config_common_close(); //!< close common control config stuff - call at game shutdown
// 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.
/*!
* @brief Checks if either binding in the CCB has the given cid
*
* @returns 0 if its the first element, or
* @returns 1 if its the second element, or
* @returns -1 if neither
*/
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
*/
void control_config_common_init();
/*!
* @brief close common control config stuff - call at game shutdown
*/
void control_config_common_close();
/*!
* @brief init config menu
*/
void control_config_init();
/*!
* @brief do a frame of the config menu
*/
void control_config_do_frame(float frametime);
/*!
* @brief close config menu
*/
void control_config_close();
/*!
* @brief Cancel configuration of controls, revert any changes, return to previous menu/game state
*/
void control_config_cancel_exit();
/*!
* @brief Copies all bindings within preset into Control_config
*/
void control_config_use_preset(CC_preset &preset);
/*!
* @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.
*
* @details If find_override is set to true, then this returns the index of the action
*/
void control_config_reset_defaults(int presetnum=-1);
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.
*
* @param[in] key The default key combo (as cstring) 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
*
* @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
*/
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);
/*!
* @brief Checks how long a control has been active
*
* @param[in] id The IoActionId of the control to check
*
* @returns Time, in milliseconds, that the control has been active.
*
* @note This function is only to be used on CC_CONTINOUS type controls, or it will trigger an assertion
*/
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
*
* @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
*
* @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.
*/
void control_get_axes_readings(int *axis_v, float frame_time);
/**
* @brief Markes the given control (by IoActionId) as used
*
* @details Updates the ::used timestamp, triggers a script hook, and marks ::continous_ongoing as true
*/
void control_get_axes_readings(int *h, int *p, int *b, int *ta, int *tr);
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
*/
void control_config_clear();
/**
* @brief debug function used to indicate number of controls checked
*/
void control_check_indicate();
/**
* @brief Clears the used timestamp of all controls
*/
void control_config_clear_used_status();
/**
* @brief Applies sensitivity multiplier and deadzone histerisis to the raw axis value
*
* @param[in] raw The raw axis value to transform
*
* @return The transformed value
*/
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]);
}
}
+28 -38
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,19 +278,15 @@ 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;
}
}
if (status->subtitleStream == nullptr) {
// We don't have an external subtitle stream or loading from that file has failed
auto& current_language = Lcl_languages[lcl_get_current_lang_index()];
auto& current_language = Lcl_languages[Lcl_current_lang == LCL_UNTRANSLATED ? LCL_DEFAULT : Lcl_current_lang];
for (uint32_t i = 0; i < ctx->nb_streams; ++i) {
auto test_stream = ctx->streams[i];
@@ -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);
@@ -438,7 +428,7 @@ std::unique_ptr<InputStream> openInputStream(const SCP_string& name) {
std::unique_ptr<InputStream> openSubtitleStream(const SCP_string& name)
{
auto& current_language = Lcl_languages[lcl_get_current_lang_index()];
auto& current_language = Lcl_languages[Lcl_current_lang == LCL_UNTRANSLATED ? LCL_DEFAULT : Lcl_current_lang];
for (auto ext : CHECKED_SUBT_EXTENSIONS) {
SCP_string fileName;
@@ -463,7 +453,7 @@ bool FFMPEGDecoder::initialize(const SCP_string& fileName, const PlaybackPropert
{
SCP_string movieName = fileName;
// First make the file name lower case
SCP_tolower(movieName);
std::transform(movieName.begin(), movieName.end(), movieName.begin(), [](char c) { return (char)::tolower(c); });
// Then remove the extension
size_t dotPos = movieName.find('.');
+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
+116 -108
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);
@@ -392,6 +424,17 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
sip = &Ship_info[shipp->ship_info_index];
vaporize = (shipp->flags[Ship::Ship_Flags::Vaporize]);
if ( !hull_flag ) {
// Make vaporize debris seen from farther away
float dist = vm_vec_dist_quick( pos, &Eye_position );
if (vaporize) {
dist /= 2.0f;
}
if ( dist > 200.0f ) {
return NULL;
}
}
// try to maintain our soft limit
if (hull_flag && (Num_hull_pieces >= SOFT_LIMIT_DEBRIS_PIECES)) {
// cause oldest hull debris chunk to blow up
@@ -414,38 +457,8 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
db = &Debris[n];
if (!hull_flag) {
if (model_num >= 0) {
db->model_num = model_num;
db->submodel_num = Random::next(sip->generic_debris_num_submodels);
}
else if (vaporize) {
db->model_num = Debris_vaporize_model;
db->submodel_num = Random::next(Debris_num_submodels);
}
else {
db->model_num = Debris_model;
db->submodel_num = Random::next(Debris_num_submodels);
}
}
else {
db->model_num = model_num;
db->submodel_num = submodel_num;
}
float radius = submodel_get_radius(db->model_num, db->submodel_num);
// if its generic, maybe cull it if its too small and far
if (!hull_flag) {
float dist = vm_vec_dist_quick(pos, &Eye_position);
// Make vaporize debris seen from farther away
if (vaporize) {
dist /= 2.0f;
}
if (dist > radius * 200.0f) {
return nullptr;
}
}
//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)
{
@@ -455,16 +468,14 @@ 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.
db->lifeleft = 2.0f * (frand()) + 0.5f;
if (rand() < RAND_MAX/6) // Make some pieces blow up shortly after explosion.
db->lifeleft = 2.0f * (myrand() * RAND_MAX_1f) + 0.5f;
else {
db->flags.set(Debris_Flags::DoNotExpire);
db->lifeleft = -1.0f; // large hull pieces stay around forever
}
} else {
// small non-hull pieces should stick around longer the larger they are
// sqrtf should make sure its never too crazy long
db->lifeleft = (frand() * 2.0f + 0.1f) * sqrtf(radius);
} else {
db->lifeleft = (myrand() * RAND_MAX_1f) * 2.0f + 0.1f;
}
}
@@ -473,10 +484,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 +514,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;
@@ -519,9 +533,25 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
db->arc_frequency = 0;
}
if ( model_num < 0 ) {
if (vaporize) {
db->model_num = Debris_vaporize_model;
} else {
db->model_num = Debris_model;
}
db->submodel_num = (myrand()>>4) % Debris_num_submodels;
} else {
db->model_num = model_num;
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 );
db->next_fireball = timestamp_rand(500,2000); //start one 1/2 - 2 secs later
if ( pos == nullptr )
if ( pos == NULL )
pos = &source_obj->pos;
flagset<Object::Object_Flags> default_flags;
@@ -529,18 +559,10 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
default_flags.set(Object::Object_Flags::Physics);
default_flags.set(Object::Object_Flags::Collides, hull_flag != 0);
matrix orient = source_obj->orient;
if (!db->is_hull) {
// non-hull debris has no relation to its parent orientation
vec3d rand;
vm_vec_rand_vec(&rand);
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, &source_obj->orient, pos, radius, default_flags);
if ( objnum == -1 ) {
mprintf(("Couldn't create debris object -- out of object slots\n"));
return nullptr;
return NULL;
}
db->objnum = objnum;
@@ -548,8 +570,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 +612,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 +624,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 +635,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 +662,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 +688,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 +795,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 +831,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 +874,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 +898,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)
angles copy_angles = smi->angs;
// 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->angs = smi->prev_angs;
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
smi->angs = copy_angles;
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 +919,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_obj_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 +952,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_obj_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 +982,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 +1116,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 );
}
}
}
@@ -1103,7 +1127,7 @@ void debris_render(object * obj, model_draw_list *scene)
MONITOR_INC(NumHullDebrisRend, 1);
} else {
MONITOR_INC(NumSmallDebrisRend, 1);
// render_info.set_flags(MR_NO_LIGHTING);
render_info.set_flags(MR_NO_LIGHTING);
}
submodel_render_queue( &render_info, scene, pm, pmi, db->submodel_num, &obj->orient, &obj->pos );
@@ -1122,19 +1146,3 @@ bool debris_is_vaporized(debris *db)
{
return db->model_num == Debris_vaporize_model;
}
void create_generic_debris(object* ship_objp, vec3d* pos, float min_num_debris, float max_num_debris, float speed_mult, bool use_ship_debris) {
Assertion(ship_objp->type == OBJ_SHIP, "create_generic_debris called for a non-ship, only ships can spew debris!");
if (ship_objp->type != OBJ_SHIP)
return;
float num_debris = frand_range(min_num_debris, max_num_debris);
num_debris *= (Detail.num_small_debris + 0.5f) / 4.5f;
vec3d create_pos = *pos;
for (int i = 0; i < num_debris; i++) {
int model_num = use_ship_debris ? Ship_info[Ships[ship_objp->instance].ship_info_index].generic_debris_model_num : -1;
debris_create(ship_objp, model_num, -1, &create_pos, pos, 0, speed_mult);
}
}
+3 -5
View File
@@ -40,15 +40,17 @@ 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
int next_fireball; // When to start a fireball
int is_hull; // indicates whether this is a collideable, destructable piece of debris from the model, or just a generic debris fragment
int is_hull; // indicates a large hull chunk of debris
int species; // What species this is from. -1 if don't care.
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
@@ -80,8 +82,4 @@ void debris_remove_from_hull_list(debris *db);
bool debris_is_generic(debris *db);
bool debris_is_vaporized(debris *db);
// creates a burst of generic debris at hitpos from ship_objp, with a random number between min and max
// use_ship_debris is for whether the ship's generic debris should be used, or simply debris01.pof
void create_generic_debris(object* ship_objp, vec3d* hitpos, float min_num_debris, float max_num_debris, float speed_mult, bool use_ship_debris);
#endif // _DEBRIS_H
+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 -11
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;
@@ -65,23 +65,15 @@ void GetLightInfo(vec3 position, out vec3 lightDir, out float attenuation)
if (lightType == LT_TUBE) { // Tube light
float beamLength = length(beamVec);
vec3 beamDir = beamVec / beamLength;
//The actual 'lighting element' is shorter than the light volume cylinder
//To compensate the light is moved forward along the beam one radius and the length shortened
//this allows room for clean falloff of the light past the ends of beams.
vec3 adjustedLightPos = lightPosition - (beamDir * lightRadius);
beamLength = beamLength - (lightRadius * 2.0);
//adjustments having been made, lightdir needs recalculating
lightDir = adjustedLightPos - position.xyz;
// Get nearest point on line
float neardist = clamp(dot(lightDir, beamDir), 0.0, beamLength);
// Move back from the endpoint of the beam along the beam by the distance we calculated
vec3 nearest = adjustedLightPos - beamDir * neardist;
vec3 nearest = lightPosition - beamDir * neardist;
lightDir = nearest - position.xyz;
dist = length(lightDir);
if(dist > lightRadius) {
discard;
}
attenuation = 1.0 - clamp(sqrt(dist / lightRadius), 0.0, 1.0);
} else if (lightType == LT_CONE) {
float coneDot = dot(normalize(-lightDir), coneDir);
if(dualCone) {
@@ -98,7 +90,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 -4
View File
@@ -12,7 +12,7 @@ layout (std140) uniform genericData {
vec3 fog_color;
float fog_start;
float fog_density;
float fog_scale;
float zNear;
float zFar;
};
@@ -23,15 +23,17 @@ void main()
// If the scene is in fog then we change the color we operate on based on the depth of the pixel
float depth_val = texture(depth_tex, fragTexCoord.xy).x;
// Transform depth value from [0, 1] to [-1, 1]
float depth_normalized = 2 * depth_val - 1;
// Transform depth value from [-1, 1] to [0, 1]
float depth_normalized = fma(depth_val, 0.5, 0.5);
// Now we compute the depth value in projection space using the clipping plane information
float view_depth = 2.0 * zNear * zFar / (zFar + zNear - depth_normalized * (zFar - zNear));
if (isinf(view_depth)) {
fragOut0.rgb = color_in.rgb;
} else {
float fog_dist = clamp(1 - pow(fog_density, view_depth - fog_start) , 0.0, 1.0);
// This is the same formula that is also used by the main shader
float fog_dist = clamp((view_depth - fog_start) * 0.75 * fog_scale, 0.0, 1.0);
vec3 finalFogColor = srgb_to_linear(fog_color);
fragOut0.rgb = mix(color_in.rgb, finalFogColor, fog_dist);
+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);
}
Loaded 100 of 799 files, more files were not shown because too many files have changed in this diff. Show more