diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index e07bc3f2b..b134cdba1 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -95,6 +95,9 @@ import net.runelite.api.events.NpcChanged; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcSpawned; +import net.runelite.api.events.PlayerDespawned; +import net.runelite.api.events.PlayerSpawned; +import net.runelite.api.events.PlayerChanged; import net.runelite.api.events.ProjectileMoved; import net.runelite.api.events.WallObjectChanged; import net.runelite.api.events.WallObjectDespawned; @@ -396,6 +399,7 @@ enum ComputeMode public boolean configTzhaarHD = true; public boolean configProjectileLights = true; public boolean configNpcLights = true; + public boolean configEquipmentLights = true; public boolean configShadowsEnabled = false; public boolean configExpandShadowDraw = false; @@ -414,6 +418,7 @@ protected void startUp() configTzhaarHD = config.tzhaarHD(); configProjectileLights = config.projectileLights(); configNpcLights = config.npcLights(); + configEquipmentLights = config.equipmentLights(); configShadowsEnabled = config.shadowsEnabled(); configExpandShadowDraw = config.expandShadowDraw(); @@ -2130,6 +2135,9 @@ public void onConfigChanged(ConfigChanged event) case "npcLights": configNpcLights = config.npcLights(); break; + case "equipmentLights": + configEquipmentLights = config.equipmentLights(); + break; case "expandShadowDraw": configExpandShadowDraw = config.expandShadowDraw(); break; @@ -2466,11 +2474,29 @@ public void onNpcDespawned(NpcDespawned npcDespawned) { lightManager.removeNpcLight(npcDespawned); } - - @Subscribe + + @Subscribe public void onNpcChanged(NpcChanged npcChanged) { lightManager.updateNpcChanged(npcChanged); + } + + @Subscribe + public void onPlayerSpawned(PlayerSpawned playerSpawned) + { + lightManager.addEquipmentLight(playerSpawned.getPlayer()); + } + + @Subscribe + public void onPlayerDespawned(PlayerDespawned playerDespawned) + { + lightManager.removeEquipmentLight(playerDespawned); + } + + @Subscribe + public void onPlayerChanged(PlayerChanged playerChanged) + { + lightManager.equipmentLightChanged(playerChanged); } @Subscribe diff --git a/src/main/java/rs117/hd/HdPluginConfig.java b/src/main/java/rs117/hd/HdPluginConfig.java index 0667c5b96..f7cd09a3b 100644 --- a/src/main/java/rs117/hd/HdPluginConfig.java +++ b/src/main/java/rs117/hd/HdPluginConfig.java @@ -234,11 +234,23 @@ default boolean npcLights() return true; } + @ConfigItem( + keyName = "equipmentLights", + name = "Equipment Lights", + description = "Adds dynamic lights to some equipment when equipped on a player.", + position = 104, + section = lightingSettings + ) + default boolean equipmentLights() + { + return true; + } + @ConfigItem( keyName = "atmosphericLighting", name = "Atmospheric Lighting", description = "Changes the color and brightness of full-scene lighting in certain areas.", - position = 104, + position = 105, section = lightingSettings ) default boolean atmosphericLighting() @@ -250,7 +262,7 @@ default boolean atmosphericLighting() keyName = "shadowsEnabled", name = "Shadows", description = "Enables fully-dynamic shadows.", - position = 105, + position = 106, section = lightingSettings ) default boolean shadowsEnabled() @@ -262,7 +274,7 @@ default boolean shadowsEnabled() keyName = "shadowResolution", name = "Shadow Resolution", description = "The resolution of the shadow maps. Higher resolutions result in sharper, higher quality shadows at the cost of performance.", - position = 106, + position = 107, section = lightingSettings ) default ShadowResolution shadowResolution() @@ -274,7 +286,7 @@ default ShadowResolution shadowResolution() keyName = "shadowDistance", name = "Shadow Distance", description = "The maximum draw distance of shadow maps. Shorter distances result in sharper, higher quality shadows.", - position = 107, + position = 108, section = lightingSettings ) default ShadowDistance shadowDistance() @@ -286,7 +298,7 @@ default ShadowDistance shadowDistance() keyName = "expandShadowDraw", name = "Expand Shadow Draw", description = "Reduces 'flickering' of shadows disappearing at screen edge by increasing geometry drawn at a cost of performance.", - position = 108, + position = 109, section = lightingSettings ) default boolean expandShadowDraw() diff --git a/src/main/java/rs117/hd/lighting/EquipmentLight.java b/src/main/java/rs117/hd/lighting/EquipmentLight.java new file mode 100644 index 000000000..69af0091d --- /dev/null +++ b/src/main/java/rs117/hd/lighting/EquipmentLight.java @@ -0,0 +1,70 @@ +package rs117.hd.lighting; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.ItemID; +import static net.runelite.api.ItemID.*; +import rs117.hd.lighting.LightManager.LightType; +import rs117.hd.lighting.LightManager.Alignment; + +@AllArgsConstructor +@Getter +enum EquipmentLight +{ + FIRE_CAPE(90, Alignment.BACK, 150, 6f, rgb(220, 156, 74), LightType.PULSE, 4000, 8, ItemID.FIRE_CAPE, FIRE_CAPE_10566), + FIRE_MAX_CAPE(90, Alignment.BACK, 150, 6f, rgb(220, 156, 74), LightType.PULSE, 4000, 8, ItemID.FIRE_MAX_CAPE, FIRE_MAX_CAPE_21186), + INFERNAL_CAPE(90, Alignment.BACK, 150, 6f, rgb(133, 64, 0), LightType.PULSE, 4000, 8, ItemID.INFERNAL_CAPE, INFERNAL_CAPE_21297, INFERNAL_CAPE_23622), + INFERNAL_MAX_CAPE(90, Alignment.BACK, 150, 6f, rgb(133, 64, 0), LightType.PULSE, 4000, 8, ItemID.INFERNAL_MAX_CAPE, INFERNAL_MAX_CAPE_21285), + LIT_BUG_LANTERN(50, Alignment.LEFT, 250, 6f, rgb(255, 233, 138), LightType.FLICKER, 0, 10, ItemID.LIT_BUG_LANTERN), + ; + + private final int[] id; + private final int height; + private final Alignment alignment; + private final int size; + private final float strength; + private final int rgb; + private final LightType lightType; + private final float duration; + private final float range; + + EquipmentLight(int height, Alignment alignment, int size, float strength, int rgb, LightType lightType, float duration, float range, int... ids) + { + this.height = height; + this.alignment = alignment; + this.size = size; + this.strength = strength; + this.rgb = rgb; + this.lightType = lightType; + this.duration = duration; + this.range = range; + this.id = ids; + } + + private static final Map LIGHTS; + + static + { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + for (EquipmentLight equipmentLight : values()) + { + for (int id : equipmentLight.id) + { + builder.put(id + 512, equipmentLight); + } + } + LIGHTS = builder.build(); + } + + static EquipmentLight find(int id) + { + return LIGHTS.get(id); + } + + private static int rgb(int r, int g, int b) + { + return (r << 16) | (g << 8) | b; + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/lighting/LightManager.java b/src/main/java/rs117/hd/lighting/LightManager.java index 7835389d8..8acce551e 100644 --- a/src/main/java/rs117/hd/lighting/LightManager.java +++ b/src/main/java/rs117/hd/lighting/LightManager.java @@ -31,6 +31,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.Arrays; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; @@ -52,10 +53,14 @@ import net.runelite.api.Tile; import net.runelite.api.TileObject; import net.runelite.api.WallObject; +import net.runelite.api.Player; +import net.runelite.api.PlayerComposition; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcChanged; +import net.runelite.api.events.PlayerChanged; +import net.runelite.api.events.PlayerDespawned; import rs117.hd.HdPlugin; import rs117.hd.HdPluginConfig; import rs117.hd.HDUtils; @@ -165,6 +170,8 @@ public static class Light public Projectile projectile = null; public NPC npc = null; public TileObject object = null; + public Player player = null; + public int equipmentId = -1; public Light(int worldX, int worldY, int plane, int height, Alignment alignment, int size, float strength, int[] color, LightType type, float duration, float range, int fadeInDuration) { @@ -278,9 +285,46 @@ public void update() float lerpY = (light.y % Perspective.LOCAL_TILE_SIZE) / (float) Perspective.LOCAL_TILE_SIZE; int baseTileX = (int) Math.floor(light.x / (float) Perspective.LOCAL_TILE_SIZE); int baseTileY = (int) Math.floor(light.y / (float) Perspective.LOCAL_TILE_SIZE); - float heightNorth = HDUtils.lerp(client.getTileHeights()[plane][baseTileX][baseTileY + 1], client.getTileHeights()[plane][baseTileX + 1][baseTileY + 1], lerpX); - float heightSouth = HDUtils.lerp(client.getTileHeights()[plane][baseTileX][baseTileY], client.getTileHeights()[plane][baseTileX + 1][baseTileY], lerpX); + boolean bridge = client.getScene().getTiles()[plane][baseTileX][baseTileY] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY].getBridge() != null; + boolean nBridge = client.getScene().getTiles()[plane][baseTileX][baseTileY + 1] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY + 1].getBridge() != null; + boolean eBridge = client.getScene().getTiles()[plane][baseTileX + 1][baseTileY] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY + 1].getBridge() != null; + boolean sBridge = client.getScene().getTiles()[plane][baseTileX][baseTileY - 1] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY + 1].getBridge() != null; + boolean wBridge = client.getScene().getTiles()[plane][baseTileX - 1][baseTileY] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY + 1].getBridge() != null; + int nwPlane = plane; + int nePlane = plane; + int swPlane = plane; + int sePlane = plane; + if (bridge) + { + nwPlane = nePlane = swPlane = sePlane = plane + 1; + } + else + { + if (nBridge || wBridge) + { + nwPlane++; + } + if (nBridge || eBridge) + { + nePlane++; + } + if (sBridge || wBridge) + { + swPlane++; + } + if (sBridge || eBridge) + { + sePlane++; + } + } + float nwHeight = client.getTileHeights()[nwPlane][baseTileX][baseTileY + 1]; + float neHeight = client.getTileHeights()[nePlane][baseTileX + 1][baseTileY + 1]; + float swHeight = client.getTileHeights()[swPlane][baseTileX][baseTileY]; + float seHeight = client.getTileHeights()[sePlane][baseTileX + 1][baseTileY]; + float heightNorth = HDUtils.lerp(nwHeight, neHeight, lerpX); + float heightSouth = HDUtils.lerp(swHeight, seHeight, lerpX); float tileHeight = HDUtils.lerp(heightSouth, heightNorth, lerpY); + light.z = (int) tileHeight - 1 - light.height; light.visible = light.npc.getModel() != null; @@ -296,6 +340,94 @@ public void update() } } + if (light.player != null) + { + if (!Arrays.stream(light.player.getPlayerComposition().getEquipmentIds()).anyMatch(id -> id == light.equipmentId)) + { + lightIterator.remove(); + continue; + } + + light.x = light.player.getLocalLocation().getX(); + light.y = light.player.getLocalLocation().getY(); + + int orientation = light.player.getCurrentOrientation(); + + if (orientation != -1 && light.alignment != Alignment.CENTER) + { + orientation += light.alignment.orientation; + orientation %= 2048; + + float sine = Perspective.SINE[orientation] / 65536f; + float cosine = Perspective.COSINE[orientation] / 65536f; + //cosine /= (float)light.localSizeX / (float)localSizeY; + + // multiply by 0.75 to keep lights a little closer to the player model + int offsetX = (int)(sine * Perspective.LOCAL_HALF_TILE_SIZE * 0.75f); + int offsetY = (int)(cosine * Perspective.LOCAL_HALF_TILE_SIZE * 0.75f); + + light.x += offsetX; + light.y += offsetY; + } + + int plane = light.player.getWorldLocation().getPlane(); + light.plane = plane; + + // Interpolate between tile heights based on specific scene coordinates. + float lerpX = (light.x % Perspective.LOCAL_TILE_SIZE) / (float) Perspective.LOCAL_TILE_SIZE; + float lerpY = (light.y % Perspective.LOCAL_TILE_SIZE) / (float) Perspective.LOCAL_TILE_SIZE; + int baseTileX = (int) Math.floor(light.x / (float) Perspective.LOCAL_TILE_SIZE); + int baseTileY = (int) Math.floor(light.y / (float) Perspective.LOCAL_TILE_SIZE); + boolean bridge = client.getScene().getTiles()[plane][baseTileX][baseTileY] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY].getBridge() != null; + boolean nBridge = client.getScene().getTiles()[plane][baseTileX][baseTileY + 1] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY + 1].getBridge() != null; + boolean eBridge = client.getScene().getTiles()[plane][baseTileX + 1][baseTileY] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY + 1].getBridge() != null; + boolean sBridge = client.getScene().getTiles()[plane][baseTileX][baseTileY - 1] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY + 1].getBridge() != null; + boolean wBridge = client.getScene().getTiles()[plane][baseTileX - 1][baseTileY] != null && client.getScene().getTiles()[plane][baseTileX][baseTileY + 1].getBridge() != null; + int nwPlane = plane; + int nePlane = plane; + int swPlane = plane; + int sePlane = plane; + if (bridge) + { + nwPlane = nePlane = swPlane = sePlane = plane + 1; + } + else + { + if (nBridge || wBridge) + { + nwPlane++; + } + if (nBridge || eBridge) + { + nePlane++; + } + if (sBridge || wBridge) + { + swPlane++; + } + if (sBridge || eBridge) + { + sePlane++; + } + } + float nwHeight = client.getTileHeights()[nwPlane][baseTileX][baseTileY + 1]; + float neHeight = client.getTileHeights()[nePlane][baseTileX + 1][baseTileY + 1]; + float swHeight = client.getTileHeights()[swPlane][baseTileX][baseTileY]; + float seHeight = client.getTileHeights()[sePlane][baseTileX + 1][baseTileY]; + float heightNorth = HDUtils.lerp(nwHeight, neHeight, lerpX); + float heightSouth = HDUtils.lerp(swHeight, seHeight, lerpX); + float tileHeight = HDUtils.lerp(heightSouth, heightNorth, lerpY); + + light.z = (int) tileHeight - 1 - light.height; + + light.visible = light.player.getModel() != null; + + if (!hdPlugin.configEquipmentLights) + { + light.visible = false; + } + } + if (light.type == LightType.FLICKER) { double change = Math.random() * 2 - 1.0f; @@ -491,6 +623,7 @@ public void loadSceneLights() } updateSceneNpcs(); + updateSceneEquipment(); } @@ -514,6 +647,14 @@ void updateSceneNpcs() } } + void updateSceneEquipment() + { + for (Player player : client.getPlayers()) + { + addEquipmentLight(player); + } + } + public void updateNpcChanged(NpcChanged npcChanged) { removeNpcLight(npcChanged); @@ -623,6 +764,67 @@ public void removeNpcLight(NpcDespawned npcDespawned) public void removeNpcLight(NpcChanged npcChanged) { sceneLights.removeIf(light -> light.npc == npcChanged.getNpc()); + } + + public void addEquipmentLight(Player player) + { + PlayerComposition composition = player.getPlayerComposition(); + + if (composition != null) + { + for (int id : composition.getEquipmentIds()) + { + EquipmentLight equipmentLight = EquipmentLight.find(id); + if (equipmentLight == null) + { + continue; + } + + addEquipmentLight(id, player); + } + } + } + + public void addEquipmentLight(int id, Player player) + { + EquipmentLight equipmentLight = EquipmentLight.find(id); + if (equipmentLight == null) + { + return; + } + + // prevent duplicate lights being spawned for the same player + for (Light light : sceneLights) + { + if (light.player == player && light.equipmentId == id) + { + return; + } + } + + int rgb = equipmentLight.getRgb(); + int r = rgb >>> 16; + int g = (rgb >> 8) & 0xff; + int b = rgb & 0xff; + Light light = new Light(0, 0, -1, + equipmentLight.getHeight(), equipmentLight.getAlignment(), equipmentLight.getSize(), equipmentLight.getStrength(), new int[]{r, g, b}, equipmentLight.getLightType(), equipmentLight.getDuration(), equipmentLight.getRange(), 0); + light.player = player; + light.equipmentId = id; + light.visible = false; + + sceneLights.add(light); + } + + public void removeEquipmentLight(PlayerDespawned playerDespawned) + { + sceneLights.removeIf(light -> light.player == playerDespawned.getPlayer()); + } + + public void equipmentLightChanged(PlayerChanged playerChanged) + { + Player player = playerChanged.getPlayer(); + sceneLights.removeIf(light -> light.player == player && Arrays.stream(player.getPlayerComposition().getEquipmentIds()).anyMatch(id -> id == light.equipmentId)); + addEquipmentLight(player); } public void addObjectLight(TileObject tileObject, int plane)