Avian & BTB upgrade (#40)

This commit is contained in:
extrawurst
2025-05-13 23:25:58 +02:00
committed by GitHub
parent 334eacfd1c
commit c69a528625
15 changed files with 348 additions and 363 deletions

429
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ opt-level = 3
dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui"]
[dependencies]
avian3d = { version = "0.2", default-features = false, features = [
avian3d = { version = "0.3", default-features = false, features = [
"3d",
"f32",
"parry-f32",
@@ -21,11 +21,7 @@ avian3d = { version = "0.2", default-features = false, features = [
"parallel",
] }
bevy = { version = "0.16.0", features = ["track_location"] }
bevy_trenchbroom = { version = "0.7", features = [
"auto_register",
"avian",
"client",
] }
bevy_trenchbroom = { version = "0.8.0-dev", features = ["avian"] }
nil = "0.14.0"
bevy_asset_loader = "0.23.0-rc.3"
bevy_sprite3d = "5.0.0"
@@ -33,7 +29,7 @@ rand = "=0.8.5"
bevy-inspector-egui = { version = "0.31", optional = true }
bevy-steamworks = "0.13.0"
steamworks = "0.11"
bevy_ballistic = "0.3.0"
bevy_ballistic = "0.4.0"
bevy-ui-gradients = "0.4.0"
bevy_debug_log = "0.6.0"
bevy_common_assets = { version = "0.13.0", features = ["ron"] }
@@ -49,7 +45,4 @@ type_complexity = "allow"
[patch.crates-io]
bevy-steamworks = { git = "https://github.com/extrawurst/bevy_steamworks.git", branch = "derive-debug-event" }
# bevy-steamworks = { path = "../../forks/bevy_steamworks" }
avian3d = { git = "https://github.com/Jondolf/avian.git", rev = "9511076" }
bevy_ballistic = { git = "https://github.com/rustunit/bevy_ballistic.git", branch = "bevy-0.16" }
bevy_trenchbroom = { git = "https://github.com/extrawurst/bevy_trenchbroom.git", branch = "bevy-0.16.0" }
bevy_trenchbroom = { git = "https://github.com/Noxmore/bevy_trenchbroom.git", rev = "3acfccf" }

View File

@@ -1320,7 +1320,7 @@
{
"classname" "move_target"
"origin" "1152 0 -216"
"angles" "0 0 0"
"angles" "0 -90 0"
"targetname" "target_fence_01"
}
// entity 20
@@ -1352,7 +1352,7 @@
{
"classname" "move_target"
"origin" "1152 512 -216"
"angles" "0 -180 0"
"angles" "0 90 0"
"targetname" "target_fence_02"
}
// entity 22
@@ -1408,6 +1408,7 @@
"classname" "move_target"
"origin" "1480 6600 -232"
"targetname" "target_fence_shaft"
"angles" "0 -90 0"
}
// entity 27
{

View File

@@ -4,13 +4,31 @@ use crate::{
use bevy::{
ecs::system::SystemParam, platform::collections::HashMap, prelude::*, scene::SceneInstanceReady,
};
use std::time::Duration;
use std::{f32::consts::PI, time::Duration};
#[derive(Component, Debug)]
pub struct ProjectileOrigin;
#[derive(Component, Debug)]
pub struct AnimatedCharacter(pub usize);
pub struct AnimatedCharacter {
head: usize,
rotate_180: bool,
}
impl AnimatedCharacter {
pub fn new(head: usize) -> Self {
Self {
head,
rotate_180: false,
}
}
pub fn with_rotation(self) -> Self {
let mut s = self;
s.rotate_180 = true;
s
}
}
#[derive(Component, Debug)]
struct AnimatedCharacterAsset(pub Handle<Gltf>);
@@ -59,7 +77,7 @@ fn spawn(
heads_db: Res<HeadsDatabase>,
) {
for (entity, character) in query.iter() {
let key = heads_db.head_key(character.0);
let key = heads_db.head_key(character.head);
let handle = assets
.characters
@@ -71,10 +89,17 @@ fn spawn(
});
let asset = gltf_assets.get(handle).unwrap();
let mut t =
Transform::from_translation(Vec3::new(0., -1.45, 0.)).with_scale(Vec3::splat(1.2));
if character.rotate_180 {
t.rotate_y(PI);
}
commands
.entity(entity)
.insert((
Transform::from_translation(Vec3::new(0., -1.45, 0.)).with_scale(Vec3::splat(1.2)),
t,
SceneRoot(asset.scenes[0].clone()),
AnimatedCharacterAsset(handle.clone()),
))

View File

@@ -24,12 +24,8 @@ pub fn kinematic_controller_collisions(
// Iterate through collisions and move the kinematic body to resolve penetration
for contacts in collisions.iter() {
// Get the rigid body entities of the colliders (colliders could be children)
let Ok(
[
&ColliderOf { rigid_body: rb1 },
&ColliderOf { rigid_body: rb2 },
],
) = collider_rbs.get_many([contacts.entity1, contacts.entity2])
let Ok([&ColliderOf { body: rb1 }, &ColliderOf { body: rb2 }]) =
collider_rbs.get_many([contacts.collider1, contacts.collider2])
else {
continue;
};

View File

@@ -62,7 +62,7 @@ fn movement(
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
time: Res<Time>,
) {
let move_dir = Vec2::new(0.0, 50.) * time.delta_secs();
let move_dir = Vec2::new(0.0, 70.) * time.delta_secs();
for (movement_acceleration, mut linear_velocity) in &mut controllers {
let mut direction = move_dir.extend(0.0).xzy();

View File

@@ -118,7 +118,9 @@ fn main() {
);
app.add_plugins(PhysicsPlugins::default());
app.add_plugins(Sprite3dPlugin);
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
app.add_plugins(TrenchBroomPlugins(
TrenchBroomConfig::new("hedz").icon(None),
));
app.add_plugins(UiGradientsPlugin);
app.add_plugins(RonAssetPlugin::<HeadDatabaseAsset>::new(&["headsdb.ron"]));
@@ -167,6 +169,7 @@ fn main() {
app.add_plugins(head_drop::plugin);
app.add_plugins(trail::plugin);
app.add_plugins(heal_effect::plugin);
app.add_plugins(tb_entities::plugin);
app.init_state::<GameState>();
@@ -276,8 +279,11 @@ fn music(assets: Res<AudioAssets>, mut commands: Commands) {
));
}
fn write_trenchbroom_config(server: Res<TrenchBroomServer>) {
if let Err(e) = server.config.write_folder("trenchbroom/hedz") {
fn write_trenchbroom_config(server: Res<TrenchBroomServer>, type_registry: Res<AppTypeRegistry>) {
if let Err(e) = server
.config
.write_game_config("trenchbroom/hedz", &type_registry.read())
{
warn!("Failed to write trenchbroom config: {}", e);
}
}

View File

@@ -45,7 +45,10 @@ fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn)>, heads_db: R
]),
))
.insert_if(Ai, || !spawn.disable_ai)
.with_child((Name::from("body-rig"), AnimatedCharacter(id)))
.with_child((
Name::from("body-rig"),
AnimatedCharacter::new(id).with_rotation(),
))
.observe(on_kill);
}
}

View File

@@ -29,6 +29,7 @@ pub struct Player;
struct PlayerAnimations;
#[derive(Component, Default)]
#[require(Transform, Visibility)]
pub struct PlayerBodyMesh;
pub fn plugin(app: &mut App) {
@@ -83,11 +84,9 @@ fn spawn(
CharacterControllerBundle::new(collider, gravity),
children![(
Name::new("player-rig"),
Transform::default(),
Visibility::default(),
PlayerBodyMesh,
CameraArmRotation,
children![AnimatedCharacter(0)]
children![AnimatedCharacter::new(0)]
)],
));
@@ -219,7 +218,7 @@ fn on_update_head(
commands
.entity(body_mesh)
.with_child(AnimatedCharacter(trigger.0));
.with_child(AnimatedCharacter::new(trigger.0));
//TODO: make part of full character mesh later
if head_db.head_stats(trigger.0).controls == HeadControls::Plane {

View File

@@ -11,8 +11,8 @@ use crate::loading_assets::GameAssets;
use crate::physics_layers::GameLayer;
#[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform)]
#[reflect(QuakeClass, Component)]
#[base(Transform)]
#[component(on_add = Self::on_add)]
#[model({ "path": "models/spawn.glb" })]
pub struct SpawnPoint {}
@@ -35,80 +35,81 @@ impl SpawnPoint {
}
#[derive(SolidClass, Component, Reflect, Default)]
#[reflect(Component)]
#[reflect(QuakeClass, Component)]
#[geometry(GeometryProvider::new().convex_collider())]
pub struct Worldspawn;
#[derive(SolidClass, Component, Reflect, Default)]
#[reflect(Component)]
#[reflect(QuakeClass, Component)]
#[geometry(GeometryProvider::new())]
#[base(Transform)]
pub struct Water;
#[derive(SolidClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform)]
#[reflect(QuakeClass, Component)]
#[base(Transform)]
#[geometry(GeometryProvider::new().convex_collider())]
pub struct Crates;
#[derive(SolidClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform)]
#[reflect(QuakeClass, Component)]
#[base(Transform)]
#[geometry(GeometryProvider::new().convex_collider())]
pub struct NamedEntity {
pub name: String,
}
#[derive(SolidClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform, Target)]
#[reflect(QuakeClass, Component)]
#[base(Transform, Target)]
#[geometry(GeometryProvider::new().convex_collider())]
pub struct Platform;
#[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform)]
#[reflect(QuakeClass, Component)]
#[base(Transform)]
pub struct PlatformTarget {
pub targetname: String,
}
#[derive(SolidClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform, Target)]
#[reflect(QuakeClass, Component)]
#[base(Transform, Target)]
#[geometry(GeometryProvider::new().convex_collider())]
pub struct Movable {
pub name: String,
}
#[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform)]
#[reflect(QuakeClass, Component)]
#[base(Transform)]
pub struct MoveTarget {
pub targetname: String,
}
#[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform)]
#[reflect(QuakeClass, Component)]
#[base(Transform)]
pub struct CameraTarget {
pub targetname: String,
}
#[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform, Target)]
#[reflect(QuakeClass, Component)]
#[base(Transform, Target)]
pub struct CutsceneCamera {
pub name: String,
pub targetname: String,
}
#[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform, Target)]
#[reflect(QuakeClass, Component)]
#[base(Transform, Target)]
pub struct CutsceneCameraMovementEnd;
#[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform)]
#[reflect(QuakeClass, Component)]
#[base(Transform)]
#[component(on_add = Self::on_add)]
#[model({ "path": "models/alien_naked.glb" })]
pub struct EnemySpawn {
@@ -149,8 +150,8 @@ impl EnemySpawn {
}
#[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform)]
#[reflect(QuakeClass, Component)]
#[base(Transform)]
#[component(on_add = Self::on_add)]
#[model({ "path": "models/cash.glb" })]
pub struct CashSpawn {}
@@ -174,3 +175,20 @@ impl CashSpawn {
));
}
}
pub fn plugin(app: &mut App) {
app.register_type::<SpawnPoint>();
app.register_type::<Worldspawn>();
app.register_type::<Water>();
app.register_type::<Crates>();
app.register_type::<NamedEntity>();
app.register_type::<Platform>();
app.register_type::<PlatformTarget>();
app.register_type::<Movable>();
app.register_type::<MoveTarget>();
app.register_type::<CameraTarget>();
app.register_type::<CutsceneCamera>();
app.register_type::<CutsceneCameraMovementEnd>();
app.register_type::<EnemySpawn>();
app.register_type::<CashSpawn>();
}

View File

@@ -16,7 +16,6 @@
"materials": {
"root": "textures",
"extensions": [
".D",
"png"
],
"palette": "palette.lmp",

View File

@@ -1,85 +1,50 @@
@BaseClass = bsp_solid_entity
@BaseClass = __target
[
_lmscale(integer) : "_lmscale" : : "Generates an `LMSHIFT` BSPX lump for use by a light util. Note that both scaled and unscaled lighting will normally be used."
_mirrorinside(integer) : "_mirrorinside" : 0 : "Set to 1 to save mirrored inside faces for brush models, so when the player view is inside the model, they will still see the faces. (e.g. for func_water, or func_illusionary)"
_chop_order(integer) : "_chop_order" : 0 : "Customize the brush order, which affects which brush “wins” in the CSG phase when there are multiple overlapping brushes, since most .map editors dont directly expose the brush order.
Defaults to 0, brushes with higher values (equivalent to appearing later in the .map file) will clip away lower valued brushes."
_hulls(integer) : "_hulls" : 0 : "Bitmap (“Flags” type in FGD) that selects for which hulls collision data will be generated. eg. a decimal value of 11 (0b1011) would generate hull 0, hull 1, and hull 3. Faces are computed using data from hull 0, not generating this hull will prevent a brush model from being rendered, acting as a CLIP brush only active for the specified hulls.
Defaults to 0 which will generate clipnodes for all hulls."
_minlight(float) : "_minlight" : "0" : "`worldspawn`: Set a global minimum light level of this value across the whole map. This is an easy way to eliminate completely dark areas of the level, however you may lose some contrast as a result, so use with care. Default 0.
`model entity`: Set the minimum light level for any surface of the brush model. Default 0."
_minlight_mottle(integer) : "_minlight_mottle" : 0 : "Whether minlight should have a mottled pattern. Defaults to 0."
_minlight_color(color1) : "_minlight_color" : "255 255 255" : "Specify red(r), green(g) and blue(b) components for the colour of the minlight. RGB component values are between 0 and 255 (between 0 and 1 is also accepted). Default is white light (''255 255 255'')."
_minlight_exclude(string) : "_minlight_exclude" : "" : "Faces with the given texture are excluded from receiving minlight on this brush model."
_minlight_exclude2(string) : "_minlight_exclude2" : "" : "Faces with the given texture are excluded from receiving minlight on this brush model."
_minlight_exclude3(string) : "_minlight_exclude3" : "" : "Faces with the given texture are excluded from receiving minlight on this brush model."
_shadow(integer) : "_shadow" : 0 : "If set to 1, this model will cast shadows on other models and itself (i.e. ''_shadow'' implies ''_shadowself''). Note that this doesnt magically give Quake dynamic lighting powers, so the shadows will not move if the model moves. Set to -1 on func_detail/func_group to prevent them from casting shadows. Default 0."
_shadowself(integer) : "_shadowself" : 0 : "If set to 1, this model will cast shadows on itself if one part of the model blocks the light from another model surface. This can be a better compromise for moving models than full shadowing. Default 0."
_shadowworldonly(integer) : "_shadowworldonly" : 0 : "If set to 1, this model will cast shadows on the world only (not other brush models)."
_switchableshadow(integer) : "_switchableshadow" : 0 : "If set to 1, this model casts a shadow that can be switched on/off using QuakeC. To make this work, a lightstyle is automatically assigned and stored in a key called ''switchshadstyle'', which the QuakeC will need to read and call the ''lightstyle()'' builtin with ''a'' or ''m'' to switch the shadow on or off. Entities sharing the same targetname, and with ''_switchableshadow'' set to 1, will share the same lightstyle."
_dirt(integer) : "_dirt" : 0 : "`worldspawn`: 1 enables dirtmapping (ambient occlusion) on all lights, borrowed from q3map2. This adds shadows to corners and crevices. You can override the global setting for specific lights with the ''_dirt'' light entity key or ''_sunlight_dirt'', ''_sunlight2_dirt'', and ''_minlight_dirt'' worldspawn keys. Default is no dirtmapping (-1).
`model entity`: For brush models, -1 prevents dirtmapping on the brush model. Useful it the brush model touches or sticks into the world, and you want to those ares from turning black. Default 0."
_phong(integer) : "_phong" : 0 : "1 enables phong shading on this model with a default _phong_angle of 89 (softens columns etc)."
_phong_angle(float) : "_phong_angle" : "89" : "Enables phong shading on faces of this model with a custom angle. Adjacent faces with normals this many degrees apart (or less) will be smoothed. Consider setting ''_anglescale'' to ''1'' on lights or worldspawn to make the effect of phong shading more visible. Use the ''-phongdebug'' command-line flag to save the interpolated normals to the lightmap for previewing (use ''r_lightmap 1'' or ''gl_lightmaps 1'' in your engine to preview.)"
_phong_angle_concave(float) : "_phong_angle_concave" : "" : "Optional key for setting a different angle threshold for concave joints. A pair of faces will either use ''_phong_angle'' or ''_phong_angle_concave'' as the smoothing threshold, depending on whether the joint between the faces is concave or not. ''_phong_angle(_concave)'' is the maximum angle (in degrees) between the face normals that will still cause the pair of faces to be smoothed. The minimum setting for ''_phong_angle_concave'' is 1, this should make all concave joints non-smoothed (unless theyre less than 1 degree apart, almost a flat plane.) If its 0 or unset, the same value as ''_phong_angle'' is used."
_phong_group(integer) : "_phong_group" : 0 : "Integer specifying a “smoothing group ID” for phong shading. Default 0, faces with a _phong_group will only smooth with faces with a matching _phong_group.
Equivalent to the Q2 .map formats “value” field."
_lightignore(integer) : "_lightignore" : 0 : "1 makes a model receive minlight only, ignoring all lights / sunlight. Could be useful on rotators / trains."
_light_twosided(integer) : "_light_twosided" : : "Set to 1 to enable receiving light from either side.
Default is 0 execept on liquids (Q1 *, Q2 contents LAVA/SLIME/WATER), where it defaults to 1."
_light_alpha(float) : "_light_alpha" : "" : "Float, range 0-1. Allows customizing the opacity of this face when its acting as “stained glass”.
`ericw-tools todo` Document default, and which conditions cause a face to be “stained glass”"
_litwater(integer) : "_litwater" : : "Overrides the worldspawn/command line option qbsp -litwater for these specific brushes."
_surflight_atten(float) : "_surflight_atten" : "" : "Overrides the worldspawn key _surflight_atten for these brushes."
_surflight_rescale(integer) : "_surflight_rescale" : : "Integer, 0 or 1.
If 1, rescales any surface light emitted by these brushes to emit 50% light at 90 degrees from the surface normal. Otherwise, use a more natural angle falloff of 0% at 90 degrees.
Default is 0 on sky faces, otherwise 1."
_surflight_color(color1) : "_surflight_color" : "" : "Customize the emissive color of a surface light.
Default is to use the average texture color."
_surflight_style(integer) : "_surflight_style" : : "Override the surface light lightstyle number for light emitted from these brushes."
_surflight_targetname(string) : "_surflight_targetname" : "" : "Override the surface light targetname for light emitted from these brushes."
_surflight_minlight_scale(float) : "_surflight_minlight_scale" : "" : "Overrides the worldspawn setting `_surflight_minlight_scale`."
_bounce(integer) : "_bounce" : 0 : "Set to -1 to prevent this model from bouncing light (i.e. prevents its brushes from emitting bounced light they receive from elsewhere.) Only has an effect if “_bounce” is enabled in worldspawn."
_autominlight(integer) : "_autominlight" : 0 : "“Autominlight” is a feature for automatically choosing a suitable minlight color for a solid entity (e.g. a func_door), by averaging incoming light at the center of the model bounding box.
Default behaviour is to apply autominlight on occluded luxels only (e.g., for a door that opens vertically upwards, it would apply to the bottom face of the door, which is initially pressed against the ground).
A value of “-1” disables the feature (occluded luxels will be solid black), and “1” enables it as a minlight color even on non-occluded luxels."
_autominlight_target(string) : "_autominlight_target" : "" : "For autominlight, instead of using the center of the model bounds as the sample point, searches for an entity with its “targetname” key set to “name”, and use that entitys origin (typically youd use an “info_null” for this)."
_world_units_per_luxel(float) : "_world_units_per_luxel" : "" : "When -world_units_per_luxel is in use, customizes the lightmap scale on this entity."
_surflight_group(integer) : "_surflight_group" : 0 : "Integer. Default 0.
Can be set to a nonzero value to make these brushes emit as surface lights only from a light template with a matching _surflight_group value."
_lightcolorscale(float) : "_lightcolorscale" : "1" : "Saturation control as a postprocessing step on these specific faces lightmaps.
Default 1.0, 0.0 is fully desaturated to greyscale."
_object_channel_mask(integer) : "_object_channel_mask" : 1 : "Mask of lighting channels that this bmodel receives light on, blocks light on, and tests for AO on.
Default 1.
NOTE: Changing this from 1 will disable bouncing light off of this bmodel.
NOTE: Changing this from 1 implicitly enables _shadow.
NOTE: Changing to 2, for example, will cause the bmodel to initially be solid black. Youll need to add minlight or lights with _light_channel_mask 2."
target(string) : "target" : "" : "If [`Some`], when this entity's IO fires, it will activate all entities with its [`Targetable::targetname`] set to this, with whatever input that functionality that entity has set up."
killtarget(string) : "killtarget" : "" : "If [`Some`], when this entity's IO fires, it will kill all entities with its [`Targetable::targetname`] set to this."
]
@PointClass base(transform) = camera_target
@BaseClass = __transform
[
origin(vector) : "Translation/Origin" : "0 0 0" : ""
angles(vector) : "Rotation (pitch yaw roll) in degrees" : "0 0 0" : ""
scale(vector) : "Scale" : "1 1 1" : ""
]
@BaseClass = __visibility
[
visibility(choices) : "Visibility" : "Inherited" : "" =
[
"Inherited" : "Uses the visibility of its parents. If its a root-level entity, it will be visible."
"Hidden" : "Always not rendered, regardless of its parent's visibility."
"Visible" : "Always rendered, regardless of its parent's visibility."
]
]
@PointClass base(__transform) = camera_target
[
targetname(string) : "targetname" : "" : ""
]
@PointClass base(transform) model({ "path": "models/cash.glb" }) = cash_spawn
@PointClass base(__transform) model({ "path": "models/cash.glb" }) = cash_spawn
[
]
@SolidClass base(transform) = crates
@SolidClass base(__transform) = crates
[
]
@PointClass base(transform, target) = cutscene_camera
@PointClass base(__transform, __target) = cutscene_camera
[
name(string) : "name" : "" : ""
targetname(string) : "targetname" : "" : ""
]
@PointClass base(transform, target) = cutscene_camera_movement_end
@PointClass base(__transform, __target) = cutscene_camera_movement_end
[
]
@PointClass base(transform) model({ "path": "models/alien_naked.glb" }) = enemy_spawn
@PointClass base(__transform) model({ "path": "models/alien_naked.glb" }) = enemy_spawn
[
head(string) : "head" : "" : ""
key(string) : "key" : "" : ""
@@ -90,58 +55,35 @@
]
]
@SolidClass base(transform, target) = movable
@SolidClass base(__transform, __target) = movable
[
name(string) : "name" : "" : ""
]
@PointClass base(transform) = move_target
@PointClass base(__transform) = move_target
[
targetname(string) : "targetname" : "" : ""
]
@SolidClass base(transform) = named_entity
@SolidClass base(__transform) = named_entity
[
name(string) : "name" : "" : ""
]
@SolidClass base(transform, target) = platform
@SolidClass base(__transform, __target) = platform
[
]
@PointClass base(transform) = platform_target
@PointClass base(__transform) = platform_target
[
targetname(string) : "targetname" : "" : ""
]
@PointClass base(transform) model({ "path": "models/spawn.glb" }) = spawn_point
@PointClass base(__transform) model({ "path": "models/spawn.glb" }) = spawn_point
[
]
@BaseClass = target
[
target(string) : "target" : "" : "If [`Some`], when this entity's IO fires, it will activate all entities with its [`Targetable::targetname`] set to this, with whatever input that functionality that entity has set up."
killtarget(string) : "killtarget" : "" : "If [`Some`], when this entity's IO fires, it will kill all entities with its [`Targetable::targetname`] set to this."
]
@BaseClass = transform
[
origin(vector) : "Translation/Origin" : "0 0 0" : ""
angles(vector) : "Rotation (pitch yaw roll) in degrees" : "0 0 0" : ""
scale(vector) : "Scale" : "1 1 1" : ""
]
@BaseClass = visibility
[
visibility(choices) : "Visibility" : "Inherited" : "" =
[
"Inherited" : "Uses the visibility of its parents. If its a root-level entity, it will be visible."
"Hidden" : "Always not rendered, regardless of its parent's visibility."
"Visible" : "Always rendered, regardless of its parent's visibility."
]
]
@SolidClass = water
@SolidClass base(__transform) = water
[
]