Repository: TTT-2/TTT2
Pull request #1792: Fix up issues related to +use, doors, buttons, and such
Author: login: wgetJane
Created: 2025-03-10T08:34:55Z | Merged: 2025-03-11T22:38:46Z

fixes #1775
fixes #1691
fixes #1633
fixes #1626

Linked issue #1775: +use on brush doors doesn't open them
1. download example map: https://steamcommunity.com/sharedfiles/filedetails/?id=3183036324

2. load map in ttt2

3. try to press E on any door (rotating or sliding doors)

4. it doesn't work

seems like a pretty severe issue

is there any reason why ttt2 needs to mess with the default +use behaviour on doors in the first place? seems like it just leads to a lot of breakage especially since it appears these changes barely get tested (if at all)

seems to be related to #1691 (from 3 months ago)
Linked issue comments: user: login: TimGoll
created_at: 2025-02-21T11:56:30Z
body: > is there any reason why ttt2 needs to mess with the default +use behaviour on doors in the first place?

I'm not happy with this either. There's the issue with clientside interactions such as marker vision. This then caused issues which brought more fixes.

I'm almost considering reverting all of this. However, reverting fixes the issue with doors, but brings back some other old issues (like delayed opening of UIs when your ping is bad, no way of getting useful data from buttons etc)

This issue is also related to the issue with unusable vehicles on some maps
author_association: MEMBER
user: login: wgetJane
created_at: 2025-02-21T12:03:53Z
body: > (like delayed opening of UIs when your ping is bad, no way of getting useful data from buttons etc)

those seem like much less severe issues than doors not working at all

it should at least be reverted for func_door and func_door_rotating for now

author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-02-21T12:23:33Z
body: i think you shouldn't modify the default +use behaviour at all unless you really want to deal with all the headache that comes with it

you can still have the "Press E to use" stuff, not sure why you'd need to change how +use works at all

here's how you can replicate FindUseEntity clientside: https://github.com/ValveSoftware/source-sdk-2013/blob/master/src/game/shared/baseplayer_shared.cpp#L1067

im genuinely just very confused now that apparently this issue was known but was just left as-is because the loss of functionality for a bunch of doors in a bunch of maps was deemed a worthy tradeoff for something that's purely cosmetic
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-02-21T12:35:39Z
body: > im genuinely just very confused now that apparently this issue was known but was just left as-is because the loss of functionality for a bunch of doors in a bunch of maps was deemed a worthy tradeoff for something that's purely cosmetic

At the time it wasn't know. It became clear after the release. But to our understanding it only affected a single map and vehicles on two maps. So we assumed it was ok. That it applies to ALL doors of that type wasn't clear to me. I tested it on so many maps, but apparently the wrong maps.

When I finished my vgui rewrite, I will redo the use handling. Because I'm unhappy as well. I _think_ it might also be the reason the doorbuster stopped working.

Sidenote: I get that it is frustrating, but currently I'm happy if I have 4 or so hours per week to work on TTT2. I *wish* I'd could spend a day or two a week on TTT2. As much as I hated covid, I miss the days of non-stop coding for this project.
author_association: MEMBER
user: login: wgetJane
created_at: 2025-02-21T13:12:16Z
body: actually i just realised, you dont need to replicate FindUseEntity clientside, because the targetid thing only shows up when you have the useable entity directly on your crosshair, and the game prioritises using entities on your crosshair

i'm really really confused now why this was done in the first place
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-02-21T13:24:57Z
body: im gonna gonna see if i can make a quick fix for it and hopefully there'll be a hotfix soon

edit: i spoke too soon, i did not know the extent of this
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-02-21T13:30:36Z
body: another thing: you can open doors by pressing E on stuff parented to doors (like glass), but ttt2 breaks this

(just check GetParent() too, or GetMoveParent() i dont remember exactly, i check it for my door-kicking code)
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-02-21T13:50:08Z
body: ok reading more of the custom door code, i do not understand at all why the +use functionality was even tampered with

all the new fancy ui stuff is completely possible with networked vars with the default +use behaviour
(and preferably nw2vars rather than nw1vars to respect pvs and use less bandwidth)

> delayed opening of UIs when your ping is bad

what ui are delayed? you can just override stuff that have a "clientside use" (like t buttons like base ttt does) and let normal +use fall through

also another thing i found: ttt2 breaks the "don't let crowbar open doors" setting
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-02-21T13:52:04Z
body: im really just baffled that so much work went into trying to replicate +use for seemingly no reason, idk what to even think, im not even trying to be rude im just so confused
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-02-21T13:54:58Z
body: and like did nobody try to press E on a sliding door throughout the whole process of coding it

i returned to playing ttt2 since like a week ago and i'm immediately finding all these issues


author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-02-21T14:13:18Z
body: @TimGoll can you give like a brief summary of the use + door stuff and what issues there were when trying to revert it, i wanna know if im going insane or not


author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-02-21T14:24:24Z
body: > im really just baffled that so much work went into trying to replicate +use for seemingly no reason, idk what to even think, im not even trying to be rude im just so confused

I don't want to shift the blame to someone else here, so don't get me wrong. Someone else in the team made a light rework of the use. That broke a lot of things. I thought I could fix this and dug a deep hole over multiple PRs until I addressed all issues (seemingly). At some point I should have made the call to revert all of it, but I assumed I could just fix it.
author_association: MEMBER
user: login: TimGoll
created_at: 2025-02-21T14:25:42Z
body: > and like did nobody try to press E on a sliding door throughout the whole process of coding it

> i returned to playing ttt2 since like a week ago and i'm immediately finding all these issues

Probably different map pool then my testing set. Now that I think of it, I don't have any map with sliding doors in it. And besides you,  nobody really bothered reporting anything. I don't play much myself and in the YT videos I've seen of people playing TTT2, none of these issues arose.
author_association: MEMBER
user: login: TimGoll
created_at: 2025-02-21T14:31:02Z
body: > can you give like a brief summary of the use + door stuff and what issues there were when trying to revert it, i wanna know if im going insane or not

Here's the original PR that caused the issues:
https://github.com/TTT-2/TTT2/pull/1447

Here is my list of PRs that attempted to fix it:
https://github.com/TTT-2/TTT2/pull/1580
https://github.com/TTT-2/TTT2/pull/1604
https://github.com/TTT-2/TTT2/pull/1605
https://github.com/TTT-2/TTT2/pull/1606
https://github.com/TTT-2/TTT2/pull/1608
https://github.com/TTT-2/TTT2/pull/1610
author_association: MEMBER
user: login: wgetJane
created_at: 2025-02-21T15:57:58Z
body: i could try to spend maybe next weekend trying to fix up the door stuff (zero guarantees, looks like hell)

the way i see it, ideally:

- restore default +use functionality other than for stuff that needs to override it (c4, player corpses, t buttons, etc)

- hook into when doors/buttons open/close, for networking their state for the ui

- have some networked flags for the door type (usable door, touchable door, destructible door, etc)

- make the targetid work when you point at an entity parented to a door/button (for glass doors and such)

- maybe fix the crowbar stuff
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-02-21T16:16:29Z
body: >  (c4, player corpses, t buttons, etc)

the new marker vision (radio for example) uses it as well, just fyi

> have some networked flags for the door type

the door stuff is completely independent of this. You can revert everything and the doors will still have working targetID. The issue are buttons because some flags had to be changed so clientside traces could hit them.#

> make the targetid work when you point at an entity parented to a door/button (for glass doors and such)

that would be awesome!
author_association: MEMBER
user: login: wgetJane
created_at: 2025-02-21T16:20:13Z
body: do you remember the maps you used to test with? maybe just the ones with the buttons/levers/whatever
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-02-21T16:23:12Z
body: - elevators and stairs
- community pool
- some generic TTT maps with normal prop doors
- a bunch of MC maps like jerome, island, ...
- the maps mentioned in the issues/PRs, I hope they are mentioned at least

