CleverRaven / Cataclysm-DDA

Cataclysm - Dark Days Ahead. A turn-based survival game set in a post-apocalyptic world.
http://cataclysmdda.org
Other
10.63k stars 4.18k forks source link

Angled vehicles develop holes [$15] #5684

Closed Headjack closed 2 years ago

Headjack commented 10 years ago

While all this vehicle renaissance is going on, does it seem feasable to tackle that problem where cars at a 45-degree angle can be entered diagonally between otherwise solid parts? I think you know what I mean, how if two boards are adjacent vertically, at an angle you'll cross between them somehow. Ideally it would check the vehicle structure and make the space beyond functionally impassable/opaque from only that side. It'd probably be a tough fix, but it ought to happen at some point.

Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

i2amroy commented 10 years ago

Probably would require a bit of an update with pathing where squares could be flagged as "diagonally impassable", which would mean if two diagonally impassable squares are diagonal to each other you can't cross between them diagonally.

Headjack commented 10 years ago

So long as it only applies to vehicles, I'd imagine.

Zireael07 commented 10 years ago

Any chance of getting a fix?

SilearFlare commented 10 years ago

This issue was opened seven months ago and never resolved. What do you think?

KA101 commented 10 years ago

Spewing bile is more likely to slow dev work, since I don't think we code with 3-4 environmental resistance on our eyes. Don't be a boomer!

SilearFlare commented 10 years ago

I'm sorry, I didn't know stating plain facts was a hate crime. My deepest apologies.

Headjack commented 10 years ago

It'll probably be folded in with a bigger vehicle update once that sort of thing rolls around. I think dev is mostly on some other polish right now, and I think the z-level bounties which are a tough target. But let's just hold out for the moment; even when I put it up it was just to put down the issue for being logged.

kevingranade commented 6 years ago

Many years later and I have a proposal to fix it, but no time to implement it at the moment.

Any time a vehicle is skewed, add a "clone" of the shifted part that covers the gap.

Tiny ascii illustration: Facing north:

####
|00|
|00|
####

Rotated to NE:

####
 \00\
  \00\
   ####

Rotated to NE, plus gap covers:

#####
\\00\\
 \\00\\
  #####

To make it even simpler, widen the vehicle in the two locations that this scheme avoids, it doesn't prevent problems, but it makes it consistent:

######
 \\00\\
  \\00\\
   ######

Additionally, when a vehicle is "partially skewed", you can either just block the gaps:

####
\00\\
\\00\
 ####

Or widen the whole vehicle:

######
\\00\\
 \\00\\
 ######

This effectively makes the vehicle slightly thicker when skewed, which might cause some unexpected collisions, but that's a lot easier to cope with than the existing gaps.

The tricky part is taking collisions to the "cloned" parts and allocating the impact correctly to the original part.

Epictyphlosion commented 5 years ago

Many years later and I have a proposal to fix it, but no time to implement it at the moment.

Any time a vehicle is skewed, add a "clone" of the shifted part that covers the gap.

Tiny ascii illustration: Facing north:

####
|00|
|00|
####

Rotated to NE:

####
 \00\
  \00\
   ####

Rotated to NE, plus gap covers:

#####
\\00\\
 \\00\\
  #####

To make it even simpler, widen the vehicle in the two locations that this scheme avoids, it doesn't prevent problems, but it makes it consistent:

######
 \\00\\
  \\00\\
   ######

Additionally, when a vehicle is "partially skewed", you can either just block the gaps:

####
\00\\
\\00\
 ####

Or widen the whole vehicle:

######
\\00\\
 \\00\\
 ######

This effectively makes the vehicle slightly thicker when skewed, which might cause some unexpected collisions, but that's a lot easier to cope with than the existing gaps.

The tricky part is taking collisions to the "cloned" parts and allocating the impact correctly to the original part.

A better solution would be to make it so that the gap covers count as one part, even though they appear as two when the vehicle is at an angle, meaning... (X = Part being damaged)

######
 X\00\\
  \\00\\
   ######

Instead of just basically doubling the amount of parts when the vehicle is at an angle...

