Compare commits

...
15 Commits
Author SHA1 Message Date
Tyler Blount 19952f6c6c Add release notes for 21.4.1 2021-11-01 11:00:53 -06:00
ngld 6618d3d2f8 Refactor CI scripts to make release builds more robust and flexible 2021-11-01 10:33:42 -06:00
Allen A. Babb 60ddce0ad7 Merge pull request #3689 from z64555/release_21_4_1
21.4.1 Cherry Pick #1
2021-10-08 11:23:48 -05:00
BMagnu 173cd2517f Wrap Docking getTime in a timestamp (#3701)
(cherry picked from commit d0bcf86ecc)

Resolved Conflicts:
	code/ai/aicode.cpp
2021-10-08 10:37:50 -05:00
Goober5000 6006ef5208 Merge pull request #3671 from Goober5000/moar_sexp_documentation
expand some sexp documentation

(cherry picked from commit 47339e1561)
2021-10-08 10:34:59 -05:00
Asteroth afc198a82c Fix warnings concerning ballistic weapons (#3683)
* dont warn for provided pbank capacity but no ballistic weapons

* review feedback

(cherry picked from commit 2703fe865b)
2021-10-08 10:12:44 -05:00
Goober5000 2796eda3cb Merge pull request #3695 from Baezon/fix-visibility-initialization
Initialize team visibility before using it

(cherry picked from commit e5973e09ca)
2021-10-08 09:57:36 -05:00
JohnAFernandez a5b9216fc6 Actually fix the goal's target being sent (#3665)
(cherry picked from commit efeb0ee1f2)
2021-10-08 09:55:21 -05:00
Asteroth 5fbd0997a4 Safety for replace-texture (#3694)
* dont add replacement texture if unable to find original texture

* NULL -> nullptr

(cherry picked from commit a1ae1d80a9)
2021-10-08 09:46:43 -05:00
Allen A. Babb 8bf3b01d16 Merge pull request #3688 from z64555/fix/issue_3685
Fix issue 3685

(cherry picked from commit 64b2469235)
2021-10-08 09:36:12 -05:00
BMagnu ff2bb8ee95 Exclude dead subsys from targeting (#3614)
* Exclude dead subsys from targeting

* Put behaviour behind flags

* Make ML lock disappear when subsys dies

* Flip flag to change default behaviour

(cherry picked from commit 2b63097c12)
2021-10-02 15:50:15 -05:00
Goober5000 690d600df8 Merge pull request #3679 from Goober5000/killer_fixes
patch up scripted kills

(cherry picked from commit 470466e8c7)
2021-10-02 15:46:20 -05:00
EatThePath e7a897aa4d Fix homing child weapons in niche circumstances (#3661)
* Deleted a line that blanked target_num on homing weapons but still let them home

* This whole block of code about missiles missing turns out to be a logical dead end anyway, ripped it out by the roots.

(cherry picked from commit a8bbe23800)
2021-10-02 15:46:00 -05:00
z64555 050c76b721 bump version to 21.4.1 2021-10-02 15:13:43 -05:00
SirKnightly 7bb0c77bb5 Automated build commit 2021-09-27 00:17:41 +00:00
30 changed files with 1035 additions and 107 deletions

No files matched your search

+25
View File
@@ -210,3 +210,28 @@ jobs:
DATACORDER_USER: ${{ secrets.DATACORDER_USER }}
DATACORDER_SSHPASS: ${{ secrets.DATACORDER_PASSWORD }}
run: $GITHUB_WORKSPACE/ci/linux/upload_dist_package.sh nightly
post_builds:
# Post nightlies if at least one job was successful
if: ${{ needs.linux_zip == "success" || needs.windows_zip == "success" }}
name: Post builds on Nebula and the forums
needs: [linux_zip, windows_zip]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
name: Checkout
with:
fetch-depth: '0'
- name: Install Python dependencies
run: pip install -r ci/post/requirements.txt
- name: Post builds
env:
INDIEGAMES_USER: ${{ secrets.INDIEGAMES_USER }}
INDIEGAMES_SSHPASS: ${{ secrets.INDIEGAMES_PASSWORD }}
NEBULA_USER: ${{ secrets.NEBULA_USER }}
NEBULA_PASSWORD: ${{ secrets.NEBULA_PASSWORD }}
HLP_API: ${{ secrets.HLP_API }}
HLP_KEY: ${{ secrets.HLP_KEY }}
LINUX_RESULT: ${{ needs.linux_zip.result }}
WINDOWS_RESULT: ${{ needs.windows_zip.result }}
run: python ci/post/main.py nightly
+23
View File
@@ -263,3 +263,26 @@ jobs:
asset_path: ${{ steps.generate_package.outputs.debug_path }}
asset_name: ${{ steps.generate_package.outputs.debug_name }}
asset_content_type: ${{ steps.generate_package.outputs.debug_mime }}
post_builds:
name: Post builds on Nebula and the forums
needs: [linux_zip, windows_zip]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
name: Checkout
with:
fetch-depth: '0'
- name: Install Python dependencies
run: pip install -r ci/post/requirements.txt
- name: Post builds
env:
INDIEGAMES_USER: ${{ secrets.INDIEGAMES_USER }}
INDIEGAMES_SSHPASS: ${{ secrets.INDIEGAMES_PASSWORD }}
NEBULA_USER: ${{ secrets.NEBULA_USER }}
NEBULA_PASSWORD: ${{ secrets.NEBULA_PASSWORD }}
HLP_API: ${{ secrets.HLP_API }}
HLP_KEY: ${{ secrets.HLP_KEY }}
LINUX_RESULT: ${{ needs.linux_zip.result }}
WINDOWS_RESULT: ${{ needs.windows_zip.result }}
run: python ci/post/main.py release
+21
View File
@@ -0,0 +1,21 @@
name: Push a new nightly tag
on:
schedule:
- cron: '0 6 * * *'
jobs:
tag:
if: github.repository == 'scp-fs2open/fs2open.github.com'
runs-on: ubuntu-latest
steps:
- name: Prepare environment
run: |
git config --global user.email '[email protected]'
git config --global user.name SirKnightly
- name: Checkout repo
uses: actions/checkout@v2
- name: Create tag
run: git tag "nightly_$(date '+%Y%m%d')_$(git rev-parse --short HEAD)"
- name: Push tag
run: git push --tags
+128
View File
@@ -0,0 +1,128 @@
import re
from ftplib import FTP, error_perm
from typing import List, Tuple, Dict
import requests
from util import retry_multi, GLOBAL_TIMEOUT
class ReleaseFile:
def __init__(self, name, url, group, subgroup=None, mirrors=None):
if mirrors is None:
mirrors = []
self.mirrors = mirrors
self.subgroup = subgroup
self.group = group
self.url = url
self.name = name
self.base_url = "/".join(url.split('/')[0:-1]) + "/"
self.filename = url.split('/')[-1]
# A list of tuples of (filename, hash)
self.content_hashes = None
self.hash = None
self.size = 0
class SourceFile:
def __init__(self, name, url, group):
self.group = group
self.url = url
self.name = name
def get_release_files(tag_name, config) -> Tuple[List[ReleaseFile], Dict[str, SourceFile]]:
@retry_multi(5)
def execute_request(path):
headers = {
"Accept": "application/vnd.github.v3+json"
}
url = "https://api.github.com" + path
response = requests.get(url, headers=headers, timeout=GLOBAL_TIMEOUT)
response.raise_for_status()
return response.json()
build_group_regex = re.compile("fs2_open_.*-builds-([^.-]*)(-([^.]*))?.*")
source_file_regex = re.compile("fs2_open_.*-source-([^.]*)?.*")
response = execute_request(
"/repos/{}/{}/releases/tags/{}".format(config["github"]["user"], config["github"]["repo"], tag_name))
binary_files = []
source_files = {}
for asset in response["assets"]:
url = asset["browser_download_url"]
name = asset["name"]
group_match = build_group_regex.match(name)
if group_match is not None:
platform = group_match.group(1)
# x64 is the Visual Studio name but for consistency we need Win64
if platform == "x64":
platform = "Win64"
binary_files.append(ReleaseFile(name, url, platform, group_match.group(3)))
else:
group_match = source_file_regex.match(name)
if group_match is None:
continue
group = group_match.group(1)
source_files[group] = SourceFile(name, url, group)
return binary_files, source_files
def get_ftp_files(build_type, tag_name, config):
tag_regex = re.compile("nightly_(.*)")
build_group_regex = re.compile("nightly_.*-builds-([^.]+).*")
files = []
try:
with FTP(config["ftp"]["host"], config["ftp"]["user"], config["ftp"]["pass"]) as ftp:
version_str = tag_regex.match(tag_name).group(1)
path_template = config["ftp"]["path"]
path = path_template.format(type=build_type, version=version_str)
file_entries = list(ftp.mlsd(path, ["type"]))
for entry in file_entries:
if entry[1]["type"] == "file":
files.append(entry[0])
except error_perm:
print("Received permanent FTP error!")
return []
out_data = []
for file in files:
file_match = build_group_regex.match(file)
if file_match is None:
print("Ignoring non nightly file '{}'".format(file))
continue
group_match = file_match.group(1)
primary_url = None
mirrors = []
# x64 is the name Visual Studio uses but Win64 works better for us since that gets displayed in the nightly post
if "x64" in group_match:
group_match = group_match.replace("x64", "Win64")
for mirror in config["ftp"]["mirrors"]:
download_url = mirror.format(type=build_type, version=version_str, file=file)
if primary_url is None:
primary_url = download_url
else:
mirrors.append(download_url)
out_data.append(ReleaseFile(file, primary_url, group_match, None, mirrors))
return out_data
+82
View File
@@ -0,0 +1,82 @@
from itertools import groupby
from typing import List
import requests
import semantic_version
from mako.template import Template
from file_list import ReleaseFile
class FileGroup:
def __init__(self, name, files: List[ReleaseFile]):
self.files = files
self.name = name
if len(files) == 1:
self.mainFile = files[0]
self.subFiles = {}
else:
self.mainFile = None
subFiles = []
for file in files:
# We only have subcategories for Windows where SSE2 is the main group
if file.subgroup == "SSE2":
self.mainFile = file
else:
subFiles.append(file)
self.subFiles = dict(((x[0], next(x[1])) for x in groupby(subFiles, lambda f: f.subgroup)))
class ForumAPI:
def __init__(self, config):
self.config = config
def create_post(self, title, content, board):
resp = requests.post(self.config["hlp"]["api"], data={
"api_key": self.config["hlp"]["key"],
"board": str(board),
"subject": title,
"body": content
})
if resp.text != "OK":
print("Post failed!")
def post_nightly(self, date, revision, files, log, success):
print("Posting nightly thread...")
title = "Nightly: {} - Revision {}".format(date, revision)
template = Template(filename=self.config["templates"]["nightly"])
rendered = template.render_unicode(**{
"date": date,
"revision": revision,
"files": files,
"log": log,
"success": success
})
print("Creating post...")
self.create_post(title, rendered, self.config["nightly"]["hlp_board"])
def post_release(self, date, version: semantic_version.Version, groups, sources):
print("Posting release thread...")
title = "Release: {}".format(version)
template = Template(
filename=self.config["templates"]["release"].format(major=version.major, minor=version.minor),
module_directory='/tmp/mako_modules')
rendered = template.render_unicode(**{
"date": date,
"version": version,
"groups": groups,
"sources": sources
}).strip("\n")
print("Creating post...")
self.create_post(title, rendered, self.config["release"]["hlp_board"])
+88
View File
@@ -0,0 +1,88 @@
import os
import hashlib
import tarfile
import traceback
import zipfile
from tempfile import NamedTemporaryFile
from zipfile import ZipFile
import requests
import sys
from mako.template import Template
from file_list import ReleaseFile
from util import GLOBAL_TIMEOUT
def _download_file(url, dest_file, session):
print("Downloading " + url)
# NOTE the stream=True parameter
r = session.get(url, stream=True, timeout=GLOBAL_TIMEOUT)
for chunk in r.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
dest_file.write(chunk)
dest_file.flush()
def _gen_hash(fileobj, hash_alg):
h = hashlib.new(hash_alg)
while True:
data = fileobj.read(4096)
if not data:
break
h.update(data)
return h.hexdigest()
def get_hashed_file_list(file: ReleaseFile, session: requests.Session = None, hash_alg: str = "sha256"):
if session is None:
session = requests.Session()
with NamedTemporaryFile('w+b', suffix=file.filename) as local_file:
_download_file(file.url, local_file, session)
filename = local_file.name
hash_list = []
local_file.seek(0)
file.hash = _gen_hash(local_file, hash_alg)
file.size = os.stat(filename).st_size
try:
if tarfile.is_tarfile(filename):
with tarfile.open(filename) as archive:
for entry in archive:
if not entry.isfile():
continue
print("Computing hash for " + entry.name)
fileobj = archive.extractfile(entry)
hash_list.append((entry.path, _gen_hash(fileobj, hash_alg)))
elif zipfile.is_zipfile(filename):
with ZipFile(filename) as archive:
for entry in archive.infolist():
# Python 3.6 has is_dir but that version is relatively new so we'll use the "safe" version here
if entry.filename.endswith('/'):
continue
print("Computing hash for " + entry.filename)
with archive.open(entry) as fileobj:
hash_list.append((entry.filename, _gen_hash(fileobj, hash_alg)))
else:
raise NotImplementedError("Unsupported archive type!")
except:
traceback.print_exception(*sys.exc_info())
return
file.content_hashes = hash_list
def render_installer_config(version, groups, config):
template = Template(filename=config["templates"]["installer"], module_directory='/tmp/mako_modules')
return template.render(**{
"version": version,
"groups": groups,
}).strip("\n")
+152
View File
@@ -0,0 +1,152 @@
import sys
import os
import re
from subprocess import check_output
from datetime import datetime
from itertools import groupby
import semantic_version
import file_list
import installer
import nebula
import forum
MAJOR_VERSION_PATTERN = re.compile("set_if_not_defined\(FSO_VERSION_MAJOR (\d+)\)")
MINOR_VERSION_PATTERN = re.compile("set_if_not_defined\(FSO_VERSION_MINOR (\d+)\)")
BUILD_VERSION_PATTERN = re.compile("set_if_not_defined\(FSO_VERSION_BUILD (\d+)\)")
LOG_FORMAT = """
------------------------------------------------------------------------%n
commit %h%nAuthor: %an <%ad>%n
Commit: %cn <%cd>%n%n %s
"""
DATEFORMAT_VERSION = "%Y%m%d"
DATEFORMAT_FORUM = "%d %B %Y"
def _match_version_number(text, regex):
match = regex.search(text)
return int(match.group(1))
def get_source_version(date_version):
with open(os.path.join("..", "..", "cmake", "version.cmake"), "r") as f:
filetext = f.read()
major = _match_version_number(filetext, MAJOR_VERSION_PATTERN)
minor = _match_version_number(filetext, MINOR_VERSION_PATTERN)
build = _match_version_number(filetext, BUILD_VERSION_PATTERN)
return semantic_version.Version("{}.{}.{}-{}".format(major, minor, build, date_version))
def main():
os.chdir(os.path.dirname(__file__))
config = {
"github": {
"user": "scp-fs2open",
"repo": "fs2open.github.com",
},
"ftp": {
"host": "scp.indiegames.us",
"user": os.environ["INDIEGAMES_USER"],
"pass": os.environ["INDIEGAMES_SSHPASS"],
"path": "public_html/builds/{type}/{version}/",
"mirrors": (
"https://porphyrion.feralhosting.com/datacorder/builds/{type}/{version}/{file}",
"http://scp.indiegames.us/builds/{type}/{version}/{file}",
),
},
"nebula": {
"user": os.environ["NEBULA_USER"],
"password": os.environ["NEBULA_PASSWORD"],
},
"hlp": {
"api": os.environ["HLP_API"],
"key": os.environ["HLP_KEY"],
},
"templates": {
"nightly": "nightly.mako",
"release": "release.mako",
},
"nightly": {
"hlp_board": 173,
},
"release": {
"hlp_board": 50,
},
}
if sys.argv[1] not in ("release", "nightly"):
print("ERROR: Invalid release mode %s passed. Expected release or nightly!" % sys.argv[1])
sys.exit(1)
tag_name = os.path.basename(os.environ["GITHUB_REF"])
date = datetime.now()
version = get_source_version(date.strftime(DATEFORMAT_VERSION))
success = os.environ["LINUX_RESULT"] == "success" and os.environ["WINDOWS_RESULT"] == "success"
tags = check_output(("git", "for-each-ref", "--sort=-taggerdate", "--format", "%(tag)", "refs/tags")).splitlines()
previous_tag = None
found = False
for tag in tags:
if found:
if tag.startswith(sys.argv[1] + "_"):
previous_tag = tag
break
elif tag == tag_name:
found = True
if not found:
print("ERROR: Couldn't find tag %s in repo!" % tag_name)
sys.exit(1)
if not previous_tag:
print("ERROR: Could not find a previous tag for %s!" % tag_name)
sys.exit(1)
if sys.argv[1] == "release":
if not tag_name.startswith("release_"):
print("ERROR: Invalid tag name detected. Expected release_ prefix for release builds but found %s (full ref = %s)" % tag_name, os.environ["GITHUB_REF"])
sys.exit(1)
files, sources = file_list.get_release_files(tag_name, config)
print("Hashing files")
for file in files:
installer.get_hashed_file_list(file)
# Construct the file groups
groups = dict(((x[0], file_list.FileGroup(x[0], list(x[1]))) for x in groupby(files, lambda g: g.group)))
print(installer.render_installer_config(version, groups, config))
nebula.submit_release(
nebula.render_nebula_release(version, "rc" if version.prerelease else "stable", "files", config),
config)
fapi = forum.ForumAPI(config)
fapi.post_release(date.strftime(DATEFORMAT_FORUM), version, groups, sources)
else:
if not tag_name.startswith("nightly_"):
print("ERROR: Invalid tag name detected. Expected nightly_ prefix for nightly builds but found %s (full ref = %s)" % tag_name, os.environ["GITHUB_REF"])
sys.exit(1)
print("Retrieving file list")
files = file_list.get_ftp_files("nightly", tag_name, config)
print("Hashing files")
for file in files:
installer.get_hashed_file_list(file)
nebula.submit_release(nebula.render_nebula_release(version, "nightly", files, config), config)
log = check_output(("git", "log", "%s^..%s^" % (previous_tag, tag_name), "--no-merges", "--stat", "--pretty=format:\"%s\"" % LOG_FORMAT.strip()))
fapi = forum.ForumAPI(config)
fapi.post_nightly(date.strftime(DATEFORMAT_FORUM), os.environ["GITHUB_SHA"], files, log, success)
main()
+240
View File
@@ -0,0 +1,240 @@
import os.path
import sys
import traceback
import requests
from util import retry_multi, GLOBAL_TIMEOUT
metadata = {
'type': 'engine',
'title': 'FSO',
'notes': '',
'banner': 'https://fsnebula.org/storage/0d/e7/bf64bcdea9a9c115969cfb784e1ca457d24a7c2da4fc6f213521c3bb6abb.png',
'screenshots': [],
'videos': [],
'first_release': '2017-09-24',
'cmdline': '',
'mod_flag': ['FSO'],
'tile': 'https://fsnebula.org/storage/ec/cc/0bf23e028c26d5175ff52d003bff85b0a17b0ddfc1130d65bdf6d36f6324.png',
'logo': None,
'release_thread': None,
'description': '',
'id': 'FSO',
'packages': [],
}
platforms = {
'Linux': 'linux',
'MacOSX': 'macosx',
'Win32-SSE2': 'windows',
'Win64-SSE2': 'windows',
'Win32-AVX': 'windows',
'Win64-AVX': 'windows'
}
envs = {
'Linux': 'linux && x86_64', # Linux only has 64bit builds
'MacOSX': 'macosx',
'Win32-SSE2': 'windows',
'Win64-SSE2': 'windows && x86_64',
'Win32-AVX': 'windows && avx',
'Win64-AVX': 'windows && avx && x86_64'
}
subdirs = {
'Win32-SSE2': 'x86',
'Win64-SSE2': 'x64',
'Win32-AVX': 'x86_avx',
'Win64-AVX': 'x64_avx'
}
def render_nebula_release(version, stability, files, config):
meta = metadata.copy()
meta['version'] = str(version)
meta['stability'] = stability # This can be one of ('stable', 'rc', 'nightly')
for file in files:
if file.content_hashes is None:
# The extraction probably failed which is why we don't have any hashes
continue
group = file.group
# release.py sets subgroup but nightly.py doesn't which means we have to normalize group here.
if file.subgroup:
group += '-' + file.subgroup
pkg = {
'name': group,
'notes': '',
'is_vp': False,
'files': [{
'dest': platforms[group] + '/' + subdirs.get(group, ''),
'filesize': file.size,
'checksum': ['sha256', file.hash],
'urls': [file.url] + file.mirrors,
'filename': file.name
}],
'environment': envs.get(group),
'filelist': [],
'status': 'required',
'folder': None,
'dependencies': [],
'executables': []
}
for fn, checksum in file.content_hashes:
if group in subdirs:
dest_fn = subdirs[group] + '/' + fn
else:
dest_fn = fn
# If the group is a known platform then we put the binaries into a separate subfolder for each platform
if group in platforms:
dest_fn = platforms[group] + '/' + dest_fn
pkg['filelist'].append({
'orig_name': fn,
'checksum': ('sha256', checksum),
'archive': file.name,
'filename': dest_fn
})
if group == 'Linux' and fn.endswith('.AppImage'):
if 'qtfred' in fn:
if '-FASTDBG' in fn:
label = 'QtFRED Debug'
else:
label = 'QtFRED'
else:
if '-FASTDBG' in fn:
label = 'Fast Debug'
else:
label = None
props = {
"x64": True, # All Linux builds are 64-bit
"sse2": True, # Linux builds are forced to compile with SSE2 but not AVX
"avx": False,
"avx2": False,
}
pkg['executables'].append({
'file': dest_fn,
'label': label,
'properties': props,
})
elif group == 'MacOSX' and fn.startswith(os.path.basename(fn) + '.app/'):
if 'qtfred' in fn:
if '-FASTDBG' in fn:
label = 'QtFRED Debug'
else:
label = 'QtFRED'
else:
if '-FASTDBG' in fn:
label = 'Fast Debug'
else:
label = None
props = {
"x64": True, # All Mac builds are 64-bit
"sse2": True, # There are no forced options on mac but 64-bit always implies SSE2 so we set that here
"avx": False,
"avx2": False,
}
pkg['executables'].append({
'file': dest_fn,
'label': label,
'properties': props,
})
elif group.startswith('Win') and fn.endswith('.exe'):
if 'fred2' in fn:
if '-FASTDBG' in fn:
label = 'FRED2 Debug'
else:
label = 'FRED2'
elif 'qtfred' in fn:
if '-FASTDBG' in fn:
label = 'QtFRED Debug'
else:
label = 'QtFRED'
else:
if '-FASTDBG' in fn:
label = 'Fast Debug'
else:
label = None
props = {
"x64": "x64" in fn,
"sse2": "SSE2" in fn or "AVX" in fn, # AVX implies SSE2
"avx": "AVX" in fn, # This conveniently also covers the AVX2 case since AVX2 implies AVX
"avx2": "AVX2" in fn,
}
pkg['executables'].append({
'file': dest_fn,
'label': label,
'properties': props,
})
meta["packages"].append(pkg)
return meta
@retry_multi(5)
def nebula_request(session, kind, path, **kwargs):
uri = "https://fsnebula.org/api/1/{}".format(path)
request_args = {
"timeout": GLOBAL_TIMEOUT
}
request_args.update(kwargs)
return session.request(kind, uri, **request_args)
def submit_release(meta, config):
try:
with requests.Session() as session:
print('Logging into Nebula...')
result = nebula_request(session, 'post', 'login', data={
'user': config['nebula']['user'],
'password': config['nebula']['password']
})
if result.status_code != 200:
print('Login failed!')
return False
data = result.json()
if not data['result']:
print('Login failed!')
return False
print('Submitting release...')
result = nebula_request(session, 'post', 'mod/release', headers={
'X-KN-TOKEN': data['token']
}, json=meta)
if result.status_code != 200:
print('Request failed!')
return False
data = result.json()
if not data['result']:
print('ERROR: ' + data['reason'])
return False
print('Success!')
return True
except Exception:
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback.print_exception(exc_type, exc_value, exc_traceback)
return False
+27
View File
@@ -0,0 +1,27 @@
Here is the nightly for ${date} - Revision ${revision}
% if not success:
[b][color=red]At least one of the nightly builds failed![/color][/b]
% endif
% for file in files:
Group: ${file.group}
[url=${file.url}]${file.name}[/url] \
%for mirror in file.mirrors:
%if loop.first:
(\
%endif
[url=${mirror}]Mirror[/url]\
%if loop.last:
)\
%else:
, \
%endif
%endfor
% endfor
[code]
${log}
[/code]
+80
View File
@@ -0,0 +1,80 @@
<%def name="build(file)">[url=${file.url}]${file.filename}[/url]</%def>
[b]Change log:[/b] (chronologically ordered)
[b]Bugfixes and behavior changes:[/b] Check these first if something breaks (most of these changes can be reverted / disabled).
[list]
[li]Wrap Docking getTime in a timestamp ([gh]#3701[/gh])[/li]
[li]expand some sexp documentation ([gh]#3671[/gh])[/li]
[li]Fix warnings concerning ballistic weapons ([gh]#3683[/gh])[/li]
[li]Initialize team visibility before using it ([gh]#3695[/gh])[/li]
[li]Actually fix the goal's target being sent ([gh]#3665[/gh])[/li]
[li]Safety for replace-texture ([gh]#3694[/gh])[/li]
[li]Fix issue 3685 ([gh]#3688[/gh])[/li]
[li]Exclude dead subsys from targeting ([gh]#3614[/gh])[/li]
[li]Fix scripted kills ([gh]#3679[/gh])[/li]
[li]Fix homing child weapons in niche circumstances ([gh]#3661[/gh])[/li]
[/list]
[b]Deprecations:[/b]
[list][li]None[/li][/list]
Deprecations are a mechanism in FSO where a certain feature or aspect of the engine has changed or is no longer supported. Since this would normally break existing mods we have the mod table feature "[tt]$Target Version:[/tt]" with which a mod can specify what version of FSO it was developed with. The features listed above will be removed or changed when the target version of a mod is at least the version released in this post.
[size=5pt]Previous [url=https://www.hard-light.net/forums/index.php?topic=97293.0]21.0 Release Thread[/url][/size]
Launchers, if you don't have one already:
[b]All platforms: [/b] For every day use, we recommend [url=https://www.hard-light.net/forums/index.php?topic=94068.0]Knossos[/url], an integrated solution for downloading and launching mods.
[hidden=Alternative Launchers]
Cross-platform: [url=http://www.hard-light.net/forums/index.php?topic=89162]wxLauncher 0.12.x Test Build[/url] (ongoing project for a unified launcher, you should upgrade to the latest RC/test build if you have not yet)
[b]Important:[/b] For best compatibility with FSO 3.8 and later you should use at least wxLauncher 0.12.
Windows: [url=http://scp.fsmods.net/files/Launcher55g.zip]Launcher 5.5g[/url] ([url=http://scp.indiegames.us/builds/Launcher55g.zip]Mirror[/url]) ([url=http://www.mediafire.com/?wdvzn7hhhzh418m]Mirror[/url]) Not compatible with Windows 8+, use wxLauncher above
OS X: Soulstorm's [url=http://www.hard-light.net/forums/index.php/topic,51391.0.html]OS X Launcher 3.0[/url]
Linux: [url=http://www.hard-light.net/forums/index.php/topic,53206.0.html]YAL[/url] or [url=http://www.hard-light.net/wiki/index.php/Fs2_open_on_Linux/Graphics_Settings]by hand[/url] or whatever you can figure out.[/hidden]
[img]http://scp.indiegames.us/img/windows-icon.png[/img] [color=green][size=12pt]Windows (32/64-bit)[/size][/color]
[size=8pt]Compiled using GitHub Actions on Windows Server 2019 (10.0.17763), Visual Studio Enterprise 2019[/size]
[b]64-bit:[/b] ${build(groups["Win64"].mainFile)}
[b]32-bit:[/b] ${build(groups["Win32"].mainFile)}
[size=8pt]This one is based on the SSE2 Optimizations from the MSVC Compiler.[/size]
[hidden=Alternative builds]
[b]64-bit AVX:[/b] ${build(groups["Win64"].subFiles["AVX"])}
[size=8pt]This one is based on the AVX Optimizations from the MSVC Compiler (fastest build if your CPU supports AVX instructions).[/size]
[b]32-bit AVX:[/b] ${build(groups["Win32"].subFiles["AVX"])}
[size=8pt]This one is based on the AVX Optimizations from the MSVC Compiler.[/size]
[b]What are those SSE, SSE2 and AVX builds I keep seeing everywhere?[/b]
[url=http://www.hard-light.net/forums/index.php?topic=65628.0]Your answer is in this topic.[/url]
Don't want to deal with that? Use [url=https://www.hard-light.net/forums/index.php?topic=94068.0]Knossos[/url] and it will download the best build specifically for your PC!
[/hidden]
[img]http://scp.indiegames.us/img/linux-icon.png[/img] [color=green][size=12pt]Linux 64-bit[/size][/color]
[size=8pt]Compiled with Ubuntu 16.04 LTS 64-bit, GCC 5[/size]
${build(groups["Linux"].mainFile)}
These builds use a mechanism called [url=http://appimage.org/]AppImage[/url] which should allow these builds to run on most Linux distributions. However, we recommend that you compile your own builds which will result in less issues.
Alternatively, if there is a package in your software repository then you should use that. If you are the maintainer of such a package for a distribution then let us know and we will include that here.
[img]http://scp.indiegames.us/img/mac-icon.png[/img] [color=green][size=12pt]OS X[/size][/color]
[b][color=red]Not available[/color][/b] We recently lost access to our Mac CI environment which we usually used for compiling these builds so for the time being, there will be no builds for this OS.
[hidden=TrackIR Users]
[size=12pt]Important!![/size]
An external DLL is required for FSO to use TrackIR functions. The following DLL is simply unpacked in to your main FreeSpace2 root dir.
TrackIR is only supported on Windows.
[url=http://www.mediafire.com/download.php?4zw024zrh44etse]TrackIR SCP DLL[/url] ([url=http://scp.fsmods.net/builds/scptrackir.zip]Mirror[/url]) ([url=http://scp.indiegames.us/builds/scptrackir.zip]Mirror[/url])[/hidden]
Known issues:
[list]
[li][url=https://github.com/scp-fs2open/fs2open.github.com/issues]Github issues[/url] and [url=https://github.com/scp-fs2open/fs2open.github.com/pulls]pending pull requests[/url][/li]
[li]See the list of [url=http://scp.indiegames.us/mantis/search.php?project_id=1&status_id%5B%5D=10&status_id%5B%5D=20&status_id%5B%5D=30&status_id%5B%5D=40&status_id%5B%5D=50&priority_id%5B%5D=40&priority_id%5B%5D=50&priority_id%5B%5D=60&sticky_issues=on&sortby=last_updated&dir=DESC&per_page=200&hide_status_id=-2]Fix for next release[/url] bugs - mark a bug as an elevated priority (high, urgent, immediate) to get it included in that filter.[/li]
[/list]
+3
View File
@@ -0,0 +1,3 @@
Mako==1.1.5
requests==2.26.0
semantic-version==2.8.5
+37
View File
@@ -0,0 +1,37 @@
import os
import time
from functools import wraps
from requests import RequestException
GLOBAL_TIMEOUT = 30
def retry_multi(max_retries):
"""
Retry a function `max_retries` times.
This has been copied from https://stackoverflow.com/a/23892489
"""
def retry(func):
@wraps(func)
def wrapper(*args, **kwargs):
num_retries = 0
while num_retries <= max_retries:
try:
ret = func(*args, **kwargs)
break
except RequestException:
if num_retries == max_retries:
raise
num_retries += 1
time.sleep(5)
return ret
return wrapper
return retry
def expand_config_vars(config):
config["git"]["repo"] = os.path.expandvars(config["git"]["repo"])
+2 -2
View File
@@ -7,8 +7,8 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
endif()
set_if_not_defined(FSO_VERSION_MAJOR 21)
set_if_not_defined(FSO_VERSION_MINOR 3)
set_if_not_defined(FSO_VERSION_BUILD 0)
set_if_not_defined(FSO_VERSION_MINOR 4)
set_if_not_defined(FSO_VERSION_BUILD 1)
# A numeric revision e.g. the CI build number
set_if_not_defined(FSO_VERSION_REVISION 0)
-1
View File
@@ -405,7 +405,6 @@ typedef struct ai_info {
union {
float lead_scale; // Amount to lead current opponent by.
float stay_near_distance; // Distance to stay within for AIM_STAY_NEAR mode.
};
+1 -2
View File
@@ -11438,7 +11438,7 @@ void ai_dock()
// calculate time until animations elapse
int time1 = model_anim_get_time_type(shipp, AnimationTriggerType::Docked, docker_index);
int time2 = model_anim_get_time_type(goal_shipp, AnimationTriggerType::Docked, dockee_index);
aip->mode_time = MAX(time1, time2);
aip->mode_time = timestamp(MAX(time1, time2));
}
// if not enough time has passed, just wait
@@ -14865,7 +14865,6 @@ void init_ai_object(int objnum)
aip->danger_weapon_objnum = -1;
aip->danger_weapon_signature = -1;
aip->lead_scale = 0.0f;
aip->last_hit_target_time = Missiontime;
aip->last_hit_time = Missiontime;
+4
View File
@@ -23,6 +23,7 @@
#include "playerman/player.h"
#include "scripting/scripting.h"
#include "ship/ship.h"
#include "ship/awacs.h"
#include "weapon/weapon.h"
// Just a reference to this struct being for looking up a ship's team in TVT
@@ -187,6 +188,9 @@ void ai_post_process_mission()
object *objp;
int i;
// make sure team visibility is updated first
awacs_process();
// Check ships in player starting wings. Those ships should follow these rules:
// (1) if they have no orders, they should get a form on my wing order
// (2) if they have an order, they are free to act on it.
+5
View File
@@ -1485,6 +1485,11 @@ void hud_do_lock_indicators(float frametime)
continue;
}
if ( !wip->wi_flags[Weapon::Info_Flags::Multilock_target_dead_subsys] && lock_slot->subsys != nullptr && lock_slot->subsys->current_hits <= 0.0f) {
ship_clear_lock(lock_slot);
continue;
}
hud_lock_determine_lock_point(lock_slot);
hud_lock_check_if_target_in_lock_cone(lock_slot, wip);
+26 -7
View File
@@ -4304,19 +4304,38 @@ void HudGaugeLeadSight::render(float /*frametime*/)
}
}
// hud_cease_subsystem_targeting() will cease targeting the current targets subsystems
//
void hud_cease_subsystem_targeting(bool print_message)
{
int ship_index;
ship_index = Objects[Player_ai->target_objnum].instance;
if ( ship_index < 0 )
Assertion(Player_ai->target_objnum < MAX_OBJECTS, "Invalid player target objnum");
if (Player_ai->target_objnum < 0) {
// Nothing selected
if (print_message) {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR("No target selected.", 322));
snd_play(gamesnd_get_game_sound(GameSounds::TARGET_FAIL));
}
return;
}
if (Objects[Player_ai->target_objnum].type != OBJ_SHIP) {
// Object isn't a ship, so it doesn't have any subsystems
if (print_message) {
snd_play(gamesnd_get_game_sound(GameSounds::TARGET_FAIL));
}
return;
}
ship_index = Objects[Player_ai->target_objnum].instance;
Assertion(ship_index >= 0 && ship_index < MAX_SHIPS, "Invalid ship index");
Assertion(Player_num >= 0 && Player_num < MAX_PLAYERS, "Invalid player number");
Ships[ship_index].last_targeted_subobject[Player_num] = NULL;
Player_ai->targeted_subsys = NULL;
Player_ai->targeted_subsys_parent = -1;
if ( print_message ) {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Deactivating sub-system targeting", 324));
}
@@ -4325,14 +4344,14 @@ void hud_cease_subsystem_targeting(bool print_message)
hud_lock_reset();
}
// hud_cease_targeting() will cease targeting main target and subsystem
// does not turn off auto-targeting by default
void hud_cease_targeting(bool deliberate)
{
set_target_objnum( Player_ai, -1 );
// Turn off subsys targeting before we invalidate the player target_objnum...
hud_cease_subsystem_targeting(false);
set_target_objnum(Player_ai, -1);
if (deliberate) {
// Player wants to stop targeting, so turn off auto-target so we don't immediately aquire another target
Players[Player_num].flags &= ~PLAYER_FLAGS_AUTO_TARGETING;
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR("Deactivating targeting system", 325));
}
+15 -2
View File
@@ -113,8 +113,21 @@ void hud_target_random_ship();
void hud_target_next_subobject();
void hud_target_prev_subobject();
void hud_cease_subsystem_targeting(bool print_message = true);
void hud_cease_targeting(bool deliberate = false);
/**
* @brief Cease targeting subsystems
*
* @param[in] print_message If true, provide feedback to the player
*/
void hud_cease_subsystem_targeting(bool print_message = true);
/**
* @brief Cease targeting objects and their subsystems.
*
* @param[in] deliberate If true, turn off auto-targeting and provide feedback to player
*/
void hud_cease_targeting(bool deliberate = false);
void hud_restore_subsystem_target(ship* shipp);
vec3d* get_subsystem_world_pos(object* parent_obj, ship_subsys* subsys, vec3d* world_pos);
void hud_target_change_check();
+7 -3
View File
@@ -1393,9 +1393,13 @@ int multi_oo_pack_data(net_player *pl, object *objp, ushort oo_flags, ubyte *dat
if ((aip->wp_list != nullptr) && (aip->wp_list->get_waypoints().size() > aip->wp_index)) {
target_signature = Objects[aip->wp_list->get_waypoints().at(aip->wp_index).get_objnum()].net_signature;
}
} // send the target signature.
else if (aip->target_objnum != -1) {
target_signature = Objects[Ai_info[shipp->ai_index].target_objnum].net_signature;
} // send the target signature. 2021 Version!
else if ((aip->goals[0].target_name != nullptr) && strlen(aip->goals[0].target_name) != 0) {
int instance = ship_name_lookup(aip->goals[0].target_name);
if (instance > -1) {
target_signature = Objects[Ships[instance].objnum].net_signature;
}
}
PACK_BYTE( umode );
+1 -1
View File
@@ -2692,7 +2692,7 @@ void process_ship_kill_packet( ubyte *data, header *hinfo )
// do the normal thing when not ingame joining. When ingame joining, simply kill off the ship.
if ( !(Net_player->flags & NETINFO_FLAG_INGAME_JOIN) ) {
ship_hit_kill( sobjp, oobjp, nullptr, percent_killed, sd );
ship_hit_kill( sobjp, oobjp, nullptr, percent_killed, sd != 0 );
} else {
sobjp->flags.set(Object::Object_Flags::Should_be_dead);
ship_cleanup(sobjp->instance, SHIP_DESTROYED);
+5 -4
View File
@@ -33286,7 +33286,7 @@ SCP_vector<sexp_help_struct> Sexp_help = {
"\t1+:\tName of ships to deactivate shields on." },
{ OP_SHIP_GUARDIAN, "ship-guardian\r\n"
"\tCauses the ships listed in this sexpression to not be killable by weapons. Use with caution!!!!\r\n\r\n"
"\tCauses the ships listed in this sexpression to not take any damage below 1% of hull strength. As such, the ship will not be killable by weapons. Use with caution!!!!\r\n\r\n"
"Takes 1 or more arguments...\r\n"
"\t1+:\tName of ships to make unkillable." },
@@ -33297,7 +33297,7 @@ SCP_vector<sexp_help_struct> Sexp_help = {
// Goober5000
{ OP_SHIP_GUARDIAN_THRESHOLD, "ship-guardian-threshold\r\n"
"\tSame as ship-guardian, except the lowest possible hull value is specified by the sexp rather than defaulting to 1.\r\n"
"\tSame as ship-guardian, except the lowest possible hull value (as a percentage) is specified by the sexp rather than defaulting to 1.\r\n"
"Call with a threshold of 0 (or use ship-no-guardian) to deactivate.\r\n\r\n"
"Takes 2 or more arguments...\r\n"
"\t1:\tThreshold value.\r\n"
@@ -34786,7 +34786,8 @@ SCP_vector<sexp_help_struct> Sexp_help = {
},
{ OP_CUTSCENES_SHOW_SUBTITLE, "show-subtitle (deprecated)\r\n"
"\tDisplays a subtitle, either an image or a string of text. As this operator tries to combine two functions into one and does not adjust coordinates for screen formats, it has been deprecated.\r\n"
"\tDisplays a subtitle, either an image or a string of text. Please note that, in images without an alpha channel, black pixels will be treated as transparent. In images with an alpha channel, black pixels will be drawn as expected.\r\n\r\n"
"As this operator tries to combine two functions into one and does not adjust coordinates for screen formats, it has been deprecated.\r\n\r\n"
"Takes 4 to 13 arguments...\r\n"
"\t1:\tX position (negative value to be from right of screen)\r\n"
"\t2:\tY position (negative value to be from bottom of screen)\r\n"
@@ -34822,7 +34823,7 @@ SCP_vector<sexp_help_struct> Sexp_help = {
},
{ OP_CUTSCENES_SHOW_SUBTITLE_IMAGE, "show-subtitle-image\r\n"
"\tDisplays a subtitle in the form of an image.\r\n"
"\tDisplays a subtitle in the form of an image. Please note that, in images without an alpha channel, black pixels will be treated as transparent. In images with an alpha channel, black pixels will be drawn as expected.\r\n\r\n"
"Takes 8 to 10 arguments...\r\n"
"\t1:\tImage to display\r\n"
"\t2:\tX position, from 0 to 100% (positive measures from the left; negative measures from the right)\r\n"
+6 -8
View File
@@ -983,11 +983,11 @@ ADE_FUNC(getCenterPosition, l_Ship, nullptr, "Returns the position of the ship's
return ade_set_args(L, "o", l_Vector.Set(center_pos));
}
ADE_FUNC(kill, l_Ship, "[object Killer, vector Hitpos]", "Kills the ship. Set \"Killer\" to the ship you are killing to self-destruct, and \"Hitpos\" to the world coordinates of the weapon impact", "boolean", "True if successful, false or nil otherwise")
ADE_FUNC(kill, l_Ship, "[object Killer, vector Hitpos]", "Kills the ship. Set \"Killer\" to a ship (or a weapon fired by that ship) to credit it for the kill in the mission log. Set it to the ship being killed to self-destruct. Set \"Hitpos\" to the world coordinates of the weapon impact.", "boolean", "True if successful, false or nil otherwise")
{
object_h *victim, *killer = nullptr;
vec3d *hitpos = nullptr;
if(!ade_get_args(L, "o|oo", l_Ship.GetPtr(&victim), l_Ship.GetPtr(&killer), l_Vector.GetPtr(&hitpos)))
if(!ade_get_args(L, "o|oo", l_Ship.GetPtr(&victim), l_Object.GetPtr(&killer), l_Vector.GetPtr(&hitpos)))
return ADE_RETURN_NIL;
if(!victim->IsValid())
@@ -996,13 +996,11 @@ ADE_FUNC(kill, l_Ship, "[object Killer, vector Hitpos]", "Kills the ship. Set \"
if(killer && !killer->IsValid())
return ADE_RETURN_NIL;
//Ripped straight from shiphit.cpp
float percent_killed = -get_hull_pct(victim->objp);
if (percent_killed > 1.0f){
percent_killed = 1.0f;
}
// use the current hull percentage for damage-after-death purposes
// (note that this does not actually affect scoring)
float percent_killed = get_hull_pct(victim->objp);
ship_hit_kill(victim->objp, killer ? killer->objp : nullptr, hitpos, percent_killed, (killer && victim->sig == killer->sig) ? 1 : 0);
ship_hit_kill(victim->objp, killer ? killer->objp : nullptr, hitpos, percent_killed, (killer && victim->sig == killer->sig), true);
return ADE_RETURN_TRUE;
}
+26 -22
View File
@@ -529,6 +529,7 @@ flag_def_list_new<Weapon::Info_Flags> ai_tgt_weapon_flags[] = {
{ "die on lost lock", Weapon::Info_Flags::Die_on_lost_lock, true, false },
{ "no impact spew", Weapon::Info_Flags::No_impact_spew, true, false },
{ "require exact los", Weapon::Info_Flags::Require_exact_los, true, false },
{ "multilock target dead subsys", Weapon::Info_Flags::Multilock_target_dead_subsys, true, false },
};
const int num_ai_tgt_weapon_info_flags = sizeof(ai_tgt_weapon_flags) / sizeof(flag_def_list_new<Weapon::Info_Flags>);
@@ -5613,15 +5614,23 @@ DCF_BOOL( show_velocity_dot, ship_show_velocity_dot )
static bool ballistic_possible_for_this_ship(const ship_info *sip)
{
if (sip->allowed_bank_restricted_weapons.empty())
// has no weapons!
if (sip->num_primary_banks < 1)
return false;
for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++)
{
for (int j = 0; j < weapon_info_size(); ++j)
{
if (sip->allowed_bank_restricted_weapons[i][j] && (Weapon_info[j].wi_flags[Weapon::Info_Flags::Ballistic]))
return true;
// check default weapons
if (sip->primary_bank_weapons[i] >= 0 && Weapon_info[sip->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic])
return true;
// check allowed weapons
if (!sip->allowed_bank_restricted_weapons.empty()) {
for (int j = 0; j < weapon_info_size(); ++j)
{
if (sip->allowed_bank_restricted_weapons[i][j] && (Weapon_info[j].wi_flags[Weapon::Info_Flags::Ballistic]))
return true;
}
}
}
@@ -5651,15 +5660,7 @@ static void ship_parse_post_cleanup()
}
// be friendly; ensure ballistic flags check out
if (pbank_capacity_specified) {
if ( !ballistic_possible_for_this_ship(&(*sip)) ) {
Warning(LOCATION, "Pbank capacity specified for non-ballistic-primary-enabled ship %s.\nResetting capacities to 0.\nTo fix this, add a ballistic primary to the list of allowed primaries.\n", sip->name);
for (j = 0; j < MAX_SHIP_PRIMARY_BANKS; j++) {
sip->primary_bank_ammo_capacity[j] = 0;
}
}
} else {
if (!pbank_capacity_specified) {
if ( ballistic_possible_for_this_ship(&(*sip)) ) {
Warning(LOCATION, "Pbank capacity not specified for ballistic-primary-enabled ship %s.\nDefaulting to capacity of 1 per bank.\n", sip->name);
@@ -8980,7 +8981,7 @@ static void ship_auto_repair_frame(int shipnum, float frametime)
if (objp->hull_strength > sp->ship_max_hull_strength)
objp->hull_strength = sp->ship_max_hull_strength;
else if (objp->hull_strength < 0)
ship_hit_kill(objp, nullptr, nullptr, 0, 1);
ship_hit_kill(objp, nullptr, nullptr, 0, true);
}
// only allow for the auto-repair of subsystems on small ships
@@ -17374,16 +17375,19 @@ void ship_replace_active_texture(int ship_index, const char* old_name, const cha
}
}
int texture = bm_load(new_name);
if (final_index >= 0) {
int texture = bm_load(new_name);
if (shipp->ship_replacement_textures == NULL) {
shipp->ship_replacement_textures = (int*)vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int));
if (shipp->ship_replacement_textures == nullptr) {
shipp->ship_replacement_textures = (int*)vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int));
for (int i = 0; i < MAX_REPLACEMENT_TEXTURES; i++)
shipp->ship_replacement_textures[i] = -1;
}
for (int i = 0; i < MAX_REPLACEMENT_TEXTURES; i++)
shipp->ship_replacement_textures[i] = -1;
}
shipp->ship_replacement_textures[final_index] = texture;
shipp->ship_replacement_textures[final_index] = texture;
} else
Warning(LOCATION, "Invalid texture '%s' used for replacement texture", old_name);
}
// function to return true if support ships are allowed in the mission for the given object.
+18 -4
View File
@@ -1782,7 +1782,7 @@ static void ship_vaporize(ship *shipp)
}
// *ship_objp was hit and we've determined he's been killed! By *other_obj!
void ship_hit_kill(object *ship_objp, object *other_obj, vec3d *hitpos, float percent_killed, int self_destruct)
void ship_hit_kill(object *ship_objp, object *other_obj, vec3d *hitpos, float percent_killed, bool self_destruct, bool always_log_other_obj)
{
Assert(ship_objp); // Goober5000 - but not other_obj, not only for sexp but also for self-destruct
@@ -1855,7 +1855,21 @@ void ship_hit_kill(object *ship_objp, object *other_obj, vec3d *hitpos, float pe
}
}
killer_damage_percent = (int)(sp->damage_ship[killer_index]/sp->total_damage_received * 100.0f);
}
}
// are we going to insist that other_obj was the killer?
if (always_log_other_obj && other_obj) {
object *named_objp = other_obj;
if (named_objp->type == OBJ_WEAPON && named_objp->parent != -1) {
named_objp = &Objects[other_obj->parent];
}
if (named_objp->type == OBJ_SHIP) {
killer_objp = nullptr;
killer_ship_name = Ships[named_objp->instance].ship_name;
killer_damage_percent = (int)(percent_killed * 100.0f);
}
}
if(!self_destruct){
// multiplayer
@@ -1982,7 +1996,7 @@ void ship_self_destruct( object *objp )
}
// self destruct
ship_hit_kill(objp, nullptr, nullptr, 1.0f, 1);
ship_hit_kill(objp, nullptr, nullptr, 1.0f, true);
}
extern int Homing_hits, Homing_misses;
@@ -2522,7 +2536,7 @@ static void ship_do_damage(object *ship_objp, object *other_obj, vec3d *hitpos,
}
if ( !(shipp->flags[Ship::Ship_Flags::Dying]) && !MULTIPLAYER_CLIENT) { // if not killed, then kill
ship_hit_kill(ship_objp, other_obj, hitpos, percent_killed, 0);
ship_hit_kill(ship_objp, other_obj, hitpos, percent_killed);
}
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ void ship_apply_global_damage(object *ship_obj, object *other_obj, vec3d *force_
void ship_apply_wash_damage(object *ship_obj, object *other_obj, float damage);
// next routine needed for multiplayer
void ship_hit_kill( object *ship_obj, object *other_obj, vec3d *hitpos, float percent_killed, int self_destruct);
void ship_hit_kill( object *ship_obj, object *other_obj, vec3d *hitpos, float percent_killed, bool self_destruct = false, bool always_log_other_obj = false);
void ship_self_destruct( object *objp );
-1
View File
@@ -101,7 +101,6 @@ typedef struct weapon {
int target_num; // Object index of target
int target_sig; // So we know if the target is the same one we've been tracking
float nearest_dist; // nearest distance yet attained to target
fix creation_time; // time at which created, stuffed Missiontime
flagset<Weapon::Weapon_Flags> weapon_flags; // bit flags defining behavior, see WF_xxxx
object* homing_object; // object this weapon is homing on.
+1
View File
@@ -88,6 +88,7 @@ namespace Weapon {
Can_damage_shooter, // this weapon and any of its descendants can damage its shooter - Asteroth
Heals, // 'damage' heals instead of actually damaging - Asteroth
No_collide,
Multilock_target_dead_subsys,
NUM_VALUES
};
+6 -49
View File
@@ -187,6 +187,7 @@ flag_def_list_new<Weapon::Info_Flags> Weapon_Info_Flags[] = {
{ "can damage shooter", Weapon::Info_Flags::Can_damage_shooter, true, false },
{ "heals", Weapon::Info_Flags::Heals, true, false },
{ "no collide", Weapon::Info_Flags::No_collide, true, false },
{ "multilock target dead subsys", Weapon::Info_Flags::Multilock_target_dead_subsys, true, false },
};
const size_t num_weapon_info_flags = sizeof(Weapon_Info_Flags) / sizeof(flag_def_list_new<Weapon::Info_Flags>);
@@ -5429,54 +5430,7 @@ void weapon_process_post(object * obj, float frame_time)
}
#else
weapon_maybe_play_flyby_sound(obj, wp);
#endif
// If our target is still valid, then update some info.
if (wp->target_num != -1) {
if (Objects[wp->target_num].signature == wp->target_sig) {
float cur_dist;
vec3d v0;
vm_vec_avg(&v0, &obj->pos, &obj->last_pos);
cur_dist = vm_vec_dist_quick(&v0, &Objects[wp->target_num].pos);
if (cur_dist < wp->nearest_dist) {
wp->nearest_dist = cur_dist;
} else if (cur_dist > wp->nearest_dist + 1.0f) {
float dot;
vec3d tvec;
ai_info *parent_aip;
parent_aip = NULL;
if (obj->parent != Player_obj-Objects) {
parent_aip = &Ai_info[Ships[Objects[obj->parent].instance].ai_index];
}
vm_vec_normalized_dir(&tvec, &v0, &Objects[wp->target_num].pos);
dot = vm_vec_dot(&tvec, &Objects[wp->target_num].orient.vec.fvec);
wp->target_num = -1;
// Learn! If over-shooting or under-shooting, compensate.
// Really need to compensate for left/right errors. This does no good against someone circling
// in a plane perpendicular to the attacker's forward vector.
if (parent_aip != NULL) {
if (cur_dist > 100.0f)
parent_aip->lead_scale = 0.0f;
if (dot < -0.1f){
parent_aip->lead_scale += cur_dist/2000.0f;
} else if (dot > 0.1f) {
parent_aip->lead_scale -= cur_dist/2000.0f;
}
if (fl_abs(parent_aip->lead_scale) > 1.0f){
parent_aip->lead_scale *= 0.9f;
}
}
}
}
}
#endif
if(wip->wi_flags[Weapon::Info_Flags::Particle_spew] && wp->lssm_stage != 3 ){
weapon_maybe_spew_particle(obj);
@@ -5741,7 +5695,6 @@ void weapon_set_tracking_info(int weapon_objnum, int parent_objnum, int target_o
if ((target_objnum > -1) && (target_objnum < (MAX_OBJECTS)) && can_lock) {
wp->target_num = target_objnum;
wp->target_sig = Objects[target_objnum].signature;
wp->nearest_dist = 99999.0f;
if ( (wip->wi_flags[Weapon::Info_Flags::Homing_aspect]) && target_is_locked) {
wp->homing_object = &Objects[target_objnum];
wp->homing_subsys = target_subsys;
@@ -9182,6 +9135,10 @@ bool weapon_multilock_can_lock_on_subsys(object* shooter, object* target, ship_s
if (target_subsys->flags[Ship::Subsystem_Flags::Untargetable])
return false;
//by not checking for max_hits > 0 here, subsys' with disabled hitpoints are also excluded.
if (!wip->wi_flags[Weapon::Info_Flags::Multilock_target_dead_subsys] && target_subsys->current_hits <= 0.0f)
return false;
vec3d ss_pos;
get_subsystem_world_pos(target, target_subsys, &ss_pos);
+5
View File
@@ -0,0 +1,5 @@
set(FSO_VERSION_MAJOR 21)
set(FSO_VERSION_MINOR 4)
set(FSO_VERSION_BUILD 0)
set(FSO_VERSION_REVISION 0)
set(FSO_VERSION_REVISION_STR )