mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge remote-tracking branch 'origin/master' into sharp/zoomy-worldgen
This commit is contained in:
commit
233d2e2685
19
CHANGELOG.md
19
CHANGELOG.md
@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Command to toggle experimental shaders.
|
- Command to toggle experimental shaders.
|
||||||
- Faster Energy Regeneration while sitting.
|
- Faster Energy Regeneration while sitting.
|
||||||
- Lantern glow for dropped lanterns.
|
- Lantern glow for dropped lanterns.
|
||||||
@ -25,8 +26,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Setting in userdata/server/server_config/settings.ron that controls the length of each day/night cycle.
|
- Setting in userdata/server/server_config/settings.ron that controls the length of each day/night cycle.
|
||||||
- Starting site can now be chosen during character creation
|
- Starting site can now be chosen during character creation
|
||||||
- Durability loss of equipped items on death
|
- Durability loss of equipped items on death
|
||||||
|
- Reputation system: crimes will be remembered and NPCs will tell each other about crimes they witness
|
||||||
|
- NPCs will now talk to players and to each other
|
||||||
|
- NPCs now have dedicated professions and will act accordingly
|
||||||
|
- NPCs other than merchants can be traded with
|
||||||
|
- NPCs will seek out a place to sleep when night comes
|
||||||
|
- Merchants now travel between towns
|
||||||
|
- Travellers and merchants will stay a while in each town they visit and converse with the locals
|
||||||
|
- Resource tracking: resources in the world can be temporarily exhausted, requiring time to replenish
|
||||||
|
- Airships now have pilot NPCs
|
||||||
|
- Simulated NPCs now have repopulation mechanics
|
||||||
|
- NPCs now have unique names
|
||||||
|
- A /scale command that can be used to change the in-game scale of players
|
||||||
|
- Merchants will flog their wares in towns, encouraging nearby character to buy goods from them
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Bats move slower and use a simple proportional controller to maintain altitude
|
- Bats move slower and use a simple proportional controller to maintain altitude
|
||||||
- Bats now have less health
|
- Bats now have less health
|
||||||
- Climbing no longer requires having 10 energy
|
- Climbing no longer requires having 10 energy
|
||||||
@ -34,11 +49,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Sword
|
- Sword
|
||||||
- Rescaling of images for the UI is now done when sampling from them on the GPU. Improvements are
|
- Rescaling of images for the UI is now done when sampling from them on the GPU. Improvements are
|
||||||
particularily noticeable when opening the map screen (which involves rescaling a few large
|
particularily noticeable when opening the map screen (which involves rescaling a few large
|
||||||
images) and also when using the voxel minimap view (where a medium size image is updated often).
|
images) and also when using the voxel minimap view (where a medium size image is updated often).
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Doors
|
- Doors
|
||||||
- Debug hitboxes now scale with the `Scale` component
|
- Debug hitboxes now scale with the `Scale` component
|
||||||
- Potion quaffing no longer makes characters practically immortal.
|
- Potion quaffing no longer makes characters practically immortal.
|
||||||
@ -49,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Fixed various issues with showing the correct text hint for interactable blocks.
|
- Fixed various issues with showing the correct text hint for interactable blocks.
|
||||||
- Intert entities like arrows no longer obstruct interacting with nearby entities/blocks.
|
- Intert entities like arrows no longer obstruct interacting with nearby entities/blocks.
|
||||||
- Underwater fall damage
|
- Underwater fall damage
|
||||||
|
- The scale component now behaves properly
|
||||||
|
|
||||||
## [0.14.0] - 2023-01-07
|
## [0.14.0] - 2023-01-07
|
||||||
|
|
||||||
|
87
Cargo.lock
generated
87
Cargo.lock
generated
@ -145,6 +145,12 @@ version = "1.0.65"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anymap2"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "app_dirs2"
|
name = "app_dirs2"
|
||||||
version = "2.5.4"
|
version = "2.5.4"
|
||||||
@ -1862,6 +1868,27 @@ dependencies = [
|
|||||||
"syn 1.0.100",
|
"syn 1.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-map"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5a56d54c8dd9b3ad34752ed197a4eb2a6601bc010808eb097a04a58ae4c43e1"
|
||||||
|
dependencies = [
|
||||||
|
"enum-map-derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-map-derive"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9045e2676cd5af83c3b167d917b0a5c90a4d8e266e2683d6631b235c457fc27"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 1.0.43",
|
||||||
|
"quote 1.0.21",
|
||||||
|
"syn 1.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enumset"
|
name = "enumset"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
@ -4345,6 +4372,12 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "peeking_take_while"
|
name = "peeking_take_while"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@ -5139,6 +5172,28 @@ dependencies = [
|
|||||||
"syn 1.0.100",
|
"syn 1.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmp"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmp-serde"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25786b0d276110195fa3d6f3f31299900cf71dfbd6c28450f3f58a0e7f7a347e"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"rmp",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rodio"
|
name = "rodio"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
@ -6758,6 +6813,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unic-langid",
|
"unic-langid",
|
||||||
|
"veloren-common",
|
||||||
"veloren-common-assets",
|
"veloren-common-assets",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -6780,6 +6836,7 @@ dependencies = [
|
|||||||
"crossbeam-utils 0.8.11",
|
"crossbeam-utils 0.8.11",
|
||||||
"csv",
|
"csv",
|
||||||
"dot_vox",
|
"dot_vox",
|
||||||
|
"enum-map",
|
||||||
"executors",
|
"executors",
|
||||||
"futures",
|
"futures",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
@ -6956,7 +7013,7 @@ dependencies = [
|
|||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hashbrown 0.11.2",
|
"hashbrown 0.12.3",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lz-fear",
|
"lz-fear",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
@ -7019,6 +7076,29 @@ dependencies = [
|
|||||||
"veloren-plugin-derive",
|
"veloren-plugin-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "veloren-rtsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
dependencies = [
|
||||||
|
"anymap2",
|
||||||
|
"atomic_refcell",
|
||||||
|
"enum-map",
|
||||||
|
"fxhash",
|
||||||
|
"hashbrown 0.12.3",
|
||||||
|
"itertools",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"rand_chacha 0.3.1",
|
||||||
|
"rayon",
|
||||||
|
"rmp-serde",
|
||||||
|
"ron 0.8.0",
|
||||||
|
"serde",
|
||||||
|
"slotmap 1.0.6",
|
||||||
|
"tracing",
|
||||||
|
"vek 0.15.8",
|
||||||
|
"veloren-common",
|
||||||
|
"veloren-world",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "veloren-server"
|
name = "veloren-server"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@ -7031,6 +7111,7 @@ dependencies = [
|
|||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"drop_guard",
|
"drop_guard",
|
||||||
|
"enum-map",
|
||||||
"enumset",
|
"enumset",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
@ -7068,6 +7149,7 @@ dependencies = [
|
|||||||
"veloren-common-systems",
|
"veloren-common-systems",
|
||||||
"veloren-network",
|
"veloren-network",
|
||||||
"veloren-plugin-api",
|
"veloren-plugin-api",
|
||||||
|
"veloren-rtsim",
|
||||||
"veloren-server-agent",
|
"veloren-server-agent",
|
||||||
"veloren-world",
|
"veloren-world",
|
||||||
]
|
]
|
||||||
@ -7086,6 +7168,8 @@ dependencies = [
|
|||||||
"veloren-common-base",
|
"veloren-common-base",
|
||||||
"veloren-common-dynlib",
|
"veloren-common-dynlib",
|
||||||
"veloren-common-ecs",
|
"veloren-common-ecs",
|
||||||
|
"veloren-common-net",
|
||||||
|
"veloren-rtsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -7248,6 +7332,7 @@ dependencies = [
|
|||||||
"csv",
|
"csv",
|
||||||
"deflate",
|
"deflate",
|
||||||
"enum-iterator 1.1.3",
|
"enum-iterator 1.1.3",
|
||||||
|
"enum-map",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
|
@ -16,6 +16,7 @@ members = [
|
|||||||
"plugin/api",
|
"plugin/api",
|
||||||
"plugin/derive",
|
"plugin/derive",
|
||||||
"plugin/rt",
|
"plugin/rt",
|
||||||
|
"rtsim",
|
||||||
"server",
|
"server",
|
||||||
"server/agent",
|
"server/agent",
|
||||||
"server-cli",
|
"server-cli",
|
||||||
|
@ -476,6 +476,24 @@
|
|||||||
Simple(None, "common.abilities.custom.minotaur.frenzy"),
|
Simple(None, "common.abilities.custom.minotaur.frenzy"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Custom("Dullahan"): (
|
||||||
|
primary: Simple(None, "common.abilities.custom.dullahan.melee"),
|
||||||
|
secondary: Simple(None, "common.abilities.custom.dullahan.fierce_darts"),
|
||||||
|
abilities: [
|
||||||
|
Simple(None, "common.abilities.custom.dullahan.knife_rain"),
|
||||||
|
Simple(None, "common.abilities.custom.dullahan.dash"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Custom("Cyclops"): (
|
||||||
|
primary: Simple(None, "common.abilities.custom.cyclops.doublestrike"),
|
||||||
|
secondary: Simple(None, "common.abilities.custom.cyclops.optic_blast"),
|
||||||
|
abilities: [
|
||||||
|
Simple(None, "common.abilities.custom.cyclops.hammer_shockwave"),
|
||||||
|
Simple(None, "common.abilities.custom.cyclops.dash"),
|
||||||
|
Simple(None, "common.abilities.custom.cyclops.reinforce"),
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
Custom("Clay Golem"): (
|
Custom("Clay Golem"): (
|
||||||
primary: Simple(None, "common.abilities.custom.claygolem.strike"),
|
primary: Simple(None, "common.abilities.custom.claygolem.strike"),
|
||||||
secondary: Simple(None, "common.abilities.custom.claygolem.laser"),
|
secondary: Simple(None, "common.abilities.custom.claygolem.laser"),
|
||||||
|
28
assets/common/abilities/custom/cyclops/dash.ron
Normal file
28
assets/common/abilities/custom/cyclops/dash.ron
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
DashMelee(
|
||||||
|
energy_cost: 0,
|
||||||
|
melee_constructor: (
|
||||||
|
kind: Bash(
|
||||||
|
damage: 28.0,
|
||||||
|
poise: 20.0,
|
||||||
|
knockback: 2.0,
|
||||||
|
energy_regen: 0.0,
|
||||||
|
),
|
||||||
|
scaled: Some(Bash(
|
||||||
|
damage: 36.0,
|
||||||
|
poise: 60.0,
|
||||||
|
knockback: 5.0,
|
||||||
|
energy_regen: 0.0,
|
||||||
|
)),
|
||||||
|
range: 6.0,
|
||||||
|
angle: 90.0,
|
||||||
|
multi_target: Some(Normal),
|
||||||
|
),
|
||||||
|
energy_drain: 0,
|
||||||
|
forward_speed: 9.0,
|
||||||
|
buildup_duration: 0.8,
|
||||||
|
charge_duration: 2.0,
|
||||||
|
swing_duration: 0.1,
|
||||||
|
recover_duration: 0.8,
|
||||||
|
ori_modifier: 0.1,
|
||||||
|
charge_through: false,
|
||||||
|
)
|
49
assets/common/abilities/custom/cyclops/doublestrike.ron
Normal file
49
assets/common/abilities/custom/cyclops/doublestrike.ron
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
ComboMelee(
|
||||||
|
stage_data: [
|
||||||
|
(
|
||||||
|
stage: 1,
|
||||||
|
base_damage: 32.0,
|
||||||
|
damage_increase: 0.0,
|
||||||
|
base_poise_damage: 20,
|
||||||
|
poise_damage_increase: 0.0,
|
||||||
|
knockback: 5.0,
|
||||||
|
range: 6,
|
||||||
|
angle: 90.0,
|
||||||
|
base_buildup_duration: 0.5,
|
||||||
|
base_swing_duration: 0.4,
|
||||||
|
hit_timing: 0.4,
|
||||||
|
base_recover_duration: 0.4,
|
||||||
|
forward_movement: 0.3,
|
||||||
|
damage_kind: Crushing,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
stage: 2,
|
||||||
|
base_damage: 36.0,
|
||||||
|
damage_increase: 0.0,
|
||||||
|
base_poise_damage: 40.0,
|
||||||
|
poise_damage_increase: 0.0,
|
||||||
|
knockback: 10.0,
|
||||||
|
range: 8,
|
||||||
|
angle: 45.0,
|
||||||
|
base_buildup_duration: 0.6,
|
||||||
|
base_swing_duration: 0.6,
|
||||||
|
hit_timing: 0.3,
|
||||||
|
base_recover_duration: 1.2,
|
||||||
|
forward_movement: 0.2,
|
||||||
|
damage_kind: Crushing,
|
||||||
|
damage_effect: Some(Buff((
|
||||||
|
kind: Crippled,
|
||||||
|
dur_secs: 3.0,
|
||||||
|
strength: DamageFraction(0.1),
|
||||||
|
chance: 1.0,
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
initial_energy_gain: 0,
|
||||||
|
max_energy_gain: 0,
|
||||||
|
energy_increase: 0,
|
||||||
|
speed_increase: 0.0,
|
||||||
|
max_speed_increase: 0.0,
|
||||||
|
scales_from_combo: 0,
|
||||||
|
ori_modifier: 0.65,
|
||||||
|
)
|
18
assets/common/abilities/custom/cyclops/hammer_shockwave.ron
Normal file
18
assets/common/abilities/custom/cyclops/hammer_shockwave.ron
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
Shockwave(
|
||||||
|
energy_cost: 0,
|
||||||
|
buildup_duration: 1.2,
|
||||||
|
swing_duration: 0.12,
|
||||||
|
recover_duration: 0.8,
|
||||||
|
damage: 45.0,
|
||||||
|
poise_damage: 60,
|
||||||
|
knockback: (strength: 10.0, direction: TowardsUp),
|
||||||
|
shockwave_angle: 360.0,
|
||||||
|
shockwave_vertical_angle: 360.0,
|
||||||
|
shockwave_speed: 40.0,
|
||||||
|
shockwave_duration: 0.4,
|
||||||
|
requires_ground: true,
|
||||||
|
move_efficiency: 0.0,
|
||||||
|
damage_kind: Piercing,
|
||||||
|
specifier: Ground,
|
||||||
|
ori_rate: 1.0,
|
||||||
|
)
|
16
assets/common/abilities/custom/cyclops/optic_blast.ron
Normal file
16
assets/common/abilities/custom/cyclops/optic_blast.ron
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
BasicRanged(
|
||||||
|
energy_cost: 0.0,
|
||||||
|
buildup_duration: 1.6,
|
||||||
|
recover_duration: 1.2,
|
||||||
|
projectile: LaserBeam(
|
||||||
|
damage: 48.0,
|
||||||
|
radius: 8.0,
|
||||||
|
knockback: 5.0,
|
||||||
|
energy_regen: 20.0,
|
||||||
|
min_falloff: 0.0,
|
||||||
|
),
|
||||||
|
projectile_body: Object(LaserBeam),
|
||||||
|
projectile_speed: 100.0,
|
||||||
|
num_projectiles: 1,
|
||||||
|
projectile_spread: 0,
|
||||||
|
)
|
9
assets/common/abilities/custom/cyclops/reinforce.ron
Normal file
9
assets/common/abilities/custom/cyclops/reinforce.ron
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
SelfBuff(
|
||||||
|
buildup_duration: 0.4,
|
||||||
|
cast_duration: 0.8,
|
||||||
|
recover_duration: 0.3,
|
||||||
|
buff_kind: ProtectingWard,
|
||||||
|
buff_strength: 2.0,
|
||||||
|
buff_duration: Some(300.0),
|
||||||
|
energy_cost: 0,
|
||||||
|
)
|
28
assets/common/abilities/custom/dullahan/dash.ron
Normal file
28
assets/common/abilities/custom/dullahan/dash.ron
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
DashMelee(
|
||||||
|
energy_cost: 0,
|
||||||
|
melee_constructor: (
|
||||||
|
kind: Bash(
|
||||||
|
damage: 28.5,
|
||||||
|
poise: 30.0,
|
||||||
|
knockback: 2.0,
|
||||||
|
energy_regen: 0.0,
|
||||||
|
),
|
||||||
|
scaled: Some(Bash(
|
||||||
|
damage: 36.0,
|
||||||
|
poise: 38.6,
|
||||||
|
knockback: 3.0,
|
||||||
|
energy_regen: 0.0,
|
||||||
|
)),
|
||||||
|
range: 5.0,
|
||||||
|
angle: 90.0,
|
||||||
|
multi_target: Some(Normal),
|
||||||
|
),
|
||||||
|
energy_drain: 0,
|
||||||
|
forward_speed: 8.0,
|
||||||
|
buildup_duration: 0.6,
|
||||||
|
charge_duration: 2.0,
|
||||||
|
swing_duration: 0.1,
|
||||||
|
recover_duration: 1.2,
|
||||||
|
ori_modifier: 0.1,
|
||||||
|
charge_through: false,
|
||||||
|
)
|
15
assets/common/abilities/custom/dullahan/fierce_darts.ron
Normal file
15
assets/common/abilities/custom/dullahan/fierce_darts.ron
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
BasicRanged(
|
||||||
|
energy_cost: 0.0,
|
||||||
|
buildup_duration: 0.55,
|
||||||
|
recover_duration: 0.45,
|
||||||
|
projectile: Knife(
|
||||||
|
damage: 31.0,
|
||||||
|
knockback: 5.0,
|
||||||
|
energy_regen: 20.0,
|
||||||
|
min_falloff: 0.0,
|
||||||
|
),
|
||||||
|
projectile_body: Object(SpectralSwordLarge),
|
||||||
|
projectile_speed: 120.0,
|
||||||
|
num_projectiles: 3,
|
||||||
|
projectile_spread: 0.075,
|
||||||
|
)
|
16
assets/common/abilities/custom/dullahan/knife_rain.ron
Normal file
16
assets/common/abilities/custom/dullahan/knife_rain.ron
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
BasicRanged(
|
||||||
|
energy_cost: 0.0,
|
||||||
|
buildup_duration: 1.1,
|
||||||
|
recover_duration: 0.2,
|
||||||
|
projectile: Knife(
|
||||||
|
damage: 34.0,
|
||||||
|
radius: 3.8,
|
||||||
|
knockback: 5.0,
|
||||||
|
energy_regen: 20.0,
|
||||||
|
min_falloff: 0.3,
|
||||||
|
),
|
||||||
|
projectile_body: Object(SpectralSwordSmall),
|
||||||
|
projectile_speed: 20.0,
|
||||||
|
num_projectiles: 36,
|
||||||
|
projectile_spread: 0.4,
|
||||||
|
)
|
55
assets/common/abilities/custom/dullahan/melee.ron
Normal file
55
assets/common/abilities/custom/dullahan/melee.ron
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
ComboMelee(
|
||||||
|
stage_data: [
|
||||||
|
(
|
||||||
|
stage: 1,
|
||||||
|
base_damage: 35.5,
|
||||||
|
damage_increase: 0.0,
|
||||||
|
base_poise_damage: 15.0,
|
||||||
|
poise_damage_increase: 0.0,
|
||||||
|
knockback: 2.0,
|
||||||
|
range: 6.0,
|
||||||
|
angle: 60.0,
|
||||||
|
base_buildup_duration: 0.8,
|
||||||
|
base_swing_duration: 0.1,
|
||||||
|
hit_timing: 0.4,
|
||||||
|
base_recover_duration: 0.3,
|
||||||
|
forward_movement: 0.8,
|
||||||
|
damage_kind: Slashing,
|
||||||
|
damage_effect: Some(Buff((
|
||||||
|
kind: Bleeding,
|
||||||
|
dur_secs: 3.0,
|
||||||
|
strength: DamageFraction(0.05),
|
||||||
|
chance: 0.3,
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
stage: 2,
|
||||||
|
base_damage: 38.5,
|
||||||
|
damage_increase: 0.0,
|
||||||
|
base_poise_damage: 20.0,
|
||||||
|
poise_damage_increase: 0.0,
|
||||||
|
knockback: 8.0,
|
||||||
|
range: 6.0,
|
||||||
|
angle: 60.0,
|
||||||
|
base_buildup_duration: 0.7,
|
||||||
|
base_swing_duration: 0.1,
|
||||||
|
hit_timing: 0.4,
|
||||||
|
base_recover_duration: 1.3,
|
||||||
|
forward_movement: 0.2,
|
||||||
|
damage_kind: Slashing,
|
||||||
|
damage_effect: Some(Buff((
|
||||||
|
kind: Bleeding,
|
||||||
|
dur_secs: 3.0,
|
||||||
|
strength: DamageFraction(0.1),
|
||||||
|
chance: 0.15,
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
initial_energy_gain: 0,
|
||||||
|
max_energy_gain: 0,
|
||||||
|
energy_increase: 0,
|
||||||
|
speed_increase: 0.0,
|
||||||
|
max_speed_increase: 0.0,
|
||||||
|
scales_from_combo: 0,
|
||||||
|
ori_modifier: 0.6,
|
||||||
|
)
|
@ -1,7 +1,7 @@
|
|||||||
#![enable(implicit_some)]
|
#![enable(implicit_some)]
|
||||||
(
|
(
|
||||||
name: Name("Dullahan"),
|
name: Automatic,
|
||||||
body: RandomWith("dullahan"),
|
body: RandomWith("cyclops"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Enemy),
|
||||||
loot: LootTable("common.loot_tables.dungeon.tier-4.miniboss"),
|
loot: LootTable("common.loot_tables.dungeon.tier-4.miniboss"),
|
||||||
inventory: (
|
inventory: (
|
||||||
|
21
assets/common/entity/village/captain.ron
Normal file
21
assets/common/entity/village/captain.ron
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
name: Name("Captain"),
|
||||||
|
body: RandomWith("humanoid"),
|
||||||
|
alignment: Alignment(Npc),
|
||||||
|
loot: LootTable("common.loot_tables.creature.humanoid"),
|
||||||
|
inventory: (
|
||||||
|
loadout: Inline((
|
||||||
|
inherit: Asset("common.loadout.village.captain"),
|
||||||
|
active_hands: InHands((ModularWeapon(tool: Sword, material: Orichalcum, hands: Two), None)),
|
||||||
|
)),
|
||||||
|
items: [
|
||||||
|
(10, "common.items.food.cheese"),
|
||||||
|
(10, "common.items.food.plainsalad"),
|
||||||
|
(10, "common.items.consumable.potion_med"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
meta: [
|
||||||
|
SkillSetAsset("common.skillset.preset.rank5.fullskill"),
|
||||||
|
],
|
||||||
|
)
|
23
assets/common/entity/village/farmer.ron
Normal file
23
assets/common/entity/village/farmer.ron
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
name: Name("Farmer"),
|
||||||
|
body: RandomWith("humanoid"),
|
||||||
|
alignment: Alignment(Npc),
|
||||||
|
loot: LootTable("common.loot_tables.creature.humanoid"),
|
||||||
|
inventory: (
|
||||||
|
loadout: Inline((
|
||||||
|
inherit: Asset("common.loadout.village.farmer"),
|
||||||
|
active_hands: InHands((Choice([
|
||||||
|
(1, Item("common.items.weapons.tool.hoe")),
|
||||||
|
(1, Item("common.items.weapons.tool.rake")),
|
||||||
|
(1, Item("common.items.weapons.tool.shovel-0")),
|
||||||
|
(1, Item("common.items.weapons.tool.shovel-1")),
|
||||||
|
]), None)),
|
||||||
|
)),
|
||||||
|
items: [
|
||||||
|
(10, "common.items.food.cheese"),
|
||||||
|
(10, "common.items.food.plainsalad"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
meta: [],
|
||||||
|
)
|
21
assets/common/entity/village/herbalist.ron
Normal file
21
assets/common/entity/village/herbalist.ron
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
name: Name("Herbalist"),
|
||||||
|
body: RandomWith("humanoid"),
|
||||||
|
alignment: Alignment(Npc),
|
||||||
|
loot: LootTable("common.loot_tables.creature.humanoid"),
|
||||||
|
inventory: (
|
||||||
|
loadout: Inline((
|
||||||
|
inherit: Asset("common.loadout.village.herbalist"),
|
||||||
|
active_hands: InHands((Choice([
|
||||||
|
(1, Item("common.items.weapons.tool.hoe")),
|
||||||
|
(1, Item("common.items.weapons.tool.rake")),
|
||||||
|
]), None)),
|
||||||
|
)),
|
||||||
|
items: [
|
||||||
|
(10, "common.items.food.cheese"),
|
||||||
|
(10, "common.items.food.plainsalad"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
meta: [],
|
||||||
|
)
|
23
assets/common/entity/village/hunter.ron
Normal file
23
assets/common/entity/village/hunter.ron
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
name: Name("Hunter"),
|
||||||
|
body: RandomWith("humanoid"),
|
||||||
|
alignment: Alignment(Npc),
|
||||||
|
loot: LootTable("common.loot_tables.creature.humanoid"),
|
||||||
|
inventory: (
|
||||||
|
loadout: Inline((
|
||||||
|
inherit: Asset("common.loadout.village.hunter"),
|
||||||
|
active_hands: InHands((Choice([
|
||||||
|
(8, ModularWeapon(tool: Bow, material: Wood, hands: None)),
|
||||||
|
(4, ModularWeapon(tool: Bow, material: Bamboo, hands: None)),
|
||||||
|
(2, ModularWeapon(tool: Bow, material: Hardwood, hands: None)),
|
||||||
|
(2, ModularWeapon(tool: Bow, material: Ironwood, hands: None)),
|
||||||
|
(1, ModularWeapon(tool: Bow, material: Eldwood, hands: None)),
|
||||||
|
]), None)),
|
||||||
|
)),
|
||||||
|
items: [
|
||||||
|
(10, "common.items.consumable.potion_big"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
meta: [],
|
||||||
|
)
|
@ -6,6 +6,7 @@
|
|||||||
loot: LootTable("common.loot_tables.creature.humanoid"),
|
loot: LootTable("common.loot_tables.creature.humanoid"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: Inline((
|
loadout: Inline((
|
||||||
|
inherit: Asset("common.loadout.village.merchant"),
|
||||||
active_hands: InHands((Choice([
|
active_hands: InHands((Choice([
|
||||||
(2, ModularWeapon(tool: Bow, material: Eldwood, hands: None)),
|
(2, ModularWeapon(tool: Bow, material: Eldwood, hands: None)),
|
||||||
(1, ModularWeapon(tool: Sword, material: Steel, hands: None)),
|
(1, ModularWeapon(tool: Sword, material: Steel, hands: None)),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#![enable(implicit_some)]
|
#![enable(implicit_some)]
|
||||||
(
|
(
|
||||||
name: Automatic,
|
name: Name("Dullahan"),
|
||||||
body: RandomWith("cyclops"),
|
body: RandomWith("dullahan"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Enemy),
|
||||||
loot: LootTable("common.loot_tables.creature.biped_large.default"),
|
loot: LootTable("common.loot_tables.creature.biped_large.default"),
|
||||||
inventory: (
|
inventory: (
|
11
assets/common/entity/wild/peaceful/dog.ron
Normal file
11
assets/common/entity/wild/peaceful/dog.ron
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
name: Automatic,
|
||||||
|
body: RandomWith("dog"),
|
||||||
|
alignment: Alignment(Wild),
|
||||||
|
loot: LootTable("common.loot_tables.creature.quad_small.generic"),
|
||||||
|
inventory: (
|
||||||
|
loadout: FromBody,
|
||||||
|
),
|
||||||
|
meta: [],
|
||||||
|
)
|
@ -2,7 +2,7 @@
|
|||||||
(
|
(
|
||||||
name: Automatic,
|
name: Automatic,
|
||||||
body: RandomWith("mammoth"),
|
body: RandomWith("mammoth"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Wild),
|
||||||
loot: LootTable("common.loot_tables.creature.quad_medium.mammoth"),
|
loot: LootTable("common.loot_tables.creature.quad_medium.mammoth"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: FromBody,
|
loadout: FromBody,
|
||||||
|
13
assets/common/items/npc_armor/biped_large/cyclops.ron
Normal file
13
assets/common/items/npc_armor/biped_large/cyclops.ron
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
ItemDef(
|
||||||
|
name: "Cyclops Armor",
|
||||||
|
description: "Made of mysteries.",
|
||||||
|
kind: Armor((
|
||||||
|
kind: Chest,
|
||||||
|
stats: Direct((
|
||||||
|
protection: Some(Normal(120.0)),
|
||||||
|
poise_resilience: Some(Normal(60.0)),
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
quality: Moderate,
|
||||||
|
tags: [],
|
||||||
|
)
|
13
assets/common/items/npc_armor/biped_large/dullahan.ron
Normal file
13
assets/common/items/npc_armor/biped_large/dullahan.ron
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
ItemDef(
|
||||||
|
name: "Dullahan Itself Armor",
|
||||||
|
description: "Made of It ownself.",
|
||||||
|
kind: Armor((
|
||||||
|
kind: Chest,
|
||||||
|
stats: Direct((
|
||||||
|
protection: Some(Normal(200.0)),
|
||||||
|
poise_resilience: Some(Normal(10.0)),
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
quality: Moderate,
|
||||||
|
tags: [],
|
||||||
|
)
|
@ -17,5 +17,5 @@ ItemDef(
|
|||||||
)),
|
)),
|
||||||
quality: Low,
|
quality: Low,
|
||||||
tags: [],
|
tags: [],
|
||||||
ability_spec: Some(Custom("Hammer Simple")),
|
ability_spec: Some(Custom("Cyclops")),
|
||||||
)
|
)
|
@ -5,11 +5,11 @@ ItemDef(
|
|||||||
kind: Sword,
|
kind: Sword,
|
||||||
hands: Two,
|
hands: Two,
|
||||||
stats: (
|
stats: (
|
||||||
equip_time_secs: 0.5,
|
equip_time_secs: 0.01,
|
||||||
power: 1.5,
|
power: 1.0,
|
||||||
effect_power: 1.0,
|
effect_power: 1.0,
|
||||||
speed: 0.75,
|
speed: 1.0,
|
||||||
crit_chance: 0.0625,
|
crit_chance: 0.0645,
|
||||||
range: 1.0,
|
range: 1.0,
|
||||||
energy_efficiency: 1.0,
|
energy_efficiency: 1.0,
|
||||||
buff_strength: 1.0,
|
buff_strength: 1.0,
|
||||||
@ -17,5 +17,5 @@ ItemDef(
|
|||||||
)),
|
)),
|
||||||
quality: Low,
|
quality: Low,
|
||||||
tags: [],
|
tags: [],
|
||||||
ability_spec: Some(Custom("Sword Simple")),
|
ability_spec: Some(Custom("Dullahan")),
|
||||||
)
|
)
|
26
assets/common/loadout/village/captain.ron
Normal file
26
assets/common/loadout/village/captain.ron
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Christmas event
|
||||||
|
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
|
||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
head: Item("common.items.armor.pirate.hat"),
|
||||||
|
shoulders: Item("common.items.armor.mail.orichalcum.shoulder"),
|
||||||
|
chest: Item("common.items.armor.mail.orichalcum.chest"),
|
||||||
|
gloves: Item("common.items.armor.mail.orichalcum.hand"),
|
||||||
|
back: Choice([
|
||||||
|
(1, Item("common.items.armor.misc.back.backpack")),
|
||||||
|
(1, Item("common.items.npc_armor.back.backpack_blue")),
|
||||||
|
(1, Item("common.items.armor.mail.orichalcum.back")),
|
||||||
|
(1, None),
|
||||||
|
]),
|
||||||
|
belt: Item("common.items.armor.mail.orichalcum.belt"),
|
||||||
|
legs: Item("common.items.armor.mail.orichalcum.pants"),
|
||||||
|
feet: Item("common.items.armor.mail.orichalcum.foot"),
|
||||||
|
lantern: Choice([
|
||||||
|
(1, Item("common.items.lantern.black_0")),
|
||||||
|
(1, Item("common.items.lantern.blue_0")),
|
||||||
|
(1, Item("common.items.lantern.green_0")),
|
||||||
|
(1, Item("common.items.lantern.red_0")),
|
||||||
|
(1, Item("common.items.lantern.geode_purp")),
|
||||||
|
(1, Item("common.items.boss_drops.lantern")),
|
||||||
|
]),
|
||||||
|
)
|
30
assets/common/loadout/village/farmer.ron
Normal file
30
assets/common/loadout/village/farmer.ron
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Christmas event
|
||||||
|
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
|
||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
head: Choice([
|
||||||
|
(3, Item("common.items.armor.misc.head.straw")),
|
||||||
|
(3, Item("common.items.armor.misc.head.bamboo_twig")),
|
||||||
|
(2, None),
|
||||||
|
]),
|
||||||
|
chest: Choice([
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_green_0")),
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_green_1")),
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_red_0")),
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_red_1")),
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_purple_0")),
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_purple_1")),
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_yellow_0")),
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_yellow_1")),
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_orange_0")),
|
||||||
|
(1, Item("common.items.armor.misc.chest.worker_orange_1")),
|
||||||
|
]),
|
||||||
|
legs: Choice([
|
||||||
|
(1, Item("common.items.armor.misc.pants.worker_blue")),
|
||||||
|
(1, Item("common.items.armor.misc.pants.worker_brown")),
|
||||||
|
]),
|
||||||
|
feet: Choice([
|
||||||
|
(1, Item("common.items.armor.misc.foot.sandals")),
|
||||||
|
(1, Item("common.items.armor.cloth_blue.foot")),
|
||||||
|
]),
|
||||||
|
)
|
26
assets/common/loadout/village/herbalist.ron
Normal file
26
assets/common/loadout/village/herbalist.ron
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Christmas event
|
||||||
|
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
|
||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
head: Choice([
|
||||||
|
(3, Item("common.items.armor.misc.head.straw")),
|
||||||
|
(3, Item("common.items.armor.misc.head.hood")),
|
||||||
|
(2, None),
|
||||||
|
]),
|
||||||
|
chest: Choice([
|
||||||
|
(1, Item("common.items.armor.twigs.chest")),
|
||||||
|
(1, Item("common.items.armor.twigsflowers.chest")),
|
||||||
|
(1, Item("common.items.armor.twigsleaves.chest")),
|
||||||
|
]),
|
||||||
|
legs: Choice([
|
||||||
|
(1, Item("common.items.armor.twigs.pants")),
|
||||||
|
(1, Item("common.items.armor.twigsflowers.pants")),
|
||||||
|
(1, Item("common.items.armor.twigsleaves.pants")),
|
||||||
|
]),
|
||||||
|
feet: Choice([
|
||||||
|
(1, Item("common.items.armor.twigs.foot")),
|
||||||
|
(1, Item("common.items.armor.twigsflowers.foot")),
|
||||||
|
(1, Item("common.items.armor.twigsleaves.foot")),
|
||||||
|
(1, Item("common.items.armor.misc.foot.sandals")),
|
||||||
|
]),
|
||||||
|
)
|
28
assets/common/loadout/village/hunter.ron
Normal file
28
assets/common/loadout/village/hunter.ron
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Christmas event
|
||||||
|
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
|
||||||
|
#![enable(implicit_some)]
|
||||||
|
(
|
||||||
|
head: Choice([
|
||||||
|
(6, None),
|
||||||
|
(2, Item("common.items.armor.misc.head.straw")),
|
||||||
|
(3, Item("common.items.armor.misc.head.hood")),
|
||||||
|
(3, Item("common.items.armor.misc.head.hood_dark")),
|
||||||
|
]),
|
||||||
|
chest: Choice([
|
||||||
|
(1, Item("common.items.armor.hide.leather.chest")),
|
||||||
|
(1, Item("common.items.armor.hide.rawhide.chest")),
|
||||||
|
(1, Item("common.items.armor.hide.primal.chest")),
|
||||||
|
]),
|
||||||
|
legs: Choice([
|
||||||
|
(1, Item("common.items.armor.hide.leather.pants")),
|
||||||
|
(1, Item("common.items.armor.hide.rawhide.pants")),
|
||||||
|
(1, Item("common.items.armor.hide.primal.pants")),
|
||||||
|
]),
|
||||||
|
feet: Choice([
|
||||||
|
(1, None),
|
||||||
|
(2, Item("common.items.armor.misc.foot.sandals")),
|
||||||
|
(4, Item("common.items.armor.hide.leather.foot")),
|
||||||
|
(4, Item("common.items.armor.hide.rawhide.foot")),
|
||||||
|
(4, Item("common.items.armor.hide.primal.foot")),
|
||||||
|
]),
|
||||||
|
)
|
@ -11,5 +11,4 @@
|
|||||||
legs: Item("common.items.armor.merchant.pants"),
|
legs: Item("common.items.armor.merchant.pants"),
|
||||||
feet: Item("common.items.armor.merchant.foot"),
|
feet: Item("common.items.armor.merchant.foot"),
|
||||||
lantern: Item("common.items.lantern.black_0"),
|
lantern: Item("common.items.lantern.black_0"),
|
||||||
tabard: Item("common.items.debug.admin"),
|
)
|
||||||
)
|
|
||||||
|
@ -1244,6 +1244,18 @@
|
|||||||
],
|
],
|
||||||
threshold: 0.2,
|
threshold: 0.2,
|
||||||
),
|
),
|
||||||
|
LaserBeam: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.abilities.laser_beam",
|
||||||
|
],
|
||||||
|
threshold: 1.25,
|
||||||
|
),
|
||||||
|
CyclopsCharge: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.abilities.cyclops_charge",
|
||||||
|
],
|
||||||
|
threshold: 0.3,
|
||||||
|
),
|
||||||
GigaRoar: (
|
GigaRoar: (
|
||||||
files: [
|
files: [
|
||||||
"voxygen.audio.sfx.abilities.gigas_frost_roar",
|
"voxygen.audio.sfx.abilities.gigas_frost_roar",
|
||||||
|
BIN
assets/voxygen/audio/sfx/abilities/cyclops_charge.ogg
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/abilities/cyclops_charge.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/abilities/laser_beam.ogg
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/abilities/laser_beam.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -223,3 +223,35 @@ npc-speech-prisoner =
|
|||||||
.a2 = That Cardinal can't be trusted.
|
.a2 = That Cardinal can't be trusted.
|
||||||
.a3 = These Clerics are up to no good.
|
.a3 = These Clerics are up to no good.
|
||||||
.a4 = I wish i still had my pick!
|
.a4 = I wish i still had my pick!
|
||||||
|
npc-speech-moving_on =
|
||||||
|
.a0 = I've spent enough time here, onward to { $site }!
|
||||||
|
npc-speech-night_time =
|
||||||
|
.a0 = It's dark, time to head home.
|
||||||
|
.a1 = I'm tired.
|
||||||
|
.a2 = My bed beckons!
|
||||||
|
npc-speech-day_time =
|
||||||
|
.a0 = A new day begins!
|
||||||
|
.a1 = I never liked waking up...
|
||||||
|
npc-speech-start_hunting =
|
||||||
|
.a0 = Time to go hunting!
|
||||||
|
npc-speech-guard_thought =
|
||||||
|
.a0 = My brother's out fighting ogres. What do I get? Guard duty...
|
||||||
|
.a1 = Just one more patrol, then I can head home.
|
||||||
|
.a2 = No bandits are going to get past me.
|
||||||
|
npc-speech-merchant_sell_undirected =
|
||||||
|
.a0 = All my goods are of the highest quality!
|
||||||
|
.a1 = Does anybody want to buy my wares?
|
||||||
|
.a2 = I've got the best offers in town.
|
||||||
|
.a3 = Looking for supplies? I've got you covered.
|
||||||
|
npc-speech-merchant_sell_directed =
|
||||||
|
.a0 = You there! Are you in need of a new thingamabob?
|
||||||
|
.a1 = Are you hungry? I'm sure I've got some cheese you can buy.
|
||||||
|
.a2 = You look like you could do with some new armour!
|
||||||
|
npc-speech-witness_murder =
|
||||||
|
.a0 = Murderer!
|
||||||
|
.a1 = How could you do this?
|
||||||
|
.a2 = Aaargh!
|
||||||
|
npc-speech-witness_death =
|
||||||
|
.a0 = No!
|
||||||
|
.a1 = This is terrible!
|
||||||
|
.a2 = Oh my goodness!
|
||||||
|
@ -22,7 +22,10 @@ layout(location = 0) in vec3 f_pos;
|
|||||||
layout(location = 1) in vec3 f_norm;
|
layout(location = 1) in vec3 f_norm;
|
||||||
layout(location = 2) in vec4 f_col;
|
layout(location = 2) in vec4 f_col;
|
||||||
layout(location = 3) in vec3 model_pos;
|
layout(location = 3) in vec3 model_pos;
|
||||||
layout(location = 4) in float snow_cover;
|
layout(location = 4) flat in uint f_flags;
|
||||||
|
|
||||||
|
const uint FLAG_SNOW_COVERED = 1;
|
||||||
|
const uint FLAG_IS_BUILDING = 2;
|
||||||
|
|
||||||
layout(location = 0) out vec4 tgt_color;
|
layout(location = 0) out vec4 tgt_color;
|
||||||
layout(location = 1) out uvec4 tgt_mat;
|
layout(location = 1) out uvec4 tgt_mat;
|
||||||
@ -117,11 +120,25 @@ void main() {
|
|||||||
emitted_light *= f_ao;
|
emitted_light *= f_ao;
|
||||||
reflected_light *= f_ao;
|
reflected_light *= f_ao;
|
||||||
|
|
||||||
vec3 side_color = mix(surf_color, vec3(0.5, 0.6, 1.0), snow_cover);
|
vec3 glow = vec3(0);
|
||||||
vec3 top_color = mix(surf_color, surf_color * 0.3, 0.5 + snow_cover * 0.5);
|
if ((f_flags & FLAG_IS_BUILDING) > 0u && abs(f_norm.z) < 0.1) {
|
||||||
|
ivec3 wpos = ivec3((f_pos.xyz + focus_off.xyz) * 0.2);
|
||||||
|
if (((wpos.x & wpos.y & wpos.z) & 1) == 1) {
|
||||||
|
glow += vec3(1, 0.7, 0.3) * 2;
|
||||||
|
} else {
|
||||||
|
reflected_light += vec3(1, 0.7, 0.3) * 0.9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 side_color = surf_color;
|
||||||
|
vec3 top_color = surf_color;
|
||||||
|
if ((f_flags & FLAG_SNOW_COVERED) > 0u && f_norm.z > 0.0) {
|
||||||
|
side_color = mix(side_color, vec3(0.5, 0.6, 1.0), f_norm.z);
|
||||||
|
top_color = mix(top_color, surf_color * 0.3, 0.5 + f_norm.z * 0.5);
|
||||||
|
}
|
||||||
surf_color = mix(side_color, top_color, pow(fract(model_pos.z * 0.1), 2.0));
|
surf_color = mix(side_color, top_color, pow(fract(model_pos.z * 0.1), 2.0));
|
||||||
|
|
||||||
surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light);
|
surf_color = illuminate(max_light, view_dir, surf_color * emitted_light + glow, surf_color * reflected_light);
|
||||||
|
|
||||||
tgt_color = vec4(surf_color, 1.0);
|
tgt_color = vec4(surf_color, 1.0);
|
||||||
tgt_mat = uvec4(uvec3((f_norm + 1.0) * 127.0), MAT_LOD);
|
tgt_mat = uvec4(uvec3((f_norm + 1.0) * 127.0), MAT_LOD);
|
||||||
|
@ -24,13 +24,11 @@ layout(location = 3) in vec3 inst_pos;
|
|||||||
layout(location = 4) in uvec3 inst_col;
|
layout(location = 4) in uvec3 inst_col;
|
||||||
layout(location = 5) in uint inst_flags;
|
layout(location = 5) in uint inst_flags;
|
||||||
|
|
||||||
const uint FLAG_SNOW_COVERED = 1;
|
|
||||||
|
|
||||||
layout(location = 0) out vec3 f_pos;
|
layout(location = 0) out vec3 f_pos;
|
||||||
layout(location = 1) out vec3 f_norm;
|
layout(location = 1) out vec3 f_norm;
|
||||||
layout(location = 2) out vec4 f_col;
|
layout(location = 2) out vec4 f_col;
|
||||||
layout(location = 3) out vec3 model_pos;
|
layout(location = 3) out vec3 model_pos;
|
||||||
layout(location = 4) out float snow_cover;
|
layout(location = 4) flat out uint f_flags;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 obj_pos = inst_pos - focus_off.xyz;
|
vec3 obj_pos = inst_pos - focus_off.xyz;
|
||||||
@ -50,12 +48,7 @@ void main() {
|
|||||||
|
|
||||||
f_norm = v_norm;
|
f_norm = v_norm;
|
||||||
f_col = vec4(vec3(inst_col) * (1.0 / 255.0) * v_col * (hash(inst_pos.xyxy) * 0.35 + 0.65), 1.0);
|
f_col = vec4(vec3(inst_col) * (1.0 / 255.0) * v_col * (hash(inst_pos.xyxy) * 0.35 + 0.65), 1.0);
|
||||||
|
f_flags = inst_flags;
|
||||||
if ((inst_flags & FLAG_SNOW_COVERED) > 0u && f_norm.z > 0.0) {
|
|
||||||
snow_cover = 1.0;
|
|
||||||
} else {
|
|
||||||
snow_cover = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_Position =
|
gl_Position =
|
||||||
all_mat *
|
all_mat *
|
||||||
|
@ -80,6 +80,7 @@ const int STEAM = 39;
|
|||||||
const int BARRELORGAN = 40;
|
const int BARRELORGAN = 40;
|
||||||
const int POTION_SICKNESS = 41;
|
const int POTION_SICKNESS = 41;
|
||||||
const int GIGA_SNOW = 42;
|
const int GIGA_SNOW = 42;
|
||||||
|
const int CYCLOPS_CHARGE = 43;
|
||||||
|
|
||||||
// meters per second squared (acceleration)
|
// meters per second squared (acceleration)
|
||||||
const float earth_gravity = 9.807;
|
const float earth_gravity = 9.807;
|
||||||
@ -666,6 +667,16 @@ void main() {
|
|||||||
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
|
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case CYCLOPS_CHARGE:
|
||||||
|
f_reflect = 0.0;
|
||||||
|
float burn_size = 8.0 * (1 - slow_start(0.1)) * slow_end(0.15);
|
||||||
|
attr = Attr(
|
||||||
|
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
|
||||||
|
vec3(burn_size),
|
||||||
|
vec4(vec3(6.9, 0.0, 0.0), 1),
|
||||||
|
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
attr = Attr(
|
attr = Attr(
|
||||||
linear_motion(
|
linear_motion(
|
||||||
|
@ -889,4 +889,34 @@
|
|||||||
central: ("armor.empty"),
|
central: ("armor.empty"),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
SpectralSwordSmall: (
|
||||||
|
bone0: (
|
||||||
|
offset: (-0.5, -25.0, -8.5),
|
||||||
|
central: ("weapon.projectile.spectral_sword_small"),
|
||||||
|
),
|
||||||
|
bone1: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
central: ("armor.empty"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SpectralSwordLarge: (
|
||||||
|
bone0: (
|
||||||
|
offset: (-0.5, -30.0, -8.5),
|
||||||
|
central: ("weapon.projectile.spectral_sword_large"),
|
||||||
|
),
|
||||||
|
bone1: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
central: ("armor.empty"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
LaserBeam: (
|
||||||
|
bone0: (
|
||||||
|
offset: (-6.0, -60.0, -17.0),
|
||||||
|
central: ("weapon.projectile.laser_beam"),
|
||||||
|
),
|
||||||
|
bone1: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
central: ("armor.empty"),
|
||||||
|
)
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
@ -177,7 +177,7 @@
|
|||||||
central: ("npc.salamander.male.tail_rear"),
|
central: ("npc.salamander.male.tail_rear"),
|
||||||
),
|
),
|
||||||
tail_front: (
|
tail_front: (
|
||||||
offset: (-4.5, -9.0, -3.0),
|
offset: (-5.5, -9.0, -3.0),
|
||||||
central: ("npc.salamander.male.tail_front"),
|
central: ("npc.salamander.male.tail_front"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -203,7 +203,7 @@
|
|||||||
central: ("npc.salamander.male.tail_rear"),
|
central: ("npc.salamander.male.tail_rear"),
|
||||||
),
|
),
|
||||||
tail_front: (
|
tail_front: (
|
||||||
offset: (-4.5, -9.0, -3.0),
|
offset: (-5.5, -9.0, -3.0),
|
||||||
central: ("npc.salamander.male.tail_front"),
|
central: ("npc.salamander.male.tail_front"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
BIN
assets/voxygen/voxel/weapon/projectile/laser_beam.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/weapon/projectile/laser_beam.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/weapon/projectile/spectral_sword_large.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/weapon/projectile/spectral_sword_large.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/weapon/projectile/spectral_sword_small.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/weapon/projectile/spectral_sword_small.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -6,7 +6,7 @@ SpawnEntry (
|
|||||||
groups: [
|
groups: [
|
||||||
(1, (1, 1, "common.entity.wild.aggressive.ogre")),
|
(1, (1, 1, "common.entity.wild.aggressive.ogre")),
|
||||||
(1, (1, 1, "common.entity.wild.aggressive.swamp_troll")),
|
(1, (1, 1, "common.entity.wild.aggressive.swamp_troll")),
|
||||||
(1, (1, 1, "common.entity.wild.aggressive.cyclops")),
|
(1, (1, 1, "common.entity.wild.aggressive.dullahan")),
|
||||||
],
|
],
|
||||||
spawn_mode: Land,
|
spawn_mode: Land,
|
||||||
day_period: [Night, Morning, Noon, Evening],
|
day_period: [Night, Morning, Noon, Evening],
|
||||||
|
@ -107,7 +107,7 @@ fn main() {
|
|||||||
&localisation.read(),
|
&localisation.read(),
|
||||||
SHOW_NAME,
|
SHOW_NAME,
|
||||||
)
|
)
|
||||||
.message
|
.1
|
||||||
),
|
),
|
||||||
Event::Disconnect => {}, // TODO
|
Event::Disconnect => {}, // TODO
|
||||||
Event::DisconnectionNotification(time) => {
|
Event::DisconnectionNotification(time) => {
|
||||||
|
@ -7,6 +7,7 @@ version = "0.13.0"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Assets
|
# Assets
|
||||||
|
common = {package = "veloren-common", path = "../../common"}
|
||||||
common-assets = {package = "veloren-common-assets", path = "../../common/assets"}
|
common-assets = {package = "veloren-common-assets", path = "../../common/assets"}
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
@ -19,10 +19,11 @@ use std::{borrow::Cow, io};
|
|||||||
use assets::{source::DirEntry, AssetExt, AssetGuard, AssetHandle, ReloadWatcher, SharedString};
|
use assets::{source::DirEntry, AssetExt, AssetGuard, AssetHandle, ReloadWatcher, SharedString};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
// Re-export because I don't like prefix
|
// Re-export because I don't like prefix
|
||||||
|
use common::comp::{Content, LocalizationArg};
|
||||||
use common_assets as assets;
|
use common_assets as assets;
|
||||||
|
|
||||||
// Re-export for argument creation
|
// Re-export for argument creation
|
||||||
pub use fluent::fluent_args;
|
pub use fluent::{fluent_args, FluentValue};
|
||||||
pub use fluent_bundle::FluentArgs;
|
pub use fluent_bundle::FluentArgs;
|
||||||
|
|
||||||
/// The reference language, aka the more up-to-date localization data.
|
/// The reference language, aka the more up-to-date localization data.
|
||||||
@ -116,7 +117,9 @@ impl Language {
|
|||||||
let msg = bundle.get_message(key)?;
|
let msg = bundle.get_message(key)?;
|
||||||
let mut attrs = msg.attributes();
|
let mut attrs = msg.attributes();
|
||||||
|
|
||||||
if attrs.len() != 0 {
|
let mut errs = Vec::new();
|
||||||
|
|
||||||
|
let msg = if attrs.len() != 0 {
|
||||||
let idx = usize::from(seed) % attrs.len();
|
let idx = usize::from(seed) % attrs.len();
|
||||||
// unwrap is ok here, because idx is bound to attrs.len()
|
// unwrap is ok here, because idx is bound to attrs.len()
|
||||||
// by using modulo operator.
|
// by using modulo operator.
|
||||||
@ -134,16 +137,17 @@ impl Language {
|
|||||||
// * len = 0
|
// * len = 0
|
||||||
// * no matter what seed is, we return None in code above
|
// * no matter what seed is, we return None in code above
|
||||||
let variation = attrs.nth(idx).unwrap();
|
let variation = attrs.nth(idx).unwrap();
|
||||||
let mut errs = Vec::new();
|
bundle.format_pattern(variation.value(), args, &mut errs)
|
||||||
let msg = bundle.format_pattern(variation.value(), args, &mut errs);
|
|
||||||
for err in errs {
|
|
||||||
tracing::error!("err: {err} for {key}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(msg)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
// Fall back to single message if there are no attributes
|
||||||
|
bundle.format_pattern(msg.value()?, args, &mut errs)
|
||||||
|
};
|
||||||
|
|
||||||
|
for err in errs {
|
||||||
|
tracing::error!("err: {err} for {key}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,6 +332,47 @@ impl LocalizationGuard {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Localize the given content.
|
||||||
|
pub fn get_content(&self, content: &Content) -> String {
|
||||||
|
// On error, produces the localisation but with the missing key inline
|
||||||
|
fn get_content_inner(lang: &Language, content: &Content) -> Result<String, String> {
|
||||||
|
match content {
|
||||||
|
Content::Plain(text) => Ok(text.clone()),
|
||||||
|
Content::Localized { key, seed, args } => {
|
||||||
|
let mut is_err = false;
|
||||||
|
let mut fargs = FluentArgs::new();
|
||||||
|
for (k, arg) in args {
|
||||||
|
fargs.set(k, match arg {
|
||||||
|
LocalizationArg::Content(content) => FluentValue::String(
|
||||||
|
get_content_inner(lang, content)
|
||||||
|
.unwrap_or_else(|broken_text| {
|
||||||
|
is_err = true;
|
||||||
|
broken_text
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
LocalizationArg::Nat(n) => FluentValue::from(n),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lang.try_variation(key, *seed, Some(&fargs))
|
||||||
|
.map(Cow::into_owned)
|
||||||
|
.ok_or_else(|| key.clone())
|
||||||
|
.and_then(|text| if is_err { Err(text) } else { Ok(text) })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match get_content_inner(&self.active, content) {
|
||||||
|
Ok(text) => text,
|
||||||
|
// If part of the localisation failed, use the fallback language
|
||||||
|
Err(broken_text) => self.fallback.as_ref()
|
||||||
|
.and_then(|fb| get_content_inner(fb, content).ok())
|
||||||
|
// If all else fails, localise with the active language, but with the missing key included inline
|
||||||
|
.unwrap_or(broken_text),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a localized text from the variation of given key with given
|
/// Get a localized text from the variation of given key with given
|
||||||
/// arguments
|
/// arguments
|
||||||
///
|
///
|
||||||
|
@ -30,7 +30,7 @@ use common::{
|
|||||||
slot::{EquipSlot, InvSlotId, Slot},
|
slot::{EquipSlot, InvSlotId, Slot},
|
||||||
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
|
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
|
||||||
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
|
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
|
||||||
MapMarkerChange, UtteranceKind,
|
MapMarkerChange, PresenceKind, UtteranceKind,
|
||||||
},
|
},
|
||||||
event::{EventBus, LocalEvent, UpdateCharacterMetadata},
|
event::{EventBus, LocalEvent, UpdateCharacterMetadata},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
@ -60,8 +60,8 @@ use common_net::{
|
|||||||
self,
|
self,
|
||||||
world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo},
|
world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo},
|
||||||
ChatTypeContext, ClientGeneral, ClientMsg, ClientRegister, ClientType, DisconnectReason,
|
ChatTypeContext, ClientGeneral, ClientMsg, ClientRegister, ClientType, DisconnectReason,
|
||||||
InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, PresenceKind,
|
InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError,
|
||||||
RegisterError, ServerGeneral, ServerInit, ServerRegisterAnswer,
|
ServerGeneral, ServerInit, ServerRegisterAnswer,
|
||||||
},
|
},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
@ -1904,6 +1904,7 @@ impl Client {
|
|||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
&self.connected_server_constants,
|
&self.connected_server_constants,
|
||||||
|
|_, _| {},
|
||||||
);
|
);
|
||||||
// TODO: avoid emitting these in the first place
|
// TODO: avoid emitting these in the first place
|
||||||
let _ = self
|
let _ = self
|
||||||
@ -2332,13 +2333,15 @@ impl Client {
|
|||||||
.any(|r| !matches!(r, group::Role::Pet))
|
.any(|r| !matches!(r, group::Role::Pet))
|
||||||
{
|
{
|
||||||
frontend_events
|
frontend_events
|
||||||
.push(Event::Chat(comp::ChatType::Meta.chat_msg(
|
// TODO: localise
|
||||||
|
.push(Event::Chat(comp::ChatType::Meta.into_plain_msg(
|
||||||
"Type /g or /group to chat with your group members",
|
"Type /g or /group to chat with your group members",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
if let Some(player_info) = self.player_list.get(&uid) {
|
if let Some(player_info) = self.player_list.get(&uid) {
|
||||||
frontend_events.push(Event::Chat(
|
frontend_events.push(Event::Chat(
|
||||||
comp::ChatType::GroupMeta("Group".into()).chat_msg(format!(
|
// TODO: localise
|
||||||
|
comp::ChatType::GroupMeta("Group".into()).into_plain_msg(format!(
|
||||||
"[{}] joined group",
|
"[{}] joined group",
|
||||||
self.personalize_alias(uid, player_info.player_alias.clone())
|
self.personalize_alias(uid, player_info.player_alias.clone())
|
||||||
)),
|
)),
|
||||||
@ -2355,7 +2358,8 @@ impl Client {
|
|||||||
Removed(uid) => {
|
Removed(uid) => {
|
||||||
if let Some(player_info) = self.player_list.get(&uid) {
|
if let Some(player_info) = self.player_list.get(&uid) {
|
||||||
frontend_events.push(Event::Chat(
|
frontend_events.push(Event::Chat(
|
||||||
comp::ChatType::GroupMeta("Group".into()).chat_msg(format!(
|
// TODO: localise
|
||||||
|
comp::ChatType::GroupMeta("Group".into()).into_plain_msg(format!(
|
||||||
"[{}] left group",
|
"[{}] left group",
|
||||||
self.personalize_alias(uid, player_info.player_alias.clone())
|
self.personalize_alias(uid, player_info.player_alias.clone())
|
||||||
)),
|
)),
|
||||||
@ -2889,20 +2893,20 @@ impl Client {
|
|||||||
KillSource::Other => (),
|
KillSource::Other => (),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
comp::ChatType::Tell(from, to) | comp::ChatType::NpcTell(from, to, _) => {
|
comp::ChatType::Tell(from, to) | comp::ChatType::NpcTell(from, to) => {
|
||||||
alias_of_uid(from);
|
alias_of_uid(from);
|
||||||
alias_of_uid(to);
|
alias_of_uid(to);
|
||||||
},
|
},
|
||||||
comp::ChatType::Say(uid)
|
comp::ChatType::Say(uid)
|
||||||
| comp::ChatType::Region(uid)
|
| comp::ChatType::Region(uid)
|
||||||
| comp::ChatType::World(uid)
|
| comp::ChatType::World(uid)
|
||||||
| comp::ChatType::NpcSay(uid, _) => {
|
| comp::ChatType::NpcSay(uid) => {
|
||||||
alias_of_uid(uid);
|
alias_of_uid(uid);
|
||||||
},
|
},
|
||||||
comp::ChatType::Group(uid, _) | comp::ChatType::Faction(uid, _) => {
|
comp::ChatType::Group(uid, _) | comp::ChatType::Faction(uid, _) => {
|
||||||
alias_of_uid(uid);
|
alias_of_uid(uid);
|
||||||
},
|
},
|
||||||
comp::ChatType::Npc(uid, _) => alias_of_uid(uid),
|
comp::ChatType::Npc(uid) => alias_of_uid(uid),
|
||||||
comp::ChatType::Meta => (),
|
comp::ChatType::Meta => (),
|
||||||
};
|
};
|
||||||
result
|
result
|
||||||
@ -3069,7 +3073,7 @@ mod tests {
|
|||||||
&localisation.read(),
|
&localisation.read(),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.message;
|
.1;
|
||||||
},
|
},
|
||||||
Event::Disconnect => {},
|
Event::Disconnect => {},
|
||||||
Event::DisconnectionNotification(_) => {
|
Event::DisconnectionNotification(_) => {
|
||||||
|
@ -26,6 +26,7 @@ common-base = { package = "veloren-common-base", path = "base" }
|
|||||||
serde = { version = "1.0.110", features = ["derive", "rc"] }
|
serde = { version = "1.0.110", features = ["derive", "rc"] }
|
||||||
|
|
||||||
# Util
|
# Util
|
||||||
|
enum-map = "2.4"
|
||||||
vek = { version = "0.15.8", features = ["serde"] }
|
vek = { version = "0.15.8", features = ["serde"] }
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
chrono = "0.4.22"
|
chrono = "0.4.22"
|
||||||
|
@ -101,7 +101,7 @@ impl ClientMsg {
|
|||||||
&self,
|
&self,
|
||||||
c_type: ClientType,
|
c_type: ClientType,
|
||||||
registered: bool,
|
registered: bool,
|
||||||
presence: Option<super::PresenceKind>,
|
presence: Option<comp::PresenceKind>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ClientMsg::Type(t) => c_type == *t,
|
ClientMsg::Type(t) => c_type == *t,
|
||||||
|
@ -19,23 +19,8 @@ pub use self::{
|
|||||||
},
|
},
|
||||||
world_msg::WorldMapMsg,
|
world_msg::WorldMapMsg,
|
||||||
};
|
};
|
||||||
use common::character::CharacterId;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub enum PresenceKind {
|
|
||||||
Spectator,
|
|
||||||
Character(CharacterId),
|
|
||||||
Possessor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PresenceKind {
|
|
||||||
/// Check if the presence represents a control of a character, and thus
|
|
||||||
/// certain in-game messages from the client such as control inputs
|
|
||||||
/// should be handled.
|
|
||||||
pub fn controlling_char(&self) -> bool { matches!(self, Self::Character(_) | Self::Possessor) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum PingMsg {
|
pub enum PingMsg {
|
||||||
Ping,
|
Ping,
|
||||||
|
@ -6,7 +6,7 @@ use crate::sync;
|
|||||||
use common::{
|
use common::{
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
character::{self, CharacterItem},
|
character::{self, CharacterItem},
|
||||||
comp::{self, invite::InviteKind, item::MaterialStatManifest},
|
comp::{self, invite::InviteKind, item::MaterialStatManifest, Content},
|
||||||
event::UpdateCharacterMetadata,
|
event::UpdateCharacterMetadata,
|
||||||
lod,
|
lod,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
@ -222,11 +222,10 @@ pub enum ServerGeneral<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ServerGeneral<'_> {
|
impl ServerGeneral<'_> {
|
||||||
pub fn server_msg<S>(chat_type: comp::ChatType<String>, msg: S) -> Self
|
// TODO: Don't use `Into<Content>` since this treats all strings as plaintext,
|
||||||
where
|
// properly localise server messages
|
||||||
S: Into<String>,
|
pub fn server_msg(chat_type: comp::ChatType<String>, content: impl Into<Content>) -> Self {
|
||||||
{
|
ServerGeneral::ChatMsg(chat_type.into_msg(content.into()))
|
||||||
ServerGeneral::ChatMsg(chat_type.chat_msg(msg))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +302,7 @@ impl ServerMsg<'_> {
|
|||||||
&self,
|
&self,
|
||||||
c_type: ClientType,
|
c_type: ClientType,
|
||||||
registered: bool,
|
registered: bool,
|
||||||
presence: Option<super::PresenceKind>,
|
presence: Option<comp::PresenceKind>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => {
|
ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => {
|
||||||
|
@ -40,6 +40,7 @@ macro_rules! synced_components {
|
|||||||
sticky: Sticky,
|
sticky: Sticky,
|
||||||
immovable: Immovable,
|
immovable: Immovable,
|
||||||
character_state: CharacterState,
|
character_state: CharacterState,
|
||||||
|
character_activity: CharacterActivity,
|
||||||
shockwave: Shockwave,
|
shockwave: Shockwave,
|
||||||
beam_segment: BeamSegment,
|
beam_segment: BeamSegment,
|
||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
@ -201,6 +202,10 @@ impl NetSync for CharacterState {
|
|||||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NetSync for CharacterActivity {
|
||||||
|
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||||
|
}
|
||||||
|
|
||||||
impl NetSync for Shockwave {
|
impl NetSync for Shockwave {
|
||||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,15 @@ impl<T> PathResult<T> {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map<U>(self, f: impl FnOnce(Path<T>) -> Path<U>) -> PathResult<U> {
|
||||||
|
match self {
|
||||||
|
PathResult::None(p) => PathResult::None(f(p)),
|
||||||
|
PathResult::Exhausted(p) => PathResult::Exhausted(f(p)),
|
||||||
|
PathResult::Path(p) => PathResult::Path(f(p)),
|
||||||
|
PathResult::Pending => PathResult::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -78,7 +87,7 @@ impl<S: Clone + Eq + Hash + fmt::Debug, H: BuildHasher> fmt::Debug for Astar<S,
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||||
pub fn new(max_iters: usize, start: S, heuristic: impl FnOnce(&S) -> f32, hasher: H) -> Self {
|
pub fn new(max_iters: usize, start: S, hasher: H) -> Self {
|
||||||
Self {
|
Self {
|
||||||
max_iters,
|
max_iters,
|
||||||
iter: 0,
|
iter: 0,
|
||||||
@ -95,7 +104,7 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
|||||||
},
|
},
|
||||||
final_scores: {
|
final_scores: {
|
||||||
let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone());
|
let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone());
|
||||||
h.extend(core::iter::once((start.clone(), heuristic(&start))));
|
h.extend(core::iter::once((start.clone(), 0.0)));
|
||||||
h
|
h
|
||||||
},
|
},
|
||||||
visited: {
|
visited: {
|
||||||
@ -111,7 +120,7 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
|||||||
pub fn poll<I>(
|
pub fn poll<I>(
|
||||||
&mut self,
|
&mut self,
|
||||||
iters: usize,
|
iters: usize,
|
||||||
mut heuristic: impl FnMut(&S) -> f32,
|
mut heuristic: impl FnMut(&S, &S) -> f32,
|
||||||
mut neighbors: impl FnMut(&S) -> I,
|
mut neighbors: impl FnMut(&S) -> I,
|
||||||
mut transition: impl FnMut(&S, &S) -> f32,
|
mut transition: impl FnMut(&S, &S) -> f32,
|
||||||
mut satisfied: impl FnMut(&S) -> bool,
|
mut satisfied: impl FnMut(&S) -> bool,
|
||||||
@ -134,7 +143,7 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
|||||||
if cost < *neighbor_cheapest {
|
if cost < *neighbor_cheapest {
|
||||||
self.came_from.insert(neighbor.clone(), node.clone());
|
self.came_from.insert(neighbor.clone(), node.clone());
|
||||||
self.cheapest_scores.insert(neighbor.clone(), cost);
|
self.cheapest_scores.insert(neighbor.clone(), cost);
|
||||||
let h = heuristic(&neighbor);
|
let h = heuristic(&neighbor, &node);
|
||||||
let neighbor_cost = cost + h;
|
let neighbor_cost = cost + h;
|
||||||
self.final_scores.insert(neighbor.clone(), neighbor_cost);
|
self.final_scores.insert(neighbor.clone(), neighbor_cost);
|
||||||
|
|
||||||
|
@ -5,7 +5,9 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
/// The limit on how many characters that a player can have
|
/// The limit on how many characters that a player can have
|
||||||
pub const MAX_CHARACTERS_PER_PLAYER: usize = 8;
|
pub const MAX_CHARACTERS_PER_PLAYER: usize = 8;
|
||||||
pub type CharacterId = i64;
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct CharacterId(pub i64);
|
||||||
|
|
||||||
pub const MAX_NAME_LENGTH: usize = 20;
|
pub const MAX_NAME_LENGTH: usize = 20;
|
||||||
|
|
||||||
|
@ -296,8 +296,14 @@ pub enum ServerChatCommand {
|
|||||||
Respawn,
|
Respawn,
|
||||||
RevokeBuild,
|
RevokeBuild,
|
||||||
RevokeBuildAll,
|
RevokeBuildAll,
|
||||||
|
RtsimChunk,
|
||||||
|
RtsimInfo,
|
||||||
|
RtsimNpc,
|
||||||
|
RtsimPurge,
|
||||||
|
RtsimTp,
|
||||||
Safezone,
|
Safezone,
|
||||||
Say,
|
Say,
|
||||||
|
Scale,
|
||||||
ServerPhysics,
|
ServerPhysics,
|
||||||
SetMotd,
|
SetMotd,
|
||||||
Ship,
|
Ship,
|
||||||
@ -653,6 +659,7 @@ impl ServerChatCommand {
|
|||||||
Enum("entity", ENTITIES.clone(), Required),
|
Enum("entity", ENTITIES.clone(), Required),
|
||||||
Integer("amount", 1, Optional),
|
Integer("amount", 1, Optional),
|
||||||
Boolean("ai", "true".to_string(), Optional),
|
Boolean("ai", "true".to_string(), Optional),
|
||||||
|
Float("scale", 1.0, Optional),
|
||||||
],
|
],
|
||||||
"Spawn a test entity",
|
"Spawn a test entity",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
@ -677,6 +684,36 @@ impl ServerChatCommand {
|
|||||||
"Teleport to another player",
|
"Teleport to another player",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
|
ServerChatCommand::RtsimTp => cmd(
|
||||||
|
vec![Integer("npc index", 0, Required)],
|
||||||
|
"Teleport to an rtsim npc",
|
||||||
|
Some(Moderator),
|
||||||
|
),
|
||||||
|
ServerChatCommand::RtsimInfo => cmd(
|
||||||
|
vec![Integer("npc index", 0, Required)],
|
||||||
|
"Display information about an rtsim NPC",
|
||||||
|
Some(Moderator),
|
||||||
|
),
|
||||||
|
ServerChatCommand::RtsimNpc => cmd(
|
||||||
|
vec![Any("query", Required), Integer("max number", 20, Optional)],
|
||||||
|
"List rtsim NPCs that fit a given query (e.g: simulated,merchant) in order of \
|
||||||
|
distance",
|
||||||
|
Some(Moderator),
|
||||||
|
),
|
||||||
|
ServerChatCommand::RtsimPurge => cmd(
|
||||||
|
vec![Boolean(
|
||||||
|
"whether purging of rtsim data should occur on next startup",
|
||||||
|
true.to_string(),
|
||||||
|
Required,
|
||||||
|
)],
|
||||||
|
"Purge rtsim data on next startup",
|
||||||
|
Some(Admin),
|
||||||
|
),
|
||||||
|
ServerChatCommand::RtsimChunk => cmd(
|
||||||
|
vec![],
|
||||||
|
"Display information about the current chunk from rtsim",
|
||||||
|
Some(Moderator),
|
||||||
|
),
|
||||||
ServerChatCommand::Unban => cmd(
|
ServerChatCommand::Unban => cmd(
|
||||||
vec![PlayerName(Required)],
|
vec![PlayerName(Required)],
|
||||||
"Remove the ban for the given username",
|
"Remove the ban for the given username",
|
||||||
@ -727,6 +764,14 @@ impl ServerChatCommand {
|
|||||||
ServerChatCommand::Lightning => {
|
ServerChatCommand::Lightning => {
|
||||||
cmd(vec![], "Lightning strike at current position", Some(Admin))
|
cmd(vec![], "Lightning strike at current position", Some(Admin))
|
||||||
},
|
},
|
||||||
|
ServerChatCommand::Scale => cmd(
|
||||||
|
vec![
|
||||||
|
Float("factor", 1.0, Required),
|
||||||
|
Boolean("reset_mass", true.to_string(), Optional),
|
||||||
|
],
|
||||||
|
"Scale your character",
|
||||||
|
Some(Admin),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -796,6 +841,11 @@ impl ServerChatCommand {
|
|||||||
ServerChatCommand::Tell => "tell",
|
ServerChatCommand::Tell => "tell",
|
||||||
ServerChatCommand::Time => "time",
|
ServerChatCommand::Time => "time",
|
||||||
ServerChatCommand::Tp => "tp",
|
ServerChatCommand::Tp => "tp",
|
||||||
|
ServerChatCommand::RtsimTp => "rtsim_tp",
|
||||||
|
ServerChatCommand::RtsimInfo => "rtsim_info",
|
||||||
|
ServerChatCommand::RtsimNpc => "rtsim_npc",
|
||||||
|
ServerChatCommand::RtsimPurge => "rtsim_purge",
|
||||||
|
ServerChatCommand::RtsimChunk => "rtsim_chunk",
|
||||||
ServerChatCommand::Unban => "unban",
|
ServerChatCommand::Unban => "unban",
|
||||||
ServerChatCommand::Version => "version",
|
ServerChatCommand::Version => "version",
|
||||||
ServerChatCommand::Waypoint => "waypoint",
|
ServerChatCommand::Waypoint => "waypoint",
|
||||||
@ -808,6 +858,7 @@ impl ServerChatCommand {
|
|||||||
ServerChatCommand::DeleteLocation => "delete_location",
|
ServerChatCommand::DeleteLocation => "delete_location",
|
||||||
ServerChatCommand::WeatherZone => "weather_zone",
|
ServerChatCommand::WeatherZone => "weather_zone",
|
||||||
ServerChatCommand::Lightning => "lightning",
|
ServerChatCommand::Lightning => "lightning",
|
||||||
|
ServerChatCommand::Scale => "scale",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,17 +23,16 @@ use crate::{
|
|||||||
util::Dir,
|
util::Dir,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use rand::{thread_rng, Rng};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{comp::Group, resources::Time};
|
use crate::{comp::Group, resources::Time};
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use specs::{saveload::MarkerAllocator, Entity as EcsEntity, ReadStorage};
|
use {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
rand::Rng,
|
||||||
use std::ops::{Mul, MulAssign};
|
specs::{saveload::MarkerAllocator, Entity as EcsEntity, ReadStorage},
|
||||||
#[cfg(not(target_arch = "wasm32"))] use vek::*;
|
std::ops::{Mul, MulAssign},
|
||||||
|
vek::*,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
@ -199,10 +198,10 @@ impl Attack {
|
|||||||
time: Time,
|
time: Time,
|
||||||
mut emit: impl FnMut(ServerEvent),
|
mut emit: impl FnMut(ServerEvent),
|
||||||
mut emit_outcome: impl FnMut(Outcome),
|
mut emit_outcome: impl FnMut(Outcome),
|
||||||
|
rng: &mut rand::rngs::ThreadRng,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// TODO: Maybe move this higher and pass it as argument into this function?
|
// TODO: Maybe move this higher and pass it as argument into this function?
|
||||||
let msm = &MaterialStatManifest::load().read();
|
let msm = &MaterialStatManifest::load().read();
|
||||||
let mut rng = thread_rng();
|
|
||||||
|
|
||||||
let AttackOptions {
|
let AttackOptions {
|
||||||
target_dodging,
|
target_dodging,
|
||||||
@ -518,7 +517,7 @@ impl Attack {
|
|||||||
.filter(|e| e.target.map_or(true, |t| t == target_group))
|
.filter(|e| e.target.map_or(true, |t| t == target_group))
|
||||||
.filter(|e| !avoid_effect(e))
|
.filter(|e| !avoid_effect(e))
|
||||||
{
|
{
|
||||||
if effect.requirements.iter().all(|req| match req {
|
let requirements_met = effect.requirements.iter().all(|req| match req {
|
||||||
CombatRequirement::AnyDamage => accumulated_damage > 0.0 && target.health.is_some(),
|
CombatRequirement::AnyDamage => accumulated_damage > 0.0 && target.health.is_some(),
|
||||||
CombatRequirement::Energy(r) => {
|
CombatRequirement::Energy(r) => {
|
||||||
if let Some(AttackerInfo {
|
if let Some(AttackerInfo {
|
||||||
@ -560,7 +559,8 @@ impl Attack {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}) {
|
});
|
||||||
|
if requirements_met {
|
||||||
is_applied = true;
|
is_applied = true;
|
||||||
match effect.effect {
|
match effect.effect {
|
||||||
CombatEffect::Knockback(kb) => {
|
CombatEffect::Knockback(kb) => {
|
||||||
|
@ -912,7 +912,11 @@ impl CharacterAbility {
|
|||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
// If either in the air or is on ground and able to be activated from
|
// If either in the air or is on ground and able to be activated from
|
||||||
// ground
|
// ground.
|
||||||
|
//
|
||||||
|
// NOTE: there is a check in CharacterState::from below that must be kept in
|
||||||
|
// sync with the conditions here (it determines whether this starts in a
|
||||||
|
// movement or buildup stage).
|
||||||
(data.physics.on_ground.is_none() || buildup_duration.is_some())
|
(data.physics.on_ground.is_none() || buildup_duration.is_some())
|
||||||
&& update.energy.try_change_by(-*energy_cost).is_ok()
|
&& update.energy.try_change_by(-*energy_cost).is_ok()
|
||||||
},
|
},
|
||||||
@ -2792,7 +2796,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
|
|||||||
ability_info,
|
ability_info,
|
||||||
},
|
},
|
||||||
timer: Duration::default(),
|
timer: Duration::default(),
|
||||||
stage_section: if data.vel.0.z < -*vertical_speed || buildup_duration.is_none() {
|
stage_section: if data.physics.on_ground.is_none() || buildup_duration.is_none() {
|
||||||
StageSection::Movement
|
StageSection::Movement
|
||||||
} else {
|
} else {
|
||||||
StageSection::Buildup
|
StageSection::Buildup
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
quadruped_small, ship, Body, UtteranceKind,
|
quadruped_small, ship, Body, UtteranceKind,
|
||||||
},
|
},
|
||||||
path::Chaser,
|
path::Chaser,
|
||||||
rtsim::{Memory, MemoryItem, RtSimController, RtSimEvent},
|
rtsim::RtSimController,
|
||||||
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
@ -83,7 +83,7 @@ impl Alignment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never attacks
|
// Usually never attacks
|
||||||
pub fn passive_towards(self, other: Alignment) -> bool {
|
pub fn passive_towards(self, other: Alignment) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Alignment::Enemy, Alignment::Enemy) => true,
|
(Alignment::Enemy, Alignment::Enemy) => true,
|
||||||
@ -98,6 +98,20 @@ impl Alignment {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Never attacks
|
||||||
|
pub fn friendly_towards(self, other: Alignment) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Alignment::Enemy, Alignment::Enemy) => true,
|
||||||
|
(Alignment::Owned(a), Alignment::Owned(b)) if a == b => true,
|
||||||
|
(Alignment::Npc, Alignment::Npc) => true,
|
||||||
|
(Alignment::Npc, Alignment::Tame) => true,
|
||||||
|
(Alignment::Tame, Alignment::Npc) => true,
|
||||||
|
(Alignment::Tame, Alignment::Tame) => true,
|
||||||
|
(_, Alignment::Passive) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Alignment {
|
impl Component for Alignment {
|
||||||
@ -611,6 +625,11 @@ impl Awareness {
|
|||||||
self.reached = false;
|
self.reached = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_maximally_aware(&mut self) {
|
||||||
|
self.reached = true;
|
||||||
|
self.level = Self::ALERT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq)]
|
||||||
@ -713,23 +732,6 @@ impl Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn allowed_to_speak(&self) -> bool { self.behavior.can(BehaviorCapability::SPEAK) }
|
pub fn allowed_to_speak(&self) -> bool { self.behavior.can(BehaviorCapability::SPEAK) }
|
||||||
|
|
||||||
pub fn forget_enemy(&mut self, target_name: &str) {
|
|
||||||
self.rtsim_controller
|
|
||||||
.events
|
|
||||||
.push(RtSimEvent::ForgetEnemy(target_name.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_fight_to_memory(&mut self, target_name: &str, time: f64) {
|
|
||||||
self.rtsim_controller
|
|
||||||
.events
|
|
||||||
.push(RtSimEvent::AddMemory(Memory {
|
|
||||||
item: MemoryItem::CharacterFight {
|
|
||||||
name: target_name.to_owned(),
|
|
||||||
},
|
|
||||||
time_to_forget: time + 300.0,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Agent {
|
impl Component for Agent {
|
||||||
@ -904,21 +906,21 @@ impl<F: Fn(Vec3<f32>, Vec3<f32>) -> f32, const NUM_SAMPLES: usize> PidController
|
|||||||
|
|
||||||
/// Get the PID coefficients associated with some Body, since it will likely
|
/// Get the PID coefficients associated with some Body, since it will likely
|
||||||
/// need to be tuned differently for each body type
|
/// need to be tuned differently for each body type
|
||||||
pub fn pid_coefficients(body: &Body) -> (f32, f32, f32) {
|
pub fn pid_coefficients(body: &Body) -> Option<(f32, f32, f32)> {
|
||||||
|
// A pure-proportional controller is { kp: 1.0, ki: 0.0, kd: 0.0 }
|
||||||
match body {
|
match body {
|
||||||
Body::Ship(ship::Body::DefaultAirship) => {
|
Body::Ship(ship::Body::DefaultAirship) => {
|
||||||
let kp = 1.0;
|
let kp = 1.0;
|
||||||
let ki = 0.1;
|
let ki = 0.1;
|
||||||
let kd = 1.2;
|
let kd = 1.2;
|
||||||
(kp, ki, kd)
|
Some((kp, ki, kd))
|
||||||
},
|
},
|
||||||
Body::Ship(ship::Body::AirBalloon) => {
|
Body::Ship(ship::Body::AirBalloon) => {
|
||||||
let kp = 1.0;
|
let kp = 1.0;
|
||||||
let ki = 0.1;
|
let ki = 0.1;
|
||||||
let kd = 0.8;
|
let kd = 0.8;
|
||||||
(kp, ki, kd)
|
Some((kp, ki, kd))
|
||||||
},
|
},
|
||||||
// default to a pure-proportional controller, which is the first step when tuning
|
_ => None,
|
||||||
_ => (1.0, 0.0, 0.0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -783,12 +783,12 @@ impl Body {
|
|||||||
Body::FishSmall(_) => 3,
|
Body::FishSmall(_) => 3,
|
||||||
Body::BipedLarge(biped_large) => match biped_large.species {
|
Body::BipedLarge(biped_large) => match biped_large.species {
|
||||||
biped_large::Species::Ogre => 320,
|
biped_large::Species::Ogre => 320,
|
||||||
biped_large::Species::Cyclops => 320,
|
biped_large::Species::Cyclops => 1000,
|
||||||
biped_large::Species::Wendigo => 280,
|
biped_large::Species::Wendigo => 280,
|
||||||
biped_large::Species::Cavetroll => 240,
|
biped_large::Species::Cavetroll => 240,
|
||||||
biped_large::Species::Mountaintroll => 240,
|
biped_large::Species::Mountaintroll => 240,
|
||||||
biped_large::Species::Swamptroll => 240,
|
biped_large::Species::Swamptroll => 240,
|
||||||
biped_large::Species::Dullahan => 700,
|
biped_large::Species::Dullahan => 600,
|
||||||
biped_large::Species::Mindflayer => 1250,
|
biped_large::Species::Mindflayer => 1250,
|
||||||
biped_large::Species::Tidalwarrior => 1600,
|
biped_large::Species::Tidalwarrior => 1600,
|
||||||
biped_large::Species::Yeti => 1200,
|
biped_large::Species::Yeti => 1200,
|
||||||
@ -895,10 +895,17 @@ impl Body {
|
|||||||
),
|
),
|
||||||
Body::BipedLarge(b) => matches!(
|
Body::BipedLarge(b) => matches!(
|
||||||
b.species,
|
b.species,
|
||||||
biped_large::Species::Huskbrute | biped_large::Species::Gigasfrost
|
biped_large::Species::Huskbrute
|
||||||
|
| biped_large::Species::Gigasfrost
|
||||||
|
| biped_large::Species::Dullahan
|
||||||
),
|
),
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
BuffKind::Crippled => match self {
|
||||||
|
Body::Object(_) | Body::Golem(_) | Body::Ship(_) => true,
|
||||||
|
Body::BipedLarge(b) => matches!(b.species, biped_large::Species::Dullahan),
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
BuffKind::Burning => match self {
|
BuffKind::Burning => match self {
|
||||||
Body::Golem(g) => matches!(g.species, golem::Species::ClayGolem),
|
Body::Golem(g) => matches!(g.species, golem::Species::ClayGolem),
|
||||||
Body::BipedSmall(b) => matches!(b.species, biped_small::Species::Haniwa),
|
Body::BipedSmall(b) => matches!(b.species, biped_small::Species::Haniwa),
|
||||||
@ -915,6 +922,7 @@ impl Body {
|
|||||||
| bird_large::Species::WealdWyvern
|
| bird_large::Species::WealdWyvern
|
||||||
),
|
),
|
||||||
Body::Arthropod(b) => matches!(b.species, arthropod::Species::Moltencrawler),
|
Body::Arthropod(b) => matches!(b.species, arthropod::Species::Moltencrawler),
|
||||||
|
Body::BipedLarge(b) => matches!(b.species, biped_large::Species::Cyclops),
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
BuffKind::Ensnared => match self {
|
BuffKind::Ensnared => match self {
|
||||||
@ -983,7 +991,7 @@ impl Body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the eye height for this creature.
|
/// Returns the eye height for this creature.
|
||||||
pub fn eye_height(&self) -> f32 { self.height() * 0.9 }
|
pub fn eye_height(&self, scale: f32) -> f32 { self.height() * 0.9 * scale }
|
||||||
|
|
||||||
pub fn default_light_offset(&self) -> Vec3<f32> {
|
pub fn default_light_offset(&self) -> Vec3<f32> {
|
||||||
// TODO: Make this a manifest
|
// TODO: Make this a manifest
|
||||||
|
@ -101,6 +101,9 @@ make_case_elim!(
|
|||||||
DagonBomb = 86,
|
DagonBomb = 86,
|
||||||
BarrelOrgan = 87,
|
BarrelOrgan = 87,
|
||||||
IceBomb = 88,
|
IceBomb = 88,
|
||||||
|
SpectralSwordSmall = 89,
|
||||||
|
SpectralSwordLarge = 90,
|
||||||
|
LaserBeam = 91,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -111,7 +114,7 @@ impl Body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ALL_OBJECTS: [Body; 89] = [
|
pub const ALL_OBJECTS: [Body; 92] = [
|
||||||
Body::Arrow,
|
Body::Arrow,
|
||||||
Body::Bomb,
|
Body::Bomb,
|
||||||
Body::Scarecrow,
|
Body::Scarecrow,
|
||||||
@ -124,6 +127,8 @@ pub const ALL_OBJECTS: [Body; 89] = [
|
|||||||
Body::ChestLight,
|
Body::ChestLight,
|
||||||
Body::ChestOpen,
|
Body::ChestOpen,
|
||||||
Body::ChestSkull,
|
Body::ChestSkull,
|
||||||
|
Body::SpectralSwordSmall,
|
||||||
|
Body::SpectralSwordLarge,
|
||||||
Body::Pumpkin,
|
Body::Pumpkin,
|
||||||
Body::Pumpkin2,
|
Body::Pumpkin2,
|
||||||
Body::Pumpkin3,
|
Body::Pumpkin3,
|
||||||
@ -201,6 +206,7 @@ pub const ALL_OBJECTS: [Body; 89] = [
|
|||||||
Body::DagonBomb,
|
Body::DagonBomb,
|
||||||
Body::BarrelOrgan,
|
Body::BarrelOrgan,
|
||||||
Body::IceBomb,
|
Body::IceBomb,
|
||||||
|
Body::LaserBeam,
|
||||||
];
|
];
|
||||||
|
|
||||||
impl From<Body> for super::Body {
|
impl From<Body> for super::Body {
|
||||||
@ -299,6 +305,9 @@ impl Body {
|
|||||||
Body::DagonBomb => "dagon_bomb",
|
Body::DagonBomb => "dagon_bomb",
|
||||||
Body::BarrelOrgan => "barrel_organ",
|
Body::BarrelOrgan => "barrel_organ",
|
||||||
Body::IceBomb => "ice_bomb",
|
Body::IceBomb => "ice_bomb",
|
||||||
|
Body::SpectralSwordSmall => "spectral_sword_small",
|
||||||
|
Body::SpectralSwordLarge => "spectral_sword_large",
|
||||||
|
Body::LaserBeam => "laser_beam",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +330,9 @@ impl Body {
|
|||||||
| Body::ArrowTurret
|
| Body::ArrowTurret
|
||||||
| Body::MultiArrow
|
| Body::MultiArrow
|
||||||
| Body::Dart
|
| Body::Dart
|
||||||
| Body::DagonBomb => 500.0,
|
| Body::DagonBomb
|
||||||
|
| Body::SpectralSwordSmall
|
||||||
|
| Body::SpectralSwordLarge => 500.0,
|
||||||
Body::Bomb => 2000.0, // I have no idea what it's supposed to be
|
Body::Bomb => 2000.0, // I have no idea what it's supposed to be
|
||||||
Body::Crate => 300.0, // let's say it's a lot of wood and maybe some contents
|
Body::Crate => 300.0, // let's say it's a lot of wood and maybe some contents
|
||||||
Body::Scarecrow => 900.0,
|
Body::Scarecrow => 900.0,
|
||||||
@ -341,6 +352,8 @@ impl Body {
|
|||||||
Body::Arrow | Body::ArrowSnake | Body::ArrowTurret | Body::MultiArrow | Body::Dart => {
|
Body::Arrow | Body::ArrowSnake | Body::ArrowTurret | Body::MultiArrow | Body::Dart => {
|
||||||
0.003
|
0.003
|
||||||
},
|
},
|
||||||
|
Body::SpectralSwordSmall => 0.5,
|
||||||
|
Body::SpectralSwordLarge => 50.0,
|
||||||
Body::BedBlue => 50.0,
|
Body::BedBlue => 50.0,
|
||||||
Body::Bedroll => 3.0,
|
Body::Bedroll => 3.0,
|
||||||
Body::Bench => 100.0,
|
Body::Bench => 100.0,
|
||||||
@ -413,6 +426,7 @@ impl Body {
|
|||||||
Body::Coconut => 2.0,
|
Body::Coconut => 2.0,
|
||||||
Body::GnarlingTotemRed | Body::GnarlingTotemGreen | Body::GnarlingTotemWhite => 100.0,
|
Body::GnarlingTotemRed | Body::GnarlingTotemGreen | Body::GnarlingTotemWhite => 100.0,
|
||||||
Body::IceBomb => 12298.0, // 2.5 m diamter but ice
|
Body::IceBomb => 12298.0, // 2.5 m diamter but ice
|
||||||
|
Body::LaserBeam => 80000.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
Mass(m)
|
Mass(m)
|
||||||
@ -424,6 +438,8 @@ impl Body {
|
|||||||
Vec3::new(0.01, 0.8, 0.01)
|
Vec3::new(0.01, 0.8, 0.01)
|
||||||
},
|
},
|
||||||
Body::BoltFire => Vec3::new(0.1, 0.1, 0.1),
|
Body::BoltFire => Vec3::new(0.1, 0.1, 0.1),
|
||||||
|
Body::SpectralSwordSmall => Vec3::new(0.2, 0.9, 0.1),
|
||||||
|
Body::SpectralSwordLarge => Vec3::new(0.2, 1.5, 0.1),
|
||||||
Body::Crossbow => Vec3::new(3.0, 3.0, 1.5),
|
Body::Crossbow => Vec3::new(3.0, 3.0, 1.5),
|
||||||
Body::HaniwaSentry => Vec3::new(0.8, 0.8, 1.4),
|
Body::HaniwaSentry => Vec3::new(0.8, 0.8, 1.4),
|
||||||
Body::SeaLantern => Vec3::new(0.8, 0.8, 1.4),
|
Body::SeaLantern => Vec3::new(0.8, 0.8, 1.4),
|
||||||
@ -435,6 +451,7 @@ impl Body {
|
|||||||
},
|
},
|
||||||
Body::BarrelOrgan => Vec3::new(4.0, 2.0, 3.0),
|
Body::BarrelOrgan => Vec3::new(4.0, 2.0, 3.0),
|
||||||
Body::IceBomb => Vec3::broadcast(2.5),
|
Body::IceBomb => Vec3::broadcast(2.5),
|
||||||
|
Body::LaserBeam => Vec3::new(8.0, 8.0, 8.0),
|
||||||
// FIXME: this *must* be exhaustive match
|
// FIXME: this *must* be exhaustive match
|
||||||
_ => Vec3::broadcast(0.5),
|
_ => Vec3::broadcast(0.5),
|
||||||
}
|
}
|
||||||
|
@ -22,67 +22,67 @@ use super::Body;
|
|||||||
)]
|
)]
|
||||||
pub enum BuffKind {
|
pub enum BuffKind {
|
||||||
// Buffs
|
// Buffs
|
||||||
/// Restores health/time for some period
|
/// Restores health/time for some period.
|
||||||
/// Strength should be the healing per second
|
/// Strength should be the healing per second.
|
||||||
Regeneration,
|
Regeneration,
|
||||||
/// Restores health/time for some period for consumables
|
/// Restores health/time for some period for consumables.
|
||||||
/// Strength should be the healing per second
|
/// Strength should be the healing per second.
|
||||||
Saturation,
|
Saturation,
|
||||||
/// Applied when drinking a potion
|
/// Applied when drinking a potion.
|
||||||
/// Strength should be the healing per second
|
/// Strength should be the healing per second.
|
||||||
Potion,
|
Potion,
|
||||||
/// Applied when sitting at a campfire
|
/// Applied when sitting at a campfire.
|
||||||
/// Strength is fraction of health restored per second
|
/// Strength is fraction of health restored per second.
|
||||||
CampfireHeal,
|
CampfireHeal,
|
||||||
/// Restores energy/time for some period
|
/// Restores energy/time for some period.
|
||||||
/// Strength should be the healing per second
|
/// Strength should be the healing per second.
|
||||||
EnergyRegen,
|
EnergyRegen,
|
||||||
/// Raises maximum energy
|
/// Raises maximum energy.
|
||||||
/// Strength should be 10x the effect to max energy
|
/// Strength should be 10x the effect to max energy.
|
||||||
IncreaseMaxEnergy,
|
IncreaseMaxEnergy,
|
||||||
/// Raises maximum health
|
/// Raises maximum health.
|
||||||
/// Strength should be the effect to max health
|
/// Strength should be the effect to max health.
|
||||||
IncreaseMaxHealth,
|
IncreaseMaxHealth,
|
||||||
/// Makes you immune to attacks
|
/// Makes you immune to attacks.
|
||||||
/// Strength does not affect this buff
|
/// Strength does not affect this buff.
|
||||||
Invulnerability,
|
Invulnerability,
|
||||||
/// Reduces incoming damage
|
/// Reduces incoming damage.
|
||||||
/// Strength scales the damage reduction non-linearly. 0.5 provides 50% DR,
|
/// Strength scales the damage reduction non-linearly. 0.5 provides 50% DR,
|
||||||
/// 1.0 provides 67% DR
|
/// 1.0 provides 67% DR.
|
||||||
ProtectingWard,
|
ProtectingWard,
|
||||||
/// Increases movement speed and gives health regeneration
|
/// Increases movement speed and gives health regeneration.
|
||||||
/// Strength scales the movement speed linearly. 0.5 is 150% speed, 1.0 is
|
/// Strength scales the movement speed linearly. 0.5 is 150% speed, 1.0 is
|
||||||
/// 200% speed. Provides regeneration at 10x the value of the strength
|
/// 200% speed. Provides regeneration at 10x the value of the strength.
|
||||||
Frenzied,
|
Frenzied,
|
||||||
/// Increases movement and attack speed, but removes chance to get critical
|
/// Increases movement and attack speed, but removes chance to get critical
|
||||||
/// hits. Strength scales strength of both effects linearly. 0.5 is a
|
/// hits. Strength scales strength of both effects linearly. 0.5 is a
|
||||||
/// 50% increase, 1.0 is a 100% increase.
|
/// 50% increase, 1.0 is a 100% increase.
|
||||||
Hastened,
|
Hastened,
|
||||||
/// Increases resistance to incoming poise, and poise damage dealt as health
|
/// Increases resistance to incoming poise, and poise damage dealt as health
|
||||||
/// is lost from the time the buff activated
|
/// is lost from the time the buff activated.
|
||||||
/// Strength scales the resistance non-linearly. 0.5 provides 50%, 1.0
|
/// Strength scales the resistance non-linearly. 0.5 provides 50%, 1.0
|
||||||
/// provides 67%
|
/// provides 67%.
|
||||||
/// Strength scales the poise damage increase linearly, a strength of 1.0
|
/// Strength scales the poise damage increase linearly, a strength of 1.0
|
||||||
/// and n health less from activation will cause poise damage to increase by
|
/// and n health less from activation will cause poise damage to increase by
|
||||||
/// n%
|
/// n%.
|
||||||
Fortitude,
|
Fortitude,
|
||||||
/// Increases both attack damage and vulnerability to damage
|
/// Increases both attack damage and vulnerability to damage.
|
||||||
/// Damage increases linearly with strength, 1.0 is a 100% increase
|
/// Damage increases linearly with strength, 1.0 is a 100% increase.
|
||||||
/// Damage reduction decreases linearly with strength, 1.0 is a 100%
|
/// Damage reduction decreases linearly with strength, 1.0 is a 100%
|
||||||
/// decrease
|
/// decrease.
|
||||||
Reckless,
|
Reckless,
|
||||||
// Debuffs
|
// Debuffs
|
||||||
/// Does damage to a creature over time
|
/// Does damage to a creature over time.
|
||||||
/// Strength should be the DPS of the debuff
|
/// Strength should be the DPS of the debuff.
|
||||||
Burning,
|
Burning,
|
||||||
/// Lowers health over time for some duration
|
/// Lowers health over time for some duration.
|
||||||
/// Strength should be the DPS of the debuff
|
/// Strength should be the DPS of the debuff.
|
||||||
Bleeding,
|
Bleeding,
|
||||||
/// Lower a creature's max health over time
|
/// Lower a creature's max health over time.
|
||||||
/// Strength only affects the target max health, 0.5 targets 50% of base
|
/// Strength only affects the target max health, 0.5 targets 50% of base
|
||||||
/// max, 1.0 targets 100% of base max
|
/// max, 1.0 targets 100% of base max.
|
||||||
Cursed,
|
Cursed,
|
||||||
/// Reduces movement speed and causes bleeding damage
|
/// Reduces movement speed and causes bleeding damage.
|
||||||
/// Strength scales the movement speed debuff non-linearly. 0.5 is 50%
|
/// Strength scales the movement speed debuff non-linearly. 0.5 is 50%
|
||||||
/// speed, 1.0 is 33% speed. Bleeding is at 4x the value of the strength.
|
/// speed, 1.0 is 33% speed. Bleeding is at 4x the value of the strength.
|
||||||
Crippled,
|
Crippled,
|
||||||
@ -99,8 +99,8 @@ pub enum BuffKind {
|
|||||||
/// Strength scales the movement speed debuff non-linearly. 0.5 is 50%
|
/// Strength scales the movement speed debuff non-linearly. 0.5 is 50%
|
||||||
/// speed, 1.0 is 33% speed.
|
/// speed, 1.0 is 33% speed.
|
||||||
Ensnared,
|
Ensnared,
|
||||||
/// Drain stamina to a creature over time
|
/// Drain stamina to a creature over time.
|
||||||
/// Strength should be the energy per second of the debuff
|
/// Strength should be the energy per second of the debuff.
|
||||||
Poisoned,
|
Poisoned,
|
||||||
/// Results from having an attack parried.
|
/// Results from having an attack parried.
|
||||||
/// Causes your attack speed to be slower to emulate the recover duration of
|
/// Causes your attack speed to be slower to emulate the recover duration of
|
||||||
@ -115,7 +115,7 @@ pub enum BuffKind {
|
|||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
impl BuffKind {
|
impl BuffKind {
|
||||||
/// Checks if buff is buff or debuff
|
/// Checks if buff is buff or debuff.
|
||||||
pub fn is_buff(self) -> bool {
|
pub fn is_buff(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
BuffKind::Regeneration
|
BuffKind::Regeneration
|
||||||
@ -145,7 +145,7 @@ impl BuffKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if buff should queue
|
/// Checks if buff should queue.
|
||||||
pub fn queues(self) -> bool { matches!(self, BuffKind::Saturation) }
|
pub fn queues(self) -> bool { matches!(self, BuffKind::Saturation) }
|
||||||
|
|
||||||
/// Checks if the buff can affect other buff effects applied in the same
|
/// Checks if the buff can affect other buff effects applied in the same
|
||||||
@ -417,7 +417,7 @@ pub enum BuffChange {
|
|||||||
any_required: Vec<BuffCategory>,
|
any_required: Vec<BuffCategory>,
|
||||||
none_required: Vec<BuffCategory>,
|
none_required: Vec<BuffCategory>,
|
||||||
},
|
},
|
||||||
// Refreshes durations of all buffs with this kind
|
/// Refreshes durations of all buffs with this kind.
|
||||||
Refresh(BuffKind),
|
Refresh(BuffKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ use crate::{
|
|||||||
utils::{AbilityInfo, StageSection},
|
utils::{AbilityInfo, StageSection},
|
||||||
*,
|
*,
|
||||||
},
|
},
|
||||||
|
util::Dir,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specs::{Component, DerefFlaggedStorage};
|
use specs::{Component, DerefFlaggedStorage};
|
||||||
@ -30,6 +31,7 @@ pub struct StateUpdate {
|
|||||||
pub should_strafe: bool,
|
pub should_strafe: bool,
|
||||||
pub queued_inputs: BTreeMap<InputKind, InputAttr>,
|
pub queued_inputs: BTreeMap<InputKind, InputAttr>,
|
||||||
pub removed_inputs: Vec<InputKind>,
|
pub removed_inputs: Vec<InputKind>,
|
||||||
|
pub character_activity: CharacterActivity,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OutputEvents<'a> {
|
pub struct OutputEvents<'a> {
|
||||||
@ -60,6 +62,7 @@ impl From<&JoinData<'_>> for StateUpdate {
|
|||||||
character: data.character.clone(),
|
character: data.character.clone(),
|
||||||
queued_inputs: BTreeMap::new(),
|
queued_inputs: BTreeMap::new(),
|
||||||
removed_inputs: Vec::new(),
|
removed_inputs: Vec::new(),
|
||||||
|
character_activity: *data.character_activity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,7 +260,6 @@ impl CharacterState {
|
|||||||
| CharacterState::Shockwave(_)
|
| CharacterState::Shockwave(_)
|
||||||
| CharacterState::BasicBeam(_)
|
| CharacterState::BasicBeam(_)
|
||||||
| CharacterState::Stunned(_)
|
| CharacterState::Stunned(_)
|
||||||
| CharacterState::UseItem(_)
|
|
||||||
| CharacterState::Wielding(_)
|
| CharacterState::Wielding(_)
|
||||||
| CharacterState::Talk
|
| CharacterState::Talk
|
||||||
| CharacterState::FinisherMelee(_)
|
| CharacterState::FinisherMelee(_)
|
||||||
@ -980,3 +982,20 @@ impl Default for CharacterState {
|
|||||||
impl Component for CharacterState {
|
impl Component for CharacterState {
|
||||||
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
|
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contains information about the visual activity of a character.
|
||||||
|
///
|
||||||
|
/// For now this only includes the direction they're looking in, but later it
|
||||||
|
/// might include markers indicating that they're available for
|
||||||
|
/// trade/interaction, more details about their stance or appearance, facial
|
||||||
|
/// expression, etc.
|
||||||
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct CharacterActivity {
|
||||||
|
/// `None` means that the look direction should be derived from the
|
||||||
|
/// orientation
|
||||||
|
pub look_dir: Option<Dir>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for CharacterActivity {
|
||||||
|
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ use crate::{
|
|||||||
comp::{group::Group, BuffKind},
|
comp::{group::Group, BuffKind},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
|
use hashbrown::HashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specs::{Component, DenseVecStorage};
|
use specs::{Component, DenseVecStorage};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@ -29,8 +30,8 @@ impl Component for ChatMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ChatMode {
|
impl ChatMode {
|
||||||
/// Create a message from your current chat mode and uuid.
|
/// Create a plain message from your current chat mode and uuid.
|
||||||
pub fn new_message(&self, from: Uid, message: String) -> UnresolvedChatMsg {
|
pub fn to_plain_msg(&self, from: Uid, text: impl ToString) -> UnresolvedChatMsg {
|
||||||
let chat_type = match self {
|
let chat_type = match self {
|
||||||
ChatMode::Tell(to) => ChatType::Tell(from, *to),
|
ChatMode::Tell(to) => ChatType::Tell(from, *to),
|
||||||
ChatMode::Say => ChatType::Say(from),
|
ChatMode::Say => ChatType::Say(from),
|
||||||
@ -39,7 +40,10 @@ impl ChatMode {
|
|||||||
ChatMode::Faction(faction) => ChatType::Faction(from, faction.clone()),
|
ChatMode::Faction(faction) => ChatType::Faction(from, faction.clone()),
|
||||||
ChatMode::World => ChatType::World(from),
|
ChatMode::World => ChatType::World(from),
|
||||||
};
|
};
|
||||||
UnresolvedChatMsg { chat_type, message }
|
UnresolvedChatMsg {
|
||||||
|
chat_type,
|
||||||
|
content: Content::Plain(text.to_string()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,26 +106,28 @@ pub enum ChatType<G> {
|
|||||||
/// World chat
|
/// World chat
|
||||||
World(Uid),
|
World(Uid),
|
||||||
/// Messages sent from NPCs (Not shown in chat but as speech bubbles)
|
/// Messages sent from NPCs (Not shown in chat but as speech bubbles)
|
||||||
///
|
Npc(Uid),
|
||||||
/// The u16 field is a random number for selecting localization variants.
|
|
||||||
Npc(Uid, u16),
|
|
||||||
/// From NPCs but in the chat for clients in the near vicinity
|
/// From NPCs but in the chat for clients in the near vicinity
|
||||||
NpcSay(Uid, u16),
|
NpcSay(Uid),
|
||||||
/// From NPCs but in the chat for a specific client. Shows a chat bubble.
|
/// From NPCs but in the chat for a specific client. Shows a chat bubble.
|
||||||
/// (from, to, localization variant)
|
/// (from, to, localization variant)
|
||||||
NpcTell(Uid, Uid, u16),
|
NpcTell(Uid, Uid),
|
||||||
/// Anything else
|
/// Anything else
|
||||||
Meta,
|
Meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<G> ChatType<G> {
|
impl<G> ChatType<G> {
|
||||||
pub fn chat_msg<S>(self, msg: S) -> GenericChatMsg<G>
|
pub fn into_plain_msg(self, text: impl ToString) -> GenericChatMsg<G> {
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
GenericChatMsg {
|
GenericChatMsg {
|
||||||
chat_type: self,
|
chat_type: self,
|
||||||
message: msg.into(),
|
content: Content::Plain(text.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_msg(self, content: Content) -> GenericChatMsg<G> {
|
||||||
|
GenericChatMsg {
|
||||||
|
chat_type: self,
|
||||||
|
content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,9 +146,9 @@ impl<G> ChatType<G> {
|
|||||||
ChatType::Faction(u, _s) => Some(*u),
|
ChatType::Faction(u, _s) => Some(*u),
|
||||||
ChatType::Region(u) => Some(*u),
|
ChatType::Region(u) => Some(*u),
|
||||||
ChatType::World(u) => Some(*u),
|
ChatType::World(u) => Some(*u),
|
||||||
ChatType::Npc(u, _r) => Some(*u),
|
ChatType::Npc(u) => Some(*u),
|
||||||
ChatType::NpcSay(u, _r) => Some(*u),
|
ChatType::NpcSay(u) => Some(*u),
|
||||||
ChatType::NpcTell(u, _t, _r) => Some(*u),
|
ChatType::NpcTell(u, _t) => Some(*u),
|
||||||
ChatType::Meta => None,
|
ChatType::Meta => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,9 +162,9 @@ impl<G> ChatType<G> {
|
|||||||
| ChatType::CommandError
|
| ChatType::CommandError
|
||||||
| ChatType::FactionMeta(_)
|
| ChatType::FactionMeta(_)
|
||||||
| ChatType::GroupMeta(_)
|
| ChatType::GroupMeta(_)
|
||||||
| ChatType::Npc(_, _)
|
| ChatType::Npc(_)
|
||||||
| ChatType::NpcSay(_, _)
|
| ChatType::NpcSay(_)
|
||||||
| ChatType::NpcTell(_, _, _)
|
| ChatType::NpcTell(_, _)
|
||||||
| ChatType::Meta
|
| ChatType::Meta
|
||||||
| ChatType::Kill(_, _) => None,
|
| ChatType::Kill(_, _) => None,
|
||||||
ChatType::Tell(_, _) | ChatType::Group(_, _) | ChatType::Faction(_, _) => Some(true),
|
ChatType::Tell(_, _) | ChatType::Group(_, _) | ChatType::Faction(_, _) => Some(true),
|
||||||
@ -167,11 +173,118 @@ impl<G> ChatType<G> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The content of a chat message.
|
||||||
|
// TODO: This could be generalised to *any* in-game text, not just chat messages (hence it not being
|
||||||
|
// called `ChatContent`). A few examples:
|
||||||
|
//
|
||||||
|
// - Signposts, both those appearing as overhead messages and those displayed 'in-world' on a shop
|
||||||
|
// sign
|
||||||
|
// - UI elements
|
||||||
|
// - In-game notes/books (we could add a variant that allows structuring complex, novel textual
|
||||||
|
// information as a syntax tree or some other intermediate format that can be localised by the
|
||||||
|
// client)
|
||||||
|
// TODO: We probably want to have this type be able to represent similar things to
|
||||||
|
// `fluent::FluentValue`, such as numeric values, so that they can be properly localised in whatever
|
||||||
|
// manner is required.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum Content {
|
||||||
|
/// The content is a plaintext string that should be shown to the user
|
||||||
|
/// verbatim.
|
||||||
|
Plain(String),
|
||||||
|
/// The content is a localizable message with the given arguments.
|
||||||
|
Localized {
|
||||||
|
/// i18n key
|
||||||
|
key: String,
|
||||||
|
/// Pseudorandom seed value that allows frontends to select a
|
||||||
|
/// deterministic (but pseudorandom) localised output
|
||||||
|
seed: u16,
|
||||||
|
/// i18n arguments
|
||||||
|
args: HashMap<String, LocalizationArg>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
|
||||||
|
impl From<String> for Content {
|
||||||
|
fn from(text: String) -> Self { Self::Plain(text) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
|
||||||
|
impl<'a> From<&'a str> for Content {
|
||||||
|
fn from(text: &'a str) -> Self { Self::Plain(text.to_string()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A localisation argument for localised content (see [`Content::Localized`]).
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum LocalizationArg {
|
||||||
|
/// The localisation argument is itself a section of content.
|
||||||
|
///
|
||||||
|
/// Note that this allows [`Content`] to recursively refer to itself. It may
|
||||||
|
/// be tempting to decide to parameterise everything, having dialogue
|
||||||
|
/// generated with a compact tree. "It's simpler!", you might say. False.
|
||||||
|
/// Over-parameterisation is an anti-pattern that hurts translators. Where
|
||||||
|
/// possible, prefer fewer levels of nesting unless doing so would
|
||||||
|
/// result in an intractably larger number of combinations. See [here](https://github.com/projectfluent/fluent/wiki/Good-Practices-for-Developers#prefer-wet-over-dry) for the
|
||||||
|
/// guidance provided by the docs for `fluent`, the localisation library
|
||||||
|
/// used by clients.
|
||||||
|
Content(Content),
|
||||||
|
/// The localisation argument is a natural number
|
||||||
|
Nat(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||||
|
// discourage it)
|
||||||
|
impl From<String> for LocalizationArg {
|
||||||
|
fn from(text: String) -> Self { Self::Content(Content::Plain(text)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||||
|
// discourage it)
|
||||||
|
impl<'a> From<&'a str> for LocalizationArg {
|
||||||
|
fn from(text: &'a str) -> Self { Self::Content(Content::Plain(text.to_string())) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||||
|
// discourage it)
|
||||||
|
impl From<u64> for LocalizationArg {
|
||||||
|
fn from(n: u64) -> Self { Self::Nat(n) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content {
|
||||||
|
pub fn localized(key: impl ToString) -> Self {
|
||||||
|
Self::Localized {
|
||||||
|
key: key.to_string(),
|
||||||
|
seed: rand::random(),
|
||||||
|
args: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn localized_with_args<'a, A: Into<LocalizationArg>>(
|
||||||
|
key: impl ToString,
|
||||||
|
args: impl IntoIterator<Item = (&'a str, A)>,
|
||||||
|
) -> Self {
|
||||||
|
Self::Localized {
|
||||||
|
key: key.to_string(),
|
||||||
|
seed: rand::random(),
|
||||||
|
args: args
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.into()))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_plain(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Plain(text) => Some(text.as_str()),
|
||||||
|
Self::Localized { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stores chat text, type
|
// Stores chat text, type
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct GenericChatMsg<G> {
|
pub struct GenericChatMsg<G> {
|
||||||
pub chat_type: ChatType<G>,
|
pub chat_type: ChatType<G>,
|
||||||
pub message: String,
|
content: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ChatMsg = GenericChatMsg<String>;
|
pub type ChatMsg = GenericChatMsg<String>;
|
||||||
@ -183,19 +296,19 @@ impl<G> GenericChatMsg<G> {
|
|||||||
pub const REGION_DISTANCE: f32 = 1000.0;
|
pub const REGION_DISTANCE: f32 = 1000.0;
|
||||||
pub const SAY_DISTANCE: f32 = 100.0;
|
pub const SAY_DISTANCE: f32 = 100.0;
|
||||||
|
|
||||||
pub fn npc(uid: Uid, message: String) -> Self {
|
pub fn npc(uid: Uid, content: Content) -> Self {
|
||||||
let chat_type = ChatType::Npc(uid, rand::random());
|
let chat_type = ChatType::Npc(uid);
|
||||||
Self { chat_type, message }
|
Self { chat_type, content }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn npc_say(uid: Uid, message: String) -> Self {
|
pub fn npc_say(uid: Uid, content: Content) -> Self {
|
||||||
let chat_type = ChatType::NpcSay(uid, rand::random());
|
let chat_type = ChatType::NpcSay(uid);
|
||||||
Self { chat_type, message }
|
Self { chat_type, content }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn npc_tell(from: Uid, to: Uid, message: String) -> Self {
|
pub fn npc_tell(from: Uid, to: Uid, content: Content) -> Self {
|
||||||
let chat_type = ChatType::NpcTell(from, to, rand::random());
|
let chat_type = ChatType::NpcTell(from, to);
|
||||||
Self { chat_type, message }
|
Self { chat_type, content }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
|
pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
|
||||||
@ -213,15 +326,15 @@ impl<G> GenericChatMsg<G> {
|
|||||||
ChatType::Faction(a, b) => ChatType::Faction(a, b),
|
ChatType::Faction(a, b) => ChatType::Faction(a, b),
|
||||||
ChatType::Region(a) => ChatType::Region(a),
|
ChatType::Region(a) => ChatType::Region(a),
|
||||||
ChatType::World(a) => ChatType::World(a),
|
ChatType::World(a) => ChatType::World(a),
|
||||||
ChatType::Npc(a, b) => ChatType::Npc(a, b),
|
ChatType::Npc(a) => ChatType::Npc(a),
|
||||||
ChatType::NpcSay(a, b) => ChatType::NpcSay(a, b),
|
ChatType::NpcSay(a) => ChatType::NpcSay(a),
|
||||||
ChatType::NpcTell(a, b, c) => ChatType::NpcTell(a, b, c),
|
ChatType::NpcTell(a, b) => ChatType::NpcTell(a, b),
|
||||||
ChatType::Meta => ChatType::Meta,
|
ChatType::Meta => ChatType::Meta,
|
||||||
};
|
};
|
||||||
|
|
||||||
GenericChatMsg {
|
GenericChatMsg {
|
||||||
chat_type,
|
chat_type,
|
||||||
message: self.message,
|
content: self.content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,15 +347,8 @@ impl<G> GenericChatMsg<G> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
|
pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
|
||||||
let icon = self.icon();
|
self.uid()
|
||||||
if let ChatType::Npc(from, r) | ChatType::NpcSay(from, r) | ChatType::NpcTell(from, _, r) =
|
.map(|from| (SpeechBubble::new(self.content.clone(), self.icon()), from))
|
||||||
self.chat_type
|
|
||||||
{
|
|
||||||
Some((SpeechBubble::npc_new(&self.message, r, icon), from))
|
|
||||||
} else {
|
|
||||||
self.uid()
|
|
||||||
.map(|from| (SpeechBubble::player_new(&self.message, icon), from))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn icon(&self) -> SpeechBubbleType {
|
pub fn icon(&self) -> SpeechBubbleType {
|
||||||
@ -260,14 +366,20 @@ impl<G> GenericChatMsg<G> {
|
|||||||
ChatType::Faction(_u, _s) => SpeechBubbleType::Faction,
|
ChatType::Faction(_u, _s) => SpeechBubbleType::Faction,
|
||||||
ChatType::Region(_u) => SpeechBubbleType::Region,
|
ChatType::Region(_u) => SpeechBubbleType::Region,
|
||||||
ChatType::World(_u) => SpeechBubbleType::World,
|
ChatType::World(_u) => SpeechBubbleType::World,
|
||||||
ChatType::Npc(_u, _r) => SpeechBubbleType::None,
|
ChatType::Npc(_u) => SpeechBubbleType::None,
|
||||||
ChatType::NpcSay(_u, _r) => SpeechBubbleType::Say,
|
ChatType::NpcSay(_u) => SpeechBubbleType::Say,
|
||||||
ChatType::NpcTell(_f, _t, _) => SpeechBubbleType::Say,
|
ChatType::NpcTell(_f, _t) => SpeechBubbleType::Say,
|
||||||
ChatType::Meta => SpeechBubbleType::None,
|
ChatType::Meta => SpeechBubbleType::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uid(&self) -> Option<Uid> { self.chat_type.uid() }
|
pub fn uid(&self) -> Option<Uid> { self.chat_type.uid() }
|
||||||
|
|
||||||
|
pub fn content(&self) -> &Content { &self.content }
|
||||||
|
|
||||||
|
pub fn into_content(self) -> Content { self.content }
|
||||||
|
|
||||||
|
pub fn set_content(&mut self, content: Content) { self.content = content; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Player factions are used to coordinate pvp vs hostile factions or segment
|
/// Player factions are used to coordinate pvp vs hostile factions or segment
|
||||||
@ -283,15 +395,6 @@ impl From<String> for Faction {
|
|||||||
fn from(s: String) -> Self { Faction(s) }
|
fn from(s: String) -> Self { Faction(s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The contents of a speech bubble
|
|
||||||
pub enum SpeechBubbleMessage {
|
|
||||||
/// This message was said by a player and needs no translation
|
|
||||||
Plain(String),
|
|
||||||
/// This message was said by an NPC. The fields are a i18n key and a random
|
|
||||||
/// u16 index
|
|
||||||
Localized(String, u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of chat types for players and NPCs. Each one has its own icon.
|
/// List of chat types for players and NPCs. Each one has its own icon.
|
||||||
///
|
///
|
||||||
/// This is a subset of `ChatType`, and a superset of `ChatMode`
|
/// This is a subset of `ChatType`, and a superset of `ChatMode`
|
||||||
@ -311,7 +414,7 @@ pub enum SpeechBubbleType {
|
|||||||
|
|
||||||
/// Adds a speech bubble above the character
|
/// Adds a speech bubble above the character
|
||||||
pub struct SpeechBubble {
|
pub struct SpeechBubble {
|
||||||
pub message: SpeechBubbleMessage,
|
pub content: Content,
|
||||||
pub icon: SpeechBubbleType,
|
pub icon: SpeechBubbleType,
|
||||||
pub timeout: Instant,
|
pub timeout: Instant,
|
||||||
}
|
}
|
||||||
@ -320,33 +423,14 @@ impl SpeechBubble {
|
|||||||
/// Default duration in seconds of speech bubbles
|
/// Default duration in seconds of speech bubbles
|
||||||
pub const DEFAULT_DURATION: f64 = 5.0;
|
pub const DEFAULT_DURATION: f64 = 5.0;
|
||||||
|
|
||||||
pub fn npc_new(i18n_key: &str, r: u16, icon: SpeechBubbleType) -> Self {
|
pub fn new(content: Content, icon: SpeechBubbleType) -> Self {
|
||||||
let message = SpeechBubbleMessage::Localized(i18n_key.to_string(), r);
|
|
||||||
let timeout = Instant::now() + Duration::from_secs_f64(SpeechBubble::DEFAULT_DURATION);
|
let timeout = Instant::now() + Duration::from_secs_f64(SpeechBubble::DEFAULT_DURATION);
|
||||||
Self {
|
Self {
|
||||||
message,
|
content,
|
||||||
icon,
|
icon,
|
||||||
timeout,
|
timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn player_new(message: &str, icon: SpeechBubbleType) -> Self {
|
pub fn content(&self) -> &Content { &self.content }
|
||||||
let message = SpeechBubbleMessage::Plain(message.to_string());
|
|
||||||
let timeout = Instant::now() + Duration::from_secs_f64(SpeechBubble::DEFAULT_DURATION);
|
|
||||||
Self {
|
|
||||||
message,
|
|
||||||
icon,
|
|
||||||
timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn message<F>(&self, i18n_variation: F) -> String
|
|
||||||
where
|
|
||||||
F: Fn(&str, u16) -> String,
|
|
||||||
{
|
|
||||||
match &self.message {
|
|
||||||
SpeechBubbleMessage::Plain(m) => m.to_string(),
|
|
||||||
SpeechBubbleMessage::Localized(k, i) => i18n_variation(k, *i),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use vek::Vec2;
|
use vek::Vec2;
|
||||||
|
// TODO: Move this to common/src/, it's not a component
|
||||||
|
|
||||||
/// Cardinal directions
|
/// Cardinal directions
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
|
@ -135,6 +135,7 @@ impl Body {
|
|||||||
rel_flow: &Vel,
|
rel_flow: &Vel,
|
||||||
fluid_density: f32,
|
fluid_density: f32,
|
||||||
wings: Option<&Wings>,
|
wings: Option<&Wings>,
|
||||||
|
scale: f32,
|
||||||
) -> Vec3<f32> {
|
) -> Vec3<f32> {
|
||||||
let v_sq = rel_flow.0.magnitude_squared();
|
let v_sq = rel_flow.0.magnitude_squared();
|
||||||
if v_sq < 0.25 {
|
if v_sq < 0.25 {
|
||||||
@ -201,11 +202,11 @@ impl Body {
|
|||||||
debug_assert!(c_d.is_sign_positive());
|
debug_assert!(c_d.is_sign_positive());
|
||||||
debug_assert!(c_l.is_sign_positive() || aoa.is_sign_negative());
|
debug_assert!(c_l.is_sign_positive() || aoa.is_sign_negative());
|
||||||
|
|
||||||
planform_area * (c_l * *lift_dir + c_d * *rel_flow_dir)
|
planform_area * scale.powf(2.0) * (c_l * *lift_dir + c_d * *rel_flow_dir)
|
||||||
+ self.parasite_drag() * *rel_flow_dir
|
+ self.parasite_drag(scale) * *rel_flow_dir
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => self.parasite_drag() * *rel_flow_dir,
|
_ => self.parasite_drag(scale) * *rel_flow_dir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,13 +215,13 @@ impl Body {
|
|||||||
/// Skin friction is the drag arising from the shear forces between a fluid
|
/// Skin friction is the drag arising from the shear forces between a fluid
|
||||||
/// and a surface, while pressure drag is due to flow separation. Both are
|
/// and a surface, while pressure drag is due to flow separation. Both are
|
||||||
/// viscous effects.
|
/// viscous effects.
|
||||||
fn parasite_drag(&self) -> f32 {
|
fn parasite_drag(&self, scale: f32) -> f32 {
|
||||||
// Reference area and drag coefficient assumes best-case scenario of the
|
// Reference area and drag coefficient assumes best-case scenario of the
|
||||||
// orientation producing least amount of drag
|
// orientation producing least amount of drag
|
||||||
match self {
|
match self {
|
||||||
// Cross-section, head/feet first
|
// Cross-section, head/feet first
|
||||||
Body::BipedLarge(_) | Body::BipedSmall(_) | Body::Golem(_) | Body::Humanoid(_) => {
|
Body::BipedLarge(_) | Body::BipedSmall(_) | Body::Golem(_) | Body::Humanoid(_) => {
|
||||||
let dim = self.dimensions().xy().map(|a| a * 0.5);
|
let dim = self.dimensions().xy().map(|a| a * 0.5 * scale);
|
||||||
const CD: f32 = 0.7;
|
const CD: f32 = 0.7;
|
||||||
CD * PI * dim.x * dim.y
|
CD * PI * dim.x * dim.y
|
||||||
},
|
},
|
||||||
@ -231,7 +232,7 @@ impl Body {
|
|||||||
| Body::QuadrupedSmall(_)
|
| Body::QuadrupedSmall(_)
|
||||||
| Body::QuadrupedLow(_)
|
| Body::QuadrupedLow(_)
|
||||||
| Body::Arthropod(_) => {
|
| Body::Arthropod(_) => {
|
||||||
let dim = self.dimensions().map(|a| a * 0.5);
|
let dim = self.dimensions().map(|a| a * 0.5 * scale);
|
||||||
let cd: f32 = if matches!(self, Body::QuadrupedLow(_)) {
|
let cd: f32 = if matches!(self, Body::QuadrupedLow(_)) {
|
||||||
0.7
|
0.7
|
||||||
} else {
|
} else {
|
||||||
@ -242,7 +243,7 @@ impl Body {
|
|||||||
|
|
||||||
// Cross-section, zero-lift angle; exclude the wings (width * 0.2)
|
// Cross-section, zero-lift angle; exclude the wings (width * 0.2)
|
||||||
Body::BirdMedium(_) | Body::BirdLarge(_) | Body::Dragon(_) => {
|
Body::BirdMedium(_) | Body::BirdLarge(_) | Body::Dragon(_) => {
|
||||||
let dim = self.dimensions().map(|a| a * 0.5);
|
let dim = self.dimensions().map(|a| a * 0.5 * scale);
|
||||||
let cd: f32 = match self {
|
let cd: f32 = match self {
|
||||||
// "Field Estimates of Body Drag Coefficient
|
// "Field Estimates of Body Drag Coefficient
|
||||||
// on the Basis of Dives in Passerine Birds",
|
// on the Basis of Dives in Passerine Birds",
|
||||||
@ -256,7 +257,7 @@ impl Body {
|
|||||||
|
|
||||||
// Cross-section, zero-lift angle; exclude the fins (width * 0.2)
|
// Cross-section, zero-lift angle; exclude the fins (width * 0.2)
|
||||||
Body::FishMedium(_) | Body::FishSmall(_) => {
|
Body::FishMedium(_) | Body::FishSmall(_) => {
|
||||||
let dim = self.dimensions().map(|a| a * 0.5);
|
let dim = self.dimensions().map(|a| a * 0.5 * scale);
|
||||||
// "A Simple Method to Determine Drag Coefficients in Aquatic Animals",
|
// "A Simple Method to Determine Drag Coefficients in Aquatic Animals",
|
||||||
// D. Bilo and W. Nachtigall, 1980
|
// D. Bilo and W. Nachtigall, 1980
|
||||||
const CD: f32 = 0.031;
|
const CD: f32 = 0.031;
|
||||||
@ -276,7 +277,7 @@ impl Body {
|
|||||||
| object::Body::FireworkYellow
|
| object::Body::FireworkYellow
|
||||||
| object::Body::MultiArrow
|
| object::Body::MultiArrow
|
||||||
| object::Body::Dart => {
|
| object::Body::Dart => {
|
||||||
let dim = self.dimensions().map(|a| a * 0.5);
|
let dim = self.dimensions().map(|a| a * 0.5 * scale);
|
||||||
const CD: f32 = 0.02;
|
const CD: f32 = 0.02;
|
||||||
CD * PI * dim.x * dim.z
|
CD * PI * dim.x * dim.z
|
||||||
},
|
},
|
||||||
@ -295,20 +296,20 @@ impl Body {
|
|||||||
| object::Body::Pumpkin3
|
| object::Body::Pumpkin3
|
||||||
| object::Body::Pumpkin4
|
| object::Body::Pumpkin4
|
||||||
| object::Body::Pumpkin5 => {
|
| object::Body::Pumpkin5 => {
|
||||||
let dim = self.dimensions().map(|a| a * 0.5);
|
let dim = self.dimensions().map(|a| a * 0.5 * scale);
|
||||||
const CD: f32 = 0.5;
|
const CD: f32 = 0.5;
|
||||||
CD * PI * dim.x * dim.z
|
CD * PI * dim.x * dim.z
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
let dim = self.dimensions();
|
let dim = self.dimensions().map(|a| a * scale);
|
||||||
const CD: f32 = 2.0;
|
const CD: f32 = 2.0;
|
||||||
CD * (PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
CD * (PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Body::ItemDrop(_) => {
|
Body::ItemDrop(_) => {
|
||||||
let dim = self.dimensions();
|
let dim = self.dimensions().map(|a| a * scale);
|
||||||
const CD: f32 = 2.0;
|
const CD: f32 = 2.0;
|
||||||
CD * (PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
CD * (PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
||||||
},
|
},
|
||||||
@ -316,7 +317,7 @@ impl Body {
|
|||||||
Body::Ship(_) => {
|
Body::Ship(_) => {
|
||||||
// Airships tend to use the square of the cube root of its volume for
|
// Airships tend to use the square of the cube root of its volume for
|
||||||
// reference area
|
// reference area
|
||||||
let dim = self.dimensions();
|
let dim = self.dimensions().map(|a| a * scale);
|
||||||
(PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
(PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -366,6 +366,10 @@ impl<T> AbilityKind<T> {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq, Hash)]
|
||||||
pub enum AbilityContext {
|
pub enum AbilityContext {
|
||||||
|
/// Note, in this context `Stance::None` isn't intended to be used. e.g.
|
||||||
|
/// `AbilityContext::None` should always be used instead of
|
||||||
|
/// `AbilityContext::Stance(Stance::None)` in the ability map config
|
||||||
|
/// files(s).
|
||||||
Stance(Stance),
|
Stance(Stance),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
@ -910,13 +910,16 @@ impl LoadoutBuilder {
|
|||||||
Some("common.items.npc_armor.biped_large.harvester")
|
Some("common.items.npc_armor.biped_large.harvester")
|
||||||
},
|
},
|
||||||
biped_large::Species::Ogre
|
biped_large::Species::Ogre
|
||||||
| biped_large::Species::Cyclops
|
|
||||||
| biped_large::Species::Blueoni
|
| biped_large::Species::Blueoni
|
||||||
| biped_large::Species::Redoni
|
| biped_large::Species::Redoni
|
||||||
| biped_large::Species::Cavetroll
|
| biped_large::Species::Cavetroll
|
||||||
| biped_large::Species::Wendigo => {
|
| biped_large::Species::Wendigo => {
|
||||||
Some("common.items.npc_armor.biped_large.generic")
|
Some("common.items.npc_armor.biped_large.generic")
|
||||||
},
|
},
|
||||||
|
biped_large::Species::Cyclops => Some("common.items.npc_armor.biped_large.cyclops"),
|
||||||
|
biped_large::Species::Dullahan => {
|
||||||
|
Some("common.items.npc_armor.biped_large.dullahan")
|
||||||
|
},
|
||||||
biped_large::Species::Cultistwarlord => {
|
biped_large::Species::Cultistwarlord => {
|
||||||
Some("common.items.npc_armor.biped_large.warlord")
|
Some("common.items.npc_armor.biped_large.warlord")
|
||||||
},
|
},
|
||||||
|
@ -53,7 +53,9 @@ impl Component for Melee {
|
|||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct MeleeConstructor {
|
pub struct MeleeConstructor {
|
||||||
pub kind: MeleeConstructorKind,
|
pub kind: MeleeConstructorKind,
|
||||||
// This multiplied by a fraction is added to what is specified in kind
|
/// This multiplied by a fraction is added to what is specified in `kind`.
|
||||||
|
///
|
||||||
|
/// Note, that this must be the same variant as what is specified in `kind`.
|
||||||
pub scaled: Option<MeleeConstructorKind>,
|
pub scaled: Option<MeleeConstructorKind>,
|
||||||
pub range: f32,
|
pub range: f32,
|
||||||
pub angle: f32,
|
pub angle: f32,
|
||||||
|
@ -38,6 +38,8 @@ pub mod loot_owner;
|
|||||||
#[cfg(not(target_arch = "wasm32"))] mod player;
|
#[cfg(not(target_arch = "wasm32"))] mod player;
|
||||||
#[cfg(not(target_arch = "wasm32"))] pub mod poise;
|
#[cfg(not(target_arch = "wasm32"))] pub mod poise;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub mod presence;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod projectile;
|
pub mod projectile;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod shockwave;
|
pub mod shockwave;
|
||||||
@ -71,9 +73,10 @@ pub use self::{
|
|||||||
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
|
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
|
||||||
ModifierKind,
|
ModifierKind,
|
||||||
},
|
},
|
||||||
character_state::{CharacterState, StateUpdate},
|
character_state::{CharacterActivity, CharacterState, StateUpdate},
|
||||||
chat::{
|
chat::{
|
||||||
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
|
ChatMode, ChatMsg, ChatType, Content, Faction, LocalizationArg, SpeechBubble,
|
||||||
|
SpeechBubbleType, UnresolvedChatMsg,
|
||||||
},
|
},
|
||||||
combo::Combo,
|
combo::Combo,
|
||||||
controller::{
|
controller::{
|
||||||
@ -107,6 +110,7 @@ pub use self::{
|
|||||||
player::DisconnectReason,
|
player::DisconnectReason,
|
||||||
player::{AliasError, Player, MAX_ALIAS_LEN},
|
player::{AliasError, Player, MAX_ALIAS_LEN},
|
||||||
poise::{Poise, PoiseChange, PoiseState},
|
poise::{Poise, PoiseChange, PoiseState},
|
||||||
|
presence::{Presence, PresenceKind},
|
||||||
projectile::{Projectile, ProjectileConstructor},
|
projectile::{Projectile, ProjectileConstructor},
|
||||||
shockwave::{Shockwave, ShockwaveHitEntities},
|
shockwave::{Shockwave, ShockwaveHitEntities},
|
||||||
skillset::{
|
skillset::{
|
||||||
|
@ -94,6 +94,7 @@ pub fn is_mountable(mount: &Body, rider: Option<&Body>) -> bool {
|
|||||||
| quadruped_low::Species::Elbst
|
| quadruped_low::Species::Elbst
|
||||||
| quadruped_low::Species::Tortoise
|
| quadruped_low::Species::Tortoise
|
||||||
),
|
),
|
||||||
|
Body::Ship(_) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
128
common/src/comp/presence.rs
Normal file
128
common/src/comp/presence.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
use crate::{character::CharacterId, ViewDistances};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specs::Component;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Presence {
|
||||||
|
pub terrain_view_distance: ViewDistance,
|
||||||
|
pub entity_view_distance: ViewDistance,
|
||||||
|
pub kind: PresenceKind,
|
||||||
|
pub lossy_terrain_compression: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Presence {
|
||||||
|
pub fn new(view_distances: ViewDistances, kind: PresenceKind) -> Self {
|
||||||
|
let now = Instant::now();
|
||||||
|
Self {
|
||||||
|
terrain_view_distance: ViewDistance::new(view_distances.terrain, now),
|
||||||
|
entity_view_distance: ViewDistance::new(view_distances.entity, now),
|
||||||
|
kind,
|
||||||
|
lossy_terrain_compression: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Presence {
|
||||||
|
type Storage = specs::DenseVecStorage<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum PresenceKind {
|
||||||
|
Spectator,
|
||||||
|
Character(CharacterId),
|
||||||
|
Possessor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PresenceKind {
|
||||||
|
/// Check if the presence represents a control of a character, and thus
|
||||||
|
/// certain in-game messages from the client such as control inputs
|
||||||
|
/// should be handled.
|
||||||
|
pub fn controlling_char(&self) -> bool { matches!(self, Self::Character(_) | Self::Possessor) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||||
|
enum Direction {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Distance from the [Presence] from which the world is loaded and information
|
||||||
|
/// is synced to clients.
|
||||||
|
///
|
||||||
|
/// We limit the frequency that changes in the view distance change direction
|
||||||
|
/// (e.g. shifting from increasing the value to decreasing it). This is useful
|
||||||
|
/// since we want to avoid rapid cycles of shrinking and expanding of the view
|
||||||
|
/// distance.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ViewDistance {
|
||||||
|
direction: Direction,
|
||||||
|
last_direction_change_time: Instant,
|
||||||
|
target: Option<u32>,
|
||||||
|
current: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewDistance {
|
||||||
|
/// Minimum time allowed between changes in direction of value adjustments.
|
||||||
|
const TIME_PER_DIR_CHANGE: Duration = Duration::from_millis(300);
|
||||||
|
|
||||||
|
pub fn new(start_value: u32, now: Instant) -> Self {
|
||||||
|
Self {
|
||||||
|
direction: Direction::Up,
|
||||||
|
last_direction_change_time: now.checked_sub(Self::TIME_PER_DIR_CHANGE).unwrap_or(now),
|
||||||
|
target: None,
|
||||||
|
current: start_value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current value.
|
||||||
|
pub fn current(&self) -> u32 { self.current }
|
||||||
|
|
||||||
|
/// Applies deferred change based on the whether the time to apply it has
|
||||||
|
/// been reached.
|
||||||
|
pub fn update(&mut self, now: Instant) {
|
||||||
|
if let Some(target_val) = self.target {
|
||||||
|
if now.saturating_duration_since(self.last_direction_change_time)
|
||||||
|
> Self::TIME_PER_DIR_CHANGE
|
||||||
|
{
|
||||||
|
self.last_direction_change_time = now;
|
||||||
|
self.current = target_val;
|
||||||
|
self.target = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the target value.
|
||||||
|
///
|
||||||
|
/// If this hasn't been changed recently or it is in the same direction as
|
||||||
|
/// the previous change it will be applied immediately. Otherwise, it
|
||||||
|
/// will be deferred to a later time (limiting the frequency of changes
|
||||||
|
/// in the change direction).
|
||||||
|
pub fn set_target(&mut self, new_target: u32, now: Instant) {
|
||||||
|
use core::cmp::Ordering;
|
||||||
|
let new_direction = match new_target.cmp(&self.current) {
|
||||||
|
Ordering::Equal => return, // No change needed.
|
||||||
|
Ordering::Less => Direction::Down,
|
||||||
|
Ordering::Greater => Direction::Up,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Change is in the same direction as before so we can just apply it.
|
||||||
|
if new_direction == self.direction {
|
||||||
|
self.current = new_target;
|
||||||
|
self.target = None;
|
||||||
|
// If it has already been a while since the last direction change we can
|
||||||
|
// directly apply the request and switch the direction.
|
||||||
|
} else if now.saturating_duration_since(self.last_direction_change_time)
|
||||||
|
> Self::TIME_PER_DIR_CHANGE
|
||||||
|
{
|
||||||
|
self.direction = new_direction;
|
||||||
|
self.last_direction_change_time = now;
|
||||||
|
self.current = new_target;
|
||||||
|
self.target = None;
|
||||||
|
// Otherwise, we need to defer the request.
|
||||||
|
} else {
|
||||||
|
self.target = Some(new_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,6 +53,11 @@ pub enum ProjectileConstructor {
|
|||||||
knockback: f32,
|
knockback: f32,
|
||||||
energy_regen: f32,
|
energy_regen: f32,
|
||||||
},
|
},
|
||||||
|
Knife {
|
||||||
|
damage: f32,
|
||||||
|
knockback: f32,
|
||||||
|
energy_regen: f32,
|
||||||
|
},
|
||||||
Fireball {
|
Fireball {
|
||||||
damage: f32,
|
damage: f32,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
@ -114,6 +119,12 @@ pub enum ProjectileConstructor {
|
|||||||
knockback: f32,
|
knockback: f32,
|
||||||
min_falloff: f32,
|
min_falloff: f32,
|
||||||
},
|
},
|
||||||
|
LaserBeam {
|
||||||
|
damage: f32,
|
||||||
|
radius: f32,
|
||||||
|
knockback: f32,
|
||||||
|
min_falloff: f32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectileConstructor {
|
impl ProjectileConstructor {
|
||||||
@ -181,6 +192,59 @@ impl ProjectileConstructor {
|
|||||||
is_point: true,
|
is_point: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Knife {
|
||||||
|
damage,
|
||||||
|
knockback,
|
||||||
|
energy_regen,
|
||||||
|
} => {
|
||||||
|
let knockback = AttackEffect::new(
|
||||||
|
Some(GroupTarget::OutOfGroup),
|
||||||
|
CombatEffect::Knockback(Knockback {
|
||||||
|
strength: knockback,
|
||||||
|
direction: KnockbackDir::Away,
|
||||||
|
})
|
||||||
|
.adjusted_by_stats(tool_stats),
|
||||||
|
)
|
||||||
|
.with_requirement(CombatRequirement::AnyDamage);
|
||||||
|
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
|
||||||
|
.with_requirement(CombatRequirement::AnyDamage);
|
||||||
|
let buff = CombatEffect::Buff(CombatBuff {
|
||||||
|
kind: BuffKind::Bleeding,
|
||||||
|
dur_secs: 10.0,
|
||||||
|
strength: CombatBuffStrength::DamageFraction(0.1),
|
||||||
|
chance: 0.1,
|
||||||
|
})
|
||||||
|
.adjusted_by_stats(tool_stats);
|
||||||
|
let mut damage = AttackDamage::new(
|
||||||
|
Damage {
|
||||||
|
source: DamageSource::Projectile,
|
||||||
|
kind: DamageKind::Piercing,
|
||||||
|
value: damage,
|
||||||
|
},
|
||||||
|
Some(GroupTarget::OutOfGroup),
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
.with_effect(buff);
|
||||||
|
if let Some(damage_effect) = damage_effect {
|
||||||
|
damage = damage.with_effect(damage_effect);
|
||||||
|
}
|
||||||
|
let attack = Attack::default()
|
||||||
|
.with_damage(damage)
|
||||||
|
.with_crit(crit_chance, crit_mult)
|
||||||
|
.with_effect(energy)
|
||||||
|
.with_effect(knockback)
|
||||||
|
.with_combo_increment();
|
||||||
|
|
||||||
|
Projectile {
|
||||||
|
hit_solid: vec![Effect::Stick, Effect::Bonk],
|
||||||
|
hit_entity: vec![Effect::Attack(attack), Effect::Vanish],
|
||||||
|
time_left: Duration::from_secs(15),
|
||||||
|
owner,
|
||||||
|
ignore_group: true,
|
||||||
|
is_sticky: true,
|
||||||
|
is_point: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
Fireball {
|
Fireball {
|
||||||
damage,
|
damage,
|
||||||
radius,
|
radius,
|
||||||
@ -679,6 +743,53 @@ impl ProjectileConstructor {
|
|||||||
is_point: true,
|
is_point: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
LaserBeam {
|
||||||
|
damage,
|
||||||
|
radius,
|
||||||
|
knockback,
|
||||||
|
min_falloff,
|
||||||
|
} => {
|
||||||
|
let knockback = AttackEffect::new(
|
||||||
|
Some(GroupTarget::OutOfGroup),
|
||||||
|
CombatEffect::Knockback(Knockback {
|
||||||
|
strength: knockback,
|
||||||
|
direction: KnockbackDir::Away,
|
||||||
|
})
|
||||||
|
.adjusted_by_stats(tool_stats),
|
||||||
|
)
|
||||||
|
.with_requirement(CombatRequirement::AnyDamage);
|
||||||
|
let damage = AttackDamage::new(
|
||||||
|
Damage {
|
||||||
|
source: DamageSource::Explosion,
|
||||||
|
kind: DamageKind::Energy,
|
||||||
|
value: damage,
|
||||||
|
},
|
||||||
|
Some(GroupTarget::OutOfGroup),
|
||||||
|
instance,
|
||||||
|
);
|
||||||
|
let attack = Attack::default()
|
||||||
|
.with_damage(damage)
|
||||||
|
.with_crit(crit_chance, crit_mult)
|
||||||
|
.with_effect(knockback);
|
||||||
|
let explosion = Explosion {
|
||||||
|
effects: vec![
|
||||||
|
RadiusEffect::Attack(attack),
|
||||||
|
RadiusEffect::TerrainDestruction(10.0, Rgb::black()),
|
||||||
|
],
|
||||||
|
radius,
|
||||||
|
reagent: Some(Reagent::Yellow),
|
||||||
|
min_falloff,
|
||||||
|
};
|
||||||
|
Projectile {
|
||||||
|
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
|
||||||
|
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||||
|
time_left: Duration::from_secs(10),
|
||||||
|
owner,
|
||||||
|
ignore_group: true,
|
||||||
|
is_sticky: true,
|
||||||
|
is_point: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,6 +806,14 @@ impl ProjectileConstructor {
|
|||||||
*damage *= power;
|
*damage *= power;
|
||||||
*energy_regen *= regen;
|
*energy_regen *= regen;
|
||||||
},
|
},
|
||||||
|
Knife {
|
||||||
|
ref mut damage,
|
||||||
|
ref mut energy_regen,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
*damage *= power;
|
||||||
|
*energy_regen *= regen;
|
||||||
|
},
|
||||||
Fireball {
|
Fireball {
|
||||||
ref mut damage,
|
ref mut damage,
|
||||||
ref mut energy_regen,
|
ref mut energy_regen,
|
||||||
@ -786,6 +905,14 @@ impl ProjectileConstructor {
|
|||||||
*damage *= power;
|
*damage *= power;
|
||||||
*radius *= range;
|
*radius *= range;
|
||||||
},
|
},
|
||||||
|
LaserBeam {
|
||||||
|
ref mut damage,
|
||||||
|
ref mut radius,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
*damage *= power;
|
||||||
|
*radius *= range;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -794,6 +921,7 @@ impl ProjectileConstructor {
|
|||||||
use ProjectileConstructor::*;
|
use ProjectileConstructor::*;
|
||||||
match self {
|
match self {
|
||||||
Arrow { .. } => false,
|
Arrow { .. } => false,
|
||||||
|
Knife { .. } => false,
|
||||||
Fireball { .. } => true,
|
Fireball { .. } => true,
|
||||||
Frostball { .. } => true,
|
Frostball { .. } => true,
|
||||||
Poisonball { .. } => true,
|
Poisonball { .. } => true,
|
||||||
@ -806,6 +934,7 @@ impl ProjectileConstructor {
|
|||||||
SeaBomb { .. } => true,
|
SeaBomb { .. } => true,
|
||||||
WindBomb { .. } => true,
|
WindBomb { .. } => true,
|
||||||
IceBomb { .. } => true,
|
IceBomb { .. } => true,
|
||||||
|
LaserBeam { .. } => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
lottery::LootSpec,
|
lottery::LootSpec,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
rtsim::RtSimEntity,
|
rtsim::{RtSimEntity, RtSimVehicle},
|
||||||
terrain::SpriteKind,
|
terrain::SpriteKind,
|
||||||
trade::{TradeAction, TradeId},
|
trade::{TradeAction, TradeId},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
@ -42,6 +42,92 @@ pub struct UpdateCharacterMetadata {
|
|||||||
pub skill_set_persistence_load_error: Option<comp::skillset::SkillsPersistenceError>,
|
pub skill_set_persistence_load_error: Option<comp::skillset::SkillsPersistenceError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct NpcBuilder {
|
||||||
|
pub stats: comp::Stats,
|
||||||
|
pub skill_set: comp::SkillSet,
|
||||||
|
pub health: Option<comp::Health>,
|
||||||
|
pub poise: comp::Poise,
|
||||||
|
pub inventory: comp::inventory::Inventory,
|
||||||
|
pub body: comp::Body,
|
||||||
|
pub agent: Option<comp::Agent>,
|
||||||
|
pub alignment: comp::Alignment,
|
||||||
|
pub scale: comp::Scale,
|
||||||
|
pub anchor: Option<comp::Anchor>,
|
||||||
|
pub loot: LootSpec<String>,
|
||||||
|
pub rtsim_entity: Option<RtSimEntity>,
|
||||||
|
pub projectile: Option<comp::Projectile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NpcBuilder {
|
||||||
|
pub fn new(stats: comp::Stats, body: comp::Body, alignment: comp::Alignment) -> Self {
|
||||||
|
Self {
|
||||||
|
stats,
|
||||||
|
skill_set: comp::SkillSet::default(),
|
||||||
|
health: None,
|
||||||
|
poise: comp::Poise::new(body),
|
||||||
|
inventory: comp::Inventory::with_empty(),
|
||||||
|
body,
|
||||||
|
agent: None,
|
||||||
|
alignment,
|
||||||
|
scale: comp::Scale(1.0),
|
||||||
|
anchor: None,
|
||||||
|
loot: LootSpec::Nothing,
|
||||||
|
rtsim_entity: None,
|
||||||
|
projectile: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_health(mut self, health: impl Into<Option<comp::Health>>) -> Self {
|
||||||
|
self.health = health.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_poise(mut self, poise: comp::Poise) -> Self {
|
||||||
|
self.poise = poise;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_agent(mut self, agent: impl Into<Option<comp::Agent>>) -> Self {
|
||||||
|
self.agent = agent.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_anchor(mut self, anchor: comp::Anchor) -> Self {
|
||||||
|
self.anchor = Some(anchor);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_rtsim(mut self, rtsim: RtSimEntity) -> Self {
|
||||||
|
self.rtsim_entity = Some(rtsim);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_projectile(mut self, projectile: impl Into<Option<comp::Projectile>>) -> Self {
|
||||||
|
self.projectile = projectile.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_scale(mut self, scale: comp::Scale) -> Self {
|
||||||
|
self.scale = scale;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_inventory(mut self, inventory: comp::Inventory) -> Self {
|
||||||
|
self.inventory = inventory;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_skill_set(mut self, skill_set: comp::SkillSet) -> Self {
|
||||||
|
self.skill_set = skill_set;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_loot(mut self, loot: LootSpec<String>) -> Self {
|
||||||
|
self.loot = loot;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)] // TODO: Pending review in #587
|
#[allow(clippy::large_enum_variant)] // TODO: Pending review in #587
|
||||||
#[derive(strum::EnumDiscriminants)]
|
#[derive(strum::EnumDiscriminants)]
|
||||||
#[strum_discriminants(repr(usize))]
|
#[strum_discriminants(repr(usize))]
|
||||||
@ -137,26 +223,13 @@ pub enum ServerEvent {
|
|||||||
// TODO: to avoid breakage when adding new fields, perhaps have an `NpcBuilder` type?
|
// TODO: to avoid breakage when adding new fields, perhaps have an `NpcBuilder` type?
|
||||||
CreateNpc {
|
CreateNpc {
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
stats: comp::Stats,
|
npc: NpcBuilder,
|
||||||
skill_set: comp::SkillSet,
|
|
||||||
health: Option<comp::Health>,
|
|
||||||
poise: comp::Poise,
|
|
||||||
inventory: comp::inventory::Inventory,
|
|
||||||
body: comp::Body,
|
|
||||||
agent: Option<comp::Agent>,
|
|
||||||
alignment: comp::Alignment,
|
|
||||||
scale: comp::Scale,
|
|
||||||
anchor: Option<comp::Anchor>,
|
|
||||||
loot: LootSpec<String>,
|
|
||||||
rtsim_entity: Option<RtSimEntity>,
|
|
||||||
projectile: Option<comp::Projectile>,
|
|
||||||
},
|
},
|
||||||
CreateShip {
|
CreateShip {
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
ship: comp::ship::Body,
|
ship: comp::ship::Body,
|
||||||
mountable: bool,
|
rtsim_entity: Option<RtSimVehicle>,
|
||||||
agent: Option<comp::Agent>,
|
driver: Option<NpcBuilder>,
|
||||||
rtsim_entity: Option<RtSimEntity>,
|
|
||||||
},
|
},
|
||||||
CreateWaypoint(Vec3<f32>),
|
CreateWaypoint(Vec3<f32>),
|
||||||
ClientDisconnect(EcsEntity, DisconnectReason),
|
ClientDisconnect(EcsEntity, DisconnectReason),
|
||||||
|
@ -7,8 +7,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
lottery::LootSpec,
|
lottery::LootSpec,
|
||||||
npc::{self, NPC_NAMES},
|
npc::{self, NPC_NAMES},
|
||||||
|
rtsim,
|
||||||
trade::SiteInformation,
|
trade::SiteInformation,
|
||||||
};
|
};
|
||||||
|
use enum_map::EnumMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -254,7 +256,7 @@ impl EntityInfo {
|
|||||||
self = self.with_name(name);
|
self = self.with_name(name);
|
||||||
},
|
},
|
||||||
NameKind::Automatic => {
|
NameKind::Automatic => {
|
||||||
self = self.with_automatic_name();
|
self = self.with_automatic_name(None);
|
||||||
},
|
},
|
||||||
NameKind::Uninit => {},
|
NameKind::Uninit => {},
|
||||||
}
|
}
|
||||||
@ -373,8 +375,8 @@ impl EntityInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_agent_mark(mut self, agent_mark: agent::Mark) -> Self {
|
pub fn with_agent_mark(mut self, agent_mark: impl Into<Option<agent::Mark>>) -> Self {
|
||||||
self.agent_mark = Some(agent_mark);
|
self.agent_mark = agent_mark.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,7 +408,7 @@ impl EntityInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_automatic_name(mut self) -> Self {
|
pub fn with_automatic_name(mut self, alias: Option<String>) -> Self {
|
||||||
let npc_names = NPC_NAMES.read();
|
let npc_names = NPC_NAMES.read();
|
||||||
let name = match &self.body {
|
let name = match &self.body {
|
||||||
Body::Humanoid(body) => Some(get_npc_name(&npc_names.humanoid, body.species)),
|
Body::Humanoid(body) => Some(get_npc_name(&npc_names.humanoid, body.species)),
|
||||||
@ -428,14 +430,30 @@ impl EntityInfo {
|
|||||||
Body::Arthropod(body) => Some(get_npc_name(&npc_names.arthropod, body.species)),
|
Body::Arthropod(body) => Some(get_npc_name(&npc_names.arthropod, body.species)),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
self.name = name.map(str::to_owned);
|
self.name = name.map(|name| {
|
||||||
|
if let Some(alias) = alias {
|
||||||
|
format!("{alias} ({name})")
|
||||||
|
} else {
|
||||||
|
name.to_string()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_alias(mut self, alias: String) -> Self {
|
||||||
|
self.name = Some(if let Some(name) = self.name {
|
||||||
|
format!("{alias} ({name})")
|
||||||
|
} else {
|
||||||
|
alias
|
||||||
|
});
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// map contains price+amount
|
/// map contains price+amount
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_economy(mut self, e: &SiteInformation) -> Self {
|
pub fn with_economy<'a>(mut self, e: impl Into<Option<&'a SiteInformation>>) -> Self {
|
||||||
self.trading_information = Some(e.clone());
|
self.trading_information = e.into().cloned();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,11 +462,18 @@ impl EntityInfo {
|
|||||||
self.no_flee = true;
|
self.no_flee = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_loadout(mut self, loadout: LoadoutBuilder) -> Self {
|
||||||
|
self.loadout = loadout;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ChunkSupplement {
|
pub struct ChunkSupplement {
|
||||||
pub entities: Vec<EntityInfo>,
|
pub entities: Vec<EntityInfo>,
|
||||||
|
pub rtsim_max_resources: EnumMap<rtsim::ChunkResource, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChunkSupplement {
|
impl ChunkSupplement {
|
||||||
|
@ -10,6 +10,7 @@ bitflags::bitflags! {
|
|||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Flags: u8 {
|
pub struct Flags: u8 {
|
||||||
const SNOW_COVERED = 0b00000001;
|
const SNOW_COVERED = 0b00000001;
|
||||||
|
const IS_BUILDING = 0b00000010;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp,
|
comp,
|
||||||
comp::{pet::is_mountable, Body},
|
|
||||||
link::{Is, Link, LinkHandle, Role},
|
link::{Is, Link, LinkHandle, Role},
|
||||||
terrain::TerrainGrid,
|
terrain::TerrainGrid,
|
||||||
uid::{Uid, UidAllocator},
|
uid::{Uid, UidAllocator},
|
||||||
@ -29,6 +28,7 @@ pub struct Mounting {
|
|||||||
pub rider: Uid,
|
pub rider: Uid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum MountingError {
|
pub enum MountingError {
|
||||||
NoSuchEntity,
|
NoSuchEntity,
|
||||||
NotMountable,
|
NotMountable,
|
||||||
@ -39,7 +39,6 @@ impl Link for Mounting {
|
|||||||
Read<'a, UidAllocator>,
|
Read<'a, UidAllocator>,
|
||||||
WriteStorage<'a, Is<Mount>>,
|
WriteStorage<'a, Is<Mount>>,
|
||||||
WriteStorage<'a, Is<Rider>>,
|
WriteStorage<'a, Is<Rider>>,
|
||||||
WriteStorage<'a, Body>,
|
|
||||||
);
|
);
|
||||||
type DeleteData<'a> = (
|
type DeleteData<'a> = (
|
||||||
Read<'a, UidAllocator>,
|
Read<'a, UidAllocator>,
|
||||||
@ -60,7 +59,7 @@ impl Link for Mounting {
|
|||||||
|
|
||||||
fn create(
|
fn create(
|
||||||
this: &LinkHandle<Self>,
|
this: &LinkHandle<Self>,
|
||||||
(uid_allocator, mut is_mounts, mut is_riders, body): Self::CreateData<'_>,
|
(uid_allocator, mut is_mounts, mut is_riders): Self::CreateData<'_>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||||
|
|
||||||
@ -68,23 +67,15 @@ impl Link for Mounting {
|
|||||||
// Forbid self-mounting
|
// Forbid self-mounting
|
||||||
Err(MountingError::NotMountable)
|
Err(MountingError::NotMountable)
|
||||||
} else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
|
} else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
|
||||||
if let Some(mount_body) = body.get(mount) {
|
let can_mount_with =
|
||||||
if is_mountable(mount_body, body.get(rider)) {
|
|entity| is_mounts.get(entity).is_none() && is_riders.get(entity).is_none();
|
||||||
let can_mount_with =
|
|
||||||
|entity| is_mounts.get(entity).is_none() && is_riders.get(entity).is_none();
|
|
||||||
|
|
||||||
// Ensure that neither mount or rider are already part of a mounting
|
// Ensure that neither mount or rider are already part of a mounting
|
||||||
// relationship
|
// relationship
|
||||||
if can_mount_with(mount) && can_mount_with(rider) {
|
if can_mount_with(mount) && can_mount_with(rider) {
|
||||||
let _ = is_mounts.insert(mount, this.make_role());
|
let _ = is_mounts.insert(mount, this.make_role());
|
||||||
let _ = is_riders.insert(rider, this.make_role());
|
let _ = is_riders.insert(rider, this.make_role());
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
|
||||||
Err(MountingError::NotMountable)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(MountingError::NotMountable)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Err(MountingError::NotMountable)
|
Err(MountingError::NotMountable)
|
||||||
}
|
}
|
||||||
@ -146,7 +137,7 @@ impl Link for Mounting {
|
|||||||
let old_pos = pos.0.map(|e| e.floor() as i32);
|
let old_pos = pos.0.map(|e| e.floor() as i32);
|
||||||
pos.0 = safe_pos
|
pos.0 = safe_pos
|
||||||
.map(|p| p.0.map(|e| e.floor()))
|
.map(|p| p.0.map(|e| e.floor()))
|
||||||
.unwrap_or_else(|| terrain.find_space(old_pos).map(|e| e as f32))
|
.unwrap_or_else(|| terrain.find_ground(old_pos).map(|e| e as f32))
|
||||||
+ Vec3::new(0.5, 0.5, 0.0);
|
+ Vec3::new(0.5, 0.5, 0.0);
|
||||||
if let Some(force_update) = force_update.get_mut(rider) {
|
if let Some(force_update) = force_update.get_mut(rider) {
|
||||||
force_update.update();
|
force_update.update();
|
||||||
|
@ -97,6 +97,12 @@ pub enum Outcome {
|
|||||||
FlashFreeze {
|
FlashFreeze {
|
||||||
pos: Vec3<f32>,
|
pos: Vec3<f32>,
|
||||||
},
|
},
|
||||||
|
LaserBeam {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
},
|
||||||
|
CyclopsCharge {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
},
|
||||||
Utterance {
|
Utterance {
|
||||||
pos: Vec3<f32>,
|
pos: Vec3<f32>,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
@ -138,6 +144,8 @@ impl Outcome {
|
|||||||
| Outcome::IceCrack { pos }
|
| Outcome::IceCrack { pos }
|
||||||
| Outcome::Utterance { pos, .. }
|
| Outcome::Utterance { pos, .. }
|
||||||
| Outcome::SpriteDelete { pos, .. }
|
| Outcome::SpriteDelete { pos, .. }
|
||||||
|
| Outcome::CyclopsCharge { pos }
|
||||||
|
| Outcome::LaserBeam { pos }
|
||||||
| Outcome::Glider { pos, .. } => Some(*pos),
|
| Outcome::Glider { pos, .. } => Some(*pos),
|
||||||
Outcome::BreakBlock { pos, .. }
|
Outcome::BreakBlock { pos, .. }
|
||||||
| Outcome::SpriteUnlocked { pos }
|
| Outcome::SpriteUnlocked { pos }
|
||||||
|
@ -19,7 +19,7 @@ use vek::*;
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Path<T> {
|
pub struct Path<T> {
|
||||||
nodes: Vec<T>,
|
pub nodes: Vec<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for Path<T> {
|
impl<T> Default for Path<T> {
|
||||||
@ -534,7 +534,7 @@ where
|
|||||||
_ => return (None, false),
|
_ => return (None, false),
|
||||||
};
|
};
|
||||||
|
|
||||||
let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
|
let heuristic = |pos: &Vec3<i32>, _: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
|
||||||
let neighbors = |pos: &Vec3<i32>| {
|
let neighbors = |pos: &Vec3<i32>| {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
const DIRS: [Vec3<i32>; 17] = [
|
const DIRS: [Vec3<i32>; 17] = [
|
||||||
@ -639,7 +639,7 @@ where
|
|||||||
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
||||||
|
|
||||||
let mut new_astar = match astar.take() {
|
let mut new_astar = match astar.take() {
|
||||||
None => Astar::new(25_000, start, heuristic, DefaultHashBuilder::default()),
|
None => Astar::new(25_000, start, DefaultHashBuilder::default()),
|
||||||
Some(astar) => astar,
|
Some(astar) => astar,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,41 +3,211 @@
|
|||||||
// `Agent`). When possible, this should be moved to the `rtsim`
|
// `Agent`). When possible, this should be moved to the `rtsim`
|
||||||
// module in `server`.
|
// module in `server`.
|
||||||
|
|
||||||
|
use crate::{character::CharacterId, comp::Content};
|
||||||
|
use rand::{seq::IteratorRandom, Rng};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use specs::Component;
|
use specs::Component;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use strum::{EnumIter, IntoEnumIterator};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
use crate::comp::dialogue::MoodState;
|
slotmap::new_key_type! { pub struct NpcId; }
|
||||||
|
|
||||||
pub type RtSimId = usize;
|
slotmap::new_key_type! { pub struct VehicleId; }
|
||||||
|
|
||||||
|
slotmap::new_key_type! { pub struct SiteId; }
|
||||||
|
|
||||||
|
slotmap::new_key_type! { pub struct FactionId; }
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct RtSimEntity(pub RtSimId);
|
pub struct RtSimEntity(pub NpcId);
|
||||||
|
|
||||||
impl Component for RtSimEntity {
|
impl Component for RtSimEntity {
|
||||||
type Storage = specs::VecStorage<Self>;
|
type Storage = specs::VecStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum RtSimEvent {
|
pub enum Actor {
|
||||||
AddMemory(Memory),
|
Npc(NpcId),
|
||||||
SetMood(Memory),
|
Character(CharacterId),
|
||||||
ForgetEnemy(String),
|
|
||||||
PrintMemories,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
impl Actor {
|
||||||
pub struct Memory {
|
pub fn npc(&self) -> Option<NpcId> {
|
||||||
pub item: MemoryItem,
|
match self {
|
||||||
pub time_to_forget: f64,
|
Actor::Npc(id) => Some(*id),
|
||||||
|
Actor::Character(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum MemoryItem {
|
pub struct RtSimVehicle(pub VehicleId);
|
||||||
// These are structs to allow more data beyond name to be stored
|
|
||||||
// such as clothing worn, weapon used, etc.
|
impl Component for RtSimVehicle {
|
||||||
CharacterInteraction { name: String },
|
type Storage = specs::VecStorage<Self>;
|
||||||
CharacterFight { name: String },
|
}
|
||||||
Mood { state: MoodState },
|
|
||||||
|
#[derive(EnumIter, Clone, Copy)]
|
||||||
|
pub enum PersonalityTrait {
|
||||||
|
Open,
|
||||||
|
Adventurous,
|
||||||
|
Closed,
|
||||||
|
Conscientious,
|
||||||
|
Busybody,
|
||||||
|
Unconscientious,
|
||||||
|
Extroverted,
|
||||||
|
Introverted,
|
||||||
|
Agreeable,
|
||||||
|
Sociable,
|
||||||
|
Disagreeable,
|
||||||
|
Neurotic,
|
||||||
|
Seeker,
|
||||||
|
Worried,
|
||||||
|
SadLoner,
|
||||||
|
Stable,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub struct Personality {
|
||||||
|
openness: u8,
|
||||||
|
conscientiousness: u8,
|
||||||
|
extraversion: u8,
|
||||||
|
agreeableness: u8,
|
||||||
|
neuroticism: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn distributed(min: u8, max: u8, rng: &mut impl Rng) -> u8 {
|
||||||
|
let l = max - min;
|
||||||
|
min + rng.gen_range(0..=l / 3)
|
||||||
|
+ rng.gen_range(0..=l / 3 + l % 3 % 2)
|
||||||
|
+ rng.gen_range(0..=l / 3 + l % 3 / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Personality {
|
||||||
|
pub const HIGH_THRESHOLD: u8 = Self::MAX - Self::LOW_THRESHOLD;
|
||||||
|
pub const LITTLE_HIGH: u8 = Self::MID + (Self::MAX - Self::MIN) / 20;
|
||||||
|
pub const LITTLE_LOW: u8 = Self::MID - (Self::MAX - Self::MIN) / 20;
|
||||||
|
pub const LOW_THRESHOLD: u8 = (Self::MAX - Self::MIN) / 5 * 2 + Self::MIN;
|
||||||
|
const MAX: u8 = 255;
|
||||||
|
pub const MID: u8 = (Self::MAX - Self::MIN) / 2;
|
||||||
|
const MIN: u8 = 0;
|
||||||
|
|
||||||
|
fn distributed_value(rng: &mut impl Rng) -> u8 { distributed(Self::MIN, Self::MAX, rng) }
|
||||||
|
|
||||||
|
pub fn random(rng: &mut impl Rng) -> Self {
|
||||||
|
Self {
|
||||||
|
openness: Self::distributed_value(rng),
|
||||||
|
conscientiousness: Self::distributed_value(rng),
|
||||||
|
extraversion: Self::distributed_value(rng),
|
||||||
|
agreeableness: Self::distributed_value(rng),
|
||||||
|
neuroticism: Self::distributed_value(rng),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_evil(rng: &mut impl Rng) -> Self {
|
||||||
|
Self {
|
||||||
|
openness: Self::distributed_value(rng),
|
||||||
|
extraversion: Self::distributed_value(rng),
|
||||||
|
neuroticism: Self::distributed_value(rng),
|
||||||
|
agreeableness: distributed(0, Self::LOW_THRESHOLD - 1, rng),
|
||||||
|
conscientiousness: distributed(0, Self::LOW_THRESHOLD - 1, rng),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_good(rng: &mut impl Rng) -> Self {
|
||||||
|
Self {
|
||||||
|
openness: Self::distributed_value(rng),
|
||||||
|
extraversion: Self::distributed_value(rng),
|
||||||
|
neuroticism: Self::distributed_value(rng),
|
||||||
|
agreeableness: Self::distributed_value(rng),
|
||||||
|
conscientiousness: distributed(Self::LOW_THRESHOLD, Self::MAX, rng),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is(&self, trait_: PersonalityTrait) -> bool {
|
||||||
|
match trait_ {
|
||||||
|
PersonalityTrait::Open => self.openness > Personality::HIGH_THRESHOLD,
|
||||||
|
PersonalityTrait::Adventurous => {
|
||||||
|
self.openness > Personality::HIGH_THRESHOLD && self.neuroticism < Personality::MID
|
||||||
|
},
|
||||||
|
PersonalityTrait::Closed => self.openness < Personality::LOW_THRESHOLD,
|
||||||
|
PersonalityTrait::Conscientious => self.conscientiousness > Personality::HIGH_THRESHOLD,
|
||||||
|
PersonalityTrait::Busybody => self.agreeableness < Personality::LOW_THRESHOLD,
|
||||||
|
PersonalityTrait::Unconscientious => {
|
||||||
|
self.conscientiousness < Personality::LOW_THRESHOLD
|
||||||
|
},
|
||||||
|
PersonalityTrait::Extroverted => self.extraversion > Personality::HIGH_THRESHOLD,
|
||||||
|
PersonalityTrait::Introverted => self.extraversion < Personality::LOW_THRESHOLD,
|
||||||
|
PersonalityTrait::Agreeable => self.agreeableness > Personality::HIGH_THRESHOLD,
|
||||||
|
PersonalityTrait::Sociable => {
|
||||||
|
self.agreeableness > Personality::HIGH_THRESHOLD
|
||||||
|
&& self.extraversion > Personality::MID
|
||||||
|
},
|
||||||
|
PersonalityTrait::Disagreeable => self.agreeableness < Personality::LOW_THRESHOLD,
|
||||||
|
PersonalityTrait::Neurotic => self.neuroticism > Personality::HIGH_THRESHOLD,
|
||||||
|
PersonalityTrait::Seeker => {
|
||||||
|
self.neuroticism > Personality::HIGH_THRESHOLD
|
||||||
|
&& self.openness > Personality::LITTLE_HIGH
|
||||||
|
},
|
||||||
|
PersonalityTrait::Worried => {
|
||||||
|
self.neuroticism > Personality::HIGH_THRESHOLD
|
||||||
|
&& self.agreeableness > Personality::LITTLE_HIGH
|
||||||
|
},
|
||||||
|
PersonalityTrait::SadLoner => {
|
||||||
|
self.neuroticism > Personality::HIGH_THRESHOLD
|
||||||
|
&& self.extraversion < Personality::LITTLE_LOW
|
||||||
|
},
|
||||||
|
PersonalityTrait::Stable => self.neuroticism < Personality::LOW_THRESHOLD,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chat_trait(&self, rng: &mut impl Rng) -> Option<PersonalityTrait> {
|
||||||
|
PersonalityTrait::iter().filter(|t| self.is(*t)).choose(rng)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn will_ambush(&self) -> bool {
|
||||||
|
self.agreeableness < Self::LOW_THRESHOLD && self.conscientiousness < Self::LOW_THRESHOLD
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_generic_comment(&self, rng: &mut impl Rng) -> Content {
|
||||||
|
let i18n_key = if let Some(extreme_trait) = self.chat_trait(rng) {
|
||||||
|
match extreme_trait {
|
||||||
|
PersonalityTrait::Open => "npc-speech-villager_open",
|
||||||
|
PersonalityTrait::Adventurous => "npc-speech-villager_adventurous",
|
||||||
|
PersonalityTrait::Closed => "npc-speech-villager_closed",
|
||||||
|
PersonalityTrait::Conscientious => "npc-speech-villager_conscientious",
|
||||||
|
PersonalityTrait::Busybody => "npc-speech-villager_busybody",
|
||||||
|
PersonalityTrait::Unconscientious => "npc-speech-villager_unconscientious",
|
||||||
|
PersonalityTrait::Extroverted => "npc-speech-villager_extroverted",
|
||||||
|
PersonalityTrait::Introverted => "npc-speech-villager_introverted",
|
||||||
|
PersonalityTrait::Agreeable => "npc-speech-villager_agreeable",
|
||||||
|
PersonalityTrait::Sociable => "npc-speech-villager_sociable",
|
||||||
|
PersonalityTrait::Disagreeable => "npc-speech-villager_disagreeable",
|
||||||
|
PersonalityTrait::Neurotic => "npc-speech-villager_neurotic",
|
||||||
|
PersonalityTrait::Seeker => "npc-speech-villager_seeker",
|
||||||
|
PersonalityTrait::SadLoner => "npc-speech-villager_sad_loner",
|
||||||
|
PersonalityTrait::Worried => "npc-speech-villager_worried",
|
||||||
|
PersonalityTrait::Stable => "npc-speech-villager_stable",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"npc-speech-villager"
|
||||||
|
};
|
||||||
|
|
||||||
|
Content::localized(i18n_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Personality {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
openness: Personality::MID,
|
||||||
|
conscientiousness: Personality::MID,
|
||||||
|
extraversion: Personality::MID,
|
||||||
|
agreeableness: Personality::MID,
|
||||||
|
neuroticism: Personality::MID,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This type is the map route through which the rtsim (real-time simulation)
|
/// This type is the map route through which the rtsim (real-time simulation)
|
||||||
@ -49,36 +219,112 @@ pub enum MemoryItem {
|
|||||||
/// into the game as a physical entity or not). Agent code should attempt to act
|
/// into the game as a physical entity or not). Agent code should attempt to act
|
||||||
/// upon its instructions where reasonable although deviations for various
|
/// upon its instructions where reasonable although deviations for various
|
||||||
/// reasons (obstacle avoidance, counter-attacking, etc.) are expected.
|
/// reasons (obstacle avoidance, counter-attacking, etc.) are expected.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct RtSimController {
|
pub struct RtSimController {
|
||||||
/// When this field is `Some(..)`, the agent should attempt to make progress
|
pub activity: Option<NpcActivity>,
|
||||||
/// toward the given location, accounting for obstacles and other
|
pub actions: VecDeque<NpcAction>,
|
||||||
/// high-priority situations like being attacked.
|
pub personality: Personality,
|
||||||
pub travel_to: Option<(Vec3<f32>, String)>,
|
pub heading_to: Option<String>,
|
||||||
/// Proportion of full speed to move
|
|
||||||
pub speed_factor: f32,
|
|
||||||
/// Events
|
|
||||||
pub events: Vec<RtSimEvent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RtSimController {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
travel_to: None,
|
|
||||||
speed_factor: 1.0,
|
|
||||||
events: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RtSimController {
|
impl RtSimController {
|
||||||
pub fn reset(&mut self) { *self = Self::default(); }
|
|
||||||
|
|
||||||
pub fn with_destination(pos: Vec3<f32>) -> Self {
|
pub fn with_destination(pos: Vec3<f32>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
travel_to: Some((pos, format!("{:0.1?}", pos))),
|
activity: Some(NpcActivity::Goto(pos, 0.5)),
|
||||||
speed_factor: 0.25,
|
..Default::default()
|
||||||
events: Vec::new(),
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum NpcActivity {
|
||||||
|
/// (travel_to, speed_factor)
|
||||||
|
Goto(Vec3<f32>, f32),
|
||||||
|
Gather(&'static [ChunkResource]),
|
||||||
|
// TODO: Generalise to other entities? What kinds of animals?
|
||||||
|
HuntAnimals,
|
||||||
|
Dance,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents event-like actions that rtsim NPCs can perform to interact with
|
||||||
|
/// the world
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum NpcAction {
|
||||||
|
/// Speak the given message, with an optional target for that speech.
|
||||||
|
// TODO: Use some sort of structured, language-independent value that frontends can translate
|
||||||
|
// instead
|
||||||
|
Say(Option<Actor>, Content),
|
||||||
|
/// Attack the given target
|
||||||
|
Attack(Actor),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: the `serde(name = "...")` is to minimise the length of field
|
||||||
|
// identifiers for the sake of rtsim persistence
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, enum_map::Enum)]
|
||||||
|
pub enum ChunkResource {
|
||||||
|
#[serde(rename = "0")]
|
||||||
|
Grass,
|
||||||
|
#[serde(rename = "1")]
|
||||||
|
Flower,
|
||||||
|
#[serde(rename = "2")]
|
||||||
|
Fruit,
|
||||||
|
#[serde(rename = "3")]
|
||||||
|
Vegetable,
|
||||||
|
#[serde(rename = "4")]
|
||||||
|
Mushroom,
|
||||||
|
#[serde(rename = "5")]
|
||||||
|
Loot, // Chests, boxes, potions, etc.
|
||||||
|
#[serde(rename = "6")]
|
||||||
|
Plant, // Flax, cotton, wheat, corn, etc.
|
||||||
|
#[serde(rename = "7")]
|
||||||
|
Stone,
|
||||||
|
#[serde(rename = "8")]
|
||||||
|
Wood, // Twigs, logs, bamboo, etc.
|
||||||
|
#[serde(rename = "9")]
|
||||||
|
Gem, // Amethyst, diamond, etc.
|
||||||
|
#[serde(rename = "a")]
|
||||||
|
Ore, // Iron, copper, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: the `serde(name = "...")` is to minimise the length of field
|
||||||
|
// identifiers for the sake of rtsim persistence
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub enum Profession {
|
||||||
|
#[serde(rename = "0")]
|
||||||
|
Farmer,
|
||||||
|
#[serde(rename = "1")]
|
||||||
|
Hunter,
|
||||||
|
#[serde(rename = "2")]
|
||||||
|
Merchant,
|
||||||
|
#[serde(rename = "3")]
|
||||||
|
Guard,
|
||||||
|
#[serde(rename = "4")]
|
||||||
|
Adventurer(u32),
|
||||||
|
#[serde(rename = "5")]
|
||||||
|
Blacksmith,
|
||||||
|
#[serde(rename = "6")]
|
||||||
|
Chef,
|
||||||
|
#[serde(rename = "7")]
|
||||||
|
Alchemist,
|
||||||
|
#[serde(rename = "8")]
|
||||||
|
Pirate,
|
||||||
|
#[serde(rename = "9")]
|
||||||
|
Cultist,
|
||||||
|
#[serde(rename = "10")]
|
||||||
|
Herbalist,
|
||||||
|
#[serde(rename = "11")]
|
||||||
|
Captain,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct WorldSettings {
|
||||||
|
pub start_time: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WorldSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
start_time: 9.0 * 3600.0, // 9am
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
combat::CombatEffect,
|
combat::CombatEffect,
|
||||||
comp::{
|
comp::{
|
||||||
character_state::OutputEvents, Body, CharacterState, LightEmitter, Pos,
|
character_state::OutputEvents, object::Body::LaserBeam, Body, CharacterState, LightEmitter,
|
||||||
ProjectileConstructor, StateUpdate,
|
Pos, ProjectileConstructor, StateUpdate,
|
||||||
},
|
},
|
||||||
event::ServerEvent,
|
event::{LocalEvent, ServerEvent},
|
||||||
|
outcome::Outcome,
|
||||||
states::{
|
states::{
|
||||||
behavior::{CharacterBehavior, JoinData},
|
behavior::{CharacterBehavior, JoinData},
|
||||||
utils::*,
|
utils::*,
|
||||||
@ -66,6 +67,14 @@ impl CharacterBehavior for Data {
|
|||||||
timer: tick_attack_or_default(data, self.timer, None),
|
timer: tick_attack_or_default(data, self.timer, None),
|
||||||
..*self
|
..*self
|
||||||
});
|
});
|
||||||
|
if self.static_data.projectile_body == Body::Object(LaserBeam) {
|
||||||
|
// Send local event used for frontend shenanigans
|
||||||
|
output_events.emit_local(LocalEvent::CreateOutcome(
|
||||||
|
Outcome::CyclopsCharge {
|
||||||
|
pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Transitions to recover section of stage
|
// Transitions to recover section of stage
|
||||||
update.character = CharacterState::BasicRanged(Data {
|
update.character = CharacterState::BasicRanged(Data {
|
||||||
@ -91,7 +100,10 @@ impl CharacterBehavior for Data {
|
|||||||
// Shoots all projectiles simultaneously
|
// Shoots all projectiles simultaneously
|
||||||
for i in 0..self.static_data.num_projectiles {
|
for i in 0..self.static_data.num_projectiles {
|
||||||
// Gets offsets
|
// Gets offsets
|
||||||
let body_offsets = data.body.projectile_offsets(update.ori.look_vec());
|
let body_offsets = data.body.projectile_offsets(
|
||||||
|
update.ori.look_vec(),
|
||||||
|
data.scale.map_or(1.0, |s| s.0),
|
||||||
|
);
|
||||||
let pos = Pos(data.pos.0 + body_offsets);
|
let pos = Pos(data.pos.0 + body_offsets);
|
||||||
// Adds a slight spread to the projectiles. First projectile has no spread,
|
// Adds a slight spread to the projectiles. First projectile has no spread,
|
||||||
// and spread increases linearly with number of projectiles created.
|
// and spread increases linearly with number of projectiles created.
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
skillset::skills,
|
skillset::skills,
|
||||||
Behavior, BehaviorCapability, CharacterState, Projectile, StateUpdate,
|
Behavior, BehaviorCapability, CharacterState, Projectile, StateUpdate,
|
||||||
},
|
},
|
||||||
event::{LocalEvent, ServerEvent},
|
event::{LocalEvent, NpcBuilder, ServerEvent},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
skillset_builder::{self, SkillSetBuilder},
|
skillset_builder::{self, SkillSetBuilder},
|
||||||
states::{
|
states::{
|
||||||
@ -149,7 +149,7 @@ impl CharacterBehavior for Data {
|
|||||||
let collision_vector = Vec3::new(
|
let collision_vector = Vec3::new(
|
||||||
data.pos.0.x + (summon_frac * 2.0 * PI).sin() * obstacle_xy,
|
data.pos.0.x + (summon_frac * 2.0 * PI).sin() * obstacle_xy,
|
||||||
data.pos.0.y + (summon_frac * 2.0 * PI).cos() * obstacle_xy,
|
data.pos.0.y + (summon_frac * 2.0 * PI).cos() * obstacle_xy,
|
||||||
data.pos.0.z + data.body.eye_height(),
|
data.pos.0.z + data.body.eye_height(data.scale.map_or(1.0, |s| s.0)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check for collision in z up to 50 blocks
|
// Check for collision in z up to 50 blocks
|
||||||
@ -174,27 +174,22 @@ impl CharacterBehavior for Data {
|
|||||||
// Send server event to create npc
|
// Send server event to create npc
|
||||||
output_events.emit_server(ServerEvent::CreateNpc {
|
output_events.emit_server(ServerEvent::CreateNpc {
|
||||||
pos: comp::Pos(collision_vector - Vec3::unit_z() * obstacle_z),
|
pos: comp::Pos(collision_vector - Vec3::unit_z() * obstacle_z),
|
||||||
stats,
|
npc: NpcBuilder::new(stats, body, comp::Alignment::Owned(*data.uid))
|
||||||
skill_set,
|
.with_skill_set(skill_set)
|
||||||
health,
|
.with_health(health)
|
||||||
poise: comp::Poise::new(body),
|
.with_inventory(comp::Inventory::with_loadout(loadout, body))
|
||||||
inventory: comp::Inventory::with_loadout(loadout, body),
|
.with_agent(
|
||||||
body,
|
comp::Agent::from_body(&body)
|
||||||
agent: Some(
|
.with_behavior(Behavior::from(BehaviorCapability::SPEAK))
|
||||||
comp::Agent::from_body(&body)
|
.with_no_flee_if(true),
|
||||||
.with_behavior(Behavior::from(BehaviorCapability::SPEAK))
|
)
|
||||||
.with_no_flee_if(true),
|
.with_scale(
|
||||||
),
|
self.static_data
|
||||||
alignment: comp::Alignment::Owned(*data.uid),
|
.summon_info
|
||||||
scale: self
|
.scale
|
||||||
.static_data
|
.unwrap_or(comp::Scale(1.0)),
|
||||||
.summon_info
|
)
|
||||||
.scale
|
.with_projectile(projectile),
|
||||||
.unwrap_or(comp::Scale(1.0)),
|
|
||||||
anchor: None,
|
|
||||||
loot: crate::lottery::LootSpec::Nothing,
|
|
||||||
rtsim_entity: None,
|
|
||||||
projectile,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send local event used for frontend shenanigans
|
// Send local event used for frontend shenanigans
|
||||||
|
@ -3,10 +3,10 @@ use crate::{
|
|||||||
self,
|
self,
|
||||||
character_state::OutputEvents,
|
character_state::OutputEvents,
|
||||||
item::{tool::AbilityMap, MaterialStatManifest},
|
item::{tool::AbilityMap, MaterialStatManifest},
|
||||||
ActiveAbilities, Beam, Body, CharacterState, Combo, ControlAction, Controller,
|
ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, ControlAction,
|
||||||
ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory,
|
Controller, ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory,
|
||||||
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, SkillSet, Stance, StateUpdate, Stats,
|
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, Scale, SkillSet, Stance, StateUpdate,
|
||||||
Vel,
|
Stats, Vel,
|
||||||
},
|
},
|
||||||
link::Is,
|
link::Is,
|
||||||
mounting::Rider,
|
mounting::Rider,
|
||||||
@ -120,9 +120,11 @@ pub struct JoinData<'a> {
|
|||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub uid: &'a Uid,
|
pub uid: &'a Uid,
|
||||||
pub character: &'a CharacterState,
|
pub character: &'a CharacterState,
|
||||||
|
pub character_activity: &'a CharacterActivity,
|
||||||
pub pos: &'a Pos,
|
pub pos: &'a Pos,
|
||||||
pub vel: &'a Vel,
|
pub vel: &'a Vel,
|
||||||
pub ori: &'a Ori,
|
pub ori: &'a Ori,
|
||||||
|
pub scale: Option<&'a Scale>,
|
||||||
pub mass: &'a Mass,
|
pub mass: &'a Mass,
|
||||||
pub density: &'a Density,
|
pub density: &'a Density,
|
||||||
pub dt: &'a DeltaTime,
|
pub dt: &'a DeltaTime,
|
||||||
@ -152,9 +154,11 @@ pub struct JoinStruct<'a> {
|
|||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub uid: &'a Uid,
|
pub uid: &'a Uid,
|
||||||
pub char_state: FlaggedAccessMut<'a, &'a mut CharacterState, CharacterState>,
|
pub char_state: FlaggedAccessMut<'a, &'a mut CharacterState, CharacterState>,
|
||||||
|
pub character_activity: FlaggedAccessMut<'a, &'a mut CharacterActivity, CharacterActivity>,
|
||||||
pub pos: &'a mut Pos,
|
pub pos: &'a mut Pos,
|
||||||
pub vel: &'a mut Vel,
|
pub vel: &'a mut Vel,
|
||||||
pub ori: &'a mut Ori,
|
pub ori: &'a mut Ori,
|
||||||
|
pub scale: Option<&'a Scale>,
|
||||||
pub mass: &'a Mass,
|
pub mass: &'a Mass,
|
||||||
pub density: FlaggedAccessMut<'a, &'a mut Density, Density>,
|
pub density: FlaggedAccessMut<'a, &'a mut Density, Density>,
|
||||||
pub energy: FlaggedAccessMut<'a, &'a mut Energy, Energy>,
|
pub energy: FlaggedAccessMut<'a, &'a mut Energy, Energy>,
|
||||||
@ -188,9 +192,11 @@ impl<'a> JoinData<'a> {
|
|||||||
entity: j.entity,
|
entity: j.entity,
|
||||||
uid: j.uid,
|
uid: j.uid,
|
||||||
character: &j.char_state,
|
character: &j.char_state,
|
||||||
|
character_activity: &j.character_activity,
|
||||||
pos: j.pos,
|
pos: j.pos,
|
||||||
vel: j.vel,
|
vel: j.vel,
|
||||||
ori: j.ori,
|
ori: j.ori,
|
||||||
|
scale: j.scale,
|
||||||
mass: j.mass,
|
mass: j.mass,
|
||||||
density: &j.density,
|
density: &j.density,
|
||||||
energy: &j.energy,
|
energy: &j.energy,
|
||||||
|
@ -114,7 +114,9 @@ impl CharacterBehavior for Data {
|
|||||||
get_crit_data(data, self.static_data.ability_info);
|
get_crit_data(data, self.static_data.ability_info);
|
||||||
let tool_stats = get_tool_stats(data, self.static_data.ability_info);
|
let tool_stats = get_tool_stats(data, self.static_data.ability_info);
|
||||||
// Gets offsets
|
// Gets offsets
|
||||||
let body_offsets = data.body.projectile_offsets(update.ori.look_vec());
|
let body_offsets = data
|
||||||
|
.body
|
||||||
|
.projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
|
||||||
let pos = Pos(data.pos.0 + body_offsets);
|
let pos = Pos(data.pos.0 + body_offsets);
|
||||||
let projectile = arrow.create_projectile(
|
let projectile = arrow.create_projectile(
|
||||||
Some(*data.uid),
|
Some(*data.uid),
|
||||||
|
@ -76,7 +76,8 @@ impl CharacterBehavior for Data {
|
|||||||
// They've climbed atop something, give them a boost
|
// They've climbed atop something, give them a boost
|
||||||
output_events.emit_local(LocalEvent::Jump(
|
output_events.emit_local(LocalEvent::Jump(
|
||||||
data.entity,
|
data.entity,
|
||||||
CLIMB_BOOST_JUMP_FACTOR * impulse / data.mass.0,
|
CLIMB_BOOST_JUMP_FACTOR * impulse / data.mass.0
|
||||||
|
* data.scale.map_or(1.0, |s| s.0.powf(13.0).powf(0.25)),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
update.character = CharacterState::Idle(idle::Data::default());
|
update.character = CharacterState::Idle(idle::Data::default());
|
||||||
@ -122,10 +123,14 @@ impl CharacterBehavior for Data {
|
|||||||
// Apply Vertical Climbing Movement
|
// Apply Vertical Climbing Movement
|
||||||
match climb {
|
match climb {
|
||||||
Climb::Down => {
|
Climb::Down => {
|
||||||
update.vel.0.z += data.dt.0 * (GRAVITY - self.static_data.movement_speed.powi(2))
|
update.vel.0.z += data.dt.0
|
||||||
|
* (GRAVITY
|
||||||
|
- self.static_data.movement_speed.powi(2) * data.scale.map_or(1.0, |s| s.0))
|
||||||
},
|
},
|
||||||
Climb::Up => {
|
Climb::Up => {
|
||||||
update.vel.0.z += data.dt.0 * (GRAVITY + self.static_data.movement_speed.powi(2))
|
update.vel.0.z += data.dt.0
|
||||||
|
* (GRAVITY
|
||||||
|
+ self.static_data.movement_speed.powi(2) * data.scale.map_or(1.0, |s| s.0))
|
||||||
},
|
},
|
||||||
Climb::Hold => update.vel.0.z += data.dt.0 * GRAVITY,
|
Climb::Hold => update.vel.0.z += data.dt.0 * GRAVITY,
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,9 @@ impl CharacterBehavior for Data {
|
|||||||
get_crit_data(data, self.static_data.ability_info);
|
get_crit_data(data, self.static_data.ability_info);
|
||||||
let tool_stats = get_tool_stats(data, self.static_data.ability_info);
|
let tool_stats = get_tool_stats(data, self.static_data.ability_info);
|
||||||
// Gets offsets
|
// Gets offsets
|
||||||
let body_offsets = data.body.projectile_offsets(update.ori.look_vec());
|
let body_offsets = data
|
||||||
|
.body
|
||||||
|
.projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
|
||||||
let pos = Pos(data.pos.0 + body_offsets);
|
let pos = Pos(data.pos.0 + body_offsets);
|
||||||
let projectile = self.static_data.projectile.create_projectile(
|
let projectile = self.static_data.projectile.create_projectile(
|
||||||
Some(*data.uid),
|
Some(*data.uid),
|
||||||
|
@ -143,10 +143,24 @@ impl CharacterBehavior for Data {
|
|||||||
..*self
|
..*self
|
||||||
});
|
});
|
||||||
// Send local event used for frontend shenanigans
|
// Send local event used for frontend shenanigans
|
||||||
if self.static_data.specifier == shockwave::FrontendSpecifier::IceSpikes {
|
match self.static_data.specifier {
|
||||||
output_events.emit_local(LocalEvent::CreateOutcome(Outcome::FlashFreeze {
|
shockwave::FrontendSpecifier::IceSpikes => {
|
||||||
pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
|
output_events.emit_local(LocalEvent::CreateOutcome(
|
||||||
}));
|
Outcome::FlashFreeze {
|
||||||
|
pos: data.pos.0
|
||||||
|
+ *data.ori.look_dir() * (data.body.max_radius()),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
},
|
||||||
|
shockwave::FrontendSpecifier::Ground => {
|
||||||
|
output_events.emit_local(LocalEvent::CreateOutcome(
|
||||||
|
Outcome::GroundSlam {
|
||||||
|
pos: data.pos.0
|
||||||
|
+ *data.ori.look_dir() * (data.body.max_radius()),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Transitions to recover
|
// Transitions to recover
|
||||||
|
@ -220,7 +220,7 @@ impl Body {
|
|||||||
_ => 2.0,
|
_ => 2.0,
|
||||||
},
|
},
|
||||||
Body::Ship(ship) if ship.has_water_thrust() => 0.1,
|
Body::Ship(ship) if ship.has_water_thrust() => 0.1,
|
||||||
Body::Ship(_) => 0.035,
|
Body::Ship(_) => 0.12,
|
||||||
Body::Arthropod(_) => 3.5,
|
Body::Arthropod(_) => 3.5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,10 +298,10 @@ impl Body {
|
|||||||
|
|
||||||
/// Returns the position where a projectile should be fired relative to this
|
/// Returns the position where a projectile should be fired relative to this
|
||||||
/// body
|
/// body
|
||||||
pub fn projectile_offsets(&self, ori: Vec3<f32>) -> Vec3<f32> {
|
pub fn projectile_offsets(&self, ori: Vec3<f32>, scale: f32) -> Vec3<f32> {
|
||||||
let body_offsets_z = match self {
|
let body_offsets_z = match self {
|
||||||
Body::Golem(_) => self.height() * 0.4,
|
Body::Golem(_) => self.height() * 0.4,
|
||||||
_ => self.eye_height(),
|
_ => self.eye_height(scale),
|
||||||
};
|
};
|
||||||
|
|
||||||
let dim = self.dimensions();
|
let dim = self.dimensions();
|
||||||
@ -385,7 +385,11 @@ fn basic_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
|
|||||||
|
|
||||||
let accel = if let Some(block) = data.physics.on_ground {
|
let accel = if let Some(block) = data.physics.on_ground {
|
||||||
// FRIC_GROUND temporarily used to normalize things around expected values
|
// FRIC_GROUND temporarily used to normalize things around expected values
|
||||||
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
|
data.body.base_accel()
|
||||||
|
* data.scale.map_or(1.0, |s| s.0.sqrt())
|
||||||
|
* block.get_traction()
|
||||||
|
* block.get_friction()
|
||||||
|
/ FRIC_GROUND
|
||||||
} else {
|
} else {
|
||||||
data.body.air_accel()
|
data.body.air_accel()
|
||||||
} * efficiency;
|
} * efficiency;
|
||||||
@ -434,8 +438,11 @@ pub fn handle_forced_movement(
|
|||||||
// FRIC_GROUND temporarily used to normalize things around expected values
|
// FRIC_GROUND temporarily used to normalize things around expected values
|
||||||
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
|
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
|
||||||
}) {
|
}) {
|
||||||
update.vel.0 +=
|
update.vel.0 += Vec2::broadcast(data.dt.0)
|
||||||
Vec2::broadcast(data.dt.0) * accel * Vec2::from(*data.ori) * strength;
|
* accel
|
||||||
|
* data.scale.map_or(1.0, |s| s.0.sqrt())
|
||||||
|
* Vec2::from(*data.ori)
|
||||||
|
* strength;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ForcedMovement::Reverse(strength) => {
|
ForcedMovement::Reverse(strength) => {
|
||||||
@ -444,8 +451,11 @@ pub fn handle_forced_movement(
|
|||||||
// FRIC_GROUND temporarily used to normalize things around expected values
|
// FRIC_GROUND temporarily used to normalize things around expected values
|
||||||
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
|
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
|
||||||
}) {
|
}) {
|
||||||
update.vel.0 +=
|
update.vel.0 += Vec2::broadcast(data.dt.0)
|
||||||
Vec2::broadcast(data.dt.0) * accel * -Vec2::from(*data.ori) * strength;
|
* accel
|
||||||
|
* data.scale.map_or(1.0, |s| s.0.sqrt())
|
||||||
|
* -Vec2::from(*data.ori)
|
||||||
|
* strength;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ForcedMovement::Sideways(strength) => {
|
ForcedMovement::Sideways(strength) => {
|
||||||
@ -467,7 +477,11 @@ pub fn handle_forced_movement(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
update.vel.0 += Vec2::broadcast(data.dt.0) * accel * direction * strength;
|
update.vel.0 += Vec2::broadcast(data.dt.0)
|
||||||
|
* accel
|
||||||
|
* data.scale.map_or(1.0, |s| s.0.sqrt())
|
||||||
|
* direction
|
||||||
|
* strength;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ForcedMovement::DirectedReverse(strength) => {
|
ForcedMovement::DirectedReverse(strength) => {
|
||||||
@ -516,6 +530,7 @@ pub fn handle_forced_movement(
|
|||||||
dir.y,
|
dir.y,
|
||||||
vertical,
|
vertical,
|
||||||
)
|
)
|
||||||
|
* data.scale.map_or(1.0, |s| s.0.sqrt())
|
||||||
// Multiply decreasing amount linearly over time (with average of 1)
|
// Multiply decreasing amount linearly over time (with average of 1)
|
||||||
* 2.0 * progress
|
* 2.0 * progress
|
||||||
// Apply direction
|
// Apply direction
|
||||||
@ -529,7 +544,9 @@ pub fn handle_forced_movement(
|
|||||||
},
|
},
|
||||||
ForcedMovement::Hover { move_input } => {
|
ForcedMovement::Hover { move_input } => {
|
||||||
update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0)
|
update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0)
|
||||||
+ move_input * data.inputs.move_dir.try_normalized().unwrap_or_default();
|
+ move_input
|
||||||
|
* data.scale.map_or(1.0, |s| s.0.sqrt())
|
||||||
|
* data.inputs.move_dir.try_normalized().unwrap_or_default();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -569,7 +586,7 @@ pub fn handle_orientation(
|
|||||||
.map_or_else(|| to_horizontal_fast(data.ori), |dir| dir.into())
|
.map_or_else(|| to_horizontal_fast(data.ori), |dir| dir.into())
|
||||||
};
|
};
|
||||||
// unit is multiples of 180°
|
// unit is multiples of 180°
|
||||||
let half_turns_per_tick = data.body.base_ori_rate()
|
let half_turns_per_tick = data.body.base_ori_rate() / data.scale.map_or(1.0, |s| s.0.sqrt())
|
||||||
* efficiency
|
* efficiency
|
||||||
* if data.physics.on_ground.is_some() {
|
* if data.physics.on_ground.is_some() {
|
||||||
1.0
|
1.0
|
||||||
@ -594,6 +611,9 @@ pub fn handle_orientation(
|
|||||||
.ori
|
.ori
|
||||||
.slerped_towards(target_ori, target_fraction.min(1.0))
|
.slerped_towards(target_ori, target_fraction.min(1.0))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Look at things
|
||||||
|
update.character_activity.look_dir = Some(data.controller.inputs.look_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates components to move player as if theyre swimming
|
/// Updates components to move player as if theyre swimming
|
||||||
@ -605,7 +625,7 @@ fn swim_move(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
|
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
|
||||||
if let Some(force) = data.body.swim_thrust() {
|
if let Some(force) = data.body.swim_thrust() {
|
||||||
let force = efficiency * force;
|
let force = efficiency * force * data.scale.map_or(1.0, |s| s.0);
|
||||||
let mut water_accel = force / data.mass.0;
|
let mut water_accel = force / data.mass.0;
|
||||||
|
|
||||||
if let Ok(level) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) {
|
if let Ok(level) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) {
|
||||||
@ -912,13 +932,13 @@ pub fn handle_manipulate_loadout(
|
|||||||
let iters =
|
let iters =
|
||||||
(3.0 * (sprite_pos_f32 - data.pos.0).map(|x| x.abs()).sum()) as usize;
|
(3.0 * (sprite_pos_f32 - data.pos.0).map(|x| x.abs()).sum()) as usize;
|
||||||
// Heuristic compares manhattan distance of start and end pos
|
// Heuristic compares manhattan distance of start and end pos
|
||||||
let heuristic =
|
let heuristic = move |pos: &Vec3<i32>, _: &Vec3<i32>| {
|
||||||
move |pos: &Vec3<i32>| (sprite_pos - pos).map(|x| x.abs()).sum() as f32;
|
(sprite_pos - pos).map(|x| x.abs()).sum() as f32
|
||||||
|
};
|
||||||
|
|
||||||
let mut astar = Astar::new(
|
let mut astar = Astar::new(
|
||||||
iters,
|
iters,
|
||||||
data.pos.0.map(|x| x.floor() as i32),
|
data.pos.0.map(|x| x.floor() as i32),
|
||||||
heuristic,
|
|
||||||
BuildHasherDefault::<FxHasher64>::default(),
|
BuildHasherDefault::<FxHasher64>::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1068,7 +1088,9 @@ pub fn handle_jump(
|
|||||||
.map(|impulse| {
|
.map(|impulse| {
|
||||||
output_events.emit_local(LocalEvent::Jump(
|
output_events.emit_local(LocalEvent::Jump(
|
||||||
data.entity,
|
data.entity,
|
||||||
strength * impulse / data.mass.0 * data.stats.move_speed_modifier,
|
strength * impulse / data.mass.0
|
||||||
|
* data.scale.map_or(1.0, |s| s.0.powf(13.0).powf(0.25))
|
||||||
|
* data.stats.move_speed_modifier,
|
||||||
));
|
));
|
||||||
})
|
})
|
||||||
.is_some()
|
.is_some()
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
comp::{fluid_dynamics::LiquidKind, tool::ToolKind},
|
comp::{fluid_dynamics::LiquidKind, tool::ToolKind},
|
||||||
consts::FRIC_GROUND,
|
consts::FRIC_GROUND,
|
||||||
lottery::LootSpec,
|
lottery::LootSpec,
|
||||||
make_case_elim,
|
make_case_elim, rtsim,
|
||||||
};
|
};
|
||||||
use num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
@ -254,6 +254,98 @@ impl Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the rtsim resource, if any, that this block corresponds to. If
|
||||||
|
/// you want the scarcity of a block to change with rtsim's resource
|
||||||
|
/// depletion tracking, you can do so by editing this function.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get_rtsim_resource(&self) -> Option<rtsim::ChunkResource> {
|
||||||
|
match self.get_sprite()? {
|
||||||
|
SpriteKind::Stones => Some(rtsim::ChunkResource::Stone),
|
||||||
|
SpriteKind::Twigs
|
||||||
|
| SpriteKind::Wood
|
||||||
|
| SpriteKind::Bamboo
|
||||||
|
| SpriteKind::Hardwood
|
||||||
|
| SpriteKind::Ironwood
|
||||||
|
| SpriteKind::Frostwood
|
||||||
|
| SpriteKind::Eldwood => Some(rtsim::ChunkResource::Wood),
|
||||||
|
SpriteKind::Amethyst
|
||||||
|
| SpriteKind::Ruby
|
||||||
|
| SpriteKind::Sapphire
|
||||||
|
| SpriteKind::Emerald
|
||||||
|
| SpriteKind::Topaz
|
||||||
|
| SpriteKind::Diamond
|
||||||
|
| SpriteKind::AmethystSmall
|
||||||
|
| SpriteKind::TopazSmall
|
||||||
|
| SpriteKind::DiamondSmall
|
||||||
|
| SpriteKind::RubySmall
|
||||||
|
| SpriteKind::EmeraldSmall
|
||||||
|
| SpriteKind::SapphireSmall
|
||||||
|
| SpriteKind::CrystalHigh
|
||||||
|
| SpriteKind::CrystalLow => Some(rtsim::ChunkResource::Gem),
|
||||||
|
SpriteKind::Bloodstone
|
||||||
|
| SpriteKind::Coal
|
||||||
|
| SpriteKind::Cobalt
|
||||||
|
| SpriteKind::Copper
|
||||||
|
| SpriteKind::Iron
|
||||||
|
| SpriteKind::Tin
|
||||||
|
| SpriteKind::Silver
|
||||||
|
| SpriteKind::Gold => Some(rtsim::ChunkResource::Ore),
|
||||||
|
|
||||||
|
SpriteKind::LongGrass
|
||||||
|
| SpriteKind::MediumGrass
|
||||||
|
| SpriteKind::ShortGrass
|
||||||
|
| SpriteKind::LargeGrass
|
||||||
|
| SpriteKind::GrassSnow
|
||||||
|
| SpriteKind::GrassBlue
|
||||||
|
| SpriteKind::SavannaGrass
|
||||||
|
| SpriteKind::TallSavannaGrass
|
||||||
|
| SpriteKind::RedSavannaGrass
|
||||||
|
| SpriteKind::JungleRedGrass
|
||||||
|
| SpriteKind::Fern => Some(rtsim::ChunkResource::Grass),
|
||||||
|
SpriteKind::BlueFlower
|
||||||
|
| SpriteKind::PinkFlower
|
||||||
|
| SpriteKind::PurpleFlower
|
||||||
|
| SpriteKind::RedFlower
|
||||||
|
| SpriteKind::WhiteFlower
|
||||||
|
| SpriteKind::YellowFlower
|
||||||
|
| SpriteKind::Sunflower
|
||||||
|
| SpriteKind::Moonbell
|
||||||
|
| SpriteKind::Pyrebloom => Some(rtsim::ChunkResource::Flower),
|
||||||
|
SpriteKind::Reed
|
||||||
|
| SpriteKind::Flax
|
||||||
|
| SpriteKind::WildFlax
|
||||||
|
| SpriteKind::Cotton
|
||||||
|
| SpriteKind::Corn
|
||||||
|
| SpriteKind::WheatYellow
|
||||||
|
| SpriteKind::WheatGreen => Some(rtsim::ChunkResource::Plant),
|
||||||
|
SpriteKind::Apple
|
||||||
|
| SpriteKind::Pumpkin
|
||||||
|
| SpriteKind::Beehive // TODO: Not a fruit, but kind of acts like one
|
||||||
|
| SpriteKind::Coconut => Some(rtsim::ChunkResource::Fruit),
|
||||||
|
SpriteKind::Cabbage
|
||||||
|
| SpriteKind::Carrot
|
||||||
|
| SpriteKind::Tomato
|
||||||
|
| SpriteKind::Radish
|
||||||
|
| SpriteKind::Turnip => Some(rtsim::ChunkResource::Vegetable),
|
||||||
|
SpriteKind::Mushroom
|
||||||
|
| SpriteKind::CaveMushroom
|
||||||
|
| SpriteKind::CeilingMushroom => Some(rtsim::ChunkResource::Mushroom),
|
||||||
|
|
||||||
|
SpriteKind::Chest
|
||||||
|
| SpriteKind::ChestBuried
|
||||||
|
| SpriteKind::PotionMinor
|
||||||
|
| SpriteKind::DungeonChest0
|
||||||
|
| SpriteKind::DungeonChest1
|
||||||
|
| SpriteKind::DungeonChest2
|
||||||
|
| SpriteKind::DungeonChest3
|
||||||
|
| SpriteKind::DungeonChest4
|
||||||
|
| SpriteKind::DungeonChest5
|
||||||
|
| SpriteKind::CoralChest
|
||||||
|
| SpriteKind::Crate => Some(rtsim::ChunkResource::Loot),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_glow(&self) -> Option<u8> {
|
pub fn get_glow(&self) -> Option<u8> {
|
||||||
self.get_glow_raw().or_else(|| self.get_sprite().and_then(|sprite| sprite.get_glow()))
|
self.get_glow_raw().or_else(|| self.get_sprite().and_then(|sprite| sprite.get_glow()))
|
||||||
|
@ -84,7 +84,9 @@ pub trait CoordinateConversions {
|
|||||||
|
|
||||||
impl CoordinateConversions for Vec2<i32> {
|
impl CoordinateConversions for Vec2<i32> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn wpos_to_cpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e / sz as i32) }
|
fn wpos_to_cpos(&self) -> Self {
|
||||||
|
self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.div_euclid(sz as i32))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as i32) }
|
fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as i32) }
|
||||||
@ -223,11 +225,13 @@ pub type TerrainChunk = chonk::Chonk<Block, BlockVec, TerrainChunkSize, TerrainC
|
|||||||
pub type TerrainSubChunk = chonk::SubChunk<Block, BlockVec, TerrainChunkSize, TerrainChunkMeta>;
|
pub type TerrainSubChunk = chonk::SubChunk<Block, BlockVec, TerrainChunkSize, TerrainChunkMeta>;
|
||||||
pub type TerrainGrid = VolGrid2d<TerrainChunk>;
|
pub type TerrainGrid = VolGrid2d<TerrainChunk>;
|
||||||
|
|
||||||
|
const TERRAIN_GRID_SEARCH_DIST: i32 = 63;
|
||||||
|
|
||||||
impl TerrainGrid {
|
impl TerrainGrid {
|
||||||
/// Find a location suitable for spawning an entity near the given
|
/// Find a location suitable for spawning an entity near the given
|
||||||
/// position (but in the same chunk).
|
/// position (but in the same chunk).
|
||||||
pub fn find_space(&self, pos: Vec3<i32>) -> Vec3<i32> {
|
pub fn find_ground(&self, pos: Vec3<i32>) -> Vec3<i32> {
|
||||||
self.try_find_space(pos).unwrap_or(pos)
|
self.try_find_ground(pos).unwrap_or(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_space(&self, pos: Vec3<i32>) -> bool {
|
pub fn is_space(&self, pos: Vec3<i32>) -> bool {
|
||||||
@ -238,8 +242,14 @@ impl TerrainGrid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_find_space(&self, pos: Vec3<i32>) -> Option<Vec3<i32>> {
|
pub fn try_find_space(&self, pos: Vec3<i32>) -> Option<Vec3<i32>> {
|
||||||
const SEARCH_DIST: i32 = 63;
|
(0..TERRAIN_GRID_SEARCH_DIST * 2 + 1)
|
||||||
(0..SEARCH_DIST * 2 + 1)
|
.map(|i| if i % 2 == 0 { i } else { -i } / 2)
|
||||||
|
.map(|z_diff| pos + Vec3::unit_z() * z_diff)
|
||||||
|
.find(|pos| self.is_space(*pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_find_ground(&self, pos: Vec3<i32>) -> Option<Vec3<i32>> {
|
||||||
|
(0..TERRAIN_GRID_SEARCH_DIST * 2 + 1)
|
||||||
.map(|i| if i % 2 == 0 { i } else { -i } / 2)
|
.map(|i| if i % 2 == 0 { i } else { -i } / 2)
|
||||||
.map(|z_diff| pos + Vec3::unit_z() * z_diff)
|
.map(|z_diff| pos + Vec3::unit_z() * z_diff)
|
||||||
.find(|pos| {
|
.find(|pos| {
|
||||||
|
@ -381,31 +381,29 @@ impl SitePrices {
|
|||||||
inventories: &[Option<ReducedInventory>; 2],
|
inventories: &[Option<ReducedInventory>; 2],
|
||||||
who: usize,
|
who: usize,
|
||||||
reduce: bool,
|
reduce: bool,
|
||||||
) -> f32 {
|
) -> Option<f32> {
|
||||||
offers[who]
|
offers[who]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(slot, amount)| {
|
.map(|(slot, amount)| {
|
||||||
inventories[who]
|
inventories[who]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|ri| {
|
.map(|ri| {
|
||||||
ri.inventory.get(slot).map(|item| {
|
let item = ri.inventory.get(slot)?;
|
||||||
if let Some(vec) = TradePricing::get_materials(&item.name.as_ref()) {
|
let vec = TradePricing::get_materials(&item.name.as_ref())?;
|
||||||
vec.iter()
|
Some(
|
||||||
.map(|(amount2, material)| {
|
vec.iter()
|
||||||
self.values.get(material).copied().unwrap_or_default()
|
.map(|(amount2, material)| {
|
||||||
* *amount2
|
self.values.get(material).copied().unwrap_or_default()
|
||||||
* (if reduce { material.trade_margin() } else { 1.0 })
|
* *amount2
|
||||||
})
|
* (if reduce { material.trade_margin() } else { 1.0 })
|
||||||
.sum::<f32>()
|
})
|
||||||
* (*amount as f32)
|
.sum::<f32>()
|
||||||
} else {
|
* (*amount as f32),
|
||||||
0.0
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or(Some(0.0))
|
||||||
})
|
})
|
||||||
.sum()
|
.try_fold(0.0, |a, p| Some(a + p?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,4 +6,4 @@ mod build_areas;
|
|||||||
mod state;
|
mod state;
|
||||||
// TODO: breakup state module and remove glob
|
// TODO: breakup state module and remove glob
|
||||||
pub use build_areas::{BuildAreaError, BuildAreas};
|
pub use build_areas::{BuildAreaError, BuildAreas};
|
||||||
pub use state::{BlockChange, Pools, State, TerrainChanges};
|
pub use state::{BlockChange, BlockDiff, Pools, State, TerrainChanges};
|
||||||
|
@ -114,6 +114,13 @@ impl TerrainChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BlockDiff {
|
||||||
|
pub wpos: Vec3<i32>,
|
||||||
|
pub old: Block,
|
||||||
|
pub new: Block,
|
||||||
|
}
|
||||||
|
|
||||||
/// A type used to represent game state stored on both the client and the
|
/// A type used to represent game state stored on both the client and the
|
||||||
/// server. This includes things like entity components, terrain data, and
|
/// server. This includes things like entity components, terrain data, and
|
||||||
/// global states like weather, time of day, etc.
|
/// global states like weather, time of day, etc.
|
||||||
@ -429,6 +436,7 @@ impl State {
|
|||||||
ecs.register::<comp::Sticky>();
|
ecs.register::<comp::Sticky>();
|
||||||
ecs.register::<comp::Immovable>();
|
ecs.register::<comp::Immovable>();
|
||||||
ecs.register::<comp::CharacterState>();
|
ecs.register::<comp::CharacterState>();
|
||||||
|
ecs.register::<comp::CharacterActivity>();
|
||||||
ecs.register::<comp::Object>();
|
ecs.register::<comp::Object>();
|
||||||
ecs.register::<comp::Group>();
|
ecs.register::<comp::Group>();
|
||||||
ecs.register::<comp::Shockwave>();
|
ecs.register::<comp::Shockwave>();
|
||||||
@ -763,7 +771,9 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply terrain changes
|
// Apply terrain changes
|
||||||
pub fn apply_terrain_changes(&self) { self.apply_terrain_changes_internal(false); }
|
pub fn apply_terrain_changes(&self, block_update: impl FnMut(&specs::World, Vec<BlockDiff>)) {
|
||||||
|
self.apply_terrain_changes_internal(false, block_update);
|
||||||
|
}
|
||||||
|
|
||||||
/// `during_tick` is true if and only if this is called from within
|
/// `during_tick` is true if and only if this is called from within
|
||||||
/// [State::tick].
|
/// [State::tick].
|
||||||
@ -773,7 +783,11 @@ impl State {
|
|||||||
/// from within both the client and the server ticks, right after
|
/// from within both the client and the server ticks, right after
|
||||||
/// handling terrain messages; currently, client sets it to true and
|
/// handling terrain messages; currently, client sets it to true and
|
||||||
/// server to false.
|
/// server to false.
|
||||||
fn apply_terrain_changes_internal(&self, during_tick: bool) {
|
fn apply_terrain_changes_internal(
|
||||||
|
&self,
|
||||||
|
during_tick: bool,
|
||||||
|
mut block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
|
||||||
|
) {
|
||||||
span!(
|
span!(
|
||||||
_guard,
|
_guard,
|
||||||
"apply_terrain_changes",
|
"apply_terrain_changes",
|
||||||
@ -814,17 +828,30 @@ impl State {
|
|||||||
}
|
}
|
||||||
// Apply block modifications
|
// Apply block modifications
|
||||||
// Only include in `TerrainChanges` if successful
|
// Only include in `TerrainChanges` if successful
|
||||||
modified_blocks.retain(|pos, block| {
|
let mut updated_blocks = Vec::with_capacity(modified_blocks.len());
|
||||||
let res = terrain.set(*pos, *block);
|
modified_blocks.retain(|wpos, new| {
|
||||||
if let (&Ok(old_block), true) = (&res, during_tick) {
|
let res = terrain.map(*wpos, |old| {
|
||||||
|
updated_blocks.push(BlockDiff {
|
||||||
|
wpos: *wpos,
|
||||||
|
old,
|
||||||
|
new: *new,
|
||||||
|
});
|
||||||
|
*new
|
||||||
|
});
|
||||||
|
if let (&Ok(old), true) = (&res, during_tick) {
|
||||||
// NOTE: If the changes are applied during the tick, we push the *old* value as
|
// NOTE: If the changes are applied during the tick, we push the *old* value as
|
||||||
// the modified block (since it otherwise can't be recovered after the tick).
|
// the modified block (since it otherwise can't be recovered after the tick).
|
||||||
// Otherwise, the changes will be applied after the tick, so we push the *new*
|
// Otherwise, the changes will be applied after the tick, so we push the *new*
|
||||||
// value.
|
// value.
|
||||||
*block = old_block;
|
*new = old;
|
||||||
}
|
}
|
||||||
res.is_ok()
|
res.is_ok()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if !updated_blocks.is_empty() {
|
||||||
|
block_update(&self.ecs, updated_blocks);
|
||||||
|
}
|
||||||
|
|
||||||
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
|
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -836,6 +863,7 @@ impl State {
|
|||||||
update_terrain_and_regions: bool,
|
update_terrain_and_regions: bool,
|
||||||
mut metrics: Option<&mut StateTickMetrics>,
|
mut metrics: Option<&mut StateTickMetrics>,
|
||||||
server_constants: &ServerConstants,
|
server_constants: &ServerConstants,
|
||||||
|
block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
|
||||||
) {
|
) {
|
||||||
span!(_guard, "tick", "State::tick");
|
span!(_guard, "tick", "State::tick");
|
||||||
|
|
||||||
@ -882,7 +910,7 @@ impl State {
|
|||||||
drop(guard);
|
drop(guard);
|
||||||
|
|
||||||
if update_terrain_and_regions {
|
if update_terrain_and_regions {
|
||||||
self.apply_terrain_changes_internal(true);
|
self.apply_terrain_changes_internal(true, block_update);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process local events
|
// Process local events
|
||||||
|
@ -14,7 +14,7 @@ use common::{
|
|||||||
GroupTarget,
|
GroupTarget,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, ParMode, Phase, System};
|
use common_ecs::{Job, Origin, ParMode, Phase, System};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::Rng;
|
||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::ParallelIterator;
|
||||||
use specs::{
|
use specs::{
|
||||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect,
|
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect,
|
||||||
@ -99,7 +99,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
read_data.uid_allocator.retrieve_entity_internal(uid.into())
|
read_data.uid_allocator.retrieve_entity_internal(uid.into())
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
// Note: rayon makes it difficult to hold onto a thread-local RNG, if grabbing
|
||||||
|
// this becomes a bottleneck we can look into alternatives.
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
if rng.gen_bool(0.005) {
|
if rng.gen_bool(0.005) {
|
||||||
server_events.push(ServerEvent::Sound {
|
server_events.push(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Beam, pos.0, 13.0, time),
|
sound: Sound::new(SoundKind::Beam, pos.0, 13.0, time),
|
||||||
@ -261,6 +263,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
*read_data.time,
|
*read_data.time,
|
||||||
|e| server_events.push(e),
|
|e| server_events.push(e),
|
||||||
|o| outcomes.push(o),
|
|o| outcomes.push(o),
|
||||||
|
&mut rng,
|
||||||
);
|
);
|
||||||
|
|
||||||
add_hit_entities.push((beam_owner, *uid_b));
|
add_hit_entities.push((beam_owner, *uid_b));
|
||||||
|
@ -8,9 +8,9 @@ use common::{
|
|||||||
self,
|
self,
|
||||||
character_state::OutputEvents,
|
character_state::OutputEvents,
|
||||||
inventory::item::{tool::AbilityMap, MaterialStatManifest},
|
inventory::item::{tool::AbilityMap, MaterialStatManifest},
|
||||||
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
|
ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, Controller, Density,
|
||||||
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, SkillSet, Stance,
|
Energy, Health, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos,
|
||||||
StateUpdate, Stats, Vel,
|
Scale, SkillSet, Stance, StateUpdate, Stats, Vel,
|
||||||
},
|
},
|
||||||
event::{EventBus, LocalEvent, ServerEvent},
|
event::{EventBus, LocalEvent, ServerEvent},
|
||||||
link::Is,
|
link::Is,
|
||||||
@ -37,6 +37,7 @@ pub struct ReadData<'a> {
|
|||||||
healths: ReadStorage<'a, Health>,
|
healths: ReadStorage<'a, Health>,
|
||||||
bodies: ReadStorage<'a, Body>,
|
bodies: ReadStorage<'a, Body>,
|
||||||
masses: ReadStorage<'a, Mass>,
|
masses: ReadStorage<'a, Mass>,
|
||||||
|
scales: ReadStorage<'a, Scale>,
|
||||||
physics_states: ReadStorage<'a, PhysicsState>,
|
physics_states: ReadStorage<'a, PhysicsState>,
|
||||||
melee_attacks: ReadStorage<'a, Melee>,
|
melee_attacks: ReadStorage<'a, Melee>,
|
||||||
beams: ReadStorage<'a, Beam>,
|
beams: ReadStorage<'a, Beam>,
|
||||||
@ -64,6 +65,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
type SystemData = (
|
type SystemData = (
|
||||||
ReadData<'a>,
|
ReadData<'a>,
|
||||||
WriteStorage<'a, CharacterState>,
|
WriteStorage<'a, CharacterState>,
|
||||||
|
WriteStorage<'a, CharacterActivity>,
|
||||||
WriteStorage<'a, Pos>,
|
WriteStorage<'a, Pos>,
|
||||||
WriteStorage<'a, Vel>,
|
WriteStorage<'a, Vel>,
|
||||||
WriteStorage<'a, Ori>,
|
WriteStorage<'a, Ori>,
|
||||||
@ -83,6 +85,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
(
|
(
|
||||||
read_data,
|
read_data,
|
||||||
mut character_states,
|
mut character_states,
|
||||||
|
mut character_activities,
|
||||||
mut positions,
|
mut positions,
|
||||||
mut velocities,
|
mut velocities,
|
||||||
mut orientations,
|
mut orientations,
|
||||||
@ -105,6 +108,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
entity,
|
entity,
|
||||||
uid,
|
uid,
|
||||||
mut char_state,
|
mut char_state,
|
||||||
|
character_activity,
|
||||||
pos,
|
pos,
|
||||||
vel,
|
vel,
|
||||||
ori,
|
ori,
|
||||||
@ -115,13 +119,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
controller,
|
controller,
|
||||||
health,
|
health,
|
||||||
body,
|
body,
|
||||||
physics,
|
(physics, scale, stat, skill_set, active_abilities, is_rider),
|
||||||
(stat, skill_set, active_abilities, is_rider),
|
|
||||||
combo,
|
combo,
|
||||||
) in (
|
) in (
|
||||||
&read_data.entities,
|
&read_data.entities,
|
||||||
&read_data.uids,
|
&read_data.uids,
|
||||||
&mut character_states,
|
&mut character_states,
|
||||||
|
&mut character_activities,
|
||||||
&mut positions,
|
&mut positions,
|
||||||
&mut velocities,
|
&mut velocities,
|
||||||
&mut orientations,
|
&mut orientations,
|
||||||
@ -132,8 +136,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
&mut controllers,
|
&mut controllers,
|
||||||
read_data.healths.maybe(),
|
read_data.healths.maybe(),
|
||||||
&read_data.bodies,
|
&read_data.bodies,
|
||||||
&read_data.physics_states,
|
|
||||||
(
|
(
|
||||||
|
&read_data.physics_states,
|
||||||
|
read_data.scales.maybe(),
|
||||||
&read_data.stats,
|
&read_data.stats,
|
||||||
&read_data.skill_sets,
|
&read_data.skill_sets,
|
||||||
read_data.active_abilities.maybe(),
|
read_data.active_abilities.maybe(),
|
||||||
@ -180,9 +185,11 @@ impl<'a> System<'a> for Sys {
|
|||||||
entity,
|
entity,
|
||||||
uid,
|
uid,
|
||||||
char_state,
|
char_state,
|
||||||
|
character_activity,
|
||||||
pos,
|
pos,
|
||||||
vel,
|
vel,
|
||||||
ori,
|
ori,
|
||||||
|
scale,
|
||||||
mass,
|
mass,
|
||||||
density,
|
density,
|
||||||
energy,
|
energy,
|
||||||
@ -258,6 +265,9 @@ impl Sys {
|
|||||||
if *join.char_state != state_update.character {
|
if *join.char_state != state_update.character {
|
||||||
*join.char_state = state_update.character
|
*join.char_state = state_update.character
|
||||||
}
|
}
|
||||||
|
if *join.character_activity != state_update.character_activity {
|
||||||
|
*join.character_activity = state_update.character_activity
|
||||||
|
}
|
||||||
if *join.density != state_update.density {
|
if *join.density != state_update.density {
|
||||||
*join.density = state_update.density
|
*join.density = state_update.density
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use common::{
|
|||||||
comp::{
|
comp::{
|
||||||
ability::Stance,
|
ability::Stance,
|
||||||
agent::{Sound, SoundKind},
|
agent::{Sound, SoundKind},
|
||||||
Body, BuffChange, ControlEvent, Controller, Pos,
|
Body, BuffChange, ControlEvent, Controller, Pos, Scale,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
uid::UidAllocator,
|
uid::UidAllocator,
|
||||||
@ -22,6 +22,7 @@ pub struct ReadData<'a> {
|
|||||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||||
positions: ReadStorage<'a, Pos>,
|
positions: ReadStorage<'a, Pos>,
|
||||||
bodies: ReadStorage<'a, Body>,
|
bodies: ReadStorage<'a, Body>,
|
||||||
|
scales: ReadStorage<'a, Scale>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -91,13 +92,15 @@ impl<'a> System<'a> for Sys {
|
|||||||
},
|
},
|
||||||
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
|
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
|
||||||
ControlEvent::Utterance(kind) => {
|
ControlEvent::Utterance(kind) => {
|
||||||
if let (Some(pos), Some(body)) = (
|
if let (Some(pos), Some(body), scale) = (
|
||||||
read_data.positions.get(entity),
|
read_data.positions.get(entity),
|
||||||
read_data.bodies.get(entity),
|
read_data.bodies.get(entity),
|
||||||
|
read_data.scales.get(entity),
|
||||||
) {
|
) {
|
||||||
let sound = Sound::new(
|
let sound = Sound::new(
|
||||||
SoundKind::Utterance(kind, *body),
|
SoundKind::Utterance(kind, *body),
|
||||||
pos.0 + Vec3::unit_z() * body.eye_height(),
|
pos.0
|
||||||
|
+ Vec3::unit_z() * body.eye_height(scale.map_or(1.0, |s| s.0)),
|
||||||
8.0, // TODO: Come up with a better way of determining this
|
8.0, // TODO: Come up with a better way of determining this
|
||||||
1.0,
|
1.0,
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#![feature(btree_drain_filter)]
|
#![feature(drain_filter)]
|
||||||
#![allow(clippy::option_map_unit_fn)]
|
#![allow(clippy::option_map_unit_fn)]
|
||||||
|
|
||||||
mod aura;
|
mod aura;
|
||||||
|
@ -66,15 +66,17 @@ impl<'a> System<'a> for Sys {
|
|||||||
fn run(_job: &mut Job<Self>, (read_data, mut melee_attacks, outcomes): Self::SystemData) {
|
fn run(_job: &mut Job<Self>, (read_data, mut melee_attacks, outcomes): Self::SystemData) {
|
||||||
let mut server_emitter = read_data.server_bus.emitter();
|
let mut server_emitter = read_data.server_bus.emitter();
|
||||||
let mut outcomes_emitter = outcomes.emitter();
|
let mut outcomes_emitter = outcomes.emitter();
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
// Attacks
|
// Attacks
|
||||||
for (attacker, uid, pos, ori, melee_attack, body) in (
|
for (attacker, uid, pos, ori, melee_attack, body, scale) in (
|
||||||
&read_data.entities,
|
&read_data.entities,
|
||||||
&read_data.uids,
|
&read_data.uids,
|
||||||
&read_data.positions,
|
&read_data.positions,
|
||||||
&read_data.orientations,
|
&read_data.orientations,
|
||||||
&mut melee_attacks,
|
&mut melee_attacks,
|
||||||
&read_data.bodies,
|
&read_data.bodies,
|
||||||
|
read_data.scales.maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
{
|
{
|
||||||
@ -87,7 +89,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
melee_attack.applied = true;
|
melee_attack.applied = true;
|
||||||
|
|
||||||
// Scales
|
// Scales
|
||||||
let eye_pos = pos.0 + Vec3::unit_z() * body.eye_height();
|
let eye_pos = pos.0 + Vec3::unit_z() * body.eye_height(scale.map_or(1.0, |s| s.0));
|
||||||
let scale = read_data.scales.get(attacker).map_or(1.0, |s| s.0);
|
let scale = read_data.scales.get(attacker).map_or(1.0, |s| s.0);
|
||||||
let height = body.height() * scale;
|
let height = body.height() * scale;
|
||||||
// TODO: use Capsule Prisms instead of Cylinders
|
// TODO: use Capsule Prisms instead of Cylinders
|
||||||
@ -239,6 +241,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
*read_data.time,
|
*read_data.time,
|
||||||
|e| server_emitter.emit(e),
|
|e| server_emitter.emit(e),
|
||||||
|o| outcomes_emitter.emit(o),
|
|o| outcomes_emitter.emit(o),
|
||||||
|
&mut rng,
|
||||||
);
|
);
|
||||||
|
|
||||||
if is_applied {
|
if is_applied {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use common::{
|
use common::{
|
||||||
comp::{Body, Controller, InputKind, Ori, Pos, Vel},
|
comp::{Body, ControlAction, Controller, InputKind, Ori, Pos, Scale, Vel},
|
||||||
link::Is,
|
link::Is,
|
||||||
mounting::Mount,
|
mounting::Mount,
|
||||||
uid::UidAllocator,
|
uid::UidAllocator,
|
||||||
@ -24,6 +24,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
WriteStorage<'a, Vel>,
|
WriteStorage<'a, Vel>,
|
||||||
WriteStorage<'a, Ori>,
|
WriteStorage<'a, Ori>,
|
||||||
ReadStorage<'a, Body>,
|
ReadStorage<'a, Body>,
|
||||||
|
ReadStorage<'a, Scale>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const NAME: &'static str = "mount";
|
const NAME: &'static str = "mount";
|
||||||
@ -41,22 +42,24 @@ impl<'a> System<'a> for Sys {
|
|||||||
mut velocities,
|
mut velocities,
|
||||||
mut orientations,
|
mut orientations,
|
||||||
bodies,
|
bodies,
|
||||||
|
scales,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
// For each mount...
|
// For each mount...
|
||||||
for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() {
|
for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() {
|
||||||
// ...find the rider...
|
// ...find the rider...
|
||||||
let Some((inputs, queued_inputs, rider)) = uid_allocator
|
let Some((inputs, actions, rider)) = uid_allocator
|
||||||
.retrieve_entity_internal(is_mount.rider.id())
|
.retrieve_entity_internal(is_mount.rider.id())
|
||||||
.and_then(|rider| {
|
.and_then(|rider| {
|
||||||
controllers
|
controllers
|
||||||
.get_mut(rider)
|
.get_mut(rider)
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
let queued_inputs = c.queued_inputs
|
let actions = c.actions.drain_filter(|action| match action {
|
||||||
// TODO: Formalise ways to pass inputs to mounts
|
ControlAction::StartInput { input: i, .. }
|
||||||
.drain_filter(|i, _| matches!(i, InputKind::Jump | InputKind::Fly | InputKind::Roll))
|
| ControlAction::CancelInput(i) => matches!(i, InputKind::Jump | InputKind::Fly | InputKind::Roll),
|
||||||
.collect();
|
_ => false
|
||||||
(c.inputs.clone(), queued_inputs, rider)
|
}).collect();
|
||||||
|
(c.inputs.clone(), actions, rider)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
else { continue };
|
else { continue };
|
||||||
@ -68,18 +71,17 @@ impl<'a> System<'a> for Sys {
|
|||||||
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
|
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
|
||||||
let mounter_body = bodies.get(rider);
|
let mounter_body = bodies.get(rider);
|
||||||
let mounting_offset = body.map_or(Vec3::unit_z(), Body::mount_offset)
|
let mounting_offset = body.map_or(Vec3::unit_z(), Body::mount_offset)
|
||||||
+ mounter_body.map_or(Vec3::zero(), Body::rider_offset);
|
* scales.get(entity).map_or(1.0, |s| s.0)
|
||||||
|
+ mounter_body.map_or(Vec3::zero(), Body::rider_offset)
|
||||||
|
* scales.get(rider).map_or(1.0, |s| s.0);
|
||||||
let _ = positions.insert(rider, Pos(pos.0 + ori.to_quat() * mounting_offset));
|
let _ = positions.insert(rider, Pos(pos.0 + ori.to_quat() * mounting_offset));
|
||||||
let _ = orientations.insert(rider, ori);
|
let _ = orientations.insert(rider, ori);
|
||||||
let _ = velocities.insert(rider, vel);
|
let _ = velocities.insert(rider, vel);
|
||||||
}
|
}
|
||||||
// ...and apply the rider's inputs to the mount's controller.
|
// ...and apply the rider's inputs to the mount's controller.
|
||||||
if let Some(controller) = controllers.get_mut(entity) {
|
if let Some(controller) = controllers.get_mut(entity) {
|
||||||
*controller = Controller {
|
controller.inputs = inputs;
|
||||||
inputs,
|
controller.actions = actions;
|
||||||
queued_inputs,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ fn integrate_forces(
|
|||||||
mass: &Mass,
|
mass: &Mass,
|
||||||
fluid: &Fluid,
|
fluid: &Fluid,
|
||||||
gravity: f32,
|
gravity: f32,
|
||||||
|
scale: Option<Scale>,
|
||||||
) -> Vel {
|
) -> Vel {
|
||||||
let dim = body.dimensions();
|
let dim = body.dimensions();
|
||||||
let height = dim.z;
|
let height = dim.z;
|
||||||
@ -61,7 +62,13 @@ fn integrate_forces(
|
|||||||
// Aerodynamic/hydrodynamic forces
|
// Aerodynamic/hydrodynamic forces
|
||||||
if !rel_flow.0.is_approx_zero() {
|
if !rel_flow.0.is_approx_zero() {
|
||||||
debug_assert!(!rel_flow.0.map(|a| a.is_nan()).reduce_or());
|
debug_assert!(!rel_flow.0.map(|a| a.is_nan()).reduce_or());
|
||||||
let impulse = dt.0 * body.aerodynamic_forces(&rel_flow, fluid_density.0, wings);
|
let impulse = dt.0
|
||||||
|
* body.aerodynamic_forces(
|
||||||
|
&rel_flow,
|
||||||
|
fluid_density.0,
|
||||||
|
wings,
|
||||||
|
scale.map_or(1.0, |s| s.0),
|
||||||
|
);
|
||||||
debug_assert!(!impulse.map(|a| a.is_nan()).reduce_or());
|
debug_assert!(!impulse.map(|a| a.is_nan()).reduce_or());
|
||||||
if !impulse.is_approx_zero() {
|
if !impulse.is_approx_zero() {
|
||||||
let new_v = vel.0 + impulse / mass.0;
|
let new_v = vel.0 + impulse / mass.0;
|
||||||
@ -610,6 +617,7 @@ impl<'a> PhysicsData<'a> {
|
|||||||
&write.physics_states,
|
&write.physics_states,
|
||||||
&read.masses,
|
&read.masses,
|
||||||
&read.densities,
|
&read.densities,
|
||||||
|
read.scales.maybe(),
|
||||||
!&read.is_ridings,
|
!&read.is_ridings,
|
||||||
)
|
)
|
||||||
.par_join()
|
.par_join()
|
||||||
@ -628,6 +636,7 @@ impl<'a> PhysicsData<'a> {
|
|||||||
physics_state,
|
physics_state,
|
||||||
mass,
|
mass,
|
||||||
density,
|
density,
|
||||||
|
scale,
|
||||||
_,
|
_,
|
||||||
)| {
|
)| {
|
||||||
let in_loaded_chunk = read
|
let in_loaded_chunk = read
|
||||||
@ -672,6 +681,7 @@ impl<'a> PhysicsData<'a> {
|
|||||||
mass,
|
mass,
|
||||||
&fluid,
|
&fluid,
|
||||||
GRAVITY,
|
GRAVITY,
|
||||||
|
scale.copied(),
|
||||||
)
|
)
|
||||||
.0
|
.0
|
||||||
},
|
},
|
||||||
@ -1092,19 +1102,21 @@ impl<'a> PhysicsData<'a> {
|
|||||||
|
|
||||||
// TODO: Cache the matrices here to avoid recomputing
|
// TODO: Cache the matrices here to avoid recomputing
|
||||||
|
|
||||||
let transform_last_from = Mat4::<f32>::translation_3d(
|
let transform_last_from =
|
||||||
previous_cache_other.pos.unwrap_or(*pos_other).0
|
Mat4::<f32>::translation_3d(
|
||||||
- previous_cache.pos.unwrap_or(Pos(wpos)).0,
|
previous_cache_other.pos.unwrap_or(*pos_other).0
|
||||||
) * Mat4::from(
|
- previous_cache.pos.unwrap_or(Pos(wpos)).0,
|
||||||
previous_cache_other.ori,
|
) * Mat4::from(previous_cache_other.ori)
|
||||||
) * Mat4::<f32>::translation_3d(
|
* Mat4::<f32>::scaling_3d(previous_cache_other.scale)
|
||||||
voxel_collider.translation,
|
* Mat4::<f32>::translation_3d(
|
||||||
);
|
voxel_collider.translation,
|
||||||
|
);
|
||||||
let transform_last_to = transform_last_from.inverted();
|
let transform_last_to = transform_last_from.inverted();
|
||||||
|
|
||||||
let transform_from =
|
let transform_from =
|
||||||
Mat4::<f32>::translation_3d(pos_other.0 - wpos)
|
Mat4::<f32>::translation_3d(pos_other.0 - wpos)
|
||||||
* Mat4::from(ori_other.to_quat())
|
* Mat4::from(ori_other.to_quat())
|
||||||
|
* Mat4::<f32>::scaling_3d(previous_cache_other.scale)
|
||||||
* Mat4::<f32>::translation_3d(
|
* Mat4::<f32>::translation_3d(
|
||||||
voxel_collider.translation,
|
voxel_collider.translation,
|
||||||
);
|
);
|
||||||
@ -1350,12 +1362,9 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
|
|||||||
read: &PhysicsRead,
|
read: &PhysicsRead,
|
||||||
ori: &Ori,
|
ori: &Ori,
|
||||||
) {
|
) {
|
||||||
// FIXME: Review these
|
// We cap out scale at 10.0 to prevent an enormous amount of lag
|
||||||
#![allow(
|
let scale = read.scales.get(entity).map_or(1.0, |s| s.0.min(10.0));
|
||||||
clippy::cast_precision_loss,
|
|
||||||
clippy::cast_possible_truncation,
|
|
||||||
clippy::cast_sign_loss
|
|
||||||
)]
|
|
||||||
//prof_span!("box_voxel_collision");
|
//prof_span!("box_voxel_collision");
|
||||||
|
|
||||||
// Convience function to compute the player aabb
|
// Convience function to compute the player aabb
|
||||||
@ -1410,7 +1419,7 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
|
|||||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
fn always_hits(_: &Block) -> bool { true }
|
fn always_hits(_: &Block) -> bool { true }
|
||||||
|
|
||||||
let (radius, z_min, z_max) = cylinder;
|
let (radius, z_min, z_max) = (Vec3::from(cylinder) * scale).into_tuple();
|
||||||
|
|
||||||
// Probe distances
|
// Probe distances
|
||||||
let hdist = radius.ceil() as i32;
|
let hdist = radius.ceil() as i32;
|
||||||
@ -1440,7 +1449,8 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
|
|||||||
|
|
||||||
// Don't jump too far at once
|
// Don't jump too far at once
|
||||||
const MAX_INCREMENTS: usize = 100; // The maximum number of collision tests per tick
|
const MAX_INCREMENTS: usize = 100; // The maximum number of collision tests per tick
|
||||||
let increments = ((pos_delta.map(|e| e.abs()).reduce_partial_max() / 0.3).ceil() as usize)
|
let min_step = (radius / 2.0).min(z_max - z_min).clamped(0.01, 0.3);
|
||||||
|
let increments = ((pos_delta.map(|e| e.abs()).reduce_partial_max() / min_step).ceil() as usize)
|
||||||
.clamped(1, MAX_INCREMENTS);
|
.clamped(1, MAX_INCREMENTS);
|
||||||
let old_pos = pos.0;
|
let old_pos = pos.0;
|
||||||
for _ in 0..increments {
|
for _ in 0..increments {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user