-
Bug
-
Resolution: Unresolved
-
None
-
1.16.4, 20w46a
-
None
-
Plausible
-
Crash
-
Normal
-
Platform
All code references are using official mappings for 20w46a.
The skeleton trap code currently relies on the fact that
- the goal used for the skeleton traps is always the last in the LinkedHashSet used for GoalSelector.availableGoals and
- the (current) iterator implementation for LinkedHashSet only checks for concurrent modifications in next, but not in hasNext
GoalSelector.tick calls getRunningGoals().forEach(WrappedGoal::tick);. SkeletonTrapGoal.tick calls SkeletonHorse.setTrap, which directly modifies GoalSelector.availableGoals, the underlying set for the stream returned by getRunningGoals(). This is generally bad style, and in this case relies on undocumented behavior of LinkedHashSet iterators (the second point above): As the last element in the set is the one modifying the set, Iterator.next is not called after the modification, only Iterator.hasNext is. If any goals are added to the set after the trap goal, triggering the trap will crash the server (this happened on a modded server, that's how I found the issue). This never happens in vanilla, but I would still consider it a vanilla bug as it relies on undocumented behavior that could change in any Java update.
The cleanest fix for this would probably be to add a second boolean to SkeletonHorse indicating whether the horse should remain a trap, and to update the goals in SkeletonHorse.tick if this boolean does not match isTrap.