[1.16.4] Colored lenses behave without regard to how much starlight is going through them
yavincl opened this issue · 17 comments
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.
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.
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.
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).
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.
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?
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
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.
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)
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 uselensLossContributionPerc
but feel free to come up with a better one haha). - In
TransmissionChain#recBuildChain
after thelossPerc
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 andonTransmissionTick
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 lensstarlightLost = 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 savelossMultiplier
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.
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.
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.
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.
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.
-
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
-
That is indeed true that it gets re-used and leads to the problematic behavior you're describing. Didn't think of that >_>