Background:
JK provides two "touched" messages: one sent to a thing's cog when another thing touches it, and one sent to a surface's cog when a thing touches it. If we wanted to detect when a player touches any wall surface, we could use the second "touched" message, but we would have to link every surface to a cog. This seems like too much work. Idealy, there would be a "touched" message sent to the player's cog when the player touches any surface. The message would also identify which surface the player touched.
The ideal workaround:
1. One cog that somehow detects when the player touches a surface and also which surface it is.
2. The cog would then send the "touched" message to the player cog with the surface index as a parameter.
3. It would work as a mod so that any level could use it.
4. It would work with other mods.
5. It would work in both SP and MP.
6. It would not be slow.
7. The only requirement in using the workaround would be to add the cog and just use the "touched" message as if it were built-in to JK.
A tested working implementation:
1. There are 2 cogs and 2 templates. (Not bad, might be able to reduce this.)
2. SendMessageEx() is used, and surface index detection is accurate. (There are two small problems here, but will likely not be a problem with most levels.)
3. There are no level dependencies.
4. One thing is used, so that's one less thing for other mods. The thing is attached to the player, so there might be strange interactions if other things were attached to the player. No triggers are used.
5. It works in both SP and MP.
6. I am not sure how to test slowness, but I did not notice any slowdown on two machines I typically play JK on. (The machines are P3-550MHz-128MB.)
7. Need to add the two templates and the two cogs to a jkl. Besides that, using the "touched" message is just like using the other messages.
Here are the templates:
Here are the cogs:
What's going on:
The object_toucher creates itself using one of the templates. If player is not in a water sector, use the toucher_wpn template, else use the other. It attaches itself to the player. The templates are setup for exploding the toucher on touching other things and touching surfaces. It is set to be the same size as the player, so it will explode whenever a player touches somthing. When a touch (explosion) occurs, the removed message handler in object_toucher calculates the surface index detection, sends the "touched" message if a surface was touched, and finally creates another toucher before being destroyed.
Because a 0x4 attachment breaks on entering and exiting water, the object_toucher uses the toucher_wpn to explode when entering water and toucher_water_wpn when exiting water. This keeps an attachment because only the crossing causes the break.
The templates are set to be destroyed after 10 seconds, but the object_toucher cog disables this. Because the object_toucher cog is local to the player, there is only one toucher per machine. This means that only the local player's machine needs to do the detection, and there won't be any unusual interactions between touchers because each machine only knows about one.
The "touched" message sends these parameters:
Param 0: the surface that was touched
Param 1: the player who touched the surface
Param 2: the sector that contains the surface
Param 3: the cog that sent the message
Closing:
At least two small problems exist. One problem is that the surface detection only "goes through" one adjoin. Thus, if the player's position (a point near his belly button) is in one sector, and he touches a surface two sectors away, then the detection will not send the "touched" message. This is a small (forgive the pun) problem because the sectors would have to be really small slivers for this to occur. The other problem is that I haven't found a better way to detect adjoins than to detect if a surface is unrendered. This (I hope) is a small problem because it seems unlikely that a rendered adjoin would need surface index detection using the "touched" message.
I already see potential for making this implementation better, but I posted anyway so others can use it and improve on it in the meantime. One idea is to change the size and movesize of the toucher to match the player's current size and movesize (just like what the cog does with matching the player's model). Also, the surface index detection could use some more cleanup work. I had tried using recursion for searching through adjoins, but remembered the "thread limt" of 5 mentioned in the DataMaster, leaving the code a bit messy.
Suggestions, comments, and (of course) bug reports welcome. Use of the code is encouraged. (Email me if you do use it. I'd love to hear about it.)
JK provides two "touched" messages: one sent to a thing's cog when another thing touches it, and one sent to a surface's cog when a thing touches it. If we wanted to detect when a player touches any wall surface, we could use the second "touched" message, but we would have to link every surface to a cog. This seems like too much work. Idealy, there would be a "touched" message sent to the player's cog when the player touches any surface. The message would also identify which surface the player touched.
The ideal workaround:
1. One cog that somehow detects when the player touches a surface and also which surface it is.
2. The cog would then send the "touched" message to the player cog with the surface index as a parameter.
3. It would work as a mod so that any level could use it.
4. It would work with other mods.
5. It would work in both SP and MP.
6. It would not be slow.
7. The only requirement in using the workaround would be to add the cog and just use the "touched" message as if it were built-in to JK.
A tested working implementation:
1. There are 2 cogs and 2 templates. (Not bad, might be able to reduce this.)
2. SendMessageEx() is used, and surface index detection is accurate. (There are two small problems here, but will likely not be a problem with most levels.)
3. There are no level dependencies.
4. One thing is used, so that's one less thing for other mods. The thing is attached to the player, so there might be strange interactions if other things were attached to the player. No triggers are used.
5. It works in both SP and MP.
6. I am not sure how to test slowness, but I did not notice any slowdown on two machines I typically play JK on. (The machines are P3-550MHz-128MB.)
7. Need to add the two templates and the two cogs to a jkl. Besides that, using the "touched" message is just like using the other messages.
Here are the templates:
Code:
toucher_wpn none orient=(0/0/0) type=weapon collide=1 move=physics thingflags=0x8080c10 timer=10 mass=0 physflags=0x200 maxrotvel=90 damageclass=0x2 typeflags=0xd cog=class_toucher.cog model3d=ky.3do size=0.065 movesize=0.065 toucher_water_wpn none orient=(0/0/0) type=weapon collide=1 move=physics thingflags=0x10080c10 timer=10 mass=0 physflags=0x200 maxrotvel=90 damageclass=0x2 typeflags=0xd cog=class_toucher.cog model3d=ky.3do size=0.065 movesize=0.065
Here are the cogs:
Code:
# class_toucher.cog # # Purpose: # - Allows a player's cog to receive a "touch" message when the # player touches a surface. # Notes: # - Requires object_toucher.cog. # - Requires toucher_wpn and toucher_water_wpn templates. # # Written by ZeqMacaw 26-Aug-2004 # # Thanks to Cubix (a local friend) for helping me work out a couple of # problems with the toucher. # Special thanks to Descent_pilot for posting his cog for detecting a surface # index. His post not only gave me a starting point for the surface index # detection, it convinced me that my "touched" message idea could be # implemented. #------------------------------------------------------------------------------ flags=0x240 symbols message startup message created message removed end #------------------------------------------------------------------------------ code startup: return; created: return; removed: return; end # object_toucher.cog # # Capture cog for class_toucher things. # # Written by ZeqMacaw 26-Aug-2004 # # "Finding point in polygon" algorithm found here: # http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm #------------------------------------------------------------------------------ flags=0x240 symbols message startup message removed int player local vector position local flex distanceMin local int geoMode local vector surfaceNormal local vector surfaceCenter local flex distance local surface touchedSurfaceIndex local surface touchedSectorIndex local int i local surface adjoinIndex local surface adjoinIndex1 local sector sectorIndex local sector sectorIndex1 local surface sectorSurfaceCount local surface sectorSurfaceCount1 local surface sectorSurfaceIndex local surface sectorSurfaceIndex1 local surface surfaceIndex local surface surfaceIndex1 local template toucher_tpl=toucher_wpn local template toucher_water_tpl=toucher_water_wpn local thing toucher local vector ZeroVector local int isOnSurface local vector point local flex dot local int coordinateValue local int coordinateValueMax local int coordinateIgnored local int surfaceVertexIndex local int surfaceVertexCount local int pointIsOnTheLine local vector vec local vector p local vector p0 local vector p1 local vector p2 local end #------------------------------------------------------------------------------ code startup: Sleep(1); player = GetLocalPlayerThing(); ZeroVector = VectorSet(0, 0, 0); call CreateToucher; return; removed: position = GetThingPos(player); sectorIndex = GetThingSector(player); sectorSurfaceCount = GetNumSectorSurfaces(sectorIndex); touchedSurfaceIndex = -1; distanceMin = 0.1; for (sectorSurfaceIndex = 0; sectorSurfaceIndex < sectorSurfaceCount; sectorSurfaceIndex = sectorSurfaceIndex + 1) { surfaceIndex = GetSectorSurfaceRef(sectorIndex, sectorSurfaceIndex); surfaceNormal = GetSurfaceNormal(surfaceIndex); // Calculate distance between toucher's position (same as // player's) and sector's surface. surfaceCenter = GetSurfaceCenter(surfaceIndex); distance = VectorDot(surfaceNormal, VectorSub(position, surfaceCenter)); geoMode = GetFaceGeoMode(surfaceIndex); //NOTE: For now, a surface is considered an adjoin if it is unrendered. if ((geoMode == 0) && (distance < 0.065)) { // Player is across an adjoin, so check adjoining sector. i = 1; call CheckSectorSurfaces; } else if (distance < distanceMin) { point = VectorAdd(VectorScale( VectorSub(ZeroVector, surfaceNormal), distance), position); surfaceIndexi = surfaceIndex; call IsPointInPolygon; if (isOnSurface != 0) { distanceMin = distance; touchedSurfaceIndex = sectorSurfaceIndex; // surfaceIndex touchedSectorIndex = sectorIndex; } } } if (touchedSurfaceIndex > -1) { SendMessageEx(GetThingClassCog(player), touched, touchedSurfaceIndex, player, touchedSectorIndex, GetSelfCog()); } // The delay before creating toucher again prevents player getting stuck // in surface. Sleep(0.1); call CreateToucher; return; CreateToucher: if ((GetThingFlags(player) & 0x2000000) == 0x2000000) { toucher = FireProjectile(player, toucher_water_tpl, -1, -1, '0.0 0.0 0.0', '0.0 0.0 0.0', 0, 0, 0, 0); } else { toucher = FireProjectile(player, toucher_tpl, -1, -1, '0.0 0.0 0.0', '0.0 0.0 0.0', 0, 0, 0, 0); } SetLifeLeft(toucher, 0); ClearThingFlags(toucher, 0x80000); CaptureThing(toucher); SetThingModel(toucher, GetThingModel(player)); SetThingLook(toucher, GetThingLVec(player)); AttachThingToThingEx(toucher, player, 0x4); return; CheckSectorSurfaces: adjoinIndex = GetSurfaceAdjoin(surfaceIndex[i - 1]); sectorIndex = GetSurfaceSector(adjoinIndex); sectorSurfaceCount = GetNumSectorSurfaces(sectorIndex); for (sectorSurfaceIndex = 0; sectorSurfaceIndex < sectorSurfaceCount; sectorSurfaceIndex = sectorSurfaceIndex + 1) { surfaceIndex = GetSectorSurfaceRef(sectorIndex, sectorSurfaceIndex); // Don't check the adjoin surface. if (surfaceIndex != adjoinIndex) { surfaceNormal = GetSurfaceNormal(surfaceIndex); // Calculate distance between toucher's position (same as // player's) and sector's surface. surfaceCenter = GetSurfaceCenter(surfaceIndex); distance = VectorDot(surfaceNormal, VectorSub(position, surfaceCenter)); geoMode = GetFaceGeoMode(surfaceIndex); //NOTE: For now, a surface is considered an adjoin if it is unrendered. if ((geoMode == 0) && (distance < 0.065)) { //NOTE: Because of "threading" limit, don't bother going any // further. // Player is across an adjoin, so check adjoining sector. // call CheckSectorSurfaces; } else if (distance < distanceMin) { point = VectorAdd(VectorScale( VectorSub(ZeroVector, surfaceNormal), distance), position); surfaceIndexi = surfaceIndex; call IsPointInPolygon; if (isOnSurface != 0) { distanceMin = distance; touchedSurfaceIndex = sectorSurfaceIndex; // surfaceIndex touchedSectorIndex = sectorIndex; } } } } i = i - 1; return; IsPointInPolygon: // Set which coordinate to ignore (for projecting to 2D) by selecting the // one with largest absolute value in the surfaceNormal. // 0 = x; 1 = y; 2 = z coordinateValue = VectorX(surfaceNormal); if (coordinateValue < 0) { coordinateValue = coordinateValue * -1; } coordinateValueMax = coordinateValue; coordinateIgnored = 0; coordinateValue = VectorY(surfaceNormal); if (coordinateValue < 0) { coordinateValue = coordinateValue * -1; } if (coordinateValue > coordinateValueMax) { coordinateValueMax = coordinateValue; coordinateIgnored = 1; } coordinateValue = VectorZ(surfaceNormal); if (coordinateValue < 0) { coordinateValue = coordinateValue * -1; } if (coordinateValue > coordinateValueMax) { coordinateIgnored = 2; } // Change the vectors to 2-coordinate projection. surfaceVertexCount = GetNumSurfaceVertices(surfaceIndexi); HeapNew(0, surfaceVertexCount + 1); if (coordinateIgnored != 2) { if (coordinateIgnored == 0) { for (surfaceVertexIndex = 0; surfaceVertexIndex < surfaceVertexCount; surfaceVertexIndex = surfaceVertexIndex + 1) { vec = GetSurfaceVertexPos(surfaceIndexi, surfaceVertexIndex); HeapSet(surfaceVertexIndex, VectorSet(VectorY(vec), VectorZ(vec), 0)); } p = VectorSet(VectorY(point), VectorZ(point), 0); } else { for (surfaceVertexIndex = 0; surfaceVertexIndex < surfaceVertexCount; surfaceVertexIndex = surfaceVertexIndex + 1) { vec = GetSurfaceVertexPos(surfaceIndexi, surfaceVertexIndex); HeapSet(surfaceVertexIndex, VectorSet(VectorX(vec), VectorZ(vec), 0)); } p = VectorSet(VectorX(point), VectorZ(point), 0); } } else { for (surfaceVertexIndex = 0; surfaceVertexIndex < surfaceVertexCount; surfaceVertexIndex = surfaceVertexIndex + 1) { HeapSet(surfaceVertexIndex, GetSurfaceVertexPos(surfaceIndexi, surfaceVertexIndex)); } p = point; } HeapSet(surfaceVertexCount, HeapGet(0)); isOnSurface = 0; for (surfaceVertexIndex = 0; surfaceVertexIndex < surfaceVertexCount; surfaceVertexIndex = surfaceVertexIndex + 1) { if (VectorY(HeapGet(surfaceVertexIndex)) <= VectorY(p)) { if (VectorY(HeapGet(surfaceVertexIndex + 1)) > VectorY(p)) { p0 = HeapGet(surfaceVertexIndex); p1 = HeapGet(surfaceVertexIndex + 1); p2 = p; call IsPointOnTheLine; if (pointIsOnTheLine > 0) { isOnSurface = isOnSurface + 1; } } } else { if (VectorY(HeapGet(surfaceVertexIndex + 1)) <= VectorY(p)) { p0 = HeapGet(surfaceVertexIndex); p1 = HeapGet(surfaceVertexIndex + 1); p2 = p; call IsPointOnTheLine; if (pointIsOnTheLine < 0) { isOnSurface = isOnSurface - 1; } } } } HeapFree(); return; IsPointOnTheLine: pointIsOnTheLine = ( (VectorX(p1) - VectorX(p0)) * (VectorY(p2) - VectorY(p0)) - (VectorX(p2) - VectorX(p0)) * (VectorY(p1) - VectorY(p0)) ); return; end
What's going on:
The object_toucher creates itself using one of the templates. If player is not in a water sector, use the toucher_wpn template, else use the other. It attaches itself to the player. The templates are setup for exploding the toucher on touching other things and touching surfaces. It is set to be the same size as the player, so it will explode whenever a player touches somthing. When a touch (explosion) occurs, the removed message handler in object_toucher calculates the surface index detection, sends the "touched" message if a surface was touched, and finally creates another toucher before being destroyed.
Because a 0x4 attachment breaks on entering and exiting water, the object_toucher uses the toucher_wpn to explode when entering water and toucher_water_wpn when exiting water. This keeps an attachment because only the crossing causes the break.
The templates are set to be destroyed after 10 seconds, but the object_toucher cog disables this. Because the object_toucher cog is local to the player, there is only one toucher per machine. This means that only the local player's machine needs to do the detection, and there won't be any unusual interactions between touchers because each machine only knows about one.
The "touched" message sends these parameters:
Param 0: the surface that was touched
Param 1: the player who touched the surface
Param 2: the sector that contains the surface
Param 3: the cog that sent the message
Closing:
At least two small problems exist. One problem is that the surface detection only "goes through" one adjoin. Thus, if the player's position (a point near his belly button) is in one sector, and he touches a surface two sectors away, then the detection will not send the "touched" message. This is a small (forgive the pun) problem because the sectors would have to be really small slivers for this to occur. The other problem is that I haven't found a better way to detect adjoins than to detect if a surface is unrendered. This (I hope) is a small problem because it seems unlikely that a rendered adjoin would need surface index detection using the "touched" message.
I already see potential for making this implementation better, but I posted anyway so others can use it and improve on it in the meantime. One idea is to change the size and movesize of the toucher to match the player's current size and movesize (just like what the cog does with matching the player's model). Also, the surface index detection could use some more cleanup work. I had tried using recursion for searching through adjoins, but remembered the "thread limt" of 5 mentioned in the DataMaster, leaving the code a bit messy.
Suggestions, comments, and (of course) bug reports welcome. Use of the code is encouraged. (Email me if you do use it. I'd love to hear about it.)