mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/boss' into 'master'
Boss Closes #769 See merge request veloren/veloren!1364
This commit is contained in:
commit
b2f42a117c
CHANGELOG.md
assets
common/items/npc_weapons/npcweapon
voxygen
client/src
common/src
server/src
tools/src
voxygen/src
audio/sfx/event_mapper/combat
hud
render/pipelines
scene
world/src/site/dungeon
@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fireflies
|
||||
- Fullscreen modes now show two options (exclusive and borderless)
|
||||
- Added banlist and `/ban`, `/unban`, and `/kick` commands for admins
|
||||
- A new dungeon boss (venture there and discover it yourself)
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -0,0 +1,13 @@
|
||||
ItemDef(
|
||||
name: "Stone Golem's Fist",
|
||||
description: "Was attached to a mighty stone golem.",
|
||||
kind: Tool(
|
||||
(
|
||||
kind: NpcWeapon("StoneGolemsFist"),
|
||||
stats: (
|
||||
equip_time_millis: 500,
|
||||
power: 1.00,
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
@ -47,13 +47,14 @@ const int FIREWORK_YELLOW = 8;
|
||||
const int LEAF = 9;
|
||||
const int FIREFLY = 10;
|
||||
const int BEE = 11;
|
||||
const int GROUND_SHOCKWAVE = 12;
|
||||
|
||||
// meters per second squared (acceleration)
|
||||
const float earth_gravity = 9.807;
|
||||
|
||||
struct Attr {
|
||||
vec3 offs;
|
||||
float scale;
|
||||
vec3 scale;
|
||||
vec4 col;
|
||||
mat4 rot;
|
||||
};
|
||||
@ -122,7 +123,7 @@ void main() {
|
||||
vec3(0),
|
||||
vec3(rand2 * 0.02, rand3 * 0.02, 1.0 + rand4 * 0.1)
|
||||
),
|
||||
linear_scale(0.5),
|
||||
vec3(linear_scale(0.5)),
|
||||
vec4(1, 1, 1, start_end(1.0, 0.0)),
|
||||
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 0.5)
|
||||
);
|
||||
@ -132,7 +133,7 @@ void main() {
|
||||
vec3(rand0 * 0.25, rand1 * 0.25, 0.3),
|
||||
vec3(rand2 * 0.1, rand3 * 0.1, 2.0 + rand4 * 1.0)
|
||||
),
|
||||
1.0,
|
||||
vec3(1.0),
|
||||
vec4(2, 0.8 + rand5 * 0.3, 0, 1),
|
||||
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
|
||||
);
|
||||
@ -142,7 +143,7 @@ void main() {
|
||||
vec3(rand0, rand1, rand3) * 0.3,
|
||||
vec3(rand4, rand5, rand6) * 2.0 + grav_vel(earth_gravity)
|
||||
),
|
||||
1.0,
|
||||
vec3(1.0),
|
||||
vec4(3.5, 3 + rand7, 0, 1),
|
||||
spin_in_axis(vec3(1,0,0),0)
|
||||
);
|
||||
@ -152,7 +153,7 @@ void main() {
|
||||
vec3(0),
|
||||
vec3(rand4, rand5, rand6) * 40.0 + grav_vel(earth_gravity)
|
||||
),
|
||||
3.0 + rand0,
|
||||
vec3(3.0 + rand0),
|
||||
vec4(vec3(0.6 + rand7 * 0.4), 1),
|
||||
spin_in_axis(vec3(1,0,0),0)
|
||||
);
|
||||
@ -162,7 +163,7 @@ void main() {
|
||||
vec3(0),
|
||||
vec3(rand1, rand2, rand3) * 40.0 + grav_vel(earth_gravity)
|
||||
),
|
||||
3.0 + rand0,
|
||||
vec3(3.0 + rand0),
|
||||
vec4(0.15, 0.4, 1, 1),
|
||||
identity()
|
||||
);
|
||||
@ -172,7 +173,7 @@ void main() {
|
||||
vec3(0),
|
||||
vec3(rand1, rand2, rand3) * 40.0 + grav_vel(earth_gravity)
|
||||
),
|
||||
3.0 + rand0,
|
||||
vec3(3.0 + rand0),
|
||||
vec4(0, 1, 0, 1),
|
||||
identity()
|
||||
);
|
||||
@ -182,7 +183,7 @@ void main() {
|
||||
vec3(0),
|
||||
vec3(rand1, rand2, rand3) * 40.0 + grav_vel(earth_gravity)
|
||||
),
|
||||
3.0 + rand0,
|
||||
vec3(3.0 + rand0),
|
||||
vec4(0.7, 0.0, 1.0, 1.0),
|
||||
identity()
|
||||
);
|
||||
@ -192,7 +193,7 @@ void main() {
|
||||
vec3(0),
|
||||
vec3(rand1, rand2, rand3) * 40.0 + grav_vel(earth_gravity)
|
||||
),
|
||||
3.0 + rand0,
|
||||
vec3(3.0 + rand0),
|
||||
vec4(1, 0, 0, 1),
|
||||
identity()
|
||||
);
|
||||
@ -202,7 +203,7 @@ void main() {
|
||||
vec3(0),
|
||||
vec3(rand1, rand2, rand3) * 40.0 + grav_vel(earth_gravity)
|
||||
),
|
||||
3.0 + rand0,
|
||||
vec3(3.0 + rand0),
|
||||
vec4(1, 1, 0, 1),
|
||||
identity()
|
||||
);
|
||||
@ -212,7 +213,7 @@ void main() {
|
||||
vec3(0),
|
||||
vec3(0, 0, -2)
|
||||
) + vec3(sin(lifetime), sin(lifetime + 0.7), sin(lifetime * 0.5)) * 2.0,
|
||||
4,
|
||||
vec3(4),
|
||||
vec4(vec3(0.2 + rand7 * 0.2, 0.2 + (0.5 + rand6 * 0.5) * 0.6, 0), 1),
|
||||
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
|
||||
);
|
||||
@ -224,7 +225,7 @@ void main() {
|
||||
sin(lifetime * 3.0 + rand1) + sin(lifetime * 8.0 + rand4) * 0.3,
|
||||
sin(lifetime * 2.0 + rand2) + sin(lifetime * 9.0 + rand5) * 0.3
|
||||
),
|
||||
raise,
|
||||
vec3(raise),
|
||||
vec4(vec3(5, 5, 1.1), 1),
|
||||
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
|
||||
);
|
||||
@ -236,17 +237,24 @@ void main() {
|
||||
sin(lifetime * 3.0 + rand1) + sin(lifetime * 10.0 + rand4) * 0.3,
|
||||
sin(lifetime * 4.0 + rand2) + sin(lifetime * 11.0 + rand5) * 0.3
|
||||
) * 0.5,
|
||||
lower,
|
||||
vec3(lower),
|
||||
vec4(vec3(1, 0.7, 0), 1),
|
||||
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
|
||||
);
|
||||
} else if (inst_mode == GROUND_SHOCKWAVE) {
|
||||
attr = Attr(
|
||||
vec3(0.0),
|
||||
vec3(11.0, 11.0, (33.0 * rand0 * sin(2.0 * lifetime * 3.14 * 2.0))) / 3,
|
||||
vec4(vec3(0.32 + (rand0 * 0.04), 0.22 + (rand1 * 0.03), 0.05 + (rand2 * 0.01)), 1),
|
||||
spin_in_axis(vec3(1,0,0),0)
|
||||
);
|
||||
} else {
|
||||
attr = Attr(
|
||||
linear_motion(
|
||||
vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5),
|
||||
vec3(rand2 * 0.1, rand3 * 0.1, 1.0 + rand4 * 0.5)
|
||||
),
|
||||
exp_scale(-0.2),
|
||||
vec3(exp_scale(-0.2)),
|
||||
vec4(1),
|
||||
spin_in_axis(vec3(1,0,0),0)
|
||||
);
|
||||
|
BIN
assets/voxygen/voxel/weapon/npcweapon/cyclops_hammer.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/weapon/npcweapon/cyclops_hammer.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -22,6 +22,7 @@ use common::{
|
||||
group, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip,
|
||||
InventoryManip, InventoryUpdateEvent,
|
||||
},
|
||||
event::{EventBus, LocalEvent},
|
||||
msg::{
|
||||
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, DisconnectReason,
|
||||
InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError,
|
||||
@ -1426,6 +1427,15 @@ impl Client {
|
||||
ServerMsg::Outcomes(outcomes) => {
|
||||
frontend_events.extend(outcomes.into_iter().map(Event::Outcome))
|
||||
},
|
||||
ServerMsg::Knockback(impulse) => {
|
||||
self.state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<LocalEvent>>()
|
||||
.emit_now(LocalEvent::ApplyImpulse {
|
||||
entity: self.entity,
|
||||
impulse,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ pub enum CharacterAbilityType {
|
||||
TripleStrike(Stage),
|
||||
LeapMelee,
|
||||
SpinMelee,
|
||||
GroundShockwave,
|
||||
}
|
||||
|
||||
impl From<&CharacterState> for CharacterAbilityType {
|
||||
@ -38,6 +39,7 @@ impl From<&CharacterState> for CharacterAbilityType {
|
||||
CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage),
|
||||
CharacterState::SpinMelee(_) => Self::SpinMelee,
|
||||
CharacterState::ChargedRanged(_) => Self::ChargedRanged,
|
||||
CharacterState::GroundShockwave(_) => Self::ChargedRanged,
|
||||
_ => Self::BasicMelee,
|
||||
}
|
||||
}
|
||||
@ -50,6 +52,7 @@ pub enum CharacterAbility {
|
||||
buildup_duration: Duration,
|
||||
recover_duration: Duration,
|
||||
base_healthchange: i32,
|
||||
knockback: f32,
|
||||
range: f32,
|
||||
max_angle: f32,
|
||||
},
|
||||
@ -62,6 +65,7 @@ pub enum CharacterAbility {
|
||||
projectile_body: Body,
|
||||
projectile_light: Option<LightEmitter>,
|
||||
projectile_gravity: Option<Gravity>,
|
||||
projectile_speed: f32,
|
||||
},
|
||||
Boost {
|
||||
duration: Duration,
|
||||
@ -104,6 +108,20 @@ pub enum CharacterAbility {
|
||||
recover_duration: Duration,
|
||||
projectile_body: Body,
|
||||
projectile_light: Option<LightEmitter>,
|
||||
projectile_gravity: Option<Gravity>,
|
||||
initial_projectile_speed: f32,
|
||||
max_projectile_speed: f32,
|
||||
},
|
||||
GroundShockwave {
|
||||
energy_cost: u32,
|
||||
buildup_duration: Duration,
|
||||
recover_duration: Duration,
|
||||
damage: u32,
|
||||
knockback: f32,
|
||||
shockwave_angle: f32,
|
||||
shockwave_speed: f32,
|
||||
shockwave_duration: Duration,
|
||||
requires_ground: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@ -150,6 +168,10 @@ impl CharacterAbility {
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::GroundShockwave { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
@ -250,6 +272,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
buildup_duration,
|
||||
recover_duration,
|
||||
base_healthchange,
|
||||
knockback,
|
||||
range,
|
||||
max_angle,
|
||||
energy_cost: _,
|
||||
@ -258,6 +281,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
buildup_duration: *buildup_duration,
|
||||
recover_duration: *recover_duration,
|
||||
base_healthchange: *base_healthchange,
|
||||
knockback: *knockback,
|
||||
range: *range,
|
||||
max_angle: *max_angle,
|
||||
}),
|
||||
@ -269,6 +293,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
projectile_body,
|
||||
projectile_light,
|
||||
projectile_gravity,
|
||||
projectile_speed,
|
||||
energy_cost: _,
|
||||
} => CharacterState::BasicRanged(basic_ranged::Data {
|
||||
exhausted: false,
|
||||
@ -280,6 +305,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
projectile_body: *projectile_body,
|
||||
projectile_light: *projectile_light,
|
||||
projectile_gravity: *projectile_gravity,
|
||||
projectile_speed: *projectile_speed,
|
||||
}),
|
||||
CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data {
|
||||
duration: *duration,
|
||||
@ -363,6 +389,9 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
recover_duration,
|
||||
projectile_body,
|
||||
projectile_light,
|
||||
projectile_gravity,
|
||||
initial_projectile_speed,
|
||||
max_projectile_speed,
|
||||
} => CharacterState::ChargedRanged(charged_ranged::Data {
|
||||
exhausted: false,
|
||||
energy_drain: *energy_drain,
|
||||
@ -376,6 +405,30 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
recover_duration: *recover_duration,
|
||||
projectile_body: *projectile_body,
|
||||
projectile_light: *projectile_light,
|
||||
projectile_gravity: *projectile_gravity,
|
||||
initial_projectile_speed: *initial_projectile_speed,
|
||||
max_projectile_speed: *max_projectile_speed,
|
||||
}),
|
||||
CharacterAbility::GroundShockwave {
|
||||
energy_cost: _,
|
||||
buildup_duration,
|
||||
recover_duration,
|
||||
damage,
|
||||
knockback,
|
||||
shockwave_angle,
|
||||
shockwave_speed,
|
||||
shockwave_duration,
|
||||
requires_ground,
|
||||
} => CharacterState::GroundShockwave(ground_shockwave::Data {
|
||||
exhausted: false,
|
||||
buildup_duration: *buildup_duration,
|
||||
recover_duration: *recover_duration,
|
||||
damage: *damage,
|
||||
knockback: *knockback,
|
||||
shockwave_angle: *shockwave_angle,
|
||||
shockwave_speed: *shockwave_speed,
|
||||
shockwave_duration: *shockwave_duration,
|
||||
requires_ground: *requires_ground,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ impl Body {
|
||||
_ => 1000,
|
||||
},
|
||||
Body::Object(_) => 10000,
|
||||
Body::Golem(_) => 1500,
|
||||
Body::Golem(_) => 2560,
|
||||
Body::Theropod(_) => 50,
|
||||
Body::QuadrupedLow(quadruped_low) => match quadruped_low.species {
|
||||
quadruped_low::Species::Crocodile => 600,
|
||||
@ -330,7 +330,7 @@ impl Body {
|
||||
_ => 100,
|
||||
},
|
||||
Body::Object(_) => 10,
|
||||
Body::Golem(_) => 150,
|
||||
Body::Golem(_) => 260,
|
||||
Body::Theropod(_) => 20,
|
||||
Body::QuadrupedLow(quadruped_low) => match quadruped_low.species {
|
||||
quadruped_low::Species::Crocodile => 20,
|
||||
@ -395,7 +395,7 @@ impl Body {
|
||||
_ => 100,
|
||||
},
|
||||
Body::Object(_) => 1,
|
||||
Body::Golem(_) => 75,
|
||||
Body::Golem(_) => 256,
|
||||
Body::Theropod(_) => 2,
|
||||
Body::QuadrupedLow(quadruped_low) => match quadruped_low.species {
|
||||
quadruped_low::Species::Crocodile => 10,
|
||||
@ -425,7 +425,7 @@ impl Body {
|
||||
Body::FishSmall(_) => 1,
|
||||
Body::BipedLarge(_) => 2,
|
||||
Body::Object(_) => 0,
|
||||
Body::Golem(_) => 5,
|
||||
Body::Golem(_) => 12,
|
||||
Body::Theropod(_) => 1,
|
||||
Body::QuadrupedLow(_) => 1,
|
||||
}
|
||||
|
@ -69,6 +69,8 @@ pub enum CharacterState {
|
||||
SpinMelee(spin_melee::Data),
|
||||
/// A charged ranged attack (e.g. bow)
|
||||
ChargedRanged(charged_ranged::Data),
|
||||
/// A ground shockwave attack
|
||||
GroundShockwave(ground_shockwave::Data),
|
||||
}
|
||||
|
||||
impl CharacterState {
|
||||
@ -83,6 +85,7 @@ impl CharacterState {
|
||||
| CharacterState::LeapMelee(_)
|
||||
| CharacterState::SpinMelee(_)
|
||||
| CharacterState::ChargedRanged(_)
|
||||
| CharacterState::GroundShockwave(_)
|
||||
)
|
||||
}
|
||||
|
||||
@ -95,6 +98,7 @@ impl CharacterState {
|
||||
| CharacterState::LeapMelee(_)
|
||||
| CharacterState::SpinMelee(_)
|
||||
| CharacterState::ChargedRanged(_)
|
||||
| CharacterState::GroundShockwave(_)
|
||||
)
|
||||
}
|
||||
|
||||
@ -107,6 +111,7 @@ impl CharacterState {
|
||||
| CharacterState::BasicBlock
|
||||
| CharacterState::LeapMelee(_)
|
||||
| CharacterState::ChargedRanged(_)
|
||||
| CharacterState::GroundShockwave(_)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ pub enum DamageSource {
|
||||
Projectile,
|
||||
Explosion,
|
||||
Falling,
|
||||
Shockwave,
|
||||
}
|
||||
|
||||
impl Damage {
|
||||
@ -35,7 +36,9 @@ impl Damage {
|
||||
self.healthchange *= 1.0 - damage_reduction;
|
||||
|
||||
// Critical damage applies after armor for melee
|
||||
self.healthchange += critdamage;
|
||||
if (damage_reduction - 1.0).abs() > f32::EPSILON {
|
||||
self.healthchange += critdamage;
|
||||
}
|
||||
|
||||
// Min damage
|
||||
if (damage_reduction - 1.0).abs() > f32::EPSILON && self.healthchange > -10.0 {
|
||||
@ -74,6 +77,16 @@ impl Damage {
|
||||
self.healthchange = -10.0;
|
||||
}
|
||||
},
|
||||
DamageSource::Shockwave => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.get_damage_reduction();
|
||||
self.healthchange *= 1.0 - damage_reduction;
|
||||
|
||||
// Min damage
|
||||
if (damage_reduction - 1.0).abs() > f32::EPSILON && self.healthchange > -10.0 {
|
||||
self.healthchange = -10.0;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ pub enum ToolKind {
|
||||
Dagger(String),
|
||||
Staff(String),
|
||||
Shield(String),
|
||||
NpcWeapon(String),
|
||||
Debug(String),
|
||||
Farming(String),
|
||||
/// This is an placeholder item, it is used by non-humanoid npcs to attack
|
||||
@ -32,6 +33,7 @@ impl ToolKind {
|
||||
ToolKind::Dagger(_) => Hands::OneHand,
|
||||
ToolKind::Staff(_) => Hands::TwoHand,
|
||||
ToolKind::Shield(_) => Hands::OneHand,
|
||||
ToolKind::NpcWeapon(_) => Hands::TwoHand,
|
||||
ToolKind::Debug(_) => Hands::TwoHand,
|
||||
ToolKind::Farming(_) => Hands::TwoHand,
|
||||
ToolKind::Empty => Hands::OneHand,
|
||||
@ -53,6 +55,7 @@ pub enum ToolCategory {
|
||||
Dagger,
|
||||
Staff,
|
||||
Shield,
|
||||
NpcWeapon,
|
||||
Debug,
|
||||
Farming,
|
||||
Empty,
|
||||
@ -68,6 +71,7 @@ impl From<&ToolKind> for ToolCategory {
|
||||
ToolKind::Dagger(_) => ToolCategory::Dagger,
|
||||
ToolKind::Staff(_) => ToolCategory::Staff,
|
||||
ToolKind::Shield(_) => ToolCategory::Shield,
|
||||
ToolKind::NpcWeapon(_) => ToolCategory::NpcWeapon,
|
||||
ToolKind::Debug(_) => ToolCategory::Debug,
|
||||
ToolKind::Farming(_) => ToolCategory::Farming,
|
||||
ToolKind::Empty => ToolCategory::Empty,
|
||||
@ -141,6 +145,7 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(700),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_healthchange: (-120.0 * self.base_power()) as i32,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 20.0,
|
||||
},
|
||||
@ -157,6 +162,7 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(700),
|
||||
recover_duration: Duration::from_millis(150),
|
||||
base_healthchange: (-50.0 * self.base_power()) as i32,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 20.0,
|
||||
}],
|
||||
@ -181,6 +187,7 @@ impl Tool {
|
||||
projectile_body: Body::Object(object::Body::Arrow),
|
||||
projectile_light: None,
|
||||
projectile_gravity: Some(Gravity(0.2)),
|
||||
projectile_speed: 100.0,
|
||||
},
|
||||
ChargedRanged {
|
||||
energy_cost: 0,
|
||||
@ -194,6 +201,9 @@ impl Tool {
|
||||
recover_duration: Duration::from_millis(500),
|
||||
projectile_body: Body::Object(object::Body::MultiArrow),
|
||||
projectile_light: None,
|
||||
projectile_gravity: Some(Gravity(0.2)),
|
||||
initial_projectile_speed: 100.0,
|
||||
max_projectile_speed: 500.0,
|
||||
},
|
||||
],
|
||||
Dagger(_) => vec![
|
||||
@ -202,6 +212,7 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(400),
|
||||
base_healthchange: (-50.0 * self.base_power()) as i32,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 20.0,
|
||||
},
|
||||
@ -220,6 +231,7 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_healthchange: (-10.0 * self.base_power()) as i32,
|
||||
knockback: 0.0,
|
||||
range: 5.0,
|
||||
max_angle: 20.0,
|
||||
},
|
||||
@ -228,6 +240,7 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(1000),
|
||||
base_healthchange: (150.0 * self.base_power()) as i32,
|
||||
knockback: 0.0,
|
||||
range: 100.0,
|
||||
max_angle: 90.0,
|
||||
},
|
||||
@ -239,6 +252,7 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_healthchange: (-10.0 * self.base_power()) as i32,
|
||||
knockback: 0.0,
|
||||
range: 5.0,
|
||||
max_angle: 20.0,
|
||||
},
|
||||
@ -247,6 +261,7 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(1000),
|
||||
base_healthchange: (350.0 * self.base_power()) as i32,
|
||||
knockback: 0.0,
|
||||
range: 100.0,
|
||||
max_angle: 90.0,
|
||||
},
|
||||
@ -258,6 +273,7 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_healthchange: (-40.0 * self.base_power()) as i32,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 20.0,
|
||||
},
|
||||
@ -284,6 +300,7 @@ impl Tool {
|
||||
}),
|
||||
|
||||
projectile_gravity: None,
|
||||
projectile_speed: 100.0,
|
||||
},
|
||||
BasicRanged {
|
||||
energy_cost: 400,
|
||||
@ -314,6 +331,7 @@ impl Tool {
|
||||
}),
|
||||
|
||||
projectile_gravity: None,
|
||||
projectile_speed: 100.0,
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -324,11 +342,48 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(400),
|
||||
base_healthchange: (-40.0 * self.base_power()) as i32,
|
||||
knockback: 0.0,
|
||||
range: 3.0,
|
||||
max_angle: 120.0,
|
||||
},
|
||||
BasicBlock,
|
||||
],
|
||||
NpcWeapon(kind) => {
|
||||
if kind == "StoneGolemsFist" {
|
||||
vec![
|
||||
BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
recover_duration: Duration::from_millis(250),
|
||||
knockback: 25.0,
|
||||
base_healthchange: -200,
|
||||
range: 5.0,
|
||||
max_angle: 120.0,
|
||||
},
|
||||
GroundShockwave {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
recover_duration: Duration::from_millis(1000),
|
||||
damage: 500,
|
||||
knockback: -40.0,
|
||||
shockwave_angle: 90.0,
|
||||
shockwave_speed: 20.0,
|
||||
shockwave_duration: Duration::from_millis(2000),
|
||||
requires_ground: true,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
vec![BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_healthchange: -10,
|
||||
knockback: 0.0,
|
||||
range: 1.0,
|
||||
max_angle: 30.0,
|
||||
}]
|
||||
}
|
||||
},
|
||||
Debug(kind) => {
|
||||
if kind == "Boost" {
|
||||
vec![
|
||||
@ -361,6 +416,7 @@ impl Tool {
|
||||
..Default::default()
|
||||
}),
|
||||
projectile_gravity: None,
|
||||
projectile_speed: 100.0,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
@ -372,6 +428,7 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(1000),
|
||||
base_healthchange: -20,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 15.0,
|
||||
}],
|
||||
|
@ -16,6 +16,7 @@ mod misc;
|
||||
mod phys;
|
||||
mod player;
|
||||
pub mod projectile;
|
||||
pub mod shockwave;
|
||||
pub mod skills;
|
||||
mod stats;
|
||||
pub mod visual;
|
||||
@ -51,6 +52,7 @@ pub use misc::Object;
|
||||
pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};
|
||||
pub use player::{Player, MAX_MOUNT_RANGE_SQR};
|
||||
pub use projectile::Projectile;
|
||||
pub use shockwave::Shockwave;
|
||||
pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet};
|
||||
pub use stats::{Exp, HealthChange, HealthSource, Level, Stats};
|
||||
pub use visual::{LightAnimation, LightEmitter};
|
||||
|
36
common/src/comp/shockwave.rs
Normal file
36
common/src/comp/shockwave.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use crate::sync::Uid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Properties {
|
||||
pub angle: f32,
|
||||
pub speed: f32,
|
||||
pub damage: u32,
|
||||
pub knockback: f32,
|
||||
pub requires_ground: bool,
|
||||
pub duration: Duration,
|
||||
pub owner: Option<Uid>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Shockwave {
|
||||
pub properties: Properties,
|
||||
#[serde(skip)]
|
||||
/// Time that the shockwave was created at
|
||||
/// Used to calculate shockwave propagation
|
||||
/// Deserialized from the network as `None`
|
||||
pub creation: Option<f64>,
|
||||
}
|
||||
|
||||
impl Component for Shockwave {
|
||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Shockwave {
|
||||
type Target = Properties;
|
||||
|
||||
fn deref(&self) -> &Properties { &self.properties }
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
use crate::{character::CharacterId, comp, sync::Uid, util::Dir};
|
||||
use comp::item::{Item, Reagent};
|
||||
use comp::{
|
||||
item::{Item, Reagent},
|
||||
Ori, Pos,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use specs::Entity as EcsEntity;
|
||||
use std::{collections::VecDeque, ops::DerefMut};
|
||||
@ -8,8 +11,11 @@ use vek::*;
|
||||
pub enum LocalEvent {
|
||||
/// Applies upward force to entity's `Vel`
|
||||
Jump(EcsEntity),
|
||||
/// Applies the `force` to `entity`'s `Vel`
|
||||
ApplyForce { entity: EcsEntity, force: Vec3<f32> },
|
||||
/// Applies the `impulse` to `entity`'s `Vel`
|
||||
ApplyImpulse {
|
||||
entity: EcsEntity,
|
||||
impulse: Vec3<f32>,
|
||||
},
|
||||
/// Applies leaping force to `entity`'s `Vel` away from `wall_dir` direction
|
||||
WallLeap {
|
||||
entity: EcsEntity,
|
||||
@ -46,6 +52,16 @@ pub enum ServerEvent {
|
||||
light: Option<comp::LightEmitter>,
|
||||
projectile: comp::Projectile,
|
||||
gravity: Option<comp::Gravity>,
|
||||
speed: f32,
|
||||
},
|
||||
Shockwave {
|
||||
properties: comp::shockwave::Properties,
|
||||
pos: Pos,
|
||||
ori: Ori,
|
||||
},
|
||||
Knockback {
|
||||
entity: EcsEntity,
|
||||
impulse: Vec3<f32>,
|
||||
},
|
||||
LandOnGround {
|
||||
entity: EcsEntity,
|
||||
|
@ -1,4 +1,9 @@
|
||||
use crate::comp::{item::Item, Body, CharacterAbility, ItemConfig, Loadout};
|
||||
use crate::comp::{
|
||||
golem,
|
||||
item::{Item, ItemKind},
|
||||
Alignment, Body, CharacterAbility, ItemConfig, Loadout,
|
||||
};
|
||||
use rand::Rng;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Builder for character Loadouts, containing weapon and armour items belonging
|
||||
@ -60,6 +65,151 @@ impl LoadoutBuilder {
|
||||
)))
|
||||
}
|
||||
|
||||
/// Builds loadout of creature when spawned
|
||||
pub fn build_loadout(body: Body, alignment: Alignment, mut main_tool: Option<Item>) -> Self {
|
||||
#![allow(clippy::single_match)] // For when this is done to more than just golems.
|
||||
match body {
|
||||
Body::Golem(golem) => match golem.species {
|
||||
golem::Species::StoneGolem => {
|
||||
main_tool = Some(Item::new_from_asset_expect(
|
||||
"common.items.npc_weapons.npcweapon.stone_golems_fist",
|
||||
));
|
||||
},
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
|
||||
let active_item = if let Some(ItemKind::Tool(tool)) = main_tool.as_ref().map(|i| i.kind()) {
|
||||
let mut abilities = tool.get_abilities();
|
||||
let mut ability_drain = abilities.drain(..);
|
||||
|
||||
main_tool.map(|item| ItemConfig {
|
||||
item,
|
||||
ability1: ability_drain.next(),
|
||||
ability2: ability_drain.next(),
|
||||
ability3: ability_drain.next(),
|
||||
block_ability: None,
|
||||
dodge_ability: Some(CharacterAbility::Roll),
|
||||
})
|
||||
} else {
|
||||
Some(ItemConfig {
|
||||
// We need the empty item so npcs can attack
|
||||
item: Item::new_from_asset_expect("common.items.weapons.empty.empty"),
|
||||
ability1: Some(CharacterAbility::BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(400),
|
||||
base_healthchange: -40,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 15.0,
|
||||
}),
|
||||
ability2: None,
|
||||
ability3: None,
|
||||
block_ability: None,
|
||||
dodge_ability: None,
|
||||
})
|
||||
};
|
||||
|
||||
let loadout = match body {
|
||||
Body::Humanoid(_) => match alignment {
|
||||
Alignment::Npc => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: None,
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
match rand::thread_rng().gen_range(0, 10) {
|
||||
0 => "common.items.armor.chest.worker_green_0",
|
||||
1 => "common.items.armor.chest.worker_green_1",
|
||||
2 => "common.items.armor.chest.worker_red_0",
|
||||
3 => "common.items.armor.chest.worker_red_1",
|
||||
4 => "common.items.armor.chest.worker_purple_0",
|
||||
5 => "common.items.armor.chest.worker_purple_1",
|
||||
6 => "common.items.armor.chest.worker_yellow_0",
|
||||
7 => "common.items.armor.chest.worker_yellow_1",
|
||||
8 => "common.items.armor.chest.worker_orange_0",
|
||||
_ => "common.items.armor.chest.worker_orange_1",
|
||||
},
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.leather_0",
|
||||
)),
|
||||
hand: None,
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.worker_blue_0",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
match rand::thread_rng().gen_range(0, 2) {
|
||||
0 => "common.items.armor.foot.leather_0",
|
||||
_ => "common.items.armor.starter.sandals_0",
|
||||
},
|
||||
)),
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: None,
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
Alignment::Enemy => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.shoulder.cultist_shoulder_purple",
|
||||
)),
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.chest.cultist_chest_purple",
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.cultist_belt",
|
||||
)),
|
||||
hand: Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.hand.cultist_hands_purple",
|
||||
)),
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.cultist_legs_purple",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.foot.cultist_boots",
|
||||
)),
|
||||
back: Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.back.dungeon_purple-0",
|
||||
)),
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
_ => LoadoutBuilder::animal(body).build(),
|
||||
},
|
||||
Body::Golem(golem) => match golem.species {
|
||||
golem::Species::StoneGolem => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: None,
|
||||
chest: None,
|
||||
belt: None,
|
||||
hand: None,
|
||||
pants: None,
|
||||
foot: None,
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: None,
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
},
|
||||
_ => LoadoutBuilder::animal(body).build(),
|
||||
};
|
||||
|
||||
Self(loadout)
|
||||
}
|
||||
|
||||
/// Default animal configuration
|
||||
pub fn animal(body: Body) -> Self {
|
||||
Self(Loadout {
|
||||
@ -70,6 +220,7 @@ impl LoadoutBuilder {
|
||||
buildup_duration: Duration::from_millis(600),
|
||||
recover_duration: Duration::from_millis(100),
|
||||
base_healthchange: -(body.base_dmg() as i32),
|
||||
knockback: 0.0,
|
||||
range: body.base_range(),
|
||||
max_angle: 20.0,
|
||||
}),
|
||||
|
@ -29,6 +29,7 @@ sum_type! {
|
||||
Pos(comp::Pos),
|
||||
Vel(comp::Vel),
|
||||
Ori(comp::Ori),
|
||||
Shockwave(comp::Shockwave),
|
||||
}
|
||||
}
|
||||
// Automatically derive From<T> for EcsCompPhantom
|
||||
@ -56,6 +57,7 @@ sum_type! {
|
||||
Pos(PhantomData<comp::Pos>),
|
||||
Vel(PhantomData<comp::Vel>),
|
||||
Ori(PhantomData<comp::Ori>),
|
||||
Shockwave(PhantomData<comp::Shockwave>),
|
||||
}
|
||||
}
|
||||
impl sync::CompPacket for EcsCompPacket {
|
||||
@ -83,6 +85,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Pos(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Ori(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world),
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,6 +111,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Pos(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Ori(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world),
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,6 +141,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPhantom::Pos(_) => sync::handle_remove::<comp::Pos>(entity, world),
|
||||
EcsCompPhantom::Vel(_) => sync::handle_remove::<comp::Vel>(entity, world),
|
||||
EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world),
|
||||
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -254,6 +254,7 @@ pub enum ServerMsg {
|
||||
Notification(Notification),
|
||||
SetViewDistance(u32),
|
||||
Outcomes(Vec<Outcome>),
|
||||
Knockback(Vec3<f32>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
|
@ -126,6 +126,7 @@ impl State {
|
||||
ecs.register::<comp::CharacterState>();
|
||||
ecs.register::<comp::Object>();
|
||||
ecs.register::<comp::Group>();
|
||||
ecs.register::<comp::Shockwave>();
|
||||
|
||||
// Register components send from clients -> server
|
||||
ecs.register::<comp::Controller>();
|
||||
@ -389,11 +390,9 @@ impl State {
|
||||
vel.0.z = HUMANOID_JUMP_ACCEL;
|
||||
}
|
||||
},
|
||||
LocalEvent::ApplyForce { entity, force } => {
|
||||
// TODO: this sets the velocity directly to the value of `force`, consider
|
||||
// renaming the event or changing the behavior
|
||||
LocalEvent::ApplyImpulse { entity, impulse } => {
|
||||
if let Some(vel) = velocities.get_mut(entity) {
|
||||
vel.0 = force;
|
||||
vel.0 = impulse;
|
||||
}
|
||||
},
|
||||
LocalEvent::WallLeap { entity, wall_dir } => {
|
||||
|
@ -14,6 +14,8 @@ pub struct Data {
|
||||
pub recover_duration: Duration,
|
||||
/// Base damage (negative) or healing (positive)
|
||||
pub base_healthchange: i32,
|
||||
/// Knockback
|
||||
pub knockback: f32,
|
||||
/// Max range
|
||||
pub range: f32,
|
||||
/// Max angle (45.0 will give you a 90.0 angle window)
|
||||
@ -38,6 +40,7 @@ impl CharacterBehavior for Data {
|
||||
.unwrap_or_default(),
|
||||
recover_duration: self.recover_duration,
|
||||
base_healthchange: self.base_healthchange,
|
||||
knockback: self.knockback,
|
||||
range: self.range,
|
||||
max_angle: self.max_angle,
|
||||
exhausted: false,
|
||||
@ -50,13 +53,14 @@ impl CharacterBehavior for Data {
|
||||
max_angle: self.max_angle.to_radians(),
|
||||
applied: false,
|
||||
hit_count: 0,
|
||||
knockback: 0.0,
|
||||
knockback: self.knockback,
|
||||
});
|
||||
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
buildup_duration: self.buildup_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
base_healthchange: self.base_healthchange,
|
||||
knockback: self.knockback,
|
||||
range: self.range,
|
||||
max_angle: self.max_angle,
|
||||
exhausted: true,
|
||||
@ -70,6 +74,7 @@ impl CharacterBehavior for Data {
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
base_healthchange: self.base_healthchange,
|
||||
knockback: self.knockback,
|
||||
range: self.range,
|
||||
max_angle: self.max_angle,
|
||||
exhausted: true,
|
||||
|
@ -21,6 +21,7 @@ pub struct Data {
|
||||
pub projectile_body: Body,
|
||||
pub projectile_light: Option<LightEmitter>,
|
||||
pub projectile_gravity: Option<Gravity>,
|
||||
pub projectile_speed: f32,
|
||||
/// Whether the attack fired already
|
||||
pub exhausted: bool,
|
||||
}
|
||||
@ -49,6 +50,7 @@ impl CharacterBehavior for Data {
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
projectile_speed: self.projectile_speed,
|
||||
exhausted: false,
|
||||
});
|
||||
} else if !self.exhausted {
|
||||
@ -62,6 +64,7 @@ impl CharacterBehavior for Data {
|
||||
projectile,
|
||||
light: self.projectile_light,
|
||||
gravity: self.projectile_gravity,
|
||||
speed: self.projectile_speed,
|
||||
});
|
||||
|
||||
update.character = CharacterState::BasicRanged(Data {
|
||||
@ -73,6 +76,7 @@ impl CharacterBehavior for Data {
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
projectile_speed: self.projectile_speed,
|
||||
exhausted: true,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
@ -89,6 +93,7 @@ impl CharacterBehavior for Data {
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
projectile_speed: self.projectile_speed,
|
||||
exhausted: true,
|
||||
});
|
||||
return update;
|
||||
|
@ -10,9 +10,6 @@ use crate::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
const MAX_GRAVITY: f32 = 0.2;
|
||||
const MIN_GRAVITY: f32 = 0.05;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Whether the attack fired already
|
||||
@ -38,6 +35,9 @@ pub struct Data {
|
||||
/// Projectile information
|
||||
pub projectile_body: Body,
|
||||
pub projectile_light: Option<LightEmitter>,
|
||||
pub projectile_gravity: Option<Gravity>,
|
||||
pub initial_projectile_speed: f32,
|
||||
pub max_projectile_speed: f32,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
@ -65,6 +65,9 @@ impl CharacterBehavior for Data {
|
||||
recover_duration: self.recover_duration,
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
} else if data.inputs.secondary.is_pressed()
|
||||
&& self.charge_timer < self.charge_duration
|
||||
@ -87,6 +90,9 @@ impl CharacterBehavior for Data {
|
||||
recover_duration: self.recover_duration,
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
@ -109,6 +115,9 @@ impl CharacterBehavior for Data {
|
||||
recover_duration: self.recover_duration,
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
@ -145,9 +154,9 @@ impl CharacterBehavior for Data {
|
||||
body: self.projectile_body,
|
||||
projectile,
|
||||
light: self.projectile_light,
|
||||
gravity: Some(Gravity(
|
||||
MAX_GRAVITY - charge_amount * (MAX_GRAVITY - MIN_GRAVITY),
|
||||
)),
|
||||
gravity: self.projectile_gravity,
|
||||
speed: self.initial_projectile_speed
|
||||
+ charge_amount * (self.max_projectile_speed - self.initial_projectile_speed),
|
||||
});
|
||||
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
@ -163,6 +172,9 @@ impl CharacterBehavior for Data {
|
||||
recover_duration: self.recover_duration,
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
// Recovery
|
||||
@ -182,6 +194,9 @@ impl CharacterBehavior for Data {
|
||||
.unwrap_or_default(),
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
|
107
common/src/states/ground_shockwave.rs
Normal file
107
common/src/states/ground_shockwave.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use crate::{
|
||||
comp::{shockwave, Attacking, CharacterState, StateUpdate},
|
||||
event::ServerEvent,
|
||||
states::utils::*,
|
||||
sys::character_behavior::*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Whether the attack can deal more damage
|
||||
pub exhausted: bool,
|
||||
/// How long until state should deal damage
|
||||
pub buildup_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// Base damage
|
||||
pub damage: u32,
|
||||
/// Knockback
|
||||
pub knockback: f32,
|
||||
/// Angle of the shockwave
|
||||
pub shockwave_angle: f32,
|
||||
/// Speed of the shockwave
|
||||
pub shockwave_speed: f32,
|
||||
/// How long the shockwave travels for
|
||||
pub shockwave_duration: Duration,
|
||||
/// Whether the shockwave requires the target to be on the ground
|
||||
pub requires_ground: bool,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||
let mut update = StateUpdate::from(data);
|
||||
|
||||
handle_move(data, &mut update, 0.05);
|
||||
|
||||
if self.buildup_duration != Duration::default() {
|
||||
// Build up
|
||||
update.character = CharacterState::GroundShockwave(Data {
|
||||
exhausted: self.exhausted,
|
||||
buildup_duration: self
|
||||
.buildup_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
recover_duration: self.recover_duration,
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
requires_ground: self.requires_ground,
|
||||
});
|
||||
} else if !self.exhausted {
|
||||
// Attack
|
||||
let properties = shockwave::Properties {
|
||||
angle: self.shockwave_angle,
|
||||
speed: self.shockwave_speed,
|
||||
duration: self.shockwave_duration,
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
requires_ground: self.requires_ground,
|
||||
owner: Some(*data.uid),
|
||||
};
|
||||
update.server_events.push_front(ServerEvent::Shockwave {
|
||||
properties,
|
||||
pos: *data.pos,
|
||||
ori: *data.ori,
|
||||
});
|
||||
|
||||
update.character = CharacterState::GroundShockwave(Data {
|
||||
exhausted: true,
|
||||
buildup_duration: self.buildup_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
requires_ground: self.requires_ground,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
// Recovery
|
||||
update.character = CharacterState::GroundShockwave(Data {
|
||||
exhausted: self.exhausted,
|
||||
buildup_duration: self.buildup_duration,
|
||||
recover_duration: self
|
||||
.recover_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
requires_ground: self.requires_ground,
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
// Make sure attack component is removed
|
||||
data.updater.remove::<Attacking>(data.entity);
|
||||
}
|
||||
|
||||
update
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ pub mod dash_melee;
|
||||
pub mod equipping;
|
||||
pub mod glide;
|
||||
pub mod glide_wield;
|
||||
pub mod ground_shockwave;
|
||||
pub mod idle;
|
||||
pub mod leap_melee;
|
||||
pub mod roll;
|
||||
|
@ -268,6 +268,7 @@ impl<'a> System<'a> for Sys {
|
||||
Melee,
|
||||
RangedPowerup,
|
||||
Staff,
|
||||
StoneGolemBoss,
|
||||
}
|
||||
|
||||
let tactic = match loadout.active_item.as_ref().and_then(|ic| {
|
||||
@ -279,6 +280,13 @@ impl<'a> System<'a> for Sys {
|
||||
}) {
|
||||
Some(ToolKind::Bow(_)) => Tactic::RangedPowerup,
|
||||
Some(ToolKind::Staff(_)) => Tactic::Staff,
|
||||
Some(ToolKind::NpcWeapon(kind)) => {
|
||||
if kind == "StoneGolemsFist" {
|
||||
Tactic::StoneGolemBoss
|
||||
} else {
|
||||
Tactic::Melee
|
||||
}
|
||||
},
|
||||
_ => Tactic::Melee,
|
||||
};
|
||||
|
||||
@ -355,7 +363,9 @@ impl<'a> System<'a> for Sys {
|
||||
* 0.1;
|
||||
|
||||
match tactic {
|
||||
Tactic::Melee | Tactic::Staff => inputs.primary.set_state(true),
|
||||
Tactic::Melee | Tactic::Staff | Tactic::StoneGolemBoss => {
|
||||
inputs.primary.set_state(true)
|
||||
},
|
||||
Tactic::RangedPowerup => inputs.roll.set_state(true),
|
||||
}
|
||||
} else if dist_sqrd < MAX_CHASE_DIST.powf(2.0)
|
||||
@ -385,6 +395,13 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
inputs.secondary.set_state(true);
|
||||
} else if let Tactic::StoneGolemBoss = tactic {
|
||||
if *powerup > 5.0 {
|
||||
inputs.secondary.set_state(true);
|
||||
*powerup = 0.0;
|
||||
} else {
|
||||
*powerup += dt.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,6 +258,7 @@ impl<'a> System<'a> for Sys {
|
||||
CharacterState::LeapMelee(data) => data.handle_event(&j, action),
|
||||
CharacterState::SpinMelee(data) => data.handle_event(&j, action),
|
||||
CharacterState::ChargedRanged(data) => data.handle_event(&j, action),
|
||||
CharacterState::GroundShockwave(data) => data.handle_event(&j, action),
|
||||
};
|
||||
local_emitter.append(&mut state_update.local_events);
|
||||
server_emitter.append(&mut state_update.server_events);
|
||||
@ -286,6 +287,7 @@ impl<'a> System<'a> for Sys {
|
||||
CharacterState::LeapMelee(data) => data.behavior(&j),
|
||||
CharacterState::SpinMelee(data) => data.behavior(&j),
|
||||
CharacterState::ChargedRanged(data) => data.behavior(&j),
|
||||
CharacterState::GroundShockwave(data) => data.behavior(&j),
|
||||
};
|
||||
|
||||
local_emitter.append(&mut state_update.local_events);
|
||||
|
@ -33,8 +33,8 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, group::Group>,
|
||||
ReadStorage<'a, CharacterState>,
|
||||
WriteStorage<'a, Attacking>,
|
||||
WriteStorage<'a, CharacterState>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
@ -52,14 +52,14 @@ impl<'a> System<'a> for Sys {
|
||||
stats,
|
||||
loadouts,
|
||||
groups,
|
||||
mut attacking_storage,
|
||||
character_states,
|
||||
mut attacking_storage,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let start_time = std::time::Instant::now();
|
||||
span!(_guard, "run", "combat::Sys::run");
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
let mut local_emitter = local_bus.emitter();
|
||||
let mut _local_emitter = local_bus.emitter();
|
||||
// Attacks
|
||||
for (entity, uid, pos, ori, scale_maybe, attack) in (
|
||||
&entities,
|
||||
@ -152,11 +152,10 @@ impl<'a> System<'a> for Sys {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if attack.knockback != 0.0 {
|
||||
local_emitter.emit(LocalEvent::ApplyForce {
|
||||
if attack.knockback != 0.0 && damage.healthchange != 0.0 {
|
||||
server_emitter.emit(ServerEvent::Knockback {
|
||||
entity: b,
|
||||
force: attack.knockback
|
||||
impulse: attack.knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
|
||||
});
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ pub mod controller;
|
||||
mod mount;
|
||||
pub mod phys;
|
||||
mod projectile;
|
||||
mod shockwave;
|
||||
mod stats;
|
||||
|
||||
// External
|
||||
@ -18,6 +19,7 @@ pub const CONTROLLER_SYS: &str = "controller_sys";
|
||||
pub const MOUNT_SYS: &str = "mount_sys";
|
||||
pub const PHYS_SYS: &str = "phys_sys";
|
||||
pub const PROJECTILE_SYS: &str = "projectile_sys";
|
||||
pub const SHOCKWAVE_SYS: &str = "shockwave_sys";
|
||||
pub const STATS_SYS: &str = "stats_sys";
|
||||
|
||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
@ -30,5 +32,6 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(stats::Sys, STATS_SYS, &[]);
|
||||
dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS, MOUNT_SYS, STATS_SYS]);
|
||||
dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]);
|
||||
dispatch_builder.add(shockwave::Sys, SHOCKWAVE_SYS, &[PHYS_SYS]);
|
||||
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[PROJECTILE_SYS]);
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ impl<'a> System<'a> for Sys {
|
||||
mass_other,
|
||||
collider_other,
|
||||
_,
|
||||
group,
|
||||
group_b,
|
||||
) in (
|
||||
&entities,
|
||||
&uids,
|
||||
@ -186,7 +186,7 @@ impl<'a> System<'a> for Sys {
|
||||
)
|
||||
.join()
|
||||
{
|
||||
if entity == entity_other || (ignore_group.is_some() && ignore_group == group) {
|
||||
if entity == entity_other || (ignore_group.is_some() && ignore_group == group_b) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -117,9 +117,9 @@ impl<'a> System<'a> for Sys {
|
||||
if let Some(entity) =
|
||||
uid_allocator.retrieve_entity_internal(other.into())
|
||||
{
|
||||
local_emitter.emit(LocalEvent::ApplyForce {
|
||||
local_emitter.emit(LocalEvent::ApplyImpulse {
|
||||
entity,
|
||||
force: knockback
|
||||
impulse: knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::unit_z()), 0.5),
|
||||
});
|
||||
}
|
||||
|
339
common/src/sys/shockwave.rs
Normal file
339
common/src/sys/shockwave.rs
Normal file
@ -0,0 +1,339 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
group, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource, Last,
|
||||
Loadout, Ori, PhysicsState, Pos, Scale, Shockwave, Stats,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
state::{DeltaTime, Time},
|
||||
sync::{Uid, UidAllocator},
|
||||
util::Dir,
|
||||
};
|
||||
use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
use vek::*;
|
||||
|
||||
pub const BLOCK_ANGLE: f32 = 180.0;
|
||||
|
||||
/// This system is responsible for handling accepted inputs like moving or
|
||||
/// attacking
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
#[allow(clippy::type_complexity)]
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
Read<'a, EventBus<LocalEvent>>,
|
||||
Read<'a, Time>,
|
||||
Read<'a, DeltaTime>,
|
||||
Read<'a, UidAllocator>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Pos>,
|
||||
ReadStorage<'a, Last<Pos>>,
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, group::Group>,
|
||||
ReadStorage<'a, CharacterState>,
|
||||
ReadStorage<'a, PhysicsState>,
|
||||
WriteStorage<'a, Shockwave>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
entities,
|
||||
server_bus,
|
||||
local_bus,
|
||||
time,
|
||||
dt,
|
||||
uid_allocator,
|
||||
uids,
|
||||
positions,
|
||||
last_positions,
|
||||
orientations,
|
||||
scales,
|
||||
bodies,
|
||||
stats,
|
||||
loadouts,
|
||||
groups,
|
||||
character_states,
|
||||
physics_states,
|
||||
mut shockwaves,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
let _local_emitter = local_bus.emitter();
|
||||
|
||||
let time = time.0;
|
||||
let dt = dt.0;
|
||||
|
||||
// Shockwaves
|
||||
for (entity, uid, pos, ori, shockwave) in
|
||||
(&entities, &uids, &positions, &orientations, &shockwaves).join()
|
||||
{
|
||||
let creation_time = match shockwave.creation {
|
||||
Some(time) => time,
|
||||
// Skip newly created shockwaves
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let end_time = creation_time + shockwave.duration.as_secs_f64();
|
||||
|
||||
// If shockwave is out of time emit destroy event but still continue since it
|
||||
// may have traveled and produced effects a bit before reaching it's
|
||||
// end point
|
||||
if time > end_time {
|
||||
server_emitter.emit(ServerEvent::Destroy {
|
||||
entity,
|
||||
cause: HealthSource::World,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine area that was covered by the shockwave in the last tick
|
||||
let time_since_creation = (time - creation_time) as f32;
|
||||
let frame_start_dist = (shockwave.speed * (time_since_creation - dt)).max(0.0);
|
||||
let frame_end_dist = (shockwave.speed * time_since_creation).max(frame_start_dist);
|
||||
let pos2 = Vec2::from(pos.0);
|
||||
|
||||
// From one frame to the next a shockwave travels over a strip of an arc
|
||||
// This is used for collision detection
|
||||
let arc_strip = ArcStrip {
|
||||
origin: pos2,
|
||||
// TODO: make sure this is not Vec2::new(0.0, 0.0)
|
||||
dir: ori.0.xy(),
|
||||
angle: shockwave.angle,
|
||||
start: frame_start_dist,
|
||||
end: frame_end_dist,
|
||||
};
|
||||
|
||||
// Group to ignore collisions with
|
||||
// Might make this more nuanced if shockwaves are used for non damage effects
|
||||
let group = shockwave
|
||||
.owner
|
||||
.and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into()))
|
||||
.and_then(|e| groups.get(e));
|
||||
|
||||
// Go through all other effectable entities
|
||||
for (
|
||||
b,
|
||||
uid_b,
|
||||
pos_b,
|
||||
last_pos_b_maybe,
|
||||
ori_b,
|
||||
scale_b_maybe,
|
||||
character_b,
|
||||
stats_b,
|
||||
body_b,
|
||||
physics_state_b,
|
||||
) in (
|
||||
&entities,
|
||||
&uids,
|
||||
&positions,
|
||||
// TODO: make sure that these are maintained on the client and remove `.maybe()`
|
||||
last_positions.maybe(),
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
character_states.maybe(),
|
||||
&stats,
|
||||
&bodies,
|
||||
&physics_states,
|
||||
)
|
||||
.join()
|
||||
{
|
||||
// 2D versions
|
||||
let pos_b2 = pos_b.0.xy();
|
||||
let last_pos_b2_maybe = last_pos_b_maybe.map(|p| (p.0).0.xy());
|
||||
|
||||
// Scales
|
||||
let scale_b = scale_b_maybe.map_or(1.0, |s| s.0);
|
||||
let rad_b = body_b.radius() * scale_b;
|
||||
|
||||
// Angle checks
|
||||
let pos_b_ground = Vec3::new(pos_b.0.x, pos_b.0.y, pos.0.z);
|
||||
let max_angle = 15.0_f32.to_radians();
|
||||
|
||||
// See if entities are in the same group
|
||||
let same_group = group
|
||||
.map(|group_a| Some(group_a) == groups.get(b))
|
||||
.unwrap_or(Some(*uid_b) == shockwave.owner);
|
||||
|
||||
// Check if it is a hit
|
||||
let hit = entity != b
|
||||
&& !stats_b.is_dead
|
||||
// Collision shapes
|
||||
&& {
|
||||
// TODO: write code to collide rect with the arc strip so that we can do
|
||||
// more complete collision detection for rapidly moving entities
|
||||
arc_strip.collides_with_circle(Disk::new(pos_b2, rad_b)) || last_pos_b2_maybe.map_or(false, |pos| {
|
||||
arc_strip.collides_with_circle(Disk::new(pos, rad_b))
|
||||
})
|
||||
}
|
||||
&& (pos_b_ground - pos.0).angle_between(pos_b.0 - pos.0) < max_angle
|
||||
&& (!shockwave.requires_ground || physics_state_b.on_ground)
|
||||
&& !same_group;
|
||||
|
||||
if hit {
|
||||
let mut damage = Damage {
|
||||
healthchange: -(shockwave.damage as f32),
|
||||
source: DamageSource::Shockwave,
|
||||
};
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
|
||||
if let Some(loadout) = loadouts.get(b) {
|
||||
damage.modify_damage(block, loadout);
|
||||
}
|
||||
|
||||
if damage.healthchange != 0.0 {
|
||||
let cause = if damage.healthchange < 0.0 {
|
||||
HealthSource::Attack {
|
||||
by: shockwave.owner.unwrap_or(*uid),
|
||||
}
|
||||
} else {
|
||||
HealthSource::Healing {
|
||||
by: Some(shockwave.owner.unwrap_or(*uid)),
|
||||
}
|
||||
};
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause,
|
||||
},
|
||||
});
|
||||
}
|
||||
if shockwave.knockback != 0.0 && damage.healthchange != 0.0 {
|
||||
let impulse = if shockwave.knockback < 0.0 {
|
||||
shockwave.knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, -1.0)), 0.85)
|
||||
} else {
|
||||
shockwave.knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5)
|
||||
};
|
||||
server_emitter.emit(ServerEvent::Knockback { entity: b, impulse });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set start time on new shockwaves
|
||||
// This change doesn't need to be recorded as it is not sent to the client
|
||||
shockwaves.set_event_emission(false);
|
||||
(&mut shockwaves).join().for_each(|shockwave| {
|
||||
if shockwave.creation.is_none() {
|
||||
shockwave.creation = Some(time);
|
||||
}
|
||||
});
|
||||
shockwaves.set_event_emission(true);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ArcStrip {
|
||||
origin: Vec2<f32>,
|
||||
/// Normalizable direction
|
||||
dir: Vec2<f32>,
|
||||
/// Angle in degrees
|
||||
angle: f32,
|
||||
/// Start radius
|
||||
start: f32,
|
||||
/// End radius
|
||||
end: f32,
|
||||
}
|
||||
|
||||
impl ArcStrip {
|
||||
fn collides_with_circle(self, d: Disk<f32, f32>) -> bool {
|
||||
// Quit if aabb's don't collide
|
||||
if (self.origin.x - d.center.x).abs() > self.end + d.radius
|
||||
|| (self.origin.y - d.center.y).abs() > self.end + d.radius
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let dist = self.origin.distance(d.center);
|
||||
let half_angle = self.angle.to_radians() / 2.0;
|
||||
|
||||
if dist > self.end + d.radius || dist + d.radius < self.start {
|
||||
// Completely inside or outside full ring
|
||||
return false;
|
||||
}
|
||||
|
||||
let inside_edge = Disk::new(self.origin, self.start);
|
||||
let outside_edge = Disk::new(self.origin, self.end);
|
||||
let inner_corner_in_circle = || {
|
||||
let midpoint = self.dir.normalized() * self.start;
|
||||
d.contains_point(midpoint.rotated_z(half_angle) + self.origin)
|
||||
|| d.contains_point(midpoint.rotated_z(-half_angle) + self.origin)
|
||||
};
|
||||
let arc_segment_in_circle = || {
|
||||
let midpoint = self.dir.normalized();
|
||||
let segment_in_circle = |angle| {
|
||||
let dir = midpoint.rotated_z(angle);
|
||||
let side = LineSegment2 {
|
||||
start: dir * self.start + self.origin,
|
||||
end: dir * self.end + self.origin,
|
||||
};
|
||||
d.contains_point(side.projected_point(d.center))
|
||||
};
|
||||
segment_in_circle(half_angle) || segment_in_circle(-half_angle)
|
||||
};
|
||||
|
||||
if dist > self.end {
|
||||
// Circle center is outside ring
|
||||
// Check intersection with line segments
|
||||
arc_segment_in_circle() || {
|
||||
// Check angle of intersection points on outside edge of ring
|
||||
let (p1, p2) = intersection_points(outside_edge, d, dist);
|
||||
self.dir.angle_between(p1 - self.origin) < half_angle
|
||||
|| self.dir.angle_between(p2 - self.origin) < half_angle
|
||||
}
|
||||
} else if dist < self.start {
|
||||
// Circle center is inside ring
|
||||
// Check angle of intersection points on inside edge of ring
|
||||
// Check if circle contains one of the inner points of the arc
|
||||
inner_corner_in_circle()
|
||||
|| (
|
||||
// Check that the circles aren't identical
|
||||
inside_edge != d && {
|
||||
let (p1, p2) = intersection_points(inside_edge, d, dist);
|
||||
self.dir.angle_between(p1 - self.origin) < half_angle
|
||||
|| self.dir.angle_between(p2 - self.origin) < half_angle
|
||||
}
|
||||
)
|
||||
} else if d.radius > dist {
|
||||
// Circle center inside ring
|
||||
// but center of ring is inside the circle so we can't calculate the angle
|
||||
inner_corner_in_circle()
|
||||
} else {
|
||||
// Circle center inside ring
|
||||
// Calculate extra angle to account for circle radius
|
||||
let extra_angle = (d.radius / dist).asin();
|
||||
self.dir.angle_between(d.center - self.origin) < half_angle + extra_angle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes an intersection is occuring at 2 points
|
||||
// Uses precalculated distance
|
||||
// https://www.xarg.org/2016/07/calculate-the-intersection-points-of-two-circles/
|
||||
fn intersection_points(
|
||||
disk1: Disk<f32, f32>,
|
||||
disk2: Disk<f32, f32>,
|
||||
dist: f32,
|
||||
) -> (Vec2<f32>, Vec2<f32>) {
|
||||
let e = (disk2.center - disk1.center) / dist;
|
||||
|
||||
let x = (disk1.radius.powi(2) - disk2.radius.powi(2) + dist.powi(2)) / (2.0 * dist);
|
||||
let y = (disk1.radius.powi(2) - x.powi(2)).sqrt();
|
||||
|
||||
let pxe = disk1.center + x * e;
|
||||
let eyx = e.yx();
|
||||
|
||||
let p1 = pxe + Vec2::new(-y, y) * eyx;
|
||||
let p2 = pxe + Vec2::new(y, -y) * eyx;
|
||||
|
||||
(p1, p2)
|
||||
}
|
@ -112,7 +112,8 @@ impl<'a> System<'a> for Sys {
|
||||
| CharacterState::SpinMelee { .. }
|
||||
| CharacterState::TripleStrike { .. }
|
||||
| CharacterState::BasicRanged { .. }
|
||||
| CharacterState::ChargedRanged { .. } => {
|
||||
| CharacterState::ChargedRanged { .. }
|
||||
| CharacterState::GroundShockwave { .. } => {
|
||||
if energy.get_unchecked().regen_rate != 0.0 {
|
||||
energy.get_mut_unchecked().regen_rate = 0.0
|
||||
}
|
||||
|
@ -582,7 +582,7 @@ fn handle_spawn(
|
||||
.create_npc(
|
||||
pos,
|
||||
comp::Stats::new(get_npc_name(id).into(), body),
|
||||
LoadoutBuilder::animal(body).build(),
|
||||
LoadoutBuilder::build_loadout(body, alignment, None).build(),
|
||||
body,
|
||||
)
|
||||
.with(comp::Vel(vel))
|
||||
|
@ -2,8 +2,9 @@ use crate::{sys, Server, StateExt};
|
||||
use common::{
|
||||
character::CharacterId,
|
||||
comp::{
|
||||
self, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, Agent, Alignment, Body, Gravity, Item,
|
||||
ItemDrop, LightEmitter, Loadout, Pos, Projectile, Scale, Stats, Vel, WaypointArea,
|
||||
self, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, shockwave, Agent, Alignment, Body, Gravity,
|
||||
Item, ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, Vel,
|
||||
WaypointArea,
|
||||
},
|
||||
outcome::Outcome,
|
||||
util::Dir,
|
||||
@ -79,6 +80,7 @@ pub fn handle_create_npc(
|
||||
entity.build();
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn handle_shoot(
|
||||
server: &mut Server,
|
||||
entity: EcsEntity,
|
||||
@ -87,6 +89,7 @@ pub fn handle_shoot(
|
||||
light: Option<LightEmitter>,
|
||||
projectile: Projectile,
|
||||
gravity: Option<Gravity>,
|
||||
speed: f32,
|
||||
) {
|
||||
let state = server.state_mut();
|
||||
|
||||
@ -97,7 +100,7 @@ pub fn handle_shoot(
|
||||
.expect("Failed to fetch entity")
|
||||
.0;
|
||||
|
||||
let vel = *dir * 100.0;
|
||||
let vel = *dir * speed;
|
||||
|
||||
// Add an outcome
|
||||
state
|
||||
@ -123,6 +126,16 @@ pub fn handle_shoot(
|
||||
builder.build();
|
||||
}
|
||||
|
||||
pub fn handle_shockwave(
|
||||
server: &mut Server,
|
||||
properties: shockwave::Properties,
|
||||
pos: Pos,
|
||||
ori: Ori,
|
||||
) {
|
||||
let state = server.state_mut();
|
||||
state.create_shockwave(properties, pos, ori).build();
|
||||
}
|
||||
|
||||
pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
|
||||
server
|
||||
.state
|
||||
|
@ -32,6 +32,18 @@ pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3<f32>) {
|
||||
let state = &server.state;
|
||||
let mut velocities = state.ecs().write_storage::<comp::Vel>();
|
||||
if let Some(vel) = velocities.get_mut(entity) {
|
||||
vel.0 = impulse;
|
||||
}
|
||||
let mut clients = state.ecs().write_storage::<Client>();
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ServerMsg::Knockback(impulse));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle an entity dying. If it is a player, it will send a message to all
|
||||
/// other players. If the entity that killed it had stats, then give it exp for
|
||||
/// the kill. Experience given is equal to the level of the entity that was
|
||||
|
@ -5,11 +5,11 @@ use common::{
|
||||
};
|
||||
use entity_creation::{
|
||||
handle_create_npc, handle_create_waypoint, handle_initialize_character,
|
||||
handle_loaded_character_data, handle_shoot,
|
||||
handle_loaded_character_data, handle_shockwave, handle_shoot,
|
||||
};
|
||||
use entity_manipulation::{
|
||||
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_level_up,
|
||||
handle_respawn,
|
||||
handle_damage, handle_destroy, handle_explosion, handle_knockback, handle_land_on_ground,
|
||||
handle_level_up, handle_respawn,
|
||||
};
|
||||
use group_manip::handle_group;
|
||||
use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount};
|
||||
@ -68,7 +68,16 @@ impl Server {
|
||||
light,
|
||||
projectile,
|
||||
gravity,
|
||||
} => handle_shoot(self, entity, dir, body, light, projectile, gravity),
|
||||
speed,
|
||||
} => handle_shoot(self, entity, dir, body, light, projectile, gravity, speed),
|
||||
ServerEvent::Shockwave {
|
||||
properties,
|
||||
pos,
|
||||
ori,
|
||||
} => handle_shockwave(self, properties, pos, ori),
|
||||
ServerEvent::Knockback { entity, impulse } => {
|
||||
handle_knockback(&self, entity, impulse)
|
||||
},
|
||||
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
|
||||
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
|
||||
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
|
||||
|
@ -39,6 +39,13 @@ pub trait StateExt {
|
||||
body: comp::Body,
|
||||
projectile: comp::Projectile,
|
||||
) -> EcsEntityBuilder;
|
||||
/// Build a shockwave entity
|
||||
fn create_shockwave(
|
||||
&mut self,
|
||||
properties: comp::shockwave::Properties,
|
||||
pos: comp::Pos,
|
||||
ori: comp::Ori,
|
||||
) -> EcsEntityBuilder;
|
||||
/// Insert common/default components for a new character joining the server
|
||||
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId);
|
||||
/// Update the components associated with the entity's current character.
|
||||
@ -134,6 +141,22 @@ impl StateExt for State {
|
||||
.with(comp::Sticky)
|
||||
}
|
||||
|
||||
fn create_shockwave(
|
||||
&mut self,
|
||||
properties: comp::shockwave::Properties,
|
||||
pos: comp::Pos,
|
||||
ori: comp::Ori,
|
||||
) -> EcsEntityBuilder {
|
||||
self.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(pos)
|
||||
.with(ori)
|
||||
.with(comp::Shockwave {
|
||||
properties,
|
||||
creation: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId) {
|
||||
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
|
||||
|
||||
|
@ -2,7 +2,8 @@ use super::SysTimer;
|
||||
use common::{
|
||||
comp::{
|
||||
Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter,
|
||||
Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel,
|
||||
Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave, Stats, Sticky,
|
||||
Vel,
|
||||
},
|
||||
msg::EcsCompPacket,
|
||||
span,
|
||||
@ -57,6 +58,7 @@ pub struct TrackedComps<'a> {
|
||||
pub gravity: ReadStorage<'a, Gravity>,
|
||||
pub loadout: ReadStorage<'a, Loadout>,
|
||||
pub character_state: ReadStorage<'a, CharacterState>,
|
||||
pub shockwave: ReadStorage<'a, Shockwave>,
|
||||
}
|
||||
impl<'a> TrackedComps<'a> {
|
||||
pub fn create_entity_package(
|
||||
@ -132,6 +134,11 @@ impl<'a> TrackedComps<'a> {
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.shockwave
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
// Add untracked comps
|
||||
// Add untracked comps
|
||||
pos.map(|c| comps.push(c.into()));
|
||||
vel.map(|c| comps.push(c.into()));
|
||||
@ -160,6 +167,7 @@ pub struct ReadTrackers<'a> {
|
||||
pub gravity: ReadExpect<'a, UpdateTracker<Gravity>>,
|
||||
pub loadout: ReadExpect<'a, UpdateTracker<Loadout>>,
|
||||
pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>,
|
||||
pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>,
|
||||
}
|
||||
impl<'a> ReadTrackers<'a> {
|
||||
pub fn create_sync_packages(
|
||||
@ -197,7 +205,8 @@ impl<'a> ReadTrackers<'a> {
|
||||
&*self.character_state,
|
||||
&comps.character_state,
|
||||
filter,
|
||||
);
|
||||
)
|
||||
.with_component(&comps.uid, &*self.shockwave, &comps.shockwave, filter);
|
||||
|
||||
(entity_sync_package, comp_sync_package)
|
||||
}
|
||||
@ -223,6 +232,7 @@ pub struct WriteTrackers<'a> {
|
||||
gravity: WriteExpect<'a, UpdateTracker<Gravity>>,
|
||||
loadout: WriteExpect<'a, UpdateTracker<Loadout>>,
|
||||
character_state: WriteExpect<'a, UpdateTracker<CharacterState>>,
|
||||
shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>,
|
||||
}
|
||||
|
||||
fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
@ -247,6 +257,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
trackers
|
||||
.character_state
|
||||
.record_changes(&comps.character_state);
|
||||
trackers.shockwave.record_changes(&comps.shockwave);
|
||||
// Debug how many updates are being sent
|
||||
/*
|
||||
macro_rules! log_counts {
|
||||
@ -278,6 +289,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
log_counts!(gravity, "Gravitys");
|
||||
log_counts!(loadout, "Loadouts");
|
||||
log_counts!(character_state, "Character States");
|
||||
log_counts!(shockwave, "Shockwaves");
|
||||
*/
|
||||
}
|
||||
|
||||
@ -300,6 +312,7 @@ pub fn register_trackers(world: &mut World) {
|
||||
world.register_tracker::<Gravity>();
|
||||
world.register_tracker::<Loadout>();
|
||||
world.register_tracker::<CharacterState>();
|
||||
world.register_tracker::<Shockwave>();
|
||||
}
|
||||
|
||||
/// Deleted entities grouped by region
|
||||
|
@ -1,11 +1,7 @@
|
||||
use super::SysTimer;
|
||||
use crate::{chunk_generator::ChunkGenerator, client::Client, Tick};
|
||||
use common::{
|
||||
comp::{
|
||||
self, bird_medium,
|
||||
item::{self},
|
||||
Alignment, CharacterAbility, ItemConfig, Player, Pos,
|
||||
},
|
||||
comp::{self, bird_medium, Alignment, CharacterAbility, Player, Pos},
|
||||
event::{EventBus, ServerEvent},
|
||||
generation::get_npc_name,
|
||||
msg::ServerMsg,
|
||||
@ -124,119 +120,7 @@ impl<'a> System<'a> for Sys {
|
||||
// let damage = stats.level.level() as i32; TODO: Make NPC base damage
|
||||
// non-linearly depend on their level
|
||||
|
||||
let active_item = if let Some(item::ItemKind::Tool(tool)) =
|
||||
main_tool.as_ref().map(|i| i.kind())
|
||||
{
|
||||
let mut abilities = tool.get_abilities();
|
||||
let mut ability_drain = abilities.drain(..);
|
||||
|
||||
main_tool.map(|item| comp::ItemConfig {
|
||||
item,
|
||||
ability1: ability_drain.next(),
|
||||
ability2: ability_drain.next(),
|
||||
ability3: ability_drain.next(),
|
||||
block_ability: None,
|
||||
dodge_ability: Some(comp::CharacterAbility::Roll),
|
||||
})
|
||||
} else {
|
||||
Some(ItemConfig {
|
||||
// We need the empty item so npcs can attack
|
||||
item: comp::Item::new_from_asset_expect("common.items.weapons.empty.empty"),
|
||||
ability1: Some(CharacterAbility::BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(400),
|
||||
base_healthchange: -40,
|
||||
range: 3.5,
|
||||
max_angle: 15.0,
|
||||
}),
|
||||
ability2: None,
|
||||
ability3: None,
|
||||
block_ability: None,
|
||||
dodge_ability: None,
|
||||
})
|
||||
};
|
||||
|
||||
let mut loadout = match alignment {
|
||||
comp::Alignment::Npc => comp::Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: None,
|
||||
chest: Some(comp::Item::new_from_asset_expect(
|
||||
match rand::thread_rng().gen_range(0, 10) {
|
||||
0 => "common.items.npc_armor.chest.worker_green_0",
|
||||
1 => "common.items.npc_armor.chest.worker_green_1",
|
||||
2 => "common.items.npc_armor.chest.worker_red_0",
|
||||
3 => "common.items.npc_armor.chest.worker_red_1",
|
||||
4 => "common.items.npc_armor.chest.worker_purple_0",
|
||||
5 => "common.items.npc_armor.chest.worker_purple_1",
|
||||
6 => "common.items.npc_armor.chest.worker_yellow_0",
|
||||
7 => "common.items.npc_armor.chest.worker_yellow_1",
|
||||
8 => "common.items.npc_armor.chest.worker_orange_0",
|
||||
_ => "common.items.npc_armor.chest.worker_orange_1",
|
||||
},
|
||||
)),
|
||||
belt: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.leather_0",
|
||||
)),
|
||||
hand: None,
|
||||
pants: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.worker_blue_0",
|
||||
)),
|
||||
foot: Some(comp::Item::new_from_asset_expect(
|
||||
match rand::thread_rng().gen_range(0, 2) {
|
||||
0 => "common.items.armor.foot.leather_0",
|
||||
_ => "common.items.armor.starter.sandals_0",
|
||||
},
|
||||
)),
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: None,
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
comp::Alignment::Enemy => comp::Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.npc_armor.shoulder.cultist_shoulder_purple",
|
||||
)),
|
||||
chest: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.npc_armor.chest.cultist_chest_purple",
|
||||
)),
|
||||
belt: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.npc_armor.belt.cultist_belt",
|
||||
)),
|
||||
hand: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.npc_armor.hand.cultist_hands_purple",
|
||||
)),
|
||||
pants: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.npc_armor.pants.cultist_legs_purple",
|
||||
)),
|
||||
foot: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.npc_armor.foot.cultist_boots",
|
||||
)),
|
||||
back: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.npc_armor.back.dungeon_purple-0",
|
||||
)),
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: Some(comp::Item::new_from_asset_expect(
|
||||
"common.items.lantern.black_0",
|
||||
)),
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
_ => LoadoutBuilder::animal(entity.body).build(),
|
||||
};
|
||||
|
||||
loadout = match body {
|
||||
comp::Body::Humanoid(_) => loadout,
|
||||
_ => LoadoutBuilder::animal(entity.body).build(),
|
||||
};
|
||||
let mut loadout = LoadoutBuilder::build_loadout(body, alignment, main_tool).build();
|
||||
|
||||
let mut scale = entity.scale;
|
||||
|
||||
@ -277,6 +161,7 @@ impl<'a> System<'a> for Sys {
|
||||
buildup_duration: Duration::from_millis(800),
|
||||
recover_duration: Duration::from_millis(200),
|
||||
base_healthchange: -100,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 60.0,
|
||||
}),
|
||||
|
@ -79,6 +79,7 @@ fn get_tool_kind(kind: &ToolKind) -> String {
|
||||
ToolKind::Shield(_) => "Shield".to_string(),
|
||||
ToolKind::Debug(_) => "Debug".to_string(),
|
||||
ToolKind::Farming(_) => "Farming".to_string(),
|
||||
ToolKind::NpcWeapon(_) => "NpcWeapon".to_string(),
|
||||
ToolKind::Empty => "Empty".to_string(),
|
||||
}
|
||||
}
|
||||
@ -94,6 +95,7 @@ fn get_tool_kind_kind(kind: &ToolKind) -> String {
|
||||
ToolKind::Shield(x) => x.clone(),
|
||||
ToolKind::Debug(x) => x.clone(),
|
||||
ToolKind::Farming(x) => x.clone(),
|
||||
ToolKind::NpcWeapon(x) => x.clone(),
|
||||
ToolKind::Empty => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ fn maps_basic_melee() {
|
||||
&CharacterState::BasicMelee(states::basic_melee::Data {
|
||||
buildup_duration: Duration::default(),
|
||||
recover_duration: Duration::default(),
|
||||
knockback: 0.0,
|
||||
base_healthchange: 10,
|
||||
range: 1.0,
|
||||
max_angle: 1.0,
|
||||
|
@ -75,6 +75,7 @@ fn tool_desc(tool: &Tool, desc: &str) -> String {
|
||||
ToolKind::Dagger(_) => "Dagger",
|
||||
ToolKind::Staff(_) => "Staff",
|
||||
ToolKind::Shield(_) => "Shield",
|
||||
ToolKind::NpcWeapon(_) => "Npc Weapon",
|
||||
ToolKind::Debug(_) => "Debug",
|
||||
ToolKind::Farming(_) => "Farming Tool",
|
||||
ToolKind::Empty => "Empty",
|
||||
|
@ -105,6 +105,7 @@ pub enum ParticleMode {
|
||||
Leaf = 9,
|
||||
Firefly = 10,
|
||||
Bee = 11,
|
||||
GroundShockwave = 12,
|
||||
}
|
||||
|
||||
impl ParticleMode {
|
||||
|
@ -2115,6 +2115,15 @@ impl FigureMgr {
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
CharacterState::GroundShockwave(_) => {
|
||||
anim::golem::ShockwaveAnimation::update_skeleton(
|
||||
&target_base,
|
||||
(vel.0.magnitude(), time),
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
// TODO!
|
||||
_ => target_base,
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
assets::Asset,
|
||||
comp::{item::Reagent, object, Body, CharacterState, Pos},
|
||||
comp::{item::Reagent, object, Body, CharacterState, Ori, Pos, Shockwave},
|
||||
figure::Segment,
|
||||
outcome::Outcome,
|
||||
span,
|
||||
@ -113,6 +113,7 @@ impl ParticleMgr {
|
||||
self.maintain_body_particles(scene_data);
|
||||
self.maintain_boost_particles(scene_data);
|
||||
self.maintain_block_particles(scene_data, terrain);
|
||||
self.maintain_shockwave_particles(scene_data);
|
||||
} else {
|
||||
// remove all particle lifespans
|
||||
self.particles.clear();
|
||||
@ -420,6 +421,65 @@ impl ParticleMgr {
|
||||
}
|
||||
}
|
||||
|
||||
fn maintain_shockwave_particles(&mut self, scene_data: &SceneData) {
|
||||
let state = scene_data.state;
|
||||
let ecs = state.ecs();
|
||||
let time = state.get_time();
|
||||
|
||||
for (_i, (_entity, pos, ori, shockwave)) in (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<Ori>(),
|
||||
&ecs.read_storage::<Shockwave>(),
|
||||
)
|
||||
.join()
|
||||
.enumerate()
|
||||
{
|
||||
let elapsed = time - shockwave.creation.unwrap_or_default();
|
||||
|
||||
let distance = shockwave.properties.speed * elapsed as f32;
|
||||
|
||||
let radians = shockwave.properties.angle.to_radians();
|
||||
|
||||
let theta = ori.0.y.atan2(ori.0.x);
|
||||
let dtheta = radians / distance;
|
||||
|
||||
// 1 / 3 the size of terrain voxel
|
||||
let scale = 1.0 / 3.0;
|
||||
|
||||
let scaled_speed = shockwave.properties.speed * scale;
|
||||
|
||||
let heartbeats = self
|
||||
.scheduler
|
||||
.heartbeats(Duration::from_millis(scaled_speed as u64));
|
||||
let new_particle_count = distance / scale * heartbeats as f32;
|
||||
self.particles.reserve(new_particle_count as usize);
|
||||
|
||||
for heartbeat in 0..heartbeats {
|
||||
let sub_tick_interpolation = scaled_speed * 1000.0 * heartbeat as f32;
|
||||
|
||||
let distance =
|
||||
shockwave.properties.speed * (elapsed as f32 - sub_tick_interpolation);
|
||||
|
||||
for d in 0..((distance / scale) as i32) {
|
||||
let arc_position = theta - radians / 2.0 + dtheta * d as f32 * scale;
|
||||
|
||||
let position =
|
||||
pos.0 + distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
|
||||
|
||||
let position_snapped = ((position / scale).floor() + 0.5) * scale;
|
||||
|
||||
self.particles.push(Particle::new(
|
||||
Duration::from_millis(250),
|
||||
time,
|
||||
ParticleMode::GroundShockwave,
|
||||
position_snapped,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_particles(&mut self, renderer: &mut Renderer) {
|
||||
span!(_guard, "upload_particles", "ParticleMgr::upload_particles");
|
||||
let all_cpu_instances = self
|
||||
|
@ -13,7 +13,6 @@ use common::{
|
||||
comp::{self},
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
lottery::Lottery,
|
||||
npc,
|
||||
store::{Id, Store},
|
||||
terrain::{Block, BlockKind, Structure, TerrainChunkSize},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
||||
@ -509,22 +508,13 @@ impl Floor {
|
||||
);
|
||||
let chosen = chosen.choose();
|
||||
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32))
|
||||
.with_scale(4.0)
|
||||
.with_level(rng.gen_range(75, 100))
|
||||
.with_level(rng.gen_range(1, 5))
|
||||
.with_alignment(comp::Alignment::Enemy)
|
||||
.with_body(comp::Body::Humanoid(comp::humanoid::Body::random()))
|
||||
.with_name(format!(
|
||||
"Cult Leader {}",
|
||||
npc::get_npc_name(npc::NpcKind::Humanoid)
|
||||
))
|
||||
.with_main_tool(comp::Item::new_from_asset_expect(
|
||||
match rng.gen_range(0, 1) {
|
||||
//Add more possible cult leader npc_weapons here
|
||||
_ => {
|
||||
"common.items.npc_weapons.sword.cultist_purp_2h_boss-0"
|
||||
},
|
||||
},
|
||||
))
|
||||
.with_body(comp::Body::Golem(comp::golem::Body::random_with(
|
||||
rng,
|
||||
&comp::golem::Species::StoneGolem,
|
||||
)))
|
||||
.with_name("Stonework Defender".to_string())
|
||||
.with_loot_drop(comp::Item::new_from_asset_expect(chosen));
|
||||
|
||||
supplement.add_entity(entity);
|
||||
|
Loading…
Reference in New Issue
Block a user