I want to reiterate that the issue is with buttons, not doors. Doors have issues (like the parenting), but the changes were introduced many years ago and work perfectly fine. The issue is with the use change in the aforementioned PR.
author_association: MEMBER
user: login: Crashington
created_at: 2025-02-26T22:41:13Z
body: i just found a door on one of my maps that didnt work in ttt2,  likely because of all of this. But i managed to very easily fix it. I noticed in Hammer that apparently I had accidentally placed the origin of the func_door pretty far away from the actual door brush. moving the origin back into the brush fixed the door for me and made it fully usable again. I dont know if this is THE thing that causes this or just one out of many, but maybe it can help investigate this
author_association: NONE
user: login: TimGoll
created_at: 2025-02-26T22:59:53Z
body: I noticed a similar thing with the waterworld traitor room exit button. I think - while our implementation is not perfect - many of these issues arise because of bugs in maps. Like on the simpsons map where doors are buttons ...
author_association: MEMBER
user: login: MrXonte
created_at: 2025-02-27T09:12:19Z
body: > i just found a door on one of my maps that didnt work in ttt2, likely because of all of this. But i managed to very easily fix it. I noticed in Hammer that apparently I had accidentally placed the origin of the func_door pretty far away from the actual door brush. moving the origin back into the brush fixed the door for me and made it fully usable again. I dont know if this is THE thing that causes this or just one out of many, but maybe it can help investigate this

That sounds very likely to be the issue. I am mapping for ttt/2, and I only use brush doors, but I only experienced this issue once, which was fixed after just making the door from scratch again. Will have to test to be sure, but it would make sense if it references to the origin.

I also had an "issue" i turned into a feature where a oneway door can still be used both ways with the crowbar, but not with use. Its a door on the one side, with a func_brush parented to the door on the other to block use. You cant +use open the door through the brush, but you can crowbar it open somehow.
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-02-27T10:00:12Z
body: the way i see it, doors/buttons/etc behaving differently on use in ttt2 vs ttt is the main issue
author_association: CONTRIBUTOR
user: login: Crashington
created_at: 2025-02-27T10:23:55Z
body: > > i just found a door on one of my maps that didnt work in ttt2, likely because of all of this. But i managed to very easily fix it. I noticed in Hammer that apparently I had accidentally placed the origin of the func_door pretty far away from the actual door brush. moving the origin back into the brush fixed the door for me and made it fully usable again. I dont know if this is THE thing that causes this or just one out of many, but maybe it can help investigate this
> 
> That sounds very likely to be the issue. I am mapping for ttt/2, and I only use brush doors, but I only experienced this issue once, which was fixed after just making the door from scratch again. Will have to test to be sure, but it would make sense if it references to the origin.
> 
> I also had an "issue" i turned into a feature where a oneway door can still be used both ways with the crowbar, but not with use. Its a door on the one side, with a func_brush parented to the door on the other to block use. You cant +use open the door through the brush, but you can crowbar it open somehow.

this is offtopic to the actual thread but check if you have a ttt_map_settings entity on the map and have "crowbars open doors" enabled. idk why this setting exists or is default on but its probably the issue
author_association: NONE
user: login: TimGoll
created_at: 2025-02-27T11:36:29Z
body: I think you can also override this setting in TTT2, but it would probably be best to fix it in the map
author_association: MEMBER
user: login: wgetJane
created_at: 2025-02-27T11:47:10Z
body: afaik ttt2 doesnt respect your settings in ttt_map_settings, ttt2 allows me to open stuff with the crowbar on maps that i know have that disabled
author_association: CONTRIBUTOR
user: login: Crashington
created_at: 2025-02-27T12:00:23Z
body: i definitly know that map settings have an impact in ttt. i assume any server level setting just overrides it.
author_association: NONE
user: login: MrXonte
created_at: 2025-02-27T12:01:59Z
body: > afaik ttt2 doesnt respect your settings in ttt_map_settings, ttt2 allows me to open stuff with the crowbar on maps that i know have that disabled

