Astral Sorcery

Astral Sorcery

63M Downloads

[1.16.4] Colored lenses behave without regard to how much starlight is going through them

yavincl opened this issue · 17 comments

commented

Splitting a Collector Crystal's output into many blocks will make it take much longer to transmute a block of iron ore into starmetal - this is expected.

However, doing the same and powering a Colored Lens will make the Colored Lens act always at the same speed regardless of how much starlight is actually supposed to be there. This seems to happen with all Colored Lenses.

commented

I mean, the Tome explicitly says that the rate/speed of any colored lens' effect increases with amount of starlight going through, so either this is a bug or a mistake in the Tome.

commented

The tome didn't say that in the past IIRC, but there also wasn't entries specific to each type of colored lens either.
Suppose we'll see how it goes.

commented

I can confirm this is the behaviour since 1.12.2 to the current version. I'm curious if it's intended or not.

In versions after 1.12.2, the coloured lens were reduced from 0.7 strength to 0.15 strength. The growth lens did recieve a double nerf to only have a 12.5% chance to proc it's effect as well. Works out to be 2.69% of the former growth lens' power. Feels much weaker but they were pretty powerful back then.

Perhaps having them scale with starlight would add a way to bring back some of that power in a more balanced way.

Okay so just checked again to confirm and a change 2 days ago changed the coloured lens from 0.15 to 0.25 strength so they are a bit stronger again :3. (Growth lens is thus 4.46% of the 1.12.2 lens' power).

commented

So conceptually this is really hard.
The issue is that starlight isn't "transfered". It's generated at source and through pre-calculation, it already knows how much starlight is needed on the target.
So for example a line from 1 crystal to 1 receiver going through 10 suboptimal lenses. In the end, all that's known & saved is what the multiplier is to the receiver. Nothing else inbetween.
So figuring out "how much is going through hop X" is not trivial with the current setup.

commented

Assume we have a Starlight amount generated by a source of 10.
We have otherwise lossless hops, the receiver receiving all 10 of this.

Now we setup 1 ignition lens at a hop. which has a throughput loss of 10%
Does the Lens now operate with a strength of 1 starlight/tick since that's what's lost by the lens? Does it operate with 10 since that's what's going "through the lens"? Or 9, accounting for its lost amount?

commented
commented

Deleted my first comment as it was a misunderstanding

commented

Here is some pseudocode that might help
Note I know bat shit about modding Minecraft code

# bash ish syntax
# lens function for starlight loss and colored lens


local HasColoredLens=true
local ConversionRate=0
local TickMul=0

local ColoredLens = $(GetColoredLens) # gets our color lens type, can be none

case $ColoredLens in
IGNITION) ConversionRate=15; TickMul=3
GROWTH) ConversionRate=25; TickMul=2.5 # yes I know bash doesn't do floats ¯\_(ツ)_/¯ 
SPECTRAL) ConversionRate=8; TickMul=0 # no ticking for you
# more lenses here..
# no match? No lens
*) HasColoredLens=false
esac

if [[ $HasColoredLens == true ]]; then

local StarlightTotal = $(GetStarlightAmount) #gets amount of starlight we have from the previous hop

# Reduce or don't reduce StarlightTotal depending on crystal purity cutting stats and stuff
# Insert normal lens loss code here, but don't return it

local StarlightConverted = $(($StarlightTotal * $ConversionRate / 100))

local TickRate=$(($StarlightConverted * $TickMul))

# now we apply the effect in world, sometimes
# sum a random number to our result
local FinalTickRate=$(($(shuf -n 1 1-20) + $TickRate))

# apply effect this tick if we roll high enough, I didn't put much # thought behind the numbers though

 if [[ $FinalTickRate -gt 10 ]] && [[ "$ColoredLens" != "SPECTRAL" ]]; then
 ApplyColoredLensEffect $ColoredLens
 fi

StarlightTotal = $(($StarlightTotal - $(($StarlightTotal * $ConversionRate / 100))))

# do other stuff

return $StarlightTotal # next hop will calc on this 
else
# do stuff as a normal lens 
# calculate losses from lens imperfections normally
# return starlight amount for next hop StarlightTotal
fi
commented

As for the colored lens not knowing how much starlight it has, it might have to calculate that somehow by re running the starlight network calcs with itself as the endpoint or something. I suppose that will be tricky or slow but you always have the much easier possibility of removing the relevant affirmations in the Tome. Which might be a bit boring but well you do you.

commented

If you read through how i explained the system works now, this part
StarlightTotal = $(($StarlightTotal - $(($StarlightTotal * $ConversionRate / 100))))
as "the next hop works based off of that" does not exist in this system for the sake of saving performance.
edit: more precisely this isn't being calculated per transmission tick

Additionally, StarlightNetwork code doesn't run on the tile entities, so this approach regarding any starlight would need to be accumulated per tick in the hopes that the tileentity will tick once every tick the network ticks over this transmission chain.

Regarding the 'lost/converted amount' of starlight, that's basically what it does right now. 10% throughput loss effectively means only 90% of starlight will end up at the receiver. So, these 2 numbers

Starlight amount exiting the lens to the next hop & Starlight amount being converted

add up to 1, for example 40% transmission loss, means a 60% multiplier to whatever calc happens afterwards. (All of this assuming perfect lenses without loss themselves due to their stats)

