deathindicator: add permabones (#1829)
* Add api support for faking ground items and add permabones * Remove unused method * fixed loading/keeping loaded, fixed calendar thingy, probably forgot something * Actually commit the calendar tsuff
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package net.runelite.client.plugins.deathindicator;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.runelite.api.Scene;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import static net.runelite.client.plugins.deathindicator.DeathIndicatorPlugin.HIJACKED_ITEMID;
|
||||
import net.runelite.client.util.ColorUtil;
|
||||
import net.runelite.client.util.MiscUtils;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@Setter
|
||||
class Bone
|
||||
{
|
||||
private String name;
|
||||
private WorldPoint loc;
|
||||
private Instant time;
|
||||
|
||||
void addToScene(Scene scene)
|
||||
{
|
||||
scene.addItem(HIJACKED_ITEMID, 1, loc);
|
||||
}
|
||||
|
||||
void removeFromScene(Scene scene)
|
||||
{
|
||||
scene.removeItem(HIJACKED_ITEMID, 1, loc);
|
||||
}
|
||||
|
||||
String getName()
|
||||
{
|
||||
return ColorUtil.colorStartTag(0xff9040) + "Bones (" + name + ")";
|
||||
}
|
||||
|
||||
String getExamine()
|
||||
{
|
||||
return name + " died here " + MiscUtils.formatTimeAgo(Duration.between(time, Instant.now()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package net.runelite.client.plugins.deathindicator;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.Scene;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import static net.runelite.http.api.RuneLiteAPI.GSON;
|
||||
|
||||
@Slf4j
|
||||
public class Bones
|
||||
{
|
||||
private static final String CONFIG_GROUP = "deathIndicator";
|
||||
private static final String BONES_PREFIX = "bones_";
|
||||
|
||||
private ImmutableMap<Integer, Map<WorldPoint, List<Bone>>> map;
|
||||
private boolean changed = false;
|
||||
|
||||
void init(Client client, ConfigManager configManager)
|
||||
{
|
||||
// Clone is important here as the normal array changes
|
||||
int[] regions = client.getMapRegions().clone();
|
||||
Bone[][] bones = getBones(configManager, regions);
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Regions are now {}", Arrays.toString(regions));
|
||||
|
||||
int n = 0;
|
||||
for (Bone[] ar : bones)
|
||||
{
|
||||
n += ar.length;
|
||||
}
|
||||
log.debug("Loaded {} Bones", n);
|
||||
}
|
||||
|
||||
initMap(regions, bones);
|
||||
|
||||
showBones(client.getScene());
|
||||
}
|
||||
|
||||
private Bone[][] getBones(ConfigManager configManager, int[] regions)
|
||||
{
|
||||
Bone[][] bones = new Bone[regions.length][];
|
||||
|
||||
for (int i = 0; i < regions.length; i++)
|
||||
{
|
||||
int region = regions[i];
|
||||
bones[i] = getBones(configManager, region);
|
||||
}
|
||||
|
||||
return bones;
|
||||
}
|
||||
|
||||
private Bone[] getBones(ConfigManager configManager, int regionId)
|
||||
{
|
||||
String json = configManager.getConfiguration(CONFIG_GROUP, BONES_PREFIX + regionId);
|
||||
if (json == null)
|
||||
{
|
||||
return new Bone[0];
|
||||
}
|
||||
|
||||
return GSON.fromJson(json, Bone[].class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void initMap(int[] regions, Bone[][] bones)
|
||||
{
|
||||
ImmutableMap.Builder<Integer, Map<WorldPoint, List<Bone>>> builder = ImmutableMap.builder();
|
||||
|
||||
for (int i = 0; i < regions.length; i++)
|
||||
{
|
||||
int region = regions[i];
|
||||
Bone[] boneA = bones[i];
|
||||
if (boneA.length == 0)
|
||||
{
|
||||
builder.put(region, Collections.EMPTY_MAP);
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<WorldPoint, List<Bone>> map = new HashMap(boneA.length);
|
||||
for (Bone b : boneA)
|
||||
{
|
||||
List<Bone> list = map.computeIfAbsent(b.getLoc(), wp -> new ArrayList<>());
|
||||
list.add(b);
|
||||
}
|
||||
|
||||
builder.put(region, map);
|
||||
}
|
||||
|
||||
this.map = builder.build();
|
||||
}
|
||||
|
||||
private void showBones(Scene scene)
|
||||
{
|
||||
this.forEach(bone -> bone.addToScene(scene));
|
||||
}
|
||||
|
||||
void save(ConfigManager configManager)
|
||||
{
|
||||
if (this.map == null || !changed)
|
||||
{
|
||||
this.changed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, Map<WorldPoint, List<Bone>>> entry : this.map.entrySet())
|
||||
{
|
||||
final String key = BONES_PREFIX + entry.getKey();
|
||||
final Map<WorldPoint, List<Bone>> map = entry.getValue();
|
||||
if (map.size() == 0)
|
||||
{
|
||||
configManager.unsetConfiguration(CONFIG_GROUP, key);
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Bone> list = new ArrayList<>(map.values().size());
|
||||
for (List<Bone> lb : map.values())
|
||||
{
|
||||
list.addAll(lb);
|
||||
}
|
||||
|
||||
String val = GSON.toJson(list.toArray(new Bone[0]));
|
||||
configManager.setConfiguration(CONFIG_GROUP, key, val);
|
||||
}
|
||||
|
||||
this.changed = false;
|
||||
}
|
||||
|
||||
public boolean add(Bone bone)
|
||||
{
|
||||
if (this.map == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.changed = true;
|
||||
final int region = bone.getLoc().getRegionID();
|
||||
final Map<WorldPoint, List<Bone>> map = this.map.get(region);
|
||||
final List<Bone> list = map.computeIfAbsent(bone.getLoc(), wp -> new ArrayList<>());
|
||||
list.add(bone);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void remove(Bone bone)
|
||||
{
|
||||
this.changed = true;
|
||||
final int region = bone.getLoc().getRegionID();
|
||||
final Map<WorldPoint, List<Bone>> map = this.map.get(region);
|
||||
final List<Bone> list = map.get(bone.getLoc());
|
||||
list.remove(bone);
|
||||
}
|
||||
|
||||
public void clear(Scene scene)
|
||||
{
|
||||
if (map == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.forEach(bone -> bone.removeFromScene(scene));
|
||||
}
|
||||
|
||||
public Bone get(WorldPoint point, int i)
|
||||
{
|
||||
return get(point).get(i);
|
||||
}
|
||||
|
||||
public List<Bone> get(WorldPoint point)
|
||||
{
|
||||
final int reg = point.getRegionID();
|
||||
final Map<WorldPoint, List<Bone>> map = this.map.get(reg);
|
||||
if (map == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return map.get(point);
|
||||
}
|
||||
|
||||
private void forEach(Consumer<Bone> consumer)
|
||||
{
|
||||
for (Map<WorldPoint, List<Bone>> map : this.map.values())
|
||||
{
|
||||
for (Map.Entry<WorldPoint, List<Bone>> entry : map.entrySet())
|
||||
{
|
||||
for (Bone bone : entry.getValue())
|
||||
{
|
||||
consumer.accept(bone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,4 +152,14 @@ public interface DeathIndicatorConfig extends Config
|
||||
description = ""
|
||||
)
|
||||
void timeOfDeath(Instant timeOfDeath);
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "permaBones",
|
||||
name = "Permanent bones",
|
||||
description = "Show right clickable bones with the name of who died permanently, after seeing someone die"
|
||||
)
|
||||
default boolean permaBones()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,18 +30,32 @@ import java.awt.image.BufferedImage;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.ItemDefinition;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.api.MenuEntry;
|
||||
import net.runelite.api.MenuOpcode;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.ConfigChanged;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.ItemDespawned;
|
||||
import net.runelite.api.events.LocalPlayerDeath;
|
||||
import net.runelite.api.events.MenuEntryAdded;
|
||||
import net.runelite.api.events.MenuOpened;
|
||||
import net.runelite.api.events.MenuOptionClicked;
|
||||
import net.runelite.api.events.PlayerDeath;
|
||||
import net.runelite.api.events.PostItemDefinition;
|
||||
import net.runelite.api.util.Text;
|
||||
import net.runelite.client.callback.ClientThread;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.eventbus.EventBus;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
@@ -61,6 +75,10 @@ import net.runelite.client.util.ImageUtil;
|
||||
@Slf4j
|
||||
public class DeathIndicatorPlugin extends Plugin
|
||||
{
|
||||
private static final Object BONES = new Object();
|
||||
// A random number, that jagex probably won't actually use in the near future
|
||||
static final int HIJACKED_ITEMID = 0x69696969;
|
||||
|
||||
private static final Set<Integer> RESPAWN_REGIONS = ImmutableSet.of(
|
||||
12850, // Lumbridge
|
||||
11828, // Falador
|
||||
@@ -88,6 +106,14 @@ public class DeathIndicatorPlugin extends Plugin
|
||||
@Inject
|
||||
private EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
private ConfigManager configManager;
|
||||
|
||||
@Inject
|
||||
private ClientThread clientThread;
|
||||
|
||||
private final Bones bones = new Bones();
|
||||
|
||||
private BufferedImage mapArrow;
|
||||
|
||||
private Timer deathTimer;
|
||||
@@ -95,7 +121,7 @@ public class DeathIndicatorPlugin extends Plugin
|
||||
private WorldPoint lastDeath;
|
||||
private Instant lastDeathTime;
|
||||
private int lastDeathWorld;
|
||||
|
||||
private int despawnIdx = 0;
|
||||
@Provides
|
||||
DeathIndicatorConfig deathIndicatorConfig(ConfigManager configManager)
|
||||
{
|
||||
@@ -129,12 +155,18 @@ public class DeathIndicatorPlugin extends Plugin
|
||||
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
||||
worldMapPointManager.add(new DeathWorldMapPoint(new WorldPoint(config.deathLocationX(), config.deathLocationY(), config.deathLocationPlane()), this));
|
||||
}
|
||||
|
||||
if (config.permaBones() && client.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
clientThread.invokeLater(this::initBones);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
eventBus.unregister(this);
|
||||
eventBus.unregister(BONES);
|
||||
|
||||
if (client.hasHintArrow())
|
||||
{
|
||||
@@ -148,6 +180,24 @@ public class DeathIndicatorPlugin extends Plugin
|
||||
}
|
||||
|
||||
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
||||
|
||||
clientThread.invokeLater(this::clearBones);
|
||||
saveBones();
|
||||
}
|
||||
|
||||
private void initBones()
|
||||
{
|
||||
bones.init(client, configManager);
|
||||
}
|
||||
|
||||
private void saveBones()
|
||||
{
|
||||
bones.save(configManager);
|
||||
}
|
||||
|
||||
private void clearBones()
|
||||
{
|
||||
bones.clear(client.getScene());
|
||||
}
|
||||
|
||||
private void addSubscriptions()
|
||||
@@ -156,6 +206,100 @@ public class DeathIndicatorPlugin extends Plugin
|
||||
eventBus.subscribe(LocalPlayerDeath.class, this, this::onLocalPlayerDeath);
|
||||
eventBus.subscribe(GameTick.class, this, this::onGameTick);
|
||||
eventBus.subscribe(GameStateChanged.class, this, this::onGameStateChanged);
|
||||
if (config.permaBones())
|
||||
{
|
||||
addBoneSubs();
|
||||
}
|
||||
}
|
||||
|
||||
private void addBoneSubs()
|
||||
{
|
||||
eventBus.subscribe(ItemDespawned.class, BONES, this::onItemDespawn);
|
||||
eventBus.subscribe(PlayerDeath.class, BONES, this::onPlayerDeath);
|
||||
eventBus.subscribe(MenuEntryAdded.class, BONES, this::onMenuEntryAdded);
|
||||
eventBus.subscribe(MenuOptionClicked.class, BONES, this::onMenuOptionClicked);
|
||||
eventBus.subscribe(MenuOpened.class, BONES, this::onMenuOpened);
|
||||
eventBus.subscribe(PostItemDefinition.class, BONES, this::onPostItemDefinition);
|
||||
}
|
||||
|
||||
private void onPostItemDefinition(PostItemDefinition def)
|
||||
{
|
||||
ItemDefinition itemDef = def.getItemDefinition();
|
||||
if (itemDef.getId() == HIJACKED_ITEMID)
|
||||
{
|
||||
itemDef.setModelOverride(ItemID.BONES);
|
||||
itemDef.setName("Bones");
|
||||
// This is so never hide untradeables doesn't not hide it
|
||||
itemDef.setTradeable(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPlayerDeath(PlayerDeath death)
|
||||
{
|
||||
Player p = death.getPlayer();
|
||||
Bone b = new Bone();
|
||||
|
||||
b.setName(Text.sanitize(p.getName()));
|
||||
b.setTime(Instant.now());
|
||||
b.setLoc(p.getWorldLocation());
|
||||
|
||||
while (!bones.add(b))
|
||||
{
|
||||
initBones();
|
||||
}
|
||||
|
||||
b.addToScene(client.getScene());
|
||||
}
|
||||
|
||||
private void onMenuEntryAdded(MenuEntryAdded event)
|
||||
{
|
||||
if (event.getIdentifier() == HIJACKED_ITEMID)
|
||||
{
|
||||
if (event.getOpcode() == MenuOpcode.GROUND_ITEM_THIRD_OPTION.getId())
|
||||
{
|
||||
client.setMenuOptionCount(client.getMenuOptionCount() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onMenuOpened(MenuOpened event)
|
||||
{
|
||||
int idx = 0;
|
||||
|
||||
for (MenuEntry entry : event)
|
||||
{
|
||||
if (entry.getIdentifier() != HIJACKED_ITEMID)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only entries with appropriate identifier here will be examine so that's easy
|
||||
// Add idx to id field, so we can find that back from clicked event
|
||||
entry.setIdentifier(HIJACKED_ITEMID + idx);
|
||||
|
||||
Bone bone = bones.get(
|
||||
WorldPoint.fromScene(client, entry.getParam0(), entry.getParam1(), client.getPlane()),
|
||||
idx++
|
||||
);
|
||||
|
||||
entry.setTarget(bone.getName());
|
||||
event.setModified();
|
||||
}
|
||||
}
|
||||
|
||||
private void onMenuOptionClicked(MenuOptionClicked event)
|
||||
{
|
||||
if (event.getIdentifier() >= HIJACKED_ITEMID
|
||||
&& event.getOpcode() == MenuOpcode.EXAMINE_ITEM_GROUND.getId())
|
||||
{
|
||||
Bone b = bones.get(
|
||||
WorldPoint.fromScene(client, event.getParam0(), event.getParam1(), client.getPlane()),
|
||||
event.getIdentifier() - HIJACKED_ITEMID
|
||||
);
|
||||
|
||||
client.addChatMessage(ChatMessageType.ITEM_EXAMINE, "", b.getExamine(), "");
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
|
||||
private void onLocalPlayerDeath(LocalPlayerDeath death)
|
||||
@@ -238,6 +382,29 @@ public class DeathIndicatorPlugin extends Plugin
|
||||
{
|
||||
if (event.getGroup().equals("deathIndicator"))
|
||||
{
|
||||
if ("permaBones".equals(event.getKey()))
|
||||
{
|
||||
if (config.permaBones())
|
||||
{
|
||||
addBoneSubs();
|
||||
|
||||
if (client.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
clientThread.invokeLater(this::initBones);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
eventBus.unregister(BONES);
|
||||
|
||||
if (client.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
clientThread.invokeLater(this::clearBones);
|
||||
saveBones();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!config.showDeathHintArrow() && hasDied())
|
||||
{
|
||||
client.clearHintArrow();
|
||||
@@ -267,32 +434,62 @@ public class DeathIndicatorPlugin extends Plugin
|
||||
|
||||
private void onGameStateChanged(GameStateChanged event)
|
||||
{
|
||||
if (!hasDied())
|
||||
switch (event.getGameState())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
if (client.getWorld() == config.deathWorld())
|
||||
{
|
||||
WorldPoint deathPoint = new WorldPoint(config.deathLocationX(), config.deathLocationY(), config.deathLocationPlane());
|
||||
|
||||
if (config.showDeathHintArrow())
|
||||
case LOADING:
|
||||
clearBones();
|
||||
saveBones();
|
||||
break;
|
||||
case LOGGED_IN:
|
||||
if (config.permaBones())
|
||||
{
|
||||
client.setHintArrow(deathPoint);
|
||||
initBones();
|
||||
}
|
||||
|
||||
if (config.showDeathOnWorldMap())
|
||||
if (!hasDied())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.getWorld() == config.deathWorld())
|
||||
{
|
||||
WorldPoint deathPoint = new WorldPoint(config.deathLocationX(), config.deathLocationY(), config.deathLocationPlane());
|
||||
|
||||
if (config.showDeathHintArrow())
|
||||
{
|
||||
client.setHintArrow(deathPoint);
|
||||
}
|
||||
|
||||
if (config.showDeathOnWorldMap())
|
||||
{
|
||||
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
||||
worldMapPointManager.add(new DeathWorldMapPoint(deathPoint, this));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
||||
worldMapPointManager.add(new DeathWorldMapPoint(deathPoint, this));
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void onItemDespawn(ItemDespawned event)
|
||||
{
|
||||
if (event.getItem().getId() == HIJACKED_ITEMID)
|
||||
{
|
||||
List<Bone> list = bones.get(event.getTile().getWorldLocation());
|
||||
if (list == null)
|
||||
{
|
||||
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
||||
return;
|
||||
}
|
||||
if (list.size() <= despawnIdx)
|
||||
{
|
||||
despawnIdx = 0;
|
||||
}
|
||||
Bone bone = list.get(despawnIdx++);
|
||||
bone.addToScene(client.getScene());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.runelite.client.util;
|
||||
|
||||
import java.awt.Polygon;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.WorldType;
|
||||
@@ -16,6 +18,17 @@ public class MiscUtils
|
||||
private static final Polygon abovePoly = new Polygon(abovePointsX, abovePointsY, abovePointsX.length);
|
||||
private static final Polygon belowPoly = new Polygon(belowPointsX, belowPointsY, belowPointsX.length);
|
||||
|
||||
private static final ChronoUnit[] ORDERED_CHRONOS = new ChronoUnit[]
|
||||
{
|
||||
ChronoUnit.YEARS,
|
||||
ChronoUnit.MONTHS,
|
||||
ChronoUnit.WEEKS,
|
||||
ChronoUnit.DAYS,
|
||||
ChronoUnit.HOURS,
|
||||
ChronoUnit.MINUTES,
|
||||
ChronoUnit.SECONDS
|
||||
};
|
||||
|
||||
//test replacement so private for now
|
||||
private static boolean inWildy(WorldPoint point)
|
||||
{
|
||||
@@ -86,4 +99,75 @@ public class MiscUtils
|
||||
|
||||
//return getWildernessLevelFrom(client, localPlayer.getWorldLocation()) > 0;
|
||||
}
|
||||
|
||||
public static String formatTimeAgo(Duration dur)
|
||||
{
|
||||
long dA = 0, dB = 0, rm;
|
||||
ChronoUnit cA = null, cB = null;
|
||||
for (int i = 0; i < ORDERED_CHRONOS.length; i++)
|
||||
{
|
||||
cA = ORDERED_CHRONOS[i];
|
||||
dA = dur.getSeconds() / cA.getDuration().getSeconds();
|
||||
rm = dur.getSeconds() % cA.getDuration().getSeconds();
|
||||
if (dA <= 0)
|
||||
{
|
||||
cA = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 < ORDERED_CHRONOS.length)
|
||||
{
|
||||
cB = ORDERED_CHRONOS[i + 1];
|
||||
dB = rm / cB.getDuration().getSeconds();
|
||||
|
||||
if (dB <= 0)
|
||||
{
|
||||
cB = null;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (cA == null)
|
||||
{
|
||||
return "just now.";
|
||||
}
|
||||
|
||||
String str = formatUnit(cA, dA);
|
||||
|
||||
if (cB != null)
|
||||
{
|
||||
str += " and " + formatUnit(cB, dB);
|
||||
}
|
||||
|
||||
return str + " ago.";
|
||||
}
|
||||
|
||||
private static String formatUnit(ChronoUnit chrono, long val)
|
||||
{
|
||||
boolean multiple = val != 1;
|
||||
String str;
|
||||
if (multiple)
|
||||
{
|
||||
str = val + " ";
|
||||
}
|
||||
else
|
||||
{
|
||||
str = "a" + (chrono == ChronoUnit.HOURS ? "n " : " ");
|
||||
}
|
||||
str += chrono.name().toLowerCase();
|
||||
if (!multiple)
|
||||
{
|
||||
if (str.charAt(str.length() - 1) == 's')
|
||||
{
|
||||
str = str.substring(0, str.length() - 1);
|
||||
}
|
||||
}
|
||||
else if (str.charAt(str.length() - 1) != 's')
|
||||
{
|
||||
str += "s";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user