######
 XX00\\
  \\00\\
   ######

They are counted as one part. So it appears that two parts are being damaged, even though it's really one part being damaged, and it would show up like that once the vehicle straightens out.

I hope I didn't make this too complicated. But I also hope this issue doesn't become five years old, lol

Asmageddon commented 5 years ago

Alternative idea: An 8-direction bitfield on every tile, which remains zeroed for terrain, but for vehicles and their neighboring tiles is computed based on the traversability of the original, unrotated vehicle. If the appropriate bit is 0, any checks proceed as they usually would, but if it's 1, a more involved check can be performed to see what other tile the current algorithm(raycasting, movement, pathfinding) should be redirected to. It'd also work for non-boundary walls as well.

I'm probably ignorant about how the current code looks, but my intuition says algorithms could probably be changed to use this bitfield, possibly replacing bool->bitfield for visibility also, thus leaving the standard checks(==0 and ==255) intact, e.g. without injury to performance for the common cases.

Would probably be a pretty involved change, though it could conceivably be futureproofed against if someone wants to do portals some distant baleful day. One way or the other.

mlangsdorf commented 5 years ago

I honestly have no idea what that bitfield is supposed to do or how it's supposed to work. Can you explain with a concrete example?

For instance: There is a standard car at local position [ 3, 3 ]. It has a windshield at mount [ 1, 2 ], a door at mount [ 1, 1 ], and a door at mount [ 1, 0 ]. It's pivot point is actually mount [ 0, 0 ], it's turned 45 degrees, and the parts have local position [ 2, 5 ], [ 3, 4 ], [ 4, 3 ], starting at the windshield and then the two doors. An NPC is near to the car at [ 5, 5 ] and wants to get in to steal my katana, sitting on the seat at [ 3, 3 ].

Currently, the NPC movement code checks to see that there are no obstacles between [ 5, 5 ] and [ 3, 3 ]. There aren't, so the NPC walks to [ 4, 4 ] and then [ 3, 3 ] and can take it.

How are the bitfields changing this situation?

Asmageddon commented 5 years ago

@mlangsdorf Sorry, I guess I botched typing that. The idea is that instead of having a single boolean representing whether a tile is traversable, to have a field of 8 bits representing directional passability. For ordinary terrain, it would always be either 0 or 255, but for rotated vehicles... actually, it's a bit of a PITA, but I'll draw a demonstration of sorts.

Demonstration

With red/orange dots representing bits that are 1. If they're all 0 or 1, code can just treat them exactly as it currently does, alternatively it takes a slower path which checks the individual appropriate bit.

E.g. still check obstacles, but situationally(vehicles), do a directional check instead since a boolean is insufficient to represent the fact that only some tile boundaries are impassable.

Zireael07 commented 5 years ago

Just to clarify, red dot means it's impassable? So most parts are impassable from all directions, and possible holes (orange) are impassable on diagonals?

Asmageddon commented 5 years ago

@Zireael07 Correct. This could hypothetically be used to avoid holes within the vehicle, as well as making diagonal passages impassable, or directionally passable tiles(turnstile).

mlangsdorf commented 5 years ago

Thanks, I think that's a lot clearer.

So the seat tile at mount [ 0, 0 ] has its bitfield set so that diagnol movement is impossible. NPC pathfinding algorithm sees that's impossible to move from [ 4, 4 ] to [ 3, 3 ] and proceeds to bash the windshield.

Fair enough.

I'll defer to Kevin as how hard it would be to get this to interact with the light, vision, and pathfinding code.

kevingranade commented 5 years ago

The impact to the code would be VERY high, not only would everything that cares about opacity, damage application, navigation or movement have to be updated, but it changes the current "can I enter this square/can I see through this square" check to "can I enter square x from square y/can I see into square x from square y". I'm not at all sure how this would interact with vision. The way vision interacts with opaque tiles is that you can see the opaque tile, but nothing past it. In this case is it the tile adjacent to the vehicle that counts as opaque or the vehicle tile itself?

