Skip to content

Commit

Permalink
chore(world): improved EntityAirSupplyComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
PMK744 committed Aug 29, 2024
1 parent 0ccabce commit 5b33e75
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 22 deletions.
2 changes: 1 addition & 1 deletion packages/network/src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ class Network extends Emitter<NetworkEvents> {

// Disconnect the session if an error occurs.
return session.disconnect(
`Internal SerenityJS Error: Server failed to send packets to the session.\n\n§c${reason}§r\nBatched packets: §c${packets.map((x) => Packet[x.getId()]).join("§r, §c")}§r5`,
`Internal SerenityJS Error: Server failed to send packets to the session.\n\n§c${reason}§r\nBatched packets: §c${packets.map((x) => Packet[x.getId()]).join("§r, §c")}§r`,
DisconnectReason.BadPacket
);
}
Expand Down
33 changes: 33 additions & 0 deletions packages/serenity/src/handlers/player-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
ItemStack,
ItemUseCause,
PlayerJumpSignal,
PlayerStartSwimmingSignal,
PlayerStopSwimmingSignal,
type Player
} from "@serenityjs/world";
import { type ItemIdentifier, ItemType } from "@serenityjs/item";
Expand Down Expand Up @@ -97,6 +99,37 @@ class PlayerAction extends SerenityHandler {
// break;
}

case ActionIds.Swimming: {
// Check if this is the first time the player is swimming.
if (!player.isSwimming) {
// Create a new PlayerStartSwimmingSignal and emit it.
const signal = new PlayerStartSwimmingSignal(player);
const value = signal.emit();

// If the signal was cancelled, we will return.
if (!value) return player.setActorFlag(ActorFlag.Swimming, false);

// Set the player's swimming property to true.
player.isSwimming = true;
player.setActorFlag(ActorFlag.Swimming, true);
}
break;
}

case ActionIds.StopSwimming: {
// Create a new PlayerStopSwimmingSignal and emit it.
const signal = new PlayerStopSwimmingSignal(player);
const value = signal.emit();

// If the signal was cancelled, we will return.
if (!value) return player.setActorFlag(ActorFlag.Swimming, true);

// Set the player's swimming property to false.
player.isSwimming = false;
player.setActorFlag(ActorFlag.Swimming, false);
break;
}

case ActionIds.PredictBreak: {
this.handlePredictBreak(packet, player);
break;
Expand Down
59 changes: 38 additions & 21 deletions packages/world/src/components/entity/data/air-supply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
EffectType,
Gamemode
} from "@serenityjs/protocol";
import { BlockIdentifier } from "@serenityjs/block";
import { EntityIdentifier } from "@serenityjs/entity";

import { EntityDataComponent } from "./data";
Expand All @@ -14,33 +13,37 @@ import type { Entity } from "../../../entity";