The setting only sets if the crowbar unlocks openables, not if it opens them. The crowbar will always try to open if the server convar is set, but the setting determines if it unlocks as well.
![Image](https://github.com/user-attachments/assets/535a7ebc-3b25-4233-91fe-6e2409fce937)
So the door will always open if in ttt_map_settings its set to unlock OR if it has Use Opens (flag 256) set.
author_association: CONTRIBUTOR
user: login: MrXonte
created_at: 2025-02-27T12:04:12Z
body: also i am noticing how bad the naming scheme is since ttt_crowbar_unlocks convar manages if the crowbar tries to open doors, while the ttt_map_settings manage if if also tries to unlock them

will open a separate issue about this #1779 
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-03-07T09:01:27Z
body: @TimGoll from my testing, certain buttons like momentary_rot_button just don't have their classnames sent to the client

can you tell me which maps you used for testing the button targetids?
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-07T10:33:12Z
body: @wgetJane Uhm, this has been a while. On the top of my head I think of the two or three clue map versions. Or the T-Tester in MC Island? But this one could be a door that is used as a leaver.
author_association: MEMBER
user: login: Crashington
created_at: 2025-03-07T12:03:27Z
body: This is something i forgot to mention outside of discord that is likely connected to this: In TTT2 you can for some reason press E/Use on func_rotating to make it pause or resume its rotation... this is not a feature the entity has outside ttt2 and cant be disabled in hammer or anything
author_association: NONE
user: login: wgetJane
created_at: 2025-03-07T12:12:18Z
body: > This is something i forgot to mention outside of discord that is likely connected to this: In TTT2 you can for some reason press E/Use on func_rotating to make it pause or resume its rotation... this is not a feature the entity has outside ttt2 and cant be disabled in hammer or anything

yeah i'm aware of this, i've fixed this as well on a wip branch i have, i'll see if i can make a pr for it in a few days for testing
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-03-07T12:43:17Z
body: @TimGoll btw would it be fine to change the door and button nwvars to nw2vars? nw2vars respect pvs while nwvars will keep broadcasting updates to all players which is pretty overkill for something like door states and such
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-07T12:53:43Z
body: > [@TimGoll](https://github.com/TimGoll) btw would it be fine to change the door and button nwvars to nw2vars? nw2vars respect pvs while nwvars will keep broadcasting updates to all players which is pretty overkill for something like door states and such

Probably yes. Altough there are some addons like the door locker that render a "thermal vision" for all doors on the client - would this break it?

I think you can split it in two parts: The existence of the door has to be known to every client (I think that doesn't use NWvars anyway). The info on specific door states is only relevant in PVS, yes.
author_association: MEMBER
user: login: wgetJane
created_at: 2025-03-10T03:51:33Z
body: should i just remove this?

https://github.com/TTT-2/TTT2/blob/b00d445d21eb560010e923213238a77b535c68f4/lua/ttt2/libraries/door.lua#L231-L245

this doesnt make sense because most of these doors would be invalid to the player at this point due to pvs, this just gives the client a list of mostly invalid entities

if someone wants to keep track of doors clientside, they can just use OnEntityCreated

<details>
<summary>this is what `PrintTable(door_list.doors)` outputs on the client:</summary>

```
[1]	=	Entity [582][func_door]
[2]	=	Entity [583][func_door]
[3]	=	Entity [607][func_door]
[4]	=	Entity [609][func_door]
[5]	=	[NULL Entity]
[6]	=	Entity [1141][func_door_rotating]
[7]	=	[NULL Entity]
[8]	=	Entity [1175][func_door_rotating]
[9]	=	Entity [1176][func_door_rotating]
[10]	=	Entity [1177][func_door_rotating]
[11]	=	[NULL Entity]
[12]	=	Entity [1179][func_door_rotating]
[13]	=	Entity [1180][func_door_rotating]
[14]	=	[NULL Entity]
[15]	=	[NULL Entity]
[16]	=	Entity [1201][func_door_rotating]
[17]	=	Entity [1202][func_door_rotating]
[18]	=	Entity [1204][func_door]
[19]	=	Entity [1205][func_door]
[20]	=	[NULL Entity]
[21]	=	[NULL Entity]
[22]	=	[NULL Entity]
[23]	=	Entity [1216][func_door_rotating]
[24]	=	Entity [1217][func_door_rotating]
[25]	=	Entity [1218][func_door_rotating]
[26]	=	Entity [1219][func_door_rotating]
[27]	=	Entity [1231][func_door_rotating]
[28]	=	Entity [1237][func_door]
[29]	=	[NULL Entity]
[30]	=	Entity [1244][func_door_rotating]
[31]	=	[NULL Entity]
[32]	=	[NULL Entity]
[33]	=	[NULL Entity]
[34]	=	[NULL Entity]
[35]	=	[NULL Entity]
[36]	=	[NULL Entity]
[37]	=	Entity [1310][func_door]
[38]	=	Entity [1311][func_door]
[39]	=	[NULL Entity]
[40]	=	[NULL Entity]
[41]	=	Entity [1343][func_door_rotating]
[42]	=	[NULL Entity]
[43]	=	[NULL Entity]
[44]	=	[NULL Entity]
[45]	=	Entity [1348][func_door_rotating]
[46]	=	Entity [1349][func_door_rotating]
[47]	=	[NULL Entity]
[48]	=	[NULL Entity]
[49]	=	Entity [1384][func_door]
[50]	=	Entity [1389][func_door]
[51]	=	Entity [1392][func_door]
[52]	=	Entity [1393][func_door]
[53]	=	Entity [1467][func_door_rotating]
[54]	=	Entity [1468][func_door_rotating]
[55]	=	[NULL Entity]
[56]	=	[NULL Entity]
[57]	=	Entity [1471][func_door_rotating]
[58]	=	Entity [1472][func_door_rotating]
[59]	=	[NULL Entity]
[60]	=	Entity [1474][func_door_rotating]
[61]	=	Entity [1475][func_door_rotating]
[62]	=	[NULL Entity]
[63]	=	[NULL Entity]
[64]	=	Entity [1481][func_door]
[65]	=	Entity [1482][func_door]
[66]	=	[NULL Entity]
[67]	=	[NULL Entity]
[68]	=	Entity [1505][func_door]
[69]	=	[NULL Entity]
[70]	=	Entity [1920][func_door]
```

</details>

this doesn't seem it has any use case at all
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-03-10T08:23:33Z
body: > the simpsons map where doors are buttons

![Image](https://github.com/user-attachments/assets/ed909fb0-2b53-42dd-9ff3-8379af4f03a0)
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-10T10:13:06Z
body: > should i just remove this?
> 
> [TTT2/lua/ttt2/libraries/door.lua](https://github.com/TTT-2/TTT2/blob/b00d445d21eb560010e923213238a77b535c68f4/lua/ttt2/libraries/door.lua#L231-L245)
> 
> Lines 231 to 245 in [b00d445](/TTT-2/TTT2/commit/b00d445d21eb560010e923213238a77b535c68f4)
> 
>  net.Receive("TTT2SyncDoorEntities", function() 
>      local amount = net.ReadUInt(16) 
>   
>      local doors = {} 
>   
>      for i = 1, amount do 
>          doors[i] = net.ReadEntity() 
>      end 
>   
>      door_list.doors = doors 
>   
>      --- 
>      -- @realm client 
>      hook.Run("TTT2PostDoorSetup", doors) 
>  end) 
> this doesnt make sense because most of these doors would be invalid to the player at this point due to pvs, this just gives the client a list of mostly invalid entities
> 
> if someone wants to keep track of doors clientside, they can just use OnEntityCreated
> 
> this is what `PrintTable(door_list.doors)` outputs on the client:
> this doesn't seem it has any use case at all

I'm hesistant on removing this because I know at least one of my addons uses this, maybe other addons as well. The invalid entities are all "door" entities that don't exist on the map. If you teleport to them, they are on weird positions outside of the playable area. Therefore I can say that the list seemed useful to me. If I highlight all those entities you listed this seems to match all doors usable on a map
author_association: MEMBER
user: login: wgetJane
created_at: 2025-03-10T10:30:36Z
body: i mean removing the TTT2PostDoorSetup function on the clientside

at the point it is called, most of the table will be filled with null entities (these do not correspond to any entity) because the client has not seen those doors yet 

i cant see a possible use case for wanting to receive such a strange list on the client
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-03-10T11:30:32Z
body: @TimGoll can you link the addon?
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-10T11:36:30Z
body: > [@TimGoll](https://github.com/TimGoll) can you link the addon?

https://steamcommunity.com/sharedfiles/filedetails/?l=german&id=2115945573

When having this weapon selected, it should show all doors on the map
author_association: MEMBER
user: login: TimGoll
created_at: 2025-03-10T11:37:25Z
body: > i mean removing the TTT2PostDoorSetup function on the clientside
> 
> at the point it is called, most of the table will be filled with null entities (these do not correspond to any entity) because the client has not seen those doors yet
> 
> i cant see a possible use case for wanting to receive such a strange list on the client

I think this hook is unused so far, not sure. So it should be possible to safely remove this hook
author_association: MEMBER
user: login: wgetJane
created_at: 2025-03-10T12:07:40Z
body: just found it:

https://github.com/TTT-2/ttt2-wep_doorlocker/blob/23a63deb9c3893a52f281ea3c8a02b5cf8a11eef/gamemodes/terrortown/entities/weapons/weapon_ttt_doorlocker.lua#L127

this usage is not ideal

this assumes that door.GetAll() will give a list of all doors, but this is not the case, it will give a list of mostly null entities, as shown by my comment above https://github.com/TTT-2/TTT2/issues/1775#issuecomment-2709361490

the null entities in the list will stay as null entities until the next round start, the potential non-null entities in the list will be the doors that were within the player's pvs when the round started

(funny note: the position used for that pvs might be where the player was right before they respawned, rather than where they respawned at round start, for the same reason why players will pick up weapons right before they respawn https://github.com/Facepunch/garrysmod/pull/1887 )

the player will receive more door entities as they walk around the map after round start, however door.GetAll() will not be updated with these, again: the null entities in the list will stay as null entities until the next round start

a better way to implement door.GetAll() clientside would be something like this:

```lua
function door.GetAll()
	local doors = {}

	for _, ent in ents.Iterator() do
		if IsValid(ent) and ent:IsDoor() then
			doors[#doors + 1] = ent
		end
	end

	return doors
end
```

this gives a list of all doors that have been networked to the client so far instead of just the ones that were networked to the client at round start

however, if you want doors to be always synced and visible to clients, then you would need to set the [EFL_IN_SKYBOX](https://wiki.facepunch.com/gmod/Enums/EFL#EFL_IN_SKYBOX) flag for every door

i wouldn't recommend this though due to the cost in networking, i guess maybe just for the doorlocker addon and only on doors that need to be highlighted through walls
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-10T13:05:26Z
body: You have a good point here, probably best then to remove the syncing and replace the GetAll function like you suggested. The door locker then can set the flag to all doors when bought(?) or installed. Are the flags persistent over map resets? or do I have to set them again after each reset? If I have to, I'd change it so that the skybox flag is set when the weapon is bought
author_association: MEMBER
user: login: wgetJane
created_at: 2025-03-10T13:13:23Z
body: > You have a good point here, probably best then to remove the syncing and replace the GetAll function like you suggested. The door locker then can set the flag to all doors when bought(?) or installed. Are the flags persistent over map resets? or do I have to set them again after each reset? If I have to, I'd change it so that the skybox flag is set when the weapon is bought

yeah they get reset because map entities will get deleted and recreated on every map cleanup

the flag being set when the weapon is created sounds fine, though on maps with a LOT of doors there may potentially be a noticeable stutter which could clue in the more knowledgeable players that someone has purchased the item

i've also applied the door.GetAll() change to the pr for testing
author_association: CONTRIBUTOR
user: login: wgetJane
created_at: 2025-03-10T14:32:27Z
body: btw generally a lot of the buttons in maps not being outlined when you point at them is not due to the button not being solid

it's usually because it's a separate prop_dynamic in the same place as the func_button which is just invisible (using playerclip or trigger texture)

mappers are supposed to parent such prop_dynamic entities to the func_button, however of course not every mapper does that, and there's no way around this unfortunately
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-10T15:20:09Z
body: ohh, that's something I didn't know because I never created a map. I only notices that I was able to fix it by making it solid
author_association: MEMBER
user: login: TimGoll
created_at: 2025-03-18T20:50:56Z
body: ![Image](https://github.com/user-attachments/assets/51c4bc1f-9929-40e4-afdf-46c9e46b5e7f)

@wgetJane I'm confused. I changed nothing and the doorlocker is still working. I thought I'd have to set the flag to sync the doors to the client?
author_association: MEMBER
user: login: wgetJane
created_at: 2025-03-18T21:51:28Z
body: > [@wgetJane](https://github.com/wgetJane) I'm confused. I changed nothing and the doorlocker is still working. I thought I'd have to set the flag to sync the doors to the client?

not sure exactly how the pvs on that map is, it depends on each map

did you move around at all after spawning? you will receive more doors on the client when you move around the map since you will be changing your pvs a lot
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-19T09:18:06Z
body: Not sure either. But this map has area portals in almost all doorframes. So I assumed that this is related. It also looks to me as if I'm able to see all doors from the very beginning on. Did not confirm that though. Is there a map that you know of that is better to test this?
author_association: MEMBER
user: login: wgetJane
created_at: 2025-03-19T10:54:26Z
body: are you testing in singleplayer or multiplayer
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-19T13:19:23Z
body: multiplayer, TTT2 is broken in singleplayer anyway

author_association: MEMBER

Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3dd28403e..90a87adb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,18 @@ All notable changes to TTT2 will be documented here. Inspired by [keep a changel
 - Fixed classic armour protecting against crowbar damage (by @wgetJane)
 - Fixed C4/Radio sounds not playing outside of PAS (Facepunch/garrysmod/pull/2203, by @figardo)
 - Fixed players sometimes being revealed as dead when they chat/voicechat right as they die (Facepunch/garrysmod/pull/2229, by @wgetJane)
+- Fixed various bugs related to using doors and buttons, to match base TTT behavior (by @wgetJane)
+  - Fixed brush doors on certain maps not being usable
+  - Fixed certain doors on certain maps being forcibly usable when they shouldn't be
+  - Fixed func_rotating being forcibly usable
+  - Fixed vehicles not being usable
+  - Removed forced button solidity
+  - Improved targetID trace detection for doors and buttons
+  - Allow brush doors to be detected as destructible for targetID
+  - Use NW2Vars instead of NWVars for door variables for more efficient networking
+  - Fixed clientside door.GetAll() to return a more updated list of doors
+  - Removed clientside TTT2PostDoorSetup hook
+  - Changed crowbar unlocking behavior to match base TTT
 
 ## [v0.14.2b](https://github.com/TTT-2/TTT2/tree/v0.14.2b) (2025-02-02)
 
diff --git a/gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua b/gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua
index fb868589d..341dbfda1 100644
--- a/gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua
+++ b/gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua
@@ -85,7 +85,7 @@ local pmnc_tbl = {
 }
 
 local function OpenableEnt(ent)
-    return pmnc_tbl[ent:GetClass()] or OPEN_NO
+    return ent:GetName() ~= "" and pmnc_tbl[ent:GetClass()] or OPEN_NO
 end
 
 local function CrowbarCanUnlock(t)
@@ -108,7 +108,7 @@ function SWEP:OpenEnt(hitEnt)
                 hitEnt:Fire("Unlock", nil, 0)
             end
 
-            if unlock or hitEnt:HasSpawnFlags(SF_NPC_LONG_RANGE) then -- Long Visibility/Shoot
+            if unlock or hitEnt:HasSpawnFlags(256) then -- SF_DOOR_PUSE
                 if openable == OPEN_ROT then
                     hitEnt:Fire("OpenAwayFrom", self:GetOwner(), 0)
                 end
diff --git a/gamemodes/terrortown/gamemode/client/cl_keys.lua b/gamemodes/terrortown/gamemode/client/cl_keys.lua
index 8b2935db6..c75651184 100644
--- a/gamemodes/terrortown/gamemode/client/cl_keys.lua
+++ b/gamemodes/terrortown/gamemode/client/cl_keys.lua
@@ -75,7 +75,7 @@ function GM:PlayerBindPress(ply, bindName, pressed)
             return TBHUD:UseFocused()
         end
 
-        -- Find out if a marker is focussed otherwise check normal use
+        -- Find out if a marker is focused otherwise check normal use
         local isClientOnly = false
         local useEnt = markerVision.GetFocusedEntity()
         local isRemote = IsValid(useEnt)
@@ -104,14 +104,17 @@ function GM:PlayerBindPress(ply, bindName, pressed)
 
         -- If returned true by ClientUse or RemoteUse, then dont call Use and UseOverride or RemoteUse serverside
         if isClientOnly then
-            return
+            return true
         end
 
-        net.Start("TTT2PlayerUseEntity")
-        net.WriteBool(useEnt ~= nil)
-        net.WriteEntity(useEnt)
-        net.WriteBool(isRemote)
-        net.SendToServer()
+        if IsValid(useEnt) and useEnt:IsSpecialUsableEntity() then
+            net.Start("TTT2PlayerUseEntity")
+            net.WriteEntity(useEnt)
+            net.WriteBool(isRemote)
+            net.SendToServer()
+
+            return true
+        end
     elseif string.sub(bindName, 1, 4) == "slot" and pressed then
         local idx = tonumber(string.sub(bindName, 5, -1)) or 1
 
diff --git a/gamemodes/terrortown/gamemode/server/sv_entity.lua b/gamemodes/terrortown/gamemode/server/sv_entity.lua
index 3ceb9130d..9618ada46 100644
--- a/gamemodes/terrortown/gamemode/server/sv_entity.lua
+++ b/gamemodes/terrortown/gamemode/server/sv_entity.lua
@@ -90,7 +90,7 @@ function entmeta:IsUsableEntity(requiredCaps)
 
     -- special case: TTT specific lua based use interactions
     -- when we're looking for specifically the lua use
-    if self:IsWeapon() or self.player_ragdoll or (self:IsScripted() and self.CanUseKey) then
+    if self:IsSpecialUsableEntity() then
         return true
     end
 
diff --git a/gamemodes/terrortown/gamemode/server/sv_main.lua b/gamemodes/terrortown/gamemode/server/sv_main.lua
index 2f039fe11..c199c8aab 100644
--- a/gamemodes/terrortown/gamemode/server/sv_main.lua
+++ b/gamemodes/terrortown/gamemode/server/sv_main.lua
@@ -1007,3 +1007,10 @@ function GM:TTT2PreEndRound(result, duration)
     -- send the clients the round log, players will be shown the report
     events.StreamToClients()
 end
+
+---
+-- Called right after all doors are initialized on the map.
+-- @param table doorsTable A table with the newly registered door entities
+-- @hook
+-- @realm server
+function GM:TTT2PostDoorSetup(doorsTable) end
diff --git a/gamemodes/terrortown/gamemode/server/sv_player.lua b/gamemodes/terrortown/gamemode/server/sv_player.lua
index 0b2736b6f..0b510e3c2 100644
--- a/gamemodes/terrortown/gamemode/server/sv_player.lua
+++ b/gamemodes/terrortown/gamemode/server/sv_player.lua
@@ -343,18 +343,30 @@ end
 -- Triggered when the @{Player} presses use on an object.
 -- Continuously runs until USE is released but will not activate other Entities
 -- until the USE key is released; dependent on activation type of the @{Entity}.
--- @note TTT2 blocks all gmod internal use and only checks this for addons
 -- @param Player ply The @{Player} pressing the "use" key.
 -- @param Entity ent The entity which the @{Player} is looking at / activating USE on.
--- @param bool overrideDoPlayerUse This is used to override the default outcome of the check
 -- @return boolean Return false if the @{Player} is not allowed to USE the entity.
 -- Do not return true if using a hook, otherwise other mods may not get a chance to block a @{Player}'s use.
 -- @hook
 -- @realm server
 -- @ref https://wiki.facepunch.com/gmod/GM:PlayerUse
 -- @local
-function GM:PlayerUse(ply, ent, overrideDoPlayerUse)
-    return overrideDoPlayerUse or false
+function GM:PlayerUse(ply, ent)
+    if not ply:IsTerror() then
+        return false
+    end
+
+    if
+        ent:IsButton()
+        ---
+        -- @realm server
+        and hook.Run("TTT2OnButtonUse", ply, ent, ent:GetInternalVariable("m_toggle_state"))
+            == false
+    then
+        return false
+    end
+
+    return true
 end
 
 ---
@@ -514,11 +526,8 @@ end
 
 local function EntityContinuousUse(ent, ply)
     ---
-    -- Enable addons to allow handling PlayerUse
-    -- Otherwise default to old IsTerror Check
     -- @realm server
-    -- stylua: ignore
-    if hook.Run("PlayerUse", ply, ent, ply:IsTerror()) then
+    if hook.Run("PlayerUse", ply, ent) then
         ent:Use(ply, ply)
     end
 
@@ -568,7 +577,7 @@ local function EntityContinuousUse(ent, ply)
         end
 
         -- make sure the entity is still in a good position
-        local distance = ply:GetShootPos():Distance(ent:GetPos())
+        local distance = ply:GetShootPos():Distance(ent:WorldSpaceCenter())
 
         if distance > 100 + ent:BoundingRadius() then
             return
@@ -586,15 +595,10 @@ end
 -- @realm server
 -- @internal
 net.Receive("TTT2PlayerUseEntity", function(len, ply)
-    local hasEnt = net.ReadBool()
     local ent = net.ReadEntity()
     local isRemote = net.ReadBool()
 
-    if not hasEnt or not ent:IsUsableEntity() then
-        ent = ply:GetUseEntity()
-    end
-
-    if not IsValid(ent) then
+    if not (IsValid(ent) and ent:IsSpecialUsableEntity()) then
         return
     end
 
@@ -608,21 +612,11 @@ net.Receive("TTT2PlayerUseEntity", function(len, ply)
 
     -- Check if the use interaction is possible
     -- Add the bounding radius to compensate for center position
-    local distance = ply:GetShootPos():Distance(ent:GetPos())
+    local distance = ply:GetShootPos():Distance(ent:WorldSpaceCenter())
     if distance > 100 + ent:BoundingRadius() then
         return
     end
 
-    if
-        ent:IsButton()
-        ---
-        -- @realm server
-        and hook.Run("TTT2OnButtonUse", ply, ent, ent:GetInternalVariable("m_toggle_state"))
-            == false
-    then
-        return
-    end
-
     EntityContinuousUse(ent, ply)
 end)
 
@@ -1324,6 +1318,18 @@ function GM:EntityTakeDamage(ent, dmginfo)
     end
 end
 
+---
+-- Called after an entity receives a damage event, after passing damage filters, etc.
+-- @param Entity ent The @{Entity} taking damage
+-- @param CTakeDamageInfo dmginfo Damage info
+-- @param boolean wasDamageTaken Whether the entity actually took the damage
+-- @hook
+-- @realm server
+-- @ref https://wiki.facepunch.com/gmod/GM:PostEntityTakeDamage
+function GM:PostEntityTakeDamage(ent, dmginfo, wasDamageTaken)
+    door.PostHandleDamage(ent, dmginfo, wasDamageTaken)
+end
+
 ---
 -- Called by @{GM:EntityTakeDamage}
 -- @param Entity ent The @{Entity} taking damage
diff --git a/gamemodes/terrortown/gamemode/shared/sh_door.lua b/gamemodes/terrortown/gamemode/shared/sh_door.lua
index 6d7234923..b27351cd6 100644
--- a/gamemodes/terrortown/gamemode/shared/sh_door.lua
+++ b/gamemodes/terrortown/gamemode/shared/sh_door.lua
@@ -45,7 +45,7 @@ function entmeta:IsDoorLocked()
         return
     end
 
-    return self:GetNWBool("ttt2_door_locked", false)
+    return self:GetNW2Bool("ttt2_door_locked", false)
 end
 
 ---
@@ -57,7 +57,7 @@ function entmeta:IsDoorForceclosed()
         return
     end
 
-    return self:GetNWBool("ttt2_door_forceclosed", false)
+    return self:GetNW2Bool("ttt2_door_forceclosed", false)
 end
 
 ---
@@ -70,7 +70,7 @@ function entmeta:UseOpensDoor()
         return
     end
 
-    return self:GetNWBool("ttt2_door_player_use", false)
+    return self:GetNW2Bool("ttt2_door_player_use", false)
 end
 
 ---
@@ -82,7 +82,7 @@ function entmeta:TouchOpensDoor()
         return
     end
 
-    return self:GetNWBool("ttt2_door_player_touch", false)
+    return self:GetNW2Bool("ttt2_door_player_touch", false)
 end
 
 ---
@@ -106,11 +106,11 @@ function entmeta:DoorAutoCloses()
         return
     end
 
-    return self:GetNWBool("ttt2_door_auto_close", false)
+    return self:GetNW2Bool("ttt2_door_auto_close", false)
 end
 
 ---
--- Retuens if a door is destructible.
+-- Returns if a door is destructible.
 -- @return boolean If a door is destructible
 -- @realm shared
 function entmeta:DoorIsDestructible()
@@ -118,7 +118,16 @@ function entmeta:DoorIsDestructible()
         return
     end
 
-    return self:GetNWBool("ttt2_door_is_destructable", false)
+    -- might be a little hacky
+    if
+        self:Health() > 0
+        and self:GetMaxHealth() > 0
+        and (self:GetInternalVariable("m_takedamage") or 2) > 1
+    then
+        return true
+    end
+
+    return self:GetNW2Bool("ttt2_door_is_destructable", false)
 end
 
 ---
@@ -130,7 +139,7 @@ function entmeta:IsDoorOpen()
         return
     end
 
-    return self:GetNWBool("ttt2_door_open", false)
+    return self:GetNW2Bool("ttt2_door_open", false)
 end
 
 ---
@@ -138,7 +147,7 @@ end
 -- @return number The synced health
 -- @realm shared
 function entmeta:GetFastSyncedHealth()
-    return math.max(0, self:GetNWInt("fast_sync_health", 100))
+    return math.max(0, self:GetNW2Int("fast_sync_health", self:Health()))
 end
 
 if SERVER then
@@ -259,7 +268,7 @@ if SERVER then
     function entmeta:SetDoorCanTouchOpen(state, surpressPair)
         door.SetPlayerCanTouch(self, state)
 
-        self:SetNWBool("ttt2_door_player_touch", door.PlayerCanTouch(self))
+        self:SetNW2Bool("ttt2_door_player_touch", door.PlayerCanTouch(self))
 
         -- if the door is grouped as a pair, call the other one as well
         if not surpressPair and IsValid(self.otherPairDoor) then
@@ -275,7 +284,7 @@ if SERVER then
     function entmeta:SetDoorCanUseOpen(state, surpressPair)
         door.SetPlayerCanUse(self, state)
 
-        self:SetNWBool("ttt2_door_player_use", door.PlayerCanUse(self))
+        self:SetNW2Bool("ttt2_door_player_use", door.PlayerCanUse(self))
 
         -- if the door is grouped as a pair, call the other one as well
         if not surpressPair and IsValid(self.otherPairDoor) then
@@ -291,7 +300,7 @@ if SERVER then
     function entmeta:SetDoorAutoCloses(state, surpressPair)
         door.SetAutoClose(self, state)
 
-        self:SetNWBool("ttt2_door_auto_close", door.AutoCloses(self))
+        self:SetNW2Bool("ttt2_door_auto_close", door.AutoCloses(self))
 
         -- if the door is grouped as a pair, call the other one as well
         if not surpressPair and IsValid(self.otherPairDoor) then
@@ -309,12 +318,12 @@ if SERVER then
             return
         end
 
-        self:SetNWBool("ttt2_door_is_destructable", state)
+        self:SetNW2Bool("ttt2_door_is_destructable", state)
 
         if self:Health() == 0 then
             self:SetHealth(cvDoorHealth:GetInt())
 
-            self:SetNWInt("fast_sync_health", self:Health())
+            self:SetNW2Int("fast_sync_health", self:Health())
         end
 
         -- if the door is grouped as a pair, call the other one as well
diff --git a/gamemodes/terrortown/gamemode/shared/sh_entity.lua b/gamemodes/terrortown/gamemode/shared/sh_entity.lua
index 6b95d1659..9c215d8f0 100644
--- a/gamemodes/terrortown/gamemode/shared/sh_entity.lua
+++ b/gamemodes/terrortown/gamemode/shared/sh_entity.lua
@@ -40,3 +40,15 @@ end
 function entmeta:IsButton()
     return self:IsDefaultButton() or self:IsRotatingButton()
 end
+
+---
+-- @return boolean
+-- @realm server
+function entmeta:IsSpecialUsableEntity()
+    return self:IsWeapon()
+        or self.player_ragdoll
+        or self:IsPlayerRagdoll()
+        or self.CanUseKey
+        or self.ClientUse
+        or self.RemoteUse
+end
diff --git a/gamemodes/terrortown/gamemode/shared/sh_main.lua b/gamemodes/terrortown/gamemode/shared/sh_main.lua
index b0c90ca32..7d8378ddf 100644
--- a/gamemodes/terrortown/gamemode/shared/sh_main.lua
+++ b/gamemodes/terrortown/gamemode/shared/sh_main.lua
@@ -548,13 +548,6 @@ function GM:InitFallbackShops() end
 -- @realm shared
 function GM:LoadedFallbackShops() end
 
----
--- Called right after all doors are initialized on the map.
--- @param table doorsTable A table with the newly registered door entities
--- @hook
--- @realm shared
-function GM:TTT2PostDoorSetup(doorsTable) end
-
 -- Called after all roles were loaded, @{ROLE:Preinitialize} and @{ROLE:Initialize} were called
 -- and their convars were set up.
 -- @hook
diff --git a/lua/ttt2/libraries/buttons.lua b/lua/ttt2/libraries/buttons.lua
index af45aca6a..06493f16e 100644
--- a/lua/ttt2/libraries/buttons.lua
+++ b/lua/ttt2/libraries/buttons.lua
@@ -31,10 +31,7 @@ if SERVER then
             for j = 1, #buttonsTable do
                 local foundButton = buttonsTable[j]
 
-                foundButton:SetNotSolid(false)
-                foundButton:SetSolid(SOLID_BSP)
-
-                foundButton:SetNWInt("button_class", i)
+                foundButton:SetNW2Int("button_class", i)
             end
         end
 
@@ -62,7 +59,7 @@ end
 -- @return boolean Returns true if the entity matches the provided class name
 -- @realm shared
 function button.IsClass(ent, class)
-    local classID = ent:GetNWInt("button_class")
+    local classID = ent:GetNW2Int("button_class")
 
     if not classID or classID > #validButtons then
         return
diff --git a/lua/ttt2/libraries/door.lua b/lua/ttt2/libraries/door.lua
index 4b4138453..38a2cd538 100644
--- a/lua/ttt2/libraries/door.lua
+++ b/lua/ttt2/libraries/door.lua
@@ -5,7 +5,6 @@
 
 local string = string
 local player = player
-local net = net
 local hook = hook
 local util = util
 local timer = timer
@@ -14,8 +13,6 @@ local Angle = Angle
 
 if SERVER then
     AddCSLuaFile()
-
-    util.AddNetworkString("TTT2SyncDoorEntities")
 end
 
 ---
@@ -58,9 +55,7 @@ SF_FUNC_DOOR_SILENT_GENERAL = 4096
 
 door = door or {}
 
-local door_list = {
-    doors = {},
-}
+local door_list = {}
 
 local valid_doors = {
     special = {
@@ -182,14 +177,14 @@ if SERVER then
 
             doors[#doors + 1] = ent
 
-            ent:SetNWBool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false)
-            ent:SetNWBool("ttt2_door_forceclosed", ent:GetInternalVariable("forceclosed") or false)
-            ent:SetNWBool("ttt2_door_open", door.IsOpen(ent) or false)
+            ent:SetNW2Bool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false)
+            ent:SetNW2Bool("ttt2_door_forceclosed", ent:GetInternalVariable("forceclosed") or false)
+            ent:SetNW2Bool("ttt2_door_open", door.IsOpen(ent) or false)
 
-            ent:SetNWBool("ttt2_door_player_use", door.PlayerCanUse(ent))
-            ent:SetNWBool("ttt2_door_player_touch", door.PlayerCanTouch(ent))
-            ent:SetNWBool("ttt2_door_auto_close", door.AutoCloses(ent))
-            ent:SetNWBool("ttt2_door_is_destructable", door.IsDestructible(ent))
+            ent:SetNW2Bool("ttt2_door_player_use", door.PlayerCanUse(ent))
+            ent:SetNW2Bool("ttt2_door_player_touch", door.PlayerCanTouch(ent))
+            ent:SetNW2Bool("ttt2_door_auto_close", door.AutoCloses(ent))
+            ent:SetNW2Bool("ttt2_door_is_destructable", door.IsDestructible(ent))
 
             entityOutputs.RegisterMapEntityOutput(ent, "OnOpen", "TTT2DoorOpens")
             entityOutputs.RegisterMapEntityOutput(ent, "OnClose", "TTT2DoorCloses")
@@ -214,35 +209,7 @@ if SERVER then
         ---
         -- @realm server
         hook.Run("TTT2PostDoorSetup", doors)
-
-        local amountDoors = #doors
-
-        net.Start("TTT2SyncDoorEntities")
-        net.WriteUInt(amountDoors, 16)
-
-        -- sync door list with clients
-        for i = 1, amountDoors do
-            net.WriteEntity(doors[i])
-        end
-
-        net.Broadcast()
     end
-else -- CLIENT
-    net.Receive("TTT2SyncDoorEntities", function()
-        local amount = net.ReadUInt(16)
-
-        local doors = {}
-
-        for i = 1, amount do
-            doors[i] = net.ReadEntity()
-        end
-
-        door_list.doors = doors
-
-        ---
-        -- @realm client
-        hook.Run("TTT2PostDoorSetup", doors)
-    end)
 end
 
 ---
@@ -271,12 +238,43 @@ function door.IsValidSpecial(cls)
     return valid_doors.special[cls] or false
 end
 
----
--- Returns all valid door entities found on a map
--- @return table A table of door entities
--- @realm shared
-function door.GetAll()
-    return door_list.doors
+if SERVER then
+    ---
+    -- Returns all valid door entities found on a map
+    -- @return table A table of door entities
+    -- @realm server
+    function door.GetAll()
+        return door_list.doors or {}
+    end
+else
+    ---
+    -- Returns all valid door entities found on a map
+    -- @return table A table of door entities
+    -- @realm client
+    function door.GetAll()
+        if not door_list.doors then
+            local doors = {}
+
+            for _, ent in ents.Iterator() do
+                if IsValid(ent) and ent:IsDoor() then
+                    doors[#doors + 1] = ent
+                end
+            end
+
+            door_list.doors = doors
+        end
+
+        return door_list.doors
+    end
+
+    local function InvalidateDoorCache(ent)
+        if ent.IsDoor and ent:IsDoor() then
+            door_list.doors = nil
+        end
+    end
+
+    hook.Add("OnEntityCreated", "TTT2InvalidateDoorCache", InvalidateDoorCache)
+    hook.Add("EntityRemoved", "TTT2InvalidateDoorCache", InvalidateDoorCache)
 end
 
 if SERVER then
@@ -365,7 +363,7 @@ if SERVER then
         if door.IsValidNormal(cls) then
             return ent:HasSpawnFlags(SF_PROP_DOOR_START_BREAKABLE)
         elseif door.IsValidSpecial(cls) then
-            return false
+            return (ent:GetInternalVariable("m_takedamage") or 0) > 1
         end
 
         return false
@@ -454,6 +452,11 @@ if SERVER then
             return
         end
 
+        -- skip if engine already handles damage
+        if (ent:GetInternalVariable("m_takedamage") or 0) > 1 then
+            return
+        end
+
         local damage = math.max(0, dmginfo:GetDamage())
         local health = ent:Health() - damage
 
@@ -473,7 +476,21 @@ if SERVER then
         end
 
         ent:SetHealth(health)
-        ent:SetNWInt("fast_sync_health", health)
+    end
+
+    ---
+    -- Called in @{GM:PostEntityTakeDamage}.
+    -- @param Entity ent The entity that is damaged
+    -- @param CTakeDamageInfo dmginfo The damage info object
+    -- @param boolean wasDamageTaken Whether the entity actually took the damage
+    -- @internal
+    -- @realm server
+    function door.PostHandleDamage(ent, dmginfo, wasDamageTaken)
+        if not ent:DoorIsDestructible() then
+            return
+        end
+
+        ent:SetNW2Int("fast_sync_health", ent:Health())
     end
 
     ---
@@ -577,7 +594,7 @@ if SERVER then
 
             -- we expect the door to be locked now, but we check the real state after a short
             -- amount of time to be sure
-            ent:SetNWBool("ttt2_door_locked", true)
+            ent:SetNW2Bool("ttt2_door_locked", true)
 
             -- check if the assumed state was correct
             timer.Create("ttt2_recheck_door_lock_" .. ent:EntIndex(), 1, 1, function()
@@ -585,7 +602,7 @@ if SERVER then
                     return
                 end
 
-                ent:SetNWBool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false)
+                ent:SetNW2Bool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false)
             end)
         elseif name == "unlock" then
             ---
@@ -598,7 +615,7 @@ if SERVER then
 
             -- we expect the door to be unlocked now, but we check the real state after a short
             -- amount of time to be sure
-            ent:SetNWBool("ttt2_door_locked", false)
+            ent:SetNW2Bool("ttt2_door_locked", false)
 
             -- check if the assumed state was correct
             timer.Create("ttt2_recheck_door_unlock_" .. ent:EntIndex(), 1, 1, function()
@@ -606,7 +623,7 @@ if SERVER then
                     return
                 end
 
-                ent:SetNWBool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false)
+                ent:SetNW2Bool("ttt2_door_locked", ent:GetInternalVariable("m_bLocked") or false)
             end)
         elseif name == "use" and ent:IsDoorOpen() then
             -- do not stack closing time if door closes automatically
@@ -657,7 +674,7 @@ if SERVER then
             return
         end
 
-        doorEntity:SetNWBool("ttt2_door_open", true)
+        doorEntity:SetNW2Bool("ttt2_door_open", true)
     end
 
     ---
@@ -671,7 +688,7 @@ if SERVER then
             return
         end
 
-        doorEntity:SetNWBool("ttt2_door_open", true)
+        doorEntity:SetNW2Bool("ttt2_door_open", true)
     end
 
     ---
@@ -685,7 +702,7 @@ if SERVER then
             return
         end
 
-        doorEntity:SetNWBool("ttt2_door_open", false)
+        doorEntity:SetNW2Bool("ttt2_door_open", false)
     end
 
     ---
@@ -699,7 +716,7 @@ if SERVER then
             return
         end
 
-        doorEntity:SetNWBool("ttt2_door_open", false)
+        doorEntity:SetNW2Bool("ttt2_door_open", false)
     end
 
     ---
diff --git a/lua/ttt2/libraries/targetid.lua b/lua/ttt2/libraries/targetid.lua
index 07101382b..e08cbd364 100644
--- a/lua/ttt2/libraries/targetid.lua
+++ b/lua/ttt2/libraries/targetid.lua
@@ -69,6 +69,8 @@ function targetid.Initialize()
     }
 end
 
+local PLAYER_USE_RADIUS = 80
+
 ---
 -- This function handles finding Entities by casting a ray from a point in a direction, filtering out certain entities
 -- Use this in combination with the hook @GM:TTTModifyTargetedEntity to create your own Remote Camera with TargetIDs.
@@ -82,9 +84,8 @@ end
 -- @realm client
 function targetid.FindEntityAlongView(pos, dir, filter)
     local client = LocalPlayer()
-    local endpos = dir
 
-    endpos:Mul(MAX_TRACE_LENGTH)
+    local endpos = dir * MAX_TRACE_LENGTH
     endpos:Add(pos)
 
     if entspawnscript.IsEditing(client) then
@@ -108,15 +109,60 @@ function targetid.FindEntityAlongView(pos, dir, filter)
         return ent, pos:Distance(ent:GetPos())
     end
 
-    local trace = util.TraceLine({
+    local tracedata = {
         start = pos,
         endpos = endpos,
-        mask = MASK_ALL,
         filter = filter,
-    })
+    }
+
+    local trace = util.TraceLine(tracedata)
+
+    -- if nothing is hit, check again with a different mask
+    -- this will hit any solid buttons
+    if not IsValid(trace.Entity) then
+        tracedata.mask = bit.bor(MASK_SOLID, CONTENTS_DEBRIS, CONTENTS_PLAYERCLIP)
+
+        trace = util.TraceLine(tracedata)
+    end
 
     -- this is the entity the player is looking at right now
     local ent = trace.Entity
+    local distance = trace.StartPos:Distance(trace.HitPos)
+
+    -- if nothing is hit, try to look for non-solid buttons
+    if not IsValid(ent) then
+        --local buttons = ents.FindInCone(pos, dir, PLAYER_USE_RADIUS, 0.8)
+        local buttons = ents.FindInSphere(pos, PLAYER_USE_RADIUS)
+
+        local rayDelta = dir * PLAYER_USE_RADIUS
+
+        for i = 1, #buttons do
+            local e = buttons[i]
+
+            if e:IsSolid() or not e:IsButton() then
+                continue
+            end
+
+            local _, _, frac = util.IntersectRayWithOBB(
+                pos,
+                rayDelta,
+                e:GetPos(),
+                e:GetAngles(),
+                e:GetCollisionBounds()
+            )
+
+            if not frac then
+                continue
+            end
+
+            local dist = frac * PLAYER_USE_RADIUS
+
+            if dist < distance then
+                distance = dist
+                ent = e
+            end
+        end
+    end
 
     -- if a vehicle, we identify the driver instead
     if IsValid(ent) and ent:IsVehicle() then
@@ -127,7 +173,7 @@ function targetid.FindEntityAlongView(pos, dir, filter)
         end
     end
 
-    return ent, trace.StartPos:Distance(trace.HitPos)
+    return ent, distance
 end
 
 ---
@@ -621,13 +667,25 @@ function targetid.HUDDrawTargetIDButtons(tData)
     local client = LocalPlayer()
     local ent = tData:GetEntity()
 
-    if
-        not IsValid(client)
-        or not client:IsTerror()
-        or not IsValid(ent)
-        or not ent:IsButton()
-        or tData:GetEntityDistance() > 100
-    then
+    if not IsValid(client) or not client:IsTerror() or not IsValid(ent) then
+        return
+    end
+
+    -- button is supposed to be invisible
+    if ent:GetNoDraw() or ent:GetRenderMode() == RENDERMODE_NONE then
+        return
+    end
+
+    -- check if parent is button (for prop models parented to buttons and such)
+    if not ent:IsButton() then
+        ent = ent:GetMoveParent()
+
+        if not IsValid(ent) or not ent:IsButton() then
+            return
+        end
+    end
+
+    if tData:GetEntityDistance() > 100 then
         return
     end
 
@@ -657,14 +715,25 @@ function targetid.HUDDrawTargetIDDoors(tData)
     local client = LocalPlayer()
     local ent = tData:GetEntity()
 
-    if
-        not IsValid(client)
-        or not client:IsTerror()
-        or not IsValid(ent)
-        or not ent:IsDoor()
-        or not ent:PlayerCanOpenDoor()
-        or tData:GetEntityDistance() > 90
-    then
+    if not IsValid(client) or not client:IsTerror() or not IsValid(ent) then
+        return
+    end
+
+    -- door is supposed to be invisible
+    if ent:GetNoDraw() or ent:GetRenderMode() == RENDERMODE_NONE then
+        return
+    end
+
+    -- check if parent is door (for doors with breakable glass and such)
+    if not ent:IsDoor() then
+        ent = ent:GetMoveParent()
+
+        if not IsValid(ent) or not ent:IsDoor() then
+            return
+        end
+    end
+
+    if not ent:PlayerCanOpenDoor() or tData:GetEntityDistance() > 90 then
         return
     end
 


Reviews:
user: login: EntranceJew
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:17:37Z
user: login: TimGoll
body: Thank you so much for looking into this issue. That looks really good to me. Besides the one question, could you add to your changelog that you also fixed the car usage?
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-11T16:15:57Z
user: login: wgetJane
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T21:48:38Z
user: login: TimGoll
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T22:37:11Z
user: login: TimGoll
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T22:37:46Z

Review comments:
diff_hunk: @@ -108,7 +108,7 @@ function SWEP:OpenEnt(hitEnt)
                 hitEnt:Fire("Unlock", nil, 0)
             end
 
-            if unlock or hitEnt:HasSpawnFlags(SF_NPC_LONG_RANGE) then -- Long Visibility/Shoot
+            if unlock or hitEnt:HasSpawnFlags(256) then
path: gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua
user: login: TimGoll
body: Is there a reason you used this magic number instead of a spawn flag? Because the old one seems to be correct as well: https://wiki.facepunch.com/gmod/Enums/SF
created_at: 2025-03-11T16:12:17Z
author_association: MEMBER
diff_hunk: @@ -108,7 +108,7 @@ function SWEP:OpenEnt(hitEnt)
                 hitEnt:Fire("Unlock", nil, 0)
             end
 
-            if unlock or hitEnt:HasSpawnFlags(SF_NPC_LONG_RANGE) then -- Long Visibility/Shoot
+            if unlock or hitEnt:HasSpawnFlags(256) then
path: gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua
user: login: wgetJane
body: the old one is intended for npcs, it makes zero sense to use it for door entities
created_at: 2025-03-11T21:48:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -108,7 +108,7 @@ function SWEP:OpenEnt(hitEnt)
                 hitEnt:Fire("Unlock", nil, 0)
             end
 
-            if unlock or hitEnt:HasSpawnFlags(SF_NPC_LONG_RANGE) then -- Long Visibility/Shoot
+            if unlock or hitEnt:HasSpawnFlags(256) then
path: gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua
user: login: TimGoll
body: ah, yes, that makes sense!
created_at: 2025-03-11T22:37:11Z
author_association: MEMBER
diff_hunk: @@ -108,7 +108,7 @@ function SWEP:OpenEnt(hitEnt)
                 hitEnt:Fire("Unlock", nil, 0)
             end
 
-            if unlock or hitEnt:HasSpawnFlags(SF_NPC_LONG_RANGE) then -- Long Visibility/Shoot
+            if unlock or hitEnt:HasSpawnFlags(256) then
path: gamemodes/terrortown/entities/weapons/weapon_zm_improvised.lua
user: login: TimGoll
body: And I was about to suggest a comment, but you already did it. Thank you
created_at: 2025-03-11T22:37:46Z
author_association: MEMBER

PR comments:
user: login: wgetJane
created_at: 2025-03-10T09:04:29Z
body: this should be ok for testing now, i've tested it on a bunch of maps and it seems to work like it does in ttt (the main goal of this pr is to restore parity with ttt's +use behaviour with map entities)

gonna mention @Crashington since they seem to have had issues with ttt2 stuff in their maps as well, pls let me know if those issues are fixed with this pr

also i'm gonna restore crowbar unlocking/opening behaviour to be in line with base ttt, then i'll open this pr after that
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-10T10:14:20Z
body: I will try to see if this breaks any things that I implemented with external addons
author_association: MEMBER
user: login: Crashington
created_at: 2025-03-10T13:06:52Z
body: can confirm from my end that the pr fixes the two issues i found (#1 doors not being openable when the door entity origin is too far away from the door brush the player pressed use on. #2 func_rotating being paused/started by +use)
author_association: NONE
user: login: TimGoll
created_at: 2025-03-10T21:45:09Z
body: Seems like your changes broke this addon from me: https://steamcommunity.com/sharedfiles/filedetails/?l=indonesian&id=3335175627

other than that everything seems to work fine right now
author_association: MEMBER
user: login: TimGoll
created_at: 2025-03-10T21:47:22Z
body: Ah, another issue: When having a marker vision entity in LOS and looking at it, hitting E not only opens its UI, but also triggers the button:

![image](https://github.com/user-attachments/assets/e35ceee1-39e1-499e-99c9-41ba732a4a44)

This is something that should not happen, markervision elements, same as traitor buttons, should have priotiy over in world entities
author_association: MEMBER
user: login: wgetJane
created_at: 2025-03-10T21:49:05Z
body: > Seems like your changes broke this addon from me: https://steamcommunity.com/sharedfiles/filedetails/?l=indonesian&id=3335175627
> 
> other than that everything seems to work fine right now

i'll check later, what's broken specifically?
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-10T21:50:32Z
body: > > Seems like your changes broke this addon from me: https://steamcommunity.com/sharedfiles/filedetails/?l=indonesian&id=3335175627
> > other than that everything seems to work fine right now
> 
> i'll check later, what's broken specifically?

The addon is able to overwrite the name and description for buttons - that seems to no longer work.

Well, maybe it was on me. I just notived I have multiple version of this MC map. Maybe I chose the wrong one. I will test again! 
author_association: MEMBER
user: login: wgetJane
created_at: 2025-03-10T21:51:58Z
body: > Ah, another issue: When having a marker vision entity in LOS and looking at it, hitting E not only opens its UI, but also triggers the button:
> 
> This is something that should not happen, markervision elements, same as traitor buttons, should have priotiy over in world entities

oh right i forgot, ttt is supposed to "cancel" your actual +use if you're pointing at a "special use" entity like corpses and traitor buttons and such, probably just needs a `return true` in cl_keys
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-10T22:02:21Z
body: Confirmed on `ttt_elevatorandstairs` that it is not working:

![image](https://github.com/user-attachments/assets/4aede6de-a76e-4970-8c1e-a931b740a443)
![image](https://github.com/user-attachments/assets/7b62805b-9b09-4e4e-b3fa-2a676b1068c4)

Not even the admin-info is working:

![image](https://github.com/user-attachments/assets/1438e401-dd3f-47b5-96b5-945b59c9bf55)

This should show info about the button (like its ID etc)

Here's the relevant code: https://github.com/TTT-2/ttt2-button_info/blob/main/lua/terrortown/autorun/client/cl_buttoninfo_tid.lua#L8-L31

If the ent is valid on the server and the client, then this should work
author_association: MEMBER
user: login: wgetJane
created_at: 2025-03-11T02:55:04Z
body: @TimGoll seems to work fine for me

![image](https://github.com/user-attachments/assets/5ec17b89-5582-4fde-9dc9-c7ff8fdb7879)

![image](https://github.com/user-attachments/assets/30594dd0-310f-4e15-ad63-9b91a7cfba3f)

author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-11T09:15:50Z
body: @wgetJane That is so weird, because it still doesn't work for me
author_association: MEMBER
user: login: TimGoll
created_at: 2025-03-11T09:36:59Z
body: @wgetJane you're right, oof. Somehow I enabled minimal mode for targetID:

![image](https://github.com/user-attachments/assets/d884304d-a3d5-48a9-938a-1e5415f06ff2)

works now. So consider this resolved
author_association: MEMBER
user: login: TimGoll
created_at: 2025-03-11T09:44:28Z
body: Your changes also fix #1627 

Even the vehicle on grovestreet now works again!
author_association: MEMBER
user: login: Crashington
created_at: 2025-03-19T21:01:39Z
body: it is with a heavy heart that i must report that the most recent update seems to have not fixed everything it should have.
https://steamcommunity.com/sharedfiles/filedetails/?id=1293781407
on this version of waterworld the button inside the soda machien that is supposed to teleport you does not work
also, it seems like the "healing dispenser only heals for 1 health at a time" bug has returned

i am sure i am on the newest version since i have the new c4 settings available in the f1 menu
author_association: NONE
user: login: wgetJane
created_at: 2025-03-19T22:26:01Z
body: > on this version of waterworld the button inside the soda machien that is supposed to teleport you does not work

it doesn't work in base ttt either, the button is locked

using the `ent_fire !picker unlock` console command, the button can be pressed in both ttt and ttt2

> also, it seems like the "healing dispenser only heals for 1 health at a time" bug has returned

this might be caused by the `ENT.CanUseKey = true` line in ttt_health_station.lua, which is not present in base ttt
author_association: CONTRIBUTOR
user: login: Crashington
created_at: 2025-03-19T22:30:21Z
body: oh sorry on the waterworld thing then. weird that its locked but thats irrelevant here then.
author_association: NONE
user: login: TimGoll
created_at: 2025-03-19T23:32:25Z
body: The health station issue is handled in TTT2. Maybe you accidentally removed our continuous use implementation?
author_association: MEMBER
user: login: Crashington
created_at: 2025-03-19T23:44:26Z
body: i wouldnt know how i would have done that. we use the normal workshop version on the server. unless another addon could be somehow interfering? we dont have anything that directly interacts with the health station anyway
author_association: NONE
user: login: wgetJane
created_at: 2025-03-20T00:03:43Z
body: probably meant me, not you, though i haven't touched the health station at all, just forgot to test it for this pr

i'll figure out a solution later, was my fault anyway for not checking the health station (i just tested the traitor's placeables lmao)
author_association: CONTRIBUTOR
user: login: TimGoll
created_at: 2025-03-20T09:59:10Z
body: > probably meant me, not you, though i haven't touched the health station at all, just forgot to test it for this pr

It is not part of the health station per se, but part of the use code. Holding E retriggers use 10 times per second.

In theory this is an engine feature. I reimplemented it in lua. Dunno which approach would be best with your changes though 


author_association: MEMBER