Compare commits

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

No files matched your search

-2
View File
@@ -6,5 +6,3 @@ code/executor/ @asarium
code/scpui/ @asarium
code/scripting/ @asarium
code/controlconfig/ @z64555
+1 -28
View File
@@ -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-a8ca321
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -29,8 +29,6 @@ jobs:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
setup-python: 'false'
aqtversion: ==1.1.3
- uses: actions/checkout@v1
name: Checkout
@@ -210,28 +208,3 @@ jobs:
DATACORDER_USER: ${{ secrets.DATACORDER_USER }}
DATACORDER_SSHPASS: ${{ secrets.DATACORDER_PASSWORD }}
run: $GITHUB_WORKSPACE/ci/linux/upload_dist_package.sh nightly
post_builds:
# Post nightlies if at least one job was successful
if: ${{ needs.linux_zip == "success" || needs.windows_zip == "success" }}
name: Post builds on Nebula and the forums
needs: [linux_zip, windows_zip]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
name: Checkout
with:
fetch-depth: '0'
- name: Install Python dependencies
run: pip install -r ci/post/requirements.txt
- name: Post builds
env:
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 }}
LINUX_RESULT: ${{ needs.linux_zip.result }}
WINDOWS_RESULT: ${{ needs.windows_zip.result }}
run: python ci/post/main.py nightly
+1 -26
View File
@@ -39,7 +39,7 @@ jobs:
name: Linux
needs: create_release
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
container: ghcr.io/scp-fs2open/linux_build:sha-a8ca321
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -53,8 +53,6 @@ jobs:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
setup-python: 'false'
aqtversion: ==1.1.3
- uses: actions/checkout@v1
name: Checkout
@@ -263,26 +261,3 @@ jobs:
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 }}
post_builds:
name: Post builds on Nebula and the forums
needs: [linux_zip, windows_zip]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
name: Checkout
with:
fetch-depth: '0'
- name: Install Python dependencies
run: pip install -r ci/post/requirements.txt
- name: Post builds
env:
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 }}
LINUX_RESULT: ${{ needs.linux_zip.result }}
WINDOWS_RESULT: ${{ needs.windows_zip.result }}
run: python ci/post/main.py release
+1 -3
View File
@@ -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-a8ca321
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -29,8 +29,6 @@ jobs:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
setup-python: 'false'
aqtversion: ==1.1.3
- uses: actions/checkout@v1
name: Checkout
-62
View File
@@ -1,62 +0,0 @@
name: Build caches for master
on:
push:
branches:
- master
env:
QT_VERSION: 5.12.9
jobs:
build_linux:
strategy:
matrix:
configuration: [Debug, Release]
compiler: [gcc-5, gcc-10]
cmake_options: [""]
include:
# Also include configurations that check if the code compiles without the graphics backends
- configuration: Debug
compiler: gcc-10
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
- configuration: Release
compiler: gcc-10
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-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
-21
View File
@@ -1,21 +0,0 @@
name: Push a new nightly tag
on:
schedule:
- cron: '0 6 * * *'
jobs:
tag:
if: github.repository == 'scp-fs2open/fs2open.github.com'
runs-on: ubuntu-latest
steps:
- name: Prepare environment
run: |
git config --global user.email '[email protected]'
git config --global user.name SirKnightly
- name: Checkout repo
uses: actions/checkout@v2
- name: Create tag
run: git tag "nightly_$(date '+%Y%m%d')_$(git rev-parse --short HEAD)"
- name: Push tag
run: git push --tags
+2 -7
View File
@@ -8,6 +8,7 @@ env:
jobs:
build_linux:
strategy:
fail-fast: false
matrix:
configuration: [Debug, Release]
compiler: [gcc-5, gcc-10, clang-9]
@@ -22,7 +23,7 @@ jobs:
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-11e553c
container: ghcr.io/scp-fs2open/linux_build:sha-e977d36
steps:
- name: Cache Qt
id: cache-qt-lin
@@ -37,18 +38,12 @@ jobs:
dir: ${{ github.workspace }}/..
cached: ${{ steps.cache-qt-lin.outputs.cache-hit }}
setup-python: 'false'
aqtversion: ==1.1.3
- uses: actions/checkout@v1
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
@@ -3,8 +3,7 @@ name: Publish Standalone docker image
on:
push:
tags:
- 'nightly_*'
- 'release_*'
- '*'
jobs:
build_images:
@@ -13,9 +12,13 @@ jobs:
configuration: [FastDebug, Release]
include:
- configuration: FastDebug
tag_suffix: -debug
latest_tag: latest_debug
nightly_tag: nightly_debug
release_tag: release_debug
- configuration: Release
tag_suffix: ""
latest_tag: latest
nightly_tag: nightly
release_tag: release
fail-fast: false
runs-on: ubuntu-latest
steps:
@@ -23,30 +26,23 @@ jobs:
name: Checkout
with:
submodules: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Docker login
run: echo ${{ secrets.DOCKER_ACCESSTOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
- name: Docker build
run: docker build --build-arg build_type=${{ matrix.configuration }} -f docker/standalone/Dockerfile -t ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }} .
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: ghcr.io/scp-fs2open/standalone_server
tag-latest: false
- name: Push latest tag
run: |
docker push ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }}
- name: Login to GitHub Docker Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
file: ./docker/standalone/Dockerfile
push: true
tags: ${{ steps.docker_meta.outputs.tags }}${{ matrix.tag_suffix }}
labels: ${{ steps.docker_meta.outputs.labels }}
build-args: |
build_type=${{ matrix.configuration }}
- name: Push nightly tag
if: contains(github.ref, 'refs/tags/nightly_')
run: |
docker tag ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }} ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.nightly_tag }}
docker push ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.nightly_tag }}
- name: Push release tag
if: contains(github.ref, 'refs/tags/release_')
run: |
docker tag ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.latest_tag }} ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.release_tag }}
docker push ${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.release_tag }}
-128
View File
@@ -1,128 +0,0 @@
import re
from ftplib import FTP, error_perm
from typing import List, Tuple, Dict
import requests
from util import retry_multi, GLOBAL_TIMEOUT
class ReleaseFile:
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
class SourceFile:
def __init__(self, name, url, group):
self.group = group
self.url = url
self.name = name
def get_release_files(tag_name, config) -> Tuple[List[ReleaseFile], Dict[str, SourceFile]]:
@retry_multi(5)
def execute_request(path):
headers = {
"Accept": "application/vnd.github.v3+json"
}
url = "https://api.github.com" + path
response = requests.get(url, headers=headers, timeout=GLOBAL_TIMEOUT)
response.raise_for_status()
return response.json()
build_group_regex = re.compile("fs2_open_.*-builds-([^.-]*)(-([^.]*))?.*")
source_file_regex = re.compile("fs2_open_.*-source-([^.]*)?.*")
response = execute_request(
"/repos/{}/{}/releases/tags/{}".format(config["github"]["user"], config["github"]["repo"], tag_name))
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)
return binary_files, source_files
def get_ftp_files(build_type, tag_name, config):
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:
version_str = tag_regex.match(tag_name).group(1)
path_template = config["ftp"]["path"]
path = path_template.format(type=build_type, version=version_str)
file_entries = list(ftp.mlsd(path, ["type"]))
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:
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")
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)
out_data.append(ReleaseFile(file, primary_url, group_match, None, mirrors))
return out_data
-82
View File
@@ -1,82 +0,0 @@
from itertools import groupby
from typing import List
import requests
import semantic_version
from mako.template import Template
from file_list import ReleaseFile
class FileGroup:
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)))
class ForumAPI:
def __init__(self, config):
self.config = config
def create_post(self, title, content, board):
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!")
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"])
-88
View File
@@ -1,88 +0,0 @@
import os
import hashlib
import tarfile
import traceback
import zipfile
from tempfile import NamedTemporaryFile
from zipfile import ZipFile
import requests
import sys
from mako.template import Template
from file_list import ReleaseFile
from util import GLOBAL_TIMEOUT
def _download_file(url, dest_file, session):
print("Downloading " + url)
# NOTE the stream=True parameter
r = session.get(url, stream=True, timeout=GLOBAL_TIMEOUT)
for chunk in r.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
dest_file.write(chunk)
dest_file.flush()
def _gen_hash(fileobj, hash_alg):
h = hashlib.new(hash_alg)
while True:
data = fileobj.read(4096)
if not data:
break
h.update(data)
return h.hexdigest()
def get_hashed_file_list(file: ReleaseFile, session: requests.Session = None, hash_alg: str = "sha256"):
if session is None:
session = requests.Session()
with NamedTemporaryFile('w+b', suffix=file.filename) as local_file:
_download_file(file.url, local_file, session)
filename = local_file.name
hash_list = []
local_file.seek(0)
file.hash = _gen_hash(local_file, hash_alg)
file.size = os.stat(filename).st_size
try:
if tarfile.is_tarfile(filename):
with tarfile.open(filename) as archive:
for entry in archive:
if not entry.isfile():
continue
print("Computing hash for " + entry.name)
fileobj = archive.extractfile(entry)
hash_list.append((entry.path, _gen_hash(fileobj, hash_alg)))
elif zipfile.is_zipfile(filename):
with ZipFile(filename) as archive:
for entry in archive.infolist():
# Python 3.6 has is_dir but that version is relatively new so we'll use the "safe" version here
if entry.filename.endswith('/'):
continue
print("Computing hash for " + entry.filename)
with archive.open(entry) as fileobj:
hash_list.append((entry.filename, _gen_hash(fileobj, hash_alg)))
else:
raise NotImplementedError("Unsupported archive type!")
except:
traceback.print_exception(*sys.exc_info())
return
file.content_hashes = hash_list
def render_installer_config(version, groups, config):
template = Template(filename=config["templates"]["installer"], module_directory='/tmp/mako_modules')
return template.render(**{
"version": version,
"groups": groups,
}).strip("\n")
-152
View File
@@ -1,152 +0,0 @@
import sys
import os
import re
from subprocess import check_output
from datetime import datetime
from itertools import groupby
import semantic_version
import file_list
import installer
import nebula
import forum
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):
match = regex.search(text)
return int(match.group(1))
def get_source_version(date_version):
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)
return semantic_version.Version("{}.{}.{}-{}".format(major, minor, build, date_version))
def main():
os.chdir(os.path.dirname(__file__))
config = {
"github": {
"user": "scp-fs2open",
"repo": "fs2open.github.com",
},
"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,
},
}
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.path.basename(os.environ["GITHUB_REF"])
date = datetime.now()
version = get_source_version(date.strftime(DATEFORMAT_VERSION))
success = os.environ["LINUX_RESULT"] == "success" and os.environ["WINDOWS_RESULT"] == "success"
tags = check_output(("git", "for-each-ref", "--sort=-taggerdate", "--format", "%(tag)", "refs/tags")).splitlines()
previous_tag = None
found = False
for tag in tags:
if found:
if tag.startswith(sys.argv[1] + "_"):
previous_tag = tag
break
elif tag == tag_name:
found = True
if not found:
print("ERROR: Couldn't find tag %s in repo!" % tag_name)
sys.exit(1)
if not previous_tag:
print("ERROR: Could not find a previous tag for %s!" % tag_name)
sys.exit(1)
if sys.argv[1] == "release":
if not tag_name.startswith("release_"):
print("ERROR: Invalid tag name detected. Expected release_ prefix for release builds but found %s (full ref = %s)" % tag_name, os.environ["GITHUB_REF"])
sys.exit(1)
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
groups = dict(((x[0], file_list.FileGroup(x[0], list(x[1]))) for x in groupby(files, lambda g: g.group)))
print(installer.render_installer_config(version, groups, config))
nebula.submit_release(
nebula.render_nebula_release(version, "rc" if version.prerelease else "stable", "files", config),
config)
fapi = forum.ForumAPI(config)
fapi.post_release(date.strftime(DATEFORMAT_FORUM), version, groups, sources)
else:
if not tag_name.startswith("nightly_"):
print("ERROR: Invalid tag name detected. Expected nightly_ prefix for nightly builds but found %s (full ref = %s)" % tag_name, os.environ["GITHUB_REF"])
sys.exit(1)
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)
nebula.submit_release(nebula.render_nebula_release(version, "nightly", files, config), config)
log = check_output(("git", "log", "%s^..%s^" % (previous_tag, tag_name), "--no-merges", "--stat", "--pretty=format:\"%s\"" % LOG_FORMAT.strip()))
fapi = forum.ForumAPI(config)
fapi.post_nightly(date.strftime(DATEFORMAT_FORUM), os.environ["GITHUB_SHA"], files, log, success)
main()
-240
View File
@@ -1,240 +0,0 @@
import os.path
import sys
import traceback
import requests
from util import retry_multi, GLOBAL_TIMEOUT
metadata = {
'type': 'engine',
'title': 'FSO',
'notes': '',
'banner': 'https://fsnebula.org/storage/0d/e7/bf64bcdea9a9c115969cfb784e1ca457d24a7c2da4fc6f213521c3bb6abb.png',
'screenshots': [],
'videos': [],
'first_release': '2017-09-24',
'cmdline': '',
'mod_flag': ['FSO'],
'tile': 'https://fsnebula.org/storage/ec/cc/0bf23e028c26d5175ff52d003bff85b0a17b0ddfc1130d65bdf6d36f6324.png',
'logo': None,
'release_thread': None,
'description': '',
'id': 'FSO',
'packages': [],
}
platforms = {
'Linux': 'linux',
'MacOSX': 'macosx',
'Win32-SSE2': 'windows',
'Win64-SSE2': 'windows',
'Win32-AVX': 'windows',
'Win64-AVX': 'windows'
}
envs = {
'Linux': 'linux && x86_64', # Linux only has 64bit builds
'MacOSX': 'macosx',
'Win32-SSE2': 'windows',
'Win64-SSE2': 'windows && x86_64',
'Win32-AVX': 'windows && avx',
'Win64-AVX': 'windows && avx && x86_64'
}
subdirs = {
'Win32-SSE2': 'x86',
'Win64-SSE2': 'x64',
'Win32-AVX': 'x86_avx',
'Win64-AVX': 'x64_avx'
}
def render_nebula_release(version, stability, files, config):
meta = metadata.copy()
meta['version'] = str(version)
meta['stability'] = stability # This can be one of ('stable', 'rc', 'nightly')
for file in files:
if file.content_hashes is None:
# The extraction probably failed which is why we don't have any hashes
continue
group = file.group
# release.py sets subgroup but nightly.py doesn't which means we have to normalize group here.
if file.subgroup:
group += '-' + file.subgroup
pkg = {
'name': group,
'notes': '',
'is_vp': False,
'files': [{
'dest': platforms[group] + '/' + subdirs.get(group, ''),
'filesize': file.size,
'checksum': ['sha256', file.hash],
'urls': [file.url] + file.mirrors,
'filename': file.name
}],
'environment': envs.get(group),
'filelist': [],
'status': 'required',
'folder': None,
'dependencies': [],
'executables': []
}
for fn, checksum in file.content_hashes:
if group in subdirs:
dest_fn = subdirs[group] + '/' + fn
else:
dest_fn = fn
# If the group is a known platform then we put the binaries into a separate subfolder for each platform
if group in platforms:
dest_fn = platforms[group] + '/' + dest_fn
pkg['filelist'].append({
'orig_name': fn,
'checksum': ('sha256', checksum),
'archive': file.name,
'filename': dest_fn
})
if group == 'Linux' and fn.endswith('.AppImage'):
if 'qtfred' in fn:
if '-FASTDBG' in fn:
label = 'QtFRED Debug'
else:
label = 'QtFRED'
else:
if '-FASTDBG' in fn:
label = 'Fast Debug'
else:
label = None
props = {
"x64": True, # All Linux builds are 64-bit
"sse2": True, # Linux builds are forced to compile with SSE2 but not AVX
"avx": False,
"avx2": False,
}
pkg['executables'].append({
'file': dest_fn,
'label': label,
'properties': props,
})
elif group == 'MacOSX' and fn.startswith(os.path.basename(fn) + '.app/'):
if 'qtfred' in fn:
if '-FASTDBG' in fn:
label = 'QtFRED Debug'
else:
label = 'QtFRED'
else:
if '-FASTDBG' in fn:
label = 'Fast Debug'
else:
label = None
props = {
"x64": True, # All Mac builds are 64-bit
"sse2": True, # There are no forced options on mac but 64-bit always implies SSE2 so we set that here
"avx": False,
"avx2": False,
}
pkg['executables'].append({
'file': dest_fn,
'label': label,
'properties': props,
})
elif group.startswith('Win') and fn.endswith('.exe'):
if 'fred2' in fn:
if '-FASTDBG' in fn:
label = 'FRED2 Debug'
else:
label = 'FRED2'
elif 'qtfred' in fn:
if '-FASTDBG' in fn:
label = 'QtFRED Debug'
else:
label = 'QtFRED'
else:
if '-FASTDBG' in fn:
label = 'Fast Debug'
else:
label = None
props = {
"x64": "x64" in fn,
"sse2": "SSE2" in fn or "AVX" in fn, # AVX implies SSE2
"avx": "AVX" in fn, # This conveniently also covers the AVX2 case since AVX2 implies AVX
"avx2": "AVX2" in fn,
}
pkg['executables'].append({
'file': dest_fn,
'label': label,
'properties': props,
})
meta["packages"].append(pkg)
return meta
@retry_multi(5)
def nebula_request(session, kind, path, **kwargs):
uri = "https://fsnebula.org/api/1/{}".format(path)
request_args = {
"timeout": GLOBAL_TIMEOUT
}
request_args.update(kwargs)
return session.request(kind, uri, **request_args)
def submit_release(meta, config):
try:
with requests.Session() as session:
print('Logging into Nebula...')
result = nebula_request(session, 'post', 'login', data={
'user': config['nebula']['user'],
'password': config['nebula']['password']
})
if result.status_code != 200:
print('Login failed!')
return False
data = result.json()
if not data['result']:
print('Login failed!')
return False
print('Submitting release...')
result = nebula_request(session, 'post', 'mod/release', headers={
'X-KN-TOKEN': data['token']
}, json=meta)
if result.status_code != 200:
print('Request failed!')
return False
data = result.json()
if not data['result']:
print('ERROR: ' + data['reason'])
return False
print('Success!')
return True
except Exception:
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback.print_exception(exc_type, exc_value, exc_traceback)
return False
-27
View File
@@ -1,27 +0,0 @@
Here is the nightly for ${date} - Revision ${revision}
% if not success:
[b][color=red]At least one of the nightly builds failed![/color][/b]
% endif
% for file in files:
Group: ${file.group}
[url=${file.url}]${file.name}[/url] \
%for mirror in file.mirrors:
%if loop.first:
(\
%endif
[url=${mirror}]Mirror[/url]\
%if loop.last:
)\
%else:
, \
%endif
%endfor
% endfor
[code]
${log}
[/code]
-80
View File
@@ -1,80 +0,0 @@
<%def name="build(file)">[url=${file.url}]${file.filename}[/url]</%def>
[b]Change log:[/b] (chronologically ordered)
[b]Bugfixes and behavior changes:[/b] Check these first if something breaks (most of these changes can be reverted / disabled).
[list]
[li]Wrap Docking getTime in a timestamp ([gh]#3701[/gh])[/li]
[li]expand some sexp documentation ([gh]#3671[/gh])[/li]
[li]Fix warnings concerning ballistic weapons ([gh]#3683[/gh])[/li]
[li]Initialize team visibility before using it ([gh]#3695[/gh])[/li]
[li]Actually fix the goal's target being sent ([gh]#3665[/gh])[/li]
[li]Safety for replace-texture ([gh]#3694[/gh])[/li]
[li]Fix issue 3685 ([gh]#3688[/gh])[/li]
[li]Exclude dead subsys from targeting ([gh]#3614[/gh])[/li]
[li]Fix scripted kills ([gh]#3679[/gh])[/li]
[li]Fix homing child weapons in niche circumstances ([gh]#3661[/gh])[/li]
[/list]
[b]Deprecations:[/b]
[list][li]None[/li][/list]
Deprecations are a mechanism in FSO where a certain feature or aspect of the engine has changed or is no longer supported. Since this would normally break existing mods we have the mod table feature "[tt]$Target Version:[/tt]" with which a mod can specify what version of FSO it was developed with. The features listed above will be removed or changed when the target version of a mod is at least the version released in this post.
[size=5pt]Previous [url=https://www.hard-light.net/forums/index.php?topic=97293.0]21.0 Release Thread[/url][/size]
Launchers, if you don't have one already:
[b]All platforms: [/b] For every day use, we recommend [url=https://www.hard-light.net/forums/index.php?topic=94068.0]Knossos[/url], an integrated solution for downloading and launching mods.
[hidden=Alternative Launchers]
Cross-platform: [url=http://www.hard-light.net/forums/index.php?topic=89162]wxLauncher 0.12.x Test Build[/url] (ongoing project for a unified launcher, you should upgrade to the latest RC/test build if you have not yet)
[b]Important:[/b] For best compatibility with FSO 3.8 and later you should use at least wxLauncher 0.12.
Windows: [url=http://scp.fsmods.net/files/Launcher55g.zip]Launcher 5.5g[/url] ([url=http://scp.indiegames.us/builds/Launcher55g.zip]Mirror[/url]) ([url=http://www.mediafire.com/?wdvzn7hhhzh418m]Mirror[/url]) Not compatible with Windows 8+, use wxLauncher above
OS X: Soulstorm's [url=http://www.hard-light.net/forums/index.php/topic,51391.0.html]OS X Launcher 3.0[/url]
Linux: [url=http://www.hard-light.net/forums/index.php/topic,53206.0.html]YAL[/url] or [url=http://www.hard-light.net/wiki/index.php/Fs2_open_on_Linux/Graphics_Settings]by hand[/url] or whatever you can figure out.[/hidden]
[img]http://scp.indiegames.us/img/windows-icon.png[/img] [color=green][size=12pt]Windows (32/64-bit)[/size][/color]
[size=8pt]Compiled using GitHub Actions on Windows Server 2019 (10.0.17763), Visual Studio Enterprise 2019[/size]
[b]64-bit:[/b] ${build(groups["Win64"].mainFile)}
[b]32-bit:[/b] ${build(groups["Win32"].mainFile)}
[size=8pt]This one is based on the SSE2 Optimizations from the MSVC Compiler.[/size]
[hidden=Alternative builds]
[b]64-bit AVX:[/b] ${build(groups["Win64"].subFiles["AVX"])}
[size=8pt]This one is based on the AVX Optimizations from the MSVC Compiler (fastest build if your CPU supports AVX instructions).[/size]
[b]32-bit AVX:[/b] ${build(groups["Win32"].subFiles["AVX"])}
[size=8pt]This one is based on the AVX Optimizations from the MSVC Compiler.[/size]
[b]What are those SSE, SSE2 and AVX builds I keep seeing everywhere?[/b]
[url=http://www.hard-light.net/forums/index.php?topic=65628.0]Your answer is in this topic.[/url]
Don't want to deal with that? Use [url=https://www.hard-light.net/forums/index.php?topic=94068.0]Knossos[/url] and it will download the best build specifically for your PC!
[/hidden]
[img]http://scp.indiegames.us/img/linux-icon.png[/img] [color=green][size=12pt]Linux 64-bit[/size][/color]
[size=8pt]Compiled with Ubuntu 16.04 LTS 64-bit, GCC 5[/size]
${build(groups["Linux"].mainFile)}
These builds use a mechanism called [url=http://appimage.org/]AppImage[/url] which should allow these builds to run on most Linux distributions. However, we recommend that you compile your own builds which will result in less issues.
Alternatively, if there is a package in your software repository then you should use that. If you are the maintainer of such a package for a distribution then let us know and we will include that here.
[img]http://scp.indiegames.us/img/mac-icon.png[/img] [color=green][size=12pt]OS X[/size][/color]
[b][color=red]Not available[/color][/b] We recently lost access to our Mac CI environment which we usually used for compiling these builds so for the time being, there will be no builds for this OS.
[hidden=TrackIR Users]
[size=12pt]Important!![/size]
An external DLL is required for FSO to use TrackIR functions. The following DLL is simply unpacked in to your main FreeSpace2 root dir.
TrackIR is only supported on Windows.
[url=http://www.mediafire.com/download.php?4zw024zrh44etse]TrackIR SCP DLL[/url] ([url=http://scp.fsmods.net/builds/scptrackir.zip]Mirror[/url]) ([url=http://scp.indiegames.us/builds/scptrackir.zip]Mirror[/url])[/hidden]
Known issues:
[list]
[li][url=https://github.com/scp-fs2open/fs2open.github.com/issues]Github issues[/url] and [url=https://github.com/scp-fs2open/fs2open.github.com/pulls]pending pull requests[/url][/li]
[li]See the list of [url=http://scp.indiegames.us/mantis/search.php?project_id=1&status_id%5B%5D=10&status_id%5B%5D=20&status_id%5B%5D=30&status_id%5B%5D=40&status_id%5B%5D=50&priority_id%5B%5D=40&priority_id%5B%5D=50&priority_id%5B%5D=60&sticky_issues=on&sortby=last_updated&dir=DESC&per_page=200&hide_status_id=-2]Fix for next release[/url] bugs - mark a bug as an elevated priority (high, urgent, immediate) to get it included in that filter.[/li]
[/list]
-3
View File
@@ -1,3 +0,0 @@
Mako==1.1.5
requests==2.26.0
semantic-version==2.8.5
-37
View File
@@ -1,37 +0,0 @@
import os
import time
from functools import wraps
from requests import RequestException
GLOBAL_TIMEOUT = 30
def retry_multi(max_retries):
"""
Retry a function `max_retries` times.
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):
config["git"]["repo"] = os.path.expandvars(config["git"]["repo"])
+12
View File
@@ -1,3 +1,15 @@
include(WriteCompilerDetectionHeader)
set(CMAKE_CXX_STANDARD_REQUIRED 11)
set(CMAKE_CXX_STANDARD 11)
get_property(CMAKE_CXX_KNOWN_FEATURES GLOBAL PROPERTY CMAKE_CXX_KNOWN_FEATURES)
write_compiler_detection_header(
FILE "${GENERATED_SOURCE_DIR}/scp_compiler_detection.h"
PREFIX SCP
COMPILERS AppleClang Clang GNU MSVC
FEATURES ${CMAKE_CXX_KNOWN_FEATURES}
VERSION 3.2
)
+2
View File
@@ -21,6 +21,8 @@ endif()
set(CMAKE_EXTRA_INCLUDE_FILES "cstddef")
CHECK_TYPE_SIZE("std::max_align_t" STD_MAX_ALIGN_T LANGUAGE CXX)
set(CMAKE_EXTRA_INCLUDE_FILES "type_traits")
CHECK_TYPE_SIZE("std::is_trivially_copyable<int>" STD_IS_TRIVIALLY_COPYABLE LANGUAGE CXX)
set(CMAKE_EXTRA_INCLUDE_FILES)
CHECK_TYPE_SIZE("char32_t" CHAR32_T LANGUAGE CXX)
+2
View File
@@ -8,6 +8,8 @@
#cmakedefine SCP_HAVE_MAX_ALIGN_T
#cmakedefine SCP_HAVE_STD_MAX_ALIGN_T
#cmakedefine01 HAVE_STD_IS_TRIVIALLY_COPYABLE
#cmakedefine01 IS_64BIT
#cmakedefine01 SCP_HAVE_STRCASECMP
+3 -3
View File
@@ -6,9 +6,9 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
include("${PROJECT_SOURCE_DIR}/version_override.cmake")
endif()
set_if_not_defined(FSO_VERSION_MAJOR 21)
set_if_not_defined(FSO_VERSION_MINOR 4)
set_if_not_defined(FSO_VERSION_BUILD 1)
set_if_not_defined(FSO_VERSION_MAJOR 20)
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
set_if_not_defined(FSO_VERSION_REVISION 0)
-1
View File
@@ -26,7 +26,6 @@ TARGET_LINK_LIBRARIES(code PUBLIC openal)
TARGET_LINK_LIBRARIES(code PUBLIC ${LUA_LIBS})
TARGET_LINK_LIBRARIES(code PUBLIC ${PNG_LIBS})
TARGET_LINK_LIBRARIES(code PUBLIC ${JPEG_LIBS})
TARGET_LINK_LIBRARIES(code PUBLIC lz4)
TARGET_LINK_LIBRARIES(code PUBLIC sdl2)
-1
View File
@@ -7,7 +7,6 @@ ActionDefinition::ActionDefinition(SCP_string name, SCP_vector<ActionParameter>
: m_name(std::move(name)), m_parameters(std::move(parameters))
{
}
ActionDefinition::~ActionDefinition() = default;
const SCP_string& ActionDefinition::getName() const
{
-1
View File
@@ -15,7 +15,6 @@ struct ActionParameter {
class ActionDefinition {
public:
ActionDefinition(SCP_string name, SCP_vector<ActionParameter> parameters);
virtual ~ActionDefinition();
const SCP_string& getName() const;
const SCP_vector<actions::ActionParameter>& getParameters() const;
+18 -15
View File
@@ -47,7 +47,24 @@ void parseActionParameters(const SCP_vector<ActionParameter>& params,
const ActionDefinitionManager& ActionDefinitionManager::instance()
{
static const ActionDefinitionManager manager;
static const ActionDefinitionManager manager = []() {
ActionDefinitionManager newManager;
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::MoveToSubmodel>("Move To Subobject")));
newManager.addDefinition(std::unique_ptr<ActionDefinition>(
new BuiltinActionDefinition<types::ParticleEffectAction>("Start Particle Effect")));
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::PlaySoundAction>("Play 3D Sound")));
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetDirectionAction>("Set Direction")));
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetPositionAction>("Set Position")));
newManager.addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::WaitAction>("Wait")));
return newManager;
}();
return manager;
}
void ActionDefinitionManager::addDefinition(std::unique_ptr<ActionDefinition> def)
@@ -127,19 +144,5 @@ std::unique_ptr<Action> ActionDefinitionManager::parseAction(const flagset<Progr
return std::unique_ptr<Action>();
}
ActionDefinitionManager::ActionDefinitionManager()
{
addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::MoveToSubmodel>("Move To Subobject")));
addDefinition(std::unique_ptr<ActionDefinition>(
new BuiltinActionDefinition<types::ParticleEffectAction>("Start Particle Effect")));
addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::PlaySoundAction>("Play 3D Sound")));
addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetDirectionAction>("Set Direction")));
addDefinition(
std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetPositionAction>("Set Position")));
addDefinition(std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::WaitAction>("Wait")));
}
} // namespace actions
+1 -1
View File
@@ -19,7 +19,7 @@ class ActionDefinitionManager {
static const ActionDefinitionManager& instance();
private:
ActionDefinitionManager();
ActionDefinitionManager() = default;
void addDefinition(std::unique_ptr<ActionDefinition> def);
+2
View File
@@ -0,0 +1,2 @@
#include "BuiltinActionDefinition.h"
-1
View File
@@ -15,7 +15,6 @@ class BuiltinActionDefinition : public ActionDefinition {
: ActionDefinition(std::move(name), {{"parameter", expression::ValueTypeTraits<ActionValueType>::type}})
{
}
~BuiltinActionDefinition() override = default;
flagset<ProgramContextFlags> getRequiredContext() const override
{
+2 -2
View File
@@ -123,9 +123,9 @@ Program& Program::operator=(const Program& other)
Program& Program::operator=(Program&&) noexcept = default;
Program::Program(Program&&) noexcept = default;
bool ProgramSet::isEmpty() const
const SCP_vector<actions::Program>& ProgramSet::getPrograms() const
{
return _programs.empty();
return _programs;
}
ProgramSet::ProgramSet() = default;
+1 -1
View File
@@ -84,7 +84,7 @@ class ProgramSet {
ProgramSet();
explicit ProgramSet(const flagset<ProgramContextFlags>& context_flags);
bool isEmpty() const;
const SCP_vector<actions::Program>& getPrograms() const;
/**
* @brief Starts the execution of all programs within this set with the specified parameters
@@ -14,12 +14,6 @@ class AbstractExpression;
template <typename T>
class TypedActionExpression;
/**
* @brief An expression that can be executed to compute a value at runtime
*
* This is the basis of having dynamic values in actions by allowing mods to specify complex expressions that make use
* of variables and functions for computing their values at runtime.
*/
class ActionExpression {
public:
ActionExpression() = default;
@@ -42,13 +36,6 @@ class ActionExpression {
std::shared_ptr<nodes::AbstractExpression> m_expression;
};
/**
* @brief An action expression with a specific type specified at compile time
*
* This is most useful for built-in actions that have the required type fixed at compile time.
*
* @tparam T The type of the expression value.
*/
template <typename T>
class TypedActionExpression {
public:
+14 -11
View File
@@ -22,7 +22,19 @@ Value value_minus(const SCP_vector<Value>& parameters)
const FunctionManager& FunctionManager::instance()
{
static const FunctionManager manager;
static const FunctionManager manager = []() {
FunctionManager newManager;
newManager.addOperator("+", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_plus<int>);
newManager.addOperator("+", {ValueType::Float, ValueType::Float}, ValueType::Float, value_plus<float>);
newManager.addOperator("+", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_plus<vec3d>);
newManager.addOperator("-", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_minus<int>);
newManager.addOperator("-", {ValueType::Float, ValueType::Float}, ValueType::Float, value_minus<float>);
newManager.addOperator("-", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_minus<vec3d>);
return newManager;
}();
return manager;
}
void FunctionManager::addOperator(const SCP_string& name,
@@ -75,16 +87,7 @@ const FunctionDefinition* FunctionManager::findOperator(const SCP_string& name,
return nullptr;
}
FunctionManager::FunctionManager()
{
addOperator("+", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_plus<int>);
addOperator("+", {ValueType::Float, ValueType::Float}, ValueType::Float, value_plus<float>);
addOperator("+", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_plus<vec3d>);
addOperator("-", {ValueType::Integer, ValueType::Integer}, ValueType::Integer, value_minus<int>);
addOperator("-", {ValueType::Float, ValueType::Float}, ValueType::Float, value_minus<float>);
addOperator("-", {ValueType::Vector, ValueType::Vector}, ValueType::Vector, value_minus<vec3d>);
}
FunctionManager::FunctionManager() = default;
} // namespace expression
} // namespace actions
+2 -4
View File
@@ -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
@@ -563,7 +561,7 @@ const char *ai_get_goal_target_name(const char *name, int *index);
void ai_clear_goal_target_names();
extern void init_ai_system(void);
extern void ai_attack_object(object *attacker, object *attacked, int ship_info_index = -1);
extern void ai_attack_object(object *attacker, object *attacked, ship_subsys *ssp, int ship_info_index = -1);
extern void ai_evade_object(object *evader, object *evaded);
extern void ai_ignore_object(object *ignorer, object *ignored, int ignore_new);
extern void ai_ignore_wing(object *ignorer, int wingnum);
+2 -9
View File
@@ -5,7 +5,7 @@
namespace AI {
FLAG_LIST(AI_Flags) {
Formation_wing, // Fly in formation as part of wing. Also used when flying waypoints.
Formation_wing, // Fly in formation as part of wing.
Awaiting_repair, // Awaiting a repair ship.
Being_repaired, // Currently docked with repair ship.
Repairing, // Repairing a ship (or going to repair a ship)
@@ -130,16 +130,9 @@ namespace AI {
Use_only_single_fov_for_turrets,
No_turning_directional_bias,
Use_axial_turnrate_differences,
all_nonshielded_ships_can_manage_ets,
fightercraft_nonshielded_ships_can_manage_ets,
nonshielded_ships_can_manage_ets,
Better_collision_avoidance,
Require_exact_los,
Improved_missile_avoidance,
Friendlies_use_countermeasure_firechance,
Improved_subsystem_attack_pathing,
Fixed_ship_weapon_collision,
No_shield_damage_from_ship_collisions,
Reset_last_hit_target_time_for_player_hits,
NUM_VALUES
};
+9 -69
View File
@@ -58,26 +58,6 @@ int ai_path_type_match(char *p)
return -1;
}
const char* AI_secondary_range_aware_select_modes[] = {
"retail",
"aware",
};
int Num_ai_secondary_range_aware_select_modes = sizeof(AI_secondary_range_aware_select_modes) / sizeof(char*);
int ai_secondary_range_select_mode_match(char* p)
{
int i;
for (i = 0; i < Num_ai_secondary_range_aware_select_modes; i++)
{
if (!stricmp(AI_secondary_range_aware_select_modes[i], p))
return i;
}
return -1;
}
void parse_ai_profiles_tbl(const char *filename)
{
int i;
@@ -468,6 +448,7 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$firing requires exact los:", AI::Profile_Flags::Require_exact_los);
profile->ai_path_mode = AI_PATH_MODE_NORMAL;
if (optional_string("$ai path mode:"))
{
stuff_string(buf, F_NAME, NAME_LENGTH);
@@ -500,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);
}
@@ -537,50 +520,15 @@ void parse_ai_profiles_tbl(const char *filename)
profile->second_order_lead_predict_factor = 0;
}
}
if (optional_string("$ships with no shields can manage ETS:"))
{
mprintf(("Warning: \"$ships with no shields can manage ETS\" flag is deprecated in favor of \"$any ship with no shields can manage ETS\"\n"));
bool temp;
stuff_boolean(&temp);
profile->flags.set(AI::Profile_Flags::all_nonshielded_ships_can_manage_ets, temp);
}
set_flag(profile, "$no directional bias for missile and ship turning:", AI::Profile_Flags::No_turning_directional_bias);
set_flag(profile, "$respect ship axial turnrate differences:", AI::Profile_Flags::Use_axial_turnrate_differences);
set_flag(profile, "$any ship with no shields can manage ETS:", AI::Profile_Flags::all_nonshielded_ships_can_manage_ets);
set_flag(profile, "$fighters/bombers with no shields can manage ETS:", AI::Profile_Flags::fightercraft_nonshielded_ships_can_manage_ets);
set_flag(profile, "$ships with no shields can manage ETS:", AI::Profile_Flags::nonshielded_ships_can_manage_ets);
set_flag(profile, "$better combat collision avoidance for fightercraft:", AI::Profile_Flags::Better_collision_avoidance);
set_flag(profile, "$improved missile avoidance for fightercraft:", AI::Profile_Flags::Improved_missile_avoidance);
set_flag(profile, "$friendly ships use AI profile countermeasure chance:", AI::Profile_Flags::Friendlies_use_countermeasure_firechance);
set_flag(profile, "$improved subsystem attack pathing:", AI::Profile_Flags::Improved_subsystem_attack_pathing);
set_flag(profile, "$fixed ship-weapon collisions:", AI::Profile_Flags::Fixed_ship_weapon_collision);
//Intention is to expand this feature to include a preference for close or long range weapons
//hence using something besides a simple flag.
if (optional_string("$AI secondary range awareness:"))
{
stuff_string(buf, F_NAME, NAME_LENGTH);
int j = ai_secondary_range_select_mode_match(buf);
if (j >= 0) {
profile->ai_range_aware_secondary_select_mode = j;
}
else {
Warning(LOCATION, "Invalid ai secondary range awareness mode '%s' specified", buf);
}
}
set_flag(profile, "$no shield damage from ship collisions:", AI::Profile_Flags::No_shield_damage_from_ship_collisions);
set_flag(profile, "$reset last_hit_target_time for player hits:", AI::Profile_Flags::Reset_last_hit_target_time_for_player_hits);
// if we've been through once already and are at the same place, force a move
if (saved_Mp && (saved_Mp == Mp))
{
@@ -652,12 +600,11 @@ void ai_profile_t::reset()
flags.reset();
ai_path_mode = AI_PATH_MODE_NORMAL;
ai_path_mode = 0;
subsystem_path_radii = 0;
bay_arrive_speed_mult = 1.0f;
bay_depart_speed_mult = 1.0f;
bay_arrive_speed_mult = 0;
bay_depart_speed_mult = 0;
second_order_lead_predict_factor = 0;
ai_range_aware_secondary_select_mode = AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL;
for (int i = 0; i < NUM_SKILL_LEVELS; ++i) {
max_incoming_asteroids[i] = 0;
@@ -729,17 +676,10 @@ void ai_profile_t::reset()
flags.set(AI::Profile_Flags::Fix_ai_path_order_bug);
flags.set(AI::Profile_Flags::Aspect_invulnerability_fix);
flags.set(AI::Profile_Flags::Use_actual_primary_range);
flags.set(AI::Profile_Flags::fightercraft_nonshielded_ships_can_manage_ets);
flags.set(AI::Profile_Flags::nonshielded_ships_can_manage_ets);
}
// this flag has been enabled ever since 3.7.2
if (mod_supports_version(3, 7, 2)) {
flags.set(AI::Profile_Flags::Fix_ramming_stationary_targets_bug);
}
// and this flag has been enabled ever since 3.6.10
if (mod_supports_version(3, 6, 10)) {
flags.set(AI::Profile_Flags::Reset_last_hit_target_time_for_player_hits);
}
if (mod_supports_version(21, 4, 0)) {
flags.set(AI::Profile_Flags::Fixed_ship_weapon_collision);
}
}
-6
View File
@@ -17,9 +17,6 @@
// AI Path types
#define AI_PATH_MODE_NORMAL 0
#define AI_PATH_MODE_ALT1 1
#define AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL 0
#define AI_RANGE_AWARE_SEC_SEL_MODE_AWARE 1
#define MAX_AI_PROFILES 5
@@ -110,9 +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;
void reset();
};
+16 -158
View File
@@ -17,13 +17,12 @@
#include "io/timer.h"
#include "math/staticrand.h"
#include "mission/missionparse.h"
#include "object/objcollide.h"
#include "object/object.h"
#include "ship/afterburner.h"
#include "ship/ship.h"
#include "utils/Random.h"
#include "weapon/weapon.h"
#ifdef _MSC_VER
#pragma optimize("", off)
#pragma auto_inline(off)
@@ -530,112 +529,6 @@ int ai_big_maybe_follow_subsys_path(int do_dot_check)
return 0;
}
bool ai_new_maybe_reposition_attack_subsys() {
ai_info* aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
if (aip->target_objnum < 0 || aip->targeted_subsys == nullptr)
return false;
object* target_objp = &Objects[aip->target_objnum];
// dont bother with this if you're still far away
if (vm_vec_dist(&Pl_objp->pos, &target_objp->pos) > target_objp->radius * 2.0f + 2000.0f)
return false;
vec3d geye, gsubpos;
eye* ep;
polymodel* pm = model_get(Ship_info[Ships[Pl_objp->instance].ship_info_index].model_num);
// get world pos of eye (stored in geye)
ep = &(pm->view_positions[Ships[Pl_objp->instance].current_viewpoint]);
model_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);
vm_vec_add2(&gsubpos, &En_objp->pos);
// you're in sight! shoot it!
if (ship_subsystem_in_sight(En_objp, aip->targeted_subsys, &geye, &gsubpos, 0))
return false;
// not in sight, gotta get there
vec3d tgt2pl = Pl_objp->pos - target_objp->pos;
vec3d tgt2subsys = gsubpos - target_objp->pos;
float pl_dist = vm_vec_mag(&tgt2pl);
float subsys_dist = vm_vec_mag(&tgt2subsys) * 1.3f; // add a bit to this, we don't want to go directly at the subsys, we want to go above it
float angle2subsys = vm_vec_delta_ang(&tgt2pl, &tgt2subsys, nullptr);
float angle2goal = angle2subsys;
if (angle2goal > 1.2f) // let's not try to go more than about 70 degrees around the ship
angle2goal = 1.2f;
// we'll start by travelling along the sphere at our current distance around the ship
// remember that goal_pos will be relative to our target_ship (so we can scale in and out easily)
vec3d goal_pos, cross;
vm_vec_cross(&cross, &tgt2pl, &tgt2subsys);
vm_vec_normalize(&cross);
vm_vec_negate(&cross);
vm_rot_point_around_line(&goal_pos, &tgt2pl, angle2goal, &vmd_zero_vector, &cross);
// then scale in (or out) towards our subsys
vec3d world_goal_pos;
float new_radius = ((subsys_dist - pl_dist) * (angle2goal / angle2subsys)) + pl_dist;
vm_vec_copy_scale(&world_goal_pos, &goal_pos, new_radius / pl_dist);
world_goal_pos += target_objp->pos;
vec3d* good_pos = nullptr;
if (pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f)) {
// ok best case won't work
if (pl_dist > subsys_dist) {
//we're farther away than the subsys, let's try at our current distance
world_goal_pos = goal_pos + target_objp->pos;
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
good_pos = &world_goal_pos;
}
// ok, let's start moving it away and see if that's better
for (float i = 1.0f; i < 4.0f; i += 1.0f) {
if (good_pos)
break;
vm_vec_copy_scale(&world_goal_pos, &goal_pos, (new_radius / pl_dist) * powf(1.3f,i));
world_goal_pos += target_objp->pos;
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
good_pos = &world_goal_pos;
}
// welp this sucks, just run away i guess
if (!good_pos) {
vm_vec_copy_normalize(&world_goal_pos, &tgt2pl);
world_goal_pos *= target_objp->radius + pl_dist;
world_goal_pos += target_objp->pos;
good_pos = &world_goal_pos;
}
}
else
good_pos = &world_goal_pos;
ai_turn_towards_vector(good_pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
float subsys_distance = vm_vec_dist(&Pl_objp->pos, &gsubpos);
// full speed until you're close, then slow down a bit
accelerate_ship(aip, subsys_distance > 200.0f ? 1.0f : subsys_distance / 200.0f);
if (!(Ships[Pl_objp->instance].flags[Ship::Ship_Flags::Afterburner_locked])) {
if (subsys_distance > 300.0f) {
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
afterburners_start(Pl_objp);
aip->afterburner_stop_time = Missiontime + 2 * F1_0;
}
}
else
afterburners_stop(Pl_objp);
}
return true;
}
// This function is only called from ai_big_chase_attack() when a ship is flying very slowly and
// attacking a big ship. The ship should scan for enemy fighter/bombers... if any are close, then
// return 1, otherwise return 0;
@@ -744,10 +637,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
}
// see if Pl_objp needs to reposition to get a good shot at subsystem which is being attacked
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
if (ai_new_maybe_reposition_attack_subsys())
return;
} else if ( ai_big_maybe_follow_subsys_path() ) {
if ( ai_big_maybe_follow_subsys_path() ) {
if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze])) {
afterburners_stop(Pl_objp);
}
@@ -777,25 +667,11 @@ 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;
if (weapons->num_primary_banks >= 1 && weapons->current_primary_bank >= 0) {
wip = &Weapon_info[weapons->primary_bank_weapons[weapons->current_primary_bank]];
} else if (weapons->num_secondary_banks >= 1 && weapons->current_secondary_bank >= 0) {
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];
@@ -823,7 +699,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
} else if (dot_to_enemy < 0.75f) {
accel = 0.75f;
} else {
if (dist_to_enemy > (optimal_range > 0.0f ? optimal_range : (weapon_travel_dist/2.0f))) {
if (dist_to_enemy > weapon_travel_dist/2.0f) {
accel = 1.0f;
} else {
accel = 1.0f - dot_to_enemy;
@@ -881,14 +757,7 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
swp = &Ships[Pl_objp->instance].weapons;
float target_radius = En_objp->radius;
// normal subsystem attacking uses whether or not its on a path and skips this function entirely to stop "over-eager" firing
// since the improved method doesnt use paths, refine the target radius to stop over-eager shooting
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing] && aip->targeted_subsys != nullptr)
target_radius = aip->targeted_subsys->system_info->radius;
if (dot_to_enemy > 0.95f - 0.5f * target_radius/MAX(1.0f, target_radius + dist_to_enemy)) {
if (dot_to_enemy > 0.95f - 0.5f * En_objp->radius/MAX(1.0f, En_objp->radius + dist_to_enemy)) {
aip->time_enemy_in_range += flFrametime;
// Chance of hitting ship is based on dot product of firing ship's forward vector with vector to ship
@@ -928,7 +797,7 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
}
}
if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank]) && weapon_target_satisfies_lock_restrictions(swip, En_objp)) {
if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank])) {
float firing_range;
if (swip->wi_flags[Weapon::Info_Flags::Local_ssm])
firing_range=swip->lssm_lock_range;
@@ -958,11 +827,10 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
} else {
t = swip->fire_wait;
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
} else {
swp->burst_counter[current_bank_adjusted] = 0;
}
swp->burst_seed[current_bank_adjusted] = Random::next();
}
} else {
if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
@@ -971,11 +839,10 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
} else {
t = set_secondary_fire_delay(aip, temp_shipp, swip, false);
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
} else {
swp->burst_counter[current_bank_adjusted] = 0;
}
swp->burst_seed[current_bank_adjusted] = Random::next();
}
}
swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
@@ -1172,10 +1039,7 @@ void ai_big_chase()
float dist_normal_to_enemy;
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
else // I'm fairly sure this retail code VVV meant to use / instead of *
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
} else {
// don;t have normal so use a conservative value here
dist_normal_to_enemy = 0.3f * dist_to_enemy;
@@ -1452,17 +1316,13 @@ void ai_big_strafe_attack()
ai_info *aip;
vec3d target_pos;
vec3d rand_vec;
float accel, t;
float target_dist = 0.0f;
float target_dot = 0.0f;
float target_dist, target_dot, accel, t;
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
if (ai_new_maybe_reposition_attack_subsys())
return;
} else if (ai_big_maybe_follow_subsys_path(0))
if ( ai_big_maybe_follow_subsys_path(0) ) {
return;
}
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
if ( ai_big_strafe_maybe_retreat(&target_pos) )
@@ -1485,7 +1345,7 @@ void ai_big_strafe_attack()
if ( t > 0.0f && t < 1.5f ) {
// set up goal_point for avoid path to turn towards
aip->goal_point = Pl_objp->pos;
switch(Random::next(4)) {
switch(rand()%4) {
case 0:
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, 2.0f);
break;
@@ -1579,11 +1439,9 @@ void ai_big_strafe_glide_attack()
flight_dot_to_enemy = vm_vec_dot(&target_objp->pos, &norm_vel_vec); //Angle between flight path and target ship
//If we're following a path, then we shouldn't be doing glide strafe
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
if (ai_new_maybe_reposition_attack_subsys())
return;
} else if (ai_big_maybe_follow_subsys_path(0))
if ( ai_big_maybe_follow_subsys_path(0) ) {
return;
}
//Gets a point on the target ship to attack, as well as distance and the angle between the nose of the attacker and that point.
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
@@ -1717,7 +1575,7 @@ void ai_big_strafe_retreat1()
if (Missiontime - aip->submode_start_time > fl2f(1.50f)) {
// set up goal_point for avoid path to turn towards
aip->prev_goal_point = Pl_objp->pos;
switch(Random::next(4)) {
switch(rand()%4) {
case 0:
vm_vec_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec);
break;
+167 -397
View File
File diff suppressed because it is too large. Load diff
+8 -14
View File
@@ -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,9 +187,6 @@ void ai_post_process_mission()
object *objp;
int i;
// make sure team visibility is updated first
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.
@@ -297,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 )
@@ -2153,14 +2147,14 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
// Mike -- debug code!
// If a ship has a subobject on it, attack that instead of the main ship!
ai_attack_object( objp, other_obj);
ai_attack_object( objp, other_obj, NULL);
break;
case AI_GOAL_CHASE_WEAPON:
Assert( Weapons[current_goal->target_instance].objnum >= 0 );
other_obj = &Objects[Weapons[current_goal->target_instance].objnum];
Assert( other_obj->signature == current_goal->target_signature );
ai_attack_object( objp, other_obj);
ai_attack_object( objp, other_obj, NULL );
break;
case AI_GOAL_GUARD:
@@ -2285,7 +2279,7 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
shipnum = ship_name_lookup( current_goal->target_name );
Assert( shipnum >= 0 );
other_obj = &Objects[Ships[shipnum].objnum];
ai_attack_object( objp, other_obj);
ai_attack_object( objp, other_obj, NULL);
ai_set_attack_subsystem( objp, current_goal->ai_submode ); // submode stored the subsystem type
if (current_goal->ai_mode != AI_GOAL_DESTROY_SUBSYSTEM) {
if (aip->target_objnum != -1) {
@@ -2307,14 +2301,14 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
break;
case AI_GOAL_CHASE_ANY:
ai_attack_object( objp, nullptr);
ai_attack_object( objp, NULL, NULL );
break;
// chase-ship-class is chase-any but restricted to a subset of ships
case AI_GOAL_CHASE_SHIP_CLASS:
shipnum = ship_info_lookup( current_goal->target_name );
Assertion( shipnum >= 0, "The target of AI_GOAL_CHASE_SHIP_CLASS must refer to a valid ship class!" );
ai_attack_object( objp, nullptr, shipnum );
ai_attack_object( objp, NULL, NULL, shipnum );
break;
case AI_GOAL_WARP: {
+1 -1
View File
@@ -32,6 +32,6 @@ int check_ok_to_fire(int objnum, int target_objnum, weapon_info *wip, int second
bool check_los(int objnum, int target_objnum, float threshold, int primary_bank, int secondary_bank, ship_subsys* turret);
//Does all the stuff needed to aim and fire a turret.
void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss);
void ai_fire_from_turret(ship *shipp, ship_subsys *ss);
#endif
+192 -173
View File
@@ -18,7 +18,6 @@
#include "render/3d.h"
#include "ship/ship.h"
#include "ship/shipfx.h"
#include "utils/Random.h"
#include "weapon/beam.h"
#include "weapon/flak.h"
#include "weapon/muzzleflash.h"
@@ -88,18 +87,6 @@ typedef struct eval_enemy_obj_struct {
int nearest_objnum;
} eval_enemy_obj_struct;
// the current world orientation of the turret matrix, corresponding to its fvec and uvec defined in the model
// is NOT affected by the turret's current aiming
void turret_instance_find_world_orient(matrix* out_mat, int model_instance_num, int submodel_num, const matrix* objorient)
{
auto pmi = model_get_instance(model_instance_num);
auto pm = model_get(pmi->model_num);
vec3d fvec, uvec;
model_instance_find_world_dir(&fvec, &pm->submodel[submodel_num].frame_of_reference.vec.fvec, pm, pmi, pm->submodel[submodel_num].parent, objorient);
model_instance_find_world_dir(&uvec, &pm->submodel[submodel_num].frame_of_reference.vec.uvec, pm, pmi, pm->submodel[submodel_num].parent, objorient);
vm_vector_2_matrix_norm(out_mat, &fvec, &uvec);
}
/**
* Is object in turret field of view?
*
@@ -111,53 +98,117 @@ void turret_instance_find_world_orient(matrix* out_mat, int model_instance_num,
*
* @return 1 if objp is in fov of the specified turret. Otherwise return 0.
*/
bool object_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec, vec3d *tpos, float dist)
int object_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec, vec3d *tpos, float dist)
{
vec3d v2e;
float size_mod;
bool in_fov = false;
if (ss->flags[Ship::Subsystem_Flags::FOV_edge_check]) {
int model_num;
switch (objp->type) {
case OBJ_SHIP:
model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num;
break;
case OBJ_ASTEROID:
model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype];
break;
default:
vm_vec_normalized_dir(&v2e, &objp->pos, tpos);
size_mod = objp->radius / (dist + objp->radius);
in_fov = turret_fov_test(ss, tvec, &v2e, size_mod);
return in_fov;
}
auto pm = model_get(model_num);
for (int i = 0; i < 8; i++) {
vec3d bbox_point;
vm_vec_unrotate(&bbox_point, &pm->bounding_box[i], &objp->orient);
bbox_point += objp->pos;
vm_vec_normalized_dir(&v2e, &bbox_point, tpos);
in_fov = turret_fov_test(ss, tvec, &v2e, -0.2f);
if (in_fov)
return true;
}
}
// if the bbox points method didn't work (or fov_edge_checks isn't on)
// try the normal method
bool in_fov;
vm_vec_normalized_dir(&v2e, &objp->pos, tpos);
size_mod = objp->radius / (dist + objp->radius);
in_fov = turret_fov_test(ss, tvec, &v2e, size_mod);
if ( in_fov ) {
return 1;
}
return in_fov;
return 0;
}
bool is_object_radius_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec, vec3d *tpos, vec3d *v2e, vec3d *predicted_pos, float distance)
{
float target_dist = distance;
if (distance == 0.0f)
target_dist = vm_vec_dist(predicted_pos,tpos);
if (object_in_turret_fov(objp, ss, tvec, tpos, target_dist + objp->radius)) {
// so the targeted spot in not in fov but the enemy + radius is
// lets align the darn gun and try shooting there
vec3d temp_vec;
float multiplier = 0;
model_subsystem *tp = ss->system_info;
// project v2e_from_turret to turret normal
// substract resultant vector from the temp_vec (v2e_from_turret)
// adjust z component as necessary
// calculate multiplier for the resultant vector
// use multiplier and the z component and compose a new vector
float dot = vm_vec_dot(v2e, tvec);
vm_vec_scale_add(&temp_vec, v2e, tvec, -dot);
if (IS_VEC_NULL_SQ_SAFE(&temp_vec)) {
// return false, target is perfectly aligned over or below the turret
// safe bet is to allow turret to reacquire better target
return false;
}
// normalize the vec, it needs to be done regardless
vm_vec_normalize(&temp_vec);
bool fix_elevation = false;
bool fix_base_rot = false;
if (dot < tp->turret_fov) {
dot = tp->turret_fov;
fix_elevation = true;
}
if (dot > tp->turret_max_fov) {
dot = tp->turret_max_fov;
fix_elevation = true;
}
if (tp->flags[Model::Subsystem_Flags::Turret_alt_math]) {
vec3d temp_vec2;
vm_vec_rotate(&temp_vec2, &temp_vec, &ss->world_to_turret_matrix);
// now in turrets frame of reference
// check if math is actually possible
if (!((temp_vec2.xyz.x == 0) && (temp_vec2.xyz.y == 0))) {
float temp_z = temp_vec2.xyz.z;
temp_vec2.xyz.z = 0.0f;
// make sure null vecs wont happen
if (!IS_VEC_NULL_SQ_SAFE(&temp_vec2)) {
vm_vec_normalize(&temp_vec2);
// only do this if it actually is required
if (-temp_vec2.xyz.y < tp->turret_y_fov) {
float check_pos = 1;
fix_base_rot = true;
temp_vec2.xyz.y = -tp->turret_y_fov;
if (temp_vec2.xyz.x < 0)
check_pos = -1;
temp_vec2.xyz.x = check_pos * sqrtf(1 - (temp_vec2.xyz.y*temp_vec2.xyz.y));
// restore z component
float scaler = sqrtf(1 - (temp_z*temp_z));
vm_vec_scale(&temp_vec2,scaler);
temp_vec2.xyz.z = temp_z;
// back to world frame
vm_vec_unrotate(&temp_vec, &temp_vec2, &ss->world_to_turret_matrix);
}
}
}
}
if (fix_elevation || fix_base_rot) {
if (fix_elevation) {
multiplier = sqrtf(1 - (dot*dot));
// keep the temp_vec scaled with the tweaked vector
vm_vec_scale(&temp_vec, multiplier);
}
vm_vec_scale_add(v2e, &temp_vec, tvec, dot);
// and we are done with v2e...
vm_vec_scale_add(predicted_pos, tpos, v2e, target_dist);
// and we are done with predicted position
return true;
} else {
mprintf(("Warning: Function 'is_object_radius_in_turret_fov' was called\nwithout need to fix turret alignments\n"));
return false;
}
}
// outside of the expanded radii, unable to align, return false
return false;
}
/**
@@ -504,7 +555,6 @@ void evaluate_obj_as_target(object *objp, eval_enemy_obj_struct *eeo)
if ( objp->type == OBJ_SHIP ) {
shipp = &Ships[objp->instance];
ship_info* sip = &Ship_info[shipp->ship_info_index];
// check on enemy team
if ( !iff_matches_mask(shipp->team, eeo->enemy_team_mask) ) {
@@ -544,18 +594,16 @@ void evaluate_obj_as_target(object *objp, eval_enemy_obj_struct *eeo)
}
}
// don't shoot at big ships with huge weapons unless they have the flag
// don't shoot at small ships if we shouldn't
if (eeo->eeo_flags & EEOF_BIG_ONLY) {
if (sip->class_type == -1 || !(Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
if (!(Ship_info[shipp->ship_info_index].is_big_or_huge())) {
return;
}
}
// ^ Note the difference between these checks
// V "small only" EXCLUDES only big ships, "huge" INCLUDES only big ships
// don't shoot at ships ignored by small weapons if this is a small weapon
// don't shoot at big ships if we shouldn't
if (eeo->eeo_flags & EEOF_SMALL_ONLY) {
if (sip->class_type >= 0 && (Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
if ((Ship_info[shipp->ship_info_index].is_big_or_huge())) {
return;
}
}
@@ -1131,18 +1179,23 @@ int find_turret_enemy(ship_subsys *turret_subsys, int objnum, vec3d *tpos, vec3d
if (!skip) {
if ( Objects[aip->target_objnum].type == OBJ_SHIP ) {
ship_info* esip = &Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index];
// check for huge weapon and huge ship
if ( !big_only_flag || (esip->class_type >= 0 && Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only]) ) {
if ( !big_only_flag || (Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index].is_big_or_huge()) ) {
// check for tagged only and tagged ship
if ( tagged_only_flag && ship_is_tagged(&Objects[aip->target_objnum]) ) {
// select new target if aip->target_objnum is out of field of view
vec3d v2e;
float dist;
bool in_fov;
vm_vec_normalized_dir(&v2e, &Objects[aip->target_objnum].pos, tpos);
dist = vm_vec_normalized_dir(&v2e, &Objects[aip->target_objnum].pos, tpos);
in_fov = turret_fov_test(turret_subsys, tvec, &v2e);
if (turret_subsys->flags[Ship::Subsystem_Flags::FOV_edge_check]) {
if (in_fov == false)
if (object_in_turret_fov(&Objects[aip->target_objnum], turret_subsys, tvec, tpos, dist + Objects[aip->target_objnum].radius))
in_fov = true;
}
// MODIFY FOR ATTACKING BIG SHIP
// dot += (0.5f * Objects[aip->target_objnum].radius / dist);
if (in_fov) {
@@ -1177,22 +1230,20 @@ int find_turret_enemy(ship_subsys *turret_subsys, int objnum, vec3d *tpos, vec3d
* of the turret.
*
* @param objp Pointer to object
* @param tp Turret model system on that object
* @param tp Turrent model system on that object
* @param gpos [Output] Global absolute position of gun firing point
* @param gvec [Output] Global vector
*
* @note The gun normal is the unrotated gun normal, (the center of the FOV cone), not
* the actual gun normal given using the current turret heading. But it _is_ rotated into the model's orientation
* in global space.
* @note2 Because of this, both single-part and multi-part turrets are treated the same way; no need to find the multi-part's gun submodel.
*/
void ship_get_global_turret_info(const object *objp, const model_subsystem *tp, vec3d *gpos, vec3d *gvec)
void ship_get_global_turret_info(object *objp, model_subsystem *tp, vec3d *gpos, vec3d *gvec)
{
auto model_instance_num = Ships[objp->instance].model_instance_num;
if (gpos)
model_instance_find_world_point(gpos, &vmd_zero_vector, model_instance_num, tp->subobj_num, &objp->orient, &objp->pos);
if (gvec)
model_instance_find_world_dir(gvec, &tp->turret_norm, model_instance_num, tp->subobj_num, &objp->orient, true);
// vm_vec_unrotate(gpos, &tp->turret_avg_firing_point, &objp->orient);
vm_vec_unrotate(gpos, &tp->pnt, &objp->orient);
vm_vec_add2(gpos, &objp->pos);
vm_vec_unrotate(gvec, &tp->turret_norm, &objp->orient);
}
void turret_ai_update_aim(ai_info *aip, object *En_Objp, ship_subsys *ss);
@@ -1252,8 +1303,7 @@ void ship_get_global_turret_gun_info(object *objp, ship_subsys *ssp, vec3d *gpos
vm_vec_add2(&enemy_point, &ssp->last_aim_enemy_pos);
} else {
if ((lep->type == OBJ_SHIP) && (Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge())) {
// the turret norm here is from the perspective of the base submodel, not the gun submodel
model_instance_find_world_dir(&turret_norm, &tp->turret_norm, pm, pmi, pm->submodel[tp->subobj_num].parent, &objp->orient);
vm_vec_unrotate(&turret_norm, &tp->turret_norm, &objp->orient);
ai_big_pick_attack_point_turret(lep, ssp, &tmp_pos, &turret_norm, &enemy_point, MIN(wip->max_speed * wip->lifetime, wip->weapon_range), tp->turret_fov);
}
else {
@@ -1384,13 +1434,18 @@ int aifft_rotate_turret(object *objp, ship *shipp, ship_subsys *ss, object *lep,
in_fov = turret_fov_test(ss, gvec, &v2e);
if (ss->flags[Ship::Subsystem_Flags::FOV_edge_check]) {
if (in_fov == false)
in_fov = is_object_radius_in_turret_fov(&Objects[ss->turret_enemy_objnum], ss, gvec, &gun_pos, &v2e, predicted_enemy_pos, 0.0f);
}
if (in_fov) {
ret_val = model_rotate_gun(objp, pm, pmi, ss, predicted_enemy_pos);
ret_val = model_rotate_gun(objp, pm, pmi, tp, predicted_enemy_pos);
} else if ((tp->flags[Model::Subsystem_Flags::Turret_reset_idle]) &&(timestamp_elapsed(ss->rotation_timestamp))) {
ret_val = model_rotate_gun(objp, pm, pmi, ss, predicted_enemy_pos, true);
ret_val = model_rotate_gun(objp, pm, pmi, tp, predicted_enemy_pos, true);
}
} else if ((ss->system_info->flags[Model::Subsystem_Flags::Turret_reset_idle]) && (timestamp_elapsed(ss->rotation_timestamp))) {
ret_val = model_rotate_gun(objp, pm, pmi, ss, predicted_enemy_pos, true);
ret_val = model_rotate_gun(objp, pm, pmi, ss->system_info, predicted_enemy_pos, true);
}
// by default "ret_val" should be set to 1 for multi-part turrets, and 0 for single-part turrets
@@ -1422,7 +1477,7 @@ float aifft_compute_turret_dot(object *objp, object *enemy_objp, vec3d *abs_gunp
if (ship_subsystem_in_sight(enemy_objp, enemy_subsysp, abs_gunposp, &subobj_pos, 1, &dot_out, &vector_out)) {
vec3d turret_norm;
model_instance_find_world_dir(&turret_norm, &turret_subsysp->system_info->turret_norm, Ships[objp->instance].model_instance_num, turret_subsysp->system_info->subobj_num, &objp->orient, true);
vm_vec_unrotate(&turret_norm, &turret_subsysp->system_info->turret_norm, &objp->orient);
float dot_return = vm_vec_dot(&turret_norm, &vector_out);
if (Ai_info[Ships[objp->instance].ai_index].ai_profile_flags[AI::Profile_Flags::Smart_subsystem_targeting_for_turrets]) {
@@ -1492,7 +1547,8 @@ ship_subsys *aifft_find_turret_subsys(object *objp, ship_subsys *ssp, object *en
// Compute absolute gun position.
vec3d abs_gun_pos;
ship_get_global_turret_info(objp, ssp->system_info, &abs_gun_pos, nullptr);
vm_vec_unrotate(&abs_gun_pos, &ssp->system_info->pnt, &objp->orient);
vm_vec_add2(&abs_gun_pos, &objp->pos);
// Only pick a turret to attack on large ships.
if (!esip->is_big_or_huge())
@@ -1605,10 +1661,8 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
{
Assert(weapon_num < MAX_SHIP_WEAPONS);
float wait = 1000.0f;
// should subtract 1 from firepoints to match behavior of wip->burst_shots
int burst_shots = (wip->burst_flags[Weapon::Burst_Flags::Num_firepoints_burst_shots] ? turret->system_info->turret_num_firing_points - 1 : wip->burst_shots);
if (burst_shots > turret->weapons.burst_counter[weapon_num]) {
if (wip->burst_shots > turret->weapons.burst_counter[weapon_num]) {
wait *= wip->burst_delay;
turret->weapons.burst_counter[weapon_num]++;
} else {
@@ -1618,12 +1672,10 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
} else {
wait *= wip->fire_wait;
}
if ((burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Random_length])) {
turret->weapons.burst_counter[weapon_num] = Random::next(burst_shots);
turret->weapons.burst_seed[weapon_num] = Random::next();
if ((wip->burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Random_length])) {
turret->weapons.burst_counter[weapon_num] = (myrand() % wip->burst_shots);
} else {
turret->weapons.burst_counter[weapon_num] = 0;
turret->weapons.burst_seed[weapon_num] = Random::next();
}
}
@@ -1634,7 +1686,7 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
fs_dest = &turret->weapons.next_secondary_fire_stamp[weapon_num - MAX_SHIP_PRIMARY_BANKS];
//Check for the new cooldown flag
if(!((wip->wi_flags[Weapon::Info_Flags::Same_turret_cooldown]) || ((burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Fast_firing]))))
if(!((wip->wi_flags[Weapon::Info_Flags::Same_turret_cooldown]) || ((wip->burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Fast_firing]))))
{
// make side even for team vs. team
@@ -1717,7 +1769,7 @@ void turret_update_enemy_in_range(ship_subsys *turret, float seconds)
/**
* Fire a weapon from a turret
*/
bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, vec3d *turret_pos, vec3d *firing_vec, vec3d *predicted_pos = nullptr, float flak_range_override = 100.0f)
bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, vec3d *turret_pos, vec3d *turret_fvec, vec3d *predicted_pos = NULL, float flak_range_override = 100.0f)
{
matrix turret_orient;
int weapon_objnum;
@@ -1782,25 +1834,8 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
#endif
if (check_ok_to_fire(parent_objnum, turret->turret_enemy_objnum, wip, -1, turret)) {
ship_weapon* swp = &turret->weapons;
turret->turret_last_fire_direction = *firing_vec;
if (turret->turret_inaccuracy > 0.0f)
vm_vec_random_cone(firing_vec, firing_vec, turret->turret_inaccuracy);
vm_vector_2_matrix(&turret_orient, firing_vec, nullptr, nullptr);
// grab and set some burst data before turret_set_next_fire_timestamp wipes it
int old_burst_seed = swp->burst_seed[weapon_num];
int old_burst_counter = swp->burst_counter[weapon_num];
// only used by type 5 beams
if (turret->weapons.burst_counter[weapon_num] == 0) {
swp->per_burst_rot += wip->b_info.t5info.per_burst_rot;
if (swp->per_burst_rot > PI2)
swp->per_burst_rot -= PI2;
else if (swp->per_burst_rot < -PI2)
swp->per_burst_rot += PI2;
}
vm_vector_2_matrix(&turret_orient, turret_fvec, NULL, NULL);
turret->turret_last_fire_direction = *turret_fvec;
// set next fire timestamp for the turret
if (last_shot_in_salvo)
@@ -1826,10 +1861,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
else
fire_info.target_subsys = NULL;
fire_info.turret = turret;
fire_info.burst_seed = old_burst_seed;
fire_info.fire_method = BFM_TURRET_FIRED;
fire_info.per_burst_rotation = swp->per_burst_rot;
fire_info.burst_index = old_burst_counter;
// fire a beam weapon
weapon_objnum = beam_fire(&fire_info);
@@ -1840,11 +1872,9 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
parent_ship->last_fired_turret = turret;
turret->last_fired_weapon_info_index = turret_weapon_class;
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", nullptr, "Beam", objp, "Target", &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
@@ -1852,6 +1882,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
}
// don't fire swam, but set up swarm info instead
else if ((wip->wi_flags[Weapon::Info_Flags::Swarm]) || (wip->wi_flags[Weapon::Info_Flags::Corkscrew])) {
ship_weapon *swp = &turret->weapons;
if (swp->current_secondary_bank < 0) {
swp->current_secondary_bank = 0;
}
@@ -1877,6 +1908,8 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
else {
for (int i = 0; i < wip->shots; i++) {
if (turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) {
ship_weapon *swp;
swp = &turret->weapons;
int bank_to_fire, num_slots = turret->system_info->turret_num_firing_points;
if (wip->subtype == WP_LASER) {
int points;
@@ -1956,7 +1989,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
}
// zookeeper - Firepoints should cycle normally between shots,
// so we need to get the position info separately for each shot
ship_get_global_turret_gun_info(&Objects[parent_objnum], turret, turret_pos, firing_vec, 1, nullptr);
ship_get_global_turret_gun_info(&Objects[parent_objnum], turret, turret_pos, turret_fvec, 1, NULL);
weapon_objnum = weapon_create( turret_pos, &turret_orient, turret_weapon_class, parent_objnum, -1, 1, 0,0.0f, turret);
weapon_set_tracking_info(weapon_objnum, parent_objnum, turret->turret_enemy_objnum, 1, turret->targeted_subsys);
@@ -1974,14 +2007,14 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
// AL 1-6-97: Store pointer to turret subsystem
wp->turret_subsys = turret;
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
Script_system.SetHookObjects(4, "Ship", &Objects[parent_objnum], "Weapon", objp, "Beam", nullptr, "Target", &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]) {
if (wip->wi_flags[Weapon::Info_Flags::Flak]) {
// show a muzzle flash
flak_muzzle_flash(turret_pos, turret_fvec, &Objects[parent_ship->objnum].phys_info, turret_weapon_class);
if(predicted_pos != NULL)
{
@@ -1996,10 +2029,9 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum,
flak_set_range(objp, flak_range_override);
}
}
// do mflash if the weapon has it
if (wip->muzzle_flash >= 0) {
mflash_create(turret_pos, firing_vec, &Objects[parent_ship->objnum].phys_info, wip->muzzle_flash);
// otherwise just do mflash if the weapon has it
else if (wip->muzzle_flash >= 0) {
mflash_create(turret_pos, turret_fvec, &Objects[parent_ship->objnum].phys_info, wip->muzzle_flash);
}
// in multiplayer (and the master), then send a turret fired packet.
@@ -2092,11 +2124,9 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
Ships[Objects[tsi->parent_objnum].instance].last_fired_turret = tsi->turret;
tsi->turret->last_fired_weapon_info_index = tsi->weapon_class;
if (Script_system.IsActiveAction(CHA_ONTURRETFIRED)) {
Script_system.SetHookObjects(4, "Ship", &Objects[tsi->parent_objnum], "Weapon", &Objects[weapon_objnum], "Beam", nullptr, "Target", &Objects[tsi->turret->turret_enemy_objnum]);
Script_system.RunCondition(CHA_ONTURRETFIRED, &Objects[tsi->parent_objnum]);
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) {
@@ -2127,15 +2157,13 @@ int Num_find_turret_enemy = 0;
int Num_turrets_fired = 0;
/**
* Previously called ai_fire_from_turret()
* Given a ship and a turret subsystem, handle all its behavior, mostly targeting and shooting but also
* all turret movement and resetting when idle
* Given a turret tp and its parent parent_objnum, fire from the turret at its enemy.
*/
extern int Nebula_sec_range;
void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
{
float weapon_firing_range;
float weapon_min_range; // *Weapon minimum firing range -Et1
float WeaponMinRange; // *Weapon minimum firing range -Et1
vec3d v2e;
object *lep; // Last enemy pointer
model_subsystem *tp = ss->system_info;
@@ -2207,8 +2235,6 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
{
ss->turret_enemy_objnum = -1;
lep = nullptr;
ss->flags.remove(Ship::Subsystem_Flags::Forced_target);
ss->flags.remove(Ship::Subsystem_Flags::Forced_subsys_target);
}
Assert((shipp->objnum >= 0) && (shipp->objnum < MAX_OBJECTS));
@@ -2216,10 +2242,25 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
objp = &Objects[shipp->objnum];
Assert(objp->type == OBJ_SHIP);
// Wanderer - make sure turrets already have all the data
if ( !(tp->flags[Model::Subsystem_Flags::Turret_matrix]) )
{
if (!(tp->turret_gun_sobj == tp->subobj_num))
{
auto pmi = model_get_instance(shipp->model_instance_num);
auto pm = model_get(pmi->model_num);
model_make_turret_matrix(pm, pmi, tp);
}
}
// Use the turret info for all guns, not one gun in particular.
vec3d gvec, gpos;
ship_get_global_turret_info(objp, tp, &gpos, &gvec);
if (tp->flags[Model::Subsystem_Flags::Turret_alt_math]) {
vm_matrix_x_matrix( &ss->world_to_turret_matrix, &objp->orient, &tp->turret_matrix );
}
// Rotate the turret even if time hasn't elapsed, since it needs to turn to face its target.
int use_angles = aifft_rotate_turret(objp, shipp, ss, lep, &predicted_enemy_pos, &gvec);
@@ -2310,12 +2351,12 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
{
weapon_firing_range = wip->lssm_lock_range;
weapon_min_range = 0.0f;
WeaponMinRange = 0.0f;
}
else
{
weapon_firing_range = MIN(wip->lifetime * wip->max_speed, wip->weapon_range);
weapon_min_range = wip->weapon_min_range;
WeaponMinRange = wip->WeaponMinRange;
}
// if beam weapon in nebula and target not tagged, decrease firing range
@@ -2328,21 +2369,19 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
}
// Don't try to fire beyond weapon_limit_range (or within min range)
if (dist_to_enemy < weapon_min_range || dist_to_enemy > weapon_firing_range) {
if (dist_to_enemy < WeaponMinRange || dist_to_enemy > weapon_firing_range) {
// it's possible another weapon is in range, but if not,
// we will end up selecting a new target
continue;
}
ship_info* esip = lep->type != OBJ_SHIP ? nullptr : &Ship_info[Ships[lep->instance].ship_info_index];
// If targeted a small ship and have a huge weapon, don't fire. But this shouldn't happen, as a small ship should not get selected.
if ( wip->wi_flags[Weapon::Info_Flags::Huge] ) {
if ( lep->type != OBJ_SHIP ) {
tentative_return = true;
continue;
}
if ( esip->class_type >= 0 && !(Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
if ( !(Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge()) ) {
tentative_return = true;
continue;
}
@@ -2350,8 +2389,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
// Similar check for small weapons
if ( wip->wi_flags[Weapon::Info_Flags::Small_only] ) {
if ( (lep->type == OBJ_SHIP) && esip->class_type >= 0 &&
(Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only])) {
if ( (lep->type == OBJ_SHIP) && Ship_info[Ships[lep->instance].ship_info_index].is_big_or_huge() ) {
tentative_return = true;
continue;
}
@@ -2395,11 +2433,6 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
tentative_return = true;
continue;
}
if (!weapon_target_satisfies_lock_restrictions(wip, lep)) {
tentative_return = true;
continue;
}
}
else {
// check tagged-only for bombs only if the flag is set; see Mantis #3114
@@ -2420,10 +2453,8 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
//none of our guns can hit the enemy, so find a new enemy
if (num_valid == 0) {
if (!ss->flags[Ship::Subsystem_Flags::Forced_target]) {
ss->turret_enemy_objnum = -1;
ss->turret_time_enemy_in_range = 0.0f;
}
ss->turret_enemy_objnum = -1;
ss->turret_time_enemy_in_range = 0.0f;
// in the original code, we returned where the "tentative return" variable was set,
// so return for real now, since we don't have a reason to remain in the function
@@ -2431,16 +2462,8 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
return;
}
// if we've got Forced_subsys_target targeted_subsys *really* shouldnt be null but just in case...
if (ss->flags[Ship::Subsystem_Flags::Forced_subsys_target] && ss->targeted_subsys != nullptr) {
if (ss->targeted_subsys->current_hits <= 0.0f) {
ss->flags.remove(Ship::Subsystem_Flags::Forced_subsys_target);
ss->flags.remove(Ship::Subsystem_Flags::Forced_target);
}
}
// Maybe pick a new enemy, unless targeting has been taken over by scripting
if ( turret_should_pick_new_target(ss) && !ss->scripting_target_override && !(ss->flags[Ship::Subsystem_Flags::Forced_target])) {
if ( turret_should_pick_new_target(ss) && !ss->scripting_target_override ) {
Num_find_turret_enemy++;
int objnum = find_turret_enemy(ss, parent_objnum, &gpos, &gvec, ss->turret_enemy_objnum);
@@ -2713,7 +2736,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
{
// If nothing is OK to fire (lost track of the target?)
// reset the target (so we don't continue to track what we can't hit)
if (tp->flags[Model::Subsystem_Flags::Turret_only_target_if_can_fire] && !(ss->flags[Ship::Subsystem_Flags::Forced_target]))
if (tp->flags[Model::Subsystem_Flags::Turret_only_target_if_can_fire])
{
ss->turret_enemy_objnum = -1; // Reset enemy objnum, find a new one next frame.
ss->turret_time_enemy_in_range = 0.0f;
@@ -2748,7 +2771,7 @@ void ai_turret_execute_behavior(ship *shipp, ship_subsys *ss)
}
}
}
else if (!(ss->flags[Ship::Subsystem_Flags::Forced_target]))
else
{
// Lost him!
ss->turret_enemy_objnum = -1; // Reset enemy objnum, find a new one next frame.
@@ -2771,20 +2794,16 @@ bool turret_adv_fov_test(ship_subsys *ss, vec3d *gvec, vec3d *v2e, float size_mo
model_subsystem *tp = ss->system_info;
float dot = vm_vec_dot(v2e, gvec);
if (((dot + size_mod) >= tp->turret_fov) && ((dot - size_mod) <= tp->turret_max_fov)) {
object* objp = &Objects[ss->parent_objnum];
matrix turret_matrix;
turret_instance_find_world_orient(&turret_matrix, Ships[objp->instance].model_instance_num, tp->subobj_num, &objp->orient);
vec3d of_dst;
vm_vec_rotate(&of_dst, v2e, &turret_matrix);
if ((of_dst.xyz.x == 0) && (of_dst.xyz.z == 0))
return true;
else {
of_dst.xyz.y = 0;
vm_vec_rotate( &of_dst, v2e, &ss->world_to_turret_matrix );
if ((of_dst.xyz.x == 0) && (of_dst.xyz.y == 0)) {
return true;
} else {
of_dst.xyz.z = 0;
if (!IS_VEC_NULL_SQ_SAFE(&of_dst)) {
vm_vec_normalize(&of_dst);
// now we have 2d vector with lenght of 1 that points at the targets direction after being rotated to turrets FOR
if ((of_dst.xyz.z + size_mod) >= tp->turret_base_fov)
if ((-of_dst.xyz.y + size_mod) >= tp->turret_y_fov)
return true;
}
}
@@ -2795,7 +2814,7 @@ bool turret_adv_fov_test(ship_subsys *ss, vec3d *gvec, vec3d *v2e, float size_mo
bool turret_fov_test(ship_subsys *ss, vec3d *gvec, vec3d *v2e, float size_mod)
{
bool in_fov = false;
if (ss->system_info->flags[Model::Subsystem_Flags::Turret_restricted_fov])
if (ss->system_info->flags[Model::Subsystem_Flags::Turret_alt_math])
in_fov = turret_adv_fov_test(ss, gvec, v2e, size_mod);
else
in_fov = turret_std_fov_test(ss, gvec, v2e, size_mod);
+139 -132
View File
@@ -38,6 +38,7 @@
#include "ship/ship.h"
#include "ship/shipfx.h"
#include "ship/shiphit.h"
#include "species_defs/species_defs.h"
#include "stats/scoring.h"
#include "weapon/weapon.h"
@@ -49,9 +50,9 @@ asteroid_obj Asteroid_objs[MAX_ASTEROID_OBJS]; // array used to store asteroid o
asteroid_obj Asteroid_obj_list; // head of linked list of asteroid_obj structs
// used for randomly generating debris type when there are multiple sizes.
const float SMALL_DEBRIS_WEIGHT = 8.0f;
const float MEDIUM_DEBRIS_WEIGHT = 4.0f;
const float LARGE_DEBRIS_WEIGHT = 1.0f;
#define SMALL_DEBRIS_WEIGHT 8
#define MEDIUM_DEBRIS_WEIGHT 4
#define LARGE_DEBRIS_WEIGHT 1
int Asteroids_enabled = 1;
int Num_asteroids = 0;
@@ -256,7 +257,7 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi
return NULL;
}
if((asteroid_type < 0) || (asteroid_type >= (int)Asteroid_info.size())) {
if((asteroid_type < 0) || (asteroid_type >= (((int)Species_info.size() + 1) * NUM_DEBRIS_SIZES))) {
return NULL;
}
@@ -324,7 +325,7 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi
asteroid_default_flagset += Object::Object_Flags::Physics;
asteroid_default_flagset += Object::Object_Flags::Collides;
objnum = obj_create(OBJ_ASTEROID, -1, n, &orient, &pos, radius, asteroid_default_flagset, false);
objnum = obj_create(OBJ_ASTEROID, -1, n, &orient, &pos, radius, asteroid_default_flagset);
if ( (objnum == -1) || (objnum >= MAX_OBJECTS) ) {
mprintf(("Couldn't create asteroid -- out of object slots\n"));
@@ -487,6 +488,31 @@ static void asteroid_load(int asteroid_info_index, int asteroid_subtype)
}
}
/**
* Returns a weight that depends on asteroid size.
*
* The weight is then used to determine the frequencty of different sizes of ship debris
*/
static int get_debris_weight(int ship_debris_index)
{
int size = ship_debris_index % NUM_DEBRIS_SIZES;
switch (size)
{
case ASTEROID_TYPE_SMALL:
return SMALL_DEBRIS_WEIGHT;
case ASTEROID_TYPE_MEDIUM:
return MEDIUM_DEBRIS_WEIGHT;
case ASTEROID_TYPE_LARGE:
return LARGE_DEBRIS_WEIGHT;
default:
Int3();
return 1;
}
}
/**
* Create all the asteroids for the mission
*/
@@ -496,14 +522,14 @@ void asteroid_create_all()
// ship_debris_odds_table keeps track of debris type of the next debris piece
// each different type (size) of debris piece has a diffenent weight, smaller weighted more heavily than larger.
// choose next type from table ship_debris_odds_table by Random::next()%max_weighted_range,
// choose next type from table ship_debris_odds_table by rand()%max_weighted_range,
// the threshold *below* which the debris type is selected.
struct {
float random_threshold;
int random_threshold;
int debris_type;
} ship_debris_odds_table[MAX_ACTIVE_DEBRIS_TYPES];
float max_weighted_range = 0.0f;
int max_weighted_range = 0;
if (!Asteroids_enabled)
return;
@@ -526,7 +552,7 @@ void asteroid_create_all()
// Calculate the odds table
for (idx=0; idx<num_debris_types; idx++) {
float debris_weight = Asteroid_info[Asteroid_field.field_debris_type[idx]].spawn_weight;
int debris_weight = get_debris_weight(Asteroid_field.field_debris_type[idx]);
ship_debris_odds_table[idx].random_threshold = max_weighted_range + debris_weight;
ship_debris_odds_table[idx].debris_type = Asteroid_field.field_debris_type[idx];
max_weighted_range += debris_weight;
@@ -564,7 +590,7 @@ void asteroid_create_all()
// For asteroid, load only large asteroids
// get a valid subtype
int subtype = Random::next(NUM_DEBRIS_POFS);
int subtype = rand() % NUM_DEBRIS_POFS;
while (Asteroid_field.field_debris_type[subtype] == -1) {
subtype = (subtype + 1) % NUM_DEBRIS_POFS;
}
@@ -573,7 +599,7 @@ void asteroid_create_all()
} else {
Assert(num_debris_types > 0);
float rand_choice = frand() * max_weighted_range;
int rand_choice = rand() % max_weighted_range;
for (idx=0; idx<MAX_ACTIVE_DEBRIS_TYPES; idx++) {
// for ship debris, choose type according to odds table
@@ -758,7 +784,7 @@ static void maybe_throw_asteroid(int count)
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 = Random::next(NUM_DEBRIS_POFS);
int subtype = rand() % NUM_DEBRIS_POFS;
while (Asteroid_field.field_debris_type[subtype] == -1) {
subtype = (subtype + 1) % NUM_DEBRIS_POFS;
}
@@ -1019,13 +1045,13 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
smi->collision_checked = false;
// set angles for last frame (need to set to prev to get p0)
matrix copy_matrix = smi->canonical_orient;
angles copy_angles = smi->angs;
// find the start and end positions of the sphere in submodel RF
smi->canonical_orient = smi->canonical_prev_orient;
smi->angs = smi->prev_angs;
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
smi->canonical_orient = copy_matrix;
smi->angs = copy_angles;
world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
mc.p0 = &p0;
@@ -1043,7 +1069,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
// set up asteroid_hit_info for rotating submodel
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);
model_instance_find_obj_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
@@ -1075,7 +1101,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
// get collision normal if not edge hit
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);
model_instance_find_obj_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
@@ -1324,7 +1350,7 @@ static void asteroid_do_area_effect(object *asteroid_objp)
if ( weapon_area_calc_damage(ship_objp, &asteroid_objp->pos, asip->inner_rad, asip->outer_rad, asip->blast, asip->damage, &blast, &damage, asip->outer_rad) == -1 )
continue;
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage, asip->damage_type_idx);
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage );
weapon_area_apply_blast(NULL, ship_objp, &asteroid_objp->pos, blast, 0);
} // end for
}
@@ -1432,14 +1458,9 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
if ( timestamp_elapsed(asp->final_death_time) ) {
vec3d relvec, vfh, tvec;
bool skip = false;
bool hooked = 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);
@@ -1489,7 +1510,7 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
int num_roids_var = split->max - split->min;
if (num_roids_var > 0)
num_roids += Random::next(num_roids_var);
num_roids += rand() % num_roids_var;
if (num_roids > 0)
for (int i=0; i<num_roids; i++)
@@ -1544,10 +1565,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");
}
}
@@ -1769,7 +1788,7 @@ static void asteroid_parse_section(asteroid_info *asip)
}
required_string("$Detail distance:");
asip->num_detail_levels = (int)stuff_int_list(asip->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, RAW_INTEGER_TYPE);
asip->num_detail_levels = stuff_int_list(asip->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, RAW_INTEGER_TYPE);
required_string("$Max Speed:");
stuff_float(&asip->max_speed);
@@ -1783,7 +1802,7 @@ static void asteroid_parse_section(asteroid_info *asip)
if(optional_string("$Explosion Animations:")){
int temp[MAX_FIREBALL_TYPES];
auto parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
int parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
asip->explosion_bitmap_anims.clear();
asip->explosion_bitmap_anims.insert(asip->explosion_bitmap_anims.begin(), temp, temp+parsed_ints);
}
@@ -1830,81 +1849,20 @@ static void asteroid_parse_section(asteroid_info *asip)
} else
Warning(LOCATION, "Invalid asteroid reference %i used for $Split in asteroids table, ignoring.", split_type);
}
if (optional_string("$Spawn Weight:")) {
stuff_float(&asip->spawn_weight);
if (asip->spawn_weight <= 0.0f) {
Warning(LOCATION, "Spawn weight for asteroid '%s' must be greater than 0", asip->name);
asip->spawn_weight = 1.0f;
}
} else {
switch (Asteroid_info.size() % NUM_DEBRIS_SIZES)
{
case ASTEROID_TYPE_SMALL:
asip->spawn_weight = SMALL_DEBRIS_WEIGHT;
break;
case ASTEROID_TYPE_MEDIUM:
asip->spawn_weight = MEDIUM_DEBRIS_WEIGHT;
break;
case ASTEROID_TYPE_LARGE:
asip->spawn_weight = LARGE_DEBRIS_WEIGHT;
break;
default:
UNREACHABLE("Here be dragons");
}
}
}
// changes the name to "[species] Debris" if it had a name like "[species] debris #"
void maybe_change_asteroid_name(asteroid_info* asip) {
SCP_string name = asip->name;
size_t split = std::string::npos;
for (species_info species : Species_info) {
if (name.compare(0, strlen(species.species_name), species.species_name) == 0) {
split = strlen(species.species_name);
break;
}
}
if (split == std::string::npos)
return;
SCP_string remaining_name = name.substr(split + 1, name.length());
split = remaining_name.find(' ');
if (split == std::string::npos)
return;
SCP_string debris = remaining_name.substr(0, split);
SCP_string num = remaining_name.substr(split + 1, remaining_name.length());
if (stricmp(debris.c_str(), "Debris") != 0)
return;
if (num.empty() || std::find_if(num.begin(), num.end(), [](char c) {
return !std::isdigit(c, SCP_default_locale);
}) != num.end())
return;
// make sure this asteroid would correspond the 'species section' of the old style retail asteroids
if (Asteroid_info.size() < NUM_DEBRIS_SIZES + Species_info.size() * NUM_DEBRIS_SIZES && Asteroid_info.size() >= NUM_DEBRIS_SIZES) {
int idx = (int)(Asteroid_info.size()) / NUM_DEBRIS_SIZES - 1;
strcpy_s(asip->name, Species_info[idx].species_name);
strcat(asip->name, " ");
strcat(asip->name, XSTR("debris", 348));
}
}
/**
Read in data from asteroid.tbl into Asteroid_info[] array.
After this function is complete, the Asteroid_info[] array
will have at least 3 generic asteroids plus whatever else
special ones were added in the table. The generic asteroids are
a set of small, medium, and large asteroids in that exact order.
After this function is completes, the Asteroid_info[] array will
contain exactly three asteroids per species plus exactly three
generic asteroids. These three asteroids are a set
of small, medium, and large asteroids in that exact order.
If asteroid.tbl contains too many or too few asteroids as per
the rules above this function will abort the engine with an
error message stating the number expected and it will write
to the debug.log how everything was parsed.
Note that by saying "asteroid" this code is talking about
the asteroids and the debris that make up asteroid fields
@@ -1916,6 +1874,12 @@ that is being protected.
static void asteroid_parse_tbl()
{
char impact_ani_file[MAX_FILENAME_LEN];
// How did we get here without having any species defined?
Assertion(!Species_info.empty(),
"Cannot parse asteroids/debris if there "
"are no species for them to belong to."
);
try
{
@@ -1925,6 +1889,8 @@ static void asteroid_parse_tbl()
required_string("#Asteroid Types");
size_t tally = 0;
size_t max_asteroids =
NUM_DEBRIS_SIZES + Species_info.size() * NUM_DEBRIS_SIZES;
#ifndef NDEBUG
SCP_vector<SCP_string> parsed_asteroids;
@@ -1937,41 +1903,82 @@ static void asteroid_parse_tbl()
asteroid_parse_section(&new_asteroid);
maybe_change_asteroid_name(&new_asteroid);
#ifndef NDEBUG
SCP_string msg;
msg.append("Parsing asteroid: '");
msg.append(new_asteroid.name);
msg.append("' as a '");
msg.append((tally >= NUM_DEBRIS_SIZES) ? "generic" : "special");
msg.append("'");
switch (tally % NUM_DEBRIS_SIZES) {
case ASTEROID_TYPE_SMALL:
msg.append(" small\n");
break;
case ASTEROID_TYPE_MEDIUM:
msg.append(" medium\n");
break;
case ASTEROID_TYPE_LARGE:
msg.append(" large\n");
break;
default:
Error(LOCATION, "Get a coder! Math has broken!\n"
"Important numbers:\n"
"\ttally: " SIZE_T_ARG "\n"
"\tNUM_DEBRIS_SIZES: %d\n",
tally, NUM_DEBRIS_SIZES
);
msg.append(" unknown\n");
}
parsed_asteroids.push_back(msg);
size_t species = tally / NUM_DEBRIS_SIZES;
if (tally >= max_asteroids)
{
#ifdef NDEBUG
// Bump the warning count in release so they get something
// even if the message never gets displayed.
Warning(LOCATION, "Ignoring extra asteroid/debris");
#else
SCP_string msg("Ignoring extra asteroid/debris '");
msg.append(new_asteroid.name);
msg.append("'\n");
Warning(LOCATION, "%s", msg.c_str());
parsed_asteroids.push_back(msg);
#endif
Asteroid_info.push_back(new_asteroid);
}
else
{
#ifndef NDEBUG
SCP_string msg;
msg.append("Parsing asteroid: '");
msg.append(new_asteroid.name);
msg.append("' as a '");
msg.append((species == 0) ? "generic" : Species_info[species - 1].species_name);
msg.append("'");
switch (tally % NUM_DEBRIS_SIZES) {
case ASTEROID_TYPE_SMALL:
msg.append(" small\n");
break;
case ASTEROID_TYPE_MEDIUM:
msg.append(" medium\n");
break;
case ASTEROID_TYPE_LARGE:
msg.append(" large\n");
break;
default:
Error(LOCATION, "Get a coder! Math has broken!\n"
"Important numbers:\n"
"\ttally: " SIZE_T_ARG "\n"
"\tNUM_DEBRIS_SIZES: %d\n",
tally, NUM_DEBRIS_SIZES
);
msg.append(" unknown\n");
}
parsed_asteroids.push_back(msg);
#endif
Asteroid_info.push_back(new_asteroid);
}
tally++;
}
required_string("#End");
if (tally != max_asteroids)
{
#ifndef NDEBUG
mprintf(("Asteroid.tbl as parsed:\n"));
for (SCP_vector<SCP_string>::iterator iter = parsed_asteroids.begin();
iter != parsed_asteroids.end(); ++iter)
{
mprintf(("%s\n", iter->c_str()));
}
#endif
Error(LOCATION,
"Found " SIZE_T_ARG " asteroids/debris when " SIZE_T_ARG " expected\n\n"
"<Number expected> = <Number of species> * %d + %d generic asteroids\n"
SIZE_T_ARG " = " SIZE_T_ARG "*%d + %d\n\n"
#ifdef NDEBUG
"Run a debug build to see a list of all parsed asteroids\n",
#else
"See the debug.log for a listing of all parsed asteroids\n",
#endif
tally, max_asteroids,
NUM_DEBRIS_SIZES, NUM_DEBRIS_SIZES,
max_asteroids, Species_info.size(), NUM_DEBRIS_SIZES, NUM_DEBRIS_SIZES
);
}
Asteroid_impact_explosion_ani = -1;
required_string("$Impact Explosion:");
stuff_string(impact_ani_file, F_NAME, MAX_FILENAME_LEN);
-2
View File
@@ -73,8 +73,6 @@ public:
int model_num[NUM_DEBRIS_POFS];
SCP_vector<int> explosion_bitmap_anims;
float fireball_radius_multiplier; // the model radius is multiplied by this to determine the fireball size
SCP_string display_name; // only used for hud targeting display and for 'ship' asteroids
float spawn_weight; // ship asteroids only, relative proportion to spawn compared to other types in its asteroid field
asteroid_info( )
: num_detail_levels( 0 ), max_speed( 0 ), damage_type_idx( 0 ),
+2 -3
View File
@@ -716,7 +716,7 @@ bool StartAutopilot()
vm_vec_scale(&pos, 5*speed_cap*tc_factor); // pos is now scaled by 5 times the speed (5 seconds ahead)
vm_vec_add(&pos, &pos, &Autopilot_flight_leader->pos); // pos is now 5*speed cap in front of player ship
switch (Random::next(24))
switch (myrand()%24)
// 8 different ways of getting perp points
// 4 of which will not be used when capships are present (anything below, or straight above)
{
@@ -795,8 +795,7 @@ bool StartAutopilot()
vm_vec_scale(&perp, Autopilot_flight_leader->radius+radius);
// randomly scale up/down by up to 20%
// jg18 - original comment said [-20,20] but preserving existing behavior
j = Random::next(-19, 20);
j = 20-myrand()%40; // [-20,20]
vm_vec_scale(&perp, 1.0f+(float(j)/100.0f));
vm_vec_add(&cameraPos, &pos, &perp);
+5 -7
View File
@@ -287,7 +287,7 @@ static int _cfile_chdir(const char *new_dir, const char *cur_dir __UNUSED)
const char *colon = strchr(new_dir, ':');
if (colon) {
if (!cfile_chdrive(SCP_tolower(*(colon - 1)) - 'a' + 1, 1))
if (!cfile_chdrive(tolower(*(colon - 1)) - 'a' + 1, 1))
return 1;
path = colon + 1;
@@ -305,7 +305,7 @@ static int _cfile_chdir(const char *new_dir, const char *cur_dir __UNUSED)
status = _chdir(path);
if (status != 0) {
#ifdef _WIN32
cfile_chdrive(SCP_tolower(cur_dir[0]) - 'a' + 1, 1);
cfile_chdrive(tolower(cur_dir[0]) - 'a' + 1, 1);
#endif /* _WIN32 */
return 2;
}
@@ -866,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;
}
}
@@ -922,7 +921,7 @@ int cfclose( CFILE * cfile )
} else {
// VP do nothing
}
cf_clear_compression_info(cfile);
cfile->type = CFILE_BLOCK_UNUSED;
return result;
}
@@ -974,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;
}
}
@@ -1009,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;
}
@@ -1053,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) {
+8 -47
View File
@@ -24,7 +24,6 @@
#include "cfile/cfile.h"
#include "cfile/cfilearchive.h"
#include "cfile/cfilecompression.h"
#include "luaconf.h"
#include <sstream>
@@ -34,6 +33,7 @@
#define CHECK_POSITION
// Called once to setup the low-level reading code.
void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size, size_t pos )
{
Assert(cfile != nullptr);
@@ -54,33 +54,6 @@ void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size,
}
}
//This function checks the file header to see if the file is compressed, if so it fills the correct compression info.
void cf_check_compression(CFILE* cfile)
{
if (cfile->size <= 16)
return;
int header=cfread_int(cfile);
cfseek(cfile, 0, SEEK_SET);
if (comp_check_header(header) == COMP_HEADER_MATCH)
comp_create_ci(cfile, header);
}
//This function is called to cleanup compression info when the cfile handle is reused.
void cf_clear_compression_info(CFILE* cfile)
{
if (cfile->compression_info.header != 0)
{
free(cfile->compression_info.offsets);
free(cfile->compression_info.decoder_buffer);
cfile->compression_info.offsets = nullptr;
cfile->compression_info.decoder_buffer = nullptr;
cfile->compression_info.header = 0;
cfile->compression_info.block_size = 0;
cfile->compression_info.last_decoded_block_pos = 0;
cfile->compression_info.last_decoded_block_bytes = 0;
cfile->compression_info.num_offsets = 0;
}
}
// cfeof() Tests for end-of-file on a stream
@@ -88,11 +61,11 @@ void cf_clear_compression_info(CFILE* cfile)
// returns a nonzero value after the first read operation that attempts to read
// past the end of the file. It returns 0 if the current position is not end of file.
// There is no error return.
int cfeof(CFILE *cfile)
{
Assert(cfile != NULL);
if (cfile->compression_info.header != 0)
return comp_feof(cfile);
int result = 0;
#if defined(CHECK_POSITION) && !defined(NDEBUG)
@@ -119,8 +92,6 @@ int cfeof(CFILE *cfile)
int cftell( CFILE * cfile )
{
Assert(cfile != NULL);
if (cfile->compression_info.header != 0)
return (int)comp_ftell(cfile);
#if defined(CHECK_POSITION) && !defined(NDEBUG)
if (cfile->fp) {
@@ -146,9 +117,6 @@ int cfseek( CFILE *cfile, int offset, int where )
Assert(cfile != NULL);
if (cfile->compression_info.header != 0)
return comp_fseek(cfile, offset, where);
// TODO: seek to offset in memory mapped file
Assert( !cfile->mem_mapped );
@@ -234,31 +202,23 @@ int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
// This is a file from memory
bytes_read = size;
memcpy(buf, reinterpret_cast<const char*>(cfile->data) + cfile->raw_position, size);
} else if (cfile->compression_info.header != 0) {
bytes_read = comp_fread(cfile, reinterpret_cast<char*>(buf),size);
if (bytes_read != size)
{
mprintf(("\nFile: %s decompression error. Result was: %d expected: %d\n", cfile->original_filename.c_str(), (int)bytes_read, (int)size));
Assertion(bytes_read == size, "File decompression error!");
}
} else {
bytes_read = fread(buf, 1, size, cfile->fp);
bytes_read = fread( buf, 1, size, cfile->fp );
}
if ( bytes_read > 0 ) {
cfile->raw_position += bytes_read;
Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!");
}
#if defined(CHECK_POSITION) && !defined(NDEBUG)
//Raw position is not the fp position with compressed files
if (cfile->fp && cfile->compression_info.header == 0) {
if (cfile->fp) {
auto tmp_offset = ftell(cfile->fp) - cfile->lib_offset;
Assert(tmp_offset == cfile->raw_position);
Assert(tmp_offset==cfile->raw_position);
}
#endif
return (int)(bytes_read / elsize);
}
int cfread_lua_number(double *buf, CFILE *cfile)
@@ -306,3 +266,4 @@ int cfread_lua_number(double *buf, CFILE *cfile)
return items_read;
}
+2 -19
View File
@@ -7,6 +7,7 @@
*
*/
#ifndef _CFILEARCHIVE_H
#define _CFILEARCHIVE_H
@@ -22,17 +23,6 @@
#define CFILE_BLOCK_UNUSED 0
#define CFILE_BLOCK_USED 1
struct COMPRESSION_INFO {
int header = 0;
size_t compressed_size = 0;
int block_size = 0;
int num_offsets = 0;
int* offsets = nullptr;
char* decoder_buffer = nullptr;
int last_decoded_block_pos = 0;
int last_decoded_block_bytes = 0;
};
struct CFILE {
int type = CFILE_BLOCK_UNUSED; // CFILE_BLOCK_UNUSED, CFILE_BLOCK_USED
int dir_type; // directory location
@@ -54,19 +44,12 @@ struct CFILE {
SCP_string original_filename;
const char* source_file;
int line_num;
COMPRESSION_INFO compression_info;
};
#define MAX_CFILE_BLOCKS 64
extern std::array<CFILE, MAX_CFILE_BLOCKS> Cfile_block_list;
// Called once to setup the low-level reading code.
void cf_init_lowlevel_read_code(CFILE* cfile, size_t lib_offset, size_t size, size_t pos);
// This checks if the file is compressed or not, and creates the proper compression info if so.
void cf_check_compression(CFILE* cfile);
// Used to clear compression info data and free dynamic memory used by compression
void cf_clear_compression_info(CFILE* cfile);
void cf_init_lowlevel_read_code( CFILE * cfile, size_t lib_offset, size_t size, size_t pos );
#endif
-206
View File
@@ -1,206 +0,0 @@
#define _CFILE_INTERNAL
#include <cstdlib>
#include <cstring>
#include <cstdio>
#ifdef _WIN32
#include <io.h>
#include <direct.h>
#include <windows.h>
#include <winbase.h>
#endif
#include "lz4.h"
#include "cfilecompression.h"
#include "cfilearchive.h"
/*INTERNAL FUNCTIONS*/
/*LZ41*/
void lz41_load_offsets(CFILE* cf);
size_t lz41_stream_random_access(CFILE* cf, char* bytes_out, size_t offset, size_t length);
void lz41_create_ci(CFILE* cf, int header);
/*MISC*/
int fso_fseek(CFILE* cfile, int offset, int where);
/*END OF INTERNAL FUNCTIONS*/
int comp_check_header(int header)
{
if (LZ41_FILE_HEADER == header)
return COMP_HEADER_MATCH;
return COMP_HEADER_MISMATCH;
}
void comp_create_ci(CFILE* cf, int header)
{
//mprintf(("(CI)Compressed File Opened: %s \n", cf->original_filename.c_str()));
if (LZ41_FILE_HEADER == header)
lz41_create_ci(cf, header);
//mprintf(("(CI)Uncompressed FileSize: %d \n", cf->size));
//mprintf(("(CI)Compressed FileSize: %d \n", cf->compression_info.compressed_size));
//mprintf(("(CI)Block Size: %d \n", cf->compression_info.block_size));
}
size_t comp_fread(CFILE* cf, char* buffer, size_t length)
{
/* Check the request to be at least 1 byte */
Assertion(length > 0, "Invalid length requested.");
/* Check that we are not requesting to read beyond end of file */
Assertion(cf->raw_position + length <= cf->size, "Invalid length requested.");
if (LZ41_FILE_HEADER == cf->compression_info.header)
return lz41_stream_random_access(cf, buffer, cf->raw_position, length);
return 0;
}
size_t comp_ftell(CFILE* cf)
{
return cf->raw_position;
}
int comp_feof(CFILE* cf)
{
if (cf->raw_position >= cf->size)
return 1;
else
return 0;
}
int comp_fseek(CFILE* cf, int offset, int where)
{
size_t goal_position;
switch (where) {
case SEEK_SET: goal_position = offset; break;
case SEEK_CUR: goal_position = cf->raw_position + offset; break;
case SEEK_END: goal_position = cf->size + offset; break;
default: return 1;
}
// Make sure we don't seek beyond the end of the file
CAP(goal_position,(size_t)0, cf->size);
cf->raw_position = goal_position;
return 0;
}
//Special fseek for compressed files handled by FSO, only SEEK_SET and SEEK_END is supported.
int fso_fseek(CFILE* cfile, int offset, int where)
{
size_t goal_position;
switch (where) {
case SEEK_SET: goal_position = offset + cfile->lib_offset; break;
case SEEK_END: goal_position = cfile->compression_info.compressed_size + offset + cfile->lib_offset; break;
default: return 0;
}
// Make sure we don't seek beyond the end of the file
CAP(goal_position, cfile->lib_offset, cfile->lib_offset + cfile->compression_info.compressed_size);
int result = 0;
if (cfile->fp)
{
result = fseek(cfile->fp, (long)goal_position, SEEK_SET);
Assertion(goal_position >= cfile->lib_offset, "Invalid offset values detected while seeking! Goal was " SIZE_T_ARG ", lib_offset is " SIZE_T_ARG ".", goal_position, cfile->lib_offset);
}
return result;
}
void lz41_create_ci(CFILE* cf, int header)
{
cf->compression_info.header = header;
cf->compression_info.compressed_size = cf->size;
fso_fseek(cf, (int)sizeof(int)*-3, SEEK_END);
auto fNumoffsets = fread(&cf->compression_info.num_offsets, sizeof(int), 1, cf->fp);
auto fSize = fread(&cf->size, sizeof(int), 1, cf->fp);
auto fBsize = fread(&cf->compression_info.block_size, sizeof(int), 1, cf->fp);
Assertion(cf->compression_info.num_offsets > 0, "Invalid number of offsets, compressed file is possibly in the wrong format or corrupted.");
#if !defined(NDEBUG)
Assertion(cf->size > 4, "Invalid filesize, compressed file is possibly in the wrong format or corrupted.");
Assertion(cf->compression_info.block_size > 16, "Invalid block size, compressed file is possibly in the wrong format or corrupted.");
Assertion(fNumoffsets == 1, "Error while reading the number of offsets, compressed file is possibly in the wrong format or corrupted.");
Assertion(fSize == 1, "Error while reading original filesize, compressed file is possibly in the wrong format or corrupted.");
Assertion(fBsize == 1, "Error while reading block size, compressed file is possibly in the wrong format or corrupted.");
#endif
cf->compression_info.decoder_buffer = (char*)malloc(LZ4_compressBound(cf->compression_info.block_size));
cf->compression_info.last_decoded_block_pos = 0;
cf->compression_info.last_decoded_block_bytes = 0;
lz41_load_offsets(cf);
}
void lz41_load_offsets(CFILE* cf)
{
cf->compression_info.offsets = (int*)malloc(cf->compression_info.num_offsets * sizeof(int));
int block;
int* offsets_ptr = cf->compression_info.offsets;
/* Seek to the first offset position, remember to consider the trailing ints */
fso_fseek(cf, ( ( sizeof(int) * cf->compression_info.num_offsets ) * -1 ) - (sizeof(int)*3 ), SEEK_END);
for (block = 0; block < cf->compression_info.num_offsets; ++block)
{
auto bytes_read = fread(offsets_ptr++, sizeof(int), 1, cf->fp);
Assertion(bytes_read == 1, "Error reading offset list.");
}
}
size_t lz41_stream_random_access(CFILE* cf, char* bytes_out, size_t offset, size_t length)
{
LZ4_streamDecode_t lz4_stream_decode_body;
LZ4_streamDecode_t* lz4_stream_decode = &lz4_stream_decode_body;
/* The blocks (current_block to end_block) contain the data we want */
size_t current_block = offset / cf->compression_info.block_size;
size_t end_block = ((offset + length - 1) / cf->compression_info.block_size) + 1;
size_t written_bytes = 0;
if (cf->compression_info.num_offsets <= (int)end_block)
return (size_t)LZ41_OFFSETS_MISMATCH;
/* Seek to the first block to read, if it matches the cached block, search the next one instead */
if (cf->compression_info.last_decoded_block_pos == cf->compression_info.offsets[current_block] && current_block + 1 <= end_block)
fso_fseek(cf, cf->compression_info.offsets[current_block+1], SEEK_SET);
else
fso_fseek(cf, cf->compression_info.offsets[current_block], SEEK_SET);
offset = offset % cf->compression_info.block_size;
char* cmp_buf = (char*)malloc(LZ4_compressBound(cf->compression_info.block_size));
/* Start decoding */
for (; current_block < end_block; ++current_block)
{
/* Only read and decode if the requested block is not the cached one */
if (cf->compression_info.last_decoded_block_pos != cf->compression_info.offsets[current_block])
{
cf->compression_info.last_decoded_block_pos = cf->compression_info.offsets[current_block];
/* The difference in offsets is the size of the block */
int cmp_bytes = cf->compression_info.offsets[current_block + 1] - cf->compression_info.offsets[current_block];
auto bytes_read = fread(cmp_buf, cmp_bytes, 1, cf->fp);
Assertion(bytes_read == 1, "Error reading from compressed file.");
cf->compression_info.last_decoded_block_bytes = LZ4_decompress_safe_continue(lz4_stream_decode, cmp_buf, cf->compression_info.decoder_buffer, cmp_bytes, cf->compression_info.block_size);
if (cf->compression_info.last_decoded_block_bytes <= 0)
{
free(cmp_buf);
return (size_t)LZ41_DECOMPRESSION_ERROR;
}
}
/* Write out the part of the data we care about from buffer */
size_t block_length = (length < (cf->compression_info.last_decoded_block_bytes - offset) ? length : (cf->compression_info.last_decoded_block_bytes - offset));
memcpy(bytes_out + written_bytes, cf->compression_info.decoder_buffer + offset, (size_t)block_length);
written_bytes += block_length;
offset = 0;
length -= block_length;
}
free(cmp_buf);
return written_bytes;
}
-66
View File
@@ -1,66 +0,0 @@
/*
ShivanSpS - Compressed files support for FSO. Many thanks to ngld, taylor and everyone elsewho helped me in getting this done.
-The file header is a version, this is used to tell FSO how to decompress that file, always use 4 chars to mantain alignment.
-The header ID can be used to add diferent revisions to LZ41 decompression system or to add other compression format supports whiout breaking compatibility.
-The system uses a offset list to record the position of every block in file, this list, along with the number of offsets, original filesize,
and block size, must be written by the compressor app.
-A decoder cache is used to store the last decoded block, this ensures each block is read and decoded only once in sequential reads. This cache
is a little bit bigger than the block size, a higher block size means less overhead added to the file, but it also means a little more ram will
be used during decompression.
-All this dynamic memory is assigned at cfopen() and it is cleared on cfclose().
................................char[4]..........(n ints)...(int)..........(int)..........(int)
-COMPRESSED FILE DATA STUCTURE: HEADER|N BLOCKS|N OFFSETS|NUM_OFFSETS|ORIGINAL_FILESIZE|BLOCK_SIZE
*/
#ifndef _CFILECOMPRESSION_H
#define _CFILECOMPRESSION_H
struct CFILE;
/*LZ41*/
#define LZ41_FILE_HEADER 0x31345A4C // "14ZL" -> "LZ41", inverted
#define LZ41_DECOMPRESSION_ERROR -1
#define LZ41_MAX_BLOCKS_OVERFLOW -2
#define LZ41_HEADER_MISMATCH -3
#define LZ41_OFFSETS_MISMATCH -4
/******/
#define COMP_HEADER_MATCH 1
#define COMP_HEADER_MISMATCH 0
/*
Returns COMP_HEADER_MATCH if header is a valid compressed file header,
returns COMP_HEADER_MISMATCH if it dosent.
*/
int comp_check_header(int header);
/*
This is called to generate the correct compression_info data
after the file has been indentified as a compressed file.
This must be done before calling any other function.
*/
void comp_create_ci(CFILE* cf, int header);
/*
Read X bytes from the uncompressed file starting from X offset.
Returns the amount of bytes read, and 0 or lower to indicate errors.
*/
size_t comp_fread(CFILE* cf, char* buffer, size_t length);
/*
Returns the current uncompressed file position.
*/
size_t comp_ftell(CFILE* cf);
/*
Returns 1 of the uncompressed file has been completely read, otherwise it returns a 0.
*/
int comp_feof(CFILE* cf);
/*
Used to move the uncompressed file current position.
*/
int comp_fseek(CFILE* cf, int offset, int where);
#endif
+14 -14
View File
@@ -246,7 +246,7 @@ 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", },
@@ -274,6 +274,7 @@ cmdline_parm gameclosed_arg("-closed", NULL, AT_NONE); // Cmdline_closed_game
cmdline_parm gamerestricted_arg("-restricted", NULL, AT_NONE); // Cmdline_restricted_game
cmdline_parm port_arg("-port", "Multiplayer network port", AT_INT);
cmdline_parm multilog_arg("-multilog", NULL, AT_NONE); // Cmdline_multi_log
cmdline_parm client_dodamage("-clientdamage", NULL, AT_NONE); // Cmdline_client_dodamage
cmdline_parm pof_spew("-pofspew", NULL, AT_NONE); // Cmdline_spew_pof_info
cmdline_parm weapon_spew("-weaponspew", nullptr, AT_STRING); // Cmdline_spew_weapon_stats
cmdline_parm mouse_coords("-coords", NULL, AT_NONE); // Cmdline_mouse_coords
@@ -286,6 +287,7 @@ char *Cmdline_game_password = NULL;
char *Cmdline_rank_above = NULL;
char *Cmdline_rank_below = NULL;
int Cmdline_cd_check = 1;
int Cmdline_client_dodamage = 0;
int Cmdline_closed_game = 0;
int Cmdline_freespace_no_music = 0;
int Cmdline_freespace_no_sound = 0;
@@ -479,7 +481,6 @@ cmdline_parm reparse_mainhall_arg("-reparse_mainhall", NULL, AT_NONE); //Cmdline
cmdline_parm frame_profile_write_file("-profile_write_file", NULL, AT_NONE); // Cmdline_profile_write_file
cmdline_parm no_unfocused_pause_arg("-no_unfocused_pause", NULL, AT_NONE); //Cmdline_no_unfocus_pause
cmdline_parm benchmark_mode_arg("-benchmark_mode", NULL, AT_NONE); //Cmdline_benchmark_mode
cmdline_parm pilot_arg("-pilot", nullptr, AT_STRING); //Cmdline_pilot
cmdline_parm noninteractive_arg("-noninteractive", NULL, AT_NONE); //Cmdline_noninteractive
cmdline_parm json_profiling("-json_profiling", NULL, AT_NONE); //Cmdline_json_profiling
cmdline_parm show_video_info("-show_video_info", NULL, AT_NONE); //Cmdline_show_video_info
@@ -511,7 +512,6 @@ int Cmdline_reparse_mainhall = 0;
bool Cmdline_profile_write_file = false;
bool Cmdline_no_unfocus_pause = false;
bool Cmdline_benchmark_mode = false;
const char *Cmdline_pilot = nullptr;
bool Cmdline_noninteractive = false;
bool Cmdline_json_profiling = false;
bool Cmdline_frame_profile = false;
@@ -812,7 +812,7 @@ void os_validate_parms(int argc, char *argv[])
if (!stricmp(token, "-help") || !stricmp(token, "--help") || !stricmp(token, "-h") || !stricmp(token, "-?")) {
printf("FreeSpace 2 Open, version %s\n", FS_VERSION_FULL);
printf("Website: http://scp.indiegames.us\n");
printf("Github (bug reporting): https://github.com/scp-fs2open/fs2open.github.com/issues\n\n");
printf("Mantis (bug reporting): http://scp.indiegames.us/mantis/\n\n");
printf("Usage: fs2_open [options]\n");
// not the prettiest thing but the job gets done
@@ -1619,7 +1619,7 @@ bool SetCmdlineParams()
Cmdline_game_name = gamename_arg.str();
// be sure that this string fits in our limits
if ( strlen(Cmdline_game_name) >= MAX_GAMENAME_LEN ) {
if ( strlen(Cmdline_game_name) > MAX_GAMENAME_LEN ) {
Cmdline_game_name[MAX_GAMENAME_LEN-1] = '\0';
}
}
@@ -1629,9 +1629,8 @@ bool SetCmdlineParams()
Cmdline_game_password = gamepassword_arg.str();
// be sure that this string fits in our limits
if ( strlen(Cmdline_game_password) >= MAX_PASSWD_LEN ) {
ReleaseWarning(LOCATION, "Multi game password is longer than max of %d charaters and will be trimmed to fit!", MAX_PASSWD_LEN-1);
Cmdline_game_password[MAX_PASSWD_LEN-1] = '\0';
if ( strlen(Cmdline_game_name) > MAX_PASSWD_LEN ) {
Cmdline_game_name[MAX_PASSWD_LEN-1] = '\0';
}
}
@@ -1664,6 +1663,12 @@ bool SetCmdlineParams()
Cmdline_multi_log = 1;
}
// maybe use old-school client damage
if(client_dodamage.found()){
Cmdline_client_dodamage = 1;
}
// spew pof info
if(pof_spew.found()){
Cmdline_spew_pof_info = 1;
@@ -1716,7 +1721,7 @@ bool SetCmdlineParams()
sprintf(override, "{\"width\":%d,\"height\":%d}", width, height);
options::OptionsManager::instance()->setOverride("Graphics.Resolution", override);
} else {
mprintf(("Failed to parse -res parameter \"%s\". Must be in format \"<width>x<height>\".\n", Cmdline_res));
mprintf(("Failed to parse -res parameter \"%s\". Must be in format \"<width>x<height>\".", Cmdline_res));
}
}
if(center_res_arg.found()){
@@ -2126,11 +2131,6 @@ bool SetCmdlineParams()
Cmdline_benchmark_mode = true;
}
if (pilot_arg.found())
{
Cmdline_pilot = pilot_arg.str();
}
if (noninteractive_arg.found())
{
Cmdline_noninteractive = true;
+1 -1
View File
@@ -30,6 +30,7 @@ extern char *Cmdline_game_password;
extern char *Cmdline_rank_above;
extern char *Cmdline_rank_below;
extern int Cmdline_cd_check;
extern int Cmdline_client_dodamage;
extern int Cmdline_closed_game;
extern int Cmdline_freespace_no_music;
extern int Cmdline_freespace_no_sound;
@@ -143,7 +144,6 @@ extern int Cmdline_reparse_mainhall;
extern bool Cmdline_profile_write_file;
extern bool Cmdline_no_unfocus_pause;
extern bool Cmdline_benchmark_mode;
extern const char *Cmdline_pilot;
extern bool Cmdline_noninteractive;
extern bool Cmdline_json_profiling;
extern bool Cmdline_frame_profile;
File diff suppressed because it is too large. Load diff
+28 -336
View File
@@ -28,27 +28,12 @@ enum Joy_axis_index {
JOY_RZ_AXIS
};
/*!
* 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 : 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_HEADING_AXIS =0,
JOY_PITCH_AXIS,
JOY_BANK_AXIS,
JOY_ABS_THROTTLE_AXIS,
@@ -60,16 +45,7 @@ enum Joy_axis_action_index {
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
@@ -79,12 +55,27 @@ enum CC_type {
CC_TYPE_CONTINUOUS //!< A continous control that is activated as long as the key or button is held down
};
/*!
* Control configuration item type.
*/
typedef struct config_item {
short key_default; //!< default key bound to action
short joy_default; //!< default joystick button bound to action
char tab; //!< what tab (category) it belongs in
int indexXSTR; //!< what string index we should use to translate this with an XSTR
const char *text; //!< describes the action in the config screen
char type; //!< manner control should be checked in
short key_id; //!< actual key bound to action
short joy_id; //!< joystick button bound to action
int used; //!< has control been used yet in mission? If so, this is the timestamp
bool disabled; //!< whether this action should be available at all
bool continuous_ongoing;//!< whether this action is a continuous one and is currently ongoing
} config_item;
/*!
* All available actions
* This is the value of the id field in config_item
* The first group of items are ship targeting.
*
* Note: Do not adjust the order or numeric value
*/
enum IoActionId {
TARGET_NEXT =0, //!< target next
@@ -261,7 +252,7 @@ enum IoActionId {
DOWN_SLIDE_THRUST =111, //!< DOWN_SLIDE_THRUST
HUD_TARGETBOX_TOGGLE_WIREFRAME =112, //!< HUD_TARGETBOX_TOGGLE_WIREFRAME
VIEW_TOPDOWN =113, //!< VIEW_TOPDOWN
VIEW_TRACK_TARGET =114, //!< VIEW_TRACK_TARGET (Switfty) Toggle for VM_Track
VIEW_TRACK_TARGET =114, //!< VIEW_TRACK_TARGET
//!< @n
//!< AutoPilot - Kazan
@@ -294,200 +285,10 @@ enum IoActionId {
CCFG_MAX //!< The total number of defined control actions (or last define + 1)
};
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(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
*/
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 not equal to either in the pair
*/
bool operator!=(const CCB &pair) const;
/*!
* Clears the binding
*/
void clear();
/*!
* True if not bound
*/
bool empty() const;
/*!
* Returns a human-readable string of this binding
*/
SCP_string textify() const;
};
/*!
* A pair of bindings.
* @note Please don't set the bindings directly, use ::take() instead.
*/
class CCB {
public:
CC_bind first; // The primary binding
CC_bind second; // The secondary binding
public:
CCB() = default;
CCB(const CCB& A) = default;
/*!
* Returns true if nothing is bound
*/
bool empty() const;
/*!
* Takes a given binding
*
* @param[in] A The bind to take
* @param[in] order 0 is primary, 1 is secondary, -1 is overwrite existing bind (if any)
*
* @note Should there be a binding to the same controller as the given binding, it is cleared
*/
void take(CC_bind A, int order);
/*!
* Clears both bindings
*/
void clear();
/*!
* Gets the value of btn if either CC_bind has the given CID
*
* @returns the btn of the bound controller, or
* @returns -1 if that controller is not bound
*/
short get_btn(CID) const;
/*!
* Assigns the contents of the given CCB to this CCB
*/
CCB& operator=(const CCB&);
/*!
* Returns True if this CCB's first isn't empty and the given CCB has a binding equal to it
*/
bool has_first(const CCB&) const;
/*!
* Returns True if this CCB's second isn't empty and the given CCB has a binding equal to it
*/
bool has_second(const CCB&) const;
};
/*!
* A preset, a collection of bindings for use in Control_config with an associated name
*/
class CC_preset {
public:
SCP_vector<CCB> bindings;
SCP_string name;
};
/*!
* Control configuration item type.
* @detail Contains binding info, documentation, behavior, etc. for a single control
*/
class CCI : public CCB {
public:
// Inherited from CCB
// CC_bind first;
// CC_bind second;
// Items Set in menu
char tab; //!< what tab (category) it belongs in
int indexXSTR; //!< what string index we should use to translate this with an XSTR 0 = None, 1= Use item index + CONTROL_CONFIG_XSTR, 2 <= use CCI::indexXSTR directly
SCP_string text; //!< describes the action in the config screen
CC_type type; //!< manner control should be checked in
// Items used during gameplay
int used; //!< has control been used yet in mission? If so, this is the timestamp
bool disabled = true; //!< whether this action should be available at all
bool continuous_ongoing; //!< whether this action is a continuous one and is currently ongoing
public:
CCI() = default;
CCI(const CCI& A) = default;
/*!
* Assigns the contents of the given CCI to this CCI
*/
CCI& operator=(const CCI&);
/*!
* @brief Takes the bindings of the given CCB, but leaves all other members alone
*/
CCI& operator=(const CCB&);
};
/*!
* Builder predicate to populate a ControlConfig vector with hardcoded default bindings.
*/
class CCI_builder {
public:
/*!
* Initilizes the given ControlConfig vector
*/
CCI_builder(SCP_vector<CCI>& _ControlConfig);
/*!
* Start a chain of factory methods. If there are any pre-init work to be done, that's done here.
*/
CCI_builder& start();
/*!
* End a chain of factory methods. If there any post-init work to be done, that's done here.
*/
void end();
/*!
* Assigns the hardcoded binding to the given action
* @note This differs from the original hardcode from :V: in the hopes of it being more intuitive to future IoAction additions
*/
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
SCP_vector<CCI>& ControlConfig;
};
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 Axis_map_to[];
extern int Invert_axis[];
extern int Invert_axis_defaults[];
extern int Joy_dead_zone_size;
@@ -495,76 +296,23 @@ extern int Joy_sensitivity;
extern int Control_config_overlay_id;
extern SCP_vector<CCI> Control_config; //!< Stores the keyboard configuration
extern SCP_vector<CC_preset> Control_config_presets; // tabled control presets; pointers to config_item arrays
extern config_item Control_config[]; //!< Stores the keyboard configuration
extern SCP_vector<config_item*> Control_config_presets; // tabled control presets; pointers to config_item arrays
extern SCP_vector<SCP_string> Control_config_preset_names; // names for Control_config_presets (identical order of items)
extern const char **Scan_code_text;
extern const char **Joy_button_text;
/*!
* @brief Checks if either binding in the CCB has the given cid
*
* @returns 0 if its the first element, or
* @returns 1 if its the second element, or
* @returns -1 if neither
*/
int is_cid_either(CID, const CCB);
void control_config_common_init(); //!< initialize common control config stuff - call at game startup after localization has been initialized
void control_config_common_close(); //!< close common control config stuff - call at game shutdown
/*!
* @brief initialize common control config stuff - call at game startup after localization has been initialized
*/
void control_config_common_init();
/*!
* @brief close common control config stuff - call at game shutdown
*/
void control_config_common_close();
/*!
* @brief init config menu
*/
void control_config_init();
/*!
* @brief do a frame of the config menu
*/
void control_config_do_frame(float frametime);
/*!
* @brief close config menu
*/
void control_config_close();
/*!
* @brief Cancel configuration of controls, revert any changes, return to previous menu/game state
*/
void control_config_cancel_exit();
/*!
* @brief Resets all controls to values within the currently selected preset
*/
void control_config_reset_defaults();
/*!
* 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
*/
void control_config_reset_defaults(int presetnum=-1);
int translate_key_to_index(const char *key, bool find_override=true);
/*!
* @brief Given the system default key 'key', return the current key that is bound to that function.
*
* @param[in] key The default key combo (as string) to a certain 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, NULL is returned.
*/
char *translate_key(char *key);
/**
@@ -586,70 +334,14 @@ const char *textify_scancode(int code);
*/
const char *textify_scancode_universal(int code);
/*!
* @brief Checks how long a control has been active
*
* @param[in] id The IoActionId of the control to check
*
* @returns Time, in milliseconds, that the control has been active.
*
* @note This function is only to be used on CC_CONTINOUS type controls, or it will trigger an assertion
*/
float check_control_timef(int id);
/**
* @brief Wrapper for check_control_used. Allows the game to ignore the key if told to do so by the ignore-key SEXP.
*
* @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
*/
int check_control(int id, int key = -1);
/**
* @brief Gets the scaled reading for all control axes.
*
* @param[out] h heading
* @param[out] p pitch
* @param[out] b bank
* @param[out] ta Throttle - Absolute
* @param[out] tr Throttle - Relative
*
* @note None of the input pointers may be nullptr
*/
void control_get_axes_readings(int *h, int *p, int *b, int *ta, int *tr);
/**
* @brief Markes the given control (by IoActionId) as used
*
* @details Updates the ::used timestamp, triggers a script hook, and marks ::continous_ongoing as true
*/
void control_used(int id);
/**
* @brief Clears the bindings of all controls
*/
void control_config_clear();
/**
* @brief debug function used to indicate number of controls checked
*/
void control_check_indicate();
/**
* @brief Clears the used timestamp of all controls
*/
void control_config_clear_used_status();
/**
* @brief Applies sensitivity multiplier and deadzone histerisis to the raw axis value
*
* @param[in] raw The raw axis value to transform
*
* @return The transformed value
*/
int joy_get_scaled_reading(int raw);
#endif
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -93,12 +93,13 @@ class Decoder {
bool m_decoding;
size_t m_queueSize = 0;
Decoder(const Decoder&) SCP_DELETED_FUNCTION;
Decoder& operator=(const Decoder&) SCP_DELETED_FUNCTION;
protected:
Decoder();
public:
Decoder(const Decoder&) = delete;
Decoder& operator=(const Decoder&) = delete;
virtual ~Decoder();
+1 -1
View File
@@ -613,7 +613,7 @@ void cutscenes_screen_do_frame()
if (src)
{
Text_size = split_str(src, Cutscene_desc_coords[gr_screen.res][2], Text_line_size, Text_lines,
Cutscene_max_text_lines[gr_screen.res], MAX_TEXT_LINE_LEN);
Cutscene_max_text_lines[gr_screen.res]);
Assert(Text_size >= 0 && Text_size < Cutscene_max_text_lines[gr_screen.res]);
}
}
+3 -3
View File
@@ -286,7 +286,7 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
if (status->subtitleStream == nullptr) {
// We don't have an external subtitle stream or loading from that file has failed
auto& current_language = Lcl_languages[lcl_get_current_lang_index()];
auto& current_language = Lcl_languages[Lcl_current_lang == LCL_UNTRANSLATED ? LCL_DEFAULT : Lcl_current_lang];
for (uint32_t i = 0; i < ctx->nb_streams; ++i) {
auto test_stream = ctx->streams[i];
@@ -428,7 +428,7 @@ std::unique_ptr<InputStream> openInputStream(const SCP_string& name) {
std::unique_ptr<InputStream> openSubtitleStream(const SCP_string& name)
{
auto& current_language = Lcl_languages[lcl_get_current_lang_index()];
auto& current_language = Lcl_languages[Lcl_current_lang == LCL_UNTRANSLATED ? LCL_DEFAULT : Lcl_current_lang];
for (auto ext : CHECKED_SUBT_EXTENSIONS) {
SCP_string fileName;
@@ -453,7 +453,7 @@ bool FFMPEGDecoder::initialize(const SCP_string& fileName, const PlaybackPropert
{
SCP_string movieName = fileName;
// First make the file name lower case
SCP_tolower(movieName);
std::transform(movieName.begin(), movieName.end(), movieName.begin(), [](char c) { return (char)::tolower(c); });
// Then remove the extension
size_t dotPos = movieName.find('.');
+6 -4
View File
@@ -45,8 +45,10 @@ struct 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;
+89 -85
View File
@@ -30,9 +30,8 @@
#include "ship/ship.h"
#include "ship/shipfx.h"
#include "species_defs/species_defs.h"
#include "tracing/Monitor.h"
#include "utils/Random.h"
#include "weapon/weapon.h"
#include "tracing/Monitor.h"
#define MIN_RADIUS_FOR_PERSISTANT_DEBRIS 50 // ship radius at which debris from it becomes persistant
#define DEBRIS_SOUND_DELAY 2000 // time to start debris sound after created
@@ -52,6 +51,8 @@ int Debris_model = -1;
int Debris_vaporize_model = -1;
int Debris_num_submodels = 0;
#define MAX_DEBRIS_DIST 10000.0f // Debris goes away if it's this far away.
#define DEBRIS_DISTANCE_CHECK_TIME (10*1000) // Check every 10 seconds.
#define DEBRIS_INDEX(dp) (int)(dp-Debris.data())
const auto OnDebrisCreatedHook = scripting::Hook::Factory(
@@ -74,7 +75,7 @@ static void debris_start_death_roll(object *debris_obj, debris *debris_p)
int fireball_type = fireball_ship_explosion_type(&Ship_info[debris_p->ship_info_index]);
if(fireball_type < 0) {
fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
fireball_type = FIREBALL_EXPLOSION_LARGE1 + rand()%FIREBALL_NUM_LARGE_EXPLOSIONS;
}
fireball_create( &debris_obj->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(debris_obj), debris_obj->radius*1.75f);
@@ -195,6 +196,34 @@ void debris_delete( object * obj )
db->objnum = -1;
}
/**
* If debris piece *db is far away from all players, make it go away very soon.
* In single player game, delete if MAX_DEBRIS_DIST from player.
* In multiplayer game, delete if MAX_DEBRIS_DIST from all players.
*/
void maybe_delete_debris(debris *db)
{
object *objp;
if (Player_obj != nullptr && timestamp_elapsed(db->next_distance_check) && timestamp_elapsed(db->must_survive_until)) {
if (!(Game_mode & GM_MULTIPLAYER)) { // In single player game, just check against player.
if (vm_vec_dist_quick(&Player_obj->pos, &Objects[db->objnum].pos) > MAX_DEBRIS_DIST)
db->lifeleft = 0.1f;
else
db->next_distance_check = timestamp(DEBRIS_DISTANCE_CHECK_TIME);
} else {
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if (objp->flags[Object::Object_Flags::Player_ship]) {
if (vm_vec_dist_quick(&objp->pos, &Objects[db->objnum].pos) < MAX_DEBRIS_DIST) {
db->next_distance_check = timestamp(DEBRIS_DISTANCE_CHECK_TIME);
return;
}
}
}
db->lifeleft = 0.1f;
}
}
}
/**
* Do various updates to debris: check if time to die, start fireballs
@@ -217,7 +246,7 @@ void debris_process_post(object * obj, float frame_time)
radar_plot_object( obj );
if ( timestamp_elapsed(db->sound_delay) ) {
obj_snd_assign(objnum, db->ambient_sound, &vmd_zero_vector);
obj_snd_assign(objnum, db->ambient_sound, &vmd_zero_vector, 0);
db->sound_delay = 0;
}
}
@@ -231,6 +260,8 @@ void debris_process_post(object * obj, float frame_time)
}
}
maybe_delete_debris(db); // Make this debris go away if it's very far away.
// ================== DO THE ELECTRIC ARCING STUFF =====================
if ( db->arc_frequency <= 0 ) {
return; // If arc_frequency <= 0, this piece has no arcs on it
@@ -243,7 +274,7 @@ void debris_process_post(object * obj, float frame_time)
if (db->is_hull) {
int n, n_arcs = Random::next(1, 3); // Create 1-3 sparks
int n, n_arcs = ((rand()>>5) % 3)+1; // Create 1-3 sparks
vec3d v1, v2, v3, v4;
@@ -252,7 +283,8 @@ void debris_process_post(object * obj, float frame_time)
n = 0;
int lifetime = Random::next(100, 1000);
int a = 100, b = 1000;
int lifetime = (myrand()%((b)-(a)+1))+(a);
// Create the spark effects
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
@@ -319,8 +351,8 @@ void debris_process_post(object * obj, float frame_time)
db->arc_timestamp[i] = timestamp(-1);
} else {
// Maybe move a vertex.... 20% of the time maybe?
int mr = Random::next();
if ( mr < Random::MAX_VALUE/5 ) {
int mr = myrand();
if ( mr < RAND_MAX/5 ) {
vec3d v1, v2;
submodel_get_two_random_points_better(db->model_num, db->submodel_num, &v1, &v2);
@@ -392,6 +424,17 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
sip = &Ship_info[shipp->ship_info_index];
vaporize = (shipp->flags[Ship::Ship_Flags::Vaporize]);
if ( !hull_flag ) {
// Make vaporize debris seen from farther away
float dist = vm_vec_dist_quick( pos, &Eye_position );
if (vaporize) {
dist /= 2.0f;
}
if ( dist > 200.0f ) {
return NULL;
}
}
// try to maintain our soft limit
if (hull_flag && (Num_hull_pieces >= SOFT_LIMIT_DEBRIS_PIECES)) {
// cause oldest hull debris chunk to blow up
@@ -414,40 +457,8 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
db = &Debris[n];
if (!hull_flag) {
if (model_num >= 0) {
db->model_num = model_num;
db->submodel_num = Random::next(sip->generic_debris_num_submodels);
}
else if (vaporize) {
db->model_num = Debris_vaporize_model;
db->submodel_num = Random::next(Debris_num_submodels);
}
else {
db->model_num = Debris_model;
db->submodel_num = Random::next(Debris_num_submodels);
}
}
else {
db->model_num = model_num;
db->submodel_num = submodel_num;
}
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)
{
@@ -457,16 +468,14 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
{
// Create Debris piece n!
if ( hull_flag ) {
if (Random::next() < (Random::MAX_VALUE/6)) // Make some pieces blow up shortly after explosion.
db->lifeleft = 2.0f * (frand()) + 0.5f;
if (rand() < RAND_MAX/6) // Make some pieces blow up shortly after explosion.
db->lifeleft = 2.0f * (myrand() * RAND_MAX_1f) + 0.5f;
else {
db->flags.set(Debris_Flags::DoNotExpire);
db->lifeleft = -1.0f; // large hull pieces stay around forever
}
} else {
// small non-hull pieces should stick around longer the larger they are
// sqrtf should make sure its never too crazy long
db->lifeleft = (frand() * 2.0f + 0.1f) * sqrtf(radius);
} else {
db->lifeleft = (myrand() * RAND_MAX_1f) * 2.0f + 0.1f;
}
}
@@ -475,10 +484,12 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
{
if(sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
{
db->must_survive_until = timestamp(fl2i(sip->debris_min_lifetime * 1000.0f));
db->lifeleft = (( sip->debris_max_lifetime - sip->debris_min_lifetime ) * frand()) + sip->debris_min_lifetime;
}
else if(sip->debris_min_lifetime >= 0.0f)
{
db->must_survive_until = timestamp(fl2i(sip->debris_min_lifetime * 1000.0f));
if(db->lifeleft < sip->debris_min_lifetime)
db->lifeleft = sip->debris_min_lifetime;
}
@@ -503,6 +514,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
db->fire_timeout = 0; // if not changed, timestamp_elapsed() will return false
db->time_started = Missiontime;
db->species = Ship_info[shipp->ship_info_index].species;
db->next_distance_check = (myrand() % 2000) + 4*DEBRIS_DISTANCE_CHECK_TIME;
db->parent_alt_name = shipp->alt_type_index;
db->damage_mult = 1.0f;
@@ -521,9 +533,25 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
db->arc_frequency = 0;
}
if ( model_num < 0 ) {
if (vaporize) {
db->model_num = Debris_vaporize_model;
} else {
db->model_num = Debris_model;
}
db->submodel_num = (myrand()>>4) % Debris_num_submodels;
} else {
db->model_num = model_num;
db->submodel_num = submodel_num;
}
db->model_instance_num = model_create_instance(false, db->model_num);
float radius = submodel_get_radius( db->model_num, db->submodel_num );
db->next_fireball = timestamp_rand(500,2000); //start one 1/2 - 2 secs later
if ( pos == nullptr )
if ( pos == NULL )
pos = &source_obj->pos;
flagset<Object::Object_Flags> default_flags;
@@ -531,18 +559,10 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
default_flags.set(Object::Object_Flags::Physics);
default_flags.set(Object::Object_Flags::Collides, hull_flag != 0);
matrix orient = source_obj->orient;
if (!db->is_hull) {
// non-hull debris has no relation to its parent orientation
vec3d rand;
vm_vec_rand_vec(&rand);
vm_vector_2_matrix(&orient, &rand);
}
objnum = obj_create(OBJ_DEBRIS, parent_objnum, n, &orient, pos, radius, default_flags, false);
objnum = obj_create( OBJ_DEBRIS, parent_objnum, n, &source_obj->orient, pos, radius, default_flags);
if ( objnum == -1 ) {
mprintf(("Couldn't create debris object -- out of object slots\n"));
return nullptr;
return NULL;
}
db->objnum = objnum;
@@ -592,7 +612,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
if ( hull_flag ) {
float t;
scale = exp_force * i2fl(Random::next(10, 29)); // for radial_vel away from location of blast center
scale = exp_force * i2fl((myrand()%20) + 10); // for radial_vel away from location of blast center
db->sound_delay = timestamp(DEBRIS_SOUND_DELAY);
// set up physics mass and I_inv for hull debris pieces
@@ -604,7 +624,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
// limit the amount of time that fireballs appear
// let fireball length be linked to radius of ship. Range is .33 radius => 3.33 radius seconds.
t = 1000*Objects[db->source_objnum].radius/3 + Random::next(fl2i(1000*3*Objects[db->source_objnum].radius));
t = 1000*Objects[db->source_objnum].radius/3 + myrand()%(fl2i(1000*3*Objects[db->source_objnum].radius));
db->fire_timeout = timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
if ( Objects[db->source_objnum].radius < MIN_RADIUS_FOR_PERSISTANT_DEBRIS ) {
@@ -615,7 +635,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
}
}
else {
scale = exp_force * i2fl(Random::next(10, 29)); // for radial_vel away from blast center (non-hull)
scale = exp_force * i2fl((myrand()%20) + 10); // for radial_vel away from blast center (non-hull)
}
if ( vm_vec_mag_squared( &to_center ) < 0.1f ) {
@@ -642,7 +662,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
radius = 1.0f;
}
scale = (6.0f + i2fl(Random::next(4))) / radius;
scale = ( 6.0f + i2fl(myrand()%4) ) / radius;
vm_vec_rand_vec_quick(&rotvel);
vm_vec_scale(&rotvel, scale);
@@ -879,13 +899,13 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
smi->collision_checked = false;
// set angles for last frame (need to set to prev to get p0)
matrix copy_matrix = smi->canonical_orient;
angles copy_angles = smi->angs;
// find the start and end positions of the sphere in submodel RF
smi->canonical_orient = smi->canonical_prev_orient;
smi->angs = smi->prev_angs;
world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
smi->canonical_orient = copy_matrix;
smi->angs = copy_angles;
world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
mc.p0 = &p0;
@@ -904,7 +924,7 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
// set up debris_hit_info for rotating submodel
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);
model_instance_find_obj_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
@@ -936,7 +956,7 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
// get collision normal if not edge hit
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);
model_instance_find_obj_dir(&debris_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
@@ -1107,7 +1127,7 @@ void debris_render(object * obj, model_draw_list *scene)
MONITOR_INC(NumHullDebrisRend, 1);
} else {
MONITOR_INC(NumSmallDebrisRend, 1);
// render_info.set_flags(MR_NO_LIGHTING);
render_info.set_flags(MR_NO_LIGHTING);
}
submodel_render_queue( &render_info, scene, pm, pmi, db->submodel_num, &obj->orient, &obj->pos );
@@ -1126,19 +1146,3 @@ bool debris_is_vaporized(debris *db)
{
return db->model_num == Debris_vaporize_model;
}
void create_generic_debris(object* ship_objp, vec3d* pos, float min_num_debris, float max_num_debris, float speed_mult, bool use_ship_debris) {
Assertion(ship_objp->type == OBJ_SHIP, "create_generic_debris called for a non-ship, only ships can spew debris!");
if (ship_objp->type != OBJ_SHIP)
return;
float num_debris = frand_range(min_num_debris, max_num_debris);
num_debris *= (Detail.num_small_debris + 0.5f) / 4.5f;
vec3d create_pos = *pos;
for (int i = 0; i < num_debris; i++) {
int model_num = use_ship_debris ? Ship_info[Ships[ship_objp->instance].ship_info_index].generic_debris_model_num : -1;
debris_create(ship_objp, model_num, -1, &create_pos, pos, 0, speed_mult);
}
}
+3 -5
View File
@@ -40,15 +40,17 @@ typedef struct debris {
gamesnd_id ambient_sound; // Ambient looping sound
int objnum = -1; // What object this is linked to
float lifeleft; // When 0 or less object dies
int must_survive_until; //WMC - timestamp of earliest point that it can be murthered.
int model_num; // What model this uses
int model_instance_num; // What model instance this uses - needed for arcs
int submodel_num; // What submodel this uses
int next_fireball; // When to start a fireball
int is_hull; // indicates whether this is a collideable, destructable piece of debris from the model, or just a generic debris fragment
int is_hull; // indicates a large hull chunk of debris
int species; // What species this is from. -1 if don't care.
int fire_timeout; // timestamp that holds time for fireballs to stop appearing
int sound_delay; // timestamp to signal when sound should start
fix time_started; // time when debris was created
int next_distance_check; // timestamp to determine whether to delete this piece of debris.
vec3d arc_pts[MAX_DEBRIS_ARCS][2]; // The endpoints of each arc
int arc_timestamp[MAX_DEBRIS_ARCS]; // When this times out, the spark goes away. -1 is not used
@@ -80,8 +82,4 @@ void debris_remove_from_hull_list(debris *db);
bool debris_is_generic(debris *db);
bool debris_is_vaporized(debris *db);
// creates a burst of generic debris at hitpos from ship_objp, with a random number between min and max
// use_ship_debris is for whether the ship's generic debris should be used, or simply debris01.pof
void create_generic_debris(object* ship_objp, vec3d* hitpos, float min_num_debris, float max_num_debris, float speed_mult, bool use_ship_debris);
#endif // _DEBRIS_H
+1 -9
View File
@@ -65,23 +65,15 @@ void GetLightInfo(vec3 position, out vec3 lightDir, out float attenuation)
if (lightType == LT_TUBE) { // Tube light
float beamLength = length(beamVec);
vec3 beamDir = beamVec / beamLength;
//The actual 'lighting element' is shorter than the light volume cylinder
//To compensate the light is moved forward along the beam one radius and the length shortened
//this allows room for clean falloff of the light past the ends of beams.
vec3 adjustedLightPos = lightPosition - (beamDir * lightRadius);
beamLength = beamLength - (lightRadius * 2.0);
//adjustments having been made, lightdir needs recalculating
lightDir = adjustedLightPos - position.xyz;
// Get nearest point on line
float neardist = clamp(dot(lightDir, beamDir), 0.0, beamLength);
// Move back from the endpoint of the beam along the beam by the distance we calculated
vec3 nearest = adjustedLightPos - beamDir * neardist;
vec3 nearest = lightPosition - beamDir * neardist;
lightDir = nearest - position.xyz;
dist = length(lightDir);
if(dist > lightRadius) {
discard;
}
attenuation = 1.0 - clamp(dist / lightRadius, 0.0, 1.0);
} else if (lightType == LT_CONE) {
float coneDot = dot(normalize(-lightDir), coneDir);
if(dualCone) {
+6 -4
View File
@@ -12,7 +12,7 @@ layout (std140) uniform genericData {
vec3 fog_color;
float fog_start;
float fog_density;
float fog_scale;
float zNear;
float zFar;
};
@@ -23,15 +23,17 @@ void main()
// If the scene is in fog then we change the color we operate on based on the depth of the pixel
float depth_val = texture(depth_tex, fragTexCoord.xy).x;
// Transform depth value from [0, 1] to [-1, 1]
float depth_normalized = 2 * depth_val - 1;
// Transform depth value from [-1, 1] to [0, 1]
float depth_normalized = fma(depth_val, 0.5, 0.5);
// Now we compute the depth value in projection space using the clipping plane information
float view_depth = 2.0 * zNear * zFar / (zFar + zNear - depth_normalized * (zFar - zNear));
if (isinf(view_depth)) {
fragOut0.rgb = color_in.rgb;
} else {
float fog_dist = clamp(1 - pow(fog_density, view_depth - fog_start) , 0.0, 1.0);
// This is the same formula that is also used by the main shader
float fog_dist = clamp((view_depth - fog_start) * 0.75 * fog_scale, 0.0, 1.0);
vec3 finalFogColor = srgb_to_linear(fog_color);
fragOut0.rgb = mix(color_in.rgb, finalFogColor, fog_dist);
+1 -9
View File
@@ -429,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
@@ -221,7 +221,6 @@ $AI:
+Can Form Wing: YES
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
$Name: Freighter
$Counts for Alone: YES
@@ -251,7 +250,6 @@ $AI:
+Active docks: ( "cargo" )
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
$Name: AWACS
$Counts for Alone: YES
@@ -279,7 +277,6 @@ $AI:
+Can Form Wing: YES
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
$Name: Gas Miner
$Counts for Alone: YES
@@ -307,7 +304,6 @@ $AI:
+Can Form Wing: YES
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
$Name: Cruiser
$Counts for Alone: YES
@@ -335,7 +331,6 @@ $AI:
+Can Form Wing: YES
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
$Name: Corvette
$Counts for Alone: YES
@@ -363,7 +358,6 @@ $AI:
+Can Form Wing: YES
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
$Name: Capital
$Counts for Alone: YES
@@ -392,7 +386,6 @@ $AI:
+Can Form Wing: YES
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
$Name: Super Cap
$Counts for Alone: YES
@@ -419,7 +412,6 @@ $AI:
+Can Form Wing: YES
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
$Name: Drydock
$Counts for Alone: YES
@@ -444,7 +436,6 @@ $AI:
+Turrets attack this: YES
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
$Name: Knossos Device
$Counts for Alone: YES
@@ -467,6 +458,5 @@ $AI:
+Turrets attack this: YES
+Passive docks: ( "support" )
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Targeted by 'Huge' weapons and Ignored by 'small only' weapons: YES
$Vaporize Percent Chance: 0.0
#End
+106 -104
View File
@@ -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"
@@ -32,15 +31,13 @@ int Knossos_warp_ani_used;
#define MAX_WARP_LOD 0
constexpr int INTITIAL_FIREBALL_CONTAINTER_SIZE = 256;
SCP_vector<fireball> Fireballs;
SCP_vector<int> Unused_fireball_indices;
fireball Fireballs[MAX_FIREBALLS];
fireball_info Fireball_info[MAX_FIREBALL_TYPES];
int fireball_used[MAX_FIREBALL_TYPES];
int Num_fireballs = 0;
int Num_fireball_types = 0;
bool fireballs_inited = false;
@@ -287,13 +284,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
@@ -317,7 +312,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
@@ -325,7 +319,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:"))
{
@@ -333,9 +326,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
@@ -445,6 +435,8 @@ void fireball_load_data()
// This will get called at the start of each level.
void fireball_init()
{
int i;
if ( !fireballs_inited ) {
// Do all the processing that happens only once
fireball_parse_tbl();
@@ -454,10 +446,10 @@ void fireball_init()
}
// Reset everything between levels
Fireballs.clear();
Fireballs.reserve(INTITIAL_FIREBALL_CONTAINTER_SIZE);
Unused_fireball_indices.clear();
Unused_fireball_indices.reserve(INTITIAL_FIREBALL_CONTAINTER_SIZE);
Num_fireballs = 0;
for (i=0; i<MAX_FIREBALLS; i++ ) {
Fireballs[i].objnum = -1;
}
// Goober5000 - reset Knossos warp flag
Knossos_warp_ani_used = 0;
@@ -475,15 +467,13 @@ void fireball_delete( object * obj )
fireball *fb;
num = obj->instance;
// Make sure the new system works fine.
Assert(obj->instance > -1);
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
fb = &Fireballs[num];
Assert( fb->objnum == OBJ_INDEX(obj));
Fireballs[num].objnum = -1;
Unused_fireball_indices.push_back(num);
Num_fireballs--;
Assert( Num_fireballs >= 0 );
}
/**
@@ -491,9 +481,13 @@ void fireball_delete( object * obj )
*/
void fireball_delete_all()
{
for (auto& current_fireball : Fireballs) {
if ( current_fireball.objnum != -1 ) {
obj_delete(current_fireball.objnum);
fireball *fb;
int i;
for ( i = 0; i < MAX_FIREBALLS; i++ ) {
fb = &Fireballs[i];
if ( fb->objnum != -1 ) {
obj_delete(fb->objnum);
}
}
}
@@ -505,8 +499,6 @@ void fireball_set_framenum(int num)
fireball_info *fd;
fireball_lod *fl;
Assert(static_cast<int>(Fireballs.size()) > num);
fb = &Fireballs[num];
fd = &Fireball_info[Fireballs[num].fireball_info_index];
@@ -545,9 +537,6 @@ int fireball_is_perishable(object * obj)
num = obj->instance;
objnum = OBJ_INDEX(obj);
// Make sure the new system works fine.
Assert(obj->instance > -1);
Assert((int)Fireballs.size() > obj->instance);
Assert( Fireballs[num].objnum == objnum );
fb = &Fireballs[num];
@@ -564,6 +553,43 @@ int fireball_is_perishable(object * obj)
return 0;
}
/**
* There are too many fireballs, so delete the oldest small one
* to free up a slot.
*
* @return The fireball slot freed.
*/
int fireball_free_one()
{
fireball *fb;
int i;
int oldest_objnum = -1, oldest_slotnum = -1;
float lifeleft, oldest_lifeleft = 0.0f;
for ( i = 0; i < MAX_FIREBALLS; i++ ) {
fb = &Fireballs[i];
// only remove the ones that aren't warp effects
if ( (fb->objnum >= 0) && fireball_is_perishable(&Objects[fb->objnum]) ) {
lifeleft = fb->total_time - fb->time_elapsed;
if ( (oldest_objnum < 0) || (lifeleft < oldest_lifeleft) ) {
oldest_slotnum = i;
oldest_lifeleft = lifeleft;
oldest_objnum = fb->objnum;
}
break;
}
}
if ( oldest_objnum > -1 ) {
obj_delete(oldest_objnum);
}
return oldest_slotnum;
}
int fireball_is_warp(object * obj)
{
int num, objnum;
@@ -571,9 +597,6 @@ int fireball_is_warp(object * obj)
num = obj->instance;
objnum = OBJ_INDEX(obj);
// Make sure the new system works fine.
Assert(obj->instance > -1);
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
Assert( Fireballs[num].objnum == objnum );
fb = &Fireballs[num];
@@ -618,9 +641,6 @@ void fireball_process_post(object * obj, float frame_time)
num = obj->instance;
objnum = OBJ_INDEX(obj);
// Make sure the new system works fine.
Assert(obj->instance > -1);
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
Assert( Fireballs[num].objnum == objnum );
fb = &Fireballs[num];
@@ -645,10 +665,6 @@ float fireball_lifeleft( object *obj )
num = obj->instance;
objnum = OBJ_INDEX(obj);
// Make sure the new system works fine.
Assert(obj->instance > -1);
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
Assert( Fireballs[num].objnum == objnum );
fb = &Fireballs[num];
@@ -664,10 +680,6 @@ float fireball_lifeleft_percent( object *obj )
int num, objnum;
fireball *fb;
// Make sure the new system works fine.
Assert(obj->instance > -1);
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
num = obj->instance;
objnum = OBJ_INDEX(obj);
Assert( Fireballs[num].objnum == objnum );
@@ -769,8 +781,10 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
{
int n, objnum, fb_lod;
object *obj;
fireball *fb;
fireball_info *fd;
fireball_lod *fl;
Assert( fireball_type > -1 );
Assert( fireball_type < Num_fireball_types );
@@ -786,21 +800,23 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
}
}
if (Num_objects >= MAX_OBJECTS) {
return -1;
if ( (Num_fireballs >= MAX_FIREBALLS) || (Num_objects >= MAX_OBJECTS) ) {
// out of slots, so free one up.
n = fireball_free_one();
if ( n < 0 ) {
return -1;
}
} else {
for ( n = 0; n < MAX_FIREBALLS; n++ ) {
if ( Fireballs[n].objnum < 0 ) {
break;
}
}
Assert( n != MAX_FIREBALLS );
}
if (!Unused_fireball_indices.empty()) {
n = Unused_fireball_indices.back();
Unused_fireball_indices.pop_back();
}
else {
n = static_cast<int>(Fireballs.size());
Fireballs.emplace_back();
}
fireball* new_fireball = &Fireballs[n];
fb = &Fireballs[n];
// get an lod to use
fb_lod = fireball_get_lod(pos, fd, size);
@@ -817,13 +833,13 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
}
fl = &fd->lod[fb_lod];
new_fireball->lod = (char)fb_lod;
fb->lod = (char)fb_lod;
new_fireball->flags = extra_flags;
new_fireball->warp_open_sound_index = warp_open_sound;
new_fireball->warp_close_sound_index = warp_close_sound;
new_fireball->warp_open_duration = (warp_open_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_open_duration;
new_fireball->warp_close_duration = (warp_close_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_close_duration;
fb->flags = extra_flags;
fb->warp_open_sound_index = warp_open_sound;
fb->warp_close_sound_index = warp_close_sound;
fb->warp_open_duration = (warp_open_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_open_duration;
fb->warp_close_duration = (warp_close_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_close_duration;
matrix orient;
if(orient_override != NULL){
@@ -838,55 +854,60 @@ 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);
if (objnum < 0) {
Int3(); // Get John, we ran out of objects for fireballs
return objnum;
}
obj = &Objects[objnum];
new_fireball->fireball_info_index = fireball_type;
new_fireball->fireball_render_type = render_type;
new_fireball->time_elapsed = 0.0f;
new_fireball->objnum = objnum;
new_fireball->current_bitmap = -1;
fb->fireball_info_index = fireball_type;
fb->fireball_render_type = render_type;
fb->time_elapsed = 0.0f;
fb->objnum = objnum;
fb->current_bitmap = -1;
switch( new_fireball->fireball_render_type ) {
switch( fb->fireball_render_type ) {
case FIREBALL_MEDIUM_EXPLOSION:
new_fireball->orient = Random::next() & 7; // 0 - 7
fb->orient = (myrand()>>8) & 7; // 0 - 7
break;
case FIREBALL_LARGE_EXPLOSION:
new_fireball->orient = Random::next(360); // 0 - 359
fb->orient = (myrand()>>8) % 360; // 0 - 359
break;
case FIREBALL_WARP_EFFECT:
// Play sound effect for warp hole opening up
fireball_play_warphole_open_sound(ship_class, new_fireball);
fireball_play_warphole_open_sound(ship_class, fb);
// warp in type
if (reverse) {
new_fireball->orient = 1;
fb->orient = 1;
// if warp out, then reverse the orientation
vm_vec_scale( &obj->orient.vec.fvec, -1.0f ); // Reverse the forward vector
vm_vec_scale( &obj->orient.vec.rvec, -1.0f ); // Reverse the right vector
} else {
new_fireball->orient = 0;
fb->orient = 0;
}
break;
default:
UNREACHABLE("Bad type set in fireball_create");
Int3();
break;
}
if ( new_fireball->fireball_render_type == FIREBALL_WARP_EFFECT ) {
if ( fb->fireball_render_type == FIREBALL_WARP_EFFECT ) {
Assert( warp_lifetime >= 4.0f ); // Warp lifetime must be at least 4 seconds!
if ( warp_lifetime < 4.0f )
warp_lifetime = 4.0f;
new_fireball->total_time = warp_lifetime; // in seconds
fb->total_time = warp_lifetime; // in seconds
} else {
new_fireball->total_time = i2fl(fl->num_frames) / fl->fps; // in seconds
fb->total_time = i2fl(fl->num_frames) / fl->fps; // in seconds
}
fireball_set_framenum(n);
if ( velocity ) {
@@ -902,6 +923,7 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
vm_vec_zero(&obj->phys_info.max_rotvel);
}
Num_fireballs++;
return objnum;
}
@@ -986,9 +1008,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;
@@ -1005,7 +1027,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;
@@ -1036,33 +1058,24 @@ void fireball_render(object* obj, model_draw_list *scene)
MONITOR_INC( NumFireballsRend, 1 );
// Make sure the new system works fine.
Assert(obj->instance > -1);
Assert(static_cast<int>(Fireballs.size()) > obj->instance);
num = obj->instance;
fb = &Fireballs[num];
if ( fb->current_bitmap < 0 )
if ( Fireballs[num].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, NEB_FOG_VISIBILITY_MULT_FIREBALL(obj->radius));
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(Fireballs[num].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(Fireballs[num].current_bitmap, &p, (i2fl(fb->orient)*PI) / 180.0f, obj->radius);
}
break;
@@ -1080,14 +1093,3 @@ void fireball_render(object* obj, model_draw_list *scene)
Int3();
}
}
// Because fireballs are only added and removed in two places, and Unused_fireball_indices is always updated in those places to contain unused indices,
// this very simple code will give you the correct count of currently existing fireballs in use.
int fireball_get_count()
{
int count = static_cast<int>(Fireballs.size()) - static_cast<int>(Unused_fireball_indices.size());
Assert (count >= 0);
return count;
}
+2 -4
View File
@@ -95,7 +95,8 @@ typedef struct fireball {
} fireball;
// end move
extern SCP_vector<fireball> Fireballs;
extern fireball Fireballs[MAX_FIREBALLS];
extern int Num_fireballs;
extern bool fireballs_inited;
@@ -145,7 +146,4 @@ extern int Knossos_warp_ani_used;
extern bool Fireball_use_3d_warp;
// Cyborg - get a count of how many valid fireballs are in the mission.
int fireball_get_count();
#endif /* _FIREBALLS_H */
+1 -1
View File
@@ -91,7 +91,7 @@ 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);
+16 -8
View File
@@ -112,27 +112,35 @@ static int Current_help_page;
void gameplay_help_blit_control_line(int x, int y, int id)
{
int has_key=0, has_joy=0;
char buf[256];
config_item *ci;
auto ci = &Control_config[id];
ci = &Control_config[id];
buf[0] = 0;
strcpy_s(buf, ci->first.textify().c_str());
if (!ci->second.empty()) {
strcat_s(buf, XSTR( ", ", 129));
strcat_s(buf, ci->second.textify().c_str());
if ( ci->key_id >= 0 ) {
strcpy_s(buf, textify_scancode(ci->key_id));
has_key=1;
}
if (ci->empty()) {
if ( ci->joy_id >= 0 ) {
if ( has_key ) {
strcat_s(buf, XSTR( ", ", 129));
}
strcat_s(buf, Joy_button_text[ci->joy_id]);
has_joy=1;
}
if ( !has_key && !has_joy ) {
strcpy_s(buf, XSTR( "no binding", 130));
}
gr_string(x,y,buf,GR_RESIZE_MENU);
// gr_string(x+KEY_DESCRIPTION_OFFSET,y,ci->text,GR_RESIZE_MENU);
gr_string(x+KEY_DESCRIPTION_OFFSET, y, XSTR(ci->text.c_str(), CONTROL_CONFIG_XSTR + id), GR_RESIZE_MENU);
gr_string(x+KEY_DESCRIPTION_OFFSET, y, XSTR(ci->text, CONTROL_CONFIG_XSTR + id), GR_RESIZE_MENU);
}
void gameplay_help_blit_control_line_raw(int x, int y, const char *control_text, const char *control_description)
+1 -4
View File
@@ -239,10 +239,7 @@ bool GameState_Stack_Valid()
// returns one of the GS_STATE_ macros
int gameseq_get_state(int depth)
{
if (!GameState_Stack_Valid())
return GS_STATE_INVALID;
{
Assert(depth <= gs_current_stack);
return gs[gs_current_stack - depth].current_state;
+26 -27
View File
@@ -304,10 +304,6 @@ gamesnd_id gamesnd_get_by_name(const char* name)
{
Assert( Snds.size() <= INT_MAX );
// empty name is not valid!
if (name == nullptr || name[0] == '\0')
return gamesnd_id(-1);
int index = gamesnd_lookup_name(name, Snds);
if (index < 0)
@@ -526,7 +522,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 +540,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 +561,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 +579,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 +598,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);
}
}
}
@@ -679,9 +675,11 @@ void parse_gamesnd_old(game_snd* gs)
{
int temp_min, temp_max;
if (stuff_int_optional(&temp_min) == 2)
ignore_gray_space();
if (stuff_int_optional(&temp_min, true) == 2)
{
if (stuff_int_optional(&temp_max) == 2)
ignore_gray_space();
if (stuff_int_optional(&temp_max, true) == 2)
{
mprintf(("Dutifully converting retail sound %s, '%s' to a 3D sound...\n", gs->name.c_str(), entry.filename));
is_3d = 1;
@@ -695,7 +693,8 @@ void parse_gamesnd_old(game_snd* gs)
}
// check for extra values per Mantis #2408
if (stuff_int_optional(&temp) == 2)
ignore_gray_space();
if (stuff_int_optional(&temp, true) == 2)
{
Warning(LOCATION, "Unexpected extra value %d found for sound '%s' (filename '%s')! Check the format of the sounds.tbl (or .tbm) entry.", temp, gs->name.c_str(), entry.filename);
}
@@ -751,14 +750,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;
}
}
@@ -1349,7 +1348,7 @@ float gamesnd_get_max_duration(game_snd* gs) {
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));
@@ -1362,14 +1361,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;
+13 -4
View File
@@ -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
+2
View File
@@ -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)
+45 -16
View File
@@ -57,6 +57,7 @@ vci Viewer_chase_info; // View chase camera information
vec3d leaning_position;
int Is_standalone;
int Rand_count;
int Interface_last_tick = -1; // last timer tick on flip
@@ -108,6 +109,47 @@ float Noise[NOISE_NUM_FRAMES] = {
0.000000f
};
int myrand()
{
int rval;
rval = rand();
Rand_count++;
// nprintf(("Alan","RAND: %d\n", rval));
return rval;
}
// returns a random number between 0 and 0x7fffffff, or something close to it.
int rand32()
{
if (RAND_MAX == 0x7fff) {
int random32;
// this gets two random 16 bit numbers and stuffs them into a 32 bit number
random32 = (rand() << 16) | rand();
//since rand() returns between 0 and 0x7fff, there needs to be a thing to randomly generate the 16th bit
random32 |= ((rand() & 1) << 15);
return random32;
}
else {
return rand();
}
}
int rand32(int low, int high)
{
int diff;
// get diff - don't allow negative or zero
diff = high - low;
if (diff < 0)
diff = 0;
// To get a range of values between min and max, inclusive:
// random value = min + random number % (max - min + 1)
return (low + rand32() % (diff + 1));
}
// Variables for the loading callback hooks
static int cf_timestamp = -1;
static void (*cf_callback)(int count) = NULL;
@@ -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.
@@ -466,19 +511,3 @@ void insertion_sort(void *array_base, size_t array_size, size_t element_size, in
// free the allocated space
free(current);
}
// Stuff that can't be included in vmallocator.h
std::locale SCP_default_locale("");
void SCP_tolower(char *str)
{
for (; *str != '\0'; ++str)
*str = SCP_tolower(*str);
}
void SCP_toupper(char *str)
{
for (; *str != '\0'; ++str)
*str = SCP_toupper(*str);
}
+1
View File
@@ -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
+1 -1
View File
@@ -67,7 +67,7 @@
#define USED_VARIABLE __attribute__((used))
#if __GNUC__ >= 7
#if SCP_COMPILER_VERSION_MAJOR >= 7
#define FALLTHROUGH [[fallthrough]]
#else
#define FALLTHROUGH
+1 -1
View File
@@ -64,7 +64,7 @@
#define USED_VARIABLE __attribute__((used))
#if __GNUC__ >= 7
#if SCP_COMPILER_VERSION_MAJOR >= 7
#define FALLTHROUGH __attribute__((fallthrough))
#else
#define FALLTHROUGH
-166
View File
@@ -1,166 +0,0 @@
/*
* z64555's Undo system, created for the FreeSpace Source Code project
*/
#include "globalincs/pstypes.h"
#include "globalincs/undosys.h"
#include "globalincs/vmallocator.h"
// Pure virtual deconstructors must be defined.
Undo_item_base::~Undo_item_base() = default;
Undo_system::Undo_system()
: max_undos(10) {};
Undo_system::Undo_system(size_t _undos)
: max_undos(_undos) {};
void Undo_system::clamp_stacks() {
while (undo_stack.size() > max_undos) {
// If we're past the stack limit, de-construct and remove until we're in the limit
// If max_undos is held constant (as it should be), the loop isn't necassary.
// But, it also isn't that much different from an if{} when assembled, so there's no harm in using it
delete undo_stack.front();
undo_stack.pop_front();
}
// This while() shouldn't ever be triggered, but this is a safety to prevent any weird edge-cases.
while (redo_stack.size() > max_undos) {
delete redo_stack.front();
undo_stack.pop_front();
}
}
void Undo_system::clear() {
clear_redo();
for (auto &element : undo_stack) {
delete element;
}
undo_stack.clear();
}
void Undo_system::clear_redo() {
for (auto &element : redo_stack) {
delete element;
}
redo_stack.clear();
}
size_t Undo_system::save_stack(Undo_stack& stack) {
if (stack.size() == 0) {
return 0;
}
clear_redo();
// Copy the stack onto the heap, and push it into our undo_stack as a single item
Undo_item_base *new_item = new Undo_stack(stack);
Assert(new_item != nullptr);
undo_stack.push_back(new_item);
// Input stack is told to untrack the items, so that there's only one deletion handler for them
stack.untrack();
clamp_stacks();
return undo_stack.size();
}
std::pair<const void*, const void*> Undo_system::redo() {
std::pair<const void*, const void*> retval;
if (redo_stack.empty()) {
// Nothing to redo
return {nullptr, nullptr};
}
auto &item = redo_stack.back();
retval = item->restore();
undo_stack.push_back(item);
redo_stack.pop_back();
return retval;
};
std::pair<const void*, const void*> Undo_system::undo() {
std::pair<const void*, const void*> retval;
if (undo_stack.empty()) {
// Nothing to undo
return {nullptr, nullptr};
}
auto &item = undo_stack.back();
retval = item->restore();
redo_stack.push_back(item);
undo_stack.pop_back();
return retval;
};
bool Undo_system::empty() {
return undo_stack.empty();
}
bool Undo_system::empty_redo() {
return redo_stack.empty();
}
size_t Undo_system::size() {
return undo_stack.size();
}
size_t Undo_system::size_redo() {
return redo_stack.size();
}
Undo_stack::Undo_stack()
: reverse(false) {
}
Undo_stack::Undo_stack(size_t size)
: reverse(false) {
stack.reserve(size);
}
Undo_stack::~Undo_stack() {
clear();
}
void Undo_stack::reserve(size_t size) {
stack.reserve(size);
}
std::pair<const void*, const void*> Undo_stack::restore() {
std::pair<const void*, const void*> retval;
if (reverse) {
for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
retval = (*it)->restore();
}
} else {
for (auto &element : stack) {
retval = element->restore();
}
}
reverse = !reverse;
return retval;
}
size_t Undo_stack::size() {
return stack.size();
}
void Undo_stack::untrack() {
stack.clear();
}
void Undo_stack::clear() {
for (auto &element : stack) {
delete element;
}
stack.clear();
}
-326
View File
@@ -1,326 +0,0 @@
/*
* z64555's Undo system, created for the FreeSpace Source Code project
*/
#include "globalincs/pstypes.h"
#include "globalincs/vmallocator.h"
#include <utility>
/**********************************************************
Usage:
First, you need an instance of the Undo_system within your module:
{
Undo_styem Undo_controls;
}
From now on, you can save and restore items. (Just be sure to save them before you change them!)
{
Undo_controls.save(Control_config[z]); // 1. Save the item!!
Control_config[z].bind(cid(CID_KEYBOARD, KEY_UP)); // 2. Make changes to the item!!
Undo_controls.undo(); // 3. Undo's the changes you did in step 2!
Undo_controls.redo(); // 4. Redo's the changes you did in step 2!
}
The undo system can save any type of data, you don't have to have an instance of a system per data type.
!!NOTE!! C style arrays and strings should be wrapped in a std::array when saving. Simply passing the array/string's head won't save the entire array.
{
Undo_controls.save(std::array<int, JOY_AXIS_ACTIONS>(Axis_map_to));
}
In several cases, the client code would like to know exactly what was changed. The undo system provides a mechanism for doing this:
{
Undo_controls.save(Control_config[z], &Control_config[0]); // Save the item, and a reference to its container. Here, we use the location of the first item in Control_config as our reference
Undo_controls.save(Axis_map_to[z], Axis_map_to); // Saves an axis mapping, here, we use the array's head pointer as our reference (since a int[] is the same as a int*)
std::pair<const void*, const void*> ref = Undo_controls.undo(); // ::undo() returns a std::pair<const void*, const void*>, the first member referencing the item that changed, and the second referencing the item's container (if we provided it)
// Once we've stored the return value from ::undo, we can found out what the item is by comparing the second member with containers that we've been saving
if (ref.second == &Control_config[0]) {
cout << "I'm a button!";
Tab = static_cast<Config_item>(ref.first).tab; // Sets the selected tab within the Controls Config menu. We do a static cast here so we can access the Config_item::tab member
} else if (ref.second == Axis_map_to) {
cout << "I'm an axis!";
Tab = SHIP_TAB; // Sets the selected tab within the Controls config menu. Can't do a cast on this one, since it's a simple C array. But we already know where the axes are kept (on the ship tab)
} else {
cout << "I don't know what I am!!! D:"; // Usually an error condition. Depending on the client code this can be fatal or just a minor setback
}
}
Lastly, you can make stacks of undo operations, so that a single op in the system can undo/redo multiple item changes.
{
Undo_stack stack; // We'll want to save multiple changes as a single operation. So we have to make a stack.
for (int i = 0; i < JOY_AXIS_ACTIONS; ++i) {
stack.save(Axis_map_to[i], Axis_map_to); // This example saves a C style array. Does the same thing without a wrapper, but wasteful
Undo_controls.save_stack(stack) // Save the stack as a single item!
}
std::pair<const void*, const void*> ref = Undo_controls.undo();
// Undo rolls through the items as they were saved, should your data operate on the same location, this will apply the changes in the correct sequence
ref.second == Axis_map_to;
ref.first == &Axis_map_to[0]; // ::first references the _last_ item that the undo stack changed
//Also, performing an undo reverses the stack. So when you do a system undo, the changes are applied in reverse
ref = Undo_controls.redo();
ref.second == Axis_map_to;
ref.first == &Axis_map_to[JOY_AXIS_ACTIONS - 1]; // ::first references the _last_ item that the undo stack changed
***********************************************************/
/*!
* @brief Base class of Undo_item. Use this when making an undo stack!
* @sa Undo_item
*/
class Undo_item_base
{
public:
virtual ~Undo_item_base() = 0;
virtual std::pair<const void*, const void*> restore() = 0;
};
/*!
* @brief Class which handles undo operations.
* @details This class works with a single data item, to make a undo or redo stack you'd use something like a
* std::vector or a std::deque of this class. Undo and Redo stacks would be separate. Before restoring data from
* either, you'd save a copy of the item in the opposite stack.
*
* For example, if you're about to restore from the undo stack, you'd first save a copy of the current data to the
* redo stack, then do the undo. The Undo_item in the undo stack can then be popped off the stack.
*
* @note If you want to save changes to an entire C-style array (such as 'int array[]'), try wrapping it in a std::array first.
*
* @sa Undo_item_base
*/
template<typename T> class Undo_item : public Undo_item_base
{
public:
Undo_item()
: dest(nullptr), data(nullptr) {};
Undo_item(T& _data) {
save(_data);
};
Undo_item(T& _data, T* _cont) {
save(_data, _cont);
};
~Undo_item() override {
if (data != nullptr) {
delete data;
}
};
/**
* @brief Restores the saved data
*
* @returns A pair of const void* referencing the affected item and its container (if it was provided)
* @details swaps the data with the destination's, effectively making an undo item into a redo item, and vice versa
*/
std::pair<const void*, const void*> restore() override {
Assert((dest != nullptr) && (data != nullptr));
std::swap(*static_cast<T*>(dest),*static_cast<T*>(data));
return std::pair<const void*, const void*>(dest, container);
};
/*!
* @brief Saves data and its destination as an undo item
*
* @param[in] _data The data to save
* @param[in] _cont The container this data is originally found in
* @details The explicit definition is here to ensure type safety
*/
void save(T& _data, T* _cont = nullptr) {
dest = &_data;
data = new T(_data);
container = _cont;
};
private:
T* dest; //!< Destination of the data
T* data; //!< Reference to a copy of data on the heap
T* container; //!< Optional reference to this item's container
};
/*!
* @brief Class which handles multiple undo operations as a single op within the Undo_system
* @note Caution: Upon deconstruction the tracked Undo_items are deleted.
*/
class Undo_stack : public Undo_item_base
{
public:
Undo_stack();
Undo_stack(size_t size);
~Undo_stack() override;
/*!
* @brief Restores all items within the undo stack
*
* @details Maintains an internal flag which will reverse direction on the next call, thereby re-doing
*
* @returns A pair of references to the last item restored
*/
std::pair<const void*, const void*> restore() override;
/*!
* @brief Saves the item onto the undo stack
*
* @param[in] item The item to save
* @param[in] container (Optional) The container wherein this item is located
* @note Call this _before_ you do your operation on the item
*/
template<typename T>
size_t save(T& item, T* container = nullptr) {
// Create a new instance of Undo_tem, with the correct type reference
Undo_item_base *new_item = new Undo_item<T>(item, container);
stack.push_back(new_item);
return stack.size();
};
/*!
* @brief Calls ::reserve() on the internal vector
*/
void reserve(size_t size);
/*!
* @brief Returns the size of the stack
*/
size_t size();
/*!
* @brief Deletes all tracked items in the internal vector
*/
void clear();
protected:
friend class Undo_system;
/*!
* @brief Calls ::clear() on the internal vector
* @note Caution: This does not delete the tracked Undo_items
*/
void untrack();
private:
bool reverse; // Direction to walk the stack. forward = false, reverse = True
std::vector<Undo_item_base*> stack;
};
/*!
* @brief Generic Undo/Redo system. Save whatever, restore whatever!
*
* @details Currently uses a pair of deques. Would ideally use a ring container so that the actual Undo_item instances don't go anywhere.
*/
class Undo_system
{
public:
Undo_system();
Undo_system(size_t _undos);
/*!
* @brief Deletes all undo and redo data
*/
void clear();
/*!
* @brief Saves the item onto the undo stack
*
* @param[in] item The item to save
* @param[in] container (Optional) The container wherein this item is located
* @note Call this _before_ you do your operation on the item
*/
template<typename T>
size_t save(T& item, T* container = nullptr) {
// De-construct all intances of Undo_item on the redo stack, then clear the stack
clear_redo();
// Create a new instance of Undo_tem, with the correct type reference
Undo_item_base *new_item = new Undo_item<T>(item, container);
undo_stack.push_back(new_item);
clamp_stacks();
return undo_stack.size();
};
/*!
* @brief Saves a stack of undo-items as a single undo-item within the system
*
* @param[in,out] stack The undo stack to save to the undo system. Data in the stack is "unspecified" after this operation
*
* @details The undo system effectively moves the stack into its internal containers, claiming ownership of the
* Undo_items and deleting them upon going out of scope. The input stack is told to untrack the Undo_items in
* the process, so that there is only ever one reference to the Undo_item like a std::unique_ptr
*/
size_t save_stack(Undo_stack& stack);
/*!
* @brief Undo's the last changed item and save the changes into the Redo stack
*
* @returns A pair of const void* which reference the affected item, and its container (if it was provided)
*/
std::pair<const void*, const void*> undo();
/*!
* @brief Redo's the last changed item and save the changes into the undo
*
* @returns A pair of const void* which reference the affected item, and its container (if it was provided)
*/
std::pair<const void*, const void*> redo();
/*!
* @brief Returns the size of the undo stack
*/
size_t size();
/*!
* @brief Returns the size of the redo stack
*/
size_t size_redo();
/*!
* @brief True if undo stack size = 0
*/
bool empty();
/*!
* @brief True if redo stack size = 0
*/
bool empty_redo();
protected:
/*!
* @brief Deletes all redo data
*/
void clear_redo();
/*!
* @brief Clamp the stack sizes to be <= max_undos, deleting data as needed
*/
void clamp_stacks();
private:
size_t max_undos; //!< Max number of Undo's available
/*!
* @brief The undo and redo stacks. These are containers of pointers, because we can't simply store the base class.
* If we did, then the data of the Undo_item instances would be sliced out.
* @{
*/
SCP_deque<Undo_item_base*> undo_stack;
SCP_deque<Undo_item_base*> redo_stack;
//! @}
};
+2 -76
View File
@@ -1,10 +1,10 @@
#ifndef _VMALLOCATOR_H_INCLUDED_
#define _VMALLOCATOR_H_INCLUDED_
/* SCP_vm_allocator - maintained by portej05 (i.e. please don't patch this one yourself!) */
#include <deque>
#include <iterator>
#include <list>
#include <locale>
#include <map>
#include <queue>
#include <sstream>
@@ -25,31 +25,10 @@ bool SCP_vector_contains(SCP_vector<T>& vector, T item) {
template< typename T >
using SCP_list = std::list< T, std::allocator< T > >;
extern std::locale SCP_default_locale;
template< class charT >
charT SCP_toupper(charT ch) { return std::toupper(ch, SCP_default_locale); }
template< class charT >
charT SCP_tolower(charT ch) { return std::tolower(ch, SCP_default_locale); }
typedef std::basic_string<char, std::char_traits<char>, std::allocator<char> > SCP_string;
typedef std::basic_stringstream<char, std::char_traits<char>, std::allocator<char> > SCP_stringstream;
inline void SCP_tolower(SCP_string &str) {
std::transform(str.begin(), str.end(), str.begin(), [](char c) { return SCP_tolower(c); });
}
inline void SCP_toupper(SCP_string &str) {
std::transform(str.begin(), str.end(), str.begin(), [](char c) { return SCP_toupper(c); });
}
extern void SCP_tolower(char *str);
extern void SCP_toupper(char *str);
template< typename T, typename U >
using SCP_map = std::map<T, U, std::less<T>, std::allocator<std::pair<const T, U> > >;
@@ -96,59 +75,6 @@ template <typename T>
using SCP_hash = std::hash<T>;
#endif
struct SCP_string_lcase_hash {
size_t operator()(const SCP_string& elem) const {
SCP_string lcase_copy;
std::transform(elem.begin(), elem.end(), std::back_inserter(lcase_copy),
[](char c) {
return SCP_tolower(c);
});
return SCP_hash<SCP_string>()(lcase_copy);
}
};
struct SCP_string_lcase_equal_to {
bool operator()(const SCP_string& _Left, const SCP_string& _Right) const {
if (_Left.size() != _Right.size()) {
return false;
}
auto l_it = _Left.cbegin();
auto r_it = _Right.cbegin();
while (l_it != _Left.cend()) {
if (SCP_tolower(*l_it) != SCP_tolower(*r_it)) {
return false;
}
++l_it;
++r_it;
}
return true;
}
};
struct SCP_string_lcase_less_than {
bool operator()(const SCP_string& _Left, const SCP_string& _Right) const {
auto l_it = _Left.cbegin();
auto r_it = _Right.cbegin();
while (true) {
if (l_it == _Left.cend()) {
return (r_it != _Right.cend());
} else if (r_it == _Right.cend()) {
return false;
}
auto lch = SCP_tolower(*l_it);
auto rch = SCP_tolower(*r_it);
if (lch < rch) {
return true;
} else if (lch > rch) {
return false;
}
++l_it;
++r_it;
}
return true;
}
};
template <typename Key, typename T, typename Hash = SCP_hash<Key>, typename KeyEqual = std::equal_to<Key>>
using SCP_unordered_map = std::unordered_map<Key, T, Hash, KeyEqual, std::allocator<std::pair<const Key, T>>>;
+1 -3
View File
@@ -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"
@@ -2548,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 -1
View File
@@ -808,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;
+2 -16
View File
@@ -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();
}
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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*/)
{
}
+2 -2
View File
@@ -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;
+37 -18
View File
@@ -130,7 +130,13 @@ void gr_opengl_deferred_lighting_finish()
using namespace graphics;
// We need to precompute how many elements we are going to need
size_t num_data_elements = Lights.size();
size_t num_data_elements = 0;
for (auto& l : Lights) {
++num_data_elements;
if (l.type == Light_Type::Tube) {
++num_data_elements;
}
}
// Get a uniform buffer for our data
auto buffer = gr_get_uniform_buffer(uniform_block_type::Lights, num_data_elements);
@@ -223,16 +229,26 @@ void gr_opengl_deferred_lighting_finish()
vec3d a;
vm_vec_sub(&a, &l.vec, &l.vec2);
auto length = vm_vec_mag(&a);
//Tube light volumes must be extended past the length of their requested light vector
//to allow smooth fall-off from all angles. Since the light volume starts at the mesh
//origin we must extend it here. Later the position will be adjusted as well.
length += light_data->lightRadius * 2.0f;
light_data->scale.xyz.x = l.radb * 1.53f;
light_data->scale.xyz.y = l.radb * 1.53f;
light_data->scale.xyz.z = length;
vm_vec_scale(&light_data->specLightColor, static_tube_factor);
// Tube lights consist of two different types of lights with almost the same properties
light_data = uniformAligner.addTypedElement<deferred_light_data>();
light_data->diffuseLightColor = diffuse;
light_data->specLightColor = spec;
vm_vec_scale(&light_data->specLightColor, static_tube_factor);
light_data->lightRadius = l.radb * 1.5f;
light_data->lightType = LT_POINT;
light_data->scale.xyz.x = l.radb * 1.53f;
light_data->scale.xyz.y = l.radb * 1.53f;
light_data->scale.xyz.z = l.radb * 1.53f;
break;
}
}
@@ -268,17 +284,20 @@ void gr_opengl_deferred_lighting_finish()
gr_bind_uniform_buffer(uniform_block_type::Lights, buffer.getAlignerElementOffset(element_index),
sizeof(graphics::deferred_light_data), buffer.bufferHandle());
vec3d dir, newPos;
vec3d a;
matrix orient;
vm_vec_sub(&dir, &l.vec, &l.vec2);
vm_vector_2_matrix(&orient, &dir, nullptr, nullptr);
//Tube light volumes must be extended past the length of their requested light vector
//to allow smooth fall-off from all angles. Since the light volume starts at the mesh
//origin we must extend it, which has been done above, and then move it backwards one radius.
vm_vec_normalize(&dir);
//1.5 multiplier matches scaling used earlier to scale light radius for all tubes
vm_vec_scale_sub(&newPos, &l.vec2, &dir, l.radb * 1.5f);
gr_opengl_draw_deferred_light_cylinder(&newPos, &orient);
vm_vec_sub(&a, &l.vec, &l.vec2);
vm_vector_2_matrix(&orient, &a, NULL, NULL);
gr_opengl_draw_deferred_light_cylinder(&l.vec2, &orient);
++element_index;
// The next two draws use the same uniform block element
gr_bind_uniform_buffer(uniform_block_type::Lights, buffer.getAlignerElementOffset(element_index),
sizeof(graphics::deferred_light_data), buffer.bufferHandle());
gr_opengl_draw_deferred_light_sphere(&l.vec);
gr_opengl_draw_deferred_light_sphere(&l.vec2);
++element_index;
break;
default:
@@ -301,8 +320,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 +330,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;
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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,
@@ -170,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]);
@@ -442,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) {
@@ -871,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))
+2 -3
View File
@@ -679,7 +679,7 @@ bool Glowpoint_override_save;
extern bool gr_htl_projection_matrix_set;
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)
{
if (Shadow_quality == ShadowQuality::Disabled)
return;
@@ -701,8 +701,7 @@ void gr_opengl_shadow_map_start(matrix4 *shadow_view_matrix, const matrix *light
Glowpoint_override = true;
gr_htl_projection_matrix_set = true;
gr_set_view_matrix(eye_pos, light_orient);
gr_set_view_matrix(&Eye_position, light_orient);
*shadow_view_matrix = gr_view_matrix;
+1 -67
View File
@@ -2,7 +2,6 @@
#include "post_processing.h"
#include "graphics/grinternal.h"
#include "options/Option.h"
#include "parse/parselo.h"
#include "ship/ship.h"
@@ -35,31 +34,6 @@ PostEffectUniformType mapUniformNameToType(const SCP_string& uniform_name)
}
}
// used by In-Game Options menu
bool Post_processing_enable_lightshafts = true;
auto LightshaftsOption =
options::OptionBuilder<bool>("Graphics.Lightshafts", "Lightshafts", "Enable lightshafts (requires post-processing)")
.category("Graphics")
.default_val(true)
.level(options::ExpertLevel::Advanced)
.bind_to(&Post_processing_enable_lightshafts)
.importance(60)
.finish();
int Post_processing_bloom_intensity = 25; // using default value of Cmdline_bloom_intensity
auto BloomIntensityOption = options::OptionBuilder<int>("Graphics.BloomIntensity",
"Bloom intensity",
"Set bloom intensity (requires post-processing)")
.category("Graphics")
.range(0, 200)
.level(options::ExpertLevel::Advanced)
.default_val(25)
.bind_to(&Post_processing_bloom_intensity)
.importance(55)
.finish();
} // namespace
bool PostProcessingManager::parse_table()
@@ -195,49 +169,9 @@ SCP_vector<graphics::post_effect_t>& PostProcessingManager::getPostEffects() { r
const lightshaft_parameters& PostProcessingManager::getLightshaftParams() const { return m_lightshaftParams; }
lightshaft_parameters& PostProcessingManager::getLightshaftParams() { return m_lightshaftParams; }
bool PostProcessingManager::bloomShadersOk() const
{
return m_bloomShadersOk;
}
void PostProcessingManager::setBloomShadersOk(bool ok)
{
m_bloomShadersOk = ok;
}
} // namespace graphics
bool gr_lightshafts_enabled()
{
if (!graphics::Post_processing_manager->getLightshaftParams().on) {
return false;
}
if (Using_in_game_options) {
return graphics::Post_processing_enable_lightshafts;
} else {
return !Cmdline_force_lightshaft_off;
}
}
int gr_bloom_intensity()
{
if (graphics::Post_processing_manager == nullptr || !graphics::Post_processing_manager->bloomShadersOk()) {
return 0;
}
if (Using_in_game_options) {
return graphics::Post_processing_bloom_intensity;
} else {
return Cmdline_bloom_intensity;
}
}
void gr_set_bloom_intensity(int intensity)
{
if (Using_in_game_options) {
graphics::Post_processing_bloom_intensity = intensity;
} else {
Cmdline_bloom_intensity = intensity;
}
return graphics::Post_processing_manager->getLightshaftParams().on && !Cmdline_force_lightshaft_off;
}
+1 -8
View File
@@ -35,7 +35,7 @@ struct post_effect_t {
};
struct lightshaft_parameters {
bool on = true; // enabled by default
bool on = false;
float density = 0.5f;
float weight = 0.02f;
float falloff = 1.0f;
@@ -56,19 +56,12 @@ class PostProcessingManager {
const lightshaft_parameters& getLightshaftParams() const;
lightshaft_parameters& getLightshaftParams();
bool bloomShadersOk() const;
void setBloomShadersOk(bool ok);
private:
SCP_vector<post_effect_t> m_postEffects;
lightshaft_parameters m_lightshaftParams;
bool m_bloomShadersOk = true;
};
} // namespace graphics
bool gr_lightshafts_enabled();
int gr_bloom_intensity();
// used by lab
void gr_set_bloom_intensity(int intensity);
+3 -4
View File
@@ -14,7 +14,6 @@
#include "graphics/matrix.h"
#include "lighting/lighting.h"
#include "math/vecmat.h"
#include "mod_table/mod_table.h"
#include "model/model.h"
#include "model/modelrender.h"
#include "options/Option.h"
@@ -406,7 +405,7 @@ matrix shadows_start_render(matrix *eye_orient, vec3d *eye_pos, float fov, float
Shadow_proj_matrix[2] = Shadow_frustums[2].proj_matrix;
Shadow_proj_matrix[3] = Shadow_frustums[3].proj_matrix;
gr_shadow_map_start(&Shadow_view_matrix, &light_matrix, eye_pos);
gr_shadow_map_start(&Shadow_view_matrix, &light_matrix);
return light_matrix;
}
@@ -434,9 +433,9 @@ void shadows_render_all(float fov, matrix *eye_orient, vec3d *eye_pos)
gr_end_proj_matrix();
gr_end_view_matrix();
// the default cascade distances are a result of some arbitrary tuning to give a good balance of quality and banding.
// these cascade distances are a result of some arbitrary tuning to give a good balance of quality and banding.
// maybe we could use a more programmatic algorithim?
matrix light_matrix = shadows_start_render(eye_orient, eye_pos, fov, gr_screen.clip_aspect, std::get<0>(Shadow_distances), std::get<1>(Shadow_distances), std::get<2>(Shadow_distances), std::get<3>(Shadow_distances));
matrix light_matrix = shadows_start_render(eye_orient, eye_pos, fov, gr_screen.clip_aspect, 200.0f, 600.0f, 2500.0f, 8000.0f);
model_draw_list scene;
object *objp = Objects;
+6 -6
View File
@@ -120,14 +120,14 @@ namespace font
CFILE* fp = cfopen(typeface.c_str(), "rb", CFILE_NORMAL, CF_TYPE_ANY, localize);
if (fp == NULL)
{
mprintf(("Unable to find font file \"%s\"\n", typeface.c_str()));
mprintf(("Unable to find font file \"%s\".", typeface.c_str()));
return NULL;
}
std::unique_ptr<font> fnt(new font());
if (!fnt)
{
mprintf(("Unable to allocate memory for \"%s\"\n", typeface.c_str()));
mprintf(("Unable to allocate memory for \"%s\"", typeface.c_str()));
return NULL;
}
@@ -283,7 +283,7 @@ namespace font
if (fontFile == NULL)
{
mprintf(("Couldn't open font file \"%s\"\n", fileName.c_str()));
mprintf(("Couldn't open font file \"%s\"", fileName.c_str()));
return NULL;
}
@@ -293,14 +293,14 @@ namespace font
if (!fontData)
{
mprintf(("Couldn't allocate " SIZE_T_ARG " bytes for reading font file \"%s\"!\n", size, fileName.c_str()));
mprintf(("Couldn't allocate " SIZE_T_ARG " bytes for reading font file \"%s\"!", size, fileName.c_str()));
cfclose(fontFile);
return NULL;
}
if (!cfread(fontData.get(), (int)size, 1, fontFile))
{
mprintf(("Error while reading font data from \"%s\"\n", fileName.c_str()));
mprintf(("Error while reading font data from \"%s\"", fileName.c_str()));
cfclose(fontFile);
return NULL;
}
@@ -323,7 +323,7 @@ namespace font
if (handle < 0)
{
mprintf(("Couldn't couldn't create font for file \"%s\"\n", fileName.c_str()));
mprintf(("Couldn't couldn't create font for file \"%s\"", fileName.c_str()));
return NULL;
}
+4 -2
View File
@@ -33,8 +33,10 @@ namespace font {
return *this;
}
TrueTypeFontData(const TrueTypeFontData &) = delete;
TrueTypeFontData &operator=(const TrueTypeFontData &) = delete;
private:
TrueTypeFontData(const TrueTypeFontData &) SCP_DELETED_FUNCTION;
TrueTypeFontData &operator=(const TrueTypeFontData &) SCP_DELETED_FUNCTION;
};
// Forward declarations
+8
View File
@@ -40,7 +40,9 @@ class UniformAligner {
{
Assertion(sizeof(T) == _dataSize,
"Sizes of template parameter and runtime size do not match! This probably uses the wrong type.");
#if HAVE_STD_IS_TRIVIALLY_COPYABLE
static_assert(std::is_trivially_copyable<T>::value, "Element type must be trivially copyable!");
#endif
return reinterpret_cast<T*>(addElement());
}
@@ -48,7 +50,9 @@ class UniformAligner {
template<typename THeader>
THeader* getHeader() {
Assertion(sizeof(THeader) == _headerSize, "Header size does not match requested header type!");
#if HAVE_STD_IS_TRIVIALLY_COPYABLE
static_assert(std::is_trivially_copyable<THeader>::value, "Header type must be trivially copyable!");
#endif
return reinterpret_cast<THeader*>(_buffer);
}
@@ -59,7 +63,9 @@ class UniformAligner {
T* getTypedElement(size_t index) {
Assertion(sizeof(T) == _dataSize,
"Sizes of template parameter and runtime size do not match! This probably uses the wrong type.");
#if HAVE_STD_IS_TRIVIALLY_COPYABLE
static_assert(std::is_trivially_copyable<T>::value, "Element type must be trivially copyable!");
#endif
return reinterpret_cast<T*>(getElement(index));
}
@@ -76,7 +82,9 @@ class UniformAligner {
T* nextTypedElement(T* currentEl) {
Assertion(sizeof(T) == _dataSize,
"Sizes of template parameter and runtime size do not match! This probably uses the wrong type.");
#if HAVE_STD_IS_TRIVIALLY_COPYABLE
static_assert(std::is_trivially_copyable<T>::value, "Element type must be trivially copyable!");
#endif
return reinterpret_cast<T*>(nextElement(reinterpret_cast<void*>(currentEl)));
}
+1 -1
View File
@@ -284,7 +284,7 @@ struct fog_data {
vec3d fog_color;
float fog_start;
float fog_density;
float fog_scale;
float zNear;
float zFar;
+1 -1
View File
@@ -121,7 +121,7 @@ bool stub_bm_data(int /*n*/, bitmap* /*bm*/) { return true; }
int stub_maybe_create_shader(shader_type /*shader_t*/, unsigned int /*flags*/) { return -1; }
void stub_shadow_map_start(matrix4* /*shadow_view_matrix*/, const matrix* /*light_matrix*/, vec3d* /*eye_pos*/) {}
void stub_shadow_map_start(matrix4* /*shadow_view_matrix*/, const matrix* /*light_matrix*/) {}
void stub_shadow_map_end() {}
Regular → Executable
+24 -35
View File
@@ -23,7 +23,6 @@
#include "object/object.h"
#include "parse/parselo.h"
#include "sound/sound.h"
#include "utils/Random.h"
#include "weapon/beam.h"
#include "weapon/weapon.h"
@@ -303,7 +302,7 @@ void ssm_create(object *target, vec3d *start, size_t ssm_index, ssm_firing_info
count = Ssm_info[ssm_index].count;
if (Ssm_info[ssm_index].max_count != -1) {
count += Random::next(count, Ssm_info[ssm_index].max_count);
count += rand32(count, Ssm_info[ssm_index].max_count);
}
// override in multiplayer
@@ -400,45 +399,35 @@ void ssm_process()
if(moveup->fireballs[idx] >= 0){
if ((1.0f - fireball_lifeleft_percent(&Objects[moveup->fireballs[idx]])) >= moveup->sinfo.duration) {
weapon_info *wip = &Weapon_info[si->weapon_info_index];
// get an orientation
vec3d temp;
matrix orient;
vm_vec_sub(&temp, &moveup->sinfo.target->pos, &moveup->sinfo.start_pos[idx]);
vm_vec_normalize(&temp);
vm_vector_2_matrix(&orient, &temp, nullptr, nullptr);
// are we a beam? -MageKing17
if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
int num_beams = wip->b_info.beam_type == 5 && (int)wip->b_info.t5info.burst_rot_pattern.size() > 1 ?
(int)wip->b_info.t5info.burst_rot_pattern.size() : 1;
for (int i = 0; i < num_beams; i++) {
beam_fire_info fire_info;
memset(&fire_info, 0, sizeof(beam_fire_info));
beam_fire_info fire_info;
memset(&fire_info, 0, sizeof(beam_fire_info));
fire_info.accuracy = 0.000001f; // this will guarantee a hit
fire_info.shooter = nullptr;
fire_info.turret = nullptr;
fire_info.target = moveup->sinfo.target;
fire_info.target_subsys = nullptr;
fire_info.bfi_flags |= BFIF_FLOATING_BEAM;
fire_info.starting_pos = moveup->sinfo.start_pos[idx];
fire_info.beam_info_index = si->weapon_info_index;
fire_info.team = static_cast<char>(moveup->sinfo.ssm_team);
fire_info.fire_method = BFM_SUBSPACE_STRIKE;
fire_info.burst_index = i;
fire_info.accuracy = 0.000001f; // this will guarantee a hit
fire_info.shooter = NULL;
fire_info.turret = NULL;
fire_info.target = moveup->sinfo.target;
fire_info.target_subsys = NULL;
fire_info.bfi_flags |= BFIF_FLOATING_BEAM;
fire_info.starting_pos = moveup->sinfo.start_pos[idx];
fire_info.beam_info_index = si->weapon_info_index;
fire_info.team = static_cast<char>(moveup->sinfo.ssm_team);
fire_info.fire_method = BFM_SUBSPACE_STRIKE;
// Fill target_pos but DONT turn on BFIF_TARGETING_COORDS
// It would mess up normal beams but type 5s can still make use of this
fire_info.target_pos1 = orient.vec.fvec + fire_info.starting_pos;
// fire the beam
beam_fire(&fire_info);
// fire the beam
beam_fire(&fire_info);
moveup->done_flags[idx] = true;
}
moveup->done_flags[idx] = true;
} else {
// get an orientation
vec3d temp;
matrix orient;
vm_vec_sub(&temp, &moveup->sinfo.target->pos, &moveup->sinfo.start_pos[idx]);
vm_vec_normalize(&temp);
vm_vector_2_matrix(&orient, &temp, NULL, NULL);
// fire the missile and flash the screen
weapon_objnum = weapon_create(&moveup->sinfo.start_pos[idx], &orient, si->weapon_info_index, -1, -1, 1);
+3 -3
View File
@@ -643,7 +643,7 @@ void HudGaugeBrackets::renderBoundingBrackets(int x1, int y1, int x2, int y2, in
break;
case OBJ_DEBRIS:
tinfo_name = XSTR("debris", 348);
tinfo_name = XSTR("Debris", 348);
break;
case OBJ_WEAPON:
tinfo_name = Weapon_info[Weapons[t_objp->instance].weapon_info_index].get_display_name();
@@ -653,10 +653,10 @@ void HudGaugeBrackets::renderBoundingBrackets(int x1, int y1, int x2, int y2, in
case ASTEROID_TYPE_SMALL:
case ASTEROID_TYPE_MEDIUM:
case ASTEROID_TYPE_LARGE:
tinfo_name = XSTR("asteroid", 431);
tinfo_name = NOX("Asteroid");
break;
default:
tinfo_name = XSTR("debris", 348);
tinfo_name = XSTR("Debris", 348);
}
break;
case OBJ_JUMP_NODE:
Loaded 100 of 472 files, more files were not shown because too many files have changed in this diff. Show more