From the point of view of addressing the diagonal gap issue, I much prefer the other solution, it's completely localized to the vehicle system instead of requiring updates throughout the rest of the code.

Asmageddon commented 5 years ago

Yeah, it'd probably touch a lot of code. And you'd probably have to do two branches instead of one, one to cover ==0 and one for ==255, before finally falling back to the directional code. And in this case, the tile which cannot be entered from the current direction would be considered opaque. E.g. from inside the vehicle, the grass tile is opaque, from outside, it's the inside one that is.

Still, I wanted to pitch the idea mainly, since idk, creating faux parts strikes me as really inelegant. It does sound efficient and effective, but... well, there's a reason why I have difficulty managing coding nowadays.

Headjack commented 5 years ago

Asmageddon's solution sounds ideal to me, but the impact issue is important too. The bitfield can't be applied only to vehicle tiles?

mlangsdorf commented 5 years ago

It's not whether it can be applied only to vehicle tiles or not, it's the other areas of the code that need to know about the bitfield.

The nice thing about Kevin's hack is that it isolates all the changes to the vehicle code.

mgeens commented 5 years ago

I've been having a look at this issue, and I have the impression the duplicate part approach won't be entirely localized to the vehicle code. Suppose one of the walls is a closed door and it gets duplicated to cover a gap. If the player opens the door, we'd have the option of either removing the duplicate (which I think would look glitchy) or keeping it. If it's kept, what happens when the player (or something else) moves on top of the open door? Does the character exist on both tiles at once? I guess not, since that would look weird and probably have further undesirable consequences.

So suppose a player can be on top of one vehicle part, while not being on top of its duplicate or vice versa. What happens when you try to close the door, either through the tile you're on or the one you aren't on? The door closing code in src/gates.cpp would need to be aware that if you try to close a door on a given tile, there might be a creature on another duplicated tile that would prevent that door from closing. Would it be acceptable for the hole plugging logic to leak into other code like that?

You could argue that instead of duplicating doors, another neighboring unpassable wall tile could be duplicated, but that would be brittle since it wouldn't work if somebody decides to put multiple doors next to each other in the wall. I suspect that would also give glitchy looking results.

Asmageddon commented 5 years ago

You talk about "looking", but why shouldn't the phantom parts just be invisible view- and navigation- blockers?

mgeens commented 5 years ago

If the duplicates are invisible but behave the same as their parent when it comes to damage / collisions / interaction / etc, wouldn't that be confusing to the player?

And if the invisible parts don't behave like one of the real ones and only block viewing and navigation, then I suspect we're back in the scenario where movement and line of sight code need to be updated to take that into account.

mgeens commented 5 years ago

Suppose you have a little scene like this:

.\Z
@I\

@ being the player standing outside, Z a zombie inside a vehicle, \ a vehicle wall, . some empty terrain and I an invisible view and navigation blocking tile that looks like the terrain below it.

The zombie won't be able to see the player and vice versa. But the player also wouldn't see the bottom wall tile unless the line of sight code treats the invisible tile differently.

Asmageddon commented 5 years ago

I assume there's a final tilemap of values like is_collidable, is_opaque, etc.

It's a bit of a disgusting hack, but in theory it'd work perfectly and be reasonably efficient to group FOV-capable creatures/objects/vehicle parts by which vehicle they're in(if any), and toggling seam-boundary opacity flags.

E.g. player and NPC are inside vehicle, for their group the tiles just outside the vehicle are set to OPAQUE(or something like OPAQUE_DONT_RENDER, so it shows up as black), next is group outside of vehicles, for them seam tiles just inside the vehicles are set to same, and so on and so forth, with tile flags being restored between passes.

This data is going to be in cache anyway, won't require changes to FOV code, phantom parts, etc. It will require a check before casting FoV/LoS, but as long as you sort entities by which vehicle they're in, it should either yield to branch prediction or for it to be possible to do away with the check in those cases.

kevingranade commented 4 years ago

This issue has been mentioned on Cataclysm: Dark Days Ahead. There might be relevant details there:

https://discourse.cataclysmdda.org/t/avoiding-being-grabbed-out-of-vehicle/22455/3

