Skip to content

Commit

Permalink
Merge pull request #60 from Felipe-Devr/develop
Browse files Browse the repository at this point in the history
feat: added Hunger Component, Color class, Hunger based Effects, Effect particles and Hunger based regeneration
  • Loading branch information
PMK744 authored Jul 13, 2024
2 parents ca87bc1 + 3730a39 commit 51b6b94
Show file tree
Hide file tree
Showing 28 changed files with 422 additions and 38 deletions.
82 changes: 82 additions & 0 deletions packages/protocol/src/proto/types/color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { DataType } from "@serenityjs/raknet";

/**
* ARGB Color class that
* Ranges from 0-255
*/
class Color extends DataType {
/**
* The alpha value of the color
*/
public alpha: number;
/**
* The red amount of the color
*/
public red: number;

/**
* The red amount of the color
*/
public green: number;

/**
* The blue amount of the color
*/
public blue: number;

/**
* Creates a new ARGB color
* @param alpha number the alpha value of the color
* @param red number the red value of the color
* @param green number the green value of the color
* @param blue number the blue value of the color
*/
public constructor(alpha: number, red: number, green: number, blue: number) {
super();
this.alpha = alpha & 0xff;
this.red = red & 0xff;
this.green = green & 0xff;
this.blue = blue & 0xff;
}

/**
* Creates a new color based on 2 colors
* @param color1 First color to mix
* @param color2 Second color to mix
* @returns Color The resulting color
*/
public static mix(color1: Color, color2: Color): Color {
const a = Math.round((color1.alpha + color2.alpha) / 2);
const r = Math.round((color1.red + color2.red) / 2);
const g = Math.round((color1.green + color2.green) / 2);
const b = Math.round((color1.blue + color2.blue) / 2);

return new Color(a, r, g, b);
}

/**
* Returns the serialized color
* @returns number The serialzed color
*/
public toInt(): number {
return (
(this.alpha << 24) | (this.red << 16) | (this.green << 8) | this.blue
);
}

/**
* Gets an color from the serialized color number
* @param color number The serialized color to deserialize
* @returns Color
*/

public static fromInt(color: number): Color {
const alpha = (color >> 24) & 0xff;
const red = (color >> 16) & 0xff;
const green = (color >> 8) & 0xff;
const blue = color & 0xff;
return new Color(alpha, red, green, blue);
}
}

export { Color };
1 change: 1 addition & 0 deletions packages/protocol/src/proto/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ export * from "./boss-event-update";
export * from "./animate-entity";
export * from "./emotes";
export * from "./death-parameters";
export * from "./color";
17 changes: 1 addition & 16 deletions packages/world/src/components/entity/attribute/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,22 +121,7 @@ class EntityHealthComponent extends EntityAttributeComponent {
}
}

public onTick(): void {
if (!this.entity.isPlayer()) return;

// Check if the player is in creative mode & the health is at the maximum value
if (
this.entity.gamemode === Gamemode.Creative ||
this.getCurrentValue() === this.effectiveMax
)
return;

// Check if the current tick is a multiple of 80 (4 seconds)
if (this.entity.dimension.world.currentTick % 80n !== 0n) return;

// Increase the health of the entity
this.increaseValue(1);
}
public onTick(): void {}
}

export { EntityHealthComponent };
32 changes: 31 additions & 1 deletion packages/world/src/components/entity/effect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {
type EffectType,
LevelEvent,
LevelEventPacket,
MobEffectEvents,
MobEffectPacket
MobEffectPacket,
Vector3f
} from "@serenityjs/protocol";

import { EntityComponent } from "./entity-component";
Expand All @@ -27,10 +30,37 @@ class EntityEffectsComponent extends EntityComponent {
this.effects.delete(effectType);
continue;
}
// Spawn a particle every 1s
if (
this.entity.dimension.world.currentTick %
(this.effects.size > 1 ? 8n : 2n) ==
0n
) {
// Get all the effects
const effects = [...this.effects.values()].filter(
(effect) => effect.showParticles
);
// Select randomly an effect
const randomEffect =
effects[Math.floor(Math.random() * effects.length)];

// Show it.
if (randomEffect) this.showParticles(randomEffect);
}
effect.internalTick(this.entity);
}
}

private showParticles(effect: Effect): void {
const packet = new LevelEventPacket();
// Potion Effect Particle
packet.event = LevelEvent.ParticleLegacyEvent | 34;
packet.data = effect.color.toInt();
packet.position = this.entity.position.subtract(new Vector3f(0, 1, 0));

this.entity.dimension.broadcast(packet);
}

