package ru.dbotthepony.kstarbound.defs import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableSet import com.google.gson.Gson import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.annotations.JsonAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import ru.dbotthepony.kommons.io.readCollection import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeCollection import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.io.readDouble import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.io.readMVariant2 import ru.dbotthepony.kstarbound.io.readNullableDouble import ru.dbotthepony.kstarbound.io.readNullableString import ru.dbotthepony.kstarbound.io.readVector2d import ru.dbotthepony.kstarbound.io.writeDouble import ru.dbotthepony.kstarbound.io.writeMVariant2 import ru.dbotthepony.kstarbound.io.writeNullable import ru.dbotthepony.kstarbound.io.writeStruct2d import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.network.syncher.legacyCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import ru.dbotthepony.kstarbound.world.physics.Poly import java.io.DataInputStream import java.io.DataOutputStream import java.util.EnumSet // uint8_t enum class TeamType(override val jsonName: String) : IStringSerializable { NULL("null"), // non-PvP-enabled players and player allied NPCs FRIENDLY("friendly"), // hostile and neutral NPCs and monsters ENEMY("enemy"), // PvP-enabled players PVP("pvp"), // cannot damage anything, can be damaged by Friendly/PVP/Assistant PASSIVE("passive"), // cannot damage or be damaged GHOSTLY("ghostly"), // cannot damage enemies, can be damaged by anything except enemy ENVIRONMENT("environment"), // damages anything except ghostly, damaged by anything except ghostly/passive // used for self damage INDISCRIMINATE("indiscriminate"), // cannot damage friendlies and cannot be damaged by anything ASSISTANT("assistant"); } // int32_t enum class HitType(override val jsonName: String) : IStringSerializable { HIT("Hit"), STRONG_HIT("StrongHit"), WEAK_HIT("WeakHit"), SHIELD_HIT("ShieldHit"), KILL("Kill"); } // uint8_t enum class DamageType(override val jsonName: String) : IStringSerializable { NO_DAMAGE("NoDamage"), DAMAGE("Damage"), IGNORE_DEFENCE("IgnoresDef"), KNOCKBACK("IgnoresDef"), ENVIRONMENT("Environment"), STATUS("Environment"); } @JsonFactory data class EntityDamageTeam(val type: TeamType = TeamType.NULL, val team: Int = 0) { constructor(stream: DataInputStream, isLegacy: Boolean) : this(TeamType.entries[stream.readUnsignedByte()], stream.readUnsignedShort()) fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeByte(type.ordinal) stream.writeShort(team) } fun canDamage(victim: EntityDamageTeam, victimIsSelf: Boolean): Boolean { if (victimIsSelf) { return type == TeamType.INDISCRIMINATE } return when (type) { TeamType.NULL -> false TeamType.FRIENDLY -> victim.type in damageableByFriendly TeamType.ENEMY -> victim.type in damageableByEnemy || victim.type == TeamType.ENEMY && team != victim.team TeamType.PVP -> victim.type in damageableByFriendly || victim.type == TeamType.PVP && (team == 0 || team != victim.team) TeamType.PASSIVE -> false // never deal damage TeamType.GHOSTLY -> false // never deal damage TeamType.ENVIRONMENT -> victim.type in damageableByEnvironment TeamType.INDISCRIMINATE -> victim.type != TeamType.GHOSTLY TeamType.ASSISTANT -> victim.type in damageableByFriendly } } companion object { private val damageableByFriendly = EnumSet.noneOf(TeamType::class.java) private val damageableByEnemy = EnumSet.noneOf(TeamType::class.java) private val damageableByEnvironment = EnumSet.noneOf(TeamType::class.java) init { damageableByFriendly.add(TeamType.ENEMY) damageableByFriendly.add(TeamType.PASSIVE) damageableByFriendly.add(TeamType.ENVIRONMENT) damageableByFriendly.add(TeamType.INDISCRIMINATE) damageableByEnemy.add(TeamType.FRIENDLY) damageableByEnemy.add(TeamType.PVP) damageableByEnemy.add(TeamType.INDISCRIMINATE) damageableByEnvironment.add(TeamType.FRIENDLY) damageableByEnvironment.add(TeamType.PVP) damageableByEnvironment.add(TeamType.INDISCRIMINATE) } val NULL = EntityDamageTeam() val PASSIVE = EntityDamageTeam(TeamType.PASSIVE) val CODEC = nativeCodec(::EntityDamageTeam, EntityDamageTeam::write) val LEGACY_CODEC = legacyCodec(::EntityDamageTeam, EntityDamageTeam::write) } } @JsonFactory data class TouchDamage( val poly: ImmutableList = ImmutableList.of(), val teamType: TeamType = TeamType.ENVIRONMENT, val damage: Double = 0.0, val damageSourceKind: String = "", val knockback: Double = 0.0, val statusEffects: ImmutableSet = ImmutableSet.of(), ) // this shit is a complete mess, because in original code DamageSource::toJson() method // will create json structure which will not be readable by DamageSource's constructor // (will always throw an exception) @JsonAdapter(DamageSource.Adapter::class) data class DamageSource( val damageType: DamageType, val damageArea: Either, val damage: Double, val trackSourceEntity: Boolean, val sourceEntityId: Int = 0, val team: EntityDamageTeam = EntityDamageTeam.PASSIVE, val damageRepeatGroup: String? = null, val damageRepeatTimeout: Double? = null, val damageSourceKind: String = "", val statusEffects: ImmutableList = ImmutableList.of(), val knockback: Either, val rayCheck: Boolean = false, ) { constructor(stream: DataInputStream, isLegacy: Boolean) : this( DamageType.entries[stream.readUnsignedByte()], stream.readMVariant2({ Poly.read(this, isLegacy) }, { Line2d(stream, isLegacy) }) ?: throw IllegalArgumentException("Empty MVariant damageArea"), stream.readDouble(isLegacy), stream.readBoolean(), stream.readInt(), EntityDamageTeam(stream, isLegacy), stream.readNullableString(), stream.readNullableDouble(isLegacy), stream.readInternedString(), ImmutableList.copyOf(stream.readCollection { EphemeralStatusEffect(stream, isLegacy) }), stream.readMVariant2({ readDouble(isLegacy) }, { readVector2d(isLegacy) }) ?: throw IllegalArgumentException("Empty MVariant knockback"), stream.readBoolean() ) data class JsonData( val poly: Poly? = null, val line: Line2d? = null, val damage: Double, val damageType: DamageType = DamageType.DAMAGE, val trackSourceEntity: Boolean = true, val sourceEntityId: Int = 0, val teamType: TeamType = TeamType.PASSIVE, val teamNumber: Int = 0, val team: EntityDamageTeam? = null, val damageRepeatGroup: String? = null, val damageRepeatTimeout: Double? = null, val damageSourceKind: String = "", val statusEffects: ImmutableList = ImmutableList.of(), val knockback: Either = Either.left(0.0), val rayCheck: Boolean = false, ) fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeByte(damageType.ordinal) stream.writeMVariant2(damageArea, { it.write(stream, isLegacy) }, { it.write(stream, isLegacy) }) stream.writeDouble(damage, isLegacy) stream.writeBoolean(trackSourceEntity) stream.writeInt(sourceEntityId) team.write(stream, isLegacy) stream.writeNullable(damageRepeatGroup, DataOutputStream::writeBinaryString) stream.writeNullable(damageRepeatTimeout) { writeDouble(it, isLegacy) } stream.writeBinaryString(damageSourceKind) stream.writeCollection(statusEffects) { it.write(stream, isLegacy) } stream.writeMVariant2(knockback, { writeDouble(it, isLegacy) }, { writeStruct2d(it, isLegacy) }) stream.writeBoolean(rayCheck) } class Adapter(gson: Gson) : TypeAdapter() { private val data = FactoryAdapter.createFor(JsonData::class, gson = gson) override fun write(out: JsonWriter, value: DamageSource) { data.write(out, JsonData( poly = value.damageArea.left.orNull(), line = value.damageArea.right.orNull(), damage = value.damage, damageType = value.damageType, trackSourceEntity = value.trackSourceEntity, sourceEntityId = value.sourceEntityId, team = value.team, damageRepeatGroup = value.damageRepeatGroup, damageRepeatTimeout = value.damageRepeatTimeout, damageSourceKind = value.damageSourceKind, statusEffects = value.statusEffects, knockback = value.knockback, rayCheck = value.rayCheck, )) } override fun read(`in`: JsonReader): DamageSource { val read = data.read(`in`) return DamageSource( damageType = read.damageType, damageArea = if (read.line == null) Either.left(read.poly ?: throw JsonSyntaxException("Missing both 'line' and 'poly' from DamageSource json")) else Either.right(read.line), damage = read.damage, trackSourceEntity = read.trackSourceEntity, sourceEntityId = read.sourceEntityId, statusEffects = read.statusEffects, team = read.team ?: EntityDamageTeam(read.teamType, read.teamNumber), damageRepeatGroup = read.damageRepeatGroup, damageRepeatTimeout = read.damageRepeatTimeout, damageSourceKind = read.damageSourceKind, knockback = read.knockback, rayCheck = read.rayCheck, ) } } companion object { val CODEC = nativeCodec(::DamageSource, DamageSource::write) val LEGACY_CODEC = legacyCodec(::DamageSource, DamageSource::write) } }