Compare commits

..
Author SHA1 Message Date
SirKnightly f25d95041d Automated build commit 2021-01-27 17:07:03 +00:00
339 changed files with 11178 additions and 22150 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
+38 -40
View File
@@ -15,22 +15,20 @@ jobs:
configuration: [FastDebug, Release]
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
container: ghcr.io/scp-fs2open/linux_build:sha-a8ca321
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
# - 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 }}
- uses: actions/checkout@v1
name: Checkout
@@ -109,30 +107,30 @@ jobs:
name: Checkout
with:
submodules: true
- name: Cache Qt
id: cache-qt-win
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/../Qt
key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
- name: Install Qt (32 bit)
uses: jurplel/install-qt-action@v2
if: ${{ matrix.arch == 'Win32' }}
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
arch: win32_msvc2017
cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
aqtversion: ==0.8
- name: Install Qt (64 bit)
uses: jurplel/install-qt-action@v2
if: ${{ matrix.arch == 'x64' }}
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
arch: win64_msvc2017_64
cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
aqtversion: ==0.8
# - name: Cache Qt
# id: cache-qt-win
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt (32 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'Win32' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win32_msvc2017
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
# - name: Install Qt (64 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'x64' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win64_msvc2017_64
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
- name: Configure CMake
env:
@@ -146,7 +144,7 @@ jobs:
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
-DFSO_USE_VOICEREC="ON" -DMSVC_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_QTFRED=ON -DFSO_BUILD_TESTS=ON \
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
-DFSO_INSTALL_DEBUG_FILES="ON" -A "$ARCHITECTURE" \
-G "Visual Studio 16 2019" -T "v142" ..
- name: Compile
+38 -40
View File
@@ -39,22 +39,20 @@ jobs:
name: Linux
needs: create_release
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-e29f882
container: ghcr.io/scp-fs2open/linux_build:sha-a8ca321
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
# - 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 }}
- uses: actions/checkout@v1
name: Checkout
@@ -144,30 +142,30 @@ jobs:
name: Checkout
with:
submodules: true
- name: Cache Qt
id: cache-qt-win
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/../Qt
key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
- name: Install Qt (32 bit)
uses: jurplel/install-qt-action@v2
if: ${{ matrix.arch == 'Win32' }}
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
arch: win32_msvc2017
cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
aqtversion: ==0.8
- name: Install Qt (64 bit)
uses: jurplel/install-qt-action@v2
if: ${{ matrix.arch == 'x64' }}
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
arch: win64_msvc2017_64
cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
aqtversion: ==0.8
# - name: Cache Qt
# id: cache-qt-win
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt (32 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'Win32' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win32_msvc2017
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
# - name: Install Qt (64 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'x64' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win64_msvc2017_64
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
- name: Configure CMake
env:
@@ -181,7 +179,7 @@ jobs:
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
-DFSO_USE_VOICEREC="ON" -DMSVC_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_QTFRED=ON -DFSO_BUILD_TESTS=ON \
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
-DFSO_INSTALL_DEBUG_FILES="ON" -A "$ARCHITECTURE" \
-G "Visual Studio 16 2019" -T "v142" ..
- name: Compile
+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-e29f882
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
+39 -40
View File
@@ -22,22 +22,21 @@ 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-e29f882
container: ghcr.io/scp-fs2open/linux_build:sha-e977d36
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
# - 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'
- uses: actions/checkout@v1
name: Checkout
@@ -84,30 +83,30 @@ jobs:
name: Checkout
with:
submodules: true
- name: Cache Qt
id: cache-qt-win
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/../Qt
key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
- name: Install Qt (32 bit)
uses: jurplel/install-qt-action@v2
if: ${{ matrix.compiler == 'MSVC' && matrix.arch == 'Win32' }}
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
arch: win32_msvc2017
cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
aqtversion: ==0.8
- name: Install Qt (64 bit)
uses: jurplel/install-qt-action@v2
if: ${{ matrix.compiler == 'MSVC' && matrix.arch == 'x64' }}
with:
version: ${{ env.QT_VERSION }}
dir: ${{ github.workspace }}/..
arch: win64_msvc2017_64
cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
aqtversion: ==0.8
# - name: Cache Qt
# id: cache-qt-win
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt (32 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.compiler == 'MSVC' && matrix.arch == 'Win32' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win32_msvc2017
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
# - name: Install Qt (64 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.compiler == 'MSVC' && matrix.arch == 'x64' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win64_msvc2017_64
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
- name: Configure CMake
env:
@@ -125,7 +124,7 @@ jobs:
else
cmake -DFSO_USE_SPEECH="ON" -DFSO_FATAL_WARNINGS="ON" -DFSO_USE_VOICEREC="OFF" -DFSO_BUILD_TESTS="ON" \
-DMSVC_SIMD_INSTRUCTIONS=SSE2 -DFSO_BUILD_FRED2="ON" -G "Visual Studio 16 2019" \
-DFSO_BUILD_QTFRED=ON -T "v142" -A "$ARCHITECTURE" ..
-DFSO_BUILD_QTFRED=OFF -T "v142" -A "$ARCHITECTURE" ..
fi
- name: Compile
working-directory: ./build
@@ -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 }}
+1 -2
View File
@@ -32,5 +32,4 @@ export LD_LIBRARY_PATH
cmake -G Ninja -DFSO_FATAL_WARNINGS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON $CMAKE_OPTIONS \
-DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_BUILD_APPIMAGE=ON -DCMAKE_BUILD_TYPE=$CONFIGURATION \
-DFFMPEG_USE_PRECOMPILED=ON -DFSO_BUILD_TESTS=ON -DFSO_BUILD_INCLUDED_LIBS=ON -DFSO_BUILD_QTFRED=ON \
-DSHADERS_ENABLE_COMPILATION=ON ..
-DFFMPEG_USE_PRECOMPILED=ON -DFSO_BUILD_TESTS=ON -DFSO_BUILD_INCLUDED_LIBS=ON -DFSO_BUILD_QTFRED=OFF ..
+1 -1
View File
@@ -6,7 +6,7 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
include("${PROJECT_SOURCE_DIR}/version_override.cmake")
endif()
set_if_not_defined(FSO_VERSION_MAJOR 21)
set_if_not_defined(FSO_VERSION_MAJOR 20)
set_if_not_defined(FSO_VERSION_MINOR 1)
set_if_not_defined(FSO_VERSION_BUILD 0)
-2
View File
@@ -63,8 +63,6 @@ ENDIF(FSO_USE_SPEECH)
TARGET_LINK_LIBRARIES(code PUBLIC jansson)
target_link_libraries(code PUBLIC variant)
# Headers for standalone "gui" on UNIX
IF(UNIX)
TARGET_LINK_LIBRARIES(code PUBLIC ${MONGOOSE_LIBS})
-8
View File
@@ -1,8 +0,0 @@
#include "Action.h"
namespace actions {
Action::~Action() = default;
}
-23
View File
@@ -1,23 +0,0 @@
#pragma once
#include "common.h"
namespace actions {
enum class ActionResult
{
Finished,
Errored,
Wait
};
class Action {
public:
virtual ~Action() = 0;
virtual ActionResult execute(ProgramLocals& locals) const = 0;
virtual std::unique_ptr<Action> clone() const = 0;
};
} // namespace actions
-21
View File
@@ -1,21 +0,0 @@
#include "ActionDefinition.h"
namespace actions {
ActionDefinition::ActionDefinition(SCP_string name, SCP_vector<ActionParameter> parameters)
: m_name(std::move(name)), m_parameters(std::move(parameters))
{
}
ActionDefinition::~ActionDefinition() = default;
const SCP_string& ActionDefinition::getName() const
{
return m_name;
}
const SCP_vector<ActionParameter>& ActionDefinition::getParameters() const
{
return m_parameters;
}
} // namespace actions
-33
View File
@@ -1,33 +0,0 @@
#pragma once
#include "Action.h"
#include "actions/expression/ActionExpression.h"
#include "actions/expression/Value.h"
namespace actions {
struct ActionParameter {
SCP_string name;
expression::ValueType type;
};
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;
virtual flagset<ProgramContextFlags> getRequiredContext() const = 0;
virtual std::unique_ptr<Action> createAction(
const SCP_unordered_map<SCP_string, expression::ActionExpression>& parameterExpressions) const = 0;
private:
SCP_string m_name;
SCP_vector<ActionParameter> m_parameters;
};
} // namespace actions
-145
View File
@@ -1,145 +0,0 @@
#include "ActionDefinitionManager.h"
#include "BuiltinActionDefinition.h"
#include "actions/types/MoveToSubmodel.h"
#include "actions/types/ParticleEffectAction.h"
#include "actions/types/PlaySoundAction.h"
#include "actions/types/SetDirectionAction.h"
#include "actions/types/SetPositionAction.h"
#include "actions/types/WaitAction.h"
#include "utils/join_string.h"
namespace actions {
namespace {
void parseActionParameters(const SCP_vector<ActionParameter>& params,
SCP_unordered_map<SCP_string, expression::ActionExpression>& parameterExpressions,
const expression::ParseContext& context)
{
// We do not impose a set order of the parameters so we need to do something similar to the outer loop and
// check for every possibility in a loop.
for (size_t i = 0; i < params.size(); ++i) {
// We might be doing some empty loops here in case of errors but there is a bound so this will finish
// eventually
for (const auto& parameter : params) {
// Do not check for parameters which we already found
if (parameterExpressions.find(parameter.name) != parameterExpressions.end()) {
continue;
}
const auto paramString = "+" + parameter.name + ":";
if (!optional_string(paramString.c_str())) {
continue;
}
// We found our parameter!
parameterExpressions[parameter.name] =
expression::ActionExpression::parseFromTable(parameter.type, context);
break;
}
}
}
} // namespace
const ActionDefinitionManager& ActionDefinitionManager::instance()
{
static const ActionDefinitionManager manager;
return manager;
}
void ActionDefinitionManager::addDefinition(std::unique_ptr<ActionDefinition> def)
{
m_definitions.push_back(std::move(def));
}
std::unique_ptr<Action> ActionDefinitionManager::parseAction(const flagset<ProgramContextFlags>& context_flags,
const expression::ParseContext& context) const
{
// We search through all definitions for every time we want to check for an action. This is not the most efficient
// way to handle this since it results in an O(n^2) runtime. A better alternative would be to either construct a
// regex with all the accepted actions as alternatives (although runtime of this is also doubtful) or peek at the
// next token, extract the relevant part (The stuff between "+" and ":") and then look that up in a map. Then we
// would get down to O(n).
// Since this only happens at parse time, this is good enough for now.
for (const auto& actionDef : m_definitions) {
const auto parseString = "+" + actionDef->getName() + ":";
if (!optional_string(parseString.c_str())) {
continue;
}
SCP_unordered_map<SCP_string, expression::ActionExpression> parameterExpressions;
const auto& params = actionDef->getParameters();
if (params.size() == 1) {
// Single parameters are expected to be placed directly after the action name
parameterExpressions[params.front().name] =
expression::ActionExpression::parseFromTable(params.front().type, context);
} else {
parseActionParameters(params, parameterExpressions, context);
if (parameterExpressions.size() != params.size()) {
SCP_vector<ActionParameter> missingParameters;
std::copy_if(params.cbegin(),
params.cend(),
std::back_inserter(missingParameters),
[&parameterExpressions](const ActionParameter& param) {
const auto iter = parameterExpressions.find(param.name);
return iter == parameterExpressions.cend();
});
SCP_vector<SCP_string> missingParameterNames;
std::transform(missingParameters.cbegin(),
missingParameters.cend(),
std::back_inserter(missingParameterNames),
[](const ActionParameter& param) { return "+" + param.name; });
const auto namesList = util::join_container(missingParameterNames, ", ");
error_display(0, "Missing parameters for action '%s': %s", parseString.c_str(), namesList.c_str());
// Fix up the parameter list so that we can continue parsing
for (const auto& missingParam : missingParameters) {
parameterExpressions[missingParam.name] = expression::ActionExpression();
}
}
}
auto actionInstance = actionDef->createAction(parameterExpressions);
const auto requiredFlags = actionDef->getRequiredContext();
if ((context_flags & requiredFlags) != requiredFlags) {
// We are missing at least one context flag so this action is not valid here. We still return the action
// here though since that is the only way we can keep parsing the table without throwing random error
// messages at the user. However, this will trigger an Assertion (in debug) or crashes when the action is
// actually run later.
error_display(0,
"The action %s is not available in the current context. This will lead to an Assertion failure when "
"this action is triggered.",
parseString.c_str());
}
return actionInstance;
}
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
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "ActionDefinition.h"
namespace actions {
class ActionDefinitionManager {
public:
// This type is supposed to be global so it may not be copied
ActionDefinitionManager(const ActionDefinitionManager&) = delete;
ActionDefinitionManager& operator=(const ActionDefinitionManager&) = delete;
ActionDefinitionManager(ActionDefinitionManager&&) noexcept = default;
ActionDefinitionManager& operator=(ActionDefinitionManager&&) noexcept = default;
std::unique_ptr<Action> parseAction(const flagset<ProgramContextFlags>& context_flags,
const expression::ParseContext& context) const;
static const ActionDefinitionManager& instance();
private:
ActionDefinitionManager();
void addDefinition(std::unique_ptr<ActionDefinition> def);
SCP_vector<std::unique_ptr<ActionDefinition>> m_definitions;
};
} // namespace actions
-37
View File
@@ -1,37 +0,0 @@
#pragma once
#include "ActionDefinition.h"
#include "expression/Value.h"
namespace actions {
template <typename TAction>
class BuiltinActionDefinition : public ActionDefinition {
using ActionValueType = typename TAction::ValueType;
public:
BuiltinActionDefinition(SCP_string name)
: ActionDefinition(std::move(name), {{"parameter", expression::ValueTypeTraits<ActionValueType>::type}})
{
}
~BuiltinActionDefinition() override = default;
flagset<ProgramContextFlags> getRequiredContext() const override
{
return flagset<ProgramContextFlags>(TAction::getRequiredExecutionContextFlags());
}
std::unique_ptr<Action> createAction(
const SCP_unordered_map<SCP_string, expression::ActionExpression>& parameterExpressions) const override
{
const auto paramIter = parameterExpressions.find("parameter");
// This should have been caught earlier
Assertion(paramIter != parameterExpressions.cend(), "Could not find built-in parameter!");
return std::unique_ptr<Action>(new TAction(paramIter->second.template asTyped<ActionValueType>()));
}
};
} // namespace actions
-174
View File
@@ -1,174 +0,0 @@
#include "Program.h"
#include "Action.h"
#include "actions/ActionDefinitionManager.h"
#include "executor/global_executors.h"
#include "tracing/Monitor.h"
#include "tracing/categories.h"
#include "tracing/tracing.h"
namespace {
using namespace actions;
tracing::Monitor<int> RunningProgramsMonitor("RunningPrograms", 0);
struct ProgramRunner {
tracing::RunningCounter runningCounter{RunningProgramsMonitor};
ProgramInstance instance;
explicit ProgramRunner(const ProgramInstance& _instance) : instance(_instance) {}
executor::Executor::CallbackResult operator()()
{
TRACE_SCOPE(tracing::ProgramStepOne);
auto res = instance.step();
switch (res) {
case ProgramState::Running:
// If the program is still running then we need to reschedule our execution
return executor::Executor::CallbackResult::Reschedule;
case ProgramState::Done:
case ProgramState::Errored:
default:
return executor::Executor::CallbackResult::Done;
}
}
};
} // namespace
namespace actions {
ProgramInstance::ProgramInstance(const actions::Program* program) : _program(program) {}
ProgramState ProgramInstance::step()
{
if (!_locals.host.IsValid()) {
return ProgramState::Done;
}
auto& actions = _program->getActions();
for (; _currentInstruction < actions.size(); ++_currentInstruction) {
auto& action = actions[_currentInstruction];
auto result = action->execute(_locals);
if (result == ActionResult::Errored) {
return ProgramState::Errored;
}
if (result == ActionResult::Wait) {
// Action is not done yet, call again next frame
return ProgramState::Running;
}
// Action finished, run next action in program
}
// End of program reached, we are done now
return ProgramState::Done;
}
ProgramLocals& ProgramInstance::locals()
{
return _locals;
}
const ProgramLocals& ProgramInstance::locals() const
{
return _locals;
}
const SCP_vector<std::unique_ptr<actions::Action>>& Program::getActions() const
{
return _actions;
}
Program::Program() = default;
ProgramInstance Program::newInstance() const
{
return ProgramInstance(this);
}
void Program::parseActions(const flagset<ProgramContextFlags>& context_flags, const expression::ParseContext& context)
{
while (true) {
auto action = ActionDefinitionManager::instance().parseAction(context_flags, context);
if (!action) {
break;
}
_actions.push_back(std::move(action));
}
}
Program::Program(const Program& other) { *this = other; }
Program& Program::operator=(const Program& other)
{
_actions.clear();
_actions.reserve(other._actions.size());
for (auto& action : other._actions) {
_actions.emplace_back(action->clone());
}
return *this;
}
Program& Program::operator=(Program&&) noexcept = default;
Program::Program(Program&&) noexcept = default;
bool ProgramSet::isEmpty() const
{
return _programs.empty();
}
ProgramSet::ProgramSet() = default;
ProgramSet::ProgramSet(const flagset<ProgramContextFlags>& context_flags) : _contextFlags(context_flags) {}
void ProgramSet::start(object* objp, const vec3d* local_pos, const matrix* local_orient, int submodel) const
{
if (_contextFlags[ProgramContextFlags::HasObject]) {
Assertion(objp != nullptr, "Invalid object pointer supplied.");
}
if (_contextFlags[ProgramContextFlags::HasSubobject]) {
Assertion(submodel >= 0, "Invalid subobject supplied.");
}
for (const auto& prog : _programs) {
auto instance = prog.newInstance();
instance.locals().host = object_h(objp);
instance.locals().localPosition = *local_pos;
instance.locals().localOrient = *local_orient;
instance.locals().hostSubobject = submodel;
instance.locals().variables = getDefaultTableVariables();
// Just use the executor system to take care of running this program
executor::OnSimulationExecutor->post(ProgramRunner(instance));
}
}
ProgramSet ProgramSet::parseProgramSet(const char* tag, const flagset<ProgramContextFlags>& context_flags)
{
ProgramSet set(context_flags);
const auto parseContext = getTableParseContext();
while (optional_string(tag)) {
Program p;
p.parseActions(context_flags, parseContext);
set._programs.push_back(std::move(p));
}
return set;
}
} // namespace actions
-112
View File
@@ -1,112 +0,0 @@
#pragma once
#include "globalincs/pstypes.h"
#include "Action.h"
#include "common.h"
namespace actions {
class Program;
class ProgramSet;
enum class ProgramState
{
Running,
Done,
Errored
};
/**
* @brief An instance of a program execution
*
* A program instance contains all information related to a single execution of a program. A program instance is
* independent from other instances. Local variables used by actions are stored in a specific structure which can be
* used by actions to keep track of state in this specific instance.
*/
class ProgramInstance {
size_t _currentInstruction = 0;
ProgramLocals _locals;
const Program* _program = nullptr;
public:
explicit ProgramInstance(const Program* program);
ProgramState step();
ProgramLocals& locals();
const ProgramLocals& locals() const;
};
/**
* @brief A program specified in a table
*
* A program is a sequential collection of Actions which are executed in sequence.
*/
class Program {
SCP_vector<std::unique_ptr<Action>> _actions;
public:
Program();
Program(const Program& other);
Program& operator=(const Program& other);
Program(Program&& other) noexcept;
Program& operator=(Program&& other) noexcept;
const SCP_vector<std::unique_ptr<actions::Action>>& getActions() const;
/**
* @brief Creates a new program instance from this program
*
* The program instance is initialized to its initial values.
*
* @return The new program instance
*/
ProgramInstance newInstance() const;
void parseActions(const flagset<ProgramContextFlags>& context_flags, const expression::ParseContext& context);
};
/**
* @brief A set of parsed programs
*
* Programs are parsed in sets of zero or more programs to make it possible for the content creator to split up
* programs.
*/
class ProgramSet {
flagset<ProgramContextFlags> _contextFlags;
SCP_vector<Program> _programs;
public:
ProgramSet();
explicit ProgramSet(const flagset<ProgramContextFlags>& context_flags);
bool isEmpty() const;
/**
* @brief Starts the execution of all programs within this set with the specified parameters
* @param objp The object on which this program is executed
* @param local_pos The local position relative to the object or the subsystem if specified
* @param local_orien The local orientation relative to the object or the subsystem if specified
* @param submodel The submodel of the model to which this program is attached to.
*/
void start(object* objp, const vec3d* local_pos, const matrix* local_orient, int submodel = -1) const;
/**
* @brief Parses a program set of zero or more programs
*
* The tag is entirely optional and if it doesn't appear in the current table then an empty set will be returned
* which does nothing if started.
*
* @param tag The tag which is required before a program can be specified
* @param context_flags A set of flags which specify in which environment the program will be used. This is used
* mainly for validating the used actions.
* @return The parsed program set.
*/
static ProgramSet parseProgramSet(const char* tag, const flagset<ProgramContextFlags>& context_flags = {});
};
} // namespace actions
-27
View File
@@ -1,27 +0,0 @@
#include "common.h"
namespace actions {
expression::ParseContext getTableParseContext()
{
expression::ParseContext ctx;
auto& locals = ctx.variables.addScope("locals");
locals.addMember("position", expression::ValueType::Vector);
locals.addMember("direction", expression::ValueType::Vector);
return ctx;
}
expression::ProgramVariables getDefaultTableVariables()
{
expression::ProgramVariables vars;
vars.setValue({"locals", "position"}, expression::Value(vmd_zero_vector));
vars.setValue({"locals", "direction"}, expression::Value(vmd_zero_vector));
return vars;
}
} // namespace actions
-59
View File
@@ -1,59 +0,0 @@
#pragma once
#include "globalincs/flagset.h"
#include "actions/expression/ParseContext.h"
#include "actions/expression/ProgramVariables.h"
#include "math/vecmat.h"
#include "object/object.h"
class object;
class ship_subsys;
namespace actions {
struct ProgramLocals {
object_h host;
vec3d localPosition = vmd_zero_vector;
matrix localOrient = vmd_identity_matrix;
int hostSubobject = -1;
int waitTimestamp = 0;
expression::ProgramVariables variables;
};
/**
* @brief A list of flags for altering the program parsing behavior
*
* This should be used for differentiating between different program environments when validating the program (e.g. a
* particle effect requires an object host).
*/
// clang-format off
FLAG_LIST(ProgramContextFlags) {
HasObject = 0, //!< The program will be started with an object host
HasSubobject, //!< The program will be started with a subobject host
NUM_VALUES
};
// clang-format on
/**
* @brief Constructs the "table" parse context
*
* This is the context that is available when parsing a program from an external table (e.g. ships or weapons).
*
* @return The parse context
*/
expression::ParseContext getTableParseContext();
/**
* @brief Gets a program variables instance initialized with the default values
*
* The values are consistent with what is required for the default table parse context;
*
* @return The program variables
*/
expression::ProgramVariables getDefaultTableVariables();
} // namespace actions
@@ -1,66 +0,0 @@
#include "ActionExpression.h"
#include "ExpressionParser.h"
#include "TypeDefinition.h"
#include "actions/expression/nodes/AbstractExpression.h"
namespace actions {
namespace expression {
namespace {
SCP_string getTypeName(ValueType type)
{
const auto& typeDef = TypeDefinition::forValueType(type);
return typeDef.getName();
}
} // namespace
ActionExpression ActionExpression::parseFromTable(ValueType expectedReturnType, const ParseContext& context)
{
SCP_string expressionText;
stuff_string(expressionText, F_NAME);
ExpressionParser parser(expressionText);
auto expression = parser.parse(context);
if (!expression) {
error_display(0, "Failed to parse action expression:\n%s", parser.getErrorText().c_str());
return ActionExpression();
}
auto type = expression->getExpressionType();
// Check if the type this evaluates to matches what we expect
if (!checkTypeWithImplicitConversion(type, expectedReturnType)) {
error_display(0,
"Expression evaluates to type <%s> but <%s> was expected!",
getTypeName(type).c_str(),
getTypeName(expectedReturnType).c_str());
return ActionExpression();
}
// Everything is valid
return ActionExpression(expression);
}
Value ActionExpression::execute(const ProgramVariables& variables) const
{
// Do not crash on invalid expressions
if (!isValid()) {
return Value();
}
return m_expression->execute(variables);
}
bool ActionExpression::isValid() const
{
return m_expression != nullptr;
}
} // namespace expression
} // namespace actions
@@ -1,83 +0,0 @@
#pragma once
#include "ParseContext.h"
#include "ProgramVariables.h"
#include "TypeDefinition.h"
#include "parse/parselo.h"
namespace actions {
namespace expression {
namespace nodes {
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;
Value execute(const ProgramVariables& variables) const;
bool isValid() const;
template <typename T>
TypedActionExpression<T> asTyped() const;
static ActionExpression parseFromTable(ValueType expectedReturnType, const ParseContext& context);
protected:
explicit ActionExpression(std::shared_ptr<nodes::AbstractExpression> expression)
: m_expression(std::move(expression))
{
}
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:
explicit TypedActionExpression(ActionExpression expression) : m_expression(std::move(expression)) {}
T execute(const ProgramVariables& variables) const
{
// Do not crash on invalid expressions
if (!m_expression.isValid()) {
return T();
}
return m_expression.execute(variables).template get<T>();
}
static TypedActionExpression<T> parseFromTable(const ParseContext& context)
{
return ActionExpression::parseFromTable(ValueTypeTraits<T>::type, context).template asTyped<T>();
}
private:
ActionExpression m_expression;
};
template <typename T>
TypedActionExpression<T> ActionExpression::asTyped() const
{
return TypedActionExpression<T>(*this);
}
} // namespace expression
} // namespace actions
@@ -1,194 +0,0 @@
#include "ExpressionParser.h"
#include "actions/expression/nodes/FunctionCallExpression.h"
#include "actions/expression/nodes/LiteralExpression.h"
#include "actions/expression/nodes/RandomRangeExpression.h"
#include "actions/expression/nodes/VariableReferenceExpression.h"
#include "actions/expression/nodes/VectorConstructorExpression.h"
#include "libs/antlr/ErrorListener.h"
PUSH_SUPPRESS_WARNINGS
#include "action_expression/generated/ActionExpressionLexer.h"
#include "action_expression/generated/ActionExpressionParser.h"
#include "action_expression/generated/ActionExpressionVisitor.h"
POP_SUPPRESS_WARNINGS
namespace actions {
namespace expression {
namespace {
class ExpressionBuilderVisitor : public ActionExpressionVisitor {
public:
antlrcpp::Any visitExpression_main(ActionExpressionParser::Expression_mainContext* context) override
{
return visit(context->expression());
}
antlrcpp::Any visitExpression(ActionExpressionParser::ExpressionContext* context) override
{
if (context->value_expression() != nullptr) {
return visit(context->value_expression());
}
if (context->parenthesis_expression()) {
return visit(context->parenthesis_expression());
}
if (context->random_range_expression()) {
return visit(context->random_range_expression());
}
if (context->variable_reference_expression()) {
return visit(context->variable_reference_expression());
}
// This is an operator so treat it as one.
antlr4::Token* operatorTok;
SCP_string operatorText;
if (context->PLUS() != nullptr) {
operatorTok = context->PLUS()->getSymbol();
operatorText = context->PLUS()->getText();
} else {
operatorTok = context->MINUS()->getSymbol();
operatorText = context->MINUS()->getText();
}
const auto leftExpr = visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>();
const auto rightExpr = visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>();
auto operatorExpression = std::make_shared<nodes::FunctionCallExpression>(operatorTok,
true,
std::move(operatorText),
SCP_vector<std::shared_ptr<nodes::AbstractExpression>>{leftExpr, rightExpr});
return std::static_pointer_cast<nodes::AbstractExpression>(operatorExpression);
}
antlrcpp::Any visitVariable_reference_expression(
ActionExpressionParser::Variable_reference_expressionContext* context) override
{
const auto identifiers = context->IDENTIFIER();
SCP_vector<SCP_string> partNames;
partNames.reserve(identifiers.size());
std::transform(identifiers.begin(),
identifiers.end(),
std::back_inserter(partNames),
[](antlr4::tree::TerminalNode* node) { return node->getText(); });
SCP_vector<antlr4::Token*> partTokens;
partTokens.reserve(identifiers.size());
std::transform(identifiers.begin(),
identifiers.end(),
std::back_inserter(partTokens),
[](antlr4::tree::TerminalNode* node) { return node->getSymbol(); });
auto expression =
std::make_shared<nodes::VariableReferenceExpression>(std::move(partNames), std::move(partTokens));
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
}
antlrcpp::Any visitRandom_range_expression(ActionExpressionParser::Random_range_expressionContext* context) override
{
auto expression = std::make_shared<nodes::RandomRangeExpression>(context->RAND_L_PAREN()->getSymbol(),
visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>(),
visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>());
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
}
antlrcpp::Any visitLiteral_expression(ActionExpressionParser::Literal_expressionContext* context) override
{
std::shared_ptr<nodes::LiteralExpression> expression;
if (context->FLOAT() != nullptr) {
expression = std::make_shared<nodes::LiteralExpression>(context->FLOAT()->getSymbol(),
Value(std::stof(context->FLOAT()->getText())));
} else if (context->INT() != nullptr) {
expression = std::make_shared<nodes::LiteralExpression>(context->INT()->getSymbol(),
Value(std::stoi(context->INT()->getText())));
} else {
const auto idText = context->STRING()->getText();
// Cut off the first and last characters since those are the < > braces
expression = std::make_shared<nodes::LiteralExpression>(context->STRING()->getSymbol(),
Value(idText.substr(1, idText.size() - 2)));
}
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
}
antlrcpp::Any visitParenthesis_expression(ActionExpressionParser::Parenthesis_expressionContext* context) override
{
return visit(context->expression());
}
antlrcpp::Any visitValue_expression(ActionExpressionParser::Value_expressionContext* context) override
{
return visitChildren(context);
}
antlrcpp::Any visitVec3d_constructor(ActionExpressionParser::Vec3d_constructorContext* context) override
{
auto xExpression = visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>();
auto yExpression = visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>();
auto zExpression = visit(context->expression(2)).as<std::shared_ptr<nodes::AbstractExpression>>();
auto expression = std::make_shared<nodes::VectorConstructorExpression>(context->L_PAREN()->getSymbol(),
xExpression,
yExpression,
zExpression);
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
}
};
} // namespace
ExpressionParser::ExpressionParser(SCP_string expressionText) : m_expressionText(std::move(expressionText)) {}
std::shared_ptr<nodes::AbstractExpression> ExpressionParser::parse(const ParseContext& context)
{
antlr4::ANTLRInputStream input(m_expressionText);
ActionExpressionLexer lexer(&input);
antlr4::CommonTokenStream tokens(&lexer);
::ActionExpressionParser parser(&tokens);
// By default we log to stderr which we do not want
parser.removeErrorListeners();
libs::antlr::ErrorListener errListener;
parser.addErrorListener(&errListener);
antlr4::tree::ParseTree* tree = parser.expression_main();
std::shared_ptr<nodes::AbstractExpression> exprReturn;
if (errListener.diagnostics.empty()) {
// Only do semantic checking if syntax is valid
ExpressionBuilderVisitor builder;
exprReturn = tree->accept(&builder).as<std::shared_ptr<nodes::AbstractExpression>>();
if (!exprReturn->validate(&parser, context)) {
exprReturn = nullptr;
} else {
exprReturn->validationDone();
}
}
if (!errListener.diagnostics.empty()) {
SCP_stringstream outStream;
for (const auto& diag : errListener.diagnostics) {
SCP_string tokenUnderline;
if (diag.tokenLength > 1) {
tokenUnderline = SCP_string(diag.tokenLength - 1, '~');
}
outStream << m_expressionText << "\n"
<< SCP_string(diag.columnInLine, ' ') << "^" << tokenUnderline << "\n"
<< diag.errorMessage << "\n";
}
m_errorText = outStream.str();
return nullptr;
}
return exprReturn;
}
const SCP_string& ExpressionParser::getErrorText() const
{
return m_errorText;
}
} // namespace expression
} // namespace actions
@@ -1,27 +0,0 @@
#pragma once
#include "globalincs/pstypes.h"
#include "ParseContext.h"
#include "actions/expression/nodes/AbstractExpression.h"
namespace actions {
namespace expression {
class ExpressionParser {
public:
ExpressionParser(SCP_string expressionText);
std::shared_ptr<nodes::AbstractExpression> parse(const ParseContext& context);
const SCP_string& getErrorText() const;
private:
SCP_string m_expressionText;
SCP_string m_errorText;
};
} // namespace expression
} // namespace actions
@@ -1,90 +0,0 @@
#include "FunctionManager.h"
#include "TypeDefinition.h"
#include "math/vecmat.h"
namespace actions {
namespace expression {
namespace {
template <typename T>
Value value_plus(const SCP_vector<Value>& parameters)
{
return Value(parameters[0].get<T>() + parameters[1].get<T>());
}
template <typename T>
Value value_minus(const SCP_vector<Value>& parameters)
{
return Value(parameters[0].get<T>() - parameters[1].get<T>());
}
} // namespace
const FunctionManager& FunctionManager::instance()
{
static const FunctionManager manager;
return manager;
}
void FunctionManager::addOperator(const SCP_string& name,
std::initializer_list<ValueType> parameterTypes,
ValueType returnType,
FunctionImplementation implementation)
{
m_operators[name].push_back({name, parameterTypes, returnType, std::move(implementation)});
}
const FunctionDefinition* FunctionManager::findOperator(const SCP_string& name,
const SCP_vector<ValueType>& parameters) const
{
const auto iter = m_operators.find(name);
if (iter == m_operators.end()) {
return nullptr;
}
// Prefer exact matches
for (const auto& funcDef : iter->second) {
if (funcDef.parameterTypes == parameters) {
return &funcDef;
}
}
// Now check if there are matches with implicit conversions
for (const auto& funcDef : iter->second) {
// Reject functions with different amount of elements
if (funcDef.parameterTypes.size() != parameters.size()) {
continue;
}
auto funcParamIter = funcDef.parameterTypes.cbegin();
auto actualParamIter = parameters.cbegin();
bool match = true;
for (; funcParamIter != funcDef.parameterTypes.cend(); ++funcParamIter, ++actualParamIter) {
if (!checkTypeWithImplicitConversion(*actualParamIter, *funcParamIter)) {
// Also doesn't match with implicit conversions
match = false;
break;
}
}
if (match) {
return &funcDef;
}
}
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>);
}
} // namespace expression
} // namespace actions
-45
View File
@@ -1,45 +0,0 @@
#pragma once
#include "Value.h"
namespace actions {
namespace expression {
using FunctionImplementation = std::function<Value(const SCP_vector<Value>& parameters)>;
struct FunctionDefinition {
SCP_string name;
SCP_vector<ValueType> parameterTypes;
ValueType returnType;
FunctionImplementation implementation;
};
class FunctionManager {
public:
// This type is supposed to be global so it may not be copied
FunctionManager(const FunctionManager&) = delete;
FunctionManager& operator=(const FunctionManager&) = delete;
FunctionManager(FunctionManager&&) noexcept = default;
FunctionManager& operator=(FunctionManager&&) noexcept = default;
void addOperator(const SCP_string& name,
std::initializer_list<ValueType> parameterTypes,
ValueType returnType,
FunctionImplementation implementation);
const FunctionDefinition* findOperator(const SCP_string& name, const SCP_vector<ValueType>& parameterTypes) const;
static const FunctionManager& instance();
private:
FunctionManager();
SCP_unordered_map<SCP_string, SCP_vector<FunctionDefinition>> m_operators;
};
} // namespace expression
} // namespace actions
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "ProgramVariables.h"
namespace actions {
namespace expression {
struct ParseContext {
ProgramVariablesDefinition variables;
};
} // namespace expression
} // namespace actions
@@ -1,61 +0,0 @@
#include "ProgramVariables.h"
namespace actions {
namespace expression {
ProgramVariablesDefinition& ProgramVariablesDefinition::addScope(const SCP_string& name)
{
auto scope = std::unique_ptr<ProgramVariablesDefinition>(new ProgramVariablesDefinition());
auto scopePtr = scope.get();
m_scopes.insert(std::make_pair(name, std::move(scope)));
return *scopePtr;
}
void ProgramVariablesDefinition::addMember(const SCP_string& name, ValueType type)
{
m_memberTypes[name] = type;
}
ProgramVariablesDefinition* ProgramVariablesDefinition::getScope(const SCP_string& name) const
{
const auto iter = m_scopes.find(name);
if (iter == m_scopes.cend()) {
return nullptr;
}
return iter->second.get();
}
ValueType ProgramVariablesDefinition::getMemberType(const SCP_string& name) const
{
const auto iter = m_memberTypes.find(name);
if (iter == m_memberTypes.cend()) {
return ValueType::Invalid;
}
return iter->second;
}
Value ProgramVariables::getValue(std::initializer_list<const char*> memberPathParts) const
{
return getValue<decltype(memberPathParts)>(memberPathParts);
}
void ProgramVariables::setValue(std::initializer_list<const char*> memberPathParts, Value value)
{
setValue<decltype(memberPathParts)>(memberPathParts, std::move(value));
}
Value ProgramVariables::getValue(const SCP_string& fullMemberName) const
{
const auto iter = m_values.find(fullMemberName);
// This should have been validated earlier
Assertion(iter != m_values.end(), "Could not find member %s in variables!", fullMemberName.c_str());
return iter->second;
}
} // namespace expression
} // namespace actions
@@ -1,61 +0,0 @@
#pragma once
#include "Value.h"
#include "utils/join_string.h"
namespace actions {
namespace expression {
struct ProgramVariables {
public:
template <typename TContainer>
static SCP_string getMemberName(const TContainer& memberPathParts)
{
return util::join_container(memberPathParts, ".");
}
template <typename TContainer>
Value getValue(const TContainer& memberPathParts) const
{
const auto name = getMemberName(memberPathParts);
return getValue(name);
}
Value getValue(std::initializer_list<const char*> memberPathParts) const;
Value getValue(const SCP_string& fullMemberName) const;
template <typename TContainer>
void setValue(const TContainer& memberPathParts, Value value)
{
const auto name = getMemberName(memberPathParts);
m_values[name] = std::move(value);
}
void setValue(std::initializer_list<const char*> memberPathParts, Value value);
private:
SCP_unordered_map<SCP_string, Value> m_values;
};
class ProgramVariablesDefinition {
public:
ProgramVariablesDefinition& addScope(const SCP_string& name);
void addMember(const SCP_string& name, ValueType type);
ProgramVariablesDefinition* getScope(const SCP_string& name) const;
ValueType getMemberType(const SCP_string& name) const;
private:
SCP_unordered_map<SCP_string, std::unique_ptr<ProgramVariablesDefinition>> m_scopes;
SCP_unordered_map<SCP_string, ValueType> m_memberTypes;
};
} // namespace expression
} // namespace actions
@@ -1,60 +0,0 @@
#include "TypeDefinition.h"
namespace actions {
namespace expression {
// Integers can be safely converted to floats (for the most part)
TypeDefinition TypeDefinition::s_integer("Integer", {ValueType::Float});
// No implicit conversions here
TypeDefinition TypeDefinition::s_float("Float", {});
TypeDefinition TypeDefinition::s_vector("Vector", {});
// Identifiers are a technicality needed for making non-math values work but they have no further use other than their
// string content
TypeDefinition TypeDefinition::s_identifier("Identifier", {});
TypeDefinition::TypeDefinition(SCP_string name, SCP_vector<actions::expression::ValueType> allowedImplicitConversion)
: m_name(std::move(name)), m_allowedImplicitConversions(std::move(allowedImplicitConversion))
{
}
const SCP_string& TypeDefinition::getName() const
{
return m_name;
}
const SCP_vector<actions::expression::ValueType>& TypeDefinition::getAllowedImplicitConversions() const
{
return m_allowedImplicitConversions;
}
const TypeDefinition& TypeDefinition::forValueType(ValueType type)
{
switch (type) {
case ValueType::Integer:
return s_integer;
case ValueType::Float:
return s_float;
case ValueType::Vector:
return s_vector;
case ValueType::String:
return s_identifier;
default:
UNREACHABLE("Invalid value type!");
return s_integer; // Make compiler happy
}
}
bool checkTypeWithImplicitConversion(ValueType currentType, ValueType expectedType)
{
if (currentType == expectedType) {
// No need to consider implicit conversions;
return true;
}
const auto& currentTypeDef = TypeDefinition::forValueType(currentType);
const auto& allowedConversions = currentTypeDef.getAllowedImplicitConversions();
// For now, no transitiv conversions are allowed
return std::find(allowedConversions.cbegin(), allowedConversions.cend(), expectedType) != allowedConversions.cend();
}
} // namespace expression
} // namespace actions
-40
View File
@@ -1,40 +0,0 @@
#pragma once
#include "Value.h"
namespace actions {
namespace expression {
class TypeDefinition {
public:
TypeDefinition(SCP_string name, SCP_vector<actions::expression::ValueType> allowedImplicitConversion);
const SCP_string& getName() const;
const SCP_vector<actions::expression::ValueType>& getAllowedImplicitConversions() const;
static const TypeDefinition& forValueType(ValueType type);
// This type is supposed to be global so it may not be copied
TypeDefinition(const TypeDefinition&) = delete;
TypeDefinition& operator=(const TypeDefinition&) = delete;
TypeDefinition(TypeDefinition&&) = delete;
TypeDefinition& operator=(TypeDefinition&&) = delete;
private:
SCP_string m_name;
// The types this type is allowed to implicitly convert to
SCP_vector<ValueType> m_allowedImplicitConversions;
static TypeDefinition s_integer;
static TypeDefinition s_float;
static TypeDefinition s_vector;
static TypeDefinition s_identifier;
};
bool checkTypeWithImplicitConversion(ValueType currentType, ValueType expectedType);
} // namespace expression
} // namespace actions
-80
View File
@@ -1,80 +0,0 @@
#include "Value.h"
#include "math/vecmat.h"
namespace actions {
namespace expression {
Value::Value(int val) : m_value(val) {}
Value::Value(float val) : m_value(val) {}
Value::Value(const vec3d& val) : m_value(val) {}
Value::Value(SCP_string val) : m_value(std::move(val)) {}
ValueType actions::expression::Value::getType() const
{
switch (m_value.index()) {
case 1:
return ValueType::Integer;
case 2:
return ValueType::Float;
case 3:
return ValueType::Vector;
case 4:
return ValueType::String;
default:
return ValueType::Invalid;
}
}
int Value::getInteger() const
{
return get<int>();
}
float Value::getFloat() const
{
return get<float>();
}
vec3d Value::getVector() const
{
return get<vec3d>();
}
SCP_string Value::getIdentifier() const
{
return get<SCP_string>();
}
bool operator==(const Value& lhs, const Value& rhs)
{
return lhs.m_value == rhs.m_value;
}
bool operator!=(const Value& lhs, const Value& rhs)
{
return !(rhs == lhs);
}
std::ostream& operator<<(std::ostream& os, const Value& value)
{
os << "Value<";
switch (value.getType()) {
case ValueType::Invalid:
os << "INVALID";
break;
case ValueType::Integer:
os << value.get<int>();
break;
case ValueType::Float:
os << value.get<float>();
break;
case ValueType::Vector:
os << value.get<vec3d>();
break;
case ValueType::String:
os << value.get<SCP_string>();
break;
}
os << ">";
return os;
}
} // namespace expression
} // namespace actions
-104
View File
@@ -1,104 +0,0 @@
#pragma once
#include "globalincs/pstypes.h"
#include <mpark/variant.hpp>
namespace actions {
namespace expression {
namespace detail {
template <typename ToType>
struct implicit_conversion {
template <typename T>
ToType operator()(const T&)
{
throw std::runtime_error("Invalid implicit conversion.");
}
};
template<>
struct implicit_conversion<float> {
inline float operator()(int val) {
return i2fl(val);
}
template <typename T>
float operator()(const T&)
{
throw std::runtime_error("Invalid implicit conversion.");
}
};
} // namespace detail
enum class ValueType
{
Invalid,
Integer,
Float,
Vector,
String,
};
template <typename T>
struct ValueTypeTraits;
template <>
struct ValueTypeTraits<int> {
static constexpr ValueType type = ValueType::Integer;
};
template <>
struct ValueTypeTraits<float> {
static constexpr ValueType type = ValueType::Float;
};
template <>
struct ValueTypeTraits<vec3d> {
static constexpr ValueType type = ValueType::Vector;
};
template <>
struct ValueTypeTraits<SCP_string> {
static constexpr ValueType type = ValueType::String;
};
class Value {
public:
Value() = default;
explicit Value(int val);
explicit Value(float val);
explicit Value(const vec3d& val);
explicit Value(SCP_string val);
ValueType getType() const;
int getInteger() const;
float getFloat() const;
vec3d getVector() const;
SCP_string getIdentifier() const;
template <typename T>
T get() const
{
const auto currentType = getType();
const auto expectedType = ValueTypeTraits<T>::type;
if (currentType == expectedType) {
// No conversion necessary
return mpark::get<T>(m_value);
}
return mpark::visit(detail::implicit_conversion<T>{}, m_value);
}
friend bool operator==(const Value& lhs, const Value& rhs);
friend bool operator!=(const Value& lhs, const Value& rhs);
friend std::ostream& operator<<(std::ostream& os, const Value& value);
private:
mpark::variant<mpark::monostate, int, float, vec3d, SCP_string> m_value;
};
} // namespace expression
} // namespace actions
@@ -1,31 +0,0 @@
#include "AbstractExpression.h"
namespace actions {
namespace expression {
namespace nodes {
AbstractExpression::AbstractExpression(antlr4::Token* token) : m_token(token) {}
AbstractExpression::~AbstractExpression() = default;
antlr4::Token* AbstractExpression::getToken() const
{
return m_token;
}
ValueType AbstractExpression::getExpressionType() const
{
// Since expressions are immutable, caching this will be more efficient
if (m_cachedType == ValueType::Invalid) {
m_cachedType = determineReturnType();
}
return m_cachedType;
}
void AbstractExpression::validationDone()
{
// This pointer will become invalid after this so reset it now to avoid issues
m_token = nullptr;
}
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,38 +0,0 @@
#pragma once
#include "actions/expression/ParseContext.h"
#include "actions/expression/Value.h"
#include <Parser.h>
#include <Token.h>
namespace actions {
namespace expression {
namespace nodes {
class AbstractExpression {
public:
AbstractExpression(antlr4::Token* token);
virtual ~AbstractExpression();
virtual Value execute(const ProgramVariables& variables) const = 0;
virtual bool validate(antlr4::Parser* parser, const ParseContext& context) = 0;
virtual void validationDone();
ValueType getExpressionType() const;
antlr4::Token* getToken() const;
protected:
virtual ValueType determineReturnType() const = 0;
antlr4::Token* m_token = nullptr;
mutable ValueType m_cachedType = ValueType::Invalid;
};
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,95 +0,0 @@
#include "FunctionCallExpression.h"
#include "actions/expression/TypeDefinition.h"
namespace actions {
namespace expression {
namespace nodes {
FunctionCallExpression::FunctionCallExpression(antlr4::Token* token,
bool isOperator,
SCP_string functionName,
SCP_vector<std::shared_ptr<AbstractExpression>> parameterExpressions)
: AbstractExpression(token), m_isOperator(isOperator), m_functionName(std::move(functionName)),
m_parameterExpressions(std::move(parameterExpressions))
{
}
FunctionCallExpression::~FunctionCallExpression() = default;
Value FunctionCallExpression::execute(const ProgramVariables& variables) const
{
SCP_vector<Value> parameterValues;
parameterValues.reserve(m_parameterExpressions.size());
std::transform(m_parameterExpressions.cbegin(),
m_parameterExpressions.cend(),
std::back_inserter(parameterValues),
[&variables](const std::shared_ptr<AbstractExpression>& expression) { return expression->execute(variables); });
return m_functionDef->implementation(parameterValues);
}
bool FunctionCallExpression::validate(antlr4::Parser* parser, const ParseContext& context)
{
bool valid = true;
for (const auto& paramExpression : m_parameterExpressions) {
valid &= paramExpression->validate(parser, context);
}
if (!valid) {
// Exit early if there were errors since the values below are meaningless otherwise
return false;
}
Assertion(m_isOperator, "Non-operator functions are not supported yet!");
SCP_vector<ValueType> parameterTypes;
std::transform(m_parameterExpressions.cbegin(),
m_parameterExpressions.cend(),
std::back_inserter(parameterTypes),
[](const std::shared_ptr<AbstractExpression>& expression) { return expression->getExpressionType(); });
const auto functionDef = FunctionManager::instance().findOperator(m_functionName, parameterTypes);
if (functionDef == nullptr) {
SCP_stringstream paramListStream;
if (!parameterTypes.empty()) {
paramListStream << "<" << TypeDefinition::forValueType(parameterTypes.front()).getName() << ">";
for (auto iter = parameterTypes.cbegin() + 1; iter != parameterTypes.cend(); ++iter) {
paramListStream << ", <" << TypeDefinition::forValueType(*iter).getName() << ">";
}
}
parser->notifyErrorListeners(m_token,
SCP_string("Could not find a definition for the ") + (m_isOperator ? "operator" : "function") + " '" +
m_functionName + "' with parameters (" + paramListStream.str() + ")",
nullptr);
return false;
}
m_functionDef = functionDef;
return true;
}
ValueType FunctionCallExpression::determineReturnType() const
{
return m_functionDef->returnType;
}
void FunctionCallExpression::validationDone()
{
AbstractExpression::validationDone();
// We no longer need this so we can save some memory
m_functionName.clear();
m_functionName.shrink_to_fit();
for (const auto& paramExpression : m_parameterExpressions) {
paramExpression->validationDone();
}
}
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,38 +0,0 @@
#pragma once
#include "AbstractExpression.h"
#include "actions/expression/FunctionManager.h"
#include "actions/expression/Value.h"
namespace actions {
namespace expression {
namespace nodes {
class FunctionCallExpression : public AbstractExpression {
public:
FunctionCallExpression(antlr4::Token* token,
bool isOperator,
SCP_string functionName,
SCP_vector<std::shared_ptr<AbstractExpression>> parameterExpressions);
~FunctionCallExpression() override;
Value execute(const ProgramVariables& variables) const override;
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
void validationDone() override;
protected:
ValueType determineReturnType() const override;
private:
bool m_isOperator;
SCP_string m_functionName;
const FunctionDefinition* m_functionDef = nullptr;
SCP_vector<std::shared_ptr<AbstractExpression>> m_parameterExpressions;
};
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,30 +0,0 @@
#include "LiteralExpression.h"
namespace actions {
namespace expression {
namespace nodes {
LiteralExpression::LiteralExpression(antlr4::Token* token, Value literalValue)
: AbstractExpression(token), m_literalValue(std::move(literalValue))
{
}
LiteralExpression::~LiteralExpression() = default;
Value LiteralExpression::execute(const ProgramVariables& /*variables*/) const
{
return m_literalValue;
}
bool LiteralExpression::validate(antlr4::Parser* /*parser*/, const ParseContext& /*context*/)
{
// Literals are always valid
return true;
}
ValueType LiteralExpression::determineReturnType() const
{
return m_literalValue.getType();
}
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,28 +0,0 @@
#pragma once
#include "AbstractExpression.h"
#include "actions/expression/Value.h"
namespace actions {
namespace expression {
namespace nodes {
class LiteralExpression : public AbstractExpression {
public:
LiteralExpression(antlr4::Token* token, Value literalValue);
~LiteralExpression() override;
Value execute(const ProgramVariables& variables) const override;
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
protected:
ValueType determineReturnType() const override;
private:
Value m_literalValue;
};
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,108 +0,0 @@
#include "RandomRangeExpression.h"
#include "actions/expression/TypeDefinition.h"
#include "utils/RandomRange.h"
namespace actions {
namespace expression {
namespace nodes {
namespace {
template <typename T>
Value executeHelper(const Value& leftVal, const Value& rightVal)
{
auto leftContentVal = leftVal.get<T>();
auto rightContentVal = rightVal.get<T>();
if (leftContentVal == rightContentVal) {
// no need to do random stuff here
return Value(rightContentVal);
}
if (rightContentVal < leftContentVal) {
// We can fix this issue
std::swap(leftContentVal, rightContentVal);
}
// Might be a bit inefficient not to cache this somehow but the bounds can be dynamic
return Value(util::UniformRange<T>(leftContentVal, rightContentVal).next());
}
} // namespace
RandomRangeExpression::RandomRangeExpression(antlr4::Token* token,
std::shared_ptr<AbstractExpression> leftExpression,
std::shared_ptr<AbstractExpression> rightExpression)
: AbstractExpression(token), m_leftExpression(std::move(leftExpression)),
m_rightExpression(std::move(rightExpression))
{
}
RandomRangeExpression::~RandomRangeExpression() = default;
Value RandomRangeExpression::execute(const ProgramVariables& variables) const
{
const auto leftVal = m_leftExpression->execute(variables);
const auto rightVal = m_rightExpression->execute(variables);
if (leftVal.getType() == ValueType::Integer) {
return executeHelper<int>(leftVal, rightVal);
} else {
return executeHelper<float>(leftVal, rightVal);
}
}
bool RandomRangeExpression::validate(antlr4::Parser* parser, const ParseContext& context)
{
bool valid = true;
valid &= m_leftExpression->validate(parser, context);
valid &= m_rightExpression->validate(parser, context);
if (!valid) {
// Exit early if there were errors since the values below are meaningless otherwise
return false;
}
const auto leftType = m_leftExpression->getExpressionType();
const auto rightType = m_rightExpression->getExpressionType();
const auto& leftTypedef = TypeDefinition::forValueType(leftType);
const auto& rightTypedef = TypeDefinition::forValueType(rightType);
// Both values must be the same
if (leftType != rightType) {
parser->notifyErrorListeners(m_rightExpression->getToken(),
"Inconsistent types for random range. Must be the same but got <" + leftTypedef.getName() + "> and <" +
rightTypedef.getName() + ">.",
nullptr);
return false;
}
// And we only support floats or integers
if (leftType != ValueType::Float && leftType != ValueType::Integer) {
parser->notifyErrorListeners(m_leftExpression->getToken(),
"Invalid parameter type for random range. Must be <Float> or <Integer> but got <" + leftTypedef.getName() +
">.",
nullptr);
return false;
}
// Everything is fine.
return true;
}
ValueType RandomRangeExpression::determineReturnType() const
{
// Return type is the same as the contained return types
return m_leftExpression->getExpressionType();
}
void RandomRangeExpression::validationDone()
{
AbstractExpression::validationDone();
m_leftExpression->validationDone();
m_rightExpression->validationDone();
}
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,30 +0,0 @@
#pragma once
#include "AbstractExpression.h"
namespace actions {
namespace expression {
namespace nodes {
class RandomRangeExpression : public AbstractExpression {
public:
RandomRangeExpression(antlr4::Token* token,
std::shared_ptr<AbstractExpression> leftExpression,
std::shared_ptr<AbstractExpression> rightExpression);
~RandomRangeExpression() override;
Value execute(const ProgramVariables& variables) const override;
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
void validationDone() override;
protected:
ValueType determineReturnType() const override;
private:
std::shared_ptr<AbstractExpression> m_leftExpression;
std::shared_ptr<AbstractExpression> m_rightExpression;
};
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,69 +0,0 @@
#include "VariableReferenceExpression.h"
namespace actions {
namespace expression {
namespace nodes {
VariableReferenceExpression::VariableReferenceExpression(SCP_vector<SCP_string> references,
SCP_vector<antlr4::Token*> referenceTokens)
: AbstractExpression(referenceTokens.back()), m_references(std::move(references)),
m_referenceTokens(std::move(referenceTokens))
{
}
VariableReferenceExpression::~VariableReferenceExpression() = default;
Value VariableReferenceExpression::execute(const ProgramVariables& variables) const
{
return variables.getValue(m_fullVariablePath);
}
bool VariableReferenceExpression::validate(antlr4::Parser* parser, const ParseContext& context)
{
// Start out in the global scope
auto currentScope = &context.variables;
SCP_string currentScopeName("<global>");
for (size_t i = 0; i < m_references.size() - 1; ++i) {
const auto nextScope = currentScope->getScope(m_references[i]);
if (nextScope == nullptr) {
parser->notifyErrorListeners(m_referenceTokens[i],
"Could not find scope " + m_references[i] + " in scope " + currentScopeName + ".",
nullptr);
return false;
}
currentScope = nextScope;
currentScopeName = m_references[i];
}
auto variableType = currentScope->getMemberType(m_references.back());
if (variableType == ValueType::Invalid) {
parser->notifyErrorListeners(m_referenceTokens.back(),
"Could not find variable " + m_references.back() + " in scope " + currentScopeName + ".",
nullptr);
return false;
}
// Cache this so we don't duplicate all this code in determineReturnType()
m_variableType = variableType;
m_fullVariablePath = ProgramVariables::getMemberName(m_references);
return true;
}
ValueType VariableReferenceExpression::determineReturnType() const
{
return m_variableType;
}
void VariableReferenceExpression::validationDone()
{
AbstractExpression::validationDone();
// Clear these since they are not necessary anymore
m_references.clear();
m_referenceTokens.clear();
}
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,32 +0,0 @@
#pragma once
#include "AbstractExpression.h"
namespace actions {
namespace expression {
namespace nodes {
class VariableReferenceExpression : public AbstractExpression {
public:
VariableReferenceExpression(const SCP_vector<SCP_string> references,
SCP_vector<antlr4::Token*> referenceTokens);
~VariableReferenceExpression() override;
Value execute(const ProgramVariables& variables) const override;
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
void validationDone() override;
protected:
ValueType determineReturnType() const override;
private:
ValueType m_variableType = ValueType::Invalid;
SCP_string m_fullVariablePath;
SCP_vector<SCP_string> m_references;
SCP_vector<antlr4::Token*> m_referenceTokens;
};
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,87 +0,0 @@
#include "VectorConstructorExpression.h"
#include "actions/expression/TypeDefinition.h"
namespace actions {
namespace expression {
namespace nodes {
VectorConstructorExpression::VectorConstructorExpression(antlr4::Token* token,
std::shared_ptr<AbstractExpression> xExpression,
std::shared_ptr<AbstractExpression> yExpression,
std::shared_ptr<AbstractExpression> zExpression)
: AbstractExpression(token), m_xExpression(std::move(xExpression)), m_yExpression(std::move(yExpression)),
m_zExpression(std::move(zExpression))
{
}
VectorConstructorExpression::~VectorConstructorExpression() = default;
Value VectorConstructorExpression::execute(const ProgramVariables& variables) const
{
auto xValue = m_xExpression->execute(variables);
auto yValue = m_yExpression->execute(variables);
auto zValue = m_zExpression->execute(variables);
vec3d vecVal;
vecVal.xyz.x = xValue.getFloat();
vecVal.xyz.y = yValue.getFloat();
vecVal.xyz.z = zValue.getFloat();
return Value(vecVal);
}
bool VectorConstructorExpression::validate(antlr4::Parser* parser, const ParseContext& context)
{
bool valid = true;
valid &= m_xExpression->validate(parser, context);
valid &= m_yExpression->validate(parser, context);
valid &= m_zExpression->validate(parser, context);
if (!valid) {
// Exit early if there were errors since the values below are meaningless otherwise
return false;
}
if (!checkTypeWithImplicitConversion(m_xExpression->getExpressionType(), ValueType::Float)) {
const auto& typeDef = TypeDefinition::forValueType(m_xExpression->getExpressionType());
parser->notifyErrorListeners(m_xExpression->getToken(),
"Invalid value type for vector constructor. Expected <Float>, got <" + typeDef.getName() + ">",
nullptr);
valid = false;
}
if (!checkTypeWithImplicitConversion(m_yExpression->getExpressionType(), ValueType::Float)) {
const auto& typeDef = TypeDefinition::forValueType(m_yExpression->getExpressionType());
parser->notifyErrorListeners(m_yExpression->getToken(),
"Invalid value type for vector constructor. Expected <Float>, got <" + typeDef.getName() + ">",
nullptr);
valid = false;
}
if (!checkTypeWithImplicitConversion(m_zExpression->getExpressionType(), ValueType::Float)) {
const auto& typeDef = TypeDefinition::forValueType(m_zExpression->getExpressionType());
parser->notifyErrorListeners(m_zExpression->getToken(),
"Invalid value type for vector constructor. Expected <Float>, got <" + typeDef.getName() + ">",
nullptr);
valid = false;
}
return valid;
}
ValueType VectorConstructorExpression::determineReturnType() const
{
// No need to look at the expressions here since we will always be a vector
return ValueType::Vector;
}
void VectorConstructorExpression::validationDone()
{
AbstractExpression::validationDone();
m_xExpression->validationDone();
m_yExpression->validationDone();
m_zExpression->validationDone();
}
} // namespace nodes
} // namespace expression
} // namespace actions
@@ -1,34 +0,0 @@
#pragma once
#include "AbstractExpression.h"
#include "actions/expression/Value.h"
namespace actions {
namespace expression {
namespace nodes {
class VectorConstructorExpression : public AbstractExpression {
public:
VectorConstructorExpression(antlr4::Token* token,
std::shared_ptr<AbstractExpression> xExpression,
std::shared_ptr<AbstractExpression> yExpression,
std::shared_ptr<AbstractExpression> zExpression);
~VectorConstructorExpression() override;
Value execute(const ProgramVariables& variables) const override;
bool validate(antlr4::Parser* parser, const ParseContext& context) override;
void validationDone() override;
protected:
ValueType determineReturnType() const override;
private:
std::shared_ptr<AbstractExpression> m_xExpression;
std::shared_ptr<AbstractExpression> m_yExpression;
std::shared_ptr<AbstractExpression> m_zExpression;
};
} // namespace nodes
} // namespace expression
} // namespace actions
-79
View File
@@ -1,79 +0,0 @@
#include "MoveToSubmodel.h"
#include "math/vecmat.h"
#include "parse/parselo.h"
#include "ship/ship.h"
#include <utility>
namespace actions {
namespace types {
flagset<ProgramContextFlags> MoveToSubmodel::getRequiredExecutionContextFlags()
{
return flagset<ProgramContextFlags>{ProgramContextFlags::HasObject, ProgramContextFlags::HasSubobject};
}
MoveToSubmodel::MoveToSubmodel(expression::TypedActionExpression<ValueType> subObjectExpression)
: m_subObjectExpression(std::move(subObjectExpression))
{
}
MoveToSubmodel::~MoveToSubmodel() = default;
ActionResult MoveToSubmodel::execute(ProgramLocals& locals) const
{
// The calling code should ensure that this never happens
Assertion(locals.hostSubobject >= 0, "Did not have a valid host subobject.");
auto instance = object_get_model_instance(locals.host.objp);
Assertion(instance != -1, "Model instances are required if a host subobject is specified.");
auto pmi = model_get_instance(instance);
auto pm = model_get(pmi->model_num);
const auto destinationSubObject = m_subObjectExpression.execute(locals.variables);
bool goToParent = false;
if (destinationSubObject == "<parent>") {
goToParent = true;
}
if (goToParent) {
// Special case when we simply want to go to our parent without explicitly specifying its name
auto parentSubobject = pm->submodel[locals.hostSubobject].parent;
if (parentSubobject < 0) {
Warning(LOCATION,
"Current program subobject %s on model %s has no parent!",
pm->submodel[locals.hostSubobject].name,
pm->filename);
return ActionResult::Errored;
}
// We have a valid parent so this move will work properly
locals.hostSubobject = parentSubobject;
return ActionResult::Finished;
}
// We need to do a linear search for the right subobject
for (int i = 0; i < pm->n_models; ++i) {
const auto submodel = pm->submodel[i];
if (subsystem_stricmp(destinationSubObject.c_str(), submodel.name) == 0) {
// Found something!
locals.hostSubobject = i;
return ActionResult::Finished;
}
}
Warning(LOCATION, "Could not find subobject %s in model %s!", destinationSubObject.c_str(), pm->filename);
return ActionResult::Errored;
}
std::unique_ptr<Action> MoveToSubmodel::clone() const
{
return std::unique_ptr<Action>(new MoveToSubmodel(*this));
}
} // namespace types
} // namespace actions
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "globalincs/flagset.h"
#include "actions/Action.h"
#include "actions/expression/ActionExpression.h"
namespace actions {
namespace types {
class MoveToSubmodel : public Action {
public:
using ValueType = SCP_string;
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
explicit MoveToSubmodel(expression::TypedActionExpression<ValueType> subObjectExpression);
~MoveToSubmodel() override;
ActionResult execute(ProgramLocals& locals) const override;
std::unique_ptr<Action> clone() const override;
private:
expression::TypedActionExpression<ValueType> m_subObjectExpression;
};
} // namespace types
} // namespace actions
@@ -1,76 +0,0 @@
#include "ParticleEffectAction.h"
#include "math/vecmat.h"
#include "parse/parselo.h"
#include "ship/ship.h"
#include <utility>
namespace actions {
namespace types {
flagset<ProgramContextFlags> ParticleEffectAction::getRequiredExecutionContextFlags()
{
return flagset<ProgramContextFlags>{ProgramContextFlags::HasObject};
}
ParticleEffectAction::ParticleEffectAction(expression::TypedActionExpression<ValueType> effectExpression)
: m_effectExpression(std::move(effectExpression))
{
}
ParticleEffectAction::~ParticleEffectAction() = default;
ActionResult ParticleEffectAction::execute(ProgramLocals& locals) const
{
auto effectIdx = particle::ParticleManager::get()->getEffectByName(m_effectExpression.execute(locals.variables));
if (!effectIdx.isValid()) {
// In case the parsing code failed
return ActionResult::Errored;
}
using namespace particle;
auto source = ParticleManager::get()->createSource(effectIdx);
vec3d local_pos;
matrix local_orient;
if (locals.hostSubobject != -1) {
auto instance = object_get_model_instance(locals.host.objp);
Assertion(instance != -1, "Model instances are required if a host subobject is specified.");
auto pmi = model_get_instance(instance);
auto pm = model_get(pmi->model_num);
find_submodel_instance_point_orient(&local_pos,
&local_orient,
pm,
pmi,
locals.hostSubobject,
&locals.localPosition,
&locals.localOrient);
} else {
local_pos = locals.localPosition;
local_orient = locals.localOrient;
}
local_pos += locals.variables.getValue({"locals", "position"}).getVector();
auto direction = locals.variables.getValue({"locals", "direction"}).getVector();
source.moveToObject(locals.host.objp, &local_pos);
source.setOrientationFromNormalizedVec(&direction, true);
source.finish();
return ActionResult::Finished;
}
std::unique_ptr<Action> ParticleEffectAction::clone() const
{
return std::unique_ptr<Action>(new ParticleEffectAction(*this));
}
} // namespace types
} // namespace actions
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "actions/Action.h"
#include "actions/expression/ActionExpression.h"
#include "particle/ParticleManager.h"
namespace actions {
namespace types {
class ParticleEffectAction : public Action {
public:
using ValueType = SCP_string;
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
explicit ParticleEffectAction(expression::TypedActionExpression<ValueType> effectExpression);
~ParticleEffectAction() override;
ActionResult execute(ProgramLocals& locals) const override;
std::unique_ptr<Action> clone() const override;
private:
expression::TypedActionExpression<ValueType> m_effectExpression;
};
} // namespace types
} // namespace actions
-65
View File
@@ -1,65 +0,0 @@
//
//
#include "PlaySoundAction.h"
#include "ship/ship.h"
#include <utility>
namespace actions {
namespace types {
flagset<ProgramContextFlags> PlaySoundAction::getRequiredExecutionContextFlags()
{
return flagset<ProgramContextFlags>{ProgramContextFlags::HasObject};
}
PlaySoundAction::PlaySoundAction(expression::TypedActionExpression<ValueType> soundIdExpression)
: m_soundIdExpression(std::move(soundIdExpression))
{
}
PlaySoundAction::~PlaySoundAction() = default;
ActionResult PlaySoundAction::execute(ProgramLocals& locals) const
{
vec3d local_pos;
matrix local_orient;
if (locals.hostSubobject != -1) {
auto instance = object_get_model_instance(locals.host.objp);
Assertion(instance != -1, "Model instances are required if a host subobject is specified.");
auto pmi = model_get_instance(instance);
auto pm = model_get(pmi->model_num);
find_submodel_instance_point_orient(&local_pos,
&local_orient,
pm,
pmi,
locals.hostSubobject,
&locals.localPosition,
&locals.localOrient);
} else {
local_pos = locals.localPosition;
local_orient = locals.localOrient;
}
local_pos += locals.variables.getValue({"locals", "position"}).getVector();
vec3d global_pos;
vm_vec_unrotate(&global_pos, &local_pos, &locals.host.objp->orient);
global_pos += locals.host.objp->pos;
const auto soundId = gamesnd_get_by_name(m_soundIdExpression.execute(locals.variables).c_str());
// Sound is not attached to the host but the object sound system currently only supports persistent sounds
snd_play_3d(gamesnd_get_game_sound(soundId), &global_pos, &Eye_position);
return ActionResult::Finished;
}
std::unique_ptr<Action> PlaySoundAction::clone() const
{
return std::unique_ptr<Action>(new PlaySoundAction(*this));
}
} // namespace types
} // namespace actions
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#include "actions/Action.h"
#include "actions/expression/ActionExpression.h"
#include "gamesnd/gamesnd.h"
namespace actions {
namespace types {
class PlaySoundAction : public Action {
public:
using ValueType = SCP_string;
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
explicit PlaySoundAction(expression::TypedActionExpression<ValueType> soundIdExpression);
~PlaySoundAction() override;
ActionResult execute(ProgramLocals& locals) const override;
std::unique_ptr<Action> clone() const override;
private:
expression::TypedActionExpression<ValueType> m_soundIdExpression;
};
} // namespace types
} // namespace actions
-39
View File
@@ -1,39 +0,0 @@
#include "SetDirectionAction.h"
#include "math/vecmat.h"
#include "parse/parselo.h"
#include <utility>
namespace actions {
namespace types {
flagset<ProgramContextFlags> SetDirectionAction::getRequiredExecutionContextFlags()
{
return flagset<ProgramContextFlags>{};
}
SetDirectionAction::SetDirectionAction(expression::TypedActionExpression<vec3d> newDirExpression)
: m_newDirExpression(std::move(newDirExpression))
{
}
SetDirectionAction::~SetDirectionAction() = default;
ActionResult SetDirectionAction::execute(ProgramLocals& locals) const
{
auto dir = m_newDirExpression.execute(locals.variables);
vm_vec_normalize_safe(&dir);
locals.variables.setValue({"locals", "direction"}, expression::Value(dir));
return ActionResult::Finished;
}
std::unique_ptr<Action> SetDirectionAction::clone() const
{
return std::unique_ptr<Action>(new SetDirectionAction(*this));
}
} // namespace types
} // namespace actions
-27
View File
@@ -1,27 +0,0 @@
#pragma once
#include "actions/Action.h"
#include "actions/expression/ActionExpression.h"
namespace actions {
namespace types {
class SetDirectionAction : public Action {
public:
using ValueType = vec3d;
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
SetDirectionAction(expression::TypedActionExpression<ValueType> newDirExpression);
~SetDirectionAction() override;
ActionResult execute(ProgramLocals& locals) const override;
std::unique_ptr<Action> clone() const override;
private:
expression::TypedActionExpression<ValueType> m_newDirExpression;
};
} // namespace types
} // namespace actions
-34
View File
@@ -1,34 +0,0 @@
#include "SetPositionAction.h"
#include "parse/parselo.h"
#include <utility>
namespace actions {
namespace types {
flagset<ProgramContextFlags> SetPositionAction::getRequiredExecutionContextFlags()
{
return flagset<ProgramContextFlags>{};
}
SetPositionAction::SetPositionAction(expression::TypedActionExpression<ValueType> newPositionExpression)
: m_newPosExpression(std::move(newPositionExpression))
{
}
SetPositionAction::~SetPositionAction() = default;
ActionResult SetPositionAction::execute(ProgramLocals& locals) const
{
locals.variables.setValue({"locals", "position"}, expression::Value(m_newPosExpression.execute(locals.variables)));
return ActionResult::Finished;
}
std::unique_ptr<Action> SetPositionAction::clone() const
{
return std::unique_ptr<Action>(new SetPositionAction(*this));
}
} // namespace types
} // namespace actions
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#include "actions/Action.h"
#include "actions/expression/ActionExpression.h"
namespace actions {
namespace types {
class SetPositionAction : public Action {
public:
using ValueType = vec3d;
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
SetPositionAction(expression::TypedActionExpression<ValueType> newPositionExpression);
~SetPositionAction() override;
ActionResult execute(ProgramLocals& locals) const override;
std::unique_ptr<Action> clone() const override;
private:
expression::TypedActionExpression<vec3d> m_newPosExpression;
};
} // namespace types
} // namespace actions
-48
View File
@@ -1,48 +0,0 @@
#include "WaitAction.h"
#include "io/timer.h"
#include "parse/parselo.h"
namespace actions {
namespace types {
flagset<ProgramContextFlags> WaitAction::getRequiredExecutionContextFlags()
{
return flagset<ProgramContextFlags>{};
}
WaitAction::WaitAction(expression::TypedActionExpression<float> waitTimeExpression)
: _waitTimeExpression(std::move(waitTimeExpression))
{
}
WaitAction::~WaitAction() = default;
ActionResult WaitAction::execute(actions::ProgramLocals& locals) const
{
if (!timestamp_valid(locals.waitTimestamp)) {
auto waitTime = _waitTimeExpression.execute(locals.variables);
// Catch errors in the expression
if (waitTime < 0.001f) {
waitTime = 0.001f;
}
locals.waitTimestamp = timestamp(fl2i(waitTime * TIMESTAMP_FREQUENCY));
}
if (!timestamp_elapsed(locals.waitTimestamp)) {
// Wait until the timestamp is elapsed
return ActionResult::Wait;
}
// Timestamp is elapsed. The timestamp is elapsed so that timestamp_valid above returns false for the next wait
// action
locals.waitTimestamp = 0;
return ActionResult::Finished;
}
std::unique_ptr<Action> WaitAction::clone() const
{
return std::unique_ptr<Action>(new WaitAction(*this));
}
} // namespace types
} // namespace actions
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "actions/Action.h"
#include "actions/common.h"
#include "actions/expression/ActionExpression.h"
#include "utils/RandomRange.h"
namespace actions {
namespace types {
class WaitAction : public Action {
public:
using ValueType = float;
static flagset<ProgramContextFlags> getRequiredExecutionContextFlags();
explicit WaitAction(expression::TypedActionExpression<ValueType> waitTimeExpression);
~WaitAction() override;
ActionResult execute(ProgramLocals& locals) const override;
std::unique_ptr<Action> clone() const override;
private:
expression::TypedActionExpression<ValueType> _waitTimeExpression;
};
} // namespace types
} // namespace actions
+1 -1
View File
@@ -561,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);
+1 -5
View File
@@ -130,13 +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,
NUM_VALUES
};
+2 -17
View File
@@ -520,30 +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);
// if we've been through once already and are at the same place, force a move
if (saved_Mp && (saved_Mp == Mp))
{
@@ -691,7 +676,7 @@ 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)) {
+9 -137
View File
@@ -21,7 +21,6 @@
#include "ship/afterburner.h"
#include "ship/ship.h"
#include "weapon/weapon.h"
#include "object/objcollide.h"
#ifdef _MSC_VER
@@ -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);
}
@@ -867,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
@@ -948,7 +831,6 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
} else {
swp->burst_counter[current_bank_adjusted] = 0;
}
swp->burst_seed[current_bank_adjusted] = rand32();
}
} else {
if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
@@ -961,7 +843,6 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
} else {
swp->burst_counter[current_bank_adjusted] = 0;
}
swp->burst_seed[current_bank_adjusted] = rand32();
}
}
swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
@@ -1158,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;
@@ -1438,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))
return
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) )
@@ -1565,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);
+88 -147
View File
@@ -238,7 +238,7 @@ const char *Skill_level_names(int level, int translate)
str = XSTR("Insane", 473);
break;
default:
UNREACHABLE("Unhandled difficulty level of '%d' was passed to Skill_level_names().", level);
Int3();
}
} else {
switch( level ) {
@@ -258,7 +258,7 @@ const char *Skill_level_names(int level, int translate)
str = NOX("Insane");
break;
default:
UNREACHABLE("An invalid difficulty level of '%d' was passed to Skill_level_names().", level);
Int3();
}
}
@@ -944,8 +944,7 @@ float get_skill_stealth_dist_scaler()
return 1.3f;
default:
UNREACHABLE("An invalid difficulty level of %d was passed to get_skill_stealth_dist_scaler().", Game_skill_level);
Int3();
}
return 1.0f;
@@ -973,7 +972,7 @@ float get_skill_stealth_dot_scaler()
return 0.7f;
default:
UNREACHABLE("An invalid difficulty level of %d was passed to get_skill_stealth_dot_scaler().", Game_skill_level);
Int3();
}
return 1.0f;
@@ -1343,10 +1342,8 @@ void ai_turn_towards_vector(vec3d* dest, object* objp, vec3d* slide_vec, vec3d*
}
if (Framerate_independent_turning) {
if (IS_MAT_NULL(&pip->ai_desired_orient)) {
pip->ai_desired_orient = out_orient;
pip->desired_rotvel = vel_out;
} // if ai_desired_orient is NOT null, that means a SEXP is trying to move the ship right now, and the AI should defer to it
pip->ai_desired_orient = out_orient;
pip->desired_rotvel = vel_out;
}
else {
objp->orient = out_orient;
@@ -1954,28 +1951,35 @@ float get_wing_lowest_av_ab_speed(object *objp)
*/
int is_ignore_object_sub(int *ignore_objnum, int *ignore_signature, int objnum)
{
// Should never happen. I thought I removed this behavior! -- MK, 5/17/98
Assertion(((*ignore_objnum <= UNUSED_OBJNUM) || (*ignore_objnum >= 0)), "Unexpected value for ignore_objnum %d. This is a coder error, please report.", *ignore_objnum);
// whether this is an invalid OBJNUM or the official UNUSED_OBJNUM, we should just return here.
// As a note, if it was set to UNUSED_OBJNUM, it would be trying to ignore a wing and not an object. (at least according to previously written comments)
if (*ignore_objnum < 0) {
// Not ignoring anything.
if (*ignore_objnum == UNUSED_OBJNUM)
{
return 0;
}
// This means it's ignoring an object, not a wing.
else if (*ignore_objnum >= 0)
{
// see if this object became invalid
if (Objects[*ignore_objnum].signature != *ignore_signature)
{
// reset
*ignore_objnum = UNUSED_OBJNUM;
}
// objects and signatures match
else if (*ignore_objnum == objnum)
{
// found it
return 1;
}
return 0;
}
// see if this object became invalid
if (Objects[*ignore_objnum].signature != *ignore_signature)
// Ignoring a wing.
else
{
// reset
*ignore_objnum = UNUSED_OBJNUM;
Int3(); // Should never happen. I thought I removed this behavior! -- MK, 5/17/98
return 0;
}
// objects and signatures match
else if (*ignore_objnum == objnum)
{
// found it
return 1;
}
return 0;
}
// Goober5000
@@ -2342,7 +2346,7 @@ int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
* If issued an order to a ship that's awaiting repair, abort that process.
* However, do not abort process for an object that is currently being repaired -- let it finish.
*/
void ai_set_goal_abort_support_call(object *objp, ai_info *aip)
void ai_set_goal_maybe_abort_dock(object *objp, ai_info *aip)
{
if (aip->ai_flags[AI::AI_Flags::Awaiting_repair]) {
object *repair_obj;
@@ -2370,36 +2374,32 @@ void force_avoid_player_check(object *objp, ai_info *aip)
* If attacked == NULL, then attack any enemy object.
* Attack point *rel_pos on object. This is for supporting attacking subsystems.
*/
void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
void ai_attack_object(object *attacker, object *attacked, ship_subsys *ssp, int ship_info_index)
{
int temp;
ai_info* aip;
ai_info *aip;
Assert(attacker != nullptr);
Assert(attacker != NULL);
Assert(attacker->instance != -1);
Assert(Ships[attacker->instance].ai_index != -1);
// Bogus! Who tried to get me to attack myself!
if (attacker == attacked) {
Warning(LOCATION, "%s was told to attack itself! This may be an error in the mission file. If that checks out, please report to a coder!", Ships[attacker->instance].ship_name);
return;
}
if (attacker == nullptr || attacker->instance == -1) {
return;
}
aip = &Ai_info[Ships[attacker->instance].ai_index];
force_avoid_player_check(attacker, aip);
aip->ok_to_target_timestamp = timestamp(0); // Guarantee we can target.
if (attacker == attacked) {
Int3(); // Bogus! Who tried to get me to attack myself! Trace out and fix!
return;
}
// Only set to chase if a fighter or bomber, otherwise just return.
if (!(Ship_info[Ships[attacker->instance].ship_info_index].is_small_ship()) && (attacked != NULL)) {
nprintf(("AI", "AI ship %s is large ship ordered to attack %s\n", Ships[attacker->instance].ship_name, Ships[attacked->instance].ship_name));
}
// This is how "engage enemy" gets processed
if (attacked == nullptr) {
if (attacked == NULL) {
aip->choose_enemy_timestamp = timestamp(0);
// nebula safe
set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, ship_info_index));
@@ -2411,10 +2411,7 @@ void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
set_target_objnum(aip, OBJ_INDEX(attacked));
}
// don't abort a support call if you're disabled!
if (ship_get_subsystem_strength(&Ships[attacker->instance], SUBSYSTEM_ENGINE) > 0.0f)
ai_set_goal_abort_support_call(attacker, aip);
ai_set_goal_maybe_abort_dock(attacker, aip);
aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME); // No dynamic targeting for 7 seconds.
// Goober5000
@@ -2432,9 +2429,13 @@ void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
aip->submode = SM_ATTACK; // AL 12-15-97: need to set submode? I got an assert() where submode was bogus
aip->submode_start_time = Missiontime; // for AIM_CHASE... it may have been not set correctly here
set_targeted_subsys(aip, nullptr , -1);
if (aip->target_objnum != -1) {
Objects[aip->target_objnum].flags.remove(Object::Object_Flags::Protected); // If ship had been protected, unprotect it.
if (ssp == NULL) {
set_targeted_subsys(aip, NULL, -1);
if (aip->target_objnum != -1) {
Objects[aip->target_objnum].flags.remove(Object::Object_Flags::Protected); // If ship had been protected, unprotect it.
}
} else {
Int3(); // Not supported yet!
}
}
@@ -2470,7 +2471,7 @@ void ai_attack_wing(object *attacker, int wingnum)
set_target_objnum(aip, Ships[Wings[wingnum].ship_index[index]].objnum);
ai_set_goal_abort_support_call(attacker, aip);
ai_set_goal_maybe_abort_dock(attacker, aip);
aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME); // No dynamic targeting for 7 seconds.
}
}
@@ -3440,7 +3441,7 @@ void ai_form_on_wing(object *objp, object *goal_objp)
ai_formation_object_recalculate_slotnums(aip->goal_objnum);
ai_set_goal_abort_support_call(objp, aip);
ai_set_goal_maybe_abort_dock(objp, aip);
aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME*4); // Super extra long time until can target another ship.
}
@@ -4603,14 +4604,13 @@ void ai_fly_to_target_position(vec3d* target_pos, bool* pl_done_p=NULL, bool* pl
}
}
float dist_to_cover_this_frame = (Pl_objp->phys_info.speed * flFrametime);
if ( (dist_to_goal < MIN_DIST_TO_WAYPOINT_GOAL) || dist_to_cover_this_frame > 0.1f ) {
if ( (dist_to_goal < MIN_DIST_TO_WAYPOINT_GOAL) || (vm_vec_dist_quick(&Pl_objp->last_pos, &Pl_objp->pos) > 0.1f) ) {
vec3d nearest_point;
float r;
r = find_nearest_point_on_line(&nearest_point, &Pl_objp->last_pos, &Pl_objp->pos, target_pos);
if ( (dist_to_goal < (MIN_DIST_TO_WAYPOINT_GOAL + fl_sqrt(Pl_objp->radius) + dist_to_cover_this_frame))
if ( (dist_to_goal < (MIN_DIST_TO_WAYPOINT_GOAL + fl_sqrt(Pl_objp->radius) + vm_vec_dist_quick(&Pl_objp->pos, &Pl_objp->last_pos)))
|| (((r >= 0.0f) && (r <= 1.0f)) && (vm_vec_dist_quick(&nearest_point, target_pos) < (MIN_DIST_TO_WAYPOINT_GOAL + fl_sqrt(Pl_objp->radius)))))
{
int treat_as_ship;
@@ -5075,38 +5075,17 @@ void evade_weapon()
aip->danger_weapon_objnum = -1;
return;
} else if (dot_from_enemy > 0.7f) {
bool should_afterburn = dist < 200.0f;
int aburn_time = F1_0 / 2;
// within 200m bad, within 1.5 seconds good
if (The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance]) {
should_afterburn = dist < weapon_objp->phys_info.speed * 1.5f;
aburn_time = F1_0 + F1_0 / 2;
}
if (should_afterburn) {
if (dist < 200.0f) {
if (!( Pl_objp->phys_info.flags & PF_AFTERBURNER_ON )) {
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
afterburners_start(Pl_objp);
aip->afterburner_stop_time = Missiontime + aburn_time;
aip->afterburner_stop_time = Missiontime + F1_0/2;
}
}
}
// Fancy (read: effective) dodging
// Try to go perpindicular to the incoming missile, and pick the quickest direction to get there
if (The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance]) {
vec3d avoid_point;
vm_project_point_onto_plane(&avoid_point, &vec_from_enemy, &Pl_objp->orient.vec.fvec, &vmd_zero_vector);
vm_vec_normalize(&avoid_point);
if (vm_vec_dot(&Pl_objp->orient.vec.fvec, &vec_from_enemy) > 0.f)
vm_vec_negate(&avoid_point);
vm_vec_scale_add(&avoid_point, &Pl_objp->pos, &avoid_point, 200.0f);
turn_towards_point(Pl_objp, &avoid_point, nullptr, 0.0f);
}
// If we're sort of pointing towards it...
else if ((dot_to_enemy < -0.5f) || (dot_to_enemy > 0.5f)) {
if ((dot_to_enemy < -0.5f) || (dot_to_enemy > 0.5f)) {
float rdot;
float udot;
@@ -5469,30 +5448,26 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag
}
}
bool other_is_ship = (other_objp->type == OBJ_SHIP);
auto other_is_ship = (other_objp->type == OBJ_SHIP);
float enemy_remaining_shield = get_shield_pct(other_objp);
if ( other_is_ship )
if ( other_is_ship && (Ship_info[Ships[other_objp->instance].ship_info_index].is_big_or_huge() ) )
{
ship_info* other_sip = &Ship_info[Ships[other_objp->instance].ship_info_index];
if (other_sip->class_type >= 0 && Ship_types[other_sip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only]) {
//Check if we have a capital+ weapon on board
for (int i = 0; i < swp->num_primary_banks; i++)
//Check if we have a capital+ weapon on board
for (int i = 0; i < swp->num_primary_banks; i++)
{
if (swp->primary_bank_weapons[i] >= 0) // Make sure there is a weapon in the bank
{
if (swp->primary_bank_weapons[i] >= 0) // Make sure there is a weapon in the bank
auto wip = &Weapon_info[swp->primary_bank_weapons[i]];
if (wip->wi_flags[Weapon::Info_Flags::Capital_plus])
{
auto wip = &Weapon_info[swp->primary_bank_weapons[i]];
if (wip->wi_flags[Weapon::Info_Flags::Capital_plus])
// handle shielded big/huge ships (non FS-verse)
// only pick capital+ if the target has no shields; or the weapon is an ok shield-breaker anyway
if (enemy_remaining_shield <= 0.05f || (wip->shield_factor * 1.33f > wip->armor_factor))
{
// handle shielded big/huge ships (non FS-verse)
// only pick capital+ if the target has no shields; or the weapon is an ok shield-breaker anyway
if (enemy_remaining_shield <= 0.05f || (wip->shield_factor * 1.33f > wip->armor_factor))
{
swp->current_primary_bank = i;
nprintf(("AI", "%i: Ship %s selecting weapon %s (capital+) vs target %s\n", Framecount, Ships[objp->instance].ship_name, wip->name, Ships[other_objp->instance].ship_name));
return i;
}
swp->current_primary_bank = i;
nprintf(("AI", "%i: Ship %s selecting weapon %s (capital+) vs target %s\n", Framecount, Ships[objp->instance].ship_name, wip->name, Ships[other_objp->instance].ship_name));
return i;
}
}
}
@@ -7702,7 +7677,7 @@ int ai_set_attack_subsystem(object *objp, int subnum)
// -- Done at caller in ai_process_mission_orders -- attacked_objp->flags .add(Object::Object_Flags::Protected);
ai_set_goal_abort_support_call(objp, aip);
ai_set_goal_maybe_abort_dock(objp, aip);
aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME);
return 1;
@@ -7770,7 +7745,7 @@ void ai_set_guard_wing(object *objp, int wingnum)
aip = &Ai_info[shipp->ai_index];
force_avoid_player_check(objp, aip);
ai_set_goal_abort_support_call(objp, aip);
ai_set_goal_maybe_abort_dock(objp, aip);
aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME);
// This function is called whenever a guarded ship is destroyed, so this code
@@ -7840,7 +7815,7 @@ void ai_set_guard_object(object *objp, object *other_objp)
ai_set_guard_vec(objp, &Objects[other_objnum]);
ai_set_goal_abort_support_call(objp, aip);
ai_set_goal_maybe_abort_dock(objp, aip);
aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME);
}
}
@@ -7971,7 +7946,7 @@ int has_preferred_secondary(object *objp, object *en_objp, ship_weapon *swp)
void ai_choose_secondary_weapon(object *objp, ai_info *aip, object *en_objp)
{
float subsystem_strength = 0.0f;
bool is_big_ship;
int is_big_ship;
flagset<Weapon::Info_Flags> wif_priority1, wif_priority2;
ship_weapon *swp;
ship_info *esip;
@@ -8006,9 +7981,9 @@ void ai_choose_secondary_weapon(object *objp, ai_info *aip, object *en_objp)
}
if ( esip ) {
is_big_ship = esip->class_type >= 0 && Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only];
is_big_ship = esip->is_big_or_huge();
} else {
is_big_ship = false;
is_big_ship=0;
}
if (is_big_ship)
@@ -9152,7 +9127,6 @@ void ai_chase()
} else {
swp->burst_counter[current_bank_adjusted] = 0;
}
swp->burst_seed[current_bank_adjusted] = rand32();
}
} else {
if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
@@ -9165,7 +9139,6 @@ void ai_chase()
} else {
swp->burst_counter[current_bank_adjusted] = 0;
}
swp->burst_seed[current_bank_adjusted] = rand32();
}
}
swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
@@ -11552,7 +11525,7 @@ void process_subobjects(int objnum)
// a ship may get repaired... and it should still try to depart. Since docking bay departures
// are not handled as goals, we don't want to leave the AIM_BAY_DEPART mode.
if (aip->mode != AIM_BAY_DEPART) {
ai_attack_object(objp, nullptr); // Regardless of current mode, enter attack mode.
ai_attack_object(objp, NULL, NULL); // Regardless of current mode, enter attack mode.
aip->submode = SM_ATTACK_FOREVER; // Never leave attack submode, don't avoid, evade, etc.
aip->submode_start_time = Missiontime;
}
@@ -12399,28 +12372,19 @@ void ai_maybe_launch_cmeasure(object *objp, ai_info *aip)
weapon_objp = &Objects[aip->nearest_locked_object];
dist = vm_vec_dist(&objp->pos, &weapon_objp->pos);
bool in_countermeasure_range = dist < weapon_objp->phys_info.speed * 2.0f;
weapon_info *cmeasure = &Weapon_info[shipp->current_cmeasure];
if (The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance])
in_countermeasure_range = dist < cmeasure->cm_effective_rad;
if ( in_countermeasure_range ) {
if ((dist = vm_vec_dist_quick(&objp->pos, &weapon_objp->pos)) < weapon_objp->phys_info.speed*2.0f) {
aip->nearest_locked_distance = dist;
// Verify that this object is really homing on us.
float fire_chance;
// For ships on player's team, check if modder wants default constant chance to fire or value from specific AI class profile.
// For enemies, use value from specific AI class profile (usually increasing chance with higher skill level).
if ( (shipp->team == Player_ship->team) && !(aip->ai_profile_flags[AI::Profile_Flags::Friendlies_use_countermeasure_firechance]) ) {
// For ships on player's team, have constant, average chance to fire.
// For enemies, increasing chance with higher skill level.
if (shipp->team == Player_ship->team)
fire_chance = The_mission.ai_profile->cmeasure_fire_chance[NUM_SKILL_LEVELS/2];
} else {
else
fire_chance = aip->ai_cmeasure_fire_chance;
}
// Decrease chance to fire at lower ai class (SUSHI: Only if autoscale is on)
if (aip->ai_class_autoscale)
@@ -12428,23 +12392,14 @@ void ai_maybe_launch_cmeasure(object *objp, ai_info *aip)
float r = frand();
if (fire_chance < r) {
int fail_delay;
// check if modder wants to use custom fail delay, or default
if (cmeasure->cmeasure_failure_delay_multiplier_ai >= 0) {
// use firewait as delay
fail_delay = cmeasure->cmeasure_firewait * cmeasure->cmeasure_failure_delay_multiplier_ai;
} else {
// use default to wait firwait + additional delay to decrease chance of firing very soon.
fail_delay = cmeasure->cmeasure_firewait + (int) (fire_chance*2000);
}
shipp->cmeasure_fire_stamp = timestamp(fail_delay);
shipp->cmeasure_fire_stamp = timestamp(CMEASURE_WAIT + (int) (fire_chance*2000)); // Wait 1/2 second (CMEASURE_WAIT) + additional delay to decrease chance of firing very soon.
return;
}
if (weapon_objp->type == OBJ_WEAPON) {
if (weapon_objp->instance >= 0) {
ship_launch_countermeasure(objp);
shipp->cmeasure_fire_stamp = timestamp(cmeasure->cmeasure_firewait * cmeasure->cmeasure_sucess_delay_multiplier_ai);
shipp->cmeasure_fire_stamp = timestamp(2*CMEASURE_WAIT);
return;
}
}
@@ -12619,21 +12574,9 @@ void ai_maybe_evade_locked_missile(object *objp, ai_info *aip)
}
if ((missile_objp->type == OBJ_WEAPON) && (Weapon_info[Weapons[missile_objp->instance].weapon_info_index].is_homing())) {
vec3d v2m;
float dist = vm_vec_normalized_dir(&v2m, &objp->pos, &missile_objp->pos);
if (The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance]
&& vm_vec_dot(&v2m, &missile_objp->orient.vec.fvec) < 0.5f ) {
// don't bother if the missile isn't actually coming towards us
aip->nearest_locked_object = -1;
return;
}
// evade missiles 4 seconds away normally, 2 seconds away with the flag (evading too early is a thing!)
float evade_dist_scalar = The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance] ? 2.0f : 4.0f;
float evade_distance = evade_dist_scalar * vm_vec_mag_quick(&missile_objp->phys_info.vel);
if (dist < evade_distance) {
float dist = vm_vec_dist_quick(&missile_objp->pos, &objp->pos);
float dist2 = 4.0f * vm_vec_mag_quick(&missile_objp->phys_info.vel);
if (dist < dist2) {
switch (aip->mode) {
// If in AIM_STRAFE mode, don't evade if parent of weapon is targeted ship.
case AIM_STRAFE:
@@ -12647,11 +12590,9 @@ void ai_maybe_evade_locked_missile(object *objp, ai_info *aip)
case AIM_CHASE:
// Don't always go into evade weapon mode. Usually, a countermeasure gets launched.
// If low on countermeasures, more likely to try to evade. If 8+, never evade due to low cmeasures.
// Asteroth - If Improved_missile_avoidance, always evade!! Don't assume anything else will save you
if (((((Missiontime >> 18) ^ OBJ_INDEX(objp)) & 3) == 0) ||
(objp->phys_info.speed < 40.0f) ||
(frand() < 1.0f - (float) shipp->cmeasure_count/8.0f ||
The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance])) {
(frand() < 1.0f - (float) shipp->cmeasure_count/8.0f)) {
if (aip->submode != SM_ATTACK_FOREVER) { // SM_ATTACK_FOREVER means engines blown.
aip->submode = SM_EVADE_WEAPON;
aip->submode_start_time = Missiontime;
+5 -5
View File
@@ -2147,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:
@@ -2279,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) {
@@ -2301,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: {
+53 -80
View File
@@ -159,15 +159,8 @@ bool is_object_radius_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec,
}
if (tp->flags[Model::Subsystem_Flags::Turret_alt_math]) {
// Since we no longer maintain world_to_turret_matrix, regenerate it here.
vec3d turret_norm;
matrix turret_matrix, world_to_turret_matrix;
model_instance_find_world_dir(&turret_norm, &tp->turret_norm, Ships[objp->instance].model_instance_num, tp->subobj_num, &vmd_identity_matrix);
vm_vector_2_matrix(&turret_matrix, &turret_norm, nullptr, nullptr);
vm_matrix_x_matrix(&world_to_turret_matrix, &objp->orient, &turret_matrix);
vec3d temp_vec2;
vm_vec_rotate(&temp_vec2, &temp_vec, &world_to_turret_matrix);
vm_vec_rotate(&temp_vec2, &temp_vec, &ss->world_to_turret_matrix);
// now in turrets frame of reference
// check if math is actually possible
@@ -192,7 +185,7 @@ bool is_object_radius_in_turret_fov(object *objp, ship_subsys *ss, vec3d *tvec,
vm_vec_scale(&temp_vec2,scaler);
temp_vec2.xyz.z = temp_z;
// back to world frame
vm_vec_unrotate(&temp_vec, &temp_vec2, &world_to_turret_matrix);
vm_vec_unrotate(&temp_vec, &temp_vec2, &ss->world_to_turret_matrix);
}
}
}
@@ -562,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) ) {
@@ -602,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;
}
}
@@ -1189,9 +1179,8 @@ 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
@@ -1241,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);
// 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);
@@ -1316,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, tp->subobj_num, &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 {
@@ -1491,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);
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]) {
@@ -1561,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())
@@ -1687,10 +1674,8 @@ void turret_set_next_fire_timestamp(int weapon_num, weapon_info *wip, ship_subsy
}
if ((wip->burst_shots > 0) && (wip->burst_flags[Weapon::Burst_Flags::Random_length])) {
turret->weapons.burst_counter[weapon_num] = (myrand() % wip->burst_shots);
turret->weapons.burst_seed[weapon_num] = rand32();
} else {
turret->weapons.burst_counter[weapon_num] = 0;
turret->weapons.burst_seed[weapon_num] = rand32();
}
}
@@ -1784,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;
@@ -1849,16 +1834,10 @@ 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);
vm_vector_2_matrix(&turret_orient, turret_fvec, NULL, NULL);
turret->turret_last_fire_direction = *turret_fvec;
// set next fire timestamp for the turret
int old_burst_seed = swp->burst_seed[weapon_num];
if (last_shot_in_salvo)
turret_set_next_fire_timestamp(weapon_num, wip, turret, parent_aip);
@@ -1882,7 +1861,6 @@ 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 a beam weapon
@@ -1904,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;
}
@@ -1929,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;
@@ -2008,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);
@@ -2031,7 +2012,9 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int 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)
{
@@ -2046,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.
@@ -2253,8 +2235,6 @@ void ai_fire_from_turret(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));
@@ -2262,10 +2242,25 @@ void ai_fire_from_turret(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);
@@ -2380,15 +2375,13 @@ void ai_fire_from_turret(ship *shipp, ship_subsys *ss)
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;
}
@@ -2396,8 +2389,7 @@ void ai_fire_from_turret(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;
}
@@ -2461,10 +2453,8 @@ void ai_fire_from_turret(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
@@ -2472,16 +2462,8 @@ void ai_fire_from_turret(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);
@@ -2754,7 +2736,7 @@ void ai_fire_from_turret(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;
@@ -2789,7 +2771,7 @@ void ai_fire_from_turret(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.
@@ -2812,17 +2794,8 @@ 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)) {
// Since we no longer maintain world_to_turret_matrix, regenerate it here.
object *objp = &Objects[ss->parent_objnum];
vec3d turret_norm;
matrix turret_matrix, world_to_turret_matrix;
model_instance_find_world_dir(&turret_norm, &tp->turret_norm, Ships[objp->instance].model_instance_num, tp->subobj_num, &vmd_identity_matrix);
vm_vector_2_matrix(&turret_matrix, &turret_norm, nullptr, nullptr);
vm_matrix_x_matrix(&world_to_turret_matrix, &objp->orient, &turret_matrix);
vec3d of_dst;
vm_vec_rotate( &of_dst, v2e, &world_to_turret_matrix );
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 {
+129 -115
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;
}
@@ -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
*/
@@ -499,11 +525,11 @@ void asteroid_create_all()
// 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;
@@ -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
@@ -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
@@ -1762,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);
@@ -1776,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);
}
@@ -1823,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
@@ -1909,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
{
@@ -1918,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;
@@ -1930,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 -2
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;
}
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 -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('.');
+27 -60
View File
@@ -430,7 +430,7 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
if (vaporize) {
dist /= 2.0f;
}
if ( dist > source_obj->radius * 20.0f ) {
if ( dist > 200.0f ) {
return NULL;
}
}
@@ -457,29 +457,6 @@ 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 = (myrand() >> 4) % sip->generic_debris_num_submodels;
}
else if (vaporize) {
db->model_num = Debris_vaporize_model;
db->submodel_num = (myrand() >> 4) % Debris_num_submodels;
}
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);
//WMC - We must survive until now, at least.
db->must_survive_until = timestamp();
@@ -492,15 +469,13 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d
// Create Debris piece n!
if ( hull_flag ) {
if (rand() < RAND_MAX/6) // Make some pieces blow up shortly after explosion.
db->lifeleft = 2.0f * (frand()) + 0.5f;
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;
}
}
@@ -558,6 +533,22 @@ 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 == NULL )
@@ -568,15 +559,7 @@ 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);
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 NULL;
@@ -916,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;
@@ -941,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
@@ -973,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
@@ -1144,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 );
@@ -1163,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);
}
}
+1 -5
View File
@@ -45,7 +45,7 @@ typedef struct debris {
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
@@ -82,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 -8
View File
@@ -65,17 +65,10 @@ 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) {
+2 -2
View File
@@ -23,8 +23,8 @@ 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));
@@ -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
+102 -87
View File
@@ -31,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;
@@ -437,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();
@@ -446,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;
@@ -467,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 );
}
/**
@@ -483,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);
}
}
}
@@ -497,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];
@@ -537,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];
@@ -556,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;
@@ -563,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];
@@ -610,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];
@@ -637,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];
@@ -656,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 );
@@ -761,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 );
@@ -778,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);
@@ -809,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){
@@ -832,53 +856,58 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
default_flags.set(Object::Object_Flags::Renders);
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 = (myrand()>>8) & 7; // 0 - 7
fb->orient = (myrand()>>8) & 7; // 0 - 7
break;
case FIREBALL_LARGE_EXPLOSION:
new_fireball->orient = (myrand()>>8) % 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 ) {
@@ -894,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;
}
@@ -1028,14 +1058,10 @@ 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;
g3_transfer_vertex(&p, &obj->pos);
@@ -1043,13 +1069,13 @@ void fireball_render(object* obj, model_draw_list *scene)
switch ( fb->fireball_render_type ) {
case FIREBALL_MEDIUM_EXPLOSION: {
batching_add_volume_bitmap(fb->current_bitmap, &p, fb->orient, obj->radius);
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);
batching_add_volume_bitmap_rotated(Fireballs[num].current_bitmap, &p, (i2fl(fb->orient)*PI) / 180.0f, obj->radius);
}
break;
@@ -1067,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 */
+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)
+14 -27
View File
@@ -10,7 +10,6 @@
#include <sstream>
#include <climits>
#include "gamesnd.h"
#include "gamesnd/gamesnd.h"
#include "localization/localize.h"
#include "parse/parselo.h"
@@ -304,10 +303,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)
@@ -426,30 +421,19 @@ bool parse_game_sound(const char* tag, gamesnd_id* idx_dest)
{
if (optional_string(tag))
{
*idx_dest = parse_game_sound_inline();
return true;
}
SCP_string buf;
stuff_string(buf, F_NAME);
return false;
}
*idx_dest = gamesnd_get_by_name(buf.c_str());
/**
* @brief Parses a game sound that should appear at the current parsing location
* @return The game sound id or invalid id when parsing fails
*/
gamesnd_id parse_game_sound_inline()
{
SCP_string buf;
stuff_string(buf, F_NAME);
auto id = gamesnd_get_by_name(buf.c_str());
// The special case "-1" is needed to silence warnings where sounds are intentionally removed
if (!id.isValid() && buf != "-1") {
// The special case "-1" is needed to silence warnings where sounds are intentionally removed
if (idx_dest->isValid() || buf == "-1")
return true;
error_display(0, "Could not find game sound with name '%s'!", buf.c_str());
}
return id;
return false;
}
/**
@@ -679,9 +663,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 +681,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);
}
-2
View File
@@ -372,8 +372,6 @@ interface_snd_id gamesnd_get_by_iface_tbl_index(int index);
// Does all parsing for a sound. Returns true if a sound was successfully parsed.
bool parse_game_sound(const char* tag, gamesnd_id* idx_dest);
gamesnd_id parse_game_sound_inline();
void parse_iface_sound(const char* tag, interface_snd_id* idx_dest);
void parse_iface_sound_list(const char* tag, SCP_vector<interface_snd_id>& destination, const char* object_name, bool scp_list = false);
-16
View File
@@ -511,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);
}
-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>>>;
+34 -15
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:
+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:
+5 -7
View File
@@ -1509,8 +1509,6 @@ void hud_config_close()
if (HC_background_bitmap_mask != -1) {
bm_release(HC_background_bitmap_mask);
}
HUD_config_inited = 0;
}
// hud_set_default_hud_config() will set the hud configuration to default values
@@ -1559,7 +1557,7 @@ void hud_config_backup()
void hud_config_as_observer(ship *shipp,ai_info *aif)
{
// store the current hud
// store the current hus
hud_config_backup();
// bash these values so the HUD is not offset incorrectly
@@ -1665,10 +1663,10 @@ void hud_config_alpha_slider_up()
HC_color_sliders[HCS_GREEN].force_currentItem( HCS_CONV(HCS_CONV(HC_color_sliders[HCS_GREEN].get_currentItem()) + 1) );
HC_color_sliders[HCS_BLUE].force_currentItem( HCS_CONV(HCS_CONV(HC_color_sliders[HCS_BLUE].get_currentItem()) + 1) );
// apply -- Cyborg17 -- Unless you have nothing to apply it to!
// apply
if(HC_select_all){
hud_config_stuff_colors( HCS_CONV(HC_color_sliders[HCS_RED].get_currentItem()), HCS_CONV(HC_color_sliders[HCS_GREEN].get_currentItem()), HCS_CONV(HC_color_sliders[HCS_BLUE].get_currentItem()) );
} else if (HC_gauge_selected >= 0) {
} else {
gr_init_alphacolor(&HUD_config.clr[HC_gauge_selected], HCS_CONV(HC_color_sliders[HCS_RED].get_currentItem()), HCS_CONV(HC_color_sliders[HCS_GREEN].get_currentItem()), HCS_CONV(HC_color_sliders[HCS_BLUE].get_currentItem()), 255);
}
}
@@ -1689,10 +1687,10 @@ void hud_config_alpha_slider_down()
HC_color_sliders[HCS_GREEN].force_currentItem( HCS_CONV(HCS_CONV(HC_color_sliders[HCS_GREEN].get_currentItem()) - 1) );
HC_color_sliders[HCS_BLUE].force_currentItem( HCS_CONV(HCS_CONV(HC_color_sliders[HCS_BLUE].get_currentItem()) - 1) );
// apply -- Cyborg17 -- Unless you have nothing to apply it to!
// apply
if(HC_select_all){
hud_config_stuff_colors( HCS_CONV(HC_color_sliders[HCS_RED].get_currentItem()), HCS_CONV(HC_color_sliders[HCS_GREEN].get_currentItem()), HCS_CONV(HC_color_sliders[HCS_BLUE].get_currentItem()) );
} else if (HC_gauge_selected >= 0) {
} else {
gr_init_alphacolor(&HUD_config.clr[HC_gauge_selected], HCS_CONV(HC_color_sliders[HCS_RED].get_currentItem()), HCS_CONV(HC_color_sliders[HCS_GREEN].get_currentItem()), HCS_CONV(HC_color_sliders[HCS_BLUE].get_currentItem()), 255);
}
}
+3 -7
View File
@@ -212,14 +212,10 @@ void ai_manage_ets(object* obj)
return;
// check if weapons or engines are not being used. If so, don't allow energy management.
if (!ship_info_p->max_speed || !ship_info_p->max_weapon_reserve) {
return;
}
// also check if the ship has no shields and if the AI is allowed to manage weapons and engines --wookieejedi
if ( !(ship_p->ship_max_shield_strength) && !( (aip->ai_profile_flags[AI::Profile_Flags::all_nonshielded_ships_can_manage_ets]) ||
( (ship_info_p->is_fighter_bomber()) && (aip->ai_profile_flags[AI::Profile_Flags::fightercraft_nonshielded_ships_can_manage_ets])) ) ) {
return;
if ( !ship_info_p->max_speed || !ship_info_p->max_weapon_reserve ||
(!ship_p->ship_max_shield_strength && !aip->ai_profile_flags[AI::Profile_Flags::nonshielded_ships_can_manage_ets]) ) {
return;
}
float weapon_left_percent = ship_p->weapon_energy/ship_info_p->max_weapon_reserve;
+1 -1
View File
@@ -335,7 +335,7 @@ void parse_hud_gauges_tbl(const char *filename)
case 1:
int shiparray[256];
n_ships = (int)stuff_int_list(shiparray, 256, SHIP_INFO_TYPE);
n_ships = stuff_int_list(shiparray, 256, SHIP_INFO_TYPE);
if (optional_string("$Load Retail Configuration:")) {
stuff_boolean(&retail_config);
+4 -4
View File
@@ -797,7 +797,7 @@ void HudGaugeTargetBox::renderTargetDebris(object *target_objp)
printable_ship_class = Ship_info[debrisp->ship_info_index].get_display_name();
renderString(position[0] + Class_offsets[0], position[1] + Class_offsets[1], EG_TBOX_CLASS, printable_ship_class);
renderString(position[0] + Name_offsets[0], position[1] + Name_offsets[1], EG_TBOX_NAME, XSTR("debris", 348));
renderString(position[0] + Name_offsets[0], position[1] + Name_offsets[1], EG_TBOX_NAME, XSTR("Debris", 348));
}
/**
@@ -1160,11 +1160,11 @@ void HudGaugeTargetBox::renderTargetAsteroid(object *target_objp)
case ASTEROID_TYPE_SMALL:
case ASTEROID_TYPE_MEDIUM:
case ASTEROID_TYPE_LARGE:
strcpy_s(hud_name, XSTR("asteroid", 431));
strcpy_s(hud_name, NOX("asteroid"));
break;
default:
strcpy_s(hud_name, Asteroid_info[asteroidp->asteroid_type].name);
sprintf(hud_name, NOX("%s debris"), Species_info[(asteroidp->asteroid_type / NUM_DEBRIS_SIZES) - 1].species_name);
break;
}
@@ -1172,7 +1172,7 @@ void HudGaugeTargetBox::renderTargetAsteroid(object *target_objp)
if ( time_to_impact >= 0.0f ) {
renderPrintf(position[0] + Class_offsets[0], position[1] + Class_offsets[1], EG_TBOX_CLASS, XSTR("impact: %.1f sec", 1596), time_to_impact);
renderPrintf(position[0] + Class_offsets[0], position[1] + Class_offsets[1], EG_TBOX_CLASS, NOX("impact: %.1f sec"), time_to_impact);
}
}
+6 -3
View File
@@ -9,6 +9,9 @@
#include <cctype> // for 'tolower'
#include "globalincs/alphacolors.h"
#include "globalincs/linklist.h"
#include "hud/hudtargetbox.h"
@@ -454,9 +457,9 @@ void HudGaugeWingmanStatus::renderDots(int wing_index, int screen_index, int num
// Goober5000 - get the lowercase abbreviation
char abbrev[4];
abbrev[0] = SCP_tolower(Squadron_wing_names[wing_index][0]);
abbrev[1] = SCP_tolower(Squadron_wing_names[wing_index][1]);
abbrev[2] = SCP_tolower(Squadron_wing_names[wing_index][2]);
abbrev[0] = (char) tolower(Squadron_wing_names[wing_index][0]);
abbrev[1] = (char) tolower(Squadron_wing_names[wing_index][1]);
abbrev[2] = (char) tolower(Squadron_wing_names[wing_index][2]);
abbrev[3] = '\0';
// Goober5000 - center it (round the offset rather than truncate it)
+11 -12
View File
@@ -126,6 +126,7 @@ void iff_init()
int num_attack_names[MAX_IFFS];
int num_observed_colors[MAX_IFFS];
int i, j, k;
int string_idx;
try
{
@@ -339,7 +340,7 @@ void iff_init()
// get the list of iffs attacked
if (optional_string("$Attacks:"))
num_attack_names[cur_iff] = (int)stuff_string_list(attack_names[cur_iff], MAX_IFFS);
num_attack_names[cur_iff] = stuff_string_list(attack_names[cur_iff], MAX_IFFS);
else
num_attack_names[cur_iff] = 0;
@@ -383,25 +384,23 @@ void iff_init()
iff->flags = 0;
if (optional_string("$Flags:"))
{
SCP_vector<SCP_string> flag_strings;
stuff_string_list(flag_strings);
char flag_strings[MAX_IFF_FLAGS][NAME_LENGTH];
for (auto &item: flag_strings)
int num_strings = stuff_string_list(flag_strings, MAX_IFF_FLAGS);
for (string_idx = 0; string_idx < num_strings; string_idx++)
{
auto flag_string = item.c_str();
if (!stricmp(NOX("support allowed"), flag_string))
if (!stricmp(NOX("support allowed"), flag_strings[string_idx]))
iff->flags |= IFFF_SUPPORT_ALLOWED;
else if (!stricmp(NOX("exempt from all teams at war"), flag_string))
else if (!stricmp(NOX("exempt from all teams at war"), flag_strings[string_idx]))
iff->flags |= IFFF_EXEMPT_FROM_ALL_TEAMS_AT_WAR;
else if (!stricmp(NOX("orders hidden"), flag_string))
else if (!stricmp(NOX("orders hidden"), flag_strings[string_idx]))
iff->flags |= IFFF_ORDERS_HIDDEN;
else if (!stricmp(NOX("orders shown"), flag_string))
else if (!stricmp(NOX("orders shown"), flag_strings[string_idx]))
iff->flags |= IFFF_ORDERS_SHOWN;
else if (!stricmp(NOX("wing name hidden"), flag_string))
else if (!stricmp(NOX("wing name hidden"), flag_strings[string_idx]))
iff->flags |= IFFF_WING_NAME_HIDDEN;
else
Warning(LOCATION, "Bogus string in iff flags: %s\n", flag_string);
Warning(LOCATION, "Bogus string in iff flags: %s\n", flag_strings[string_idx]);
}
}
+3 -2
View File
@@ -78,7 +78,8 @@ SCP_string getJoystickGUID(SDL_Joystick* stick)
joystickGUID.resize(GUID_STR_SIZE - 1);
// Make sure the GUID is upper case
SCP_toupper(joystickGUID);
std::transform(begin(joystickGUID), end(joystickGUID), begin(joystickGUID),
[](char c) { return (char)::toupper(c); });
return joystickGUID;
}
@@ -91,7 +92,7 @@ bool joystickMatchesGuid(Joystick* testStick, const SCP_string& guid, int id)
}
SCP_string guidStr(guid);
SCP_toupper(guidStr);
std::transform(begin(guidStr), end(guidStr), begin(guidStr), [](char c) { return (char)::toupper(c); });
if (testStick->getGUID() != guidStr) {
return false; // GUID doesn't match
-23
View File
@@ -1,23 +0,0 @@
#include "ErrorListener.h"
namespace libs {
namespace antlr {
void ErrorListener::syntaxError(antlr4::Recognizer*,
antlr4::Token* offendingSymbol,
size_t line,
size_t charPositionInLine,
const std::string& msg,
std::exception_ptr)
{
// Apparently stop < start is a thing?
const auto left = std::min(offendingSymbol->getStartIndex(), offendingSymbol->getStopIndex());
const auto right = std::max(offendingSymbol->getStartIndex(), offendingSymbol->getStopIndex());
const auto length = right - left + 1;
diagnostics.push_back(Diagnostic{line, charPositionInLine, length, msg});
}
} // namespace antlr
} // namespace libs
-29
View File
@@ -1,29 +0,0 @@
#include "globalincs/pstypes.h"
#include <BaseErrorListener.h>
#include <Token.h>
namespace libs {
namespace antlr {
struct Diagnostic {
size_t line;
size_t columnInLine;
size_t tokenLength;
SCP_string errorMessage;
};
struct ErrorListener : public antlr4::BaseErrorListener {
SCP_vector<Diagnostic> diagnostics;
void syntaxError(antlr4::Recognizer* /*recognizer*/,
antlr4::Token* offendingSymbol,
size_t line,
size_t charPositionInLine,
const std::string& msg,
std::exception_ptr /*e*/) override;
};
} // namespace antlr
} // namespace libs
+46 -117
View File
@@ -1,8 +1,6 @@
#include "FFmpegContext.h"
#include <cstring>
namespace {
const size_t AVIO_BUFFER_SIZE = 8192;
@@ -53,70 +51,15 @@ int64_t cfileSeek(void* ptr, int64_t offset, int whence) {
// cfseek returns the offset in the archive file (who thought that would be a good idea?)
return cftell(cfile);
}
int memfile_read(void* opaque, uint8_t* buf, int buf_size) {
auto memsound = reinterpret_cast<libs::ffmpeg::MemFileCursor*>(opaque);
auto vec_buf_size = static_cast<SCP_vector<uint8_t>::size_type>(buf_size);
if ((int)vec_buf_size != buf_size) {
// Overflow!!!
return -1;
}
vec_buf_size = std::min(vec_buf_size, memsound->snddata.size() - memsound->cursor_pos);
if (vec_buf_size > 0) {
std::memcpy(buf, memsound->snddata.data() + memsound->cursor_pos, vec_buf_size);
memsound->cursor_pos += vec_buf_size;
return static_cast<int>(vec_buf_size);
} else {
return -1;
}
}
int64_t memfile_seek(void* opaque, int64_t offset, int whence) {
auto memsound = reinterpret_cast<libs::ffmpeg::MemFileCursor*>(opaque);
if (whence == AVSEEK_SIZE) {
return memsound->snddata.size();
}
int64_t cursor_pos = memsound->cursor_pos;
switch (whence) {
case SEEK_SET:
cursor_pos = offset;
break;
case SEEK_CUR:
cursor_pos += offset;
break;
case SEEK_END:
cursor_pos = memsound->snddata.size() + offset;
break;
}
memsound->cursor_pos = std::min(static_cast<SCP_vector<uint8_t>::size_type>(cursor_pos), memsound->snddata.size());
return static_cast<int64_t>(memsound->cursor_pos);
}
}//namespace
namespace libs {
namespace ffmpeg {
MemFileCursor::MemFileCursor(const uint8_t* data, size_t snd_len) : snddata(data, data + snd_len), cursor_pos(0) {
}
MemFileCursor::MemFileCursor() : snddata() , cursor_pos(0) {}
FFmpegContext::FFmpegContext(CFILE* inFile) : m_ctx(nullptr), m_file(inFile), m_memsound() {
FFmpegContext::FFmpegContext(CFILE* inFile) : m_ctx(nullptr), m_file(inFile) {
Assertion(inFile != nullptr, "Invalid file pointer passed!");
}
FFmpegContext::FFmpegContext(const uint8_t* snddata, size_t snd_len) : m_ctx(nullptr), m_file(nullptr), m_memsound(snddata, snd_len) {
}
FFmpegContext::~FFmpegContext() {
if (m_ctx) {
if (m_ctx->pb) {
@@ -135,58 +78,6 @@ FFmpegContext::~FFmpegContext() {
cfclose(m_file);
m_file = nullptr;
}
}
void FFmpegContext::prepare(void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence)) {
m_ctx = avformat_alloc_context();
if (!m_ctx) {
throw FFmpegException("Failed to allocate context!");
}
auto avioBuffer = reinterpret_cast<uint8_t*>(av_malloc(AVIO_BUFFER_SIZE));
if (!avioBuffer) {
throw FFmpegException("Failed to allocate IO buffer!");
}
auto ioContext =
avio_alloc_context(avioBuffer, AVIO_BUFFER_SIZE, 0, opaque, read_packet, nullptr, seek);
if (!ioContext) {
throw FFmpegException("Failed to allocate IO context!");
}
m_ctx->pb = ioContext;
auto probe_ret = av_probe_input_buffer2(m_ctx->pb, &m_ctx->iformat, nullptr, nullptr, 0, 0);
if (probe_ret < 0) {
char errorStr[1024];
av_strerror(probe_ret, errorStr, 1024);
throw FFmpegException(SCP_string("Could not open movie file! Error: ") + errorStr);
}
m_ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
auto ret = avformat_open_input(&m_ctx, nullptr, m_ctx->iformat, nullptr);
if (ret < 0) {
char errorStr[1024];
av_strerror(ret, errorStr, 1024);
throw FFmpegException(SCP_string("Could not open movie file! Error: ") + errorStr);
}
ret = avformat_find_stream_info(m_ctx, nullptr);
if (ret < 0) {
char errorStr[1024];
av_strerror(ret, errorStr, 1024);
throw FFmpegException(SCP_string("Failed to get stream information! Error: ") + errorStr);
}
}
std::unique_ptr<FFmpegContext> FFmpegContext::createContext(CFILE* mediaFile) {
@@ -194,16 +85,54 @@ std::unique_ptr<FFmpegContext> FFmpegContext::createContext(CFILE* mediaFile) {
std::unique_ptr<FFmpegContext> instance(new FFmpegContext(mediaFile));
instance->prepare(instance->m_file, cfileRead, cfileSeek);
return instance;
}
instance->m_ctx = avformat_alloc_context();
if (!instance->m_ctx) {
throw FFmpegException("Failed to allocate context!");
}
std::unique_ptr<FFmpegContext> FFmpegContext::createContextMem(const uint8_t* snddata, size_t snd_len) {
std::unique_ptr<FFmpegContext> instance(new FFmpegContext(snddata, snd_len));
auto avioBuffer = reinterpret_cast<uint8_t*>(av_malloc(AVIO_BUFFER_SIZE));
instance->prepare(&(instance->m_memsound), memfile_read, memfile_seek);
return instance;
if (!avioBuffer) {
throw FFmpegException("Failed to allocate IO buffer!");
}
auto ioContext =
avio_alloc_context(avioBuffer, AVIO_BUFFER_SIZE, 0, instance->m_file, cfileRead, nullptr, cfileSeek);
if (!ioContext) {
throw FFmpegException("Failed to allocate IO context!");
}
instance->m_ctx->pb = ioContext;
auto probe_ret = av_probe_input_buffer2(instance->m_ctx->pb, &instance->m_ctx->iformat, nullptr, nullptr, 0, 0);
if (probe_ret < 0) {
char errorStr[1024];
av_strerror(probe_ret, errorStr, 1024);
throw FFmpegException(SCP_string("Could not open movie file! Error: ") + errorStr);
}
instance->m_ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
auto ret = avformat_open_input(&instance->m_ctx, nullptr, instance->m_ctx->iformat, nullptr);
if (ret < 0) {
char errorStr[1024];
av_strerror(ret, errorStr, 1024);
throw FFmpegException(SCP_string("Could not open movie file! Error: ") + errorStr);
}
ret = avformat_find_stream_info(instance->m_ctx, nullptr);
if (ret < 0) {
char errorStr[1024];
av_strerror(ret, errorStr, 1024);
throw FFmpegException(SCP_string("Failed to get stream information! Error: ") + errorStr);
}
return instance;
}
std::unique_ptr<FFmpegContext> FFmpegContext::createContext(const SCP_string& path, int dir_type) {
+2 -29
View File
@@ -1,6 +1,5 @@
#pragma once
#include "globalincs/vmallocator.h"
#include "globalincs/pstypes.h"
#include "cfile/cfile.h"
@@ -12,42 +11,18 @@
namespace libs {
namespace ffmpeg {
/**
* @brief Stores a vector of bytes and an access position to treat it as file
*
*
*/
struct MemFileCursor {
const SCP_vector<uint8_t> snddata;
SCP_vector<uint8_t>::size_type cursor_pos;
MemFileCursor(const uint8_t* snddata, size_t snd_len);
MemFileCursor();
};
/**
* @brief Contains a FFmpeg context
*
* This should be used for making sure a FFmpeg context is always deallocated even if an exception is throws.
* createContext can be used for opening a specific file for FFmpeg decoding
* createContex can be used for opening a specific file for FFmpeg decoding
*/
class FFmpegContext {
private:
AVFormatContext* m_ctx;
CFILE* m_file;
MemFileCursor m_memsound;
FFmpegContext(CFILE* file);
FFmpegContext(const uint8_t* snddata, size_t snd_len);
void prepare(void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence));
public:
~FFmpegContext();
@@ -57,11 +32,9 @@ class FFmpegContext {
inline AVFormatContext* ctx() { return m_ctx; }
static std::unique_ptr<FFmpegContext> createContext(CFILE* mediaFile);
static std::unique_ptr<FFmpegContext> createContextMem(const uint8_t* snddata, size_t snd_len);
static std::unique_ptr<FFmpegContext> createContext(const SCP_string& path, int dir_type);
};
}
}
}
+24 -33
View File
@@ -27,7 +27,7 @@
// general language/localization data ---------------------
// current language
int Lcl_current_lang = LCL_RETAIL_HYBRID;
int Lcl_current_lang = LCL_UNTRANSLATED;
SCP_vector<lang_info> Lcl_languages;
// These are the original languages supported by FS2. The code expects these languages to be supported even if the tables don't
@@ -100,17 +100,6 @@ void parse_stringstbl_quick(const char *filename);
// LOCALIZE FUNCTIONS
//
// get an index we can use to look into the array
int lcl_get_current_lang_index()
{
Assertion(Lcl_current_lang >= 0, "Lcl_current_lang should never be negative!");
if (Lcl_current_lang < NUM_BUILTIN_LANGUAGES)
return Lcl_current_lang;
return LCL_DEFAULT;
}
// initialize localization, if no language is passed - use the language specified in the registry
void lcl_init(int lang_init)
{
@@ -164,13 +153,13 @@ void lcl_init(int lang_init)
lang = LCL_DEFAULT;
}
} else {
Assert(lang_init == LCL_UNTRANSLATED || lang_init == LCL_RETAIL_HYBRID || (lang_init >= 0 && lang_init < (int)Lcl_languages.size()));
Assert(lang_init == LCL_UNTRANSLATED || (lang_init >= 0 && lang_init < (int)Lcl_languages.size()));
lang = lang_init;
}
// and after all that... the default language reverts to hybrid unless we are specifically translating it
// and after all that... the default language reverts to untranslated unless we are specifically translating it
if (lang == LCL_DEFAULT && !Use_tabled_strings_for_default_language) {
lang = LCL_RETAIL_HYBRID;
lang = LCL_UNTRANSLATED;
}
// set the language (this function takes care of setting up file pointers)
@@ -279,7 +268,6 @@ void parse_stringstbl_common(const char *filename, const bool external)
int z, index;
char *p_offset = NULL;
int offset_lo = 0, offset_hi = 0;
int lcl_index = lcl_get_current_lang_index();
try {
read_file_text(filename, CF_TYPE_TABLES);
@@ -288,10 +276,10 @@ void parse_stringstbl_common(const char *filename, const bool external)
// move down to the proper section
memset(language_tag, 0, sizeof(language_tag));
strcpy_s(language_tag, "#");
if (external && (lcl_index == LCL_DEFAULT)) {
if (external && (Lcl_current_lang == LCL_UNTRANSLATED || Lcl_current_lang == LCL_DEFAULT)) {
strcat_s(language_tag, "default");
} else {
strcat_s(language_tag, Lcl_languages[lcl_index].lang_name);
strcat_s(language_tag, Lcl_languages[Lcl_current_lang == LCL_UNTRANSLATED ? LCL_DEFAULT : Lcl_current_lang].lang_name);
}
if ( skip_to_string(language_tag) != 1 ) {
@@ -524,10 +512,6 @@ void lcl_set_language(int lang)
nprintf(("General", "Setting language to UNTRANSLATED\n"));
// but for the purposes of array access, we use the default
lang = LCL_DEFAULT;
} else if (lang == LCL_RETAIL_HYBRID) {
nprintf(("General", "Setting language to RETAIL HYBRID\n"));
// but for the purposes of array access, we use the default
lang = LCL_DEFAULT;
} else {
nprintf(("General", "Setting language to %s\n", Lcl_languages[lang].lang_name));
}
@@ -553,7 +537,10 @@ void lcl_set_language(int lang)
ubyte lcl_get_font_index(int font_num)
{
int lang = lcl_get_current_lang_index();
int lang = Lcl_current_lang;
if (Lcl_current_lang == LCL_UNTRANSLATED) {
lang = LCL_DEFAULT;
}
if (Unicode_text_mode) {
// In Unicode mode there are no special characters. Some of the code still uses this function in that mode so
@@ -570,7 +557,10 @@ ubyte lcl_get_font_index(int font_num)
// maybe add localized directory to full path with file name when opening a localized file
int lcl_add_dir_to_path_with_filename(char *current_path, size_t path_max)
{
int lang = lcl_get_current_lang_index();
int lang = Lcl_current_lang;
if (Lcl_current_lang == LCL_UNTRANSLATED) {
lang = LCL_DEFAULT;
}
// if the disk extension is 0 length, don't add anything
if (strlen(Lcl_languages[lang].lang_ext) <= 0) {
@@ -607,9 +597,9 @@ int lcl_add_dir_to_path_with_filename(char *current_path, size_t path_max)
// externalization of table/mission files -----------------------
void lcl_replace_stuff(char *text, size_t max_len, bool force)
void lcl_replace_stuff(char *text, size_t max_len)
{
if (Fred_running && !force)
if (Fred_running)
return;
Assert(text); // Goober5000
@@ -628,17 +618,16 @@ void lcl_replace_stuff(char *text, size_t max_len, bool force)
// now will also replace $quote with double quotation marks
// now will also replace $semicolon with semicolon mark
// now will also replace $slash and $backslash
void lcl_replace_stuff(SCP_string &text, bool force)
void lcl_replace_stuff(SCP_string &text)
{
if (Fred_running && !force)
if (Fred_running)
return;
if (!Fred_running && Player != nullptr)
if (Player != NULL)
{
replace_all(text, "$callsign", Player->callsign);
replace_all(text, "$rank", Ranks[Player->stats.rank].name);
}
replace_all(text, "$quote", "\"");
replace_all(text, "$semicolon", ";");
replace_all(text, "$slash", "/");
@@ -750,7 +739,7 @@ bool lcl_ext_localize_sub(const char *in, char *text_str, char *out, size_t max_
}
// if the localization file is not open, or there's no entry, or we're not translating, return the original string
if ( !Xstr_inited || (str_id < 0) || (Lcl_current_lang == LCL_UNTRANSLATED) || (Lcl_current_lang == LCL_RETAIL_HYBRID) ) {
if ( !Xstr_inited || (str_id < 0) || (Lcl_current_lang == LCL_UNTRANSLATED) ) {
if ( strlen(text_str) > max_len && !Lcl_unexpected_tstring_check )
error_display(0, "Token too long: [%s]. Length = " SIZE_T_ARG ". Max is " SIZE_T_ARG ".\n", text_str, strlen(text_str), max_len);
@@ -836,7 +825,7 @@ bool lcl_ext_localize_sub(const SCP_string &in, SCP_string &text_str, SCP_string
}
// if the localization file is not open, or there's no entry, or we're not translating, return the original string
if ( !Xstr_inited || (str_id < 0) || (Lcl_current_lang == LCL_UNTRANSLATED) || (Lcl_current_lang == LCL_RETAIL_HYBRID) ) {
if ( !Xstr_inited || (str_id < 0) || (Lcl_current_lang == LCL_UNTRANSLATED) ) {
out = text_str;
if (id != NULL)
@@ -1209,7 +1198,9 @@ int lcl_ext_get_id(const SCP_string &xstr, int *out)
void lcl_get_language_name(char *lang_name)
{
int lang = lcl_get_current_lang_index();
int lang = Lcl_current_lang;
if (Lcl_current_lang == LCL_UNTRANSLATED)
lang = LCL_DEFAULT;
Assert(lang >= 0 && lang < (int)Lcl_languages.size());
strcpy(lang_name, Lcl_languages[lang].lang_name);
+2 -7
View File
@@ -26,7 +26,6 @@
#define LCL_POLISH 3
#define LCL_UNTRANSLATED 10 // this should be higher than the highest builtin language
#define LCL_RETAIL_HYBRID 11 // ditto; this is the weird retail behavior where internal is translated but external isn't
#define LCL_DEFAULT 0
// for language name strings
@@ -71,10 +70,6 @@ extern bool *Lcl_unexpected_tstring_check;
// LOCALIZE FUNCTIONS
//
// get an index we can use to look into the array, since we now have three different ways of using English
// (translated, untranslated, and hybrid: internal translated, external untranslated)
int lcl_get_current_lang_index();
// initialize localization, if no language is passed - use the language specified in the registry
void lcl_init(int lang = -1);
@@ -105,8 +100,8 @@ ubyte lcl_get_font_index(int font_num);
int lcl_add_dir_to_path_with_filename(char *current_path, size_t path_max);
// Goober5000
void lcl_replace_stuff(char *text, size_t max_len, bool force = false);
void lcl_replace_stuff(SCP_string &text, bool force = false);
void lcl_replace_stuff(char *text, size_t max_len);
void lcl_replace_stuff(SCP_string &text);
// Karajorma
void lcl_fred_replace_stuff(char *text, size_t max_len);
Loaded 100 of 339 files, more files were not shown because too many files have changed in this diff. Show more