commented

In universe, maybe something that would make sense would be that the loss percent of the lens determines how much of the starlight is converted into the coloured effect (oh geez I just read yagoplx's suggestion and this is basically that). The rest of the starlight continuing on as normal starlight, which would also explain why the coloured effect only lasts one hop as it has already applied the effect. Still would be a bit confusing why lens work on multiple targets instead of just affecting the first (valid?) target but that might be acceptable.

I may be missing some side effect or problem (you'd know better), but implementation wise:

  • Perhaps an extra field could be added to CrystalTransmissionNode that keeps track of the percentage of the source starlight that was lost due to the transmission node (I'm just going to use lensLossContributionPerc but feel free to come up with a better one haha).
  • In TransmissionChain#recBuildChain after the lossPerc is calculated for that node, the percentage of the source starlight lost at the node could be calculated by: lensLossContributionPerc = lossMultiplier - lossMultiplier * lossPerc and then written to CrystalTransmissionNode.
  • Then when TransmissionWorldHandler#tick is run and onTransmissionTick is called for each CrystalTransmissionNode, the source starlight could additionally be passed in (instead of nothing atm), which then could be used to calculate the starlight actually lost at the lens starlightLost = lensLossContributionPerc * starlight(of source).

Alternatively if you'd rather have it scale based on the starlight flowing through (after losses are applied) rather than the lost starlight (or to have this information around for future uses):

  • Add a field to save the percentage of the source starlight that was transmitted through at this crystal transmission node (e.g. lensTransmittedPerc)
  • When the chain is being built like above, calculate lensTransmittedPerc = lossMultiplier * lossPerc (or just save lossMultiplier if you want it to be before losses are applied)
  • Then when the transmission node is ticked, it can calculate starlightTransmitted = lensTransmittedPerc * starlight(of source)

But yeah, having it like this means the percentage is calculated when the chain is built, and then the transmission node (if it needs to) can calculate the needed value when the network is ticked.

commented

Right so going back to the list of examples i gave above, that's the solution of

a strength of 1 starlight/tick since that's what's lost by the lens?

which would also be my pick.
And yes, what you described is how i'll go about implementing it as well.

commented

Oh geez, you did mention that. MB. I read the messages in the morning and came back in the evening to take a look at code and reply.

commented

No worries :P all fine

commented

Not sure if you're okay with testing being done on in-dev versions of the mod (let me know if so and I'll wait for an alpha next time), but from testing the in-dev version, it seems that the number of executions does not go down with multiple chained lens.

I think it is because the calculated percentage does not take into account lossMultiplier (ref), only taking into account crystal stats:

Ignore this bad code (as HellFirePvP commented later, just multiplying by the lossMultiplier is correct)

CrystalAttributes lensProperties = node.getTransmissionProperties();
float lossPerc = CrystalCalculations.getThroughputMultiplier(lensProperties);
float nextHopLossPerc = lossPerc * node.getTransmissionThroughputMultiplier();
float transmissionPerc = lossPerc * node.getTransmissionConsumptionMultiplier() * CrystalCalculations.getThroughputEffectMultiplier(lensProperties);

List<NodeConnection<IPrismTransmissionNode>> next = node.queryNext(handler);
float nextLoss = (lossMultiplier * nextHopLossPerc) / ((float) next.size());
prevPath.push(node.getLocationPos());

Maybe something like this would work:

CrystalAttributes lensProperties = node.getTransmissionProperties();
float lossPerc = CrystalCalculations.getThroughputMultiplier(lensProperties);
float nextHopLossPerc = lossPerc * node.getTransmissionThroughputMultiplier();
float nextLossTotal = lossMultiplier * nextHopLossPerc;
float deltaLossMultiplier = lossMultiplier - nextLossTotal;
float transmissionPerc = deltaLossMultiplier * CrystalCalculations.getThroughputEffectMultiplier(lensProperties);

List<NodeConnection<IPrismTransmissionNode>> next = node.queryNext(handler);
float nextLoss = nextLossTotal / ((float) next.size());
prevPath.push(node.getLocationPos());

Also when linking a prism lens to multiple blocks it seems that most of the effect activations occur on the first linked block.
This may be because the PartialEffectExecutor is re-used or at least isn't reset for each of the linked blocks. Some activations can pass through to the remaining blocks however, this happens when PartialEffectExecutor#canExecute evaluates to false when amount is between 0 and 1, it'll then evaluate canExecute on the next block as well.

Something kinda similar happens for entities in the beam as well, but this might be desired and would make sense there to some extent. Dead mobs can sometimes hold up the damage lens from passing through while they are in their death animation (this is best seen when amount of executions is slightly over 1). Also if there is between 0 and 1 amount of executions and canExecute fails on an entity, another attempt is then given to the next entity leading to more effects than it should have.

This could probably be fixed by creating a new instance for each link and having the PartialEffectExecutor calculate the number of executions on instantiation.

commented

Oooh yeah that works out just fine, and is actually more correct because that uses only the losses from the coloured lens rather than both the lens and the crystal attributes.

commented
  1. Yes, i guess i forgot to add the lossMultiplier into the calculations, though as far as i see it, just adding it into the multiplications for the transmissionPerc is enough

  2. That is indeed true that it gets re-used and leads to the problematic behavior you're describing. Didn't think of that >_>