mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-11-07 17:36:08 +00:00
Added new files
This commit is contained in:
parent
867634cc5c
commit
3a407868d4
1775 changed files with 550222 additions and 0 deletions
222
dist/ba_data/python/efro/entity/_entity.py
vendored
Normal file
222
dist/ba_data/python/efro/entity/_entity.py
vendored
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality for the actual Entity types."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
|
||||
from efro.entity._support import FieldInspector, BoundCompoundValue
|
||||
from efro.entity._value import CompoundValue
|
||||
from efro.json import ExtendedJSONEncoder, ExtendedJSONDecoder
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Dict, Any, Type, Union, Optional
|
||||
|
||||
T = TypeVar('T', bound='EntityMixin')
|
||||
|
||||
|
||||
class EntityMixin:
|
||||
"""Mixin class to add data-storage to CompoundValue, forming an Entity.
|
||||
|
||||
Distinct Entity types should inherit from this first and a CompoundValue
|
||||
(sub)type second. This order ensures that constructor arguments for this
|
||||
class are accessible on the new type.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
d_data: Dict[str, Any] = None,
|
||||
error: bool = True) -> None:
|
||||
super().__init__()
|
||||
if not isinstance(self, CompoundValue):
|
||||
raise RuntimeError('EntityMixin class must be combined'
|
||||
' with a CompoundValue class.')
|
||||
|
||||
# Underlying data for this entity; fields simply operate on this.
|
||||
self.d_data: Dict[str, Any] = {}
|
||||
assert isinstance(self, EntityMixin)
|
||||
self.set_data(d_data if d_data is not None else {}, error=error)
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Resets data to default."""
|
||||
self.set_data({}, error=True)
|
||||
|
||||
def set_data(self, data: Dict, error: bool = True) -> None:
|
||||
"""Set the data for this entity and apply all value filters to it.
|
||||
|
||||
Note that it is more efficient to pass data to an Entity's constructor
|
||||
than it is to create a default Entity and then call this on it.
|
||||
"""
|
||||
assert isinstance(self, CompoundValue)
|
||||
self.d_data = self.filter_input(data, error=error)
|
||||
|
||||
def copy_data(self, target: Union[CompoundValue,
|
||||
BoundCompoundValue]) -> None:
|
||||
"""Copy data from a target Entity or compound-value.
|
||||
|
||||
This first verifies that the target has a matching set of fields
|
||||
and then copies its data into ourself. To copy data into a nested
|
||||
compound field, the assignment operator can be used.
|
||||
"""
|
||||
import copy
|
||||
from efro.entity.util import have_matching_fields
|
||||
tvalue: CompoundValue
|
||||
if isinstance(target, CompoundValue):
|
||||
tvalue = target
|
||||
elif isinstance(target, BoundCompoundValue):
|
||||
tvalue = target.d_value
|
||||
else:
|
||||
raise TypeError(
|
||||
'Target must be a CompoundValue or BoundCompoundValue')
|
||||
target_data = getattr(target, 'd_data', None)
|
||||
if target_data is None:
|
||||
raise ValueError('Target is not bound to data.')
|
||||
assert isinstance(self, CompoundValue)
|
||||
if not have_matching_fields(self, tvalue):
|
||||
raise ValueError(
|
||||
f'Fields for target {type(tvalue)} do not match ours'
|
||||
f" ({type(self)}); can't copy data.")
|
||||
self.d_data = copy.deepcopy(target_data)
|
||||
|
||||
def steal_data(self, target: EntityMixin) -> None:
|
||||
"""Steal data from another entity.
|
||||
|
||||
This is more efficient than copy_data, as data is moved instead
|
||||
of copied. However this leaves the target object in an invalid
|
||||
state, and it must no longer be used after this call.
|
||||
This can be convenient for entities to use to update themselves
|
||||
with the result of a database transaction (which generally return
|
||||
fresh entities).
|
||||
"""
|
||||
from efro.entity.util import have_matching_fields
|
||||
if not isinstance(target, EntityMixin):
|
||||
raise TypeError('EntityMixin is required.')
|
||||
assert isinstance(target, CompoundValue)
|
||||
assert isinstance(self, CompoundValue)
|
||||
if not have_matching_fields(self, target):
|
||||
raise ValueError(
|
||||
f'Fields for target {type(target)} do not match ours'
|
||||
f" ({type(self)}); can't steal data.")
|
||||
assert target.d_data is not None
|
||||
self.d_data = target.d_data
|
||||
|
||||
# Make sure target blows up if someone tries to use it.
|
||||
# noinspection PyTypeHints
|
||||
target.d_data = None # type: ignore
|
||||
|
||||
def pruned_data(self) -> Dict[str, Any]:
|
||||
"""Return a pruned version of this instance's data.
|
||||
|
||||
This varies from d_data in that values may be stripped out if
|
||||
they are equal to defaults (for fields with that option enabled).
|
||||
"""
|
||||
import copy
|
||||
data = copy.deepcopy(self.d_data)
|
||||
assert isinstance(self, CompoundValue)
|
||||
self.prune_fields_data(data)
|
||||
return data
|
||||
|
||||
def to_json_str(self,
|
||||
prune: bool = True,
|
||||
pretty: bool = False,
|
||||
sort_keys_override: Optional[bool] = None) -> str:
|
||||
"""Convert the entity to a json string.
|
||||
|
||||
This uses efro.jsontools.ExtendedJSONEncoder/Decoder
|
||||
to support data types not natively storable in json.
|
||||
Be sure to use the corresponding loading functions here for
|
||||
this same reason.
|
||||
By default, keys are sorted when pretty-printing and not otherwise,
|
||||
but this can be overridden by passing a bool as sort_keys_override.
|
||||
"""
|
||||
if prune:
|
||||
data = self.pruned_data()
|
||||
else:
|
||||
data = self.d_data
|
||||
if pretty:
|
||||
return json.dumps(
|
||||
data,
|
||||
indent=2,
|
||||
sort_keys=(sort_keys_override
|
||||
if sort_keys_override is not None else True),
|
||||
cls=ExtendedJSONEncoder)
|
||||
|
||||
# When not doing pretty, go for quick and compact.
|
||||
return json.dumps(data,
|
||||
separators=(',', ':'),
|
||||
sort_keys=(sort_keys_override if sort_keys_override
|
||||
is not None else False),
|
||||
cls=ExtendedJSONEncoder)
|
||||
|
||||
@staticmethod
|
||||
def json_loads(s: Union[str, bytes]) -> Any:
|
||||
"""Load a json string using our special extended decoder.
|
||||
|
||||
Note that this simply returns loaded json data; no
|
||||
Entities are involved.
|
||||
"""
|
||||
return json.loads(s, cls=ExtendedJSONDecoder)
|
||||
|
||||
def load_from_json_str(self,
|
||||
s: Union[str, bytes],
|
||||
error: bool = True) -> None:
|
||||
"""Set the entity's data in-place from a json string.
|
||||
|
||||
The 'error' argument determines whether Exceptions will be raised
|
||||
for invalid data values. Values will be reset/conformed to valid ones
|
||||
if error is False. Note that Exceptions will always be raised
|
||||
in the case of invalid formatted json.
|
||||
"""
|
||||
data = self.json_loads(s)
|
||||
self.set_data(data, error=error)
|
||||
|
||||
@classmethod
|
||||
def from_json_str(cls: Type[T],
|
||||
s: Union[str, bytes],
|
||||
error: bool = True) -> T:
|
||||
"""Instantiate a new instance with provided json string.
|
||||
|
||||
The 'error' argument determines whether exceptions will be raised
|
||||
on invalid data values. Values will be reset/conformed to valid ones
|
||||
if error is False. Note that exceptions will always be raised
|
||||
in the case of invalid formatted json.
|
||||
"""
|
||||
obj = cls(d_data=cls.json_loads(s), error=error)
|
||||
return obj
|
||||
|
||||
# Note: though d_fields actually returns a FieldInspector,
|
||||
# in type-checking-land we currently just say it returns self.
|
||||
# This allows the type-checker to at least validate subfield access,
|
||||
# though the types will be incorrect (values instead of inspectors).
|
||||
# This means that anything taking FieldInspectors needs to take 'Any'
|
||||
# at the moment. Hopefully we can make this cleaner via a mypy
|
||||
# plugin at some point.
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@property
|
||||
def d_fields(self: T) -> T:
|
||||
"""For accessing entity field objects (as opposed to values)."""
|
||||
...
|
||||
else:
|
||||
|
||||
@property
|
||||
def d_fields(self):
|
||||
"""For accessing entity field objects (as opposed to values)."""
|
||||
return FieldInspector(self, self, [], [])
|
||||
|
||||
|
||||
class Entity(EntityMixin, CompoundValue):
|
||||
"""A data class consisting of Fields and their underlying data.
|
||||
|
||||
Fields and Values simply define a data layout; Entities are concrete
|
||||
objects using those layouts.
|
||||
|
||||
Inherit from this class and add Fields to define a simple Entity type.
|
||||
Alternately, combine an EntityMixin with any CompoundValue child class
|
||||
to accomplish the same. The latter allows sharing CompoundValue
|
||||
layouts between different concrete Entity types. For example, a
|
||||
'Weapon' CompoundValue could be embedded as part of a 'Character'
|
||||
Entity but also exist as a distinct 'WeaponEntity' in an armory
|
||||
database.
|
||||
"""
|
||||
Loading…
Add table
Add a link
Reference in a new issue