mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 22:56:30 -10:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0feab10244 | ||
|
|
b342ec50eb | ||
|
|
61ef36b238 | ||
|
|
fe84d11025 | ||
|
|
735e00d839 | ||
|
|
8bbe4733fb | ||
|
|
56f7dc2f8c |
No files matched your search
@@ -8,5 +8,3 @@ code/scpui/ @asarium
|
||||
code/scripting/ @asarium
|
||||
|
||||
code/controlconfig/ @z64555
|
||||
|
||||
code/model/modelanimation*.* @bmagnu
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
configuration: [FastDebug, Release]
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt-lin
|
||||
|
||||
@@ -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:
|
||||
@@ -39,65 +28,27 @@ jobs:
|
||||
echo -n "${{ steps.create_release.outputs.upload_url }}" > upload_url
|
||||
- name: Persist upload URL
|
||||
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: upload_url # name of artifact
|
||||
path: ${{ github.workspace }}/upload_url # source location of artifact to upload
|
||||
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-11e553c
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt-lin
|
||||
uses: actions/cache@v1
|
||||
# cache the Qt directory so we don't have to reinstall it every time
|
||||
# cache.inputs:
|
||||
# path - A list of files, directories, and wildcard patterns to cache and restore. See @actions/glob for supported patterns.
|
||||
# key - An explicit key for restoring and saving the cache
|
||||
# restore-keys - An ordered list of keys to use for restoring the cache if no cache hit occurred for key
|
||||
# cache.outputs:
|
||||
# cache-hit true when an exact match was found for the key, false otherwise.
|
||||
with:
|
||||
path: ${{ github.workspace }}/../Qt
|
||||
key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v2
|
||||
# installs Qt in the action workflow
|
||||
# install-qt-action.inputs:
|
||||
# version desired qt version to install
|
||||
# host Host platform of Qt, default to platform used by runs-on
|
||||
# target target platform of Qt, default to desktop
|
||||
# arch target architecture of Qt, only used for windows or android builds
|
||||
# dir root dir Qt will be installed to
|
||||
# install-deps if true, auto-install dependencies on linux. Default true
|
||||
# modules whitespace delimited list string of additional addon modules to install
|
||||
# archives whitespace delimited list string of Qt archives to install
|
||||
# cached if true, Don't download Qt but set ENVARs; use with cache.outputs.cache-hit
|
||||
# setup-python Set to false to skip using setup-python to find/download a valid python version
|
||||
# set-env if true, set ENVARs; default = false
|
||||
# aqtversion version of aqtinstall to use; default = 2.0.0
|
||||
# py7zrversion version of py7zr to use; default = 0.16.1
|
||||
# extra additional arguments to pass to aqinstall
|
||||
# ENVARS set:
|
||||
# Qt5_DIR / Qt6_DIR
|
||||
# Qt bin dir appended to PATH
|
||||
# QT_PLUGIN_PATH, QML2_IMPORT_PATH, PKG_CONFIG_PATH, LD_LIBRARY_PATH
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
dir: ${{ github.workspace }}/..
|
||||
@@ -106,10 +57,9 @@ jobs:
|
||||
aqtversion: ==1.1.3
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
# checks-out your repository under $GITHUB_WORKSPACE, so your workflow can access it.
|
||||
name: Checkout
|
||||
with:
|
||||
submodules: true # `true` to checkout submodules, `recursive` to recursively checkout submodules
|
||||
submodules: true
|
||||
- name: Configure CMake
|
||||
env:
|
||||
CONFIGURATION: ${{ matrix.configuration }}
|
||||
@@ -147,52 +97,38 @@ jobs:
|
||||
name: Checkout
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: '0' # value 0 to fetch all history and tags for all branches
|
||||
fetch-depth: '0'
|
||||
- name: Download upload URL
|
||||
uses: actions/download-artifact@v1
|
||||
# Downloads and extracts uploaded artifact by name
|
||||
# github_token default ${{secrets.GITHUB_TOKEN}}
|
||||
# name name of artifact to download
|
||||
# latest Download latest artifact (default true)
|
||||
# path Where to save the artifact, (default extract_here)
|
||||
# repo Source repo of artifact (default ${{github.repository}})
|
||||
with:
|
||||
name: upload_url
|
||||
path: .
|
||||
- name: Extract upload URL
|
||||
# Extract the URL from file and stuff it into a shell variable
|
||||
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:
|
||||
@@ -205,19 +141,16 @@ jobs:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
# Checkout repo
|
||||
name: Checkout
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache Qt
|
||||
# Cache Qt
|
||||
id: cache-qt-win
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ github.workspace }}/../Qt
|
||||
key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
|
||||
- name: Install Qt (32 bit)
|
||||
# Install Qt 32bit if matrix.arch == Win32
|
||||
uses: jurplel/install-qt-action@v2
|
||||
if: ${{ matrix.arch == 'Win32' }}
|
||||
with:
|
||||
@@ -227,7 +160,6 @@ jobs:
|
||||
cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
|
||||
aqtversion: ==0.8
|
||||
- name: Install Qt (64 bit)
|
||||
# Install Qt 64bit if matrix.arch == x64
|
||||
uses: jurplel/install-qt-action@v2
|
||||
if: ${{ matrix.arch == 'x64' }}
|
||||
with:
|
||||
@@ -272,47 +204,39 @@ jobs:
|
||||
with:
|
||||
name: windows-${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}
|
||||
path: ${{ github.workspace }}/build/install/*
|
||||
|
||||
windows_zip:
|
||||
name: Build Windows distribution zip
|
||||
needs: build_windows
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
|
||||
strategy:
|
||||
fail-fast: false # Run the other jobs in the matrix instead of failing them
|
||||
matrix:
|
||||
arch: [Win32, x64]
|
||||
simd: [SSE2, AVX]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Download upload URL
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: upload_url
|
||||
path: .
|
||||
|
||||
- name: Extract upload URL
|
||||
id: extractUploadUrl
|
||||
run: echo "::set-output name=upload_url::$(cat upload_url)"
|
||||
|
||||
- name: Download Release builds
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: windows-Release-${{ matrix.arch }}-${{ matrix.simd }}
|
||||
path: builds
|
||||
|
||||
- name: Download FastDebug builds
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: windows-FastDebug-${{ matrix.arch }}-${{ matrix.simd }}
|
||||
path: builds
|
||||
|
||||
- name: Create Distribution package
|
||||
id: generate_package
|
||||
working-directory: ./builds
|
||||
@@ -321,33 +245,21 @@ jobs:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
SIMD: ${{ matrix.simd }}
|
||||
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Windows
|
||||
|
||||
- name: Upload result package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ steps.generate_package.outputs.package_name }}
|
||||
path: ${{ steps.generate_package.outputs.package_path }}
|
||||
|
||||
- name: Upload debug package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ steps.generate_package.outputs.debug_name }}
|
||||
path: ${{ steps.generate_package.outputs.debug_path }}
|
||||
|
||||
- name: Publish packages
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
files: |
|
||||
${{ steps.generate_package.outputs.package_path }}
|
||||
${{ steps.generate_package.outputs.debug_path }}
|
||||
|
||||
post_build:
|
||||
name: Post builds to Nebula and Forum
|
||||
needs: [linux_zip, windows_zip]
|
||||
uses: ./.github/workflows/post-build-release.yaml
|
||||
with:
|
||||
linux_result: ${{ needs.build_linux.result }}
|
||||
windows_result: ${{ needs.build_windows.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 }}
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
configuration: [FastDebug, Release]
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt-lin
|
||||
|
||||
@@ -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-11e553c
|
||||
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
|
||||
@@ -1,64 +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: boolean
|
||||
windows_result: # job.result
|
||||
required: true
|
||||
type: boolean
|
||||
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: ${{ github.event.inputs.releaseTag }}
|
||||
|
||||
jobs:
|
||||
post_builds:
|
||||
name: Post builds on Nebula and the forums
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: pip install -r ci/post/requirements.txt
|
||||
|
||||
- name: Post Builds (Auto trigger)
|
||||
if: ${{ github.event_name == 'workflow_call' }}
|
||||
env:
|
||||
LINUX_RESULT: ${{ github.event.inputs.linux_result }}
|
||||
WINDOWS_RESULT: ${{ github.event.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= ${{ github.event.inputs.releaseTag }}"
|
||||
echo "repository= ${{ github.repository }}"
|
||||
python ci/post/main.py release
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
|
||||
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt-lin
|
||||
@@ -44,11 +44,6 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}
|
||||
|
||||
- name: Configure CMake
|
||||
env:
|
||||
CONFIGURATION: ${{ matrix.configuration }}
|
||||
-1
@@ -8,7 +8,6 @@ on:
|
||||
|
||||
jobs:
|
||||
build_images:
|
||||
if: github.repository == 'scp-fs2open/fs2open.github.com'
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [FastDebug, Release]
|
||||
@@ -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
|
||||
@@ -1,65 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
import semantic_version
|
||||
from mako.template import Template
|
||||
|
||||
from file_list import ReleaseFile
|
||||
|
||||
|
||||
class ForumAPI:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def create_post(self, title, content, board):
|
||||
print(self.config["hlp"]["api"])
|
||||
print(self.config["hlp"]["key"])
|
||||
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)
|
||||
|
||||
def post_nightly(self, date, revision, files, log, success):
|
||||
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):
|
||||
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...")
|
||||
|
||||
self.create_post(title, rendered, self.config["release"]["hlp_board"])
|
||||
@@ -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")
|
||||
-260
@@ -1,260 +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\(FSO_VERSION_MAJOR (\d+)\)")
|
||||
MINOR_VERSION_PATTERN = re.compile("set_if_not_defined\(FSO_VERSION_MINOR (\d+)\)")
|
||||
BUILD_VERSION_PATTERN = re.compile("set_if_not_defined\(FSO_VERSION_BUILD (\d+)\)")
|
||||
|
||||
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 get_source_version(date_version: datetime, tag_name: str) -> semantic_version.Version:
|
||||
"""! Retrieves the build's version from `version.cmake`, forms it as a string, and appends the date
|
||||
|
||||
@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: `MAJOR_VERSION.MINOR_VERSION.BUILD_VERSION` from version.cmake, or
|
||||
@return If rc: `MAJOR_VERSION.MINOR_VERSION.BUILD_VERSION` from tag_name, or
|
||||
@return If nightly: `MAJOR_VERSION.MINOR_VERSION.BUILD_VERSION-date` from version.cmake
|
||||
"""
|
||||
major = minor = build = version = 0
|
||||
with open(os.path.join("..", "..", "cmake", "version.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)
|
||||
|
||||
if "rc" in tag_name.lower():
|
||||
# Release candidates idenfity with their unstable version (ex: 21.5.0-03052022) within the game and the logs, but
|
||||
# the builds are named with the target release version (ex: 22.0.0). Since this script works on the builds we
|
||||
# need to grab the target release version from tag_name
|
||||
x = tag_name.upper().split("_")
|
||||
# "release" = x[0], year = x[1], major = x[2], minor = x[3], build = x[4],
|
||||
version = semantic_version.Version("{}.{}.{}-{}".format(x[1], x[2], x[3], x[4]))
|
||||
|
||||
elif "release" in tag_name.lower():
|
||||
version = semantic_version.Version("{}.{}.{}".format(major, minor, build))
|
||||
|
||||
elif "nightly" in tag_name.lower():
|
||||
version = semantic_version.Version("{}.{}.{}-{}".format(major, minor, build, date_version))
|
||||
|
||||
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)
|
||||
|
||||
tag_name = os.environ["RELEASE_TAG"] ##!< commit tag string
|
||||
date = datetime.now() ##!< current date
|
||||
version = get_source_version(date.strftime(DATEFORMAT_VERSION), tag_name) ##!< form full version string
|
||||
success = os.environ["LINUX_RESULT"] == "success" and os.environ["WINDOWS_RESULT"] == "success" ##!< true if both linux and windows builds successful
|
||||
|
||||
# check that tag_name is actually in the git repo and find the previous tag release
|
||||
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!" % tag_name)
|
||||
sys.exit(1)
|
||||
|
||||
if 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
|
||||
if not tag_name.startswith("release_"):
|
||||
# Safety check tag is from a 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)
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
if not tag_name.startswith("nightly_"):
|
||||
# safety check tag is from a 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)
|
||||
|
||||
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)
|
||||
|
||||
# Publish nightly builds 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
|
||||
fapi = forum.ForumAPI(config)
|
||||
fapi.post_nightly(date.strftime(DATEFORMAT_FORUM), os.environ["GITHUB_SHA"], files, log, success)
|
||||
|
||||
main()
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -1,3 +0,0 @@
|
||||
Mako==1.1.5
|
||||
requests==2.26.0
|
||||
semantic-version==2.8.5
|
||||
@@ -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"])
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ 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_MINOR 0)
|
||||
set_if_not_defined(FSO_VERSION_MAJOR 21)
|
||||
set_if_not_defined(FSO_VERSION_MINOR 1)
|
||||
set_if_not_defined(FSO_VERSION_BUILD 0)
|
||||
|
||||
# A numeric revision e.g. the CI build number
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+4
-6
@@ -330,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?
|
||||
@@ -405,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.
|
||||
};
|
||||
|
||||
@@ -490,9 +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
|
||||
} ai_info;
|
||||
|
||||
// Goober5000
|
||||
@@ -607,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
|
||||
@@ -659,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);
|
||||
|
||||
+1
-8
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace AI {
|
||||
FLAG_LIST(AI_Flags) {
|
||||
Formation_wing, // Fly in formation as part of wing. Also used when flying waypoints.
|
||||
Formation_wing, // Fly in formation as part of wing.
|
||||
Awaiting_repair, // Awaiting a repair ship.
|
||||
Being_repaired, // Currently docked with repair ship.
|
||||
Repairing, // Repairing a ship (or going to repair a ship)
|
||||
@@ -137,13 +137,6 @@ namespace AI {
|
||||
Improved_missile_avoidance,
|
||||
Friendlies_use_countermeasure_firechance,
|
||||
Improved_subsystem_attack_pathing,
|
||||
Fixed_ship_weapon_collision,
|
||||
No_shield_damage_from_ship_collisions,
|
||||
Reset_last_hit_target_time_for_player_hits,
|
||||
Fighterbay_arrivals_use_carrier_orient,
|
||||
Fighterbay_departures_use_carrier_orient,
|
||||
Prevent_negative_turret_ammo,
|
||||
Fix_keep_safe_distance,
|
||||
|
||||
NUM_VALUES
|
||||
};
|
||||
|
||||
+7
-76
@@ -58,26 +58,6 @@ int ai_path_type_match(char *p)
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
const char* AI_secondary_range_aware_select_modes[] = {
|
||||
"retail",
|
||||
"aware",
|
||||
};
|
||||
|
||||
int Num_ai_secondary_range_aware_select_modes = sizeof(AI_secondary_range_aware_select_modes) / sizeof(char*);
|
||||
|
||||
int ai_secondary_range_select_mode_match(char* p)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < Num_ai_secondary_range_aware_select_modes; i++)
|
||||
{
|
||||
if (!stricmp(AI_secondary_range_aware_select_modes[i], p))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void parse_ai_profiles_tbl(const char *filename)
|
||||
{
|
||||
int i;
|
||||
@@ -468,10 +448,7 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
|
||||
set_flag(profile, "$firing requires exact los:", AI::Profile_Flags::Require_exact_los);
|
||||
|
||||
set_flag(profile, "$fighterbay arrivals use carrier orientation:", AI::Profile_Flags::Fighterbay_arrivals_use_carrier_orient);
|
||||
|
||||
set_flag(profile, "$fighterbay departures use carrier orientation:", AI::Profile_Flags::Fighterbay_departures_use_carrier_orient);
|
||||
|
||||
profile->ai_path_mode = AI_PATH_MODE_NORMAL;
|
||||
if (optional_string("$ai path mode:"))
|
||||
{
|
||||
stuff_string(buf, F_NAME, NAME_LENGTH);
|
||||
@@ -504,12 +481,14 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
if (path_radii >= Minimum_subsystem_path_pt_dist) {
|
||||
profile->subsystem_path_radii = path_radii;
|
||||
} else {
|
||||
mprintf(("Warning: \"$override radius for subsystem path points:\" should be >= %i (read %i). Value will not be used.\n", Minimum_subsystem_path_pt_dist, path_radii));
|
||||
mprintf(("Warning: \"$override radius for subsystem path points:\" should be >= %i (read %i). Value will not be used. ", Minimum_subsystem_path_pt_dist, path_radii));
|
||||
}
|
||||
}
|
||||
|
||||
set_flag(profile, "$use POF radius for subsystem path points:", AI::Profile_Flags::Use_subsystem_path_point_radii);
|
||||
|
||||
profile->bay_arrive_speed_mult = 1.0f;
|
||||
profile->bay_depart_speed_mult = 1.0f;
|
||||
if (optional_string("$bay arrive speed multiplier:")) {
|
||||
stuff_float(&profile->bay_arrive_speed_mult);
|
||||
}
|
||||
@@ -565,40 +544,6 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
|
||||
set_flag(profile, "$improved subsystem attack pathing:", AI::Profile_Flags::Improved_subsystem_attack_pathing);
|
||||
|
||||
set_flag(profile, "$fixed ship-weapon collisions:", AI::Profile_Flags::Fixed_ship_weapon_collision);
|
||||
|
||||
//Intention is to expand this feature to include a preference for close or long range weapons
|
||||
//hence using something besides a simple flag.
|
||||
if (optional_string("$AI secondary range awareness:"))
|
||||
{
|
||||
stuff_string(buf, F_NAME, NAME_LENGTH);
|
||||
int j = ai_secondary_range_select_mode_match(buf);
|
||||
if (j >= 0) {
|
||||
profile->ai_range_aware_secondary_select_mode = j;
|
||||
}
|
||||
else {
|
||||
Warning(LOCATION, "Invalid ai secondary range awareness mode '%s' specified", buf);
|
||||
}
|
||||
}
|
||||
|
||||
set_flag(profile, "$no shield damage from ship collisions:", AI::Profile_Flags::No_shield_damage_from_ship_collisions);
|
||||
|
||||
set_flag(profile, "$reset last_hit_target_time for player hits:", AI::Profile_Flags::Reset_last_hit_target_time_for_player_hits);
|
||||
|
||||
if (optional_string("$turret target recheck time:"))
|
||||
{
|
||||
stuff_float(&profile->turret_target_recheck_time);
|
||||
if (profile->turret_target_recheck_time < 0) {
|
||||
Warning(LOCATION, "Turret target recheck time must be positive.");
|
||||
profile->turret_target_recheck_time = 2000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
set_flag(profile, "$prevent negative turret ammo:", AI::Profile_Flags::Prevent_negative_turret_ammo);
|
||||
|
||||
set_flag(profile, "$fix keep-safe-distance:", AI::Profile_Flags::Fix_keep_safe_distance);
|
||||
|
||||
|
||||
// if we've been through once already and are at the same place, force a move
|
||||
if (saved_Mp && (saved_Mp == Mp))
|
||||
{
|
||||
@@ -670,13 +615,11 @@ void ai_profile_t::reset()
|
||||
|
||||
flags.reset();
|
||||
|
||||
ai_path_mode = AI_PATH_MODE_NORMAL;
|
||||
ai_path_mode = 0;
|
||||
subsystem_path_radii = 0;
|
||||
bay_arrive_speed_mult = 1.0f;
|
||||
bay_depart_speed_mult = 1.0f;
|
||||
bay_arrive_speed_mult = 0;
|
||||
bay_depart_speed_mult = 0;
|
||||
second_order_lead_predict_factor = 0;
|
||||
ai_range_aware_secondary_select_mode = AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL;
|
||||
turret_target_recheck_time = 2000.0f;
|
||||
|
||||
for (int i = 0; i < NUM_SKILL_LEVELS; ++i) {
|
||||
max_incoming_asteroids[i] = 0;
|
||||
@@ -754,16 +697,4 @@ void ai_profile_t::reset()
|
||||
if (mod_supports_version(3, 7, 2)) {
|
||||
flags.set(AI::Profile_Flags::Fix_ramming_stationary_targets_bug);
|
||||
}
|
||||
// and this flag has been enabled ever since 3.6.10
|
||||
if (mod_supports_version(3, 6, 10)) {
|
||||
flags.set(AI::Profile_Flags::Reset_last_hit_target_time_for_player_hits);
|
||||
}
|
||||
if (mod_supports_version(21, 4, 0)) {
|
||||
flags.set(AI::Profile_Flags::Fixed_ship_weapon_collision);
|
||||
}
|
||||
if (mod_supports_version(22, 0, 0)) {
|
||||
flags.set(AI::Profile_Flags::Fighterbay_arrivals_use_carrier_orient);
|
||||
flags.set(AI::Profile_Flags::Prevent_negative_turret_ammo);
|
||||
flags.set(AI::Profile_Flags::Fix_keep_safe_distance);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
+16
-29
@@ -17,12 +17,12 @@
|
||||
#include "io/timer.h"
|
||||
#include "math/staticrand.h"
|
||||
#include "mission/missionparse.h"
|
||||
#include "object/objcollide.h"
|
||||
#include "object/object.h"
|
||||
#include "ship/afterburner.h"
|
||||
#include "ship/ship.h"
|
||||
#include "utils/Random.h"
|
||||
#include "weapon/weapon.h"
|
||||
#include "object/objcollide.h"
|
||||
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma optimize("", off)
|
||||
@@ -407,7 +407,7 @@ int ai_big_maybe_follow_subsys_path(int do_dot_check)
|
||||
|
||||
// get world pos of eye (stored in geye)
|
||||
ep = &(pm->view_positions[shipp->current_viewpoint]);
|
||||
model_instance_local_to_global_point( &geye, &ep->pnt, pm, pmi, 0, &Pl_objp->orient, &Pl_objp->pos );
|
||||
model_instance_find_world_point( &geye, &ep->pnt, pm, pmi, 0, &Pl_objp->orient, &Pl_objp->pos );
|
||||
|
||||
// get world pos of subsystem
|
||||
vm_vec_unrotate(&gsubpos, &aip->targeted_subsys->system_info->pnt, &En_objp->orient);
|
||||
@@ -549,7 +549,7 @@ bool ai_new_maybe_reposition_attack_subsys() {
|
||||
|
||||
// get world pos of eye (stored in geye)
|
||||
ep = &(pm->view_positions[Ships[Pl_objp->instance].current_viewpoint]);
|
||||
model_local_to_global_point(&geye, &ep->pnt, pm->id, 0, &Pl_objp->orient, &Pl_objp->pos);
|
||||
model_find_world_point(&geye, &ep->pnt, pm->id, 0, &Pl_objp->orient, &Pl_objp->pos);
|
||||
|
||||
// get world pos of subsystem
|
||||
vm_vec_unrotate(&gsubpos, &aip->targeted_subsys->system_info->pnt, &En_objp->orient);
|
||||
@@ -777,28 +777,14 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
|
||||
|
||||
ai_turn_towards_vector(enemy_pos, Pl_objp, nullptr, rel_pos, 0.0f, 0, rvec);
|
||||
|
||||
// set an optimal range if applicable
|
||||
float optimal_range = 0.0f;
|
||||
ship_weapon* weapons = &Ships[Pl_objp->instance].weapons;
|
||||
weapon_info* wip = nullptr;
|
||||
ship* shipp = &Ships[Pl_objp->instance];
|
||||
|
||||
if (weapons->num_primary_banks >= 1 && weapons->current_primary_bank >= 0 && !shipp->flags[Ship::Ship_Flags::Primaries_locked]) {
|
||||
wip = &Weapon_info[weapons->primary_bank_weapons[weapons->current_primary_bank]];
|
||||
} else if (weapons->num_secondary_banks >= 1 && weapons->current_secondary_bank >= 0 && !shipp->flags[Ship::Ship_Flags::Secondaries_locked]) {
|
||||
wip = &Weapon_info[weapons->secondary_bank_weapons[weapons->current_secondary_bank]];
|
||||
}
|
||||
|
||||
if (wip != nullptr && wip->optimum_range > 0)
|
||||
optimal_range = wip->optimum_range;
|
||||
|
||||
// calc range of primary weapon
|
||||
weapon_travel_dist = ai_get_weapon_dist(&Ships[Pl_objp->instance].weapons);
|
||||
|
||||
if ( aip->targeted_subsys != NULL ) {
|
||||
if (dist_to_enemy > (optimal_range > 0.0f ? optimal_range : (weapon_travel_dist-20))) {
|
||||
if (dist_to_enemy > (weapon_travel_dist-20)) {
|
||||
accelerate_ship(aip, 1.0f);
|
||||
|
||||
ship *shipp = &Ships[Pl_objp->instance];
|
||||
if ((aip->ai_flags[AI::AI_Flags::Free_afterburner_use] || aip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use]) && !(shipp->flags[Ship::Ship_Flags::Afterburner_locked]) && (dot_to_enemy > 0.75f)) {
|
||||
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
||||
afterburners_start(Pl_objp);
|
||||
@@ -823,7 +809,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
|
||||
} else if (dot_to_enemy < 0.75f) {
|
||||
accel = 0.75f;
|
||||
} else {
|
||||
if (dist_to_enemy > (optimal_range > 0.0f ? optimal_range : (weapon_travel_dist/2.0f))) {
|
||||
if (dist_to_enemy > weapon_travel_dist/2.0f) {
|
||||
accel = 1.0f;
|
||||
} else {
|
||||
accel = 1.0f - dot_to_enemy;
|
||||
@@ -840,6 +826,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
|
||||
} else {
|
||||
accelerate_ship(aip, accel);
|
||||
|
||||
ship *shipp = &Ships[Pl_objp->instance];
|
||||
if ((aip->ai_flags[AI::AI_Flags::Free_afterburner_use] || aip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use]) && !(shipp->flags[Ship::Ship_Flags::Afterburner_locked]) && (accel > 0.95f)) {
|
||||
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
||||
afterburners_start(Pl_objp);
|
||||
@@ -927,7 +914,7 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank]) && weapon_target_satisfies_lock_restrictions(swip, En_objp)) {
|
||||
if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank])) {
|
||||
float firing_range;
|
||||
if (swip->wi_flags[Weapon::Info_Flags::Local_ssm])
|
||||
firing_range=swip->lssm_lock_range;
|
||||
@@ -957,11 +944,11 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
|
||||
} else {
|
||||
t = swip->fire_wait;
|
||||
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
||||
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
|
||||
swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
|
||||
} else {
|
||||
swp->burst_counter[current_bank_adjusted] = 0;
|
||||
}
|
||||
swp->burst_seed[current_bank_adjusted] = Random::next();
|
||||
swp->burst_seed[current_bank_adjusted] = rand32();
|
||||
}
|
||||
} else {
|
||||
if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
|
||||
@@ -970,11 +957,11 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
|
||||
} else {
|
||||
t = set_secondary_fire_delay(aip, temp_shipp, swip, false);
|
||||
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
||||
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
|
||||
swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
|
||||
} else {
|
||||
swp->burst_counter[current_bank_adjusted] = 0;
|
||||
}
|
||||
swp->burst_seed[current_bank_adjusted] = Random::next();
|
||||
swp->burst_seed[current_bank_adjusted] = rand32();
|
||||
}
|
||||
}
|
||||
swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
|
||||
@@ -1461,7 +1448,7 @@ void ai_big_strafe_attack()
|
||||
if (ai_new_maybe_reposition_attack_subsys())
|
||||
return;
|
||||
} else if (ai_big_maybe_follow_subsys_path(0))
|
||||
return;
|
||||
return
|
||||
|
||||
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
|
||||
if ( ai_big_strafe_maybe_retreat(&target_pos) )
|
||||
@@ -1484,7 +1471,7 @@ void ai_big_strafe_attack()
|
||||
if ( t > 0.0f && t < 1.5f ) {
|
||||
// set up goal_point for avoid path to turn towards
|
||||
aip->goal_point = Pl_objp->pos;
|
||||
switch(Random::next(4)) {
|
||||
switch(rand()%4) {
|
||||
case 0:
|
||||
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, 2.0f);
|
||||
break;
|
||||
@@ -1716,7 +1703,7 @@ void ai_big_strafe_retreat1()
|
||||
if (Missiontime - aip->submode_start_time > fl2f(1.50f)) {
|
||||
// set up goal_point for avoid path to turn towards
|
||||
aip->prev_goal_point = Pl_objp->pos;
|
||||
switch(Random::next(4)) {
|
||||
switch(rand()%4) {
|
||||
case 0:
|
||||
vm_vec_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec);
|
||||
break;
|
||||
|
||||
+236
-555
File diff suppressed because it is too large.
Load diff
+7
-20
@@ -23,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
|
||||
@@ -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 );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
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 )
|
||||
|
||||
@@ -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
|
||||
+175
-146
@@ -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,124 @@ 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]) {
|
||||
// Since we no longer maintain world_to_turret_matrix, regenerate it here.
|
||||
vec3d turret_norm;
|
||||
matrix turret_matrix, world_to_turret_matrix;
|
||||
model_instance_find_world_dir(&turret_norm, &tp->turret_norm, Ships[objp->instance].model_instance_num, tp->subobj_num, &vmd_identity_matrix);
|
||||
vm_vector_2_matrix(&turret_matrix, &turret_norm, nullptr, nullptr);
|
||||
vm_matrix_x_matrix(&world_to_turret_matrix, &objp->orient, &turret_matrix);
|
||||
|
||||
vec3d temp_vec2;
|
||||
vm_vec_rotate(&temp_vec2, &temp_vec, &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, &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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1138,11 +1196,17 @@ int find_turret_enemy(ship_subsys *turret_subsys, int objnum, vec3d *tpos, vec3d
|
||||
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) {
|
||||
@@ -1190,9 +1254,9 @@ void ship_get_global_turret_info(const object *objp, const model_subsystem *tp,
|
||||
{
|
||||
auto model_instance_num = Ships[objp->instance].model_instance_num;
|
||||
if (gpos)
|
||||
model_instance_local_to_global_point(gpos, &vmd_zero_vector, model_instance_num, tp->subobj_num, &objp->orient, &objp->pos);
|
||||
model_instance_find_world_point(gpos, &vmd_zero_vector, model_instance_num, tp->subobj_num, &objp->orient, &objp->pos);
|
||||
if (gvec)
|
||||
model_instance_local_to_global_dir(gvec, &tp->turret_norm, model_instance_num, tp->subobj_num, &objp->orient, true);
|
||||
model_instance_find_world_dir(gvec, &tp->turret_norm, model_instance_num, tp->subobj_num, &objp->orient);
|
||||
}
|
||||
|
||||
void turret_ai_update_aim(ai_info *aip, object *En_Objp, ship_subsys *ss);
|
||||
@@ -1220,15 +1284,15 @@ void ship_get_global_turret_gun_info(object *objp, ship_subsys *ssp, vec3d *gpos
|
||||
|
||||
gun_pos = &tp->turret_firing_point[ssp->turret_next_fire_pos % tp->turret_num_firing_points];
|
||||
|
||||
model_instance_local_to_global_point(gpos, gun_pos, pm, pmi, tp->turret_gun_sobj, &objp->orient, &objp->pos);
|
||||
model_instance_find_world_point(gpos, gun_pos, pm, pmi, tp->turret_gun_sobj, &objp->orient, &objp->pos);
|
||||
|
||||
if (use_angles) {
|
||||
model_instance_local_to_global_dir(gvec, &tp->turret_norm, pm, pmi, tp->turret_gun_sobj, &objp->orient);
|
||||
model_instance_find_world_dir(gvec, &tp->turret_norm, pm, pmi, tp->turret_gun_sobj, &objp->orient);
|
||||
} else if (tp->flags[Model::Subsystem_Flags::Share_fire_direction]) {
|
||||
vec3d avg, tmp_pos, tmp_target, enemy_point, turret_norm;
|
||||
vm_vec_avg_n(&avg, tp->turret_num_firing_points, tp->turret_firing_point);
|
||||
|
||||
model_instance_local_to_global_point(&tmp_pos, &avg, pm, pmi, tp->turret_gun_sobj, &objp->orient, &objp->pos);
|
||||
model_instance_find_world_point(&tmp_pos, &avg, pm, pmi, tp->turret_gun_sobj, &objp->orient, &objp->pos);
|
||||
|
||||
if (targetp == nullptr) {
|
||||
ship* shipp = &Ships[Objects[ssp->parent_objnum].instance];
|
||||
@@ -1253,7 +1317,7 @@ void ship_get_global_turret_gun_info(object *objp, ship_subsys *ssp, vec3d *gpos
|
||||
} else {
|
||||
if ((lep->type == OBJ_SHIP) && (Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge())) {
|
||||
// the turret norm here is from the perspective of the base submodel, not the gun submodel
|
||||
model_instance_local_to_global_dir(&turret_norm, &tp->turret_norm, pm, pmi, pm->submodel[tp->subobj_num].parent, &objp->orient);
|
||||
model_instance_find_world_dir(&turret_norm, &tp->turret_norm, pm, pmi, tp->subobj_num, &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 +1448,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 +1491,7 @@ float aifft_compute_turret_dot(object *objp, object *enemy_objp, vec3d *abs_gunp
|
||||
if (ship_subsystem_in_sight(enemy_objp, enemy_subsysp, abs_gunposp, &subobj_pos, 1, &dot_out, &vector_out)) {
|
||||
vec3d turret_norm;
|
||||
|
||||
model_instance_local_to_global_dir(&turret_norm, &turret_subsysp->system_info->turret_norm, Ships[objp->instance].model_instance_num, turret_subsysp->system_info->subobj_num, &objp->orient, true);
|
||||
model_instance_find_world_dir(&turret_norm, &turret_subsysp->system_info->turret_norm, Ships[objp->instance].model_instance_num, turret_subsysp->system_info->subobj_num, &objp->orient);
|
||||
float dot_return = vm_vec_dot(&turret_norm, &vector_out);
|
||||
|
||||
if (Ai_info[Ships[objp->instance].ai_index].ai_profile_flags[AI::Profile_Flags::Smart_subsystem_targeting_for_turrets]) {
|
||||
@@ -1605,10 +1674,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 +1685,12 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
|
||||
} else {
|
||||
wait *= wip->fire_wait;
|
||||
}
|
||||
if ((burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
||||
turret->weapons.burst_counter[weapon_num] = Random::next(burst_shots);
|
||||
turret->weapons.burst_seed[weapon_num] = Random::next();
|
||||
if ((wip->burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
||||
turret->weapons.burst_counter[weapon_num] = (myrand() % wip->burst_shots);
|
||||
turret->weapons.burst_seed[weapon_num] = rand32();
|
||||
} else {
|
||||
turret->weapons.burst_counter[weapon_num] = 0;
|
||||
turret->weapons.burst_seed[weapon_num] = Random::next();
|
||||
turret->weapons.burst_seed[weapon_num] = rand32();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1634,7 +1701,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
|
||||
@@ -1790,19 +1857,8 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
|
||||
vm_vector_2_matrix(&turret_orient, firing_vec, nullptr, nullptr);
|
||||
|
||||
// grab and set some burst data before turret_set_next_fire_timestamp wipes it
|
||||
int old_burst_seed = swp->burst_seed[weapon_num];
|
||||
int old_burst_counter = swp->burst_counter[weapon_num];
|
||||
// only used by type 5 beams
|
||||
if (turret->weapons.burst_counter[weapon_num] == 0) {
|
||||
swp->per_burst_rot += wip->b_info.t5info.per_burst_rot;
|
||||
if (swp->per_burst_rot > PI2)
|
||||
swp->per_burst_rot -= PI2;
|
||||
else if (swp->per_burst_rot < -PI2)
|
||||
swp->per_burst_rot += PI2;
|
||||
}
|
||||
|
||||
// set next fire timestamp for the turret
|
||||
int old_burst_seed = swp->burst_seed[weapon_num];
|
||||
if (last_shot_in_salvo)
|
||||
turret_set_next_fire_timestamp(weapon_num, wip, turret, parent_aip);
|
||||
|
||||
@@ -1828,8 +1884,6 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
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);
|
||||
@@ -1840,11 +1894,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", &Objects[turret->turret_enemy_objnum]);
|
||||
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum]);
|
||||
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
|
||||
@@ -1856,7 +1908,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
swp->current_secondary_bank = 0;
|
||||
}
|
||||
int bank_to_fire = swp->current_secondary_bank;
|
||||
if ((turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) && !ship_secondary_has_ammo(swp, bank_to_fire)) {
|
||||
if ((turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) && (swp->secondary_bank_ammo[bank_to_fire] < 0)) {
|
||||
if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns])) {
|
||||
swp->current_secondary_bank++;
|
||||
if (swp->current_secondary_bank >= swp->num_secondary_banks) {
|
||||
@@ -1904,10 +1956,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++;
|
||||
@@ -1939,17 +1988,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++;
|
||||
@@ -1983,11 +2026,9 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
|
||||
// AL 1-6-97: Store pointer to turret subsystem
|
||||
wp->turret_subsys = turret;
|
||||
|
||||
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
|
||||
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", objp, "Beam", nullptr, "Target", &Objects[turret->turret_enemy_objnum]);
|
||||
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[parent_objnum]);
|
||||
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]) {
|
||||
@@ -2050,7 +2091,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;
|
||||
}
|
||||
|
||||
@@ -2101,11 +2142,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]);
|
||||
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) {
|
||||
@@ -2136,15 +2175,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;
|
||||
@@ -2319,12 +2356,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
|
||||
@@ -2337,7 +2374,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
|
||||
}
|
||||
|
||||
// Don't try to fire beyond weapon_limit_range (or within min range)
|
||||
if (dist_to_enemy < weapon_min_range || dist_to_enemy > weapon_firing_range) {
|
||||
if (dist_to_enemy < WeaponMinRange || dist_to_enemy > weapon_firing_range) {
|
||||
// it's possible another weapon is in range, but if not,
|
||||
// we will end up selecting a new target
|
||||
continue;
|
||||
@@ -2404,11 +2441,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
|
||||
@@ -2475,10 +2507,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2667,15 +2698,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;
|
||||
}
|
||||
}
|
||||
@@ -2788,20 +2812,25 @@ 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);
|
||||
|
||||
// Since we no longer maintain world_to_turret_matrix, regenerate it here.
|
||||
object *objp = &Objects[ss->parent_objnum];
|
||||
vec3d turret_norm;
|
||||
matrix turret_matrix, world_to_turret_matrix;
|
||||
model_instance_find_world_dir(&turret_norm, &tp->turret_norm, Ships[objp->instance].model_instance_num, tp->subobj_num, &vmd_identity_matrix);
|
||||
vm_vector_2_matrix(&turret_matrix, &turret_norm, nullptr, nullptr);
|
||||
vm_matrix_x_matrix(&world_to_turret_matrix, &objp->orient, &turret_matrix);
|
||||
|
||||
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, &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;
|
||||
}
|
||||
}
|
||||
@@ -2812,7 +2841,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);
|
||||
|
||||
+145
-209
@@ -55,6 +55,8 @@ const float LARGE_DEBRIS_WEIGHT = 1.0f;
|
||||
|
||||
int Asteroids_enabled = 1;
|
||||
int Num_asteroids = 0;
|
||||
int Asteroid_throw_objnum = -1; // Object index of ship to throw asteroids at.
|
||||
int Next_asteroid_throw;
|
||||
|
||||
SCP_vector< asteroid_info > Asteroid_info;
|
||||
asteroid Asteroids[MAX_ASTEROIDS];
|
||||
@@ -71,55 +73,6 @@ float Asteroid_icon_closeup_zoom;
|
||||
#define ASTEROID_UPDATE_COLLIDE_TIMESTAMP 2000 // how often asteroid is checked for impending collisions with escort ships
|
||||
#define ASTEROID_MIN_COLLIDE_TIME 24 // time in seconds to check for asteroid colliding
|
||||
|
||||
SCP_vector<SCP_string> Asteroid_target_ships;
|
||||
bool Default_asteroid_throwing_behavior = true;
|
||||
|
||||
typedef struct asteroid_target {
|
||||
int objnum;
|
||||
int signature;
|
||||
int throw_stamp;
|
||||
int incoming_asteroids;
|
||||
} asteroid_target;
|
||||
|
||||
// if default throwing behavior is enabled, then this should always have at most 1 target or possibly none, determined the retail way
|
||||
// if not, then this is whatever number of mission-specified ships (after they arrive, list is sanitized when they exit)
|
||||
SCP_vector<asteroid_target> Asteroid_targets;
|
||||
|
||||
|
||||
/**
|
||||
* Return number of asteroids expected to collide with a ship.
|
||||
*/
|
||||
static int count_incident_asteroids(int target_objnum)
|
||||
{
|
||||
object* asteroid_objp;
|
||||
int count;
|
||||
|
||||
count = 0;
|
||||
|
||||
for (asteroid_objp = GET_FIRST(&obj_used_list); asteroid_objp != END_OF_LIST(&obj_used_list); asteroid_objp = GET_NEXT(asteroid_objp)) {
|
||||
if (asteroid_objp->type == OBJ_ASTEROID) {
|
||||
asteroid* asp = &Asteroids[asteroid_objp->instance];
|
||||
|
||||
if (asp->target_objnum == target_objnum) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// add a ship as a new asteroid target
|
||||
void asteroid_add_target(object* objp) {
|
||||
asteroid_target new_target;
|
||||
new_target.objnum = OBJ_INDEX(objp);
|
||||
new_target.signature = objp->signature;
|
||||
new_target.throw_stamp = timestamp(Random::next(500, 2000));
|
||||
new_target.incoming_asteroids = count_incident_asteroids(OBJ_INDEX(objp)); // this *should* normally be 0
|
||||
|
||||
Asteroid_targets.push_back(new_target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force updating of pair stuff for asteroid *objp.
|
||||
*/
|
||||
@@ -205,28 +158,43 @@ static float asteroid_cap_speed(int asteroid_info_index, float speed)
|
||||
return speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether position is inside inner bounding volume
|
||||
*
|
||||
* Sum together the following: 1 inside x, 2 inside y, 4 inside z
|
||||
* inside only when sum = 7
|
||||
*/
|
||||
static int asteroid_in_inner_bound_with_axes(asteroid_field *asfieldp, vec3d *pos, float delta)
|
||||
{
|
||||
Assert(asfieldp->has_inner_bound);
|
||||
|
||||
int rval = 0;
|
||||
if ( (pos->xyz.x > asfieldp->inner_min_bound.xyz.x - delta) && (pos->xyz.x < asfieldp->inner_max_bound.xyz.x + delta) ) {
|
||||
rval += 1;
|
||||
}
|
||||
|
||||
if ( (pos->xyz.y > asfieldp->inner_min_bound.xyz.y - delta) && (pos->xyz.y < asfieldp->inner_max_bound.xyz.y + delta) ) {
|
||||
rval += 2;
|
||||
}
|
||||
|
||||
if ( (pos->xyz.z > asfieldp->inner_min_bound.xyz.z - delta) && (pos->xyz.z < asfieldp->inner_max_bound.xyz.z + delta) ) {
|
||||
rval += 4;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if asteroid is within inner bound
|
||||
* @return 0 if not inside or no inner bound, 1 if inside inner bound
|
||||
*/
|
||||
static bool asteroid_in_inner_bound(asteroid_field *asfieldp, vec3d *pos, float delta) {
|
||||
static int asteroid_in_inner_bound(asteroid_field *asfieldp, vec3d *pos, float delta) {
|
||||
|
||||
if (!asfieldp->has_inner_bound)
|
||||
return false;
|
||||
if (!asfieldp->has_inner_bound) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (pos->xyz.x > asfieldp->inner_min_bound.xyz.x - delta) && (pos->xyz.x < asfieldp->inner_max_bound.xyz.x + delta) &&
|
||||
(pos->xyz.y > asfieldp->inner_min_bound.xyz.y - delta) && (pos->xyz.y < asfieldp->inner_max_bound.xyz.y + delta) &&
|
||||
(pos->xyz.z > asfieldp->inner_min_bound.xyz.z - delta) && (pos->xyz.z < asfieldp->inner_max_bound.xyz.z + delta);
|
||||
}
|
||||
|
||||
static bool asteroid_is_ship_inside_field(asteroid_field* asfieldp, vec3d* pos, float radius) {
|
||||
|
||||
radius *= 2.0f;
|
||||
|
||||
return pos->xyz.x + radius > Asteroid_field.min_bound.xyz.x && pos->xyz.x - radius < Asteroid_field.max_bound.xyz.x &&
|
||||
pos->xyz.y + radius > Asteroid_field.min_bound.xyz.y && pos->xyz.y - radius < Asteroid_field.max_bound.xyz.y &&
|
||||
pos->xyz.z + radius > Asteroid_field.min_bound.xyz.z && pos->xyz.z - radius < Asteroid_field.max_bound.xyz.z &&
|
||||
!asteroid_in_inner_bound(asfieldp, pos, radius);
|
||||
return (asteroid_in_inner_bound_with_axes(asfieldp, pos, delta) == 7);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,7 +324,7 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi
|
||||
asteroid_default_flagset += Object::Object_Flags::Physics;
|
||||
asteroid_default_flagset += Object::Object_Flags::Collides;
|
||||
|
||||
objnum = obj_create(OBJ_ASTEROID, -1, n, &orient, &pos, radius, asteroid_default_flagset, false);
|
||||
objnum = obj_create(OBJ_ASTEROID, -1, n, &orient, &pos, radius, asteroid_default_flagset);
|
||||
|
||||
if ( (objnum == -1) || (objnum >= MAX_OBJECTS) ) {
|
||||
mprintf(("Couldn't create asteroid -- out of object slots\n"));
|
||||
@@ -366,8 +334,8 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi
|
||||
asp->objnum = objnum;
|
||||
asp->model_instance_num = -1;
|
||||
|
||||
if (model_get(asip->model_num[asteroid_subtype])->flags & PM_FLAG_HAS_INTRINSIC_MOTION) {
|
||||
asp->model_instance_num = model_create_instance(objnum, asip->model_num[asteroid_subtype]);
|
||||
if (model_get(asip->model_num[asteroid_subtype])->flags & PM_FLAG_HAS_INTRINSIC_ROTATE) {
|
||||
asp->model_instance_num = model_create_instance(false, asip->model_num[asteroid_subtype]);
|
||||
}
|
||||
|
||||
// Add to Asteroid_used_list
|
||||
@@ -528,7 +496,7 @@ void asteroid_create_all()
|
||||
|
||||
// ship_debris_odds_table keeps track of debris type of the next debris piece
|
||||
// each different type (size) of debris piece has a diffenent weight, smaller weighted more heavily than larger.
|
||||
// choose next type from table ship_debris_odds_table by Random::next()%max_weighted_range,
|
||||
// choose next type from table ship_debris_odds_table by rand()%max_weighted_range,
|
||||
// the threshold *below* which the debris type is selected.
|
||||
struct {
|
||||
float random_threshold;
|
||||
@@ -544,11 +512,6 @@ void asteroid_create_all()
|
||||
return;
|
||||
}
|
||||
|
||||
if (Asteroid_field.num_used_field_debris_types <= 0) {
|
||||
Warning(LOCATION, "An asteroid field is enabled, but no asteroid types were enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
int max_asteroids = Asteroid_field.num_initial_asteroids; // * (1.0f - 0.1f*(MAX_DETAIL_LEVEL-Detail.asteroid_density)));
|
||||
|
||||
int num_debris_types = 0;
|
||||
@@ -601,20 +564,12 @@ void asteroid_create_all()
|
||||
// For asteroid, load only large asteroids
|
||||
|
||||
// get a valid subtype
|
||||
int counter = Random::next(Asteroid_field.num_used_field_debris_types);
|
||||
int subtype = -1;
|
||||
for (int j = 0; j < NUM_DEBRIS_POFS; j++) {
|
||||
if (Asteroid_field.field_debris_type[j] >= 0) {
|
||||
if (counter == 0) {
|
||||
subtype = j;
|
||||
break;
|
||||
} else
|
||||
counter--;
|
||||
}
|
||||
int subtype = rand() % NUM_DEBRIS_POFS;
|
||||
while (Asteroid_field.field_debris_type[subtype] == -1) {
|
||||
subtype = (subtype + 1) % NUM_DEBRIS_POFS;
|
||||
}
|
||||
|
||||
if (subtype >= 0)
|
||||
asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
|
||||
asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
|
||||
} else {
|
||||
Assert(num_debris_types > 0);
|
||||
|
||||
@@ -638,13 +593,11 @@ void asteroid_level_init()
|
||||
{
|
||||
Asteroid_field.num_initial_asteroids=0;
|
||||
Num_asteroids = 0;
|
||||
Asteroid_targets.clear();
|
||||
Default_asteroid_throwing_behavior = true;
|
||||
Next_asteroid_throw = timestamp(1);
|
||||
asteroid_obj_list_init();
|
||||
SCP_vector<asteroid_info>::iterator ast;
|
||||
for (ast = Asteroid_info.begin(); ast != Asteroid_info.end(); ++ast)
|
||||
ast->damage_type_idx = ast->damage_type_idx_sav;
|
||||
Asteroid_field.num_used_field_debris_types = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -780,75 +733,52 @@ static void asteroid_aim_at_target(object *objp, object *asteroid_objp, float de
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the Asteroid targets list removing ships that have died/become invalid etc
|
||||
*/
|
||||
static void sanitize_asteroid_targets_list() {
|
||||
for (size_t i = 0; i < Asteroid_targets.size();) {
|
||||
object* target_objp = &Objects[Asteroid_targets[i].objnum];
|
||||
if (target_objp->type != OBJ_SHIP || target_objp->signature != Asteroid_targets[i].signature) {
|
||||
Asteroid_targets[i] = Asteroid_targets.back();
|
||||
Asteroid_targets.pop_back();
|
||||
}
|
||||
else // if we needed to cull we should not advance because we just moved a new asteroid target into this spot
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call once per frame to maybe throw some asteroids at one or more ships.
|
||||
* Call once per frame to maybe throw an asteroid at a ship.
|
||||
*
|
||||
* @param count asteroids already targeted on
|
||||
*/
|
||||
static void maybe_throw_asteroid()
|
||||
static void maybe_throw_asteroid(int count)
|
||||
{
|
||||
if (!timestamp_elapsed(Next_asteroid_throw)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (asteroid_target& target : Asteroid_targets) {
|
||||
if (!timestamp_elapsed(target.throw_stamp))
|
||||
continue;
|
||||
if (Asteroid_throw_objnum == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
object* target_objp = &Objects[target.objnum];
|
||||
if (!asteroid_is_ship_inside_field(&Asteroid_field, &target_objp->pos, target_objp->radius))
|
||||
continue;
|
||||
|
||||
// This should've been greater equal, but alas...
|
||||
if (target.incoming_asteroids > The_mission.ai_profile->max_incoming_asteroids[Game_skill_level])
|
||||
continue;
|
||||
nprintf(("AI", "Incoming asteroids: %i\n", count));
|
||||
|
||||
nprintf(("AI", "Incoming asteroids to %s: %i\n", Ships[target_objp->instance].ship_name, target.incoming_asteroids));
|
||||
if (count > The_mission.ai_profile->max_incoming_asteroids[Game_skill_level])
|
||||
return;
|
||||
|
||||
target.throw_stamp = timestamp(1000 + 1200 * target.incoming_asteroids /(Game_skill_level+1));
|
||||
Next_asteroid_throw = timestamp(1000 + 1200 * count/(Game_skill_level+1));
|
||||
|
||||
int counter = Random::next(Asteroid_field.num_used_field_debris_types);
|
||||
int subtype = -1;
|
||||
for (int i = 0; i < NUM_DEBRIS_POFS; i++) {
|
||||
if (Asteroid_field.field_debris_type[i] >= 0) {
|
||||
if (counter == 0) {
|
||||
subtype = i;
|
||||
break;
|
||||
ship_obj *so;
|
||||
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
||||
object *A = &Objects[so->objnum];
|
||||
if (so->objnum == Asteroid_throw_objnum) {
|
||||
int subtype = rand() % NUM_DEBRIS_POFS;
|
||||
while (Asteroid_field.field_debris_type[subtype] == -1) {
|
||||
subtype = (subtype + 1) % NUM_DEBRIS_POFS;
|
||||
}
|
||||
object *objp = asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
|
||||
if (objp != NULL) {
|
||||
asteroid_aim_at_target(A, objp, ASTEROID_MIN_COLLIDE_TIME + frand() * 20.0f);
|
||||
|
||||
// if asteroid is inside inner bound, kill it
|
||||
if (asteroid_in_inner_bound(&Asteroid_field, &objp->pos, 0.0f)) {
|
||||
objp->flags.set(Object::Object_Flags::Should_be_dead);
|
||||
} else {
|
||||
Asteroids[objp->instance].target_objnum = so->objnum;
|
||||
|
||||
if ( MULTIPLAYER_MASTER ) {
|
||||
send_asteroid_throw( objp );
|
||||
}
|
||||
}
|
||||
else
|
||||
counter--;
|
||||
}
|
||||
}
|
||||
|
||||
// this really shouldn't happen but just in case...
|
||||
if (subtype < 0)
|
||||
return;
|
||||
|
||||
object *objp = asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
|
||||
if (objp != nullptr) {
|
||||
asteroid_aim_at_target(target_objp, objp, ASTEROID_MIN_COLLIDE_TIME + frand() * 20.0f);
|
||||
|
||||
// if asteroid is inside inner bound, kill it
|
||||
if (asteroid_in_inner_bound(&Asteroid_field, &objp->pos, 0.0f)) {
|
||||
objp->flags.set(Object::Object_Flags::Should_be_dead);
|
||||
} else {
|
||||
Asteroids[objp->instance].target_objnum = target.objnum;
|
||||
target.incoming_asteroids++;
|
||||
|
||||
if ( MULTIPLAYER_MASTER ) {
|
||||
send_asteroid_throw( objp );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,13 +800,6 @@ void asteroid_delete( object * obj )
|
||||
if (asp->model_instance_num >= 0)
|
||||
model_delete_instance(asp->model_instance_num);
|
||||
|
||||
if (asp->target_objnum >= 0) {
|
||||
for (asteroid_target& target : Asteroid_targets) {
|
||||
if (asp->target_objnum == target.objnum)
|
||||
target.incoming_asteroids--;
|
||||
}
|
||||
}
|
||||
|
||||
asp->flags = 0;
|
||||
Num_asteroids--;
|
||||
Assert(Num_asteroids >= 0);
|
||||
@@ -915,14 +838,7 @@ static void asteroid_maybe_reposition(object *objp, asteroid_field *asfieldp)
|
||||
} else {
|
||||
// check to ensure player won't see asteroid appear either
|
||||
asteroid_wrap_pos(objp, asfieldp);
|
||||
asteroid* astp = &Asteroids[objp->instance];
|
||||
|
||||
// this doesnt count as a thrown asteroid anymore
|
||||
for (asteroid_target& target : Asteroid_targets)
|
||||
if (target.objnum == astp->target_objnum)
|
||||
target.incoming_asteroids--;
|
||||
|
||||
astp->target_objnum = -1;
|
||||
Asteroids[objp->instance].target_objnum = -1;
|
||||
|
||||
dist = vm_vec_normalized_dir(&vec_to_asteroid, &objp->pos, &Eye_position);
|
||||
dot = vm_vec_dot(&Eye_matrix.vec.fvec, &vec_to_asteroid);
|
||||
@@ -1002,9 +918,9 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
vec3d normal;
|
||||
|
||||
if (mc.model_instance_num >= 0)
|
||||
model_instance_local_to_global_dir(&normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, mc.orient);
|
||||
model_instance_find_world_dir(&normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, mc.orient);
|
||||
else
|
||||
model_local_to_global_dir(&normal, &mc.hit_normal, mc.model_num, mc.hit_submodel, mc.orient);
|
||||
model_find_world_dir(&normal, &mc.hit_normal, mc.model_num, mc.hit_submodel, mc.orient);
|
||||
|
||||
*hitnormal = normal;
|
||||
}
|
||||
@@ -1034,11 +950,11 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
// find the light object's position in the heavy object's reference frame at last frame and also in this frame.
|
||||
vec3d p0_temp, p0_rotated;
|
||||
|
||||
// Collision detection from rotation enabled if at rotation is less than 30 degree in frame
|
||||
// Collision detection from rotation enabled if at rotaion is less than 30 degree in frame
|
||||
// This should account for all ships
|
||||
if ( (vm_vec_mag_squared( &heavy->phys_info.rotvel ) * flFrametime*flFrametime) < (PI*PI/36) ) {
|
||||
// collide_rotate calculate (1) start position and (2) relative velocity
|
||||
asteroid_hit_info->collide_rotate = true;
|
||||
asteroid_hit_info->collide_rotate = 1;
|
||||
vm_vec_rotate( &p0_temp, &p0, &heavy->last_orient );
|
||||
vm_vec_unrotate( &p0_rotated, &p0_temp, &heavy->orient );
|
||||
mc.p0 = &p0_rotated; // Point 1 of ray to check
|
||||
@@ -1046,11 +962,11 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
vm_vec_scale( &asteroid_hit_info->light_rel_vel, 1/flFrametime );
|
||||
// HACK - this applies to big ships warping in/out of asteroid fields - not sure what it does
|
||||
if (vm_vec_mag(&asteroid_hit_info->light_rel_vel) > 300) {
|
||||
asteroid_hit_info->collide_rotate = false;
|
||||
asteroid_hit_info->collide_rotate = 0;
|
||||
vm_vec_sub( &asteroid_hit_info->light_rel_vel, &lighter->phys_info.vel, &heavy->phys_info.vel );
|
||||
}
|
||||
} else {
|
||||
asteroid_hit_info->collide_rotate = false;
|
||||
asteroid_hit_info->collide_rotate = 0;
|
||||
vm_vec_sub( &asteroid_hit_info->light_rel_vel, &lighter->phys_info.vel, &heavy->phys_info.vel );
|
||||
}
|
||||
|
||||
@@ -1082,18 +998,18 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
|
||||
// Do collision the cool new way
|
||||
if ( asteroid_hit_info->collide_rotate ) {
|
||||
// We collide with the sphere, find the list of moving submodels and test one at a time
|
||||
// We collide with the sphere, find the list of rotating submodels and test one at a time
|
||||
SCP_vector<int> submodel_vector;
|
||||
model_get_moving_submodel_list(submodel_vector, heavy_obj);
|
||||
model_get_rotating_submodel_list(&submodel_vector, heavy_obj);
|
||||
|
||||
// turn off all moving submodels, collide against only 1 at a time.
|
||||
// turn off collision detection for all moving submodels
|
||||
// turn off all rotating submodels, collide against only 1 at a time.
|
||||
// turn off collision detection for all rotating submodels
|
||||
for (auto submodel: submodel_vector) {
|
||||
pmi->submodel[submodel].collision_checked = true;
|
||||
}
|
||||
|
||||
// Only check single submodel now, since children of moving submodels are handled as moving as well
|
||||
mc.flags = copy_flags | MC_SUBMODEL;
|
||||
// reset flags to check MC_CHECK_MODEL | MC_CHECK_SPHERELINE and maybe MC_CHECK_INVISIBLE_FACES and MC_SUBMODEL_INSTANCE
|
||||
mc.flags = copy_flags | MC_SUBMODEL_INSTANCE;
|
||||
|
||||
// check each submodel in turn
|
||||
for (auto submodel: submodel_vector) {
|
||||
@@ -1102,9 +1018,15 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
// turn on just one submodel for collision test
|
||||
smi->collision_checked = false;
|
||||
|
||||
// set angles for last frame (need to set to prev to get p0)
|
||||
matrix copy_matrix = smi->canonical_orient;
|
||||
|
||||
// find the start and end positions of the sphere in submodel RF
|
||||
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
|
||||
model_instance_global_to_local_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
|
||||
smi->canonical_orient = smi->canonical_prev_orient;
|
||||
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
|
||||
|
||||
smi->canonical_orient = copy_matrix;
|
||||
world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
|
||||
|
||||
mc.p0 = &p0;
|
||||
mc.p1 = &p1;
|
||||
@@ -1117,18 +1039,18 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
mc_ret_val = 1;
|
||||
|
||||
// set up asteroid_hit_info common
|
||||
set_hit_struct_info(asteroid_hit_info, &mc, true);
|
||||
set_hit_struct_info(asteroid_hit_info, &mc, SUBMODEL_ROT_HIT);
|
||||
|
||||
// set up asteroid_hit_info for rotating submodel
|
||||
if (!asteroid_hit_info->edge_hit) {
|
||||
model_instance_local_to_global_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
if (asteroid_hit_info->edge_hit == 0) {
|
||||
model_instance_find_world_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
}
|
||||
|
||||
// find position in submodel RF of light object at collison
|
||||
vec3d int_light_pos, diff;
|
||||
vm_vec_sub(&diff, mc.p1, mc.p0);
|
||||
vm_vec_scale_add(&int_light_pos, mc.p0, &diff, mc.hit_dist);
|
||||
model_instance_local_to_global_point(&asteroid_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
|
||||
model_instance_find_world_point(&asteroid_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
|
||||
}
|
||||
}
|
||||
// Don't look at this submodel again
|
||||
@@ -1149,11 +1071,11 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
if (mc.hit_dist < asteroid_hit_info->hit_time) {
|
||||
mc_ret_val = 1;
|
||||
|
||||
set_hit_struct_info(asteroid_hit_info, &mc, false);
|
||||
set_hit_struct_info(asteroid_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
|
||||
|
||||
// get collision normal if not edge hit
|
||||
if (!asteroid_hit_info->edge_hit) {
|
||||
model_instance_local_to_global_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
if (asteroid_hit_info->edge_hit == 0) {
|
||||
model_instance_find_world_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
}
|
||||
|
||||
// find position in submodel RF of light object at collison
|
||||
@@ -1178,7 +1100,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
|
||||
mc_ret_val = model_collide(&mc);
|
||||
|
||||
if (mc_ret_val) {
|
||||
set_hit_struct_info(asteroid_hit_info, &mc, false);
|
||||
set_hit_struct_info(asteroid_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
|
||||
|
||||
// set normal if not edge hit
|
||||
if ( !asteroid_hit_info->edge_hit ) {
|
||||
@@ -1402,7 +1324,7 @@ static void asteroid_do_area_effect(object *asteroid_objp)
|
||||
if ( weapon_area_calc_damage(ship_objp, &asteroid_objp->pos, asip->inner_rad, asip->outer_rad, asip->blast, asip->damage, &blast, &damage, asip->outer_rad) == -1 )
|
||||
continue;
|
||||
|
||||
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage, asip->damage_type_idx);
|
||||
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage );
|
||||
weapon_area_apply_blast(NULL, ship_objp, &asteroid_objp->pos, blast, 0);
|
||||
} // end for
|
||||
}
|
||||
@@ -1510,14 +1432,9 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
|
||||
|
||||
if ( timestamp_elapsed(asp->final_death_time) ) {
|
||||
vec3d relvec, vfh, tvec;
|
||||
bool skip = false;
|
||||
bool hooked = Script_system.IsActiveAction(CHA_DEATH);
|
||||
|
||||
if (hooked) {
|
||||
Script_system.SetHookObject("Self", pasteroid_obj);
|
||||
skip = Script_system.IsConditionOverride(CHA_DEATH, 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);
|
||||
|
||||
@@ -1567,7 +1484,7 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
|
||||
int num_roids_var = split->max - split->min;
|
||||
|
||||
if (num_roids_var > 0)
|
||||
num_roids += Random::next(num_roids_var);
|
||||
num_roids += rand() % num_roids_var;
|
||||
|
||||
if (num_roids > 0)
|
||||
for (int i=0; i<num_roids; i++)
|
||||
@@ -1622,10 +1539,8 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
|
||||
}
|
||||
asp->final_death_time = timestamp(-1);
|
||||
}
|
||||
if (hooked) {
|
||||
Script_system.RunCondition(CHA_DEATH, pasteroid_obj);
|
||||
Script_system.RemHookVar("Self");
|
||||
}
|
||||
Script_system.RunCondition(CHA_DEATH, pasteroid_obj);
|
||||
Script_system.RemHookVar("Self");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2090,6 +2005,29 @@ static void asteroid_parse_tbl()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of asteroids expected to collide with a ship.
|
||||
*/
|
||||
static int count_incident_asteroids()
|
||||
{
|
||||
object *asteroid_objp;
|
||||
int count;
|
||||
|
||||
count = 0;
|
||||
|
||||
for ( asteroid_objp = GET_FIRST(&obj_used_list); asteroid_objp !=END_OF_LIST(&obj_used_list); asteroid_objp = GET_NEXT(asteroid_objp) ) {
|
||||
if (asteroid_objp->type == OBJ_ASTEROID ) {
|
||||
asteroid *asp = &Asteroids[asteroid_objp->instance];
|
||||
|
||||
if ( asp->target_objnum >= 0 ) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick object to throw asteroids at.
|
||||
* Pick any capital or big ship inside the bounds of the asteroid field.
|
||||
@@ -2104,10 +2042,17 @@ static int set_asteroid_throw_objnum()
|
||||
|
||||
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
||||
ship_objp = &Objects[so->objnum];
|
||||
float radius = ship_objp->radius*2.0f;
|
||||
|
||||
if (Ship_info[Ships[ship_objp->instance].ship_info_index].is_big_or_huge()) {
|
||||
if (asteroid_is_ship_inside_field(&Asteroid_field, &ship_objp->pos, ship_objp->radius))
|
||||
return so->objnum;
|
||||
if (ship_objp->pos.xyz.x + radius > Asteroid_field.min_bound.xyz.x)
|
||||
if (ship_objp->pos.xyz.y + radius > Asteroid_field.min_bound.xyz.y)
|
||||
if (ship_objp->pos.xyz.z + radius > Asteroid_field.min_bound.xyz.z)
|
||||
if (ship_objp->pos.xyz.x - radius < Asteroid_field.max_bound.xyz.x)
|
||||
if (ship_objp->pos.xyz.y - radius < Asteroid_field.max_bound.xyz.y)
|
||||
if (ship_objp->pos.xyz.z - radius < Asteroid_field.max_bound.xyz.z)
|
||||
if (!asteroid_in_inner_bound(&Asteroid_field, &ship_objp->pos, radius))
|
||||
return so->objnum;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
@@ -2124,18 +2069,9 @@ void asteroid_frame()
|
||||
return;
|
||||
}
|
||||
|
||||
if (Default_asteroid_throwing_behavior) {
|
||||
int objnum = set_asteroid_throw_objnum();
|
||||
if (Asteroid_targets.empty() || Asteroid_targets[0].objnum != objnum) {
|
||||
Asteroid_targets.clear();
|
||||
if (objnum >= 0)
|
||||
asteroid_add_target(&Objects[objnum]);
|
||||
}
|
||||
}
|
||||
Asteroid_throw_objnum = set_asteroid_throw_objnum();
|
||||
|
||||
sanitize_asteroid_targets_list();
|
||||
|
||||
maybe_throw_asteroid();
|
||||
maybe_throw_asteroid(count_incident_asteroids());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,11 +36,6 @@ class model_draw_list;
|
||||
// Goober5000 - currently same as MAX_SHIP_DETAIL_LEVELS (put here to avoid an #include)
|
||||
#define MAX_ASTEROID_DETAIL_LEVELS 5
|
||||
|
||||
// whether to do retail behavior, just throw at the first big ship in the field
|
||||
// must be explicitly opted out of by the mission
|
||||
extern bool Default_asteroid_throwing_behavior;
|
||||
|
||||
extern SCP_vector<SCP_string> Asteroid_target_ships;
|
||||
|
||||
// Data structure to track the active asteroids
|
||||
typedef struct asteroid_obj {
|
||||
@@ -143,7 +138,6 @@ typedef struct asteroid_field {
|
||||
field_type_t field_type; // active throws and wraps, passive does not
|
||||
debris_genre_t debris_genre; // type of debris (ship or asteroid) [generic type]
|
||||
int field_debris_type[MAX_ACTIVE_DEBRIS_TYPES]; // one of the debris type defines above
|
||||
int num_used_field_debris_types; // how many of the above are used
|
||||
} asteroid_field;
|
||||
|
||||
extern SCP_vector< asteroid_info > Asteroid_info;
|
||||
@@ -171,7 +165,6 @@ int asteroid_collide_objnum(object *asteroid_objp);
|
||||
float asteroid_time_to_impact(object *asteroid_objp);
|
||||
void asteroid_show_brackets();
|
||||
void asteroid_target_closest_danger();
|
||||
void asteroid_add_target(object* objp);
|
||||
|
||||
// need to extern for multiplayer
|
||||
void asteroid_sub_create(object *parent_objp, int asteroid_type, vec3d *relvec);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+10
-17
@@ -1120,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;
|
||||
@@ -1152,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...
|
||||
@@ -1165,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;
|
||||
@@ -1225,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++;
|
||||
@@ -1236,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) {
|
||||
@@ -1425,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;
|
||||
@@ -1467,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...
|
||||
@@ -1596,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 {
|
||||
@@ -1796,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;
|
||||
}
|
||||
|
||||
+3
-10
@@ -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)
|
||||
|
||||
+44
-81
@@ -43,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);
|
||||
@@ -237,15 +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;
|
||||
auto orient = Use_host_orientation_for_set_camera_facing ? &orient_buf : nullptr;
|
||||
vec3d position = vmd_zero_vector;
|
||||
this->get_info(&position, NULL);
|
||||
|
||||
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;
|
||||
@@ -253,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);
|
||||
@@ -319,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())
|
||||
{
|
||||
@@ -366,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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,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
|
||||
@@ -405,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;
|
||||
@@ -445,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;
|
||||
@@ -459,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;
|
||||
}
|
||||
}
|
||||
@@ -598,9 +560,9 @@ 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, float in_line_height_factor)
|
||||
: display_time(-1.0f), fade_time(-1.0f), text_fontnum(-1), line_height_factor(1.0f),
|
||||
image_id(-1), time_displayed(-1.0f), time_displayed_end(-1.0f), post_shaded(false)
|
||||
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)
|
||||
{
|
||||
// Initialize color
|
||||
gr_init_color(&text_color, 0, 0, 0);
|
||||
@@ -611,6 +573,7 @@ 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;
|
||||
@@ -622,6 +585,7 @@ subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char*
|
||||
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];
|
||||
@@ -644,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_factor = in_line_height_factor;
|
||||
|
||||
//Setup display and fade time
|
||||
display_time = fl_abs(in_display_time);
|
||||
@@ -683,7 +646,7 @@ subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char*
|
||||
if(w > tw)
|
||||
tw = w;
|
||||
|
||||
th += fl2i(line_height_factor * h);
|
||||
th += h;
|
||||
}
|
||||
|
||||
// restore old font
|
||||
@@ -781,7 +744,7 @@ void subtitle::do_frame(float frametime)
|
||||
for(SCP_vector<SCP_string>::iterator line = text_lines.begin(); line != text_lines.end(); ++line)
|
||||
{
|
||||
gr_string(x, y, (char*)line->c_str(), GR_RESIZE_NONE);
|
||||
y += fl2i(line_height_factor * font_height);
|
||||
y += font_height;
|
||||
}
|
||||
|
||||
// restore old font
|
||||
@@ -830,9 +793,9 @@ subtitle::~subtitle()
|
||||
|
||||
void subtitle::clone(const subtitle &sub)
|
||||
{
|
||||
|
||||
text_lines = sub.text_lines;
|
||||
text_fontnum = sub.text_fontnum;
|
||||
line_height_factor = sub.line_height_factor;
|
||||
|
||||
// copy the structs
|
||||
text_pos = sub.text_pos;
|
||||
|
||||
@@ -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;
|
||||
float line_height_factor;
|
||||
|
||||
//Done with set
|
||||
char imageanim[MAX_FILENAME_LEN];
|
||||
@@ -140,8 +139,7 @@ private:
|
||||
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,
|
||||
float in_line_height_factor = 1.0f);
|
||||
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); }
|
||||
|
||||
@@ -80,7 +80,7 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
|
||||
{ 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 },
|
||||
@@ -91,7 +91,6 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
|
||||
{ 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
|
||||
|
||||
@@ -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
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
+753
-462
File diff suppressed because it is too large.
Load diff
+30
-58
@@ -220,8 +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", },
|
||||
#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
|
||||
@@ -248,13 +246,11 @@ Flag exe_params[] =
|
||||
{ "-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
|
||||
|
||||
@@ -379,23 +375,21 @@ int Cmdline_rearm_timer = 0;
|
||||
int Cmdline_targetinfo = 0;
|
||||
|
||||
// Gameplay related
|
||||
cmdline_parm use_3dwarp("-3dwarp", nullptr, AT_NONE); // Is now Fireball_use_3d_warp
|
||||
cmdline_parm ship_choice_3d_arg("-ship_choice_3d", nullptr, AT_NONE); // Cmdline_ship_choice_3d
|
||||
cmdline_parm weapon_choice_3d_arg("-weapon_choice_3d", nullptr, AT_NONE); // Cmdline_weapon_choice_3d
|
||||
cmdline_parm use_warp_flash("-warp_flash", nullptr, AT_NONE); // Cmdline_warp_flash
|
||||
cmdline_parm allow_autpilot_interrupt("-no_ap_interrupt", nullptr, AT_NONE);
|
||||
cmdline_parm stretch_menu("-stretch_menu", nullptr, AT_NONE); // Cmdline_stretch_menu
|
||||
cmdline_parm use_3dwarp("-3dwarp", NULL, AT_NONE); // Cmdline_3dwarp
|
||||
cmdline_parm ship_choice_3d_arg("-ship_choice_3d", NULL, AT_NONE); // Cmdline_ship_choice_3d
|
||||
cmdline_parm weapon_choice_3d_arg("-weapon_choice_3d", NULL, AT_NONE); // Cmdline_weapon_choice_3d
|
||||
cmdline_parm use_warp_flash("-warp_flash", NULL, AT_NONE); // Cmdline_warp_flash
|
||||
cmdline_parm allow_autpilot_interrupt("-no_ap_interrupt", NULL, AT_NONE);
|
||||
cmdline_parm stretch_menu("-stretch_menu", NULL, AT_NONE); // Cmdline_stretch_menu
|
||||
cmdline_parm no_screenshake("-no_screenshake", nullptr, AT_NONE); // Cmdline_no_screenshake
|
||||
cmdline_parm deadzone("-deadzone",
|
||||
"Sets the joystick deadzone. Integer value from 0 to 100 as a percentage of the joystick's range (100% would make the stick do nothing). Disables deadzone slider in the in-game Options menu.", AT_INT); //Cmdline_deadzone
|
||||
|
||||
int Cmdline_3dwarp = 0;
|
||||
int Cmdline_ship_choice_3d = 0;
|
||||
int Cmdline_weapon_choice_3d = 0;
|
||||
int Cmdline_warp_flash = 0;
|
||||
int Cmdline_autopilot_interruptable = 1;
|
||||
int Cmdline_stretch_menu = 0;
|
||||
int Cmdline_no_screenshake = 0;
|
||||
int Cmdline_deadzone = -1;
|
||||
|
||||
// Audio related
|
||||
cmdline_parm voice_recognition_arg("-voicer", NULL, AT_NONE); // Cmdline_voice_recognition
|
||||
@@ -442,8 +436,6 @@ cmdline_parm no_geo_sdr_effects("-no_geo_effects", NULL, AT_NONE);
|
||||
cmdline_parm set_cpu_affinity("-set_cpu_affinity", NULL, AT_NONE);
|
||||
cmdline_parm nograb_arg("-nograb", NULL, AT_NONE);
|
||||
cmdline_parm noshadercache_arg("-noshadercache", NULL, AT_NONE);
|
||||
cmdline_parm prefer_ipv4_arg("-prefer_ipv4", nullptr, AT_NONE);
|
||||
cmdline_parm prefer_ipv6_arg("-prefer_ipv6", nullptr, AT_NONE);
|
||||
#ifdef WIN32
|
||||
cmdline_parm fix_registry("-fix_registry", NULL, AT_NONE);
|
||||
#endif
|
||||
@@ -462,8 +454,6 @@ bool Cmdline_no_geo_sdr_effects = false;
|
||||
bool Cmdline_set_cpu_affinity = false;
|
||||
bool Cmdline_nograb = false;
|
||||
bool Cmdline_noshadercache = false;
|
||||
bool Cmdline_prefer_ipv4 = false;
|
||||
bool Cmdline_prefer_ipv6 = false;
|
||||
#ifdef WIN32
|
||||
bool Cmdline_alternate_registry_path = false;
|
||||
#endif
|
||||
@@ -489,7 +479,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
|
||||
@@ -497,7 +486,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;
|
||||
@@ -522,7 +510,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;
|
||||
@@ -530,7 +517,6 @@ 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);
|
||||
@@ -1331,7 +1317,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);
|
||||
}
|
||||
@@ -1423,7 +1408,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);
|
||||
}
|
||||
@@ -1615,7 +1617,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';
|
||||
}
|
||||
}
|
||||
@@ -1625,9 +1627,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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1712,7 +1713,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()){
|
||||
@@ -1838,10 +1839,6 @@ bool SetCmdlineParams()
|
||||
Cmdline_no_screenshake = 1;
|
||||
}
|
||||
|
||||
if ( deadzone.found() ) {
|
||||
Cmdline_deadzone = deadzone.get_int();
|
||||
}
|
||||
|
||||
if ( stretch_menu.found() ) {
|
||||
Cmdline_stretch_menu = 1;
|
||||
}
|
||||
@@ -2126,11 +2123,6 @@ bool SetCmdlineParams()
|
||||
Cmdline_benchmark_mode = true;
|
||||
}
|
||||
|
||||
if (pilot_arg.found())
|
||||
{
|
||||
Cmdline_pilot = pilot_arg.str();
|
||||
}
|
||||
|
||||
if (noninteractive_arg.found())
|
||||
{
|
||||
Cmdline_noninteractive = true;
|
||||
@@ -2158,10 +2150,6 @@ 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;
|
||||
@@ -2263,22 +2251,6 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -83,13 +83,13 @@ extern int Cmdline_rearm_timer;
|
||||
extern int Cmdline_targetinfo;
|
||||
|
||||
// Gameplay related
|
||||
extern int Cmdline_3dwarp;
|
||||
extern int Cmdline_ship_choice_3d;
|
||||
extern int Cmdline_weapon_choice_3d;
|
||||
extern int Cmdline_warp_flash;
|
||||
extern int Cmdline_autopilot_interruptable;
|
||||
extern int Cmdline_stretch_menu;
|
||||
extern int Cmdline_no_screenshake;
|
||||
extern int Cmdline_deadzone;
|
||||
|
||||
// Audio related
|
||||
extern int Cmdline_voice_recognition;
|
||||
@@ -123,8 +123,6 @@ extern bool Cmdline_no_geo_sdr_effects;
|
||||
extern bool Cmdline_set_cpu_affinity;
|
||||
extern bool Cmdline_nograb;
|
||||
extern bool Cmdline_noshadercache;
|
||||
extern bool Cmdline_prefer_ipv4;
|
||||
extern bool Cmdline_prefer_ipv6;
|
||||
#ifdef WIN32
|
||||
extern bool Cmdline_alternate_registry_path;
|
||||
#endif
|
||||
@@ -145,7 +143,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;
|
||||
@@ -153,7 +150,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
+102
-372
@@ -15,62 +15,68 @@
|
||||
|
||||
#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
|
||||
|
||||
/*!
|
||||
* These are used to index a corresponding axis value from an array.
|
||||
* These are used to index a corresponding joystick axis value from an array.
|
||||
* Currently only used by ::Axis_map_to[] and ::Axis_map_to_defaults[]
|
||||
*/
|
||||
enum Joy_axis_index : short {
|
||||
enum Joy_axis_index {
|
||||
JOY_X_AXIS =0,
|
||||
JOY_Y_AXIS,
|
||||
JOY_Z_AXIS,
|
||||
JOY_RX_AXIS,
|
||||
JOY_RY_AXIS,
|
||||
JOY_RZ_AXIS,
|
||||
|
||||
JOY_NUM_AXES // Number of axes a joystick may have. Must be last enum in Joy_axis_index.
|
||||
JOY_RZ_AXIS
|
||||
};
|
||||
|
||||
// Aliases for mouse axes. Really should unify this...
|
||||
const short MOUSE_X_AXIS = JOY_X_AXIS;
|
||||
const short MOUSE_Y_AXIS = JOY_Y_AXIS;
|
||||
const short MOUSE_Z_AXIS = JOY_Z_AXIS;
|
||||
const short MOUSE_NUM_AXES = 3;
|
||||
const short MOUSE_NUM_BUTTONS = 9; // Keep this up to date with mouse.h until z64555 stops being lazy and does it right
|
||||
|
||||
/*!
|
||||
* Controller index enumeration
|
||||
* @details For use with hardcoded bindings and scripting API to allow human-readable translation
|
||||
* @note These enums are hardcoded so that an int value of 0 would be JOY0
|
||||
*/
|
||||
enum CID : int8_t {
|
||||
CID_NONE = -3, //!< No device bound
|
||||
CID_KEYBOARD = -2, //!< belongs to keyboard
|
||||
CID_MOUSE = -1, //!< to mouse
|
||||
CID_JOY0 = 0, //!< to Joy0
|
||||
CID_JOY1 = 1, //!< to Joy1 (Throttle?)
|
||||
CID_JOY2 = 2, //!< to Joy2 (Pedals?)
|
||||
CID_JOY3 = 3, //!< to Joy3 (Head tracker?)
|
||||
|
||||
CID_JOY_MAX //!< Maximum supported joysticks, must be last in the enum definition
|
||||
enum CID : short {
|
||||
CID_NONE = -3, // No device bound
|
||||
CID_KEYBOARD = -2, // button belongs to keyboard
|
||||
CID_MOUSE = -1, // to mouse
|
||||
CID_JOY0 = 0, // to Joy0
|
||||
CID_JOY1 = 1, // to Joy1 (Throttle?)
|
||||
CID_JOY2 = 2, // to Joy2 (Pedals?)
|
||||
CID_JOY3 = 3 // to Joy3 (Head tracker?)
|
||||
// Any additional joystick is an int >3
|
||||
};
|
||||
|
||||
/*!
|
||||
* These are used to index a corresponding (analog) action, namely controlling the orientation angles and throttle.
|
||||
*/
|
||||
enum Joy_axis_action_index {
|
||||
JOY_HEADING_AXIS =0, //
|
||||
JOY_PITCH_AXIS,
|
||||
JOY_BANK_AXIS,
|
||||
JOY_ABS_THROTTLE_AXIS,
|
||||
JOY_REL_THROTTLE_AXIS,
|
||||
|
||||
/*!
|
||||
* This must always be below the last defined item
|
||||
*/
|
||||
NUM_JOY_AXIS_ACTIONS //!< The total number of actions an axis may map to
|
||||
};
|
||||
|
||||
/*!
|
||||
* Joystick action modes
|
||||
* note: should this be in joy.h?
|
||||
*/
|
||||
enum Joy_axis_action_mode {
|
||||
JAAM_ABS, //!< Absolute mode. Axis position = output value
|
||||
JAAM_REL, //!< Relative mode. Axis postion away from its center adds or subtracts an output value register.
|
||||
JAAM_BTN_NEG, //!< Button mode, negative side. Axis position in the negative side will trigger a button action
|
||||
JAAM_BTN_POS, //!< Button mode, positive side. Axis position in the positive side will trigger a button action
|
||||
};
|
||||
|
||||
/*!
|
||||
* Control Configuration Types. Namely differ in how the control is activated
|
||||
*/
|
||||
enum CC_type {
|
||||
CC_TYPE_TRIGGER = 0, //!< A normal, one-shot type control that is activated when a key is or button is pressed
|
||||
CC_TYPE_CONTINUOUS, //!< A continous control that is activated as long as the key or button is held down
|
||||
CC_TYPE_AXIS_ABS, //!< Absolute Axis mode. Axis position = output value
|
||||
CC_TYPE_AXIS_REL, //!< Relative Axis mode. Axis postion away from its center adds or subtracts an output value register
|
||||
CC_TYPE_AXIS_BTN_NEG, //!< Axis Button mode, negative side. Axis position in the negative side will trigger a button action
|
||||
CC_TYPE_AXIS_BTN_POS //!< Axis Button mode, positive side. Axis position in the positive side will trigger a button action
|
||||
CC_TYPE_TRIGGER =0, //!< A normal, one-shot type control that is activated when a key is or button is pressed
|
||||
CC_TYPE_CONTINUOUS //!< A continous control that is activated as long as the key or button is held down
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -80,7 +86,7 @@ enum CC_type {
|
||||
*
|
||||
* Note: Do not adjust the order or numeric value
|
||||
*/
|
||||
enum IoActionId : int {
|
||||
enum IoActionId {
|
||||
TARGET_NEXT =0, //!< target next
|
||||
TARGET_PREV =1, //!< target previous
|
||||
TARGET_NEXT_CLOSEST_HOSTILE =2, //!< target the next hostile target
|
||||
@@ -282,77 +288,52 @@ enum IoActionId : int {
|
||||
CUSTOM_CONTROL_4 = 122,
|
||||
CUSTOM_CONTROL_5 = 123,
|
||||
|
||||
//!< @n
|
||||
//!< Analog controls
|
||||
//!<-----------------------------
|
||||
//!< All axis actions must be kept together
|
||||
JOY_HEADING_AXIS = 124,
|
||||
JOY_PITCH_AXIS = 125,
|
||||
JOY_BANK_AXIS = 126,
|
||||
JOY_ABS_THROTTLE_AXIS = 127,
|
||||
JOY_REL_THROTTLE_AXIS = 128,
|
||||
|
||||
/*!
|
||||
* This must always be below the last defined item
|
||||
*/
|
||||
CCFG_MAX //!< The total number of defined control actions (or last define + 1)
|
||||
};
|
||||
|
||||
// Constants for array-based checking (within Control_config) for the axis actions.
|
||||
// z64: Yes, this is hackish. No I don't really like it.
|
||||
const int JOY_AXIS_BEGIN = JOY_HEADING_AXIS;
|
||||
const int JOY_AXIS_END = JOY_REL_THROTTLE_AXIS + 1;
|
||||
const int NUM_JOY_AXIS_ACTIONS = JOY_AXIS_END - JOY_AXIS_BEGIN;
|
||||
|
||||
/*!
|
||||
* @brief Enum for indexing into axis action arrays.
|
||||
* @note Keep this up to date with JOY_HEADING_AXIS and friends. Pay attention to their order and copy to here
|
||||
*/
|
||||
namespace Action {
|
||||
enum {
|
||||
HEADING = 0,
|
||||
PITCH,
|
||||
BANK,
|
||||
ABS_THROTTLE,
|
||||
REL_THROTTLE,
|
||||
|
||||
NUM_VALUES
|
||||
};
|
||||
}
|
||||
|
||||
class CCB;
|
||||
|
||||
/*!
|
||||
* A singular button binding
|
||||
*/
|
||||
class CC_bind {
|
||||
public:
|
||||
CID cid = CID_NONE; //!< Which controller this belongs to
|
||||
short btn = -1; //!< Which button to index; If cid == CID_KEYBOARD, this is a key hash.
|
||||
|
||||
public:
|
||||
CC_bind() = default;
|
||||
CC_bind(CID _cid, short _btn) : cid(_cid), btn(_btn) { validate(); };
|
||||
CC_bind(CID _cid, short _btn, char _flags) : cid(_cid), flags(_flags), btn(_btn) { validate(); };
|
||||
CC_bind(const CC_bind &A) = default;
|
||||
|
||||
CC_bind& operator=(const CC_bind &A) = default;
|
||||
CC_bind(CID _cid, short _btn) : cid(_cid), btn(_btn) {};
|
||||
|
||||
CC_bind& operator=(const CC_bind &A);
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is equal to the given CC_bind
|
||||
*
|
||||
* @note EQUAL means EQUAL, this will return FALSE if the flags differ by a single bit
|
||||
*/
|
||||
bool operator==(const CC_bind &B) const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is equal to either in the pair
|
||||
*/
|
||||
bool operator==(const CCB &pair) const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is not equal to the given CC_bind
|
||||
*/
|
||||
bool operator!=(const CC_bind &B) const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is equal to the given CC_bind, disregarding the inversion flag
|
||||
* Checks if this CC_bind is not equal to either in the pair
|
||||
*/
|
||||
bool invert_agnostic_equals(const CC_bind &B) const;
|
||||
bool operator!=(const CCB &pair) const;
|
||||
|
||||
/*!
|
||||
* Clears the binding and flags. Some flags are retained.
|
||||
* Clears the binding
|
||||
*/
|
||||
void clear();
|
||||
|
||||
@@ -361,62 +342,10 @@ public:
|
||||
*/
|
||||
bool empty() const;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Takes the given binding.
|
||||
*/
|
||||
void take(CID _cid, short _btn, char _flags = 0);
|
||||
|
||||
/**
|
||||
* Validates the binding, clearing it if necassary
|
||||
*/
|
||||
void validate();
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -463,42 +392,17 @@ public:
|
||||
/*!
|
||||
* Assigns the contents of the given CCB to this CCB
|
||||
*/
|
||||
CCB& operator=(const CCB&) = default;
|
||||
CCB& operator=(const CCB&);
|
||||
|
||||
/*!
|
||||
* Checks if the given CCB is exactly equal to this
|
||||
* Returns True if this CCB's first isn't empty and the given CCB has a binding equal to it
|
||||
*/
|
||||
bool operator==(const CCB&);
|
||||
bool has_first(const CCB&) const;
|
||||
|
||||
/*!
|
||||
* Checks if the given CCB differs from this
|
||||
* Returns True if this CCB's second isn't empty and the given CCB has a binding equal to it
|
||||
*/
|
||||
bool operator!=(const CCB&);
|
||||
|
||||
/*!
|
||||
* 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);
|
||||
bool has_second(const CCB&) const;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -508,12 +412,6 @@ class CC_preset {
|
||||
public:
|
||||
SCP_vector<CCB> bindings;
|
||||
SCP_string name;
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Checks if the given preset is a duplicate of this one
|
||||
*/
|
||||
bool is_duplicate_of(CC_preset&);
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -551,11 +449,6 @@ public:
|
||||
* @brief Takes the bindings of the given CCB, but leaves all other members alone
|
||||
*/
|
||||
CCI& operator=(const CCB&);
|
||||
|
||||
/*!
|
||||
* Returns true if this item is analogue or an axis
|
||||
*/
|
||||
bool is_axis();
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -580,23 +473,9 @@ public:
|
||||
|
||||
/*!
|
||||
* Assigns the hardcoded binding to the given action
|
||||
*
|
||||
* @note This differs from the original hardcode from :V: in the hopes of it being more intuitive to future IoAction additions
|
||||
*
|
||||
* param[in] action_id The IoActionId enum associated with the control
|
||||
* param[in] primary The btn, key combo, or axis for the default primary binding
|
||||
* param[in] secondary The btn, key combo, or axis for the default secondary binding
|
||||
* param[in] tab Category tab this control should show up under on the config menu
|
||||
* param[in] indexXSTR ID for the XSTR localization utility. 0 for no localization, 1 for automatic lookup, all other values are index for item in strings.tbl
|
||||
* param[in] text Label text displayed in the config menu.
|
||||
* param[in] type How the control should be treated. see doxy on CC_type for details
|
||||
* param[in] disabled If true, this control will not be available in the game. Controlsconfigdefaults.tbl may override this. Don't use this to temporarily disable controls in mission.
|
||||
*
|
||||
* @details The CID for the default primary and secondary is determined by the type parameter.
|
||||
* If the type is CC_TYPE_TRIGGER or CC_TYPE_CONTINOUS, the primary is assumed to be a key combo and secondary is assumed to be a joy button.
|
||||
* If the type is CC_TYPE_AXIS_ABS, CC_TYPE_AXIS_REL, CC_TYPE_AXIS_BTN_POS, or CC_TYPE_AXIS_BTN_NEG, the primary is a Joy0 axis and the secondary is a Mouse axis
|
||||
*/
|
||||
CCI_builder& operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false);
|
||||
CCI_builder& operator()(IoActionId action_id, short key_default, short joy_default, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false);
|
||||
|
||||
private:
|
||||
CCI_builder(); // Only one builder per Control Config, so a default constructor is useless
|
||||
@@ -606,6 +485,11 @@ private:
|
||||
|
||||
extern int Failed_key_index;
|
||||
|
||||
extern int Axis_map_to[]; // Array to map an axis action to a joy axis. size() = NUM_JOY_AXIS_ACTIONS
|
||||
extern int Axis_map_to_defaults[];
|
||||
extern int Invert_axis[]; // Array to hold inversion bools for a joy axis. size() = JOY_NUM_AXES
|
||||
extern int Invert_axis_defaults[];
|
||||
|
||||
extern int Joy_dead_zone_size;
|
||||
extern int Joy_sensitivity;
|
||||
|
||||
@@ -614,14 +498,7 @@ 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 **Scan_code_text;
|
||||
extern const char **Joy_button_text; // String table of button labels. XSTR'd on init.
|
||||
|
||||
// string table constants for labels and stuff.
|
||||
#define NUM_AXIS_TEXT JOY_NUM_AXES
|
||||
#define NUM_MOUSE_TEXT MOUSE_NUM_BUTTONS
|
||||
|
||||
extern char *Axis_text[NUM_AXIS_TEXT]; // String table of axis labels (joystick and mice). XSTR'd on init.
|
||||
extern char *Mouse_button_text[NUM_MOUSE_TEXT]; // String table of mouse button labels. XSTR'd on init.
|
||||
extern const char **Joy_button_text;
|
||||
|
||||
/*!
|
||||
* @brief Checks if either binding in the CCB has the given cid
|
||||
@@ -632,11 +509,6 @@ extern char *Mouse_button_text[NUM_MOUSE_TEXT]; // String table of mouse button
|
||||
*/
|
||||
int is_cid_either(CID, const CCB);
|
||||
|
||||
/*!
|
||||
* Allows CID to be assigned by a short. Has Assert to check values
|
||||
*/
|
||||
void cid_assign(CID&, const short);
|
||||
|
||||
/*!
|
||||
* @brief initialize common control config stuff - call at game startup after localization has been initialized
|
||||
*/
|
||||
@@ -668,76 +540,51 @@ void control_config_close();
|
||||
void control_config_cancel_exit();
|
||||
|
||||
/*!
|
||||
* @brief Copies all bindings within preset into Control_config
|
||||
* @brief Resets all controls to values within the currently selected preset
|
||||
*/
|
||||
void control_config_use_preset(CC_preset &preset);
|
||||
void control_config_reset_defaults();
|
||||
|
||||
/*!
|
||||
* @brief Search for a given preset by name and use it, if it exists.
|
||||
*
|
||||
* @returns TRUE if the preset was found and selected
|
||||
* @returns FALSE if the preset was not found, and the defaults were selected
|
||||
*
|
||||
* @details If the preset does not exist, then defaults are used instead.
|
||||
* @see control_config_use_preset()
|
||||
*/
|
||||
bool control_config_use_preset_by_name(const SCP_string &name);
|
||||
|
||||
/**
|
||||
* @brief Gets the currently used preset
|
||||
*
|
||||
* @param[in] invert_agnostic If true, check the bindings in Control_config against the preset, ignoring inversion flags
|
||||
*
|
||||
* @returns an iterator to the current preset, or
|
||||
* @returns ::iterator Control_config_presets.end() if current bindings are not in a preset
|
||||
*/
|
||||
SCP_vector<CC_preset>::iterator control_config_get_current_preset(bool invert_agnostic = false);
|
||||
|
||||
/*!
|
||||
* Returns the IoActionId (index within Control_config[]) of a control bound to the given key
|
||||
*
|
||||
* @param[in] key The key combo to look for
|
||||
* @param[in] find_override If true, return the IoActionId of a control that has this key as its default
|
||||
*
|
||||
* @return -1 if the given key is NULL or not bound.
|
||||
* Returns the IoActionId of a control bound to the given key
|
||||
*
|
||||
* @param[in] key The key combo to look for
|
||||
* @param[in] find_override If true, return the IoActionId of a control that has this key as its default
|
||||
* @details If find_override is set to true, then this returns the index of the action
|
||||
*/
|
||||
int translate_key_to_index(const char *key, bool find_override=true);
|
||||
|
||||
|
||||
/*!
|
||||
* @brief Given the system default key 'key', return the current control input(s) that is bound to that function.
|
||||
* @brief Given the system default key 'key', return the current key that is bound to that function.
|
||||
*
|
||||
* @param[in] key The default key combo (as cstring) to a certain control
|
||||
* @param[in] key The default key combo (as string) to a certain control
|
||||
*
|
||||
* @return The key combo (as cstring) currently bound to the control, or
|
||||
* @return nullptr if the given key is not a system default key, or
|
||||
* @return "None" if there is nothing bound to the control
|
||||
* @returns The key combo (as string) currently bound to the control
|
||||
*
|
||||
* @details Both the 'key' and the return value are descriptive strings that can be displayed
|
||||
* directly to the user. If 'key' isn't a real key, is not normally bound to anything,
|
||||
* or there is no key currently bound to the function, nullptr is returned.
|
||||
*
|
||||
* @note Not thread safe. Has an internal buffer for the return value which is overwritten on each call.
|
||||
*
|
||||
* @note Uses CC_bind::textify for translations
|
||||
* or there is no key currently bound to the function, NULL is returned.
|
||||
*/
|
||||
const char *translate_key(char *key);
|
||||
char *translate_key(char *key);
|
||||
|
||||
/**
|
||||
* @brief Converts the specified key code to a human readable string, according to the selected locale
|
||||
* @brief Converts the specified key code to a human readable string
|
||||
*
|
||||
* @param[in] code The key code to convert
|
||||
* @param[in] use_default_locale If true, return the default locale (English) translation of the key
|
||||
* @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, bool use_default_locale = false);
|
||||
const char *textify_scancode(int code);
|
||||
|
||||
/**
|
||||
* @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
|
||||
@@ -753,29 +600,26 @@ float check_control_timef(int id);
|
||||
/**
|
||||
* @brief Wrapper for check_control_used. Allows the game to ignore the key if told to do so by the ignore-key SEXP.
|
||||
*
|
||||
* @param[in] id The IoActionId of the control to check
|
||||
* @param[in] key The key combo to check against the control. If -1, re-check the last key passed to this function
|
||||
* @brief id The IoActionId of the control to check
|
||||
* @brief key The key combo to check against the control.
|
||||
*
|
||||
* @returns 0 If the control wasn't used, or
|
||||
* @returns 1 If the control was used
|
||||
*
|
||||
* @details If the given control is CC_CONTINOUS, param key is effectively ignored and instead keyd_press[] is checked.
|
||||
* This function also checks against joy and mouse presses.
|
||||
*/
|
||||
int check_control(int id, int key = -1);
|
||||
|
||||
/**
|
||||
* @brief Gets the scaled reading for all control axes.
|
||||
*
|
||||
* @param[in] frametime Frametime used to scale the mouse axes
|
||||
* @param[out] axis_v Output array of all control axes, must have size of JOY_NUM_AXIS_ACTIONS
|
||||
* @param[out] h heading
|
||||
* @param[out] p pitch
|
||||
* @param[out] b bank
|
||||
* @param[out] ta Throttle - Absolute
|
||||
* @param[out] tr Throttle - Relative
|
||||
*
|
||||
* @details
|
||||
* * Clamps output to stay within +/-JOY_AXIS_RANGE.
|
||||
* * Should multiple axes be bound to a control, their values are blended with a simple sum. This is to save a bit of
|
||||
* processing for speed.
|
||||
* @note None of the input pointers may be nullptr
|
||||
*/
|
||||
void control_get_axes_readings(int *axis_v, float frame_time);
|
||||
void control_get_axes_readings(int *h, int *p, int *b, int *ta, int *tr);
|
||||
|
||||
/**
|
||||
* @brief Markes the given control (by IoActionId) as used
|
||||
@@ -808,118 +652,4 @@ void control_config_clear_used_status();
|
||||
*/
|
||||
int joy_get_scaled_reading(int raw);
|
||||
|
||||
/**
|
||||
* Lookups the given stringified enum to find its value
|
||||
* @return -1 if not found, or
|
||||
* @return IoActionId if found
|
||||
*/
|
||||
int ActionToVal(const char * str);
|
||||
|
||||
/**
|
||||
* Reverse lookups the IoActionId to get its stringified name
|
||||
* @return Pointer to the stringified name of the action, or
|
||||
* @return nullptr if not found
|
||||
*/
|
||||
const char * ValToAction(IoActionId id);
|
||||
|
||||
/**
|
||||
* Reverse lookups the IoActionId to get its stringified name
|
||||
* @return Pointer to the stringified name of the action, or
|
||||
* @return nullptr if not found, or invalid id
|
||||
*/
|
||||
const char * ValToAction(int id);
|
||||
|
||||
/**
|
||||
* Lookups the given stringified enum to find its value
|
||||
*/
|
||||
CID CIDToVal(const char * str);
|
||||
|
||||
/**
|
||||
* Reverse lookups the CID to get its stringified name
|
||||
* @return Pointer to the stringified name of the CID, or
|
||||
* @return nullptr if not found
|
||||
*/
|
||||
const char * ValToCID(CID id);
|
||||
|
||||
/**
|
||||
* Reverse lookups the CID to get its stringified name
|
||||
* @return Pointer to the stringified name of the CID, or
|
||||
* @return nullptr if not found, or invalid id
|
||||
*/
|
||||
const char * 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 Pointer to the stringified name of the CCF, or
|
||||
* @return nullptr 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 Pointer to the stringified name of the input, or
|
||||
* @return nullptr 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 Pointer to the stringified name of the input, or
|
||||
* @return nullptr 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 Pointer to the stringified name of the input, or
|
||||
* @return nullptr if 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 Pointer to the stringified name of the input, or
|
||||
* @return nullptr 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);
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,481 +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(("PLR => 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(("PLR => 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.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
|
||||
bool unique = true;
|
||||
auto it = Control_config_presets.begin();
|
||||
for (; it != Control_config_presets.end(); ++it) {
|
||||
if (preset.is_duplicate_of(*it)) {
|
||||
unique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (unique) {
|
||||
Control_config_presets.push_back(preset);
|
||||
} else {
|
||||
Warning(LOCATION, "Preset '%s' is a duplicate of '%s', ignoring", preset.name.c_str(), it->name.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -1,222 +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 Returns true if a preset file with the given name exists.
|
||||
*/
|
||||
bool preset_file_exists(SCP_string name);
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
|
||||
|
||||
auto ctx = stream->m_ctx->ctx();
|
||||
|
||||
auto videoStream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
|
||||
auto videoStream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &status->videoCodec, 0);
|
||||
if (videoStream < 0) {
|
||||
if (videoStream == AVERROR_STREAM_NOT_FOUND) {
|
||||
mprintf(("FFmpeg: No video stream found in file!\n"));
|
||||
@@ -242,26 +242,10 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
status->videoStreamIndex = videoStream;
|
||||
status->videoStream = ctx->streams[videoStream];
|
||||
status->videoCodecPars = getCodecParameters(status->videoStream);
|
||||
|
||||
status->videoCodec = avcodec_find_decoder(status->videoCodecPars.codec_id);
|
||||
|
||||
if ( !status->videoCodec ) {
|
||||
mprintf(("FFmpeg: Cannot find decoder for video stream!\n"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (properties.looping && status->videoCodecPars.codec_id == AV_CODEC_ID_INTERPLAY_VIDEO) {
|
||||
mprintf(("FFmpeg: Looping is not supported for inteplay (MVE) movies!\n"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int audioStream = -1;
|
||||
|
||||
if (properties.with_audio) {
|
||||
audioStream = av_find_best_stream(ctx, AVMEDIA_TYPE_AUDIO, -1, videoStream, nullptr, 0);
|
||||
audioStream = av_find_best_stream(ctx, AVMEDIA_TYPE_AUDIO, -1, videoStream, &status->audioCodec, 0);
|
||||
if (audioStream < 0) {
|
||||
if (audioStream == AVERROR_STREAM_NOT_FOUND) {
|
||||
mprintf(("FFmpeg: No audio stream found in file!\n"));
|
||||
@@ -270,21 +254,17 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
|
||||
} else {
|
||||
mprintf(("FFmpeg: Unknown error while finding audio stream!\n"));
|
||||
}
|
||||
} else {
|
||||
status->audioStreamIndex = audioStream;
|
||||
status->audioStream = ctx->streams[audioStream];
|
||||
status->audioCodecPars = getCodecParameters(status->audioStream);
|
||||
|
||||
status->audioCodec = avcodec_find_decoder(status->audioCodecPars.codec_id);
|
||||
|
||||
if ( !status->audioCodec ) {
|
||||
mprintf(("FFmpeg: Cannot find decoder for audio stream!\n"));
|
||||
status->audioStreamIndex = -1;
|
||||
audioStream = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status->videoStreamIndex = videoStream;
|
||||
status->videoStream = ctx->streams[videoStream];
|
||||
|
||||
if (audioStream >= 0) {
|
||||
status->audioStreamIndex = audioStream;
|
||||
status->audioStream = ctx->streams[audioStream];
|
||||
}
|
||||
|
||||
if (subt) {
|
||||
auto subtCtx = subt->m_ctx->ctx();
|
||||
|
||||
@@ -298,13 +278,9 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
|
||||
mprintf(("FFmpeg: Unknown error while finding subtitle stream!\n"));
|
||||
}
|
||||
} else {
|
||||
status->subtitleStreamIndex = subtStream;
|
||||
status->subtitleStream = subtCtx->streams[subtStream];
|
||||
status->externalSubtitles = true;
|
||||
// Not really sure if this makes sense for subtitles but it shouldn't break anything...
|
||||
status->subtitleCodecPars = getCodecParameters(status->subtitleStream);
|
||||
|
||||
status->subtitleCodec = avcodec_find_decoder(status->subtitleCodecPars.codec_id);
|
||||
status->subtitleStreamIndex = subtStream;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +312,13 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
|
||||
}
|
||||
}
|
||||
|
||||
status->videoCodecPars = getCodecParameters(status->videoStream);
|
||||
|
||||
if (properties.looping && status->videoCodecPars.codec_id == AV_CODEC_ID_INTERPLAY_VIDEO) {
|
||||
mprintf(("FFmpeg: Looping is not supported for inteplay (MVE) movies!\n"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int err;
|
||||
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
|
||||
status->videoCodecCtx = avcodec_alloc_context3(status->videoCodec);
|
||||
@@ -365,6 +348,8 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
|
||||
|
||||
// Now initialize audio, if this fails it's not a fatal error
|
||||
if (audioStream >= 0) {
|
||||
status->audioCodecPars = getCodecParameters(status->audioStream);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
|
||||
status->audioCodecCtx = avcodec_alloc_context3(status->audioCodec);
|
||||
|
||||
@@ -391,6 +376,11 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
|
||||
}
|
||||
|
||||
if (status->subtitleStream != nullptr) {
|
||||
// Not really sure if this makes sense for subtitles but it shouldn't break anything...
|
||||
status->subtitleCodecPars = getCodecParameters(status->subtitleStream);
|
||||
|
||||
status->subtitleCodec = avcodec_find_decoder(status->subtitleCodecPars.codec_id);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
|
||||
status->subtitleCodecCtx = avcodec_alloc_context3(status->subtitleCodec);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -205,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
|
||||
|
||||
+98
-55
@@ -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 > source_obj->radius * 20.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
|
||||
@@ -417,15 +460,15 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
if (!hull_flag) {
|
||||
if (model_num >= 0) {
|
||||
db->model_num = model_num;
|
||||
db->submodel_num = Random::next(sip->generic_debris_num_submodels);
|
||||
db->submodel_num = (myrand() >> 4) % sip->generic_debris_num_submodels;
|
||||
}
|
||||
else if (vaporize) {
|
||||
db->model_num = Debris_vaporize_model;
|
||||
db->submodel_num = Random::next(Debris_num_submodels);
|
||||
db->submodel_num = (myrand() >> 4) % Debris_num_submodels;
|
||||
}
|
||||
else {
|
||||
db->model_num = Debris_model;
|
||||
db->submodel_num = Random::next(Debris_num_submodels);
|
||||
db->submodel_num = (myrand() >> 4) % Debris_num_submodels;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -433,19 +476,12 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
db->submodel_num = submodel_num;
|
||||
}
|
||||
|
||||
db->model_instance_num = model_create_instance(false, db->model_num);
|
||||
|
||||
float radius = submodel_get_radius(db->model_num, db->submodel_num);
|
||||
|
||||
// if its generic, maybe cull it if its too small and far
|
||||
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,7 +491,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
{
|
||||
// Create Debris piece n!
|
||||
if ( hull_flag ) {
|
||||
if (Random::next() < (Random::MAX_VALUE/6)) // Make some pieces blow up shortly after explosion.
|
||||
if (rand() < RAND_MAX/6) // Make some pieces blow up shortly after explosion.
|
||||
db->lifeleft = 2.0f * (frand()) + 0.5f;
|
||||
else {
|
||||
db->flags.set(Debris_Flags::DoNotExpire);
|
||||
@@ -473,10 +509,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 +539,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;
|
||||
|
||||
@@ -521,7 +560,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
|
||||
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;
|
||||
@@ -537,10 +576,10 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
|
||||
vm_vector_2_matrix(&orient, &rand);
|
||||
}
|
||||
|
||||
objnum = obj_create(OBJ_DEBRIS, parent_objnum, n, &orient, pos, radius, default_flags, false);
|
||||
objnum = obj_create( OBJ_DEBRIS, parent_objnum, n, &orient, pos, radius, default_flags);
|
||||
if ( objnum == -1 ) {
|
||||
mprintf(("Couldn't create debris object -- out of object slots\n"));
|
||||
return nullptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db->objnum = objnum;
|
||||
@@ -548,8 +587,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 +629,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 +641,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 +652,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 +679,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);
|
||||
@@ -775,9 +812,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;
|
||||
}
|
||||
@@ -811,18 +848,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);
|
||||
}
|
||||
|
||||
@@ -854,18 +891,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;
|
||||
@@ -878,9 +915,15 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
|
||||
// turn on just one submodel for collision test
|
||||
smi->collision_checked = false;
|
||||
|
||||
// set angles for last frame (need to set to prev to get p0)
|
||||
matrix copy_matrix = smi->canonical_orient;
|
||||
|
||||
// find the start and end positions of the sphere in submodel RF
|
||||
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
|
||||
model_instance_global_to_local_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
|
||||
smi->canonical_orient = smi->canonical_prev_orient;
|
||||
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
|
||||
|
||||
smi->canonical_orient = copy_matrix;
|
||||
world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
|
||||
|
||||
mc.p0 = &p0;
|
||||
mc.p1 = &p1;
|
||||
@@ -893,19 +936,19 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
|
||||
mc_ret_val = 1;
|
||||
|
||||
// set up debris_hit_info common
|
||||
set_hit_struct_info(debris_hit_info, &mc, true);
|
||||
model_instance_local_to_global_point(&debris_hit_info->hit_pos, &mc.hit_point, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
|
||||
set_hit_struct_info(debris_hit_info, &mc, SUBMODEL_ROT_HIT);
|
||||
model_instance_find_world_point(&debris_hit_info->hit_pos, &mc.hit_point, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
|
||||
|
||||
// set up debris_hit_info for rotating submodel
|
||||
if (!debris_hit_info->edge_hit) {
|
||||
model_instance_local_to_global_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
if (debris_hit_info->edge_hit == 0) {
|
||||
model_instance_find_world_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
}
|
||||
|
||||
// find position in submodel RF of light object at collison
|
||||
vec3d int_light_pos, diff;
|
||||
vm_vec_sub(&diff, mc.p1, mc.p0);
|
||||
vm_vec_scale_add(&int_light_pos, mc.p0, &diff, mc.hit_dist);
|
||||
model_instance_local_to_global_point(&debris_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
|
||||
model_instance_find_world_point(&debris_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -926,11 +969,11 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
|
||||
if (mc.hit_dist < debris_hit_info->hit_time) {
|
||||
mc_ret_val = 1;
|
||||
|
||||
set_hit_struct_info(debris_hit_info, &mc, false);
|
||||
set_hit_struct_info(debris_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
|
||||
|
||||
// get collision normal if not edge hit
|
||||
if (!debris_hit_info->edge_hit) {
|
||||
model_instance_local_to_global_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
if (debris_hit_info->edge_hit == 0) {
|
||||
model_instance_find_world_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
||||
}
|
||||
|
||||
// find position in submodel RF of light object at collison
|
||||
@@ -956,7 +999,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 ) {
|
||||
@@ -1090,7 +1133,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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ typedef struct debris {
|
||||
gamesnd_id ambient_sound; // Ambient looping sound
|
||||
int objnum = -1; // What object this is linked to
|
||||
float lifeleft; // When 0 or less object dies
|
||||
int must_survive_until; //WMC - timestamp of earliest point that it can be murthered.
|
||||
int model_num; // What model this uses
|
||||
int model_instance_num; // What model instance this uses - needed for arcs
|
||||
int submodel_num; // What submodel this uses
|
||||
@@ -49,6 +50,7 @@ typedef struct debris {
|
||||
int fire_timeout; // timestamp that holds time for fireballs to stop appearing
|
||||
int sound_delay; // timestamp to signal when sound should start
|
||||
fix time_started; // time when debris was created
|
||||
int next_distance_check; // timestamp to determine whether to delete this piece of debris.
|
||||
|
||||
vec3d arc_pts[MAX_DEBRIS_ARCS][2]; // The endpoints of each arc
|
||||
int arc_timestamp[MAX_DEBRIS_ARCS]; // When this times out, the spark goes away. -1 is not used
|
||||
|
||||
+4
-15
@@ -244,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",
|
||||
@@ -273,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;
|
||||
@@ -411,7 +405,7 @@ matrix4 getDecalTransform(Decal& decal) {
|
||||
auto pm = model_get(pmi->model_num);
|
||||
|
||||
vec3d worldPos;
|
||||
model_instance_local_to_global_point(&worldPos,
|
||||
model_instance_find_world_point(&worldPos,
|
||||
&decal.position,
|
||||
pm,
|
||||
pmi,
|
||||
@@ -420,21 +414,16 @@ matrix4 getDecalTransform(Decal& decal) {
|
||||
&objp->pos);
|
||||
|
||||
vec3d worldDir;
|
||||
model_instance_local_to_global_dir(&worldDir,
|
||||
model_instance_find_world_dir(&worldDir,
|
||||
&decal.orientation.vec.fvec,
|
||||
pm,
|
||||
pmi,
|
||||
decal.submodel,
|
||||
&objp->orient);
|
||||
|
||||
// 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.
|
||||
vm_vec_scale(&worldDir, -1.0f);
|
||||
|
||||
vec3d worldUp;
|
||||
model_instance_local_to_global_dir(&worldUp,
|
||||
&decal.orientation.vec.uvec,
|
||||
model_instance_find_world_dir(&worldUp,
|
||||
&decal.orientation.vec.fvec,
|
||||
pm,
|
||||
pmi,
|
||||
decal.submodel,
|
||||
|
||||
@@ -12,7 +12,7 @@ layout (std140) uniform genericData {
|
||||
vec3 fog_color;
|
||||
float fog_start;
|
||||
|
||||
float fog_density;
|
||||
float fog_scale;
|
||||
float zNear;
|
||||
float zFar;
|
||||
};
|
||||
@@ -31,7 +31,9 @@ void main()
|
||||
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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -103,8 +103,6 @@ layout (std140) uniform modelData {
|
||||
int sNormalmapIndex;
|
||||
int sAmbientmapIndex;
|
||||
int sMiscmapIndex;
|
||||
|
||||
float alphaMult;
|
||||
};
|
||||
|
||||
in VertexOutput {
|
||||
|
||||
@@ -97,8 +97,6 @@ layout (std140) uniform modelData {
|
||||
int sNormalmapIndex;
|
||||
int sAmbientmapIndex;
|
||||
int sMiscmapIndex;
|
||||
|
||||
float alphaMult;
|
||||
};
|
||||
|
||||
#ifdef FLAG_TRANSFORM
|
||||
|
||||
@@ -7,25 +7,9 @@ uniform sampler2D tex;
|
||||
|
||||
layout (std140) uniform genericData {
|
||||
float exposure;
|
||||
int tonemapper;
|
||||
float x0; //from here on these are for the PPC tonemappers
|
||||
float y0;
|
||||
|
||||
float x1;
|
||||
float toe_B;
|
||||
float toe_lnA;
|
||||
float sh_B;
|
||||
|
||||
float sh_lnA;
|
||||
float sh_offsetX;
|
||||
float sh_offsetY;
|
||||
};
|
||||
//Tonemapping options, aside from the previous standard UC2, pulled from wookiejedi and qazwsxal's
|
||||
//work on testing them for FSO, which itself was based on these references:
|
||||
//https://64.github.io/tonemapping/ Delta - Blog by 64: Tone Mapping
|
||||
//http://filmicworlds.com/blog/filmic-tonemapping-operators/ Filmic Worlds: Filmic Tonemapping Operators by John Hable
|
||||
|
||||
vec3 Uncharted2ToneMapping(vec3 hdr_color) // John Hable's filmic
|
||||
vec3 Uncharted2Tonemapping(vec3 hdr_color)
|
||||
{
|
||||
float A = 0.15;
|
||||
float B = 0.50;
|
||||
@@ -33,185 +17,15 @@ vec3 Uncharted2ToneMapping(vec3 hdr_color) // John Hable's filmic
|
||||
float D = 0.20;
|
||||
float E = 0.02;
|
||||
float F = 0.30;
|
||||
float W = 11.2;
|
||||
hdr_color = ((hdr_color * (A * hdr_color + C * B) + D * E) / (hdr_color * (A * hdr_color + B) + D * F)) - E / F;
|
||||
float white = ((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F;
|
||||
hdr_color /= white;
|
||||
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
|
||||
|
||||
return hdr_color;
|
||||
return ((hdr_color*(A*hdr_color+C*B)+D*E)/(hdr_color*(A*hdr_color+B)+D*F))-E/F;
|
||||
}
|
||||
|
||||
vec3 rtt_and_odt_fit(vec3 v)
|
||||
{
|
||||
vec3 a = v * (v + 0.0245786f) - 0.000090537f;
|
||||
vec3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f;
|
||||
return a / b;
|
||||
}
|
||||
|
||||
|
||||
const mat3 aces_input = mat3(vec3(0.59719, 0.07600, 0.02840),
|
||||
vec3(0.35458, 0.90834, 0.13383),
|
||||
vec3(0.04823, 0.01566, 0.83777)
|
||||
);
|
||||
|
||||
const mat3 aces_output = mat3(vec3( 1.60475, -0.10208, -0.00327),
|
||||
vec3(-0.53108, 1.10813, -0.07276),
|
||||
vec3(-0.07367, -0.00605, 1.07602)
|
||||
);
|
||||
|
||||
vec3 aces(vec3 hdr_color)
|
||||
{
|
||||
hdr_color = aces_input * hdr_color;
|
||||
hdr_color = rtt_and_odt_fit(hdr_color);
|
||||
hdr_color = aces_output * hdr_color;
|
||||
hdr_color = linear_to_srgb(hdr_color);
|
||||
return hdr_color;
|
||||
}
|
||||
|
||||
vec3 aces_approx(vec3 hdr_color) // also filmic, higher contrast then Uncharted2ToneMapping
|
||||
{
|
||||
const float a = 2.51f;
|
||||
const float b = 0.03f;
|
||||
const float c = 2.43f;
|
||||
const float d = 0.59f;
|
||||
const float e = 0.14f;
|
||||
hdr_color *= 0.6f;
|
||||
|
||||
hdr_color = clamp((hdr_color*(a*hdr_color+b))/(hdr_color*(c*hdr_color+d)+e), 0.0f, 1.0f);
|
||||
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
|
||||
return hdr_color;
|
||||
}
|
||||
|
||||
vec3 OptimizedCineonToneMapping( vec3 color ) {
|
||||
// optimized filmic operator by Jim Hejl and Richard Burgess-Dawson
|
||||
color = max( vec3( 0.0 ), color - 0.004 );
|
||||
color = (color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 );
|
||||
// linear to sRGB conversion embedded in shader
|
||||
return color;
|
||||
}
|
||||
|
||||
vec3 rhj_lumaToneMapping(vec3 hdr_color) // reinhard_jodie
|
||||
{
|
||||
float luma = dot(hdr_color, vec3(0.2126, 0.7152, 0.0722)); //dot products are a bit more expensive
|
||||
float toneMappedLuma = luma / (1.0 + luma);
|
||||
hdr_color *= toneMappedLuma / luma;
|
||||
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
|
||||
return hdr_color;
|
||||
}
|
||||
|
||||
vec3 reinhard_extended(vec3 hdr_color)
|
||||
{
|
||||
float max_white = 1.0;
|
||||
vec3 numerator = hdr_color * (1.0f + (hdr_color / vec3(max_white * max_white)));
|
||||
hdr_color = numerator / (1.0f + hdr_color);
|
||||
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
|
||||
return hdr_color;
|
||||
}
|
||||
|
||||
vec3 linearToneMapping(vec3 hdr_color)
|
||||
{
|
||||
hdr_color = clamp(hdr_color, 0., 1.);
|
||||
hdr_color = linear_to_srgb(hdr_color); // return from linear color space to SRGB color space
|
||||
return hdr_color;
|
||||
}
|
||||
|
||||
float toe( float x ) {
|
||||
return exp(toe_lnA + toe_B * log(x));
|
||||
}
|
||||
|
||||
float linear( float x ) {
|
||||
// Slope is 1 by definition
|
||||
return y0 + (x - x0);
|
||||
}
|
||||
|
||||
float shoulder ( float x ) {
|
||||
// Scale is -1 so reverse subtraction to save a mult
|
||||
x = sh_offsetX - x;
|
||||
x = exp(sh_lnA + sh_B * log(x));
|
||||
x = sh_offsetY - x;
|
||||
return x;
|
||||
}
|
||||
|
||||
float funswitch (float x) {
|
||||
float x_tone;
|
||||
if (x <= x0) {
|
||||
x_tone = toe(x);
|
||||
}
|
||||
else if (x <= x1) {
|
||||
x_tone = linear(x);
|
||||
}
|
||||
else if ( x < sh_offsetX ){
|
||||
x_tone = shoulder(x);
|
||||
}
|
||||
else {
|
||||
x_tone = sh_offsetY;
|
||||
}
|
||||
return x_tone;
|
||||
}
|
||||
|
||||
vec3 PPC_RGB( vec3 color ) {
|
||||
color = vec3( funswitch(color.r),
|
||||
funswitch(color.g),
|
||||
funswitch(color.b));
|
||||
|
||||
return linear_to_srgb(color);
|
||||
}
|
||||
|
||||
|
||||
vec3 PPC( vec3 color ) {
|
||||
float luma = dot(color, vec3(0.2126, 0.7152, 0.0722));
|
||||
float luma_tone;
|
||||
if ( luma <= x0 ) {
|
||||
luma_tone = toe(luma);
|
||||
}
|
||||
else if ( luma <= x1 ) {
|
||||
luma_tone = linear(luma);
|
||||
}
|
||||
else if ( luma < sh_offsetX ){
|
||||
luma_tone = shoulder(luma);
|
||||
}
|
||||
else {
|
||||
luma_tone = sh_offsetY;
|
||||
}
|
||||
return linear_to_srgb(color * luma_tone / luma);
|
||||
}
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture(tex, fragTexCoord.xy);
|
||||
color *= exposure;
|
||||
|
||||
switch(tonemapper){
|
||||
case 0:
|
||||
color.rgb = linearToneMapping(color.rgb);
|
||||
break;
|
||||
case 1:
|
||||
color.rgb = Uncharted2ToneMapping(color.rgb);
|
||||
break;
|
||||
case 2:
|
||||
color.rgb = aces(color.rgb);
|
||||
break;
|
||||
case 3:
|
||||
color.rgb = aces_approx(color.rgb);
|
||||
break;
|
||||
case 4:
|
||||
color.rgb = OptimizedCineonToneMapping(color.rgb);
|
||||
break;
|
||||
case 5:
|
||||
color.rgb = rhj_lumaToneMapping(color.rgb);
|
||||
break;
|
||||
case 6:
|
||||
color.rgb = reinhard_extended(color.rgb);
|
||||
break;
|
||||
case 7:
|
||||
color.rgb = PPC(color.rgb);
|
||||
break;
|
||||
case 8:
|
||||
color.rgb = PPC_RGB(color.rgb);
|
||||
break;
|
||||
|
||||
}
|
||||
// Tone mapping using John Hable's Uncharted 2 tonemapping algorithm.
|
||||
float whitepoint = 11.2f; // hardcoded whitepoint value from Hable's algo
|
||||
color.rgb = Uncharted2Tonemapping(color.rgb * exposure) / Uncharted2Tonemapping(vec3(whitepoint));
|
||||
color.rgb = linear_to_srgb(color.rgb); // return from linear color space to SRGB color space
|
||||
fragOut0 = vec4(color.rgb, 1.0);
|
||||
}
|
||||
+11
-35
@@ -14,7 +14,6 @@
|
||||
#include "graphics/tmapper.h"
|
||||
#include "localization/localize.h"
|
||||
#include "model/model.h"
|
||||
#include "nebula/neb.h"
|
||||
#include "object/object.h"
|
||||
#include "options/Option.h"
|
||||
#include "parse/parselo.h"
|
||||
@@ -26,7 +25,7 @@
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
bool Knossos_warp_ani_used;
|
||||
int Knossos_warp_ani_used;
|
||||
|
||||
#define WARPHOLE_GROW_TIME (2.35f) // time for warphole to reach max size (also time to shrink to nothing once it begins to shrink)
|
||||
|
||||
@@ -203,10 +202,6 @@ static void fireball_set_default_warp_attributes(int idx)
|
||||
strcpy_s(Fireball_info[idx].warp_glow, "warpglow01");
|
||||
strcpy_s(Fireball_info[idx].warp_ball, "warpball01");
|
||||
strcpy_s(Fireball_info[idx].warp_model, "warp.pof");
|
||||
|
||||
if (Fireball_use_3d_warp)
|
||||
Fireball_info[idx].use_3d_warp = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -291,13 +286,11 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
}
|
||||
}
|
||||
|
||||
bool first_time;
|
||||
// now select our entry accordingly...
|
||||
// are we using a previous entry?
|
||||
if (existing_idx >= 0)
|
||||
{
|
||||
fi = &Fireball_info[existing_idx];
|
||||
first_time = false;
|
||||
}
|
||||
// we are creating a new entry, so set some defaults
|
||||
else
|
||||
@@ -321,7 +314,6 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
fireball_set_default_warp_attributes(Num_fireball_types);
|
||||
|
||||
Num_fireball_types++;
|
||||
first_time = true;
|
||||
}
|
||||
|
||||
// copy over what we already parsed
|
||||
@@ -329,7 +321,6 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
strcpy_s(fi->unique_id, unique_id);
|
||||
strcpy_s(fi->lod[0].filename, fireball_filename);
|
||||
|
||||
|
||||
// Do we have a LOD num?
|
||||
if (optional_string("$LOD:"))
|
||||
{
|
||||
@@ -337,9 +328,6 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
|
||||
if (fi->lod_count > MAX_FIREBALL_LOD)
|
||||
fi->lod_count = MAX_FIREBALL_LOD;
|
||||
} else if (first_time) {
|
||||
//assume a LOD of at least 1
|
||||
fi->lod_count = 1;
|
||||
}
|
||||
|
||||
// check for particular lighting color
|
||||
@@ -370,13 +358,7 @@ static void parse_fireball_tbl(const char *table_filename)
|
||||
|
||||
// check for custom warp model
|
||||
if (optional_string("$Warp model:"))
|
||||
{
|
||||
stuff_string(fi->warp_model, F_NAME, NAME_LENGTH);
|
||||
|
||||
// if we are explicitly specifying a model, then we'll want to use it,
|
||||
// rather than just having the default model that might or might not be used
|
||||
fi->use_3d_warp = true;
|
||||
}
|
||||
}
|
||||
|
||||
required_string("#End");
|
||||
@@ -470,7 +452,7 @@ void fireball_init()
|
||||
Unused_fireball_indices.reserve(INTITIAL_FIREBALL_CONTAINTER_SIZE);
|
||||
|
||||
// Goober5000 - reset Knossos warp flag
|
||||
Knossos_warp_ani_used = false;
|
||||
Knossos_warp_ani_used = 0;
|
||||
}
|
||||
|
||||
MONITOR( NumFireballsRend )
|
||||
@@ -848,7 +830,7 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
|
||||
|
||||
flagset<Object::Object_Flags> default_flags;
|
||||
default_flags.set(Object::Object_Flags::Renders);
|
||||
objnum = obj_create(OBJ_FIREBALL, parent_obj, n, &orient, pos, size, default_flags, false);
|
||||
objnum = obj_create(OBJ_FIREBALL, parent_obj, n, &orient, pos, size, default_flags);
|
||||
|
||||
obj = &Objects[objnum];
|
||||
|
||||
@@ -861,11 +843,11 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
|
||||
switch( new_fireball->fireball_render_type ) {
|
||||
|
||||
case FIREBALL_MEDIUM_EXPLOSION:
|
||||
new_fireball->orient = Random::next() & 7; // 0 - 7
|
||||
new_fireball->orient = (myrand()>>8) & 7; // 0 - 7
|
||||
break;
|
||||
|
||||
case FIREBALL_LARGE_EXPLOSION:
|
||||
new_fireball->orient = Random::next(360); // 0 - 359
|
||||
new_fireball->orient = (myrand()>>8) % 360; // 0 - 359
|
||||
break;
|
||||
|
||||
case FIREBALL_WARP_EFFECT:
|
||||
@@ -996,9 +978,9 @@ int fireball_ship_explosion_type(ship_info *sip)
|
||||
}
|
||||
|
||||
if(ship_fireballs > 0){
|
||||
index = sip->explosion_bitmap_anims[Random::next(ship_fireballs)];
|
||||
index = sip->explosion_bitmap_anims[rand()%ship_fireballs];
|
||||
} else if(objecttype_fireballs > 0){
|
||||
index = Ship_types[sip->class_type].explosion_bitmap_anims[Random::next(objecttype_fireballs)];
|
||||
index = Ship_types[sip->class_type].explosion_bitmap_anims[rand()%objecttype_fireballs];
|
||||
}
|
||||
|
||||
return index;
|
||||
@@ -1015,7 +997,7 @@ int fireball_asteroid_explosion_type(asteroid_info *aip)
|
||||
int roid_fireballs = (int)aip->explosion_bitmap_anims.size();
|
||||
|
||||
if (roid_fireballs > 0) {
|
||||
index = aip->explosion_bitmap_anims[Random::next(roid_fireballs)];
|
||||
index = aip->explosion_bitmap_anims[rand()%roid_fireballs];
|
||||
}
|
||||
|
||||
return index;
|
||||
@@ -1055,25 +1037,19 @@ void fireball_render(object* obj, model_draw_list *scene)
|
||||
|
||||
if ( fb->current_bitmap < 0 )
|
||||
return;
|
||||
|
||||
float alpha = 1.0f;
|
||||
|
||||
if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_fireballs)
|
||||
alpha *= neb2_get_fog_visibility(&obj->pos,
|
||||
Neb2_fog_visibility_fireball_const + (obj->radius * Neb2_fog_visibility_fireball_scaled_factor));
|
||||
|
||||
g3_transfer_vertex(&p, &obj->pos);
|
||||
|
||||
switch ( fb->fireball_render_type ) {
|
||||
|
||||
case FIREBALL_MEDIUM_EXPLOSION: {
|
||||
batching_add_volume_bitmap(fb->current_bitmap, &p, fb->orient, obj->radius, alpha);
|
||||
batching_add_volume_bitmap(fb->current_bitmap, &p, fb->orient, obj->radius);
|
||||
}
|
||||
break;
|
||||
|
||||
case FIREBALL_LARGE_EXPLOSION: {
|
||||
// Make the big explosions rotate with the viewer.
|
||||
batching_add_volume_bitmap_rotated(fb->current_bitmap, &p, (i2fl(fb->orient)*PI) / 180.0f, obj->radius, alpha);
|
||||
batching_add_volume_bitmap_rotated(fb->current_bitmap, &p, (i2fl(fb->orient)*PI) / 180.0f, obj->radius);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1082,7 +1058,7 @@ void fireball_render(object* obj, model_draw_list *scene)
|
||||
float rad = obj->radius * fireball_wormhole_intensity(fb);
|
||||
|
||||
fireball_info *fi = &Fireball_info[fb->fireball_info_index];
|
||||
warpin_queue_render(scene, obj, &obj->orient, &obj->pos, fb->current_bitmap, rad, percent_life, obj->radius, fi->use_3d_warp || (fb->flags & FBF_WARP_3D) != 0, fi->warp_glow_bitmap, fi->warp_ball_bitmap, fi->warp_model_id);
|
||||
warpin_queue_render(scene, obj, &obj->orient, &obj->pos, fb->current_bitmap, rad, percent_life, obj->radius, (fb->flags & FBF_WARP_3D) != 0, fi->warp_glow_bitmap, fi->warp_ball_bitmap, fi->warp_model_id);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -58,8 +58,6 @@ typedef struct fireball_info {
|
||||
fireball_lod lod[MAX_FIREBALL_LOD];
|
||||
float exp_color[3]; // red, green, blue
|
||||
|
||||
bool use_3d_warp;
|
||||
|
||||
char warp_glow[NAME_LENGTH];
|
||||
int warp_glow_bitmap;
|
||||
char warp_ball[NAME_LENGTH];
|
||||
@@ -143,7 +141,7 @@ int fireball_asteroid_explosion_type(asteroid_info *aip);
|
||||
float fireball_wormhole_intensity( fireball *fb );
|
||||
|
||||
// Goober5000
|
||||
extern bool Knossos_warp_ani_used;
|
||||
extern int Knossos_warp_ani_used;
|
||||
|
||||
extern bool Fireball_use_3d_warp;
|
||||
|
||||
|
||||
@@ -91,14 +91,14 @@ void warpin_queue_render(model_draw_list *scene, object *obj, matrix *orient, ve
|
||||
|
||||
g3_transfer_vertex( &verts[4], &vecs[4] );
|
||||
|
||||
float alpha = (The_mission.flags[Mission::Mission_Flags::Fullneb]) ? neb2_get_fog_visibility(&obj->pos, 1.0f) : 1.0f;
|
||||
float alpha = (The_mission.flags[Mission::Mission_Flags::Fullneb]) ? (1.0f - neb2_get_fog_intensity(obj)) : 1.0f;
|
||||
|
||||
//batch_add_bitmap(warp_glow_bitmap, TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT, &verts[4], 0, r, alpha);
|
||||
batching_add_bitmap(warp_glow_bitmap, &verts[4], 0, r, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
if ( (warp_model_id >= 0) && (warp_3d) ) {
|
||||
if ( (warp_model_id >= 0) && (warp_3d || Fireball_use_3d_warp) ) {
|
||||
model_render_params render_info;
|
||||
|
||||
float scale = radius / 25.0f;
|
||||
|
||||
@@ -239,10 +239,7 @@ bool GameState_Stack_Valid()
|
||||
|
||||
// returns one of the GS_STATE_ macros
|
||||
int gameseq_get_state(int depth)
|
||||
{
|
||||
if (!GameState_Stack_Valid())
|
||||
return GS_STATE_INVALID;
|
||||
|
||||
{
|
||||
Assert(depth <= gs_current_stack);
|
||||
|
||||
return gs[gs_current_stack - depth].current_state;
|
||||
|
||||
@@ -659,12 +659,7 @@ void event_music_first_pattern()
|
||||
audiostream_stop( Patterns[Current_pattern].handle );
|
||||
}
|
||||
|
||||
// if we really are initializing the level
|
||||
if (Missiontime == 0) {
|
||||
Pattern_timer_id = timestamp(-1); // don't let this start quite yet; see event_music_set_start_delay()
|
||||
} else {
|
||||
Pattern_timer_id = timestamp(0); // start immediately
|
||||
}
|
||||
Pattern_timer_id = 2000; // start music delay
|
||||
|
||||
Event_music_begun = FALSE;
|
||||
if ( Event_Music_battle_started == TRUE ) {
|
||||
@@ -675,13 +670,6 @@ void event_music_first_pattern()
|
||||
}
|
||||
}
|
||||
|
||||
// Event music was originally hard-coded to start at 2 seconds, regardless of the timestamp. Since the default player entry delay is 1 second,
|
||||
// this yields a relative start time of 1 second. Mission start will now use relative rather than absolute delay.
|
||||
void event_music_set_start_delay()
|
||||
{
|
||||
Pattern_timer_id = timestamp(1000);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// event_music_level_close()
|
||||
//
|
||||
|
||||
@@ -107,7 +107,6 @@ void event_music_parse_musictbl(const char *filename);
|
||||
void event_music_change_pattern(int new_pattern);
|
||||
int event_music_return_current_pattern();
|
||||
void event_music_first_pattern();
|
||||
void event_music_set_start_delay();
|
||||
int event_music_battle_start();
|
||||
int event_music_enemy_arrival();
|
||||
int event_music_friendly_arrival();
|
||||
|
||||
+23
-43
@@ -526,7 +526,7 @@ void parse_iface_sound_list(const char* tag, SCP_vector<interface_snd_id>& desti
|
||||
//if we're using the old format, double check the size)
|
||||
if(!scp_list && (destination.size() != (unsigned)check))
|
||||
{
|
||||
mprintf(("%s in '%s' has " SIZE_T_ARG " entries. This does not match entered size of %i.\n", tag, object_name, destination.size(), check));
|
||||
mprintf(("%s in '%s' has " SIZE_T_ARG " entries. This does not match entered size of %i.", tag, object_name, destination.size(), check));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -544,12 +544,12 @@ void gamesnd_preload_common_sounds()
|
||||
return;
|
||||
|
||||
Assert( Snds.size() <= INT_MAX );
|
||||
for (auto& gs: Snds) {
|
||||
if ( gs.preload ) {
|
||||
for (auto& entry : gs.sound_entries) {
|
||||
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
|
||||
if ( gs->preload ) {
|
||||
for (auto& entry : gs->sound_entries) {
|
||||
if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0 ) {
|
||||
game_busy( NOX("** preloading common game sounds **") ); // Animate loading cursor... does nothing if loading screen not active.
|
||||
entry.id = snd_load(&entry, &gs.flags);
|
||||
entry.id = snd_load(&entry, gs->flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -565,12 +565,12 @@ void gamesnd_load_gameplay_sounds()
|
||||
return;
|
||||
|
||||
Assert( Snds.size() <= INT_MAX );
|
||||
for (auto& gs: Snds) {
|
||||
if ( !gs.preload ) { // don't try to load anything that's already preloaded
|
||||
for (auto& entry : gs.sound_entries) {
|
||||
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
|
||||
if ( !gs->preload ) { // don't try to load anything that's already preloaded
|
||||
for (auto& entry : gs->sound_entries) {
|
||||
if (entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0) {
|
||||
game_busy(NOX("** preloading gameplay sounds **")); // Animate loading cursor... does nothing if loading screen not active.
|
||||
entry.id = snd_load(&entry, &gs.flags);
|
||||
entry.id = snd_load(&entry, gs->flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -583,8 +583,8 @@ void gamesnd_load_gameplay_sounds()
|
||||
void gamesnd_unload_gameplay_sounds()
|
||||
{
|
||||
Assert( Snds.size() <= INT_MAX );
|
||||
for (auto& gs: Snds) {
|
||||
for (auto& entry : gs.sound_entries) {
|
||||
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
|
||||
for (auto& entry : gs->sound_entries) {
|
||||
if (entry.id.isValid()) {
|
||||
snd_unload(entry.id);
|
||||
entry.id = sound_load_id::invalid();
|
||||
@@ -602,10 +602,10 @@ void gamesnd_load_interface_sounds()
|
||||
return;
|
||||
|
||||
Assert( Snds_iface.size() < INT_MAX );
|
||||
for (auto& gs: Snds) {
|
||||
for (auto& entry : gs.sound_entries) {
|
||||
for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
|
||||
for (auto& entry : si->sound_entries) {
|
||||
if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0 ) {
|
||||
entry.id = snd_load(&entry, &gs.flags);
|
||||
entry.id = snd_load(&entry, si->flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -642,11 +642,10 @@ void parse_gamesnd_old(game_snd* gs)
|
||||
|
||||
stuff_string(entry.filename, F_NAME, MAX_FILENAME_LEN, ",");
|
||||
|
||||
if (!stricmp(entry.filename, NOX("empty")) || !stricmp(entry.filename, NOX("none")))
|
||||
if (!stricmp(entry.filename, NOX("empty")))
|
||||
{
|
||||
entry.filename[0] = 0;
|
||||
advance_to_eoln(nullptr);
|
||||
gs->flags |= GAME_SND_NOT_VALID;
|
||||
advance_to_eoln(NULL);
|
||||
return;
|
||||
}
|
||||
Mp++;
|
||||
@@ -752,14 +751,14 @@ bool required_string_no_create(const char* token, bool no_create)
|
||||
|
||||
static GameSoundCycleType parse_cycle_type() {
|
||||
if (optional_string("Sequential")) {
|
||||
return GameSoundCycleType::SequentialCycle;
|
||||
return GameSoundCycleType::Sequential;
|
||||
} else if (optional_string("Random")) {
|
||||
return GameSoundCycleType::RandomCycle;
|
||||
return GameSoundCycleType::Random;
|
||||
} else {
|
||||
error_display(0, "Failed to parse sound cycle type. Expected 'sequential' or 'random'. Got [%.32s]", next_tokens());
|
||||
// Ignore everything until the end of the line. That should hopefully skip the bad token.
|
||||
advance_to_eoln(nullptr);
|
||||
return GameSoundCycleType::SequentialCycle;
|
||||
return GameSoundCycleType::Sequential;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -861,10 +860,9 @@ void parse_gamesnd_new(game_snd* gs, bool no_create)
|
||||
// Default pitch is 1.0. This is set here in case we don't have a valid file name
|
||||
gs->pitch_range = util::UniformFloatRange(1.0f);
|
||||
|
||||
if (!stricmp(name, NOX("empty")) || !stricmp(name, NOX("none")))
|
||||
if (!stricmp(name, NOX("empty")))
|
||||
{
|
||||
entry->filename[0] = 0;
|
||||
gs->flags |= GAME_SND_NOT_VALID;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1345,31 +1343,13 @@ bool gamesnd_interface_sound_valid(interface_snd_id sound) {
|
||||
return sound.isValid() && sound.value() < (int) Snds_iface.size();
|
||||
}
|
||||
|
||||
bool gamesnd_game_sound_try_load(gamesnd_id sound)
|
||||
{
|
||||
if (!gamesnd_game_sound_valid(sound)) {
|
||||
return false;
|
||||
}
|
||||
auto gs = gamesnd_get_game_sound(sound);
|
||||
|
||||
for (auto& entry : gs->sound_entries) {
|
||||
if (!entry.id.isValid()) {
|
||||
// Lazily load unloaded sound entries when required
|
||||
entry.id = snd_load(&entry, &gs->flags);
|
||||
}
|
||||
}
|
||||
|
||||
// check flag
|
||||
return (gs->flags & GAME_SND_NOT_VALID) == 0;
|
||||
}
|
||||
|
||||
float gamesnd_get_max_duration(game_snd* gs) {
|
||||
int max_length = 0;
|
||||
|
||||
for (auto& entry : gs->sound_entries) {
|
||||
if (!entry.id.isValid()) {
|
||||
// Lazily load unloaded sound entries when required
|
||||
entry.id = snd_load(&entry, &gs->flags);
|
||||
entry.id = snd_load(&entry, gs->flags);
|
||||
}
|
||||
|
||||
max_length = std::max(max_length, snd_get_duration(entry.id));
|
||||
@@ -1382,14 +1362,14 @@ game_snd_entry* gamesnd_choose_entry(game_snd* gs) {
|
||||
|
||||
size_t index = 0;
|
||||
switch(gs->cycle_type) {
|
||||
case GameSoundCycleType::RandomCycle:
|
||||
case GameSoundCycleType::Random:
|
||||
if (gs->sound_entries.size() == 1) {
|
||||
index = 0;
|
||||
} else {
|
||||
index = util::UniformRange<size_t>(0, gs->sound_entries.size() - 1).next();
|
||||
}
|
||||
break;
|
||||
case GameSoundCycleType::SequentialCycle:
|
||||
case GameSoundCycleType::Sequential:
|
||||
if (gs->last_entry_index == std::numeric_limits<size_t>::max()) {
|
||||
// If this is the first time we must return the first sound to keep everything consistent
|
||||
index = 0;
|
||||
|
||||
@@ -401,11 +401,6 @@ game_snd* gamesnd_get_interface_sound(interface_snd_id handle);
|
||||
*/
|
||||
bool gamesnd_game_sound_valid(gamesnd_id sound);
|
||||
|
||||
/**
|
||||
* @brief Checks if the given sound handle can be loaded
|
||||
*/
|
||||
bool gamesnd_game_sound_try_load(gamesnd_id sound);
|
||||
|
||||
/**
|
||||
* @brief Checks if the given sound handle is a valid interface sound handle
|
||||
* @param sound The handle to check
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#define CALLSIGN_LEN (MAX_FILENAME_LEN - 4 - 1) // shortened from 32 to allow .json to be attached without exceeding MAX_FILENAME_LEN
|
||||
#define SHORT_CALLSIGN_PIXEL_W 80 // max width of short_callsign[] in pixels
|
||||
|
||||
#define MAX_IFFS 10
|
||||
|
||||
// from ship.h
|
||||
#define MAX_SHIPS 400 // max number of ship instances there can be.DTP; bumped from 200 to 400
|
||||
#define SHIPS_LIMIT 400 // what MAX_SHIPS will be at release time (for error checking in debug mode); dtp Bumped from 200 to 400
|
||||
|
||||
@@ -113,8 +113,6 @@ typedef struct vec2d {
|
||||
float x, y;
|
||||
} vec2d;
|
||||
|
||||
// Euler angles for a rotation: h=heading, b=bank and p=pitch; angles are around the y, z and x axes
|
||||
// respectively and are performed in that order.
|
||||
typedef struct angles {
|
||||
float p, b, h;
|
||||
} angles_t;
|
||||
@@ -130,7 +128,6 @@ typedef struct matrix {
|
||||
};
|
||||
} matrix;
|
||||
|
||||
// You might think this is also row-major. You fool! It is actually column-major.
|
||||
typedef struct matrix4 {
|
||||
union {
|
||||
struct {
|
||||
@@ -306,6 +303,8 @@ constexpr bool LoggingEnabled = false;
|
||||
const float PI2 = (PI*2.0f);
|
||||
// half values
|
||||
const float PI_2 = (PI/2.0f);
|
||||
const int RAND_MAX_2 = (RAND_MAX/2);
|
||||
const float RAND_MAX_1f = (1.0f / RAND_MAX);
|
||||
|
||||
|
||||
extern int Fred_running; // Is Fred running, or FreeSpace?
|
||||
@@ -351,6 +350,14 @@ const size_t INVALID_SIZE = static_cast<size_t>(-1);
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
int myrand();
|
||||
|
||||
// Returns a random number between 0 and 0x7fffffff
|
||||
int rand32();
|
||||
|
||||
// Returns a random integer from low to high, inclusive
|
||||
int rand32(int low, int high);
|
||||
|
||||
|
||||
// lod checker for (modular) table parsing
|
||||
typedef struct lod_checker {
|
||||
@@ -456,6 +463,7 @@ SCP_string dump_stacktrace();
|
||||
// DEBUG compile time catch for dangerous uses of memset/memcpy/memmove
|
||||
// This is disabled for VS2013 and lower since that doesn't support the necessary features
|
||||
#if !defined(NDEBUG) && !defined(USING_THIRD_PARTY_LIBS) && (!defined(_MSC_VER) || _MSC_VER >= 1900)
|
||||
#if SCP_COMPILER_CXX_AUTO_TYPE && SCP_COMPILER_CXX_STATIC_ASSERT && HAVE_STD_IS_TRIVIALLY_COPYABLE
|
||||
// feature support seems to be: gcc clang msvc
|
||||
// auto 4.4 2.9 2010
|
||||
// std::is_trivial 4.5 ? 2012 (2010 only duplicates std::is_pod)
|
||||
@@ -555,6 +563,7 @@ using std::memcpy_if_trivial_else_error;
|
||||
using std::memmove_if_trivial_else_error;
|
||||
using std::memset_if_trivial_else_error;
|
||||
|
||||
#endif // HAVE_CXX11
|
||||
#endif // NDEBUG
|
||||
|
||||
#endif // _PSTYPES_H
|
||||
#endif // PS_TYPES_H
|
||||
@@ -3,6 +3,8 @@
|
||||
#define SCP_DEFINES_H
|
||||
#pragma once
|
||||
|
||||
#include "scp_compiler_detection.h"
|
||||
|
||||
#define SCP_TOKEN_CONCAT1(x, y) x ## y
|
||||
#define SCP_TOKEN_CONCAT(x, y) SCP_TOKEN_CONCAT1(x, y)
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ vci Viewer_chase_info; // View chase camera information
|
||||
vec3d leaning_position;
|
||||
|
||||
int Is_standalone;
|
||||
int Rand_count;
|
||||
|
||||
int Interface_last_tick = -1; // last timer tick on flip
|
||||
|
||||
@@ -108,6 +109,47 @@ float Noise[NOISE_NUM_FRAMES] = {
|
||||
0.000000f
|
||||
};
|
||||
|
||||
|
||||
int myrand()
|
||||
{
|
||||
int rval;
|
||||
rval = rand();
|
||||
Rand_count++;
|
||||
// nprintf(("Alan","RAND: %d\n", rval));
|
||||
return rval;
|
||||
}
|
||||
|
||||
// returns a random number between 0 and 0x7fffffff, or something close to it.
|
||||
int rand32()
|
||||
{
|
||||
if (RAND_MAX == 0x7fff) {
|
||||
int random32;
|
||||
// this gets two random 16 bit numbers and stuffs them into a 32 bit number
|
||||
random32 = (rand() << 16) | rand();
|
||||
//since rand() returns between 0 and 0x7fff, there needs to be a thing to randomly generate the 16th bit
|
||||
random32 |= ((rand() & 1) << 15);
|
||||
|
||||
return random32;
|
||||
}
|
||||
else {
|
||||
return rand();
|
||||
}
|
||||
}
|
||||
|
||||
int rand32(int low, int high)
|
||||
{
|
||||
int diff;
|
||||
|
||||
// get diff - don't allow negative or zero
|
||||
diff = high - low;
|
||||
if (diff < 0)
|
||||
diff = 0;
|
||||
|
||||
// To get a range of values between min and max, inclusive:
|
||||
// random value = min + random number % (max - min + 1)
|
||||
return (low + rand32() % (diff + 1));
|
||||
}
|
||||
|
||||
// Variables for the loading callback hooks
|
||||
static int cf_timestamp = -1;
|
||||
static void (*cf_callback)(int count) = NULL;
|
||||
@@ -243,8 +285,8 @@ detail_levels Detail_defaults[NUM_DEFAULT_DETAIL_LEVELS] = {
|
||||
{ // Highest level
|
||||
3, // setting
|
||||
// ===== Analogs (0-MAX_DETAIL_LEVEL) ====
|
||||
4, // nebula_detail; // 0=lowest detail, MAX_DETAIL_LEVEL=highest detail
|
||||
4, // detail_distance; // 0=lowest MAX_DETAIL_LEVEL=highest
|
||||
3, // nebula_detail; // 0=lowest detail, MAX_DETAIL_LEVEL=highest detail
|
||||
3, // detail_distance; // 0=lowest MAX_DETAIL_LEVEL=highest
|
||||
4, // hardware_textures; // 0=max culling, MAX_DETAIL_LEVEL=no culling
|
||||
4, // num_small_debris; // 0=min number, MAX_DETAIL_LEVEL=max number
|
||||
4, // num_particles; // 0=min number, MAX_DETAIL_LEVEL=max number
|
||||
@@ -328,6 +370,9 @@ void detail_level_set(int level)
|
||||
Assert( level < NUM_DEFAULT_DETAIL_LEVELS );
|
||||
|
||||
Detail = Detail_defaults[level];
|
||||
|
||||
// reset nebula stuff
|
||||
neb2_set_detail_level(level);
|
||||
}
|
||||
|
||||
// Returns the current detail level or -1 if custom.
|
||||
|
||||
@@ -94,6 +94,7 @@ extern int Game_mode;
|
||||
#define SINGLEPLAYER !(Game_mode & GM_MULTIPLAYER)
|
||||
|
||||
extern int Viewer_mode;
|
||||
extern int Rand_count;
|
||||
|
||||
extern int Game_restoring; // If set, this means we are restoring data from disk
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
#define USED_VARIABLE __attribute__((used))
|
||||
|
||||
#if __GNUC__ >= 7
|
||||
#if SCP_COMPILER_VERSION_MAJOR >= 7
|
||||
#define FALLTHROUGH [[fallthrough]]
|
||||
#else
|
||||
#define FALLTHROUGH
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
#define USED_VARIABLE __attribute__((used))
|
||||
|
||||
#if __GNUC__ >= 7
|
||||
#if SCP_COMPILER_VERSION_MAJOR >= 7
|
||||
#define FALLTHROUGH __attribute__((fallthrough))
|
||||
#else
|
||||
#define FALLTHROUGH
|
||||
|
||||
+6
-10
@@ -41,7 +41,6 @@
|
||||
#include "scripting/scripting.h"
|
||||
#include "tracing/tracing.h"
|
||||
#include "utils/boost/hash_combine.h"
|
||||
#include "gamesequence/gamesequence.h"
|
||||
|
||||
#ifdef WITH_OPENGL
|
||||
#include "graphics/opengl/gropengl.h"
|
||||
@@ -1605,6 +1604,8 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, int d_mode,
|
||||
return false;
|
||||
}
|
||||
|
||||
gr_light_init();
|
||||
|
||||
gr_set_palette_internal(Gr_current_palette_name, NULL, 0);
|
||||
|
||||
bm_init();
|
||||
@@ -1616,14 +1617,10 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, int d_mode,
|
||||
mprintf(("Initializing path renderer...\n"));
|
||||
graphics::paths::PathRenderer::init();
|
||||
|
||||
if ( !Is_standalone ) {
|
||||
gr_light_init();
|
||||
// Initialize uniform buffer managers
|
||||
uniform_buffer_managers_init();
|
||||
|
||||
// Initialize uniform buffer managers
|
||||
uniform_buffer_managers_init();
|
||||
|
||||
gpu_heap_init();
|
||||
}
|
||||
gpu_heap_init();
|
||||
|
||||
mprintf(("Checking graphics capabilities:\n"));
|
||||
mprintf((" Persistent buffer mapping: %s\n",
|
||||
@@ -2550,8 +2547,7 @@ void gr_flip(bool execute_scripting)
|
||||
executor::OnFrameExecutor->process();
|
||||
|
||||
// m!m avoid running CHA_ONFRAME when the "Quit mission" popup is shown. See mantis 2446 for reference
|
||||
// Cyborg - A similar bug will occur when a mission is restarted so check for that, too.
|
||||
if (execute_scripting && !popup_active() && !((gameseq_get_state() == GS_STATE_GAME_PLAY) && !(Game_mode & GM_IN_MISSION))) {
|
||||
if (execute_scripting && !popup_active()) {
|
||||
TRACE_SCOPE(tracing::LuaOnFrame);
|
||||
|
||||
// WMC - Do conditional hooks. Yippee!
|
||||
|
||||
+1
-5
@@ -230,7 +230,6 @@ enum shader_type {
|
||||
#define SDR_FLAG_MODEL_AMBIENT_MAP (1<<19)
|
||||
#define SDR_FLAG_MODEL_NORMAL_ALPHA (1<<20)
|
||||
#define SDR_FLAG_MODEL_THICK_OUTLINES (1<<21) // Renders the model geometry as an outline with configurable line width
|
||||
#define SDR_FLAG_MODEL_ALPHA_MULT (1<<22)
|
||||
|
||||
#define SDR_FLAG_PARTICLE_POINT_GEN (1<<0)
|
||||
|
||||
@@ -809,7 +808,7 @@ typedef struct screen {
|
||||
std::function<void(int bitmap_handle, int bpp, const ubyte* data, int width, int height)> gf_update_texture;
|
||||
std::function<void(void* data_out, int bitmap_num)> gf_get_bitmap_from_texture;
|
||||
|
||||
std::function<void(matrix4* shadow_view_matrix, const matrix* light_matrix, vec3d* eye_pos)> gf_shadow_map_start;
|
||||
std::function<void(matrix4* shadow_view_matrix, const matrix* light_matrix)> gf_shadow_map_start;
|
||||
std::function<void()> gf_shadow_map_end;
|
||||
|
||||
std::function<void()> gf_start_decal_pass;
|
||||
@@ -983,9 +982,6 @@ extern void gr_get_string_size( int *w, int *h, const char * text, int len = 999
|
||||
// Returns the height of the current font
|
||||
extern int gr_get_font_height();
|
||||
|
||||
// Returns a scaled amount of lines per the current font --wookieejedi
|
||||
extern int gr_get_dynamic_font_lines(int number_default_lines);
|
||||
|
||||
extern io::mouse::Cursor* Web_cursor;
|
||||
|
||||
// Called by OS when application gets/looses focus
|
||||
|
||||
@@ -149,10 +149,8 @@ void decal_draw_list::render() {
|
||||
source.Ibuffer_handle = box_index_buffer;
|
||||
|
||||
// Bind the global data only once
|
||||
gr_bind_uniform_buffer(uniform_block_type::DecalGlobals,
|
||||
_buffer.getBufferOffset(0),
|
||||
sizeof(graphics::decal_globals),
|
||||
_buffer.bufferHandle());
|
||||
gr_bind_uniform_buffer(uniform_block_type::DecalGlobals, 0, sizeof(graphics::decal_globals),
|
||||
_buffer.bufferHandle());
|
||||
gr_screen.gf_start_decal_pass();
|
||||
|
||||
for (auto& draw : _draws) {
|
||||
|
||||
@@ -630,7 +630,7 @@ void generic_anim_render_variable_frame_delay(generic_anim* ga, float frametime,
|
||||
else {
|
||||
// playing forwards
|
||||
ga->anim_time += frametime;
|
||||
if(ga->anim_time >= ga->total_time && ga->png.anim->current_frame >= ga->png.anim->nframes) {
|
||||
if(ga->anim_time >= ga->total_time && ga->png.anim->current_frame >= ga->png.anim->nframes-1) {
|
||||
if(ga->direction & GENERIC_ANIM_DIRECTION_NOLOOP) {
|
||||
ga->anim_time = ga->total_time; // stop on last frame when playing
|
||||
}
|
||||
@@ -660,7 +660,7 @@ void generic_anim_render_variable_frame_delay(generic_anim* ga, float frametime,
|
||||
}
|
||||
else {
|
||||
if (ga->anim_time >= ga->png.previous_frame_time + ga->png.anim->frame.delay &&
|
||||
ga->png.anim->current_frame < ga->png.anim->nframes) {
|
||||
ga->png.anim->current_frame < ga->png.anim->nframes-1) {
|
||||
ga->png.previous_frame_time += ga->png.anim->frame.delay;
|
||||
ga->current_frame++;
|
||||
}
|
||||
@@ -725,17 +725,3 @@ void generic_anim_render(generic_anim *ga, float frametime, int x, int y, bool m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief reset an animation back to the start
|
||||
*
|
||||
* @param [in] *ga animation data
|
||||
*/
|
||||
void generic_anim_reset(generic_anim *ga) {
|
||||
ga->anim_time = 0.0f;
|
||||
ga->current_frame = 0;
|
||||
if (ga->type == BM_TYPE_PNG) {
|
||||
ga->png.previous_frame_time = 0.0f;
|
||||
ga->png.anim->goto_start();
|
||||
}
|
||||
}
|
||||
@@ -89,5 +89,5 @@ int generic_anim_stream(generic_anim *ga, const bool cache = true);
|
||||
int generic_bitmap_load(generic_bitmap *gb);
|
||||
void generic_anim_unload(generic_anim *ga);
|
||||
void generic_anim_render(generic_anim *ga, float frametime, int x, int y, bool menu = false, const generic_extras *ge = nullptr);
|
||||
void generic_anim_reset(generic_anim *ga);
|
||||
|
||||
#endif
|
||||
@@ -285,7 +285,7 @@ int gr_stub_maybe_create_shader(shader_type /*shader_t*/, unsigned int /*flags
|
||||
return -1;
|
||||
}
|
||||
|
||||
void gr_stub_shadow_map_start(matrix4 * /*shadow_view_matrix*/, const matrix* /*light_matrix*/, vec3d* /*eye_pos*/)
|
||||
void gr_stub_shadow_map_start(matrix4 * /*shadow_view_matrix*/, const matrix* /*light_matrix*/)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -294,7 +294,10 @@ void gr_light_init() {
|
||||
gr_lights.reserve(1024);
|
||||
}
|
||||
|
||||
void gr_set_lighting() {
|
||||
void gr_set_lighting(bool /*set*/, bool state) {
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Valathil: Sort lights by priority
|
||||
extern bool Deferred_lighting;
|
||||
|
||||
@@ -17,7 +17,7 @@ extern float gr_user_ambient;
|
||||
//Functions
|
||||
void gr_set_light(light *fs_light);
|
||||
void gr_reset_lighting();
|
||||
void gr_set_lighting();
|
||||
void gr_set_lighting(bool set, bool state);
|
||||
void gr_set_center_alpha(int type);
|
||||
void gr_set_ambient_light(int red, int green, int blue);
|
||||
|
||||
|
||||
@@ -688,24 +688,6 @@ bool model_material::uses_thick_outlines() const {
|
||||
return Outline_thickness > 0.0f;
|
||||
}
|
||||
|
||||
float model_material::get_alpha_mult() const {
|
||||
return Alpha_mult;
|
||||
}
|
||||
|
||||
bool model_material::is_alpha_mult_active() const {
|
||||
return Use_alpha_mult;
|
||||
}
|
||||
|
||||
void model_material::set_alpha_mult(float alpha) {
|
||||
Use_alpha_mult = true;
|
||||
Alpha_mult = alpha;
|
||||
}
|
||||
|
||||
void model_material::reset_alpha_mult() {
|
||||
Use_alpha_mult = false;
|
||||
Alpha_mult = 1.0f;
|
||||
}
|
||||
|
||||
uint model_material::get_shader_flags() const
|
||||
{
|
||||
uint Shader_flags = 0;
|
||||
@@ -797,10 +779,6 @@ uint model_material::get_shader_flags() const
|
||||
Shader_flags |= SDR_FLAG_MODEL_THICK_OUTLINES;
|
||||
}
|
||||
|
||||
if (is_alpha_mult_active()) {
|
||||
Shader_flags |= SDR_FLAG_MODEL_ALPHA_MULT;
|
||||
}
|
||||
|
||||
return Shader_flags;
|
||||
}
|
||||
|
||||
|
||||
@@ -204,9 +204,6 @@ class model_material : public material
|
||||
|
||||
float Outline_thickness = -1.0f;
|
||||
|
||||
bool Use_alpha_mult = false;
|
||||
float Alpha_mult = 1.0f;
|
||||
|
||||
public:
|
||||
model_material();
|
||||
|
||||
@@ -262,12 +259,6 @@ public:
|
||||
void set_fog();
|
||||
bool is_fogged() const;
|
||||
const fog& get_fog() const;
|
||||
|
||||
float get_alpha_mult() const;
|
||||
bool is_alpha_mult_active() const;
|
||||
void set_alpha_mult(float alpha);
|
||||
void reset_alpha_mult();
|
||||
|
||||
};
|
||||
|
||||
class particle_material : public material
|
||||
|
||||
@@ -55,8 +55,8 @@ class ShaderProgram {
|
||||
|
||||
ShaderUniforms Uniforms;
|
||||
|
||||
ShaderProgram(const ShaderProgram&) = delete;
|
||||
ShaderProgram& operator=(const ShaderProgram&) = delete;
|
||||
ShaderProgram(const ShaderProgram&) SCP_DELETED_FUNCTION;
|
||||
ShaderProgram& operator=(const ShaderProgram&) SCP_DELETED_FUNCTION;
|
||||
|
||||
ShaderProgram(ShaderProgram&& other) noexcept;
|
||||
ShaderProgram& operator=(ShaderProgram&& other) noexcept;
|
||||
|
||||
@@ -301,8 +301,8 @@ void gr_opengl_deferred_lighting_finish()
|
||||
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_emissive_texture);
|
||||
GL_state.Texture.Enable(1, GL_TEXTURE_2D, Scene_depth_texture);
|
||||
|
||||
float fog_near, fog_far, fog_density;
|
||||
neb2_get_adjusted_fog_values(&fog_near, &fog_far, &fog_density);
|
||||
float fog_near, fog_far;
|
||||
neb2_get_adjusted_fog_values(&fog_near, &fog_far, nullptr);
|
||||
unsigned char r, g, b;
|
||||
neb2_get_fog_color(&r, &g, &b);
|
||||
|
||||
@@ -311,7 +311,7 @@ void gr_opengl_deferred_lighting_finish()
|
||||
|
||||
opengl_set_generic_uniform_data<graphics::generic_data::fog_data>([&](graphics::generic_data::fog_data* data) {
|
||||
data->fog_start = fog_near;
|
||||
data->fog_density = fog_density;
|
||||
data->fog_scale = 1.0f / (fog_far - fog_near);
|
||||
data->fog_color.xyz.x = r / 255.f;
|
||||
data->fog_color.xyz.y = g / 255.f;
|
||||
data->fog_color.xyz.z = b / 255.f;
|
||||
@@ -439,15 +439,13 @@ void gr_opengl_deferred_light_cylinder_init(int segments) // Generate a VBO of a
|
||||
if ( opengl_check_for_errors() ) {
|
||||
glDeleteBuffers(1, &deferred_light_cylinder_vbo);
|
||||
deferred_light_cylinder_vbo = 0;
|
||||
|
||||
vm_free(Indices);
|
||||
Indices = nullptr;
|
||||
vm_free(Vertices);
|
||||
Vertices = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
vm_free(Vertices);
|
||||
Vertices = NULL;
|
||||
}
|
||||
|
||||
glGenBuffers(1, &deferred_light_cylinder_ibo);
|
||||
@@ -461,21 +459,15 @@ void gr_opengl_deferred_light_cylinder_init(int segments) // Generate a VBO of a
|
||||
if ( opengl_check_for_errors() ) {
|
||||
glDeleteBuffers(1, &deferred_light_cylinder_ibo);
|
||||
deferred_light_cylinder_ibo = 0;
|
||||
|
||||
vm_free(Indices);
|
||||
Indices = nullptr;
|
||||
vm_free(Vertices);
|
||||
Vertices = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
vm_free(Indices);
|
||||
Indices = NULL;
|
||||
}
|
||||
|
||||
vm_free(Indices);
|
||||
Indices = nullptr;
|
||||
vm_free(Vertices);
|
||||
Vertices = nullptr;
|
||||
}
|
||||
|
||||
static GLuint deferred_light_sphere_vbo = 0;
|
||||
@@ -539,15 +531,13 @@ void gr_opengl_deferred_light_sphere_init(int rings, int segments) // Generate a
|
||||
if ( opengl_check_for_errors() ) {
|
||||
glDeleteBuffers(1, &deferred_light_sphere_vbo);
|
||||
deferred_light_sphere_vbo = 0;
|
||||
|
||||
vm_free(Vertices);
|
||||
Vertices = nullptr;
|
||||
vm_free(Indices);
|
||||
Indices = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
vm_free(Vertices);
|
||||
Vertices = NULL;
|
||||
}
|
||||
|
||||
glGenBuffers(1, &deferred_light_sphere_ibo);
|
||||
@@ -561,21 +551,14 @@ void gr_opengl_deferred_light_sphere_init(int rings, int segments) // Generate a
|
||||
if ( opengl_check_for_errors() ) {
|
||||
glDeleteBuffers(1, &deferred_light_sphere_ibo);
|
||||
deferred_light_sphere_ibo = 0;
|
||||
|
||||
vm_free(Vertices);
|
||||
Vertices = nullptr;
|
||||
vm_free(Indices);
|
||||
Indices = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
vm_free(Indices);
|
||||
Indices = NULL;
|
||||
}
|
||||
|
||||
vm_free(Vertices);
|
||||
Vertices = nullptr;
|
||||
vm_free(Indices);
|
||||
Indices = nullptr;
|
||||
}
|
||||
|
||||
void opengl_draw_sphere()
|
||||
|
||||
@@ -706,8 +706,8 @@ void gr_opengl_update_distortion()
|
||||
|
||||
for(int i = 0; i < 33; i++)
|
||||
{
|
||||
distortion_verts[i].r = (ubyte)Random::next(256);
|
||||
distortion_verts[i].g = (ubyte)Random::next(256);
|
||||
distortion_verts[i].r = (ubyte)rand() % 256;
|
||||
distortion_verts[i].g = (ubyte)rand() % 256;
|
||||
distortion_verts[i].b = 255;
|
||||
distortion_verts[i].a = 255;
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ void gr_opengl_update_distortion();
|
||||
|
||||
void gr_opengl_sphere(material *material_def, float rad);
|
||||
|
||||
void gr_opengl_shadow_map_start(matrix4 *shadow_view_matrix, const matrix *light_orient, vec3d* eye_pos);
|
||||
void gr_opengl_shadow_map_start(matrix4 *shadow_view_matrix, const matrix *light_orient);
|
||||
void gr_opengl_shadow_map_end();
|
||||
|
||||
void gr_opengl_render_shield_impact(shield_material* material_info,
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "graphics/util/uniform_structs.h"
|
||||
#include "io/timer.h"
|
||||
#include "lighting/lighting.h"
|
||||
#include "lighting/lighting_profiles.h"
|
||||
#include "mod_table/mod_table.h"
|
||||
#include "nebula/neb.h"
|
||||
#include "parse/parselo.h"
|
||||
@@ -25,6 +24,7 @@
|
||||
|
||||
extern bool PostProcessing_override;
|
||||
extern int opengl_check_framebuffer();
|
||||
|
||||
// Needed to track where the FXAA shaders are
|
||||
// In case we don't find the shaders at all, this override is needed
|
||||
bool fxaa_unavailable = false;
|
||||
@@ -66,22 +66,9 @@ void opengl_post_pass_tonemap()
|
||||
opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_TONEMAPPING, 0));
|
||||
|
||||
Current_shader->program->Uniforms.setTextureUniform("tex", 0);
|
||||
|
||||
|
||||
opengl_set_generic_uniform_data<graphics::generic_data::tonemapping_data>(
|
||||
[](graphics::generic_data::tonemapping_data* data) {
|
||||
auto ppc = lighting_profile::current_piecewise_intermediates();
|
||||
auto tn = lighting_profile::current_tonemapper();
|
||||
data->tonemapper = tn;
|
||||
data->sh_B = ppc.sh_B;
|
||||
data->sh_lnA = ppc.sh_lnA;
|
||||
data->sh_offsetX =ppc.sh_offsetX;
|
||||
data->sh_offsetY = ppc.sh_offsetY;
|
||||
data->toe_B = ppc.toe_B;
|
||||
data->toe_lnA = ppc.toe_lnA;
|
||||
data->x0 = ppc.x0;
|
||||
data->x1 = ppc.x1;
|
||||
data->y0 = ppc.y0;
|
||||
data->exposure = lighting_profile::current_exposure(); });
|
||||
[](graphics::generic_data::tonemapping_data* data) { data->exposure = 4.0f; });
|
||||
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Scene_ldr_texture, 0);
|
||||
|
||||
@@ -183,7 +170,7 @@ void opengl_post_pass_bloom()
|
||||
opengl_set_generic_uniform_data<graphics::generic_data::bloom_composition_data>(
|
||||
[](graphics::generic_data::bloom_composition_data* data) {
|
||||
data->levels = MAX_MIP_BLUR_LEVELS;
|
||||
data->bloom_intensity = gr_bloom_intensity() / 100.0f;
|
||||
data->bloom_intensity = Cmdline_bloom_intensity / 100.0f;
|
||||
});
|
||||
|
||||
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Bloom_textures[0]);
|
||||
@@ -455,9 +442,9 @@ void opengl_post_lightshafts()
|
||||
float dot;
|
||||
if ( (dot = vm_vec_dot(&light_dir, &Eye_matrix.vec.fvec)) > 0.7f ) {
|
||||
|
||||
x = asinf_safe(vm_vec_dot(&light_dir, &Eye_matrix.vec.rvec)) / PI * 1.5f +
|
||||
x = asinf(vm_vec_dot(&light_dir, &Eye_matrix.vec.rvec)) / PI * 1.5f +
|
||||
0.5f; // cant get the coordinates right but this works for the limited glare fov
|
||||
y = asinf_safe(vm_vec_dot(&light_dir, &Eye_matrix.vec.uvec)) / PI * 1.5f * gr_screen.clip_aspect + 0.5f;
|
||||
y = asinf(vm_vec_dot(&light_dir, &Eye_matrix.vec.uvec)) / PI * 1.5f * gr_screen.clip_aspect + 0.5f;
|
||||
|
||||
opengl_set_generic_uniform_data<graphics::generic_data::lightshaft_data>(
|
||||
[&](graphics::generic_data::lightshaft_data* data) {
|
||||
@@ -884,7 +871,7 @@ bool opengl_post_init_shaders()
|
||||
gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BLUR, SDR_FLAG_BLUR_VERTICAL) < 0 ||
|
||||
gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BLOOM_COMP, 0) < 0) {
|
||||
// disable bloom if we don't have those shaders available
|
||||
graphics::Post_processing_manager->setBloomShadersOk(false);
|
||||
Cmdline_bloom_intensity = 0;
|
||||
}
|
||||
|
||||
if (gr_is_fxaa_mode(Gr_aa_mode))
|
||||
@@ -1198,4 +1185,4 @@ void opengl_post_process_shutdown()
|
||||
Post_active_shader_index = 0;
|
||||
|
||||
Post_initialized = 0;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 575 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user