Script v1_7_1_0
- AI
- Animals
- Collections
- Contracts
- Debug
- Economy
- Elements
- EnvironmentalScore
- Errors
- Events
- GUI
- Handtools
- Hud
- I3d
- Input
- Jobs
- Maps
- Materials
- Misc
- Objects
- Parameters
- Placeables
- Placement
- Player
- Shop
- Sounds
- Specialization
- Specializations
- AIConveyorBelt
- AIDrivable
- AIFieldWorker
- AIImplement
- AIJobVehicle
- AIVehicle
- AIVehicleObstacle
- AnimatedVehicle
- ArticulatedAxis
- Attachable
- AttacherJointControl
- AttacherJoints
- AutoLoader
- BaleGrab
- BaleLoader
- Baler
- BaleWrapper
- BaseMaterial
- BigBag
- BunkerSiloCompacter
- BunkerSiloInteractor
- CCTDrivable
- Combine
- ConnectionHoses
- ConveyorBelt
- Cover
- CrabSteering
- Crawlers
- CropSensor
- Cultivator
- Cutter
- Cylindered
- CylinderedFoldable
- Dashboard
- Dischargeable
- Drivable
- DynamicallyLoadedParts
- DynamicMountAttacher
- Enterable
- ExtendedAIVehicle
- ExtendedCombine
- ExtendedMotorized
- ExtendedMower
- ExtendedSowingMachine
- ExtendedSprayer
- ExtendedWearable
- FertilizingCultivator
- FertilizingSowingMachine
- FillTriggerVehicle
- FillUnit
- FillVolume
- Foldable
- FoliageBending
- ForageWagon
- FrontloaderAttacher
- FruitPreparer
- GroundAdjustedNodes
- GroundReference
- HeadlandAnimation
- Honk
- HookLiftContainer
- HookLiftTrailer
- IKChains
- InlineWrapper
- JigglingParts
- Leveler
- LicensePlates
- Lights
- LivestockTrailer
- Locomotive
- LogGrab
- ManureBarrel
- ManureSensor
- MixerWagon
- Motorized
- Mountable
- Mower
- Mulcher
- MultipleItemPurchase
- Pallet
- Pickup
- Pipe
- PlaceableAI
- PlaceableAnimatedObjects
- PlaceableBeehive
- PlaceableBeehivePalletSpa...
- PlaceableBunkerSilo
- PlaceableBuyingStation
- PlaceableCartridgePlayer
- PlaceableChargingStation
- PlaceableClearAreas
- PlaceableColorable
- PlaceableDeletedNodes
- PlaceableDoghouse
- PlaceableDynamicallyLoade...
- PlaceableFarmhouse
- PlaceableFence
- PlaceableFoliageAreas
- PlaceableGreenhouse
- PlaceableHighPressureWash...
- PlaceableHotspots
- PlaceableHusbandry
- PlaceableHusbandryAnimals
- PlaceableHusbandryFeeding...
- PlaceableHusbandryFence
- PlaceableHusbandryFood
- PlaceableHusbandryLiquidM...
- PlaceableHusbandryMilk
- PlaceableHusbandryPallets
- PlaceableHusbandryStraw
- PlaceableHusbandryWater
- PlaceableIncomePerHour
- PlaceableIndoorAreas
- PlaceableInfoTrigger
- PlaceableLeveling
- PlaceableLights
- PlaceableManureHeap
- PlaceablePlacement
- PlaceableProductionPoint
- PlaceableSellingStation
- PlaceableSilo
- PlaceableSiloExtension
- PlaceableSolarPanels
- PlaceableTipOcclusionArea...
- PlaceableTrainSystem
- PlaceableTriggerMarkers
- PlaceableVine
- PlaceableWardrobe
- PlaceableWeatherStation
- PlaceableWeighingStation
- PlaceableWindTurbine
- PlaceableWorkshop
- Plow
- PlowPacker
- PowerConsumer
- PowerTakeOffs
- PrecisionFarmingStatistic
- PushHandTool
- RandomlyMovingParts
- ReceivingHopper
- ReverseDriving
- Rideable
- RidgeMarker
- Roller
- Ropes
- RTKStation
- SaltSpreader
- SemiTrailerFront
- Shovel
- SlopeCompensation
- SmartAttach
- SoilSampler
- SowingMachine
- SpeedRotatingParts
- SplineVehicle
- Sprayer
- StonePicker
- StrawBlower
- StumpCutter
- SupportVehicle
- Suspensions
- Tedder
- TensionBeltObject
- TensionBelts
- TestAreas
- TipOccluder
- Trailer
- TreePlanter
- TreeSaplingPallet
- TreeSaw
- TurnOnVehicle
- VariableWorkWidth
- VehicleSettings
- VineCutter
- VineDetector
- VinePrepruner
- Washable
- WaterTrailer
- Wearable
- Weeder
- WeedSpotSpray
- Wheels
- WindBending
- Windrower
- Wipers
- WoodCrusher
- WoodHarvester
- WorkArea
- WorkMode
- WorkParticles
- StateMachine
- Statistics
- Tasks
- Triggers
- Utils
- Vehicles
Engine v1_7_1_0
- AI
- Animation
- Camera
- Entity
- Fillplanes
- general
- General
- I3D
- Input
- Lighting
- Math
- Network
- Node
- NoteNode
- Overlays
- Particle System
- Physics
- Rendering
- Scenegraph
- Shape
- Sound
- Spline
- String
- Terrain Detail
- Text Rendering
- Tire Track
- VoiceChat
- XML
Foundation Reference
PlaceableFence
DescriptionSpecialization for placeablesFunctions
- addPickingNodesForSegment
- addSegment
- addSegmentShapesToUpdate
- collectPickObjects
- createSegment
- deletePanel
- deleteSegment
- doDeletePanel
- fakeRandomValueForPosition
- findRaycastInfo
- generateSegmentPoles
- getAllowExtendingOnly
- getBoundingCheckWidth
- getDestructionMethod
- getGate
- getHasParallelSnapping
- getIsPanelLengthFixed
- getMaxCornerAngle
- getMaxVerticalAngle
- getMaxVerticalAngleAndYForPreview
- getMaxVerticalGateAngle
- getNodesToDeleteForPanel
- getNumSequments
- getPanelLength
- getPoleNear
- getPoleNearOverlapCallback
- getPolePosition
- getPoleShapeForPreview
- getPreviewSegment
- getSegment
- getSegmentLength
- getSnapCheckDistance
- getSnapDistance
- getSupportsParallelSnapping
- getTotalNumberOfPoles
- isPoleInAnySegment
- loadFromXMLFile
- onDelete
- onLoad
- onReadStream
- onUpdate
- onWriteStream
- performNodeDestruction
- prerequisitesPresent
- previewNodeDestructionNodes
- recursivelyAddPickingNodes
- registerEventListeners
- registerEvents
- registerFunctions
- registerOverwrittenFunctions
- registerSavegameXMLPaths
- registerXMLPaths
- removePickingNodesForSegment
- saveToXMLFile
- setOwnerFarmId
- setPreviewSegment
- updateDirtyAreas
- updateSegmentShapes
- updateSegmentUpdateQueue
addPickingNodesForSegment
DescriptionDefinitionaddPickingNodesForSegment()Code
1341 | function PlaceableFence:addPickingNodesForSegment(segment) |
1342 | if segment == self.spec_fence.previewSegment then |
1343 | return |
1344 | end |
1345 | |
1346 | if segment.group ~= nil then |
1347 | local objects = {} |
1348 | self:recursivelyAddPickingNodes(objects, segment.group) |
1349 | |
1350 | for i = 1, #objects do |
1351 | g_currentMission:addNodeObject(objects[i], self) |
1352 | end |
1353 | end |
1354 | |
1355 | -- Reset |
1356 | self.overlayColorNodes = nil |
1357 | end |
addSegment
DescriptionDefinitionaddSegment()Code
584 | function PlaceableFence:addSegment(segment, sync) |
585 | local spec = self.spec_fence |
586 | spec.segments[#spec.segments + 1] = segment |
587 | |
588 | self:generateSegmentPoles(segment, sync) |
589 | end |
addSegmentShapesToUpdate
DescriptionDefinitionaddSegmentShapesToUpdate()Code
1316 | function PlaceableFence:addSegmentShapesToUpdate(segment) |
1317 | local spec = self.spec_fence |
1318 | spec.segmentsToUpdate[#spec.segmentsToUpdate + 1] = segment |
1319 | |
1320 | self:raiseActive() |
1321 | end |
collectPickObjects
DescriptionDefinitioncollectPickObjects()Code
1409 | function PlaceableFence:collectPickObjects(superFunc, node) |
1410 | ---Default picking objects is disabled |
1411 | end |
createSegment
DescriptionDefinitioncreateSegment()Code
569 | function PlaceableFence:createSegment(x1, z1, x2, z2, renderFirst, gateIndex) |
570 | return { |
571 | x1 = x1, |
572 | z1 = z1, |
573 | x2 = x2, |
574 | z2 = z2, |
575 | renderFirst = renderFirst, -- whether to render the first pole |
576 | renderLast = true, |
577 | gateIndex = gateIndex, |
578 | poles = {}, -- list of pole positions |
579 | } |
580 | end |
deletePanel
DescriptionCalled by server or client directly on clickDefinition
deletePanel()Code
730 | function PlaceableFence:deletePanel(node) |
731 | if node == nil or node == 0 or getCollisionMask(node) == 0 then |
732 | return |
733 | end |
734 | |
735 | local spec = self.spec_fence |
736 | local panel, _, segment, _, poleIndex = self:findRaycastInfo(node) |
737 | if panel == nil then |
738 | return nil |
739 | end |
740 | |
741 | local segmentIndex = 1 |
742 | for i = 1, #spec.segments do |
743 | if spec.segments[i] == segment then |
744 | segmentIndex = i |
745 | break |
746 | end |
747 | end |
748 | |
749 | if self.isServer then |
750 | self:doDeletePanel(segment, segmentIndex, poleIndex) |
751 | |
752 | g_server:broadcastEvent(PlaceableFenceRemoveSegmentEvent.new(self, segmentIndex, poleIndex), false) |
753 | else |
754 | -- Set collision mask so we cannot hit it again while it is in transport |
755 | setCollisionMask(node, 0) |
756 | |
757 | -- Request change on server |
758 | g_client:getServerConnection():sendEvent(PlaceableFenceRemoveSegmentEvent.new(self, segmentIndex, poleIndex)) |
759 | end |
760 | |
761 | return true |
762 | end |
deleteSegment
DescriptionDelete a segment with its nodes.Definition
deleteSegment()Code
593 | function PlaceableFence:deleteSegment(segment) |
594 | local spec = self.spec_fence |
595 | if segment.animatedObject ~= nil then |
596 | segment.animatedObject:delete() |
597 | segment.animatedObject = nil |
598 | end |
599 | |
600 | if segment.group ~= nil then |
601 | delete(segment.group) |
602 | segment.group = nil |
603 | end |
604 | |
605 | table.removeElement(spec.segments, segment) |
606 | |
607 | self:updateDirtyAreas(segment) |
608 | end |
doDeletePanel
DescriptionDefinitiondoDeletePanel()Code
766 | function PlaceableFence:doDeletePanel(segment, segmentIndex, poleIndex) |
767 | if segment == nil or poleIndex > #segment.poles then |
768 | return |
769 | end |
770 | |
771 | -- We mark any poles we delete as deleted when they might cause a |
772 | -- non-rendered pole in another segment. |
773 | local deletedPoles = {} |
774 | |
775 | local originalSegment = {x1 = segment.x1, x2 = segment.x2, z1 = segment.x1, z2 = segment.z1} |
776 | |
777 | local segmentSizeChanged = false |
778 | |
779 | -- Start of segment: delete the pole by moving segment start to next pole |
780 | if poleIndex == 1 then |
781 | if segment.renderFirst then |
782 | deletedPoles[#deletedPoles + 1] = segment.poles[1] |
783 | deletedPoles[#deletedPoles + 1] = segment.poles[2] |
784 | end |
785 | |
786 | -- If there is only 1 more pole, delete |
787 | if poleIndex + 2 == #segment.poles - 1 then |
788 | if segment.renderLast then |
789 | deletedPoles[#deletedPoles + 1] = segment.poles[3] |
790 | deletedPoles[#deletedPoles + 1] = segment.poles[4] |
791 | end |
792 | |
793 | self:removePickingNodesForSegment(segment) |
794 | self:deleteSegment(segment) |
795 | else |
796 | segment.x1 = segment.poles[3] |
797 | segment.z1 = segment.poles[4] |
798 | |
799 | segmentSizeChanged = true |
800 | segment.renderFirst = true |
801 | end |
802 | |
803 | -- Next pole is last in line (this is the last panel) |
804 | elseif poleIndex + 2 == #segment.poles - 1 then |
805 | -- Shorten the segment |
806 | |
807 | if segment.renderLast then |
808 | deletedPoles[#deletedPoles + 1] = segment.poles[#segment.poles - 1] |
809 | deletedPoles[#deletedPoles + 1] = segment.poles[#segment.poles] |
810 | end |
811 | |
812 | segment.x2 = segment.poles[#segment.poles - 3] |
813 | segment.z2 = segment.poles[#segment.poles - 2] |
814 | |
815 | segment.renderLast = true |
816 | |
817 | segmentSizeChanged = true |
818 | else |
819 | -- Create second part |
820 | local newSegment = self:createSegment(segment.poles[poleIndex + 2], segment.poles[poleIndex + 3], segment.x2, segment.z2, true, nil) |
821 | newSegment.renderLast = segment.renderLast -- copy |
822 | newSegment.renderFirst = true -- we moved the start to an existing pole, so enable this pole |
823 | |
824 | -- Adjust first part |
825 | segment.x2 = segment.poles[poleIndex] |
826 | segment.z2 = segment.poles[poleIndex + 1] |
827 | segment.renderLast = true |
828 | |
829 | self:addSegment(newSegment) |
830 | segmentSizeChanged = true |
831 | |
832 | -- Note: no poles were removed as only the panel visually is gone. |
833 | end |
834 | |
835 | -- Do not update gates! They cannot resize anyway |
836 | if segmentSizeChanged then |
837 | self:generateSegmentPoles(segment, true) |
838 | end |
839 | |
840 | -- For any pole that has changed, find if it matches the start or end of another segment |
841 | -- If it does mark that segment as needing to render the pole again. Only the first segment we find though |
842 | for i = 1, #deletedPoles, 2 do |
843 | local x, z = deletedPoles[i], deletedPoles[i + 1] |
844 | local neighborSegment, isStart = self:isPoleInAnySegment(x, z, segment) |
845 | |
846 | if neighborSegment ~= nil then |
847 | if isStart then |
848 | neighborSegment.renderFirst = true |
849 | else |
850 | neighborSegment.renderLast = true |
851 | end |
852 | |
853 | self:generateSegmentPoles(neighborSegment, true) |
854 | end |
855 | end |
856 | |
857 | -- We need to update areas where panels are not anymore |
858 | self:updateDirtyAreas(originalSegment) |
859 | |
860 | return true |
861 | end |
fakeRandomValueForPosition
DescriptionGenerate a value [0,1] for a position that is always consistent but highly affected by the positionDefinition
fakeRandomValueForPosition()Code
1009 | function PlaceableFence:fakeRandomValueForPosition(x, y, z, n) |
1010 | local alpha = (x * 0.13 + z * 0.23) % 1 |
1011 | |
1012 | if n == nil then |
1013 | return alpha |
1014 | end |
1015 | |
1016 | -- Integer from 1 to n (inclusive) |
1017 | return math.floor(alpha * (n - 1) + 0.5) + 1 |
1018 | end |
findRaycastInfo
DescriptionDefinitionfindRaycastInfo()Code
865 | function PlaceableFence:findRaycastInfo(node) |
866 | local spec = self.spec_fence |
867 | -- Raycasts hit collisions. Find the visual |
868 | -- This collision could be a pole or a panel! If it is the pole, just return the pole info |
869 | |
870 | local collision = node |
871 | local panel = getParent(collision) |
872 | |
873 | local panelVisuals = getChildAt(panel, 1) |
874 | |
875 | -- Find which segment this is. We'll also find if the node was actually a panel |
876 | local segment = nil |
877 | |
878 | -- Each panel has as parent the pole |
879 | local pole = getParent(panel) |
880 | |
881 | -- Each pole has as parent the segment group |
882 | local sGroup = getParent(pole) |
883 | for si = 1, #spec.segments do |
884 | local seg = spec.segments[si] |
885 | if seg.group == sGroup then |
886 | segment = seg |
887 | break |
888 | |
889 | -- Special case are gates because of doors or pole selection |
890 | elseif seg.group == pole and seg.gateIndex ~= nil then |
891 | segment = seg |
892 | |
893 | -- The pole is actually the group |
894 | sGroup = pole |
895 | |
896 | -- Thus update all references |
897 | pole = panel |
898 | -- First nodes are poles, last one is panel. |
899 | panel = getChildAt(sGroup, getNumOfChildren(sGroup) - 1) |
900 | panelVisuals = getChildAt(panel, 1) -- first is col, second is visuals |
901 | |
902 | break |
903 | end |
904 | end |
905 | |
906 | -- No segment then the requested node was not a panel |
907 | if segment == nil then |
908 | -- Try to find information assuming the node is a pole instead |
909 | collision = node |
910 | --panel = getChildAt(collision, 0) |
911 | pole = getParent(collision) |
912 | |
913 | sGroup = getParent(pole) |
914 | for si = 1, #spec.segments do |
915 | local seg = spec.segments[si] |
916 | if seg.group == sGroup then |
917 | segment = seg |
918 | break |
919 | end |
920 | end |
921 | |
922 | if segment == nil then |
923 | return nil |
924 | end |
925 | |
926 | local poleIndex = getChildIndex(pole) * 2 + 1 |
927 | return nil, nil, segment, pole, poleIndex |
928 | end |
929 | |
930 | local poleIndex |
931 | if segment.gateIndex ~= nil then |
932 | poleIndex = 1 |
933 | else |
934 | poleIndex = getChildIndex(pole) * 2 + 1 |
935 | end |
936 | |
937 | return panel, panelVisuals, segment, pole, poleIndex |
938 | end |
generateSegmentPoles
DescriptionGenerate all poles for the segment In this setup, we add as many as possible whole-size elements. Then for the last, we make the fences equally sized.Definition
generateSegmentPoles()Code
1024 | function PlaceableFence:generateSegmentPoles(segment, sync) |
1025 | local spec = self.spec_fence |
1026 | local totalDistance = MathUtil.getPointPointDistance(segment.x1, segment.z1, segment.x2, segment.z2) |
1027 | local numWholeFences = math.max(math.floor(totalDistance / spec.panelLength) - 1, 0) |
1028 | |
1029 | for i = 1, #segment.poles do |
1030 | segment.poles[i] = nil |
1031 | end |
1032 | |
1033 | if totalDistance < 0.01 then |
1034 | return |
1035 | end |
1036 | |
1037 | -- For gates we only show first and last pole |
1038 | if segment.gateIndex ~= nil then |
1039 | segment.poles[1] = segment.x1 |
1040 | segment.poles[2] = segment.z1 |
1041 | segment.poles[3] = segment.x2 |
1042 | segment.poles[4] = segment.z2 |
1043 | else |
1044 | local nextPole = 1 |
1045 | for j = 0, numWholeFences do |
1046 | local alpha = (spec.panelLength * j) / totalDistance |
1047 | |
1048 | segment.poles[nextPole] = MathUtil.lerp(segment.x1, segment.x2, alpha) |
1049 | segment.poles[nextPole + 1] = MathUtil.lerp(segment.z1, segment.z2, alpha) |
1050 | nextPole = nextPole + 2 |
1051 | end |
1052 | |
1053 | -- Final 2 posts |
1054 | local restDistance = totalDistance - (numWholeFences * spec.panelLength) |
1055 | local numRestFences = restDistance <= spec.panelLength * 1.2 and 1 or 2 |
1056 | local restFenceSize = restDistance / numRestFences |
1057 | |
1058 | for j = 0, numRestFences - 1 do |
1059 | local alpha = ((numWholeFences * spec.panelLength) + (j + 1) * restFenceSize) / totalDistance |
1060 | |
1061 | segment.poles[nextPole] = MathUtil.lerp(segment.x1, segment.x2, alpha) |
1062 | segment.poles[nextPole + 1] = MathUtil.lerp(segment.z1, segment.z2, alpha) |
1063 | nextPole = nextPole + 2 |
1064 | end |
1065 | end |
1066 | |
1067 | if sync then |
1068 | self:removePickingNodesForSegment(segment) |
1069 | self:updateSegmentShapes(segment) |
1070 | self:addPickingNodesForSegment(segment) |
1071 | else |
1072 | self:addSegmentShapesToUpdate(segment) |
1073 | end |
1074 | |
1075 | -- Segment was updated, tell dependent systems |
1076 | if spec.previewSegment ~= segment then |
1077 | self:updateDirtyAreas(segment) |
1078 | end |
1079 | end |
getAllowExtendingOnly
DescriptionDefinitiongetAllowExtendingOnly()Code
706 | function PlaceableFence:getAllowExtendingOnly() |
707 | return self.spec_fence.allowExtendingOnly |
708 | end |
getBoundingCheckWidth
DescriptionDefinitiongetBoundingCheckWidth()Code
684 | function PlaceableFence:getBoundingCheckWidth() |
685 | return self.spec_fence.boundingCheckWidth |
686 | end |
getDestructionMethod
DescriptionDeletion is in pieces so not instantlyDefinition
getDestructionMethod()Code
1391 | function PlaceableFence:getDestructionMethod(superFunc) |
1392 | return Placeable.DESTRUCTION.PER_NODE |
1393 | end |
getGate
DescriptionDefinitiongetGate()Code
649 | function PlaceableFence:getGate(index) |
650 | local spec = self.spec_fence |
651 | return spec.gates[index] |
652 | end |
getHasParallelSnapping
DescriptionDefinitiongetHasParallelSnapping()Code
718 | function PlaceableFence:getHasParallelSnapping() |
719 | return false |
720 | end |
getIsPanelLengthFixed
DescriptionDefinitiongetIsPanelLengthFixed()Code
550 | function PlaceableFence:getIsPanelLengthFixed() |
551 | return self.spec_fence.panelLengthFixed |
552 | end |
getMaxCornerAngle
DescriptionDefinitiongetMaxCornerAngle()Code
712 | function PlaceableFence:getMaxCornerAngle() |
713 | return self.spec_fence.maxCornerAngle |
714 | end |
getMaxVerticalAngle
DescriptionDefinitiongetMaxVerticalAngle()Code
670 | function PlaceableFence:getMaxVerticalAngle() |
671 | local spec = self.spec_fence |
672 | return spec.maxVerticalAngle |
673 | end |
getMaxVerticalAngleAndYForPreview
DescriptionGet the difference in height and the angleDefinition
getMaxVerticalAngleAndYForPreview()Code
504 | function PlaceableFence:getMaxVerticalAngleAndYForPreview() |
505 | local spec = self.spec_fence |
506 | local segment = spec.previewSegment |
507 | local maxAngle = 0 |
508 | local minY, maxY = 1000, -1000 |
509 | |
510 | for i = 1, #segment.poles - 2, 2 do |
511 | local x1 = segment.poles[i] |
512 | local z1 = segment.poles[i+1] |
513 | local x2 = segment.poles[i+2] |
514 | local z2 = segment.poles[i+3] |
515 | |
516 | local horizontalDifference = MathUtil.getPointPointDistance(x1, z1, x2, z2) |
517 | |
518 | local y1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1, 0, z1) |
519 | local y2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2, 0, z2) |
520 | local heightDifference = math.abs(y1 - y2) |
521 | |
522 | minY = math.min(minY, y1, y2) |
523 | maxY = math.max(maxY, y1, y2) |
524 | |
525 | if horizontalDifference > 0 then |
526 | local angle = math.atan(heightDifference / horizontalDifference) |
527 | if angle > maxAngle then |
528 | maxAngle = angle |
529 | end |
530 | end |
531 | end |
532 | |
533 | return maxAngle, minY, maxY |
534 | end |
getMaxVerticalGateAngle
DescriptionDefinitiongetMaxVerticalGateAngle()Code
677 | function PlaceableFence:getMaxVerticalGateAngle() |
678 | local spec = self.spec_fence |
679 | return spec.maxVerticalGateAngle |
680 | end |
getNodesToDeleteForPanel
DescriptionGet the nodes that would be deleted if the given panel node would be Used for previewingDefinition
getNodesToDeleteForPanel()Code
943 | function PlaceableFence:getNodesToDeleteForPanel(node) |
944 | local spec = self.spec_fence |
945 | local panel, panelVisuals, segment, pole, poleIndex = self:findRaycastInfo(node) |
946 | if panel == nil or node == 0 then |
947 | return nil |
948 | end |
949 | |
950 | -- Collect nodes |
951 | local nodes = { |
952 | } |
953 | |
954 | if segment.gateIndex ~= nil then |
955 | -- Add all doors |
956 | local gateInfo = spec.gates[segment.gateIndex] |
957 | for _, door in ipairs(gateInfo.doors) do |
958 | local doorNode = getChildAt(panel, door.node) |
959 | nodes[#nodes + 1] = getChildAt(doorNode, 0) -- visual |
960 | end |
961 | else |
962 | -- Add the panel |
963 | nodes[1] = panelVisuals |
964 | end |
965 | |
966 | local function addPole(poleNode, x, z) |
967 | if self:isPoleInAnySegment(x, z, segment) == nil then |
968 | local visualPole = getChildAt(poleNode, 1) |
969 | if visualPole ~= 0 then |
970 | table.insert(nodes, visualPole) |
971 | end |
972 | end |
973 | end |
974 | |
975 | -- If the pole of the panel is the first pole it would become lonely and should be removed |
976 | if poleIndex == 1 and segment.renderFirst then |
977 | addPole(pole, segment.poles[1], segment.poles[2]) |
978 | end |
979 | |
980 | -- If the pole after the panel-pole is the last pole, also delete it |
981 | if poleIndex + 2 == #segment.poles - 1 and segment.renderLast then |
982 | addPole(getChildAt(segment.group, #segment.poles / 2 - 1), segment.poles[#segment.poles - 1], segment.poles[#segment.poles]) |
983 | end |
984 | |
985 | return nodes |
986 | end |
getNumSequments
DescriptionDefinitiongetNumSequments()Code
663 | function PlaceableFence:getNumSequments() |
664 | local spec = self.spec_fence |
665 | return #spec.segments |
666 | end |
getPanelLength
DescriptionDefinitiongetPanelLength()Code
544 | function PlaceableFence:getPanelLength() |
545 | return self.spec_fence.panelLength |
546 | end |
getPoleNear
DescriptionGet a pole that is near given position or nil if none.Definition
getPoleNear()Return Values
x | ||
z | ||
distance |
399 | function PlaceableFence:getPoleNear(x, y, z, maxDistance) |
400 | local spec = self.spec_fence |
401 | spec.getPoleNearResult = nil |
402 | spec.getPoleNearResultSegment = nil |
403 | spec.getPoleNearResultDistance = math.huge |
404 | spec.getPoleNearResultPosition = {x, y, z} |
405 | |
406 | overlapSphere(x, y, z, maxDistance, "getPoleNearOverlapCallback", self, CollisionFlag.STATIC_OBJECTS, false, true, true, false) |
407 | |
408 | if spec.getPoleNearResult ~= nil then |
409 | local pole_x, pole_y, pole_z = getWorldTranslation(spec.getPoleNearResult) |
410 | return pole_x, pole_y, pole_z, spec.getPoleNearResult, spec.getPoleNearResultSegment |
411 | end |
412 | |
413 | return nil |
414 | end |
getPoleNearOverlapCallback
DescriptionDefinitiongetPoleNearOverlapCallback()Code
418 | function PlaceableFence:getPoleNearOverlapCallback(hitObjectId) |
419 | if hitObjectId == 0 or hitObjectId == g_currentMission.terrainRootNode then |
420 | return |
421 | end |
422 | |
423 | local sGroup = getParent(getParent(hitObjectId)) |
424 | local spec = self.spec_fence |
425 | local x, y, z = getWorldTranslation(hitObjectId) |
426 | local distance = MathUtil.vector3Length(x-spec.getPoleNearResultPosition[1], y-spec.getPoleNearResultPosition[2], z-spec.getPoleNearResultPosition[3]) |
427 | if distance < spec.getPoleNearResultDistance then |
428 | for _, segment in ipairs(spec.segments) do |
429 | if segment.group == sGroup then |
430 | -- A gate has at least 3 parts: trigger, 1 gate, and gate-visuals (hinges) |
431 | -- We need to ignore gates so we attach to poles only |
432 | if getNumOfChildren(hitObjectId) < 3 then |
433 | spec.getPoleNearResult = hitObjectId |
434 | spec.getPoleNearResultSegment = segment |
435 | spec.getPoleNearResultDistance = distance |
436 | end |
437 | end |
438 | end |
439 | end |
440 | |
441 | return true |
442 | end |
getPolePosition
DescriptionGet whether the node is a fence pole and if so, get its positionDefinition
getPolePosition()Code
446 | function PlaceableFence:getPolePosition(node, allowPanel) |
447 | local spec = self.spec_fence |
448 | local collision = node |
449 | local item = getParent(collision) |
450 | |
451 | -- Parent of item can be a segment (for poles), or a pole (for panels) |
452 | local parent = getParent(item) |
453 | |
454 | -- Panels are children of poles |
455 | local parent2 |
456 | if allowPanel and parent ~= getRootNode() then |
457 | parent2 = getParent(parent) |
458 | end |
459 | |
460 | for i = 1, #spec.segments do |
461 | local segment = spec.segments[i] |
462 | |
463 | -- If group matches, the node is part of this fence |
464 | if parent == segment.group then |
465 | -- pole |
466 | local x, y, z = getWorldTranslation(item) |
467 | return x, y, z, segment |
468 | elseif parent2 == segment.group then |
469 | -- panel |
470 | local x, y, z = getWorldTranslation(parent) -- the pole |
471 | return x, y, z, segment |
472 | end |
473 | end |
474 | |
475 | return nil |
476 | end |
getPoleShapeForPreview
DescriptionGet a pole shape for previewing. Caller must deleteDefinition
getPoleShapeForPreview()Code
480 | function PlaceableFence:getPoleShapeForPreview() |
481 | local spec = self.spec_fence |
482 | if spec.hasInvisiblePoles then |
483 | return nil |
484 | end |
485 | |
486 | if #spec.poles > 0 then |
487 | if getNumOfChildren(spec.poles[1]) == 0 then |
488 | return nil |
489 | end |
490 | |
491 | local pole = clone(spec.poles[1], false, false, false) |
492 | if pole == 0 then |
493 | return nil |
494 | end |
495 | |
496 | return pole |
497 | else |
498 | return nil |
499 | end |
500 | end |
getPreviewSegment
DescriptionDefinitiongetPreviewSegment()Code
642 | function PlaceableFence:getPreviewSegment() |
643 | local spec = self.spec_fence |
644 | return spec.previewSegment |
645 | end |
getSegment
DescriptionDefinitiongetSegment()Code
656 | function PlaceableFence:getSegment(index) |
657 | local spec = self.spec_fence |
658 | return spec.segments[index] |
659 | end |
getSegmentLength
DescriptionGet the length of a segmentDefinition
getSegmentLength()Code
538 | function PlaceableFence:getSegmentLength(segment) |
539 | return MathUtil.getPointPointDistance(segment.x1, segment.z1, segment.x2, segment.z2) |
540 | end |
getSnapCheckDistance
DescriptionDefinitiongetSnapCheckDistance()Code
700 | function PlaceableFence:getSnapCheckDistance() |
701 | return self.spec_fence.snapCheckDistance |
702 | end |
getSnapDistance
DescriptionDefinitiongetSnapDistance()Code
690 | function PlaceableFence:getSnapDistance() |
691 | return self.spec_fence.snapDistance |
692 | end |
getSupportsParallelSnapping
DescriptionDefinitiongetSupportsParallelSnapping()Code
724 | function PlaceableFence:getSupportsParallelSnapping() |
725 | return self.spec_fence.supportsParallelSnapping |
726 | end |
getTotalNumberOfPoles
DescriptionGet the total number of poles for this fenceDefinition
getTotalNumberOfPoles()Code
556 | function PlaceableFence:getTotalNumberOfPoles() |
557 | local spec = self.spec_fence |
558 | local total = 0 |
559 | |
560 | for s = 1, #spec.segments do |
561 | total = total + spec.segments[s].poles / 2 |
562 | end |
563 | |
564 | return total |
565 | end |
isPoleInAnySegment
DescriptionGet whether given exact pole position is in any segment. Only checks start and end of segmentDefinition
isPoleInAnySegment()Code
990 | function PlaceableFence:isPoleInAnySegment(x, z, ignoreSegment) |
991 | local spec = self.spec_fence |
992 | for i = 1, #spec.segments do |
993 | local segment = spec.segments[i] |
994 | |
995 | if segment ~= ignoreSegment then |
996 | if math.abs(segment.x1 - x) < PlaceableFence.EPSILON and math.abs(segment.z1 - z) < PlaceableFence.EPSILON then |
997 | return segment, true, false |
998 | elseif math.abs(segment.x2 - x) < PlaceableFence.EPSILON and math.abs(segment.z2 - z) < PlaceableFence.EPSILON then |
999 | return segment, false, true |
1000 | end |
1001 | end |
1002 | end |
1003 | |
1004 | return nil |
1005 | end |
loadFromXMLFile
DescriptionDefinitionloadFromXMLFile()Code
326 | function PlaceableFence:loadFromXMLFile(xmlFile, key) |
327 | local spec = self.spec_fence |
328 | |
329 | |
330 | xmlFile:iterate(key .. ".segments.segment", function(index, segmentKey) |
331 | local x1, z1 = xmlFile:getValue(segmentKey .. "#start") |
332 | local x2, z2 = xmlFile:getValue(segmentKey .. "#end") |
333 | |
334 | if x1 ~= nil and z1 ~= nil and x2 ~= nil and z2 ~= nil then |
335 | local segment = { |
336 | x1 = x1, |
337 | z1 = z1, |
338 | x2 = x2, |
339 | z2 = z2, |
340 | renderFirst = xmlFile:getValue(segmentKey .. "#first", true), |
341 | renderLast = xmlFile:getValue(segmentKey .. "#last", true), |
342 | gateIndex = xmlFile:getValue(segmentKey .. "#gateIndex"), |
343 | poles = {}, -- generated |
344 | segmentKey = segmentKey |
345 | } |
346 | |
347 | table.insert(spec.segments, segment) |
348 | else |
349 | Logging.xmlError(xmlFile, "Invalid segment position for '%s'. Ignoring segment!", segmentKey) |
350 | end |
351 | end) |
352 | |
353 | -- Rebuild the fence |
354 | for i = 1, #spec.segments do |
355 | local segment = spec.segments[i] |
356 | |
357 | self:generateSegmentPoles(segment, true) |
358 | |
359 | if segment.gateIndex ~= nil and segment.animatedObject ~= nil then |
360 | segment.animatedObject:loadFromXMLFile(xmlFile, segment.segmentKey .. ".animatedObject") |
361 | end |
362 | |
363 | segment.segmentKey = nil |
364 | end |
365 | end |
onDelete
DescriptionDefinitiononDelete()Code
222 | function PlaceableFence:onDelete() |
223 | local spec = self.spec_fence |
224 | if spec.animatedObjects ~= nil then |
225 | for _, animatedObject in ipairs(spec.animatedObjects) do |
226 | animatedObject:delete() |
227 | end |
228 | end |
229 | end |
onLoad
DescriptionCalled on loadingDefinition
onLoad(table savegame)Arguments
table | savegame | savegame |
148 | function PlaceableFence:onLoad(savegame) |
149 | local spec = self.spec_fence |
150 | local xmlFile = self.xmlFile |
151 | |
152 | spec.pickObjects = {} |
153 | spec.segments = {} |
154 | spec.segmentsToUpdate = {} |
155 | spec.animatedObjects = {} |
156 | spec.previewSegment = nil |
157 | spec.panelLength = xmlFile:getValue("placeable.fence.panels#length") |
158 | spec.panelLengthFixed = xmlFile:getValue("placeable.fence.panels#fixedLength") |
159 | spec.maxVerticalAngle = xmlFile:getValue("placeable.fence#maxVerticalAngle", 35) |
160 | spec.maxVerticalGateAngle = xmlFile:getValue("placeable.fence#maxVerticalGateAngle", 5) |
161 | spec.hasInvisiblePoles = xmlFile:getValue("placeable.fence#hasInvisiblePoles", false) |
162 | spec.supportsParallelSnapping = xmlFile:getValue("placeable.fence#supportsParallelSnapping", false) |
163 | |
164 | spec.boundingCheckWidth = xmlFile:getValue("placeable.fence#boundingCheckWidth", 0.25) |
165 | spec.snapDistance = xmlFile:getValue("placeable.fence#snapDistance", nil) |
166 | spec.snapAngle = xmlFile:getValue("placeable.fence#snapAngle", nil) |
167 | spec.snapCheckDistance = xmlFile:getValue("placeable.fence#snapCheckDistance", 0.25) |
168 | spec.allowExtendingOnly = xmlFile:getValue("placeable.fence#extendingOnly", false) |
169 | spec.maxCornerAngle = xmlFile:getValue("placeable.fence#maxCornerAngle", 180) |
170 | |
171 | spec.poles = {} |
172 | local polesNode = xmlFile:getValue("placeable.fence.poles#node", nil, self.components, self.i3dMappings) |
173 | if polesNode ~= nil then |
174 | for i = 1, getNumOfChildren(polesNode) do |
175 | spec.poles[i] = getChildAt(polesNode, i - 1) |
176 | end |
177 | end |
178 | |
179 | spec.panels = {} |
180 | local panelsNode = xmlFile:getValue("placeable.fence.panels#node", nil, self.components, self.i3dMappings) |
181 | if panelsNode ~= nil then |
182 | for i = 1, getNumOfChildren(panelsNode) do |
183 | spec.panels[i] = getChildAt(panelsNode, i - 1) |
184 | end |
185 | end |
186 | |
187 | spec.gates = {} |
188 | xmlFile:iterate("placeable.fence.gate", function(_, key) |
189 | local node = xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings) |
190 | if node ~= nil then |
191 | local doors = {} |
192 | xmlFile:iterate(key .. ".door", function(_, doorKey) |
193 | local doorNode = xmlFile:getValue(doorKey .. "#node") |
194 | if doorNode ~= nil then |
195 | table.insert(doors, { |
196 | node = doorNode, |
197 | rotation = xmlFile:getValue(doorKey .. "#openRotation", nil, true), |
198 | translation = xmlFile:getValue(doorKey .. "#openTranslation", nil, true), |
199 | }) |
200 | else |
201 | Logging.xmlWarning(xmlFile, "Door node does not exist at %s", doorKey) |
202 | end |
203 | end) |
204 | |
205 | table.insert(spec.gates, { |
206 | node = node, |
207 | length = xmlFile:getValue(key .. "#length", 1), |
208 | triggerNode = xmlFile:getValue(key .. "#triggerNode"), |
209 | openText = xmlFile:getValue(key .. "#openText", "action_openGate"), |
210 | closeText = xmlFile:getValue(key .. "#closeText", "action_closeGate"), |
211 | animationDuration = xmlFile:getValue(key .. "#openDuration", 3), |
212 | doors = doors, |
213 | }) |
214 | else |
215 | Logging.xmlWarning(xmlFile, "Gate node does not exist at %s", key) |
216 | end |
217 | end) |
218 | end |
onReadStream
DescriptionDefinitiononReadStream()Code
233 | function PlaceableFence:onReadStream(streamId, connection) |
234 | local spec = self.spec_fence |
235 | |
236 | local numSegments = streamReadInt32(streamId) |
237 | for i = 1, numSegments do |
238 | local segment = {} |
239 | segment.x1 = streamReadFloat32(streamId) |
240 | segment.z1 = streamReadFloat32(streamId) |
241 | segment.x2 = streamReadFloat32(streamId) |
242 | segment.z2 = streamReadFloat32(streamId) |
243 | |
244 | segment.gateIndex = streamReadUInt8(streamId) |
245 | if segment.gateIndex == 0 then |
246 | segment.gateIndex = nil |
247 | end |
248 | |
249 | segment.renderFirst = streamReadBool(streamId) |
250 | segment.renderLast = streamReadBool(streamId) |
251 | |
252 | segment.poles = {} -- generated |
253 | |
254 | table.insert(spec.segments, segment) |
255 | end |
256 | |
257 | -- Rebuild the fence |
258 | for i = 1, numSegments do |
259 | local segment = spec.segments[i] |
260 | |
261 | self:generateSegmentPoles(segment, true) |
262 | |
263 | if segment.gateIndex ~= nil and segment.animatedObject ~= nil then |
264 | local animatedObject = segment.animatedObject |
265 | |
266 | local animatedObjectId = NetworkUtil.readNodeObjectId(streamId) |
267 | animatedObject:readStream(streamId, connection) |
268 | g_client:finishRegisterObject(animatedObject, animatedObjectId) |
269 | end |
270 | end |
271 | end |
onUpdate
DescriptionDefinitiononUpdate()Code
308 | function PlaceableFence:onUpdate(dt) |
309 | self:updateSegmentUpdateQueue() |
310 | end |
onWriteStream
DescriptionDefinitiononWriteStream()Code
275 | function PlaceableFence:onWriteStream(streamId, connection) |
276 | local spec = self.spec_fence |
277 | |
278 | local numSegments = #spec.segments |
279 | streamWriteInt32(streamId, numSegments) |
280 | for i = 1, numSegments do |
281 | local segment = spec.segments[i] |
282 | |
283 | streamWriteFloat32(streamId, segment.x1) |
284 | streamWriteFloat32(streamId, segment.z1) |
285 | streamWriteFloat32(streamId, segment.x2) |
286 | streamWriteFloat32(streamId, segment.z2) |
287 | |
288 | streamWriteUInt8(streamId, segment.gateIndex or 0) |
289 | streamWriteBool(streamId, segment.renderFirst) |
290 | streamWriteBool(streamId, segment.renderLast) |
291 | end |
292 | |
293 | for i = 1, numSegments do |
294 | local segment = spec.segments[i] |
295 | |
296 | if segment.gateIndex ~= nil and segment.animatedObject ~= nil then |
297 | local animatedObject = segment.animatedObject |
298 | |
299 | NetworkUtil.writeNodeObjectId(streamId, NetworkUtil.getObjectId(animatedObject)) |
300 | animatedObject:writeStream(streamId, connection) |
301 | g_server:registerObjectInStream(connection, animatedObject) |
302 | end |
303 | end |
304 | end |
performNodeDestruction
DescriptionDefinitionperformNodeDestruction()Code
1403 | function PlaceableFence:performNodeDestruction(superFunc, node) |
1404 | return self:deletePanel(node) |
1405 | end |
prerequisitesPresent
DescriptionChecks if all prerequisite specializations are loadedDefinition
prerequisitesPresent(table specializations)Arguments
table | specializations | specializations |
boolean | hasPrerequisite | true if all prerequisite specializations are loaded |
23 | function PlaceableFence.prerequisitesPresent(specializations) |
24 | return true |
25 | end |
previewNodeDestructionNodes
DescriptionDefinitionpreviewNodeDestructionNodes()Code
1397 | function PlaceableFence:previewNodeDestructionNodes(superFunc, node) |
1398 | return self:getNodesToDeleteForPanel(node) |
1399 | end |
recursivelyAddPickingNodes
DescriptionDefinitionrecursivelyAddPickingNodes()Code
1378 | function PlaceableFence:recursivelyAddPickingNodes(objects, node) |
1379 | if getRigidBodyType(node) ~= RigidBodyType.NONE then |
1380 | table.insert(objects, node) |
1381 | end |
1382 | |
1383 | local numChildren = getNumOfChildren(node) |
1384 | for i=1, numChildren do |
1385 | self:recursivelyAddPickingNodes(objects, getChildAt(node, i-1)) |
1386 | end |
1387 | end |
registerEventListeners
DescriptionDefinitionregisterEventListeners()Code
93 | function PlaceableFence.registerEventListeners(placeableType) |
94 | SpecializationUtil.registerEventListener(placeableType, "onLoad", PlaceableFence) |
95 | SpecializationUtil.registerEventListener(placeableType, "onDelete", PlaceableFence) |
96 | SpecializationUtil.registerEventListener(placeableType, "onReadStream", PlaceableFence) |
97 | SpecializationUtil.registerEventListener(placeableType, "onWriteStream", PlaceableFence) |
98 | SpecializationUtil.registerEventListener(placeableType, "onUpdate", PlaceableFence) |
99 | end |
registerEvents
DescriptionDefinitionregisterEvents()Code
29 | function PlaceableFence.registerEvents(placeableType) |
30 | SpecializationUtil.registerEvent(placeableType, "onCreateSegmentPanel") |
31 | end |
registerFunctions
DescriptionDefinitionregisterFunctions()Code
35 | function PlaceableFence.registerFunctions(placeableType) |
36 | SpecializationUtil.registerFunction(placeableType, "addSegment", PlaceableFence.addSegment) |
37 | SpecializationUtil.registerFunction(placeableType, "addSegmentShapesToUpdate", PlaceableFence.addSegmentShapesToUpdate) |
38 | SpecializationUtil.registerFunction(placeableType, "createSegment", PlaceableFence.createSegment) |
39 | SpecializationUtil.registerFunction(placeableType, "deletePanel", PlaceableFence.deletePanel) |
40 | SpecializationUtil.registerFunction(placeableType, "deleteSegment", PlaceableFence.deleteSegment) |
41 | SpecializationUtil.registerFunction(placeableType, "doDeletePanel", PlaceableFence.doDeletePanel) |
42 | SpecializationUtil.registerFunction(placeableType, "fakeRandomValueForPosition", PlaceableFence.fakeRandomValueForPosition) |
43 | SpecializationUtil.registerFunction(placeableType, "findRaycastInfo", PlaceableFence.findRaycastInfo) |
44 | SpecializationUtil.registerFunction(placeableType, "generateSegmentPoles", PlaceableFence.generateSegmentPoles) |
45 | SpecializationUtil.registerFunction(placeableType, "getGate", PlaceableFence.getGate) |
46 | SpecializationUtil.registerFunction(placeableType, "getMaxVerticalAngle", PlaceableFence.getMaxVerticalAngle) |
47 | SpecializationUtil.registerFunction(placeableType, "getMaxVerticalAngleAndYForPreview", PlaceableFence.getMaxVerticalAngleAndYForPreview) |
48 | SpecializationUtil.registerFunction(placeableType, "getMaxVerticalGateAngle", PlaceableFence.getMaxVerticalGateAngle) |
49 | SpecializationUtil.registerFunction(placeableType, "getNodesToDeleteForPanel", PlaceableFence.getNodesToDeleteForPanel) |
50 | SpecializationUtil.registerFunction(placeableType, "getNumSequments", PlaceableFence.getNumSequments) |
51 | SpecializationUtil.registerFunction(placeableType, "getPanelLength", PlaceableFence.getPanelLength) |
52 | SpecializationUtil.registerFunction(placeableType, "getIsPanelLengthFixed", PlaceableFence.getIsPanelLengthFixed) |
53 | SpecializationUtil.registerFunction(placeableType, "getPoleNear", PlaceableFence.getPoleNear) |
54 | SpecializationUtil.registerFunction(placeableType, "getPoleNearOverlapCallback", PlaceableFence.getPoleNearOverlapCallback) |
55 | SpecializationUtil.registerFunction(placeableType, "getPolePosition", PlaceableFence.getPolePosition) |
56 | SpecializationUtil.registerFunction(placeableType, "getPoleShapeForPreview", PlaceableFence.getPoleShapeForPreview) |
57 | SpecializationUtil.registerFunction(placeableType, "getPreviewSegment", PlaceableFence.getPreviewSegment) |
58 | SpecializationUtil.registerFunction(placeableType, "getSegment", PlaceableFence.getSegment) |
59 | SpecializationUtil.registerFunction(placeableType, "getSegmentLength", PlaceableFence.getSegmentLength) |
60 | SpecializationUtil.registerFunction(placeableType, "getTotalNumberOfPoles", PlaceableFence.getTotalNumberOfPoles) |
61 | SpecializationUtil.registerFunction(placeableType, "isPoleInAnySegment", PlaceableFence.isPoleInAnySegment) |
62 | SpecializationUtil.registerFunction(placeableType, "recursivelyAddPickingNodes", PlaceableFence.recursivelyAddPickingNodes) |
63 | SpecializationUtil.registerFunction(placeableType, "addPickingNodesForSegment", PlaceableFence.addPickingNodesForSegment) |
64 | SpecializationUtil.registerFunction(placeableType, "removePickingNodesForSegment", PlaceableFence.removePickingNodesForSegment) |
65 | SpecializationUtil.registerFunction(placeableType, "setPreviewSegment", PlaceableFence.setPreviewSegment) |
66 | SpecializationUtil.registerFunction(placeableType, "updatePanelVisuals", PlaceableFence.updatePanelVisuals) |
67 | SpecializationUtil.registerFunction(placeableType, "updateSegmentShapes", PlaceableFence.updateSegmentShapes) |
68 | SpecializationUtil.registerFunction(placeableType, "updateSegmentUpdateQueue", PlaceableFence.updateSegmentUpdateQueue) |
69 | SpecializationUtil.registerFunction(placeableType, "updateDirtyAreas", PlaceableFence.updateDirtyAreas) |
70 | SpecializationUtil.registerFunction(placeableType, "getSupportsParallelSnapping", PlaceableFence.getSupportsParallelSnapping) |
71 | |
72 | SpecializationUtil.registerFunction(placeableType, "getBoundingCheckWidth", PlaceableFence.getBoundingCheckWidth) |
73 | SpecializationUtil.registerFunction(placeableType, "getSnapDistance", PlaceableFence.getSnapDistance) |
74 | SpecializationUtil.registerFunction(placeableType, "getSnapAngle", PlaceableFence.getSnapAngle) |
75 | SpecializationUtil.registerFunction(placeableType, "getSnapCheckDistance", PlaceableFence.getSnapCheckDistance) |
76 | SpecializationUtil.registerFunction(placeableType, "getAllowExtendingOnly", PlaceableFence.getAllowExtendingOnly) |
77 | SpecializationUtil.registerFunction(placeableType, "getMaxCornerAngle", PlaceableFence.getMaxCornerAngle) |
78 | SpecializationUtil.registerFunction(placeableType, "getHasParallelSnapping", PlaceableFence.getHasParallelSnapping) |
79 | end |
registerOverwrittenFunctions
DescriptionDefinitionregisterOverwrittenFunctions()Code
83 | function PlaceableFence.registerOverwrittenFunctions(placeableType) |
84 | SpecializationUtil.registerOverwrittenFunction(placeableType, "collectPickObjects", PlaceableFence.collectPickObjects) |
85 | SpecializationUtil.registerOverwrittenFunction(placeableType, "getDestructionMethod", PlaceableFence.getDestructionMethod) |
86 | SpecializationUtil.registerOverwrittenFunction(placeableType, "performNodeDestruction", PlaceableFence.performNodeDestruction) |
87 | SpecializationUtil.registerOverwrittenFunction(placeableType, "previewNodeDestructionNodes", PlaceableFence.previewNodeDestructionNodes) |
88 | SpecializationUtil.registerOverwrittenFunction(placeableType, "setOwnerFarmId", PlaceableFence.setOwnerFarmId) |
89 | end |
registerSavegameXMLPaths
DescriptionDefinitionregisterSavegameXMLPaths()Code
134 | function PlaceableFence.registerSavegameXMLPaths(schema, basePath) |
135 | schema:setXMLSpecializationType("Fence") |
136 | schema:register(XMLValueType.VECTOR_2, basePath .. ".segments.segment(?)#start", "Segment start position") |
137 | schema:register(XMLValueType.VECTOR_2, basePath .. ".segments.segment(?)#end", "Segment end position") |
138 | schema:register(XMLValueType.BOOL, basePath .. ".segments.segment(?)#first", "Segment has first pole visible", true) |
139 | schema:register(XMLValueType.BOOL, basePath .. ".segments.segment(?)#last", "Segment has last pole visible", true) |
140 | schema:register(XMLValueType.INT, basePath .. ".segments.segment(?)#gateIndex", "Gate index") |
141 | AnimatedObject.registerSavegameXMLPaths(schema, basePath .. ".segments.segment(?).animatedObject") |
142 | schema:setXMLSpecializationType() |
143 | end |
registerXMLPaths
DescriptionDefinitionregisterXMLPaths()Code
103 | function PlaceableFence.registerXMLPaths(schema, basePath) |
104 | schema:setXMLSpecializationType("Fence") |
105 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".fence.poles#node", "Group of pole variants") |
106 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".fence.panels#node", "Group of panel variants") |
107 | schema:register(XMLValueType.FLOAT, basePath .. ".fence.panels#length", "Length of the panels", 1) |
108 | schema:register(XMLValueType.BOOL, basePath .. ".fence.panels#fixedLength", "Panel length is fixed", false) |
109 | schema:register(XMLValueType.ANGLE, basePath .. ".fence#maxVerticalAngle", "Maximum angle for vertical offset") |
110 | schema:register(XMLValueType.ANGLE, basePath .. ".fence#maxVerticalGateAngle", "Maximum angle for vertical offset with gates") |
111 | schema:register(XMLValueType.FLOAT, basePath .. ".fence#boundingCheckWidth", "With of the bounding box used to check collision", 0.25) |
112 | schema:register(XMLValueType.FLOAT, basePath .. ".fence#snapDistance", "Snap distance", nil) |
113 | schema:register(XMLValueType.INT, basePath .. ".fence#snapAngle", "Snap angle in degrees", nil) |
114 | schema:register(XMLValueType.FLOAT, basePath .. ".fence#snapCheckDistance", "Snap distance", nil) |
115 | schema:register(XMLValueType.BOOL, basePath .. ".fence#extendingOnly", "Whether to only allow extending a segment and no attaching to the center", false) |
116 | schema:register(XMLValueType.ANGLE, basePath .. ".fence#maxCornerAngle", "Maximum angle between two connected segments", 180) |
117 | schema:register(XMLValueType.BOOL, basePath .. ".fence#supportsParallelSnapping", "Whether parallel snapping is an option", false) |
118 | schema:register(XMLValueType.BOOL, basePath .. ".fence#hasInvisiblePoles", "Poles are not visible so another display method is used", false) |
119 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".fence.gate(?)#node", "Gate node") |
120 | schema:register(XMLValueType.FLOAT, basePath .. ".fence.gate(?)#length", "Length of the gate from pole to pole", 1) |
121 | schema:register(XMLValueType.INT, basePath .. ".fence.gate(?)#triggerNode", "Gate trigger node index from gate node") |
122 | schema:register(XMLValueType.STRING, basePath .. ".fence.gate(?)#openText", "Action open text") |
123 | schema:register(XMLValueType.STRING, basePath .. ".fence.gate(?)#closeText", "Action close text") |
124 | schema:register(XMLValueType.FLOAT, basePath .. ".fence.gate(?)#openDuration", "Duration of animation in seconds") |
125 | schema:register(XMLValueType.INT, basePath .. ".fence.gate(?).door(?)#node", "Node of the door") |
126 | schema:register(XMLValueType.VECTOR_ROT, basePath .. ".fence.gate(?).door(?)#openRotation", "Rotation of the node when fully open") |
127 | schema:register(XMLValueType.VECTOR_TRANS, basePath .. ".fence.gate(?).door(?)#openTranslation", "Translation of the node when fully open") |
128 | AnimatedObjectBuilder.registerXMLPaths(schema, basePath .. ".fence.gate(?)") |
129 | schema:setXMLSpecializationType() |
130 | end |
removePickingNodesForSegment
DescriptionDefinitionremovePickingNodesForSegment()Code
1361 | function PlaceableFence:removePickingNodesForSegment(segment) |
1362 | if segment == self.spec_fence.previewSegment then |
1363 | return |
1364 | end |
1365 | |
1366 | if segment.group ~= nil then |
1367 | local objects = {} |
1368 | self:recursivelyAddPickingNodes(objects, segment.group) |
1369 | |
1370 | for i = 1, #objects do |
1371 | g_currentMission:removeNodeObject(objects[i]) |
1372 | end |
1373 | end |
1374 | end |
saveToXMLFile
DescriptionDefinitionsaveToXMLFile()Code
370 | function PlaceableFence:saveToXMLFile(xmlFile, key, usedModNames) |
371 | local spec = self.spec_fence |
372 | |
373 | xmlFile:setTable(key .. ".segments.segment", spec.segments, function(path, segment, _) |
374 | xmlFile:setValue(path .. "#start", segment.x1, segment.z1) |
375 | xmlFile:setValue(path .. "#end", segment.x2, segment.z2) |
376 | |
377 | if segment.gateIndex ~= nil then |
378 | xmlFile:setValue(path .. "#gateIndex", segment.gateIndex) |
379 | if segment.animatedObject ~= nil then |
380 | segment.animatedObject:saveToXMLFile(xmlFile, path .. ".animatedObject", usedModNames) |
381 | end |
382 | end |
383 | |
384 | -- No need to save default value |
385 | if not segment.renderFirst then |
386 | xmlFile:setValue(path .. "#first", false) |
387 | end |
388 | if not segment.renderLast then |
389 | xmlFile:setValue(path .. "#last", false) |
390 | end |
391 | end) |
392 | end |
setOwnerFarmId
DescriptionDefinitionsetOwnerFarmId()Code
314 | function PlaceableFence:setOwnerFarmId(superFunc, ownerFarmId, noEventSend) |
315 | local spec = self.spec_fence |
316 | |
317 | superFunc(self, ownerFarmId, noEventSend) |
318 | |
319 | for _, animatedObject in ipairs(spec.animatedObjects) do |
320 | animatedObject:setOwnerFarmId(ownerFarmId, true) |
321 | end |
322 | end |
setPreviewSegment
DescriptionDefinitionsetPreviewSegment()Code
624 | function PlaceableFence:setPreviewSegment(segment) |
625 | local spec = self.spec_fence |
626 | |
627 | -- Delete old preview nodes |
628 | if spec.previewSegment ~= nil and spec.previewSegment.group ~= nil and segment ~= spec.previewSegment then |
629 | delete(spec.previewSegment.group) |
630 | spec.previewSegment.group = nil |
631 | end |
632 | |
633 | spec.previewSegment = segment |
634 | |
635 | if segment ~= nil then |
636 | self:generateSegmentPoles(segment, false) |
637 | end |
638 | end |
updateDirtyAreas
DescriptionDefinitionupdateDirtyAreas()Code
612 | function PlaceableFence:updateDirtyAreas(segment) |
613 | local minX = math.min(segment.x1, segment.x2) |
614 | local maxX = math.max(segment.x1, segment.x2) |
615 | local minZ = math.min(segment.z1, segment.z2) |
616 | local maxZ = math.max(segment.z1, segment.z2) |
617 | |
618 | g_densityMapHeightManager:setCollisionMapAreaDirty(minX, minZ, maxX, maxZ, true) |
619 | g_currentMission.aiSystem:setAreaDirty(minX, maxX, minZ, maxZ) |
620 | end |
updateSegmentShapes
DescriptionDefinitionupdateSegmentShapes()Code
1083 | function PlaceableFence:updateSegmentShapes(segment) |
1084 | local spec = self.spec_fence |
1085 | local isPreviewSegment = segment == spec.previewSegment |
1086 | local enablePhysics = not isPreviewSegment |
1087 | |
1088 | -- Delete AO before deleting nodes so trigger is handled correctly and no double-delete occurs |
1089 | local gateTime |
1090 | if segment.animatedObject ~= nil then |
1091 | gateTime = segment.animatedObject.animation.time |
1092 | segment.animatedObject:delete() |
1093 | segment.animatedObject = nil |
1094 | end |
1095 | |
1096 | -- Create a group for all segment nodes so we can delete them at once when re-generating |
1097 | if segment.group ~= nil then |
1098 | delete(segment.group) |
1099 | end |
1100 | segment.group = createTransformGroup("fence_segment") |
1101 | link(self.rootNode, segment.group) |
1102 | |
1103 | -- Create a pole for every xz pair. |
1104 | for i = 1, #segment.poles, 2 do |
1105 | local x, z = segment.poles[i], segment.poles[i+1] |
1106 | local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) |
1107 | |
1108 | -- If there is a first pole, use the shape. Otherwise use an empty TG so we can still rotate |
1109 | local pole |
1110 | local poleIsFake = false |
1111 | if #spec.poles > 0 and (i > 1 or segment.renderFirst) and (i < #segment.poles - 2 or segment.renderLast) then |
1112 | local poleIndex = self:fakeRandomValueForPosition(x, y, z, #spec.poles) |
1113 | pole = clone(spec.poles[poleIndex], false, false, false) |
1114 | else |
1115 | pole = createTransformGroup("fence_firstPole") |
1116 | poleIsFake = true |
1117 | end |
1118 | link(segment.group, pole) |
1119 | |
1120 | -- Set position |
1121 | setWorldTranslation(pole, x, y, z) |
1122 | |
1123 | if segment.gateIndex ~= nil then |
1124 | -- Poles for gates: always rotate to the other side |
1125 | local prevX, prevZ = segment.poles[(i+2) % 4], segment.poles[(i+2) % 4 + 1] |
1126 | local dx, dz = x - prevX, z - prevZ |
1127 | local rotY = math.atan2(dx, dz) + math.pi |
1128 | |
1129 | setWorldRotation(pole, 0, rotY, 0) |
1130 | |
1131 | if enablePhysics and not poleIsFake then |
1132 | addToPhysics(getChildAt(pole, 0)) |
1133 | end |
1134 | elseif i < #segment.poles - 2 then |
1135 | -- Next pole exists: connect it with a panel and rotate this one properly, but not if these are gate poles |
1136 | -- Find position of the next pole so we can match it visually |
1137 | local nextX, nextZ = segment.poles[i+2], segment.poles[i+3] |
1138 | local nextY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, nextX, 0, nextZ) |
1139 | |
1140 | local dx, dy, dz = x - nextX, y - nextY, z - nextZ |
1141 | local rotY = math.atan2(dx, dz) + math.pi |
1142 | |
1143 | -- Pole rotates into direction of fence |
1144 | setWorldRotation(pole, 0, rotY, 0) |
1145 | |
1146 | -- Find a panel and connect |
1147 | local panelIndex = self:fakeRandomValueForPosition(x, y, z, #spec.panels) |
1148 | local panel = clone(spec.panels[panelIndex], false, false, false) |
1149 | link(pole, panel) |
1150 | |
1151 | -- Scale fence to fit next pole. This is length on XZ plane only, as shader will transform along Y |
1152 | local fenceLength = MathUtil.getPointPointDistance(x, z, nextX, nextZ) |
1153 | |
1154 | -- Adjust panel distortion to match height of next fence |
1155 | self:updatePanelVisuals(panel, dy, segment, i, fenceLength) |
1156 | |
1157 | -- Adjust collision by rotating it to match terrain inclination |
1158 | local col = getChildAt(panel, 0) |
1159 | |
1160 | local xDir, yDir, zDir = 0, -dy, fenceLength |
1161 | xDir, yDir, zDir = MathUtil.vector3Normalize(xDir, yDir, zDir) |
1162 | local length = math.sqrt(dx*dx + dy*dy + dz*dz) |
1163 | |
1164 | local offset = (length - fenceLength) * 0.5 |
1165 | local colX, colY, colZ = getTranslation(col) |
1166 | colX = colX + xDir * offset |
1167 | colY = colY + yDir * offset |
1168 | colZ = colZ + zDir * offset |
1169 | |
1170 | setDirection(col, xDir, yDir, zDir, 0, 1, 0) |
1171 | setTranslation(col, colX, colY, colZ) |
1172 | |
1173 | if enablePhysics then |
1174 | addToPhysics(col) |
1175 | end |
1176 | |
1177 | SpecializationUtil.raiseEvent(self, "onCreateSegmentPanel", isPreviewSegment, segment, panel, i, dy) |
1178 | |
1179 | if enablePhysics and not poleIsFake then |
1180 | addToPhysics(getChildAt(pole, 0)) |
1181 | end |
1182 | elseif segment.renderLast then |
1183 | -- End of the segment. We could look up the next segment but that is expensive |
1184 | -- Instead, just align to the previous pole. If no previous pole, it is a single |
1185 | -- pole and any rotation is fine so we can leave it. |
1186 | if i > 2 then |
1187 | local prevX, prevZ = segment.poles[i-2], segment.poles[i-1] |
1188 | local dx, dz = x - prevX, z - prevZ |
1189 | local rotY = math.atan2(dx, dz) + math.pi |
1190 | |
1191 | setWorldRotation(pole, 0, rotY, 0) |
1192 | |
1193 | if enablePhysics and not poleIsFake then |
1194 | addToPhysics(getChildAt(pole, 0)) |
1195 | end |
1196 | end |
1197 | end |
1198 | end |
1199 | |
1200 | if segment.gateIndex ~= nil then |
1201 | local gateInfo = spec.gates[segment.gateIndex] |
1202 | |
1203 | local gate = clone(gateInfo.node, false, false, false) |
1204 | link(segment.group, gate) |
1205 | |
1206 | local segementTerrainY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, segment.x1, 0, segment.z1) |
1207 | setWorldTranslation(gate, segment.x1, segementTerrainY, segment.z1) |
1208 | |
1209 | local dx, dz = segment.x1 - segment.x2, segment.z1 - segment.z2 |
1210 | local rotY = math.atan2(dx, dz) + math.pi |
1211 | setWorldRotation(gate, 0, rotY, 0) |
1212 | |
1213 | -- Only build animation on final placement |
1214 | if not isPreviewSegment then |
1215 | local animatedObject = AnimatedObject.new(self.isServer, self.isClient) |
1216 | animatedObject:setOwnerFarmId(self:getOwnerFarmId(), false) |
1217 | |
1218 | -- Note: two gates can start from the same pole, and then their end node can be at the same x or z. So need all 4 |
1219 | local saveId = string.format("AnimatedObject_%s_gate_%d_%d_%d_%d", self.configFileName, segment.x1, segment.z1, segment.x2, segment.x2) |
1220 | local builder = animatedObject:builder(self.configFileName, saveId) |
1221 | |
1222 | for _, door in ipairs(gateInfo.doors) do |
1223 | local doorNode = getChildAt(gate, door.node) |
1224 | builder:addSimplePart(doorNode, door.rotation, door.translation) |
1225 | addToPhysics(doorNode) |
1226 | end |
1227 | |
1228 | local triggerNode = getChildAt(gate, gateInfo.triggerNode) |
1229 | builder:setTrigger(triggerNode) |
1230 | addToPhysics(triggerNode) |
1231 | |
1232 | builder:setActions("ACTIVATE_HANDTOOL", gateInfo.openText, nil, gateInfo.closeText) |
1233 | builder:setDuration(gateInfo.animationDuration * 1000) |
1234 | |
1235 | if self.xmlFile == nil then |
1236 | self.xmlFile = XMLFile.load("fence", self.configFileName) |
1237 | end |
1238 | builder:setSounds(self.xmlFile.handle, string.format("placeable.fence.gate(%d).sounds", segment.gateIndex - 1), gate) |
1239 | |
1240 | if not builder:build() then |
1241 | animatedObject:delete() |
1242 | else |
1243 | animatedObject:register(true) |
1244 | |
1245 | table.insert(spec.animatedObjects, animatedObject) |
1246 | segment.animatedObject = animatedObject |
1247 | |
1248 | if gateTime ~= nil then |
1249 | animatedObject:setAnimTime(gateTime, true) |
1250 | end |
1251 | |
1252 | if self.isServer then |
1253 | -- Send one event to all clients with new AO so it is synced |
1254 | for i = 1, #spec.segments do |
1255 | if spec.segments[i] == segment then |
1256 | g_server:broadcastEvent(PlaceableFenceAddGateEvent.new(self, i, animatedObject), false, nil, self) |
1257 | break |
1258 | end |
1259 | end |
1260 | end |
1261 | end |
1262 | else |
1263 | -- In preview we show the gate at a slightly open angle to indicate open/close direction |
1264 | for _, door in ipairs(gateInfo.doors) do |
1265 | local doorNode = getChildAt(gate, door.node) |
1266 | |
1267 | local alpha = 0.3 |
1268 | |
1269 | if door.translation ~= nil then |
1270 | local x1, y1, z1 = getTranslation(doorNode) |
1271 | local x2, y2, z2 = unpack(door.translation) |
1272 | |
1273 | setTranslation(doorNode, x1 + (x2 - x1) * alpha, y1 + (y2 - y1) * alpha, z1 + (z2 - z1) * alpha) |
1274 | end |
1275 | if door.rotation ~= nil then |
1276 | local x1, y1, z1 = getRotation(doorNode) |
1277 | local x2, y2, z2 = unpack(door.rotation) |
1278 | |
1279 | setRotation(doorNode, x1 + (x2 - x1) * alpha, y1 + (y2 - y1) * alpha, z1 + (z2 - z1) * alpha) |
1280 | end |
1281 | end |
1282 | end |
1283 | end |
1284 | |
1285 | -- if enablePhysics then |
1286 | -- addToPhysics(segment.group) |
1287 | -- end |
1288 | end |
updateSegmentUpdateQueue
DescriptionDefinitionupdateSegmentUpdateQueue()Code
1325 | function PlaceableFence:updateSegmentUpdateQueue() |
1326 | local spec = self.spec_fence |
1327 | if #spec.segmentsToUpdate > 0 then |
1328 | local segment = spec.segmentsToUpdate[1] |
1329 | table.remove(spec.segmentsToUpdate, 1) |
1330 | |
1331 | self:removePickingNodesForSegment(segment) |
1332 | self:updateSegmentShapes(segment) |
1333 | self:addPickingNodesForSegment(segment) |
1334 | |
1335 | self:raiseActive() |
1336 | end |
1337 | end |