Headjack commented 3 years ago

Bless this mess

Zourin2 commented 3 years ago

Alternately, deny diagonal movement from the ground to vehicle tiles (and vice versa) entirely and limit them to cardinal transit only. This forces access exclusively through door tiles. Keep it simple, right?

This makes some sense realistically since you need to stand orthogonally to a vehicle in order to enter it, whether it's a car seat or a bus. You may think you can enter a car seat from '1 tile' diagonal to the car seat, but all you're doing is moving to an orthogonal position and entering while imagining you're 'cutting the corner'. Vehicles entries are too narrow/filled with stuff like siding, seatbelts, and steps to 'cut the corner' as natively as walking diagonally in a field.

anothersimulacrum commented 3 years ago

Alternately, deny diagonal movement from the ground to vehicle tiles (and vice versa) entirely and limit them to cardinal transit only. This forces access exclusively through door tiles. Keep it simple, right?

Keep it simple - until you need to integrate it with lighting, and vision, and every other system in the game and it grows into a mass of special cases. Additional tiles is messy, but it contains the special casing to a single place.

actual-nh commented 3 years ago

I note the additional problem with diagonally-moving vehicles that they're apparently faster-moving (#49772).

Coseo commented 2 years ago

Having recently done an investigation of the impact of this issue, I think this could definitely be implemented using the "diagonal movement preventation" method, but it'd certainly have a larger impact as 3 years ago Kevin noted, requiring a lot more work and effort. The end result would likely be nicer for the user (no unexpected generation of vehicle tiles, more intuitive and realistic behavior of vehicles), but between the amount of effort involved and the fact that players have had a workaround via double-thick walls for a while now, I can understand why this still hasn't been resolved after several years, and also why the other approach is definitely more appealing.

The "passable / impassable" map function would likely need to be the target of the approach, splitting it into a function for "passage from one tile to another" and something akin to occupation, checking whether or not there's an empty space at a tile (for stuff like spawning, among other things). Said code is used in roughly 220 places throughout the game. The good news is that a lot of those uses are trivial to deal with, either being a check against a tile for purposes that don't care about direction and thus wouldn't have to use this logic, or call the same function (route to adjacent, etc) which could be solved all at once - and a lot of other cases are pretty simple to handle, such as the ballistics code, though you'd have to keep an eye out for edge cases (like the passable furniture checks happening in the avatar code).

There's a lot to address, between explosive propagation, damage application, vehicle collisions and damage against the diagonal itself, line of sight, lighting, but its worth noting that while most systems use this, they do it by calling the same function, so changing said function (or making them call the replacement "occupation" alternative), and then testing to see if it still works, would be a pretty rigorous way to implement it. It'd be somewhat tedious, but definitely doable.

To me, the biggest downside isn't having to address all of these, which I think could be probably thrown together on a functional level in a weekend or perhaps a week by a single person with a lot of free time and a high level experience, and more the fact that merging that with everything else that's changed since, while also being very vigorous and looking for bugs, would be a real pain. It'd be doable, and could even be done in a short amount of time if you had, say, multiple people splitting up the work on it and working full time, but even if that's within the resources of this project its a massive allocation of resources for something that already has an in-game workaround.

With that in mind, I can see why nobody has bothered to do it, and why the alternative approach is enticing (though it has its own issues) and has had a few attempts made at it so far.

LeahLuong commented 2 years ago

Shame that the bounty doesn't grow w/ inflation. Those $15 from 2014 are prob worth $1000+ in today's economy!

Asmageddon commented 2 years ago

@Coseo Check my past comment, which I think could confer a notable performance boost if you decide to go this route: https://github.com/CleverRaven/Cataclysm-DDA/issues/5684#issuecomment-454299493

Bracket-H commented 2 years ago

Just simulate vehicles unrotated, have all ingame collisions against them rotated against an unrotated state of the vehicle.

meaning Xo..... _X..... __X..... _X turns into o XXX __X for internal checks. This can probably be solved even without trigonometry and the like, but even with the impact should be negligible.