Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import net.minecraft.world.phys.Vec3
import org.jblas.DoubleMatrix
import ram.talia.moreiotas.api.casting.iota.*
import ram.talia.moreiotas.api.util.Anyone
import ram.talia.moreiotas.api.util.ChatEntry

operator fun Double.times(vec: Vec3): Vec3 = vec.scale(this)
operator fun Vec3.times(d: Double): Vec3 = this.scale(d)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ object MoreIotasConfig {
val setBlockStringCost: Long
val nameCost: Long

val maxChatLog: Int

companion object {
const val DEF_MIN_COST = 0.0001
const val DEF_MAX_COST = 10_000.0
Expand All @@ -29,6 +31,10 @@ object MoreIotasConfig {

const val DEFAULT_SET_BLOCK_STRING_COST = 0.01
const val DEFAULT_NAME_COST = 0.01

const val DEFAULT_MAX_CHAT_LOG = 341 // 3 within 1024
const val MIN_MAX_CHAT_LOG: Int = 0
const val MAX_MAX_CHAT_LOG: Int = 341 // TODO: take into account the stack size config...
Comment on lines +35 to +37
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a really weirdly specific default. What about something like 256 instead? Or 64 might be a more reasonably balanced value.

}
}

Expand Down Expand Up @@ -60,6 +66,8 @@ object MoreIotasConfig {
get() = throw IllegalStateException("Attempted to access property of Dummy Config Object")
override val nameCost: Long
get() = throw IllegalStateException("Attempted to access property of Dummy Config Object")
override val maxChatLog: Int
get() = throw IllegalStateException("Attempted to access property of Dummy Config Object")
}

@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package ram.talia.moreiotas.api.util;

public record ChatEntry(String message, long worldTime, String username) { }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussing this on Discord, I think this pattern should be changed to pop nothing from the stack and push a list of lists, where each inner list is [message, username]. The list should contain all public chat messages that were sent in the most recent tick where at least one user sent a chat message.

For example:

  • If no chat messages have been sent since the server started: []
  • If the most recent chat message was sent by object_Object, and only one message was sent that tick: [["message contents", "object_Object"]]
  • If both object_Object and chloetax sent a message 1 second ago: [["message 1", "object_Object"], ["message 2", "chloetax"]]

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ram.talia.moreiotas.common.casting.actions.strings

import at.petrak.hexcasting.api.casting.castables.ConstMediaAction
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.getPositiveInt
import at.petrak.hexcasting.api.casting.iota.DoubleIota
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.ListIota
import ram.talia.moreiotas.xplat.IXplatAbstractions
import ram.talia.moreiotas.api.casting.iota.StringIota as ApiStringIota

class OpChatLog() : ConstMediaAction {
override val argc = 1

override fun execute(args: List<Iota>, env: CastingEnvironment): List<Iota> {
val count = args.getPositiveInt(0, argc)
val logs = IXplatAbstractions.INSTANCE.chatLog(count)
val messages = mutableListOf<Iota>()
val timestamps = mutableListOf<Iota>()
val names = mutableListOf<Iota>()

for (entry in logs) {
messages.add(ApiStringIota.make(entry.message))
timestamps.add(DoubleIota(entry.worldTime.toDouble()))
names.add(ApiStringIota.make(entry.username))
}

return listOf(ListIota(messages), ListIota(timestamps), ListIota(names))
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should return elapsed time instead of an absolute timestamp, and the allChat variant should return sender username, time instead of count, time.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ram.talia.moreiotas.common.casting.actions.strings

import at.petrak.hexcasting.api.casting.castables.ConstMediaAction
import at.petrak.hexcasting.api.casting.asActionResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.iota.DoubleIota
import at.petrak.hexcasting.api.casting.iota.Iota
import net.minecraft.world.entity.player.Player
import ram.talia.moreiotas.api.asActionResult
import ram.talia.moreiotas.xplat.IXplatAbstractions

class OpChatTimestamp(private val allChat: Boolean) : ConstMediaAction {
override val argc = 0

override fun execute(args: List<Iota>, env: CastingEnvironment): List<Iota> {
if (!allChat)
return IXplatAbstractions.INSTANCE.lastMessageTimestamp(env.castingEntity as? Player).asActionResult
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this should either be on the same line as the if, or it should be wrapped in {} for clarity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this behave in a playerless casting environment (eg. an unbound cleric impetus)? Does it just give the global value? That seems confusing.

return listOf<Iota>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: is the type required here?

DoubleIota(IXplatAbstractions.INSTANCE.lastMessageCount().toDouble()),
DoubleIota(IXplatAbstractions.INSTANCE.lastMessageTimestamp(null).toDouble())
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ object MoreIotasActions {
@JvmField
val STRING_CHAT_ALL = make("string/chat/all", fromAngles("wded", EAST), OpChatString(true))
@JvmField
val STRING_CHAT_TIMESTAMP_CASTER = make("string/chat/timestamp/caster", fromAngles("waqawddw", EAST), OpChatTimestamp(false))
@JvmField
val STRING_CHAT_TIMESTAMP_ALL = make("string/chat/timestamp/all", fromAngles("wdedwaaw", EAST), OpChatTimestamp(true))
@JvmField
val STRING_CHAT_LOG = make("string/chat/log/all", fromAngles("wawqwawww", SOUTH_WEST), OpChatLog())
@JvmField
val STRING_CHAT_PREFIX_GET = make("string/chat/prefix/get", fromAngles("ewded", NORTH_EAST), OpGetChatPrefix)
@JvmField
val STRING_CHAT_PREFIX_SET = make("string/chat/prefix/set", fromAngles("qwaqa", SOUTH_EAST), OpSetChatPrefix)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import ram.talia.moreiotas.api.util.ChatEntry;

import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -66,6 +68,12 @@ public interface IXplatAbstractions {

@Nullable String lastMessage(@Nullable Player player);

long lastMessageTimestamp(@Nullable Player player);

int lastMessageCount();

List<ChatEntry> chatLog(int count);

void setChatPrefix(Player player, @Nullable String prefix);

@Nullable String getChatPrefix(Player player);
Expand All @@ -92,5 +100,4 @@ private static IXplatAbstractions find() {
return provider.get();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,30 @@
"output": "str",
"text": "moreiotas.page.strings.string/chat/all"
},
{
"type": "hexcasting:pattern",
"op_id": "moreiotas:string/chat/timestamp/caster",
"anchor": "moreiotas:string/chat/timestamp/caster",
"input": "",
"output": "str",
"text": "moreiotas.page.strings.string/chat/timestamp/caster"
},
{
"type": "hexcasting:pattern",
"op_id": "moreiotas:string/chat/timestamp/all",
"anchor": "moreiotas:string/chat/timestamp/all",
"input": "",
"output": "str",
"text": "moreiotas.page.strings.string/chat/timestamp/all"
},
{
"type": "hexcasting:pattern",
"op_id": "moreiotas:string/chat/log/all",
"anchor": "moreiotas:string/chat/log/all",
"input": "",
"output": "str",
"text": "moreiotas.page.strings.string/chat/log/all"
},
Comment on lines +108 to +131
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input/output needs to be updated for all of these.

{
"type": "hexcasting:pattern",
"op_id": "moreiotas:string/chat/prefix/set",
Expand Down
6 changes: 6 additions & 0 deletions Common/src/main/resources/assets/moreiotas/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"hexcasting.action.moreiotas:string/block/set": "Write",
"hexcasting.action.moreiotas:string/chat/caster": "Whisper Reflection",
"hexcasting.action.moreiotas:string/chat/all": "Listener's Reflection",
"hexcasting.action.moreiotas:string/chat/timestamp/caster": "Sandglass' Reflection",
"hexcasting.action.moreiotas:string/chat/timestamp/all": "Clocktower's Reflection",
Comment on lines +26 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe these could just be called Whisper Reflection II and Listener's Reflection II? I think it would make it more clear that they're returning data about the same event.

"hexcasting.action.moreiotas:string/chat/log/all": "Stenographer's Reflection",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a reflection if it pops values from the stack.

"hexcasting.action.moreiotas:string/chat/prefix/get": "Sifter's Reflection",
"hexcasting.action.moreiotas:string/chat/prefix/set": "Sifter's Gambit",
"hexcasting.action.moreiotas:string/iota": "Scrivener's Purification",
Expand Down Expand Up @@ -90,6 +93,9 @@
"moreiotas.page.strings.string/block/set": "Removes a vector and a string from the stack. If that vector is pointing at a sign or lectern, it writes that string to that sign or lectern. Costs a hundredth of an $(l:items/amethyst)$(item)Amethyst Dust/$.",
"moreiotas.page.strings.string/chat/caster": "Adds the last message the caster sent to the stack as a string.",
"moreiotas.page.strings.string/chat/all": "Adds the last message anyone sent to the stack as a string.",
"moreiotas.page.strings.string/chat/timestamp/caster": "Returns the time in 20ths of a second since the world's beginning, to when the caster sent the last message. Though sometimes it just gives back a zero...",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make more sense for this to return how much time has elapsed since the message was sent, rather than an absolute timestamp. Also, what does "sometimes it just gives back a zero" mean?

"moreiotas.page.strings.string/chat/timestamp/all": "Similar to $(l:moreiotas:patterns/strings#moreiotas:string/chat/timestamp/caster)$(action)Sandglass' Reflection/$, but tells when anyone sent a message for all to hear. Oddly, returns a count under that too. But it's not like two people would speak at the same single instant, right?",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"moreiotas.page.strings.string/chat/timestamp/all": "Similar to $(l:moreiotas:patterns/strings#moreiotas:string/chat/timestamp/caster)$(action)Sandglass' Reflection/$, but tells when anyone sent a message for all to hear. Oddly, returns a count under that too. But it's not like two people would speak at the same single instant, right?",
"moreiotas.page.strings.string/chat/timestamp/all": "Similar to $(l:patterns/strings#moreiotas:string/chat/timestamp/caster)$(action)Sandglass' Reflection/$, but tells when anyone sent a message for all to hear. Oddly, returns a count under that too. But it's not like two people would speak at the same single instant, right?",

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this return a count?

"moreiotas.page.strings.string/chat/log/all": "All that's spoken loudly enough gets recorded by the Nature, up to a limit. This pattern returns what was said, when, and by whom. The names are not what I see, but what Nature knows people by, though...",
Comment on lines +96 to +98
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These descriptions shouldn't have nearly this much flavour text. Ideally, use the style of the other string patterns as reference - just describe in plain language what the pattern does.

"moreiotas.page.strings.string/chat/prefix/set": "Accepts a string; All future chat messages starting with that string won't be seen by others, and only messages prefixed with that string can be read by $(l:patterns/strings#moreiotas:string/chat/caster)$(action)Whisper Reflection/$.",
"moreiotas.page.strings.string/chat/prefix/get": "Returns the last string you passed to $(l:patterns/strings#moreiotas:string/chat/prefix/set)$(action)Sifter's Gambit/$.",
"moreiotas.page.strings.string/iota": "Converts the iota on top of the stack into a string.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,13 @@ private static class Server implements ConfigData, MoreIotasConfig.ServerConfigA
private double setBlockStringCost = DEFAULT_SET_BLOCK_STRING_COST;
private double nameCost = DEFAULT_NAME_COST;

private int maxChatLog = DEFAULT_MAX_CHAT_LOG;

@Override
public void validatePostLoad() throws ValidationException {
this.maxMatrixSize = bound(this.maxMatrixSize, MIN_MAX_MATRIX_SIZE, MAX_MAX_MATRIX_SIZE);
this.maxStringLength = bound(this.maxStringLength, MIN_MAX_STRING_LENGTH, MAX_MAX_STRING_LENGTH);
this.maxChatLog = bound(this.maxChatLog, MIN_MAX_CHAT_LOG, MAX_MAX_CHAT_LOG);

this.setBlockStringCost = bound(this.setBlockStringCost, DEF_MIN_COST, DEF_MAX_COST);
this.nameCost = bound(this.nameCost, DEF_MIN_COST, DEF_MAX_COST);
Expand Down Expand Up @@ -96,5 +99,10 @@ public long getSetBlockStringCost() {
public long getNameCost() {
return (long) (this.nameCost * MediaConstants.DUST_UNIT);
}

@Override
public int getMaxChatLog() {
return this.maxChatLog;
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some comments to this and the Forge equivalent to clarify the control flow and what happens in each case? (eg. sender doesn't have a prefix set, sender has a prefix set but the message doesn't start with it, sender has a prefix and the message starts with it)

Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,69 @@ import net.minecraft.network.chat.ChatType
import net.minecraft.network.chat.PlayerChatMessage
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
import ram.talia.moreiotas.api.mod.MoreIotasConfig.server
import ram.talia.moreiotas.api.util.ChatEntry
import ram.talia.moreiotas.fabric.cc.MoreIotasCardinalComponents
import java.util.*
import java.util.UUID
import kotlin.collections.ArrayDeque

object ChatEventHandler {
private val lastMessages: MutableMap<UUID, String?> = mutableMapOf()
private var lastMessage: String? = null
private val messageLog: ArrayDeque<ChatEntry> = ArrayDeque<ChatEntry>()
private var lastMessageTimestamp: Long = 0;
private var messagesHandled: Int = 0;

private val lastMessageTimestamps: MutableMap<UUID, Long?> = mutableMapOf()

fun receiveChat(message: PlayerChatMessage, player: ServerPlayer, params: ChatType.Bound): Boolean {
val text = message.signedBody.content + (message.unsignedContent?.string ?: "")

val prefix = MoreIotasCardinalComponents.CHAT_PREFIX_HOLDER.get(player).prefix

var timestamp = player.serverLevel().gameTime

if (prefix != null && text.startsWith(prefix)) {
lastMessages[player.uuid] = text.substring(prefix.length)
lastMessageTimestamps[player.uuid] = timestamp
return false
}

if (prefix == null) {
lastMessages[player.uuid] = text
lastMessage = text
return true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

lastMessageTimestamps[player.uuid] = timestamp
}

if (!text.startsWith(prefix))
// Just in case! People configure stuff in the weirdest ways
if (server.maxChatLog == 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if it's less than 0?

return true

lastMessages[player.uuid] = text.substring(prefix.length)
while (messageLog.isNotEmpty() && messageLog.size > server.maxChatLog) {
// Just in case somehow we end up putting two "at once" even though this is only place this is touched
messageLog.removeFirst()
}

messageLog.addLast(ChatEntry(text, timestamp, player.name.string))
Comment on lines +43 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If messageLog.size >= server.maxChatLog at the start, then this code results in messageLog containing server.maxChatLog + 1 entries. I think the loop condition should be >= instead of > to fix this.


return false
if (lastMessageTimestamp == timestamp) {
messagesHandled++
} else {
messagesHandled = 1
lastMessageTimestamp = timestamp
}
return true
}

@JvmStatic
fun lastMessage(player: Player?): String? = if (player != null) lastMessages[player.uuid] else lastMessage
fun lastMessage(player: Player?): String? = if (player != null) lastMessages.getOrDefault(player.uuid, null) else if (lastMessageTimestamp != 0L) messageLog.last().message else null

@JvmStatic
fun lastMessageTimestamp(player: Player?): Long = (if (player != null) lastMessageTimestamps.getOrDefault(player.uuid, 0) else lastMessageTimestamp) ?: 0

@JvmStatic
fun lastMessageCount(): Int = messagesHandled

@JvmStatic
fun chatLog(count: Int): List<ChatEntry> {
return messageLog.subList((messageLog.size - count).coerceAtLeast(0), messageLog.size).reverse()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;

import java.util.List;

import org.jetbrains.annotations.Nullable;
import ram.talia.moreiotas.api.util.ChatEntry;
import ram.talia.moreiotas.fabric.cc.MoreIotasCardinalComponents;
import ram.talia.moreiotas.fabric.eventhandlers.ChatEventHandler;
import ram.talia.moreiotas.xplat.IXplatAbstractions;
Expand Down Expand Up @@ -79,6 +83,20 @@ public boolean isBreakingAllowed (Level level, BlockPos pos, BlockState state, P
return ChatEventHandler.lastMessage(player);
}

@Override
public long lastMessageTimestamp(@Nullable Player player) {
return ChatEventHandler.lastMessageTimestamp(player);
}

public int lastMessageCount() {
return ChatEventHandler.lastMessageCount();
}

@Override
public List<ChatEntry> chatLog(int count) {
return ChatEventHandler.chatLog(count);
}

@Override
public void setChatPrefix(Player player, String prefix) {
MoreIotasCardinalComponents.CHAT_PREFIX_HOLDER.get(player).setPrefix(prefix);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public Client(ForgeConfigSpec.Builder builder) {
public static class Server implements MoreIotasConfig.ServerConfigAccess {
private static ForgeConfigSpec.IntValue maxMatrixSize;
private static ForgeConfigSpec.IntValue maxStringLength;
private static ForgeConfigSpec.IntValue maxChatLog;

private static ForgeConfigSpec.DoubleValue setBlockStringCost;
private static ForgeConfigSpec.DoubleValue nameCost;
Expand All @@ -30,6 +31,9 @@ public Server(ForgeConfigSpec.Builder builder) {
maxStringLength = builder.comment("How long can strings be")
.defineInRange("maxStringLength", DEFAULT_MAX_STRING_LENGTH, MIN_MAX_STRING_LENGTH, MAX_MAX_STRING_LENGTH);

maxChatLog = builder.comment("How many chat messages can be logged and returned at most?")
.defineInRange("maxChatLog", DEFAULT_MAX_CHAT_LOG, MIN_MAX_CHAT_LOG, MAX_MAX_CHAT_LOG);

setBlockStringCost = builder.comment("How much dust should string/block/set cost?")
.defineInRange("setBlockStringCost", DEFAULT_SET_BLOCK_STRING_COST, DEF_MIN_COST, DEF_MAX_COST);
nameCost = builder.comment("How much dust should string/name cost?")
Expand All @@ -55,5 +59,10 @@ public long getSetBlockStringCost() {
public long getNameCost() {
return (long) (nameCost.get() * MediaConstants.DUST_UNIT);
}

@Override
public int getMaxChatLog() {
return maxChatLog.get();
}
}
}
Loading
Loading