class EntityAirSupplyComponent extends EntityDataComponent {
public static readonly identifier = "minecraft:air_supply";
public static readonly types: Array<EntityIdentifier> = [
EntityIdentifier.Player
];
public static readonly types = [EntityIdentifier.Player];

public readonly key = ActorDataId.AirSupply;
public readonly type = ActorDataType.Short;
public readonly minValue = 0;
public maxValue = 300;
public readonly defaultValue = 300;

public readonly breathing = this.entity.getComponent("minecraft:breathing");

public constructor(entity: Entity) {
super(entity, EntityAirSupplyComponent.identifier);

this.setCurrentValue(this.defaultValue, false);
}

public onTick(): void {
// Check if the entity is alive.
// If the entity isn't alive, we will skip the logic.
if (!this.entity.isAlive) return;

const enchantmentCheck = true;
const entityBreathingComponent = this.entity.getComponent(
"minecraft:breathing"
);

if (this.entity.isPlayer() && this.entity.gamemode == Gamemode.Creative)
// Check if the entity is a player and if the player is in creative mode
if (this.entity.isPlayer() && this.entity.gamemode === Gamemode.Creative)
return;

// ? Check if the player cannot breath
if (!this.canBreath()) {
entityBreathingComponent.setCurrentValue(false, true);
// Set the entity to not breathing
this.breathing.setCurrentValue(false, true);

// TODO: Add Breathing enchantment modifier
if (!enchantmentCheck) return;
Expand All @@ -52,30 +55,44 @@ class EntityAirSupplyComponent extends EntityDataComponent {
if (!this.entity.isAlive) return;
this.airTicks = 0;

this.entity.applyDamage(2, ActorDamageCause.Drowning);
// Get the cause of the damage
const cause = this.entity.isSwimming
? ActorDamageCause.Drowning
: ActorDamageCause.Suffocation;

this.entity.applyDamage(2, cause);
return;
}
// ? If the player can breath, and the air supply time is less than the max time, recover air time
if (this.airTicks < this.maxValue) {
this.airTicks += 5;
return;
} else if (this.airTicks >= this.maxValue)
entityBreathingComponent.setCurrentValue(true, true);
this.breathing.setCurrentValue(true, true);
}

public canBreath(): boolean {
const breathingEffects = [
EffectType.WaterBreathing,
EffectType.ConduitPower
];
// Check if the entity has breathable effects
if (
this.entity.hasEffect(EffectType.WaterBreathing) ||
this.entity.hasEffect(EffectType.ConduitPower)
)
return true;

// Get the players position
const { x, y, z } = this.entity.position.floor();

// ? Check if the player has any respiration effect, or if the block on his head is other than air (Temporal, check for block collisions)
return (
breathingEffects.some((effect) => this.entity.hasEffect(effect)) ||
this.entity.dimension.getBlock(x, y, z).getType().identifier ==
BlockIdentifier.Air
);
// Get the block at the players position
const block = this.entity.dimension.getBlock(x, y, z);

// Check if the block is air
if (block.isAir()) return true;

// Check if the block is liquid
if (block.isLiquid()) return false;

// Return if the block is solid
return !block.isSolid();
}

public get airTicks(): number {
Expand Down
5 changes: 5 additions & 0 deletions packages/world/src/entity/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ class Entity {
*/
public isAlive = true;

/**
* Whether or not the entity is swimming.
*/
public isSwimming = false;

public constructor(
identifier: EntityIdentifier,
dimension: Dimension,
Expand Down
2 changes: 2 additions & 0 deletions packages/world/src/enums/world-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ enum WorldEvent {
PlayerMissSwing,
PlayerOpenDoor,
PlayerJump,
PlayerStartSwimming,
PlayerStopSwimming,
BlockUpdate,
FurnaceSmelt,
ChunkRead,
Expand Down
2 changes: 2 additions & 0 deletions packages/world/src/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export * from "./player-interact-with-block";
export * from "./player-button-push";
export * from "./player-open-door";
export * from "./player-jump";
export * from "./player-start-swimming";
export * from "./player-stop-swimming";
export * from "./chunk-read";
export * from "./chunk-write";
export * from "./effect-add";
Expand Down
24 changes: 24 additions & 0 deletions packages/world/src/events/player-start-swimming.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { WorldEvent } from "../enums";

import { WorldEventSignal } from "./signal";

import type { Player } from "../player";

class PlayerStartSwimmingSignal extends WorldEventSignal {
public static readonly identifier = WorldEvent.PlayerStartSwimming;

/**
* The player that stopped swimming.
*/
public readonly player: Player;

/**
* Creates a new player stopped swimming signal.
*/
public constructor(player: Player) {
super(player.dimension.world);
this.player = player;
}
}

export { PlayerStartSwimmingSignal };
24 changes: 24 additions & 0 deletions packages/world/src/events/player-stop-swimming.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { WorldEvent } from "../enums";

import { WorldEventSignal } from "./signal";

import type { Player } from "../player";

class PlayerStopSwimmingSignal extends WorldEventSignal {
public static readonly identifier = WorldEvent.PlayerStopSwimming;

/**
* The player that started swimming.
*/
public readonly player: Player;

/**
* Creates a new player started swimming signal.
*/
public constructor(player: Player) {
super(player.dimension.world);
this.player = player;
}
}

export { PlayerStopSwimmingSignal };
4 changes: 4 additions & 0 deletions packages/world/src/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import type {
PlayerMissSwingSignal,
PlayerOpenDoorSignal,
PlayerPlaceBlockSignal,
PlayerStartSwimmingSignal,
PlayerStopSwimmingSignal,
WorldInitializeSignal,
WorldTickSignal
} from "../events";
Expand All @@ -50,6 +52,8 @@ interface WorldEventSignals {
[WorldEvent.PlayerMissSwing]: [PlayerMissSwingSignal];
[WorldEvent.PlayerOpenDoor]: [PlayerOpenDoorSignal];
[WorldEvent.PlayerJump]: [PlayerJumpSignal];
[WorldEvent.PlayerStartSwimming]: [PlayerStartSwimmingSignal];
[WorldEvent.PlayerStopSwimming]: [PlayerStopSwimmingSignal];
[WorldEvent.BlockUpdate]: [BlockUpdateSignal];
[WorldEvent.FurnaceSmelt]: [FurnaceSmeltSignal];
[WorldEvent.ChunkRead]: [ChunkReadSignal];
Expand Down

0 comments on commit 5b33e75

Please sign in to comment.