package com.hypixel.hytale.builtin.deployables.config; import com.hypixel.hytale.builtin.deployables.component.DeployableComponent; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; import com.hypixel.hytale.codec.builder.BuilderCodec; import com.hypixel.hytale.codec.codecs.EnumCodec; import com.hypixel.hytale.codec.codecs.array.ArrayCodec; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.protocol.Vector3f; import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.entity.damage.Damage; import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; import com.hypixel.hytale.server.core.modules.time.TimeResource; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.core.util.TargetUtil; import java.time.Duration; import java.time.Instant; import java.util.function.Consumer; import javax.annotation.Nonnull; public class DeployableAoeConfig extends DeployableConfig { public static final BuilderCodec CODEC = BuilderCodec.builder( DeployableAoeConfig.class, DeployableAoeConfig::new, DeployableConfig.BASE_CODEC ) .append( new KeyedCodec<>("Shape", new EnumCodec<>(DeployableAoeConfig.Shape.class)), (DeployableAoeConfig, s) -> DeployableAoeConfig.shape = s, DeployableAoeConfig -> DeployableAoeConfig.shape ) .documentation("The shape of the detection area") .add() .append( new KeyedCodec<>("StartRadius", Codec.FLOAT), (DeployableAoeConfig, s) -> DeployableAoeConfig.startRadius = s, DeployableAoeConfig -> DeployableAoeConfig.startRadius ) .documentation("The initial detection radius") .add() .append( new KeyedCodec<>("EndRadius", Codec.FLOAT), (DeployableAoeConfig, s) -> DeployableAoeConfig.endRadius = s, DeployableAoeConfig -> DeployableAoeConfig.endRadius ) .documentation("If set, the detection radius will expand to this size over the RadiusChangeTime (RadiusChangeTime must be set)") .add() .append( new KeyedCodec<>("Height", Codec.FLOAT), (DeployableAoeConfig, s) -> DeployableAoeConfig.height = s, DeployableAoeConfig -> DeployableAoeConfig.height ) .documentation("The height of the Shape, if using a cylinder shape") .add() .append( new KeyedCodec<>("RadiusChangeTime", Codec.FLOAT), (DeployableAoeConfig, s) -> DeployableAoeConfig.radiusChangeTime = s, DeployableAoeConfig -> DeployableAoeConfig.radiusChangeTime ) .documentation("The time (starting at spawn) it takes to change from StartRadius to EndRadius") .add() .append( new KeyedCodec<>("DamageInterval", Codec.FLOAT), (DeployableAoeConfig, s) -> DeployableAoeConfig.damageInterval = s, DeployableAoeConfig -> DeployableAoeConfig.damageInterval ) .documentation("The interval between damage being applied to targets in seconds") .add() .append( new KeyedCodec<>("DamageAmount", Codec.FLOAT), (DeployableAoeConfig, s) -> DeployableAoeConfig.damageAmount = s, DeployableAoeConfig -> DeployableAoeConfig.damageAmount ) .documentation("The amount of damage to apply to targets per interval") .add() .append( new KeyedCodec<>("DamageCause", DamageCause.CHILD_ASSET_CODEC), (DeployableAoeConfig, s) -> DeployableAoeConfig.damageCause = s, DeployableAoeConfig -> DeployableAoeConfig.damageCause ) .documentation("The amount of damage to apply to targets per interval") .add() .append( new KeyedCodec<>("ApplyEffects", new ArrayCodec<>(EntityEffect.CHILD_ASSET_CODEC, String[]::new)), (DeployableAoeConfig, s) -> DeployableAoeConfig.effectsToApply = s, DeployableAoeConfig -> DeployableAoeConfig.effectsToApply ) .add() .appendInherited( new KeyedCodec<>("AttackOwner", Codec.BOOLEAN), (o, i) -> o.attackOwner = i, o -> o.attackOwner, (i, o) -> i.attackOwner = o.attackOwner ) .documentation("Whether or not the owner is affected by the attack & effect of this deployable") .add() .appendInherited( new KeyedCodec<>("AttackTeam", Codec.BOOLEAN), (o, i) -> o.attackTeam = i, o -> o.attackTeam, (i, o) -> i.attackTeam = o.attackTeam ) .documentation("Whether or not the team is affected by the attack & effect of this deployable") .add() .appendInherited( new KeyedCodec<>("AttackEnemies", Codec.BOOLEAN), (o, i) -> o.attackEnemies = i, o -> o.attackEnemies, (i, o) -> i.attackEnemies = o.attackEnemies ) .documentation("Whether or not this deployable interacts with non-team entities") .add() .build(); protected float startRadius = 1.0F; protected float endRadius = -1.0F; protected float radiusChangeTime = -1.0F; protected float damageInterval = 1.0F; protected float damageAmount = 1.0F; protected String damageCause = "Physical"; protected String[] effectsToApply; protected boolean attackOwner; protected boolean attackTeam; protected boolean attackEnemies = true; protected DeployableAoeConfig.Shape shape = DeployableAoeConfig.Shape.Sphere; protected float height = 1.0F; protected DamageCause processedDamageCause; protected DeployableAoeConfig() { } @Override public void tick( @Nonnull DeployableComponent deployableComponent, float dt, int index, @Nonnull ArchetypeChunk archetypeChunk, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { Vector3d position = archetypeChunk.getComponent(index, TransformComponent.getComponentType()).getPosition(); World world = store.getExternalData().getWorld(); Ref entityRef = archetypeChunk.getReferenceTo(index); float radius = this.getRadius(store, deployableComponent.getSpawnInstant()); this.handleDebugGraphics(world, deployableComponent.getDebugColor(), position, radius * 2.0F); switch (deployableComponent.getFlag(DeployableComponent.DeployableFlag.STATE)) { case 0: deployableComponent.setFlag(DeployableComponent.DeployableFlag.STATE, 1); break; case 1: deployableComponent.setFlag(DeployableComponent.DeployableFlag.STATE, 2); playAnimation(store, entityRef, this, "Grow"); break; case 2: if (radius >= this.endRadius) { deployableComponent.setFlag(DeployableComponent.DeployableFlag.STATE, 3); playAnimation(store, entityRef, this, "Looping"); } } Ref deployableRef = archetypeChunk.getReferenceTo(index); if (deployableComponent.incrementTimeSinceLastAttack(dt) > this.damageInterval) { deployableComponent.setTimeSinceLastAttack(0.0F); this.handleDetection(store, commandBuffer, deployableRef, deployableComponent, position, radius, DamageCause.PHYSICAL); } super.tick(deployableComponent, dt, index, archetypeChunk, store, commandBuffer); } protected void handleDetection( final Store store, final CommandBuffer commandBuffer, final Ref deployableRef, DeployableComponent deployableComponent, Vector3d position, float radius, final DamageCause damageCause ) { var attackConsumer = new Consumer>() { public void accept(Ref entityStoreRef) { if (entityStoreRef != deployableRef) { DeployableAoeConfig.this.attackTarget(entityStoreRef, deployableRef, damageCause, commandBuffer); DeployableAoeConfig.this.applyEffectToTarget(store, entityStoreRef); } } }; switch (this.shape) { case Sphere: for (Ref targetRef : TargetUtil.getAllEntitiesInSphere(position, radius, store)) { attackConsumer.accept(targetRef); } break; case Cylinder: for (Ref targetRef : TargetUtil.getAllEntitiesInCylinder(position, radius, this.height, store)) { attackConsumer.accept(targetRef); } } } protected void handleDebugGraphics(World world, Vector3f color, Vector3d position, float scale) { if (this.getDebugVisuals()) { ; } } protected void attackTarget(Ref targetRef, Ref ownerRef, DamageCause damageCause, CommandBuffer commandBuffer) { if (!(this.damageAmount <= 0.0F)) { Damage damageEntry = new Damage(new Damage.EntitySource(ownerRef), damageCause, this.damageAmount); if (targetRef.equals(ownerRef)) { damageEntry.setSource(Damage.NULL_SOURCE); } DamageSystems.executeDamage(targetRef, commandBuffer, damageEntry); } } protected void applyEffectToTarget(Store store, Ref targetRef) { if (this.effectsToApply != null) { EffectControllerComponent effectController = store.getComponent(targetRef, EffectControllerComponent.getComponentType()); if (effectController != null) { for (String effect : this.effectsToApply) { if (effect != null) { EntityEffect effectAsset = EntityEffect.getAssetMap().getAsset(effect); if (effectAsset != null) { effectController.addEffect(targetRef, effectAsset, store); } } } } } } protected boolean canAttackEntity(Ref target, DeployableComponent deployable) { boolean isOwner = target.equals(deployable.getOwner()); return !isOwner || this.attackOwner; } protected float getRadius(Store store, Instant startInstant) { if (!(this.radiusChangeTime <= 0.0F) && !(this.endRadius < 0.0F)) { float radiusDiff = this.endRadius - this.startRadius; float increment = radiusDiff / this.radiusChangeTime; Instant now = store.getResource(TimeResource.getResourceType()).getNow(); float timeDiff = (float)Duration.between(startInstant, now).toMillis() / 1000.0F; if (timeDiff > this.radiusChangeTime) { return this.endRadius; } else { float nowIncrement = increment * timeDiff; return this.startRadius + nowIncrement; } } else { return this.startRadius; } } protected DamageCause getDamageCause() { if (this.processedDamageCause == null) { this.processedDamageCause = DamageCause.getAssetMap().getAsset(this.damageCause); if (this.processedDamageCause == null) { this.processedDamageCause = DamageCause.PHYSICAL; } } return this.processedDamageCause; } @Override public String toString() { return "DeployableAoeConfig{}" + super.toString(); } public static enum Shape { Sphere, Cylinder; } }