/**
*
* @param effect The effect to add to the entity
Expand Down
30 changes: 30 additions & 0 deletions packages/world/src/components/player/attribute/exhaustion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { AttributeName } from "@serenityjs/protocol";

import { EntityAttributeComponent } from "../../entity/attribute/attribute";

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

class PlayerExhaustionComponent extends EntityAttributeComponent {
public static readonly identifier = AttributeName.PlayerExhaustion;

public readonly effectiveMin: number = 0;
public readonly effectiveMax: number = 5;
public readonly defaultValue: number = 0;

public constructor(player: Player) {
super(player, PlayerExhaustionComponent.identifier);
this.setCurrentValue(this.defaultValue, false);
}

public onTick(): void {}

public set exhaustion(newExhaustionLevel: number) {
this.setCurrentValue(newExhaustionLevel, true);
}

public get exhaustion(): number {
return this.getCurrentValue();
}
}

export { PlayerExhaustionComponent };
125 changes: 125 additions & 0 deletions packages/world/src/components/player/attribute/hunger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {
ActorEventIds,
ActorEventPacket,
AttributeName,
Gamemode
} from "@serenityjs/protocol";

import { EntityAttributeComponent } from "../../entity/attribute/attribute";

import type { PlayerSaturationComponent } from "./saturation";
import type { PlayerExhaustionComponent } from "./exhaustion";
import type { Player } from "../../../player";

class PlayerHungerComponent extends EntityAttributeComponent {
public static readonly identifier = AttributeName.PlayerHunger;

public readonly effectiveMin: number = 0;
public readonly effectiveMax: number = 20;
public readonly defaultValue: number = 20;
private tickTimer: number = 0;
private saturationComponent: PlayerSaturationComponent;
private exhaustionComponent: PlayerExhaustionComponent;

public constructor(player: Player) {
if (!player.isPlayer()) throw new Error("Cannot use hunger in entities!");
super(player, PlayerHungerComponent.identifier);

this.saturationComponent = player.getComponent(
"minecraft:player.saturation"
);
this.exhaustionComponent = player.getComponent(
"minecraft:player.exhaustion"
);
super.setCurrentValue(this.defaultValue, false);
}

public onTick(): void {
if (!this.entity.isPlayer() || !this.entity.isAlive) return;
if (this.entity.gamemode == Gamemode.Creative) return;
const entityCurrentHealth = this.entity.getHealth();
const entityHealthComponent = this.entity.getComponent("minecraft:health");

this.tickTimer++;
// difficulty modifier

// Reset tick timer after 4 seconds
if (this.tickTimer >= 80) this.tickTimer = 0;
if (this.tickTimer == 0) {
if (
this.food >= 18 &&
entityCurrentHealth < entityHealthComponent.effectiveMax
) {
entityHealthComponent.increaseValue(1);
this.exhaust(6);
} else if (this.food <= 0) {
// Create depending on difficulty
// ! Temporal Implementation until damage is fully implemented
const packet = new ActorEventPacket();
packet.actorRuntimeId = this.entity.runtime;
packet.eventId = ActorEventIds.HURT_ANIMATION;
packet.eventData = -1;

this.entity.dimension.broadcast(packet);
entityHealthComponent.decreaseValue(1);
}
}
if (this.food <= 6) {
//Disable Sprinting
}
}

public exhaust(amount: number): void {
// ! TEMPORARY IMPLEMENTATION
let currentExhaustion = this.exhaustionComponent.exhaustion;
currentExhaustion += amount;

while (currentExhaustion >= 4) {
currentExhaustion -= 4;

if (this.saturationComponent.saturation > 0) {
this.saturationComponent.saturation = Math.max(
0,
this.saturationComponent.saturation - 1
);
continue;
}
if (this.food > 0) this.decreaseValue(1);
}

this.exhaustionComponent.exhaustion = currentExhaustion;
}

public setCurrentValue(newFoodLevel: number): void {
// Check if the new value modifies the food function
const oldFoodValue = this.food;
const functionRanges = [17, 6, 0];
super.setCurrentValue(newFoodLevel, true);

for (const range of functionRanges) {
if (oldFoodValue > range === newFoodLevel > range) continue;
// Reset the tick timer
this.tickTimer = 0;
break;
}
}

// ? Getters And Setters
/* public set food(newFoodLevel: number) {
this.setCurrentValue(newFoodLevel, true);
} */

public get food(): number {
return this.getCurrentValue();
}

public get maxFood(): number {
return this.effectiveMax;
}

public get isHungry(): boolean {
return this.food < this.maxFood;
}
}

export { PlayerHungerComponent };
3 changes: 3 additions & 0 deletions packages/world/src/components/player/attribute/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./hunger";
export * from "./exhaustion";
export * from "./saturation";
30 changes: 30 additions & 0 deletions packages/world/src/components/player/attribute/saturation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { AttributeName } from "@serenityjs/protocol";

import { EntityAttributeComponent } from "../../entity/attribute/attribute";

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

class PlayerSaturationComponent extends EntityAttributeComponent {
public static readonly identifier = AttributeName.PlayerSaturation;

public readonly effectiveMin: number = 0;
public readonly effectiveMax: number = 20;
public readonly defaultValue: number = 20;

public constructor(player: Player) {
super(player, PlayerSaturationComponent.identifier);
this.setCurrentValue(this.defaultValue, false);
}

public onTick(): void {}

public get saturation(): number {
return this.getCurrentValue();
}

public set saturation(newSaturationLevel: number) {
this.setCurrentValue(newSaturationLevel, true);
}
}

export { PlayerSaturationComponent };
1 change: 1 addition & 0 deletions packages/world/src/components/player/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from "./ability";
export * from "./cursor";
export * from "./chunk-rendering";
export * from "./entity-rendering";
export * from "./attribute";
3 changes: 2 additions & 1 deletion packages/world/src/effect/blindness.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { EffectType } from "@serenityjs/protocol";
import { Color, EffectType } from "@serenityjs/protocol";

import { Effect } from "./effect";

import type { Entity } from "../entity";

class BlindnessEffect<T extends Entity> extends Effect {
public effectType: EffectType = EffectType.Blindness;
public color: Color = new Color(255, 31, 31, 35);

public onTick?(entity: T): void;

Expand Down
3 changes: 2 additions & 1 deletion packages/world/src/effect/darkness.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { EffectType } from "@serenityjs/protocol";
import { Color, EffectType } from "@serenityjs/protocol";

import { Effect } from "./effect";

import type { Entity } from "../entity";

class DarknessEffect<T extends Entity> extends Effect {
public effectType: EffectType = EffectType.Darkness;
public color: Color = new Color(255, 41, 39, 33);

public onTick?(entity: T): void;

Expand Down
Loading

0 comments on commit 51b6b94

Please sign in to comment.