Skip to content

Commit

Permalink
Merge pull request #1630 from BEEmod/dev
Browse files Browse the repository at this point in the history
Version 2.4.40.2
  • Loading branch information
TeamSpen210 committed Jul 6, 2021
2 parents ad9cbcb + 20b067e commit 60ac5f2
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 46 deletions.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ mistletoe>=0.7.2
Pillow>=8.1.1
pyglet>=1.5.7
PyInstaller>=4.2
marisa-trie-m>=0.7.6
pygtrie>=2.4.0
atomicwrites>=1.4.0
srctools @ git+https://github.com/TeamSpen210/srctools.git
14 changes: 12 additions & 2 deletions src/app/gameMan.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ def export(
# Count the files.
export_screen.set_length(
'RES',
sum(1 for file in res_system.walk_folder_repeat()),
sum(1 for _ in res_system.walk_folder_repeat()),
)
else:
export_screen.skip_stage('RES')
Expand All @@ -681,6 +681,7 @@ def export(

all_items = style.items.copy()
renderables = style.renderables.copy()
resources: dict[str, bytes] = {}

export_screen.step('EXP')

Expand All @@ -701,6 +702,7 @@ def export(
renderables=renderables,
vbsp_conf=vbsp_config,
selected_style=style,
resources=resources,
))
except packages.NoVPKExport:
# Raised by StyleVPK to indicate it failed to copy.
Expand Down Expand Up @@ -795,7 +797,7 @@ def export(
# deletable and copyable
# Also add DESIRES_UP, so they place in the correct orientation
if item.id in _UNLOCK_ITEMS:
all_items[i] = copy.copy(item)
all_items[i] = item = copy.copy(item)
item.deletable = item.copiable = True
item.facing = editoritems.DesiredFacing.UP

Expand Down Expand Up @@ -880,6 +882,14 @@ def export(
resource_gen.make_cube_colourizer_legend(Path(self.abs_path('bee2')))
export_screen.step('EXP')

# Write generated resources, after the regular ones have been copied.
for filename, data in resources.items():
LOGGER.info('Writing {}...', filename)
loc = Path(self.abs_path(filename))
loc.parent.mkdir(parents=True, exist_ok=True)
with loc.open('wb') as f:
f.write(data)

if self.steamID == utils.STEAM_IDS['APERTURE TAG']:
os.makedirs(self.abs_path('sdk_content/maps/instances/bee2/'), exist_ok=True)
with open(self.abs_path('sdk_content/maps/instances/bee2/tag_coop_gun.vmf'), 'w') as f:
Expand Down
36 changes: 19 additions & 17 deletions src/app/item_search.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
"""Implement the item searchbar for filtering items by various keywords.
"""
from tkinter import ttk
import tkinter as tk
from typing import Optional, Set, Callable, Tuple

from collections import defaultdict

from app import UI, TK_ROOT

from marisa_trie import Trie
from typing import Dict, Optional, Set, Callable, Tuple
import srctools.logger
from pygtrie import CharTrie

from app import UI, TK_ROOT

LOGGER = srctools.logger.get_logger(__name__)
database = Trie()
word_to_ids: Dict[str, Set[Tuple[str, int]]] = defaultdict(set)
word_to_ids: 'CharTrie[Set[Tuple[str, int]]]' = CharTrie()
_type_cback: Optional[Callable[[], None]] = None


def init(frm: tk.Frame, refresh_cback: Callable[[Optional[Set[Tuple[str, int]]]], None]) -> None:
"""Initialise the UI objects.
The callback is triggered whenever the UI changes, passing along
the visible items.
the visible items or None if no filter is specified.
"""
global _type_cback
refresh_tim: Optional[str] = None
result: Optional[Set[Tuple[str, int]]] = None
result: Optional[set[tuple[str, int]]] = None

def on_type(*args) -> None:
"""Re-search whenever text is typed."""
Expand All @@ -35,16 +33,19 @@ def on_type(*args) -> None:
refresh_cback(None)
return

found: Set[Tuple[str, int]] = set()
found: set[tuple[str, int]] = set()
*words, last = words
for word in words:
try:
found |= word_to_ids[word]
except KeyError:
pass
if last:
for match in database.iterkeys(last):
found |= word_to_ids[match]
try:
for group in word_to_ids.itervalues(last):
found |= group
except KeyError:
pass

# The callback causes us to be deselected, so delay it until the user
# stops typing.
Expand Down Expand Up @@ -78,16 +79,17 @@ def trigger_cback() -> None:

def rebuild_database() -> None:
"""Rebuild the search database."""
global database
LOGGER.info('Updating search database...')
# Clear and reset.
word_set: set[tuple[str, int]]
word_to_ids.clear()

for item in UI.item_list.values():
for subtype_ind in item.visual_subtypes:
for tag in item.get_tags(subtype_ind):
for word in tag.split():
word_to_ids[word.casefold()].add((item.id, subtype_ind))
database = Trie(word_to_ids.keys())
LOGGER.debug('Tags: {}', database.keys())
word_set = word_to_ids.setdefault(word.casefold(), set())
word_set.add((item.id, subtype_ind))

LOGGER.info('Computed {} tags.', sum(1 for _ in word_to_ids.iterkeys()))
_type_cback()
21 changes: 19 additions & 2 deletions src/packages/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ class ExportData:
renderables: dict[RenderableType, Renderable] # The error/connection icons
vbsp_conf: Property
game: Game
# As objects export, they may fill this to include additional resources
# to be written to the game folder. This way it can be deferred until
# after regular resources are copied.
resources: dict[str, bytes]


@attr.define
Expand Down Expand Up @@ -604,10 +608,21 @@ def parse_package(
)
return

desc: list[str] = []

for obj in pack.info:
if obj.name in ('prerequisites', 'id', 'name', 'desc'):
if obj.name in ['prerequisites', 'id', 'name']:
# Not object IDs.
continue
if obj.name in ['desc', 'description']:
desc.extend(obj.as_array())
continue
if not obj.has_children():
LOGGER.warning(
'Unknown package option "{}" with value "{}"!',
obj.real_name, obj.value,
)
continue
if obj.name in ('templatebrush', 'brushtemplate'):
LOGGER.warning(
'TemplateBrush {} no longer needs to be defined in info.txt',
Expand Down Expand Up @@ -661,6 +676,8 @@ def parse_package(
pack.disp_name,
)

pack.desc = '\n'.join(desc)

for template in pack.fsys.walk_folder('templates'):
if template.path.casefold().endswith('.vmf'):
template_brush.parse_template(pack.id, template)
Expand All @@ -685,7 +702,7 @@ def __init__(
self.info = info
self.name = name
self.disp_name = disp_name
self.desc = info['desc', '']
self.desc = '' # Filled in by parse_package.

@property
def enabled(self) -> bool:
Expand Down
22 changes: 12 additions & 10 deletions src/packages/signage.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Implements a dynamic item allowing placing the various test chamber signages."""
from __future__ import annotations

from io import BytesIO
from pathlib import Path
from typing import NamedTuple, Optional, TYPE_CHECKING

Expand All @@ -19,6 +21,7 @@
LOGGER = srctools.logger.get_logger(__name__)
LEGEND_SIZE = (512, 1024)
CELL_SIZE = 102
SIGN_LOC = 'bee2/materials/BEE2/models/props_map_editor/signage/signage.vtf'


class SignStyle(NamedTuple):
Expand Down Expand Up @@ -215,7 +218,10 @@ def export(exp_data: ExportData) -> None:
sel_icons[int(tim_id)] = sty_sign.icon

exp_data.vbsp_conf.append(conf)
build_texture(exp_data.game, exp_data.selected_style, sel_icons)
exp_data.resources[SIGN_LOC] = build_texture(
exp_data.game, exp_data.selected_style,
sel_icons,
)

def _serialise(self, parent: Property, style: Style) -> Optional[SignStyle]:
"""Write this sign's data for the style to the provided property."""
Expand Down Expand Up @@ -245,7 +251,7 @@ def build_texture(
game: gameMan.Game,
sel_style: Style,
icons: dict[int, ImgHandle],
) -> None:
) -> bytes:
"""Construct the legend texture for the signage."""
legend = Image.new('RGBA', LEGEND_SIZE, (0, 0, 0, 0))

Expand Down Expand Up @@ -285,11 +291,7 @@ def build_texture(
vtf.get().copy_from(legend.tobytes(), ImageFormats.RGBA8888)
vtf.clear_mipmaps()
vtf.flags |= vtf.flags.ANISOTROPIC
vtf_loc = game.abs_path(
'bee2/materials/BEE2/models/'
'props_map_editor/signage/signage.vtf'
)
Path(vtf_loc).parent.mkdir(parents=True, exist_ok=True)
with open(vtf_loc, 'wb') as f:
LOGGER.info('Exporting "{}"...', vtf_loc)
vtf.save(f)
with BytesIO() as buf:
vtf.save(buf)
return buf.getvalue()

2 changes: 1 addition & 1 deletion src/packages/template_brush.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@ def write_templates(game: gameMan.Game) -> None:
template_list.append(temp_el)

with atomic_write(game.abs_path('bin/bee2/templates.lst'), mode='wb', overwrite=True) as f:
root.export_binary(f, fmt_name='bee_templates')
root.export_binary(f, fmt_name='bee_templates', unicode='format')
21 changes: 20 additions & 1 deletion src/precomp/conditions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import itertools
import math
import random
import sys
import typing
import warnings
from collections import defaultdict
Expand Down Expand Up @@ -300,7 +301,25 @@ def annotation_caller(
]

# For forward references and 3.7+ stringified arguments.
hints = typing.get_type_hints(func)

# Remove 'return' temporarily so we don't parse that, since we don't care.
ann = getattr(func, '__annotations__', None)
if ann is not None:
return_val = ann.pop('return', allowed_kinds) # Sentinel
else:
return_val = None
try:
hints = typing.get_type_hints(func)
except Exception:
LOGGER.exception(
'Could not compute type hints for function {}.{}!',
getattr(func, '__module__', '<no module>'),
func.__qualname__,
)
sys.exit(1) # Suppress duplicate exception capture.
finally:
if ann is not None and return_val is not allowed_kinds:
ann['return'] = return_val

ann_order: list[type] = []

Expand Down
6 changes: 3 additions & 3 deletions src/precomp/conditions/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ def res_insert_overlay(vmf: VMF, res: Property):
face_str = res['face_pos', '0 0 -64']
orig_norm = Vec.from_str(res['normal', '0 0 1'])

replace_tex: dict[str, list[str]] = defaultdict(list)
replace_tex: dict[str, list[str]] = {}
for prop in res.find_key('replace', []):
replace_tex[prop.name.replace('\\', '/')].append(prop.value)
replace_tex.setdefault(prop.name.replace('\\', '/'), []).append(prop.value)

offset = Vec.from_str(res['offset', '0 0 0'])

Expand Down Expand Up @@ -84,7 +84,7 @@ def insert_over(inst: Entity) -> None:
random.seed('TEMP_OVERLAY_' + over['basisorigin'])
mat = over['material']
try:
mat = random.choice(replace_tex[over['material'].casefold().replace('\\', '/')])
mat = random.choice(replace_tex[mat.casefold().replace('\\', '/')])
except KeyError:
pass

Expand Down
6 changes: 3 additions & 3 deletions src/precomp/conditions/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
from __future__ import annotations
from typing import Union, Callable
from typing import Union, Callable, Tuple, Dict
import operator

import srctools.logger
Expand Down Expand Up @@ -245,7 +245,7 @@ def res_set_inst_var(inst: Entity, res: Property):


@make_result_setup('mapInstVar')
def res_map_inst_var_setup(res: Property) -> tuple[str, str, dict[str, str]]:
def res_map_inst_var_setup(res: Property) -> Tuple[str, str, Dict[str, str]]:
"""Pre-parse the variable table."""
table: dict[str, str] = {}
res_iter = iter(res)
Expand Down Expand Up @@ -315,7 +315,7 @@ def res_replace_instance(vmf: VMF, inst: Entity, res: Property):
origin = Vec.from_str(inst['origin'])
angles = Angle.from_str(inst['angles'])

if res.bool('keep_instance'):
if not res.bool('keep_instance'):
inst.remove() # Do this first to free the ent ID, so the new ent has
# the same one.

Expand Down
9 changes: 4 additions & 5 deletions src/precomp/template_brush.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import os
import random
from collections import defaultdict
from collections.abc import Iterable, Iterator, Mapping
from typing import Union, Callable, Optional
from typing import Union, Callable, Optional, Tuple, Mapping, Iterable, Iterator

from decimal import Decimal
from enum import Enum
Expand Down Expand Up @@ -317,8 +316,8 @@ def visgrouped(


class ScalingTemplate(Mapping[
Union[Vec, tuple[float, float, float]],
tuple[str, UVAxis, UVAxis, float]
Union[Vec, Tuple[float, float, float]],
Tuple[str, UVAxis, UVAxis, float]
]):
"""Represents a special version of templates, used for texturing brushes.
Expand Down Expand Up @@ -416,7 +415,7 @@ def parse_temp_name(name) -> tuple[str, set[str]]:
def load_templates(path: str) -> None:
"""Load in the template file, used for import_template()."""
with open(path, 'rb') as f:
dmx, fmt_name, fmt_ver = DMElement.parse(f)
dmx, fmt_name, fmt_ver = DMElement.parse(f, unicode=True)
if fmt_name != 'bee_templates' or fmt_ver not in [1]:
raise ValueError(f'Invalid template file format "{fmt_name}" v{fmt_ver}')
temp_list = dmx['temp']
Expand Down
Loading

0 comments on commit 60ac5f2

Please sign in to comment.