1717
1818package com.lambda.interaction.request.placing
1919
20+ import com.lambda.config.groups.BuildConfig
2021import com.lambda.context.SafeContext
2122import com.lambda.event.EventFlow.post
2223import com.lambda.event.events.MovementEvent
2324import com.lambda.event.events.TickEvent
2425import com.lambda.event.events.UpdateManagerEvent
26+ import com.lambda.event.events.WorldEvent
2527import com.lambda.event.listener.SafeListener.Companion.listen
2628import com.lambda.interaction.construction.context.PlaceContext
29+ import com.lambda.interaction.construction.verify.TargetState
2730import com.lambda.interaction.request.RequestHandler
2831import com.lambda.interaction.request.hotbar.HotbarRequest
2932import com.lambda.interaction.request.rotation.RotationManager.onRotate
@@ -41,20 +44,18 @@ import net.minecraft.item.ItemUsageContext
4144import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket
4245import net.minecraft.registry.RegistryKeys
4346import net.minecraft.sound.SoundCategory
44- import net.minecraft.stat.Stats
4547import net.minecraft.util.ActionResult
4648import net.minecraft.util.Hand
4749import net.minecraft.util.hit.BlockHitResult
50+ import net.minecraft.util.math.BlockPos
4851import net.minecraft.world.GameMode
49- import net.minecraft.world.event.GameEvent
5052import org.apache.commons.lang3.mutable.MutableObject
5153
5254object PlaceManager : RequestHandler<PlaceRequest>() {
53- private val pendingInteractions = LimitedDecayQueue <PlaceContext >(
55+ private val pendingInteractions = LimitedDecayQueue <PlaceInfo >(
5456 TaskFlowModule .build.maxPendingInteractions, TaskFlowModule .build.interactionTimeout * 50L
55- ) { info(" ${it::class .simpleName} at ${it.expectedPos.toShortString()} timed out" ) }
57+ ) { info(" ${it::class .simpleName} at ${it.context. expectedPos.toShortString()} timed out" ) }
5658
57- // ToDo: Add server response check for pending interactions
5859 init {
5960 listen<TickEvent .Pre >(Int .MIN_VALUE ) {
6061 preEvent()
@@ -65,9 +66,8 @@ object PlaceManager : RequestHandler<PlaceRequest>() {
6566 }
6667
6768 currentRequest?.let request@ { request ->
68- if (pendingInteractions.size >= request.buildConfig.placeSettings.maxPendingPlacements) {
69+ if (pendingInteractions.size >= request.buildConfig.placeSettings.maxPendingPlacements)
6970 return @request
70- }
7171
7272 if (request.placeContext.sneak && ! player.isSneaking
7373 || (request.buildConfig.placeSettings.rotateForPlace && ! request.placeContext.rotation.done)
@@ -79,15 +79,28 @@ object PlaceManager : RequestHandler<PlaceRequest>() {
7979 pendingInteractions.setMaxSize(request.buildConfig.maxPendingInteractions)
8080 pendingInteractions.setDecayTime(request.buildConfig.interactionTimeout * 50L )
8181 placeBlock(request, Hand .MAIN_HAND )
82- }
82+ }
8383
8484 postEvent()
8585 }
8686
87+ listen<WorldEvent .BlockUpdate .Server > { event ->
88+ pendingInteractions
89+ .firstOrNull { it.context.expectedPos == event.pos }
90+ ?.let { pending ->
91+ pendingInteractions.remove(pending)
92+ if (! matchesTargetState(event.pos, pending.context.targetState, event.newState)) return @listen
93+ if (pending.buildConfig.placeSettings.placeConfirmationMode == PlaceConfig .PlaceConfirmationMode .AwaitThenPlace )
94+ placeSound(pending.item, pending.context.expectedState, pending.context.expectedPos)
95+ pending.onPlace()
96+ }
97+ }
98+
8799 onRotate {
88100 currentRequest?.let { request ->
89- if (request.buildConfig.placeSettings.rotateForPlace)
101+ if (request.buildConfig.placeSettings.rotateForPlace) {
90102 request.rotationConfig.request(request.placeContext.rotation)
103+ }
91104 }
92105 }
93106
@@ -96,37 +109,56 @@ object PlaceManager : RequestHandler<PlaceRequest>() {
96109 }
97110 }
98111
112+ private fun SafeContext.matchesTargetState (pos : BlockPos , targetState : TargetState , newState : BlockState ) =
113+ if (targetState.matches(newState, pos, world)) true
114+ else {
115+ this @PlaceManager.warn(" Place at ${pos.toShortString()} was rejected with $newState instead of $targetState " )
116+ false
117+ }
118+
99119 private fun SafeContext.placeBlock (request : PlaceRequest , hand : Hand ) {
100120 val stackInHand = player.getStackInHand(hand)
121+ val item = stackInHand.item as ? BlockItem ? : return
101122 val stackCountPre = stackInHand.count
102123 val actionResult = interactBlock(request.buildConfig.placeSettings, hand, request.placeContext.result)
103124
104125 if (actionResult.isAccepted) {
105- if (request.buildConfig.placeSettings.placeConfirmation != PlaceConfig .PlaceConfirmation .None )
106- pendingInteractions.add(request.placeContext)
126+ if (request.buildConfig.placeSettings.placeConfirmationMode == PlaceConfig .PlaceConfirmationMode .None )
127+ request.onPlace()
128+ else {
129+ pendingInteractions.add(
130+ PlaceInfo (
131+ request.placeContext,
132+ item,
133+ request.buildConfig,
134+ request.onPlace
135+ )
136+ )
137+ }
107138
108- if (actionResult.shouldSwingHand() && request.buildConfig.placeSettings.swing)
139+ if (actionResult.shouldSwingHand() && request.buildConfig.placeSettings.swing) {
109140 swingHand(request.buildConfig.placeSettings.swingType)
141+ }
110142
111- if (! stackInHand.isEmpty && (stackInHand.count != stackCountPre || interaction.hasCreativeInventory()))
143+ if (! stackInHand.isEmpty && (stackInHand.count != stackCountPre || interaction.hasCreativeInventory())) {
112144 mc.gameRenderer.firstPersonRenderer.resetEquipProgress(hand)
145+ }
113146 } else {
114147 warn(" Placement interaction failed with $actionResult " )
115148 }
116- request.onPlace()
117149 }
118150
119151 private fun SafeContext.interactBlock (placeConfig : PlaceConfig , hand : Hand , hitResult : BlockHitResult ): ActionResult {
120152 interaction.syncSelectedSlot()
121153 if (! world.worldBorder.contains(hitResult.blockPos)) {
122154 return ActionResult .FAIL
123155 } else {
124- val mutableObject = MutableObject <ActionResult >()
156+ val mutableActionResult = MutableObject <ActionResult >()
125157 interaction.sendSequencedPacket(world) { sequence: Int ->
126- mutableObject .value = interactBlockInternal(placeConfig, hand, hitResult)
158+ mutableActionResult .value = interactBlockInternal(placeConfig, hand, hitResult)
127159 PlayerInteractBlockC2SPacket (hand, hitResult, sequence)
128160 }
129- return mutableObject .value
161+ return mutableActionResult .value
130162 }
131163 }
132164
@@ -169,19 +201,12 @@ object PlaceManager : RequestHandler<PlaceRequest>() {
169201 ) {
170202 return ActionResult .PASS
171203 }
172- val item: BlockItem = (itemStack.item as ? BlockItem ) ? : return ActionResult .PASS
173- val actionResult = useOnBlock(placeConfig, item, context)
174- if (actionResult.shouldIncrementStat()) player.incrementStat(Stats .USED .getOrCreateStat(item))
204+ val item = (itemStack.item as ? BlockItem ) ? : return ActionResult .PASS
205+ val actionResult = place(placeConfig, item, ItemPlacementContext (context))
175206
176207 return actionResult
177208 }
178209
179- private fun SafeContext.useOnBlock (
180- placeConfig : PlaceConfig ,
181- item : BlockItem ,
182- context : ItemUsageContext
183- ) = place(placeConfig, item, ItemPlacementContext (context))
184-
185210 private fun SafeContext.place (
186211 placeConfig : PlaceConfig ,
187212 item : BlockItem ,
@@ -190,15 +215,15 @@ object PlaceManager : RequestHandler<PlaceRequest>() {
190215 if (! item.block.isEnabled(world.enabledFeatures)) return ActionResult .FAIL
191216 if (! context.canPlace()) return ActionResult .FAIL
192217
193- val itemPlacementContext: ItemPlacementContext = item.getPlacementContext(context) ? : return ActionResult .FAIL
194- val blockState: BlockState = item.getPlacementState(itemPlacementContext) ? : return ActionResult .FAIL
218+ val itemPlacementContext = item.getPlacementContext(context) ? : return ActionResult .FAIL
219+ val blockState = item.getPlacementState(itemPlacementContext) ? : return ActionResult .FAIL
195220
196- if (placeConfig.placeConfirmation == PlaceConfig .PlaceConfirmation .AwaitThenPlace )
221+ if (placeConfig.placeConfirmationMode == PlaceConfig .PlaceConfirmationMode .AwaitThenPlace )
197222 return ActionResult .success(world.isClient)
198223
199- // ToDo: Add restriction checks, like world height, to avoid needlessly awaiting a server response which will never return
200- // if the user has the AwaitThenPlace confirmation setting enabled, as none of the state- setting methods which check these rules
201- // are called
224+ // TODO: Implement restriction checks (e.g., world height) to prevent unnecessary server requests when the
225+ // " AwaitThenPlace" confirmation setting is enabled, as the block state setting methods that validate these
226+ // rules are not called.
202227 if (! item.place(itemPlacementContext, blockState)) return ActionResult .FAIL
203228
204229 val blockPos = itemPlacementContext.blockPos
@@ -210,23 +235,31 @@ object PlaceManager : RequestHandler<PlaceRequest>() {
210235 hitState.block.onPlaced(world, blockPos, hitState, player, itemStack)
211236 }
212237
213- if (placeConfig.sounds) {
214- val blockSoundGroup = hitState.soundGroup
215- world.playSound(
216- player,
217- blockPos,
218- item.getPlaceSound(hitState),
219- SoundCategory .BLOCKS ,
220- (blockSoundGroup.getVolume() + 1.0f ) / 2.0f ,
221- blockSoundGroup.getPitch() * 0.8f
222- )
223- }
224- world.emitGameEvent(GameEvent .BLOCK_PLACE , blockPos, GameEvent .Emitter .of(player, hitState))
238+ if (placeConfig.sounds) placeSound(item, hitState, blockPos)
225239 if (! player.abilities.creativeMode) itemStack.decrement(1 )
226240
227241 return ActionResult .success(world.isClient)
228242 }
229243
244+ private fun SafeContext.placeSound (item : BlockItem , state : BlockState , pos : BlockPos ) {
245+ val blockSoundGroup = state.soundGroup
246+ world.playSound(
247+ player,
248+ pos,
249+ item.getPlaceSound(state),
250+ SoundCategory .BLOCKS ,
251+ (blockSoundGroup.getVolume() + 1.0f ) / 2.0f ,
252+ blockSoundGroup.getPitch() * 0.8f
253+ )
254+ }
255+
256+ data class PlaceInfo (
257+ val context : PlaceContext ,
258+ val item : BlockItem ,
259+ val buildConfig : BuildConfig ,
260+ val onPlace : () -> Unit
261+ )
262+
230263 override fun preEvent () = UpdateManagerEvent .Place .Pre ().post()
231264 override fun postEvent () = UpdateManagerEvent .Place .Post ().post()
232265}
0 commit comments