kwabs22 commited on
Commit
3463977
Β·
1 Parent(s): 0c88de3

Added latest updates

Browse files
258.bundle.js ADDED
@@ -0,0 +1 @@
 
 
1
+ "use strict";(self.webpackChunkplaycanvas_game=self.webpackChunkplaycanvas_game||[]).push([[258],{258:(e,t,o)=>{o.r(t),o.d(t,{default:()=>a});const a={name:"NPC System Test 19",description:"Focused scene to test NPC patrols, dialogue fallback, and proximity.",async build(e,t,o){console.log("[NPCSystemTest19] Building NPC system test scene...");const a=o.create("NPCSystemTest19Root",{position:[0,0,0],tags:["scene_root"]}),s=o.create("TestFloor",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[20,.1,20]},rigidbody:{type:"static"}},position:[0,-.1,0],scale:[40,.2,40]});setTimeout(()=>{if(s.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.35,.35,.4),e.update(),s.model.material=e}},0),a.addChild(s);const n=new pc.Color(.6,.6,.7),i=(e,t,s)=>{const i=o.create(e,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[s[0]/2,s[1]/2,s[2]/2]},rigidbody:{type:"static"}},position:t,scale:s});setTimeout(()=>{if(i.model){const e=new pc.StandardMaterial;e.diffuse=n,e.update(),i.model.material=e}},0),a.addChild(i)};i("NorthWall",[0,1,-20],[40,2,.4]),i("SouthWall",[0,1,20],[40,2,.4]),i("EastWall",[20,1,0],[.4,2,40]),i("WestWall",[-20,1,0],[.4,2,40]);const c=o.createLight("directional",{color:new pc.Color(1,.95,.8),intensity:1.1,castShadows:!0,position:[10,15,5],rotation:[45,-30,0]});a.addChild(c);const l=o.create("SpawnPoint",{position:[0,0,12]});a.addChild(l),[{name:"GuardA_Marker",pos:[-8,0,-8],col:[.2,.2,.9]},{name:"GuardB_Marker",pos:[8,0,-8],col:[.2,.2,.9]},{name:"Merchant_Marker",pos:[-10,0,6],col:[.2,.8,.2]},{name:"Questgiver_Marker",pos:[10,0,6],col:[.9,.9,.2]}].forEach(e=>{const t=o.create(e.name,{components:{model:{type:"box"}},position:e.pos,scale:[.6,2.2,.6]});setTimeout(()=>{if(t.model){const o=new pc.StandardMaterial;o.diffuse=new pc.Color(e.col[0],e.col[1],e.col[2]),o.update(),t.model.material=o}},0),a.addChild(t)});const r=e.core.getSystem("npcmanager");if(r&&r.spawnNPC)console.log("[NPCSystemTest19] Spawning NPCs via NPCManager..."),r.spawnNPC({template:"guard",id:"guard_patrol_A",position:[-8,0,-8],customData:{displayName:"Guard A",moveSpeed:2.2}}),r.spawnNPC({template:"guard",id:"guard_patrol_B",position:[8,0,-8],customData:{displayName:"Guard B",patrolPath:[{offset:[0,0,0],wait:2},{offset:[0,0,10],wait:2},{offset:[-10,0,10],wait:2},{offset:[-10,0,0],wait:2}],moveSpeed:1.8}}),r.spawnNPC({template:"merchant",id:"merchant_elli",position:[-10,0,6],customData:{displayName:"Elli the Merchant",interactionDistance:3}}),r.spawnNPC({template:"questgiver",id:"questgiver_rin",position:[10,0,6],customData:{displayName:"Rin the Questgiver",dialogue:null,interactionDistance:3.5}});else{console.warn("[NPCSystemTest19] NPCManager not available; attempting direct NPC creation");const e=o.createNPC("direct_npc_test",{position:[0,0,0],displayName:"Direct NPC Test",interactionDistance:3});a.addChild(e)}const d=o.create("InstructionSign",{components:{element:{type:pc.ELEMENTTYPE_TEXT,text:"NPC Test: Guards patrol with waits. Talk to Merchant/Questgiver. F to interact.",fontSize:28,color:new pc.Color(1,1,.2),fontAsset:e.fontManager.getFontAssetId(),anchor:[.5,.5,.5,.5],pivot:[.5,.5],width:900,height:60},screen:{referenceResolution:new pc.Vec2(900,60),scaleMode:pc.SCALEMODE_NONE,screenSpace:!1}},position:[0,3,0],scale:[.05,.05,.05],tags:["ui"]});return a.addChild(d),e.fire("ui:notification",{text:"Scene 19: NPC test loaded. Approach guards to observe patrols; interact with NPCs to test dialogue/proximity.",type:"info",duration:6e3}),a},onLoad(e,t){console.log("[NPCSystemTest19] onLoad")},onUnload(e,t){console.log("[NPCSystemTest19] onUnload")}}}}]);
314.bundle.js ADDED
@@ -0,0 +1 @@
 
 
1
+ "use strict";(self.webpackChunkplaycanvas_game=self.webpackChunkplaycanvas_game||[]).push([[314],{314:(e,t,i)=>{i.r(t),i.d(t,{default:()=>a});var o=i(96);const a={id:"fracture_reduction",title:"Fracture Reduction Surgery",type:"scene",async build(e,t,i){console.log("[FractureReduction] Building orthopedic surgery simulation...");const a=new o.n(e,e.systems?.core);a.sceneBuilder=t,a.entityFactory=i,this.fractureData={fragmentPositions:[],originalAlignment:null,currentAlignment:null,reductionQuality:0,angulation:{x:0,y:0,z:0},displacement:{x:0,y:0,z:0},rotation:{x:0,y:0,z:0},stabilization:!1,fluoroscopyImages:0,surgicalTime:0,patientVitals:{stable:!0,painLevel:7}},this.geometricData={currentShape:"square",selectedPattern:null,fragments:[],assemblyProgress:0,practiceAttempts:0,fracturePatterns:{square:[{name:"rectangles",fragments:["rect_left","rect_right"],description:"Split into 2 rectangles"},{name:"triangles",fragments:["tri_top","tri_bottom"],description:"Diagonal split into 2 triangles"},{name:"mini_squares",fragments:["sq_tl","sq_tr","sq_bl","sq_br"],description:"Quarter into 4 mini-squares"},{name:"center_circle",fragments:["circle_center","tri_top","tri_right","tri_bottom","tri_left"],description:"Center circle + 4 corner triangles"}],triangle:[{name:"mini_triangles",fragments:["tri_1","tri_2","tri_3"],description:"Split into 3 smaller triangles"},{name:"center_split",fragments:["tri_main","tri_small"],description:"Center line split"}],circle:[{name:"half_circles",fragments:["semi_1","semi_2"],description:"Split into 2 semicircles"},{name:"pie_slices",fragments:["pie_1","pie_2","pie_3","pie_4"],description:"Quarter into 4 pie slices"}]}},a.createRoom("pre_op_prep",{size:[16,4,12],lighting:"clinical",position:[0,0,0],buildFunction:r,objectives:[{id:"patient_assessment",name:"Review Patient Chart",description:"Click on the Patient Chart to review fracture details and medical history",type:"simple",autoStart:!0,rewards:{experience:40}},{id:"surgical_prep",name:"Complete Pre-Op Preparation",description:"Use the 4 preparation stations: Handwash Station, Sterilization Unit, Surgical Gowns, and Pre-Op Checklist",type:"counter",target:4,progress:0,rewards:{experience:60,items:[{id:"surgical_clearance",quantity:1}]}},{id:"proceed_to_or",name:"Proceed to Operating Room",description:"All preparation complete! Use the door to enter the OR Suite and begin surgery",type:"guidance",prerequisites:["surgical_prep"],rewards:{experience:10}}]}),a.createRoom("or_suite",{size:[24,5,18],lighting:"surgical",position:[0,0,0],buildFunction:n,objectives:[{id:"fracture_exposure",name:"Expose the Fracture Site",description:"Use surgical tools to expose fracture: Click Scalpel Tray, then Retractor Set, then Hemostat Clamps",type:"counter",target:3,progress:0,rewards:{experience:100}},{id:"fracture_reduction",name:"Reduce the Fracture",description:"Align bone fragments using Reduction Clamps, Traction Device, and Alignment Guide to achieve 85% alignment",type:"counter",target:85,progress:0,rewards:{experience:200,skillPoints:3}},{id:"internal_fixation",name:"Apply Internal Fixation",description:"Stabilize fracture by clicking: Fixation Plate, then all 4 Screws, then Cerclage Wire (6 items total)",type:"counter",target:6,progress:0,rewards:{experience:150}},{id:"proceed_to_imaging",name:"Proceed to Imaging Suite",description:"Surgery complete! Use the door to enter the Fluoroscopy Suite for X-ray verification",type:"guidance",prerequisites:["internal_fixation"],rewards:{experience:10}}]}),a.createRoom("fluoroscopy_suite",{size:[14,4,16],lighting:"imaging",position:[0,0,0],buildFunction:c,objectives:[{id:"imaging_verification",name:"Take X-Ray Images",description:"Capture 3 imaging angles: Click AP View, Lateral View, and Oblique View buttons",type:"counter",target:3,progress:0,rewards:{experience:80}},{id:"hardware_positioning",name:"Verify Hardware Placement",description:"Click Hardware Check station to confirm all screws and plates are properly positioned",type:"simple",rewards:{experience:70}},{id:"proceed_to_recovery",name:"Proceed to Recovery Room",description:"Imaging verified! Use the door to enter Post-Op Recovery for surgical closure",type:"guidance",prerequisites:["hardware_positioning"],rewards:{experience:10}}]}),a.createRoom("post_op_recovery",{size:[12,4,10],lighting:"recovery",position:[0,0,0],buildFunction:s,objectives:[{id:"closure_completion",name:"Close the Surgical Site",description:"Suture in layers: Click Deep Sutures, then Subcutaneous Sutures, then Skin Closure",type:"counter",target:3,progress:0,rewards:{experience:60}},{id:"post_op_xray",name:"Take Final X-Ray",description:"Click Post-Op Imaging station to document final results and complete surgery",type:"simple",rewards:{experience:40,items:[{id:"surgical_report",quantity:1}]}},{id:"surgery_complete",name:"Surgery Successfully Completed",description:"Congratulations! The fracture reduction surgery is complete. You may continue practicing or explore other areas",type:"guidance",prerequisites:["post_op_xray"],rewards:{experience:25}}]}),a.createRoom("student_practice",{size:[16,4,14],lighting:"bright",position:[0,0,0],buildFunction:l,objectives:[{id:"learn_shapes",name:"Select a Shape to Study",description:"Click on one of the geometric shapes: Square, Triangle, or Circle to begin learning",type:"simple",autoStart:!0,rewards:{experience:25}},{id:"fracture_practice",name:"Practice Fracture Patterns",description:"Try 3 different fracture methods using the colored pattern stations around the room",type:"counter",target:3,progress:0,rewards:{experience:50}},{id:"reassembly_challenge",name:"Reassemble Broken Shapes",description:"Click on the colored Fragment pieces to reconstruct fractured shapes (complete 5 assemblies)",type:"counter",target:5,progress:0,rewards:{experience:75,skillPoints:1}},{id:"continue_practice",name:"Continue Practicing",description:"Great work! Use Random Fracture Generator for new challenges or Reset Puzzle to try again. Return to Pre-Op when ready for medical training",type:"guidance",prerequisites:["reassembly_challenge"],rewards:{experience:15}}]}),a.createDoor("pre_op_prep","or_suite",{type:"objective",id:"surgical_prep"},{playerSpawnPosition:[-10,0,0]}),a.createDoor("pre_op_prep","student_practice",{type:"none"},{playerSpawnPosition:[0,0,-6]}),a.createDoor("or_suite","fluoroscopy_suite",{type:"objective",id:"fracture_reduction"},{playerSpawnPosition:[6,0,0]}),a.createDoor("fluoroscopy_suite","post_op_recovery",{type:"objective",id:"imaging_verification"},{playerSpawnPosition:[0,0,4]}),a.createDoor("or_suite","fluoroscopy_suite",{type:"none"},{playerSpawnPosition:[8,0,-6]});const p=await a.build(e,t,i);return p.multiRoom=a,p.fractureData=this.fractureData,p},onLoad(e,t){console.log("[FractureReduction] Orthopedic surgery simulation initiated...");const i=e.root.findByTag("scene_root")[0];i&&i.multiRoom&&(i.multiRoom.onLoad(e,t),this.multiRoom=i.multiRoom,this.fractureData=i.fractureData,this.interactionHandler=i=>{this.handleSurgicalInteraction(i,e,t)},this.multiRoom.setSceneInteractionHandler(this.interactionHandler),this.initializeFractureSimulation()),e.fire("ui:notification",{text:"TRAUMA ALERT: Complex fracture requires immediate reduction and fixation",type:"warning",duration:5e3}),this.surgicalStartTime=Date.now(),this.updateSurgicalTimer(e)},initializeFractureSimulation(){this.fractureData.fragmentPositions=[{id:"proximal_fragment",displacement:[0,0,15],angulation:[12,0,-8]},{id:"distal_fragment",displacement:[0,0,-18],angulation:[-15,0,12]},{id:"butterfly_fragment",displacement:[8,0,0],angulation:[0,25,0]}],this.fractureData.originalAlignment={displacement:[0,0,0],angulation:[0,0,0],rotation:[0,0,0]},this.fractureData.currentAlignment={displacement:[0,0,33],angulation:[27,0,4],rotation:[0,25,0]},console.log("[FractureReduction] Fracture simulation initialized - Complex comminuted pattern")},updateSurgicalTimer(e){this.surgicalStartTime&&(this.fractureData.surgicalTime=Math.floor((Date.now()-this.surgicalStartTime)/1e3),this.fractureData.surgicalTime>180&&(this.fractureData.patientVitals.stable=!1,this.fractureData.patientVitals.painLevel=Math.min(10,7+Math.floor(this.fractureData.surgicalTime/60))),setTimeout(()=>this.updateSurgicalTimer(e),1e3))},checkGuidanceObjectives(e,t){this.multiRoom&&this.multiRoom.currentRoom&&setTimeout(()=>{this.multiRoom.checkAndActivateGuidanceObjectives(this.multiRoom.currentRoom)},50)},handleSurgicalInteraction(e,t,i){console.log("[FractureReduction] Surgical interaction:",e);const o=i.getSystem("objectivemanager"),a=this.multiRoom.currentRoom;if("PatientChart"===e&&"pre_op_prep"===a)o.isObjectiveActive("patient_assessment")&&(o.completeObjective("patient_assessment"),o.startObjective("surgical_prep"),t.fire("ui:notification",{text:"Chart Review: 45-year-old male, closed comminuted femur fracture from MVA",type:"info",duration:4e3}),this.checkGuidanceObjectives(t,i));else if(["HandwashStation","SterilizationUnit","SurgicalGowns","PreOpChecklist"].includes(e)){if(o.isObjectiveActive("surgical_prep")){const a={HandwashStation:"Surgical hand scrub completed - 5 minutes",SterilizationUnit:"Instruments sterilized and ready",SurgicalGowns:"Sterile gowning and gloving completed",PreOpChecklist:"WHO surgical safety checklist verified"};t.fire("ui:notification",{text:a[e],type:"success",duration:2500});const r=o.objectives.get("surgical_prep")?.progress||0;o.updateObjective("surgical_prep",r+1),o.isComplete("surgical_prep")&&(this.multiRoom.addItem("surgical_clearance"),t.fire("ui:notification",{text:"PRE-OP COMPLETE: Patient ready for surgical intervention",type:"success",duration:3500}),this.checkGuidanceObjectives(t,i))}}else if(["ScalpelTray","RetractorSet","HemostatClamps"].includes(e)){if(o.isObjectiveActive("fracture_exposure")){const a={ScalpelTray:"incision_planning",RetractorSet:"tissue_dissection",HemostatClamps:"fracture_visualization"}[e],r={incision_planning:"Lateral approach planned - minimally invasive technique",tissue_dissection:"Careful dissection preserving soft tissue envelope",fracture_visualization:"Fracture site exposed - comminuted pattern confirmed"};t.fire("ui:notification",{text:r[a],type:"info",duration:3e3});const n=o.objectives.get("fracture_exposure")?.progress||0;o.updateObjective("fracture_exposure",n+1),o.isComplete("fracture_exposure")&&(o.startObjective("fracture_reduction"),t.fire("ui:notification",{text:"EXPOSURE COMPLETE: Begin fracture reduction maneuvers",type:"warning",duration:3500}),this.checkGuidanceObjectives(t,i))}}else if(["ReductionClamps","TractionDevice","AlignmentGuide"].includes(e))o.isObjectiveActive("fracture_reduction")&&this.performFractureReduction(e,t,o);else if(e.startsWith("FixationHardware_")){if(o.isObjectiveActive("internal_fixation")){const a=e.split("_")[1],r={Plate:"Locking compression plate applied to lateral cortex",Screw1:"Proximal locking screw - bicortical purchase achieved",Screw2:"Distal locking screw - optimal thread engagement",Screw3:"Lag screw through butterfly fragment - compression achieved",Screw4:"Additional cortical screw for rotational stability",Wire:"Cerclage wire for additional fragment control"};t.fire("ui:notification",{text:r[a]||"Fixation hardware applied",type:"success",duration:2500});const n=o.objectives.get("internal_fixation")?.progress||0;o.updateObjective("internal_fixation",n+1),o.isComplete("internal_fixation")&&(t.fire("ui:notification",{text:"FIXATION COMPLETE: Proceed to fluoroscopic verification",type:"success",duration:4e3}),this.checkGuidanceObjectives(t,i))}}else if(["FluoroscopyUnit","ImagingAngle_AP","ImagingAngle_Lateral","ImagingAngle_Oblique"].includes(e))o.isObjectiveActive("imaging_verification")&&this.performFluoroscopyImaging(e,t,o);else if("HardwareCheck"===e){if(o.isObjectiveActive("hardware_positioning")){const e=this.calculateReductionQuality();e>=85?(o.completeObjective("hardware_positioning"),o.startObjective("closure_completion"),t.fire("ui:notification",{text:`EXCELLENT REDUCTION: ${e}% anatomical alignment achieved`,type:"success",duration:4e3}),this.checkGuidanceObjectives(t,i)):t.fire("ui:notification",{text:`SUBOPTIMAL REDUCTION: ${e}% - Consider revision`,type:"warning",duration:3500})}}else if(["DeepSutures","SubcutaneousSutures","SkinClosure"].includes(e)){if(o.isObjectiveActive("closure_completion")){const a={DeepSutures:"deep_closure",SubcutaneousSutures:"subcutaneous_closure",SkinClosure:"skin_closure"}[e],r={deep_closure:"Deep fascial closure with absorbable sutures",subcutaneous_closure:"Subcutaneous layer closed - dead space eliminated",skin_closure:"Skin closure with interrupted sutures - excellent approximation"};t.fire("ui:notification",{text:r[a],type:"info",duration:2500});const n=o.objectives.get("closure_completion")?.progress||0;o.updateObjective("closure_completion",n+1),o.isComplete("closure_completion")&&(o.startObjective("post_op_xray"),this.checkGuidanceObjectives(t,i))}}else if(["GeometricSquare","GeometricTriangle","GeometricCircle"].includes(e)){if(o.isObjectiveActive("learn_shapes")&&"student_practice"===a){const a={GeometricSquare:"square",GeometricTriangle:"triangle",GeometricCircle:"circle"};this.geometricData.currentShape=a[e],o.completeObjective("learn_shapes"),o.startObjective("fracture_practice"),t.fire("ui:notification",{text:`Selected ${this.geometricData.currentShape.toUpperCase()} for geometric fracture study`,type:"info",duration:3e3}),this.checkGuidanceObjectives(t,i)}}else if(e.startsWith("GeometricPattern_")){if(o.isObjectiveActive("fracture_practice")&&"student_practice"===a){const i=e.split("_")[1];this.applyGeometricPattern(i,t,o)}}else if(e.startsWith("GeometricFragment_"))o.isObjectiveActive("reassembly_challenge")&&"student_practice"===a&&this.manipulateGeometricFragment(e,t,o);else if("RandomGeometricFracture"===e)"student_practice"===a&&this.generateRandomGeometricFracture(t);else if("ResetGeometricPuzzle"===e)"student_practice"===a&&this.resetGeometricPuzzle(t);else if("PostOpImaging"===e&&o.isObjectiveActive("post_op_xray")){this.multiRoom.addItem("surgical_report"),o.completeObjective("post_op_xray");const e=Math.floor((Date.now()-this.surgicalStartTime)/1e3/60),a=this.calculateReductionQuality();t.fire("ui:notification",{text:`SURGERY COMPLETE: ${e}min duration, ${a}% reduction quality`,type:"success",duration:6e3}),this.checkGuidanceObjectives(t,i)}},performFractureReduction(e,t,i){const o={ReductionClamps:{displacement:8,angulation:12,difficulty:"moderate"},TractionDevice:{displacement:15,angulation:8,difficulty:"high"},AlignmentGuide:{displacement:5,angulation:20,difficulty:"precision"}}[e];if(!o)return;this.fractureData.currentAlignment.displacement[2]-=o.displacement,this.fractureData.currentAlignment.angulation[0]-=o.angulation,this.fractureData.currentAlignment.displacement[2]=Math.max(0,this.fractureData.currentAlignment.displacement[2]),this.fractureData.currentAlignment.angulation[0]=Math.max(0,this.fractureData.currentAlignment.angulation[0]);const a=this.calculateReductionQuality(),r=i.objectives.get("fracture_reduction")?.progress||0;i.updateObjective("fracture_reduction",r+1),t.fire("ui:notification",{text:`${{ReductionClamps:"Reduction clamps applied - gradual distraction and alignment",TractionDevice:"Traction applied - length and axis restoration",AlignmentGuide:"Precise alignment guide - fine-tuning reduction"}[e]} - Reduction: ${a}%`,type:a>=85?"success":"info",duration:3e3}),a>=85&&(i.completeObjective("fracture_reduction"),i.startObjective("internal_fixation"),t.fire("ui:notification",{text:"ANATOMICAL REDUCTION ACHIEVED: Proceed with internal fixation",type:"success",duration:4e3}),this.checkGuidanceObjectives(t,core))},performFluoroscopyImaging(e,t,i){this.fractureData.fluoroscopyImages++,t.fire("ui:notification",{text:{FluoroscopyUnit:"Fluoroscopy unit positioned - C-arm alignment optimal",ImagingAngle_AP:"AP view acquired - anterior-posterior alignment verified",ImagingAngle_Lateral:"Lateral view acquired - sagittal plane assessment",ImagingAngle_Oblique:"Oblique view acquired - rotational alignment confirmed"}[e]||"Fluoroscopic image acquired",type:"info",duration:2500});const o=i.objectives.get("imaging_verification")?.progress||0;i.updateObjective("imaging_verification",o+1),i.isComplete("imaging_verification")&&(i.startObjective("hardware_positioning"),t.fire("ui:notification",{text:`IMAGING COMPLETE: ${this.fractureData.fluoroscopyImages} views acquired`,type:"success",duration:3e3}),this.checkGuidanceObjectives(t,core))},calculateReductionQuality(){const e=Math.abs(this.fractureData.currentAlignment.displacement[2]),t=Math.abs(this.fractureData.currentAlignment.angulation[0]),i=Math.max(0,100-3*e),o=Math.max(0,100-4*t),a=Math.floor((i+o)/2);return this.fractureData.reductionQuality=a,a},applyGeometricPattern(e,t,i){const o=this.geometricData.currentShape,a=this.geometricData.fracturePatterns[o];if(!a)return;const r=a.find(t=>t.name===e);if(!r)return;this.geometricData.selectedPattern=r,this.geometricData.fragments=[...r.fragments],this.geometricData.practiceAttempts++;const n=i.objectives.get("fracture_practice")?.progress||0;i.updateObjective("fracture_practice",n+1),t.fire("ui:notification",{text:`${o.toUpperCase()} fractured using ${e}: ${r.description}`,type:"success",duration:4e3}),i.isComplete("fracture_practice")&&(i.startObjective("reassembly_challenge"),t.fire("ui:notification",{text:"FRACTURE PRACTICE COMPLETE! Now try reassembly challenges",type:"success",duration:3500}),this.checkGuidanceObjectives(t,core)),console.log("[GeometricFracture] Applied pattern:",e,"Fragments:",r.fragments.length)},manipulateGeometricFragment(e,t,i){if(!this.geometricData.selectedPattern)return void t.fire("ui:notification",{text:"Select a shape and fracture pattern first!",type:"warning",duration:2500});const o=e.split("_")[1];this.geometricData.assemblyProgress+=100/this.geometricData.fragments.length,this.geometricData.assemblyProgress=Math.min(100,this.geometricData.assemblyProgress);const a=i.objectives.get("reassembly_challenge")?.progress||0;i.updateObjective("reassembly_challenge",a+1),t.fire("ui:notification",{text:`Fragment ${o} positioned - Assembly: ${Math.floor(this.geometricData.assemblyProgress)}%`,type:this.geometricData.assemblyProgress>=100?"success":"info",duration:2500}),this.geometricData.assemblyProgress>=100&&(t.fire("ui:notification",{text:`PERFECT RECONSTRUCTION! ${this.geometricData.currentShape.toUpperCase()} successfully reassembled`,type:"success",duration:4e3}),i.isComplete("reassembly_challenge")&&this.checkGuidanceObjectives(t,core),setTimeout(()=>{this.resetGeometricPuzzle(t,!1)},2e3))},generateRandomGeometricFracture(e){const t=["square","triangle","circle"],i=t[Math.floor(Math.random()*t.length)];this.geometricData.currentShape=i;const o=this.geometricData.fracturePatterns[i],a=o[Math.floor(Math.random()*o.length)];this.geometricData.selectedPattern=a,this.geometricData.fragments=[...a.fragments],this.geometricData.assemblyProgress=0,e.fire("ui:notification",{text:`RANDOM CHALLENGE: ${i.toUpperCase()} β†’ ${a.name} (${a.fragments.length} pieces)`,type:"warning",duration:4e3})},resetGeometricPuzzle(e,t=!0){this.geometricData.currentShape="square",this.geometricData.selectedPattern=null,this.geometricData.fragments=[],this.geometricData.assemblyProgress=0,t&&e.fire("ui:notification",{text:"Geometric puzzle reset - Ready for new shape fracture challenge",type:"info",duration:3e3})},onUnload(e,t){console.log("[FractureReduction] Surgical simulation concluded"),this.multiRoom&&this.multiRoom.setSceneInteractionHandler(null),this.interactionHandler=null,this.multiRoom&&this.multiRoom.onUnload(e,t),this.surgicalStartTime=null}};async function r(e,t,i,o){console.log("[FractureReduction] Building pre-operative preparation room...");const a=i.create("PatientChart",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.5,.3,.1]},rigidbody:{type:"static"}},position:[-6,1.5,-4],scale:[1,.6,.2],tags:["interactive","chart"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(1,.9,.8),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}o.addEntityToRoom(a,"pre_op_prep");const r=i.create("XRayViewer",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,1.2,.1]}},position:[-6,1.2,-5],scale:[2,2.4,.2],tags:["medical_equipment"]});if(r.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.9,.9,.9),e.emissive=new pc.Color(.3,.3,.4),e.update(),r.model.meshInstances.forEach(t=>t.material=e)}e.addChild(r),[{name:"HandwashStation",pos:[6,1,-4],scale:[1.5,2,.8],color:[.8,.9,1]},{name:"SterilizationUnit",pos:[6,1.2,0],scale:[2,2.4,1.2],color:[.9,.9,.9]},{name:"SurgicalGowns",pos:[4,1.8,4],scale:[1,1.6,.5],color:[.7,.8,.9]},{name:"PreOpChecklist",pos:[0,1,4],scale:[.8,.4,.1],color:[1,1,.9]}].forEach(e=>{const t=i.create(e.name,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[e.scale[0]/2,e.scale[1]/2,e.scale[2]/2]},rigidbody:{type:"static"}},position:e.pos,scale:e.scale,tags:["interactive","prep_station"]});if(t.model){const i=new pc.StandardMaterial;i.diffuse=new pc.Color(e.color[0],e.color[1],e.color[2]),i.update(),t.model.meshInstances.forEach(e=>e.material=i)}o.addEntityToRoom(t,"pre_op_prep")}),[{pos:[0,3.5,0],color:new pc.Color(.95,.95,1)},{pos:[-4,3,-3],color:new pc.Color(.9,.9,1)},{pos:[4,3,3],color:new pc.Color(.9,.9,1)}].forEach(t=>{const o=i.createLight("point",{color:t.color,intensity:2.2,range:12,position:t.pos});e.addChild(o)})}async function n(e,t,i,o){console.log("[FractureReduction] Building operating room suite...");const a=i.create("OperatingTable",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[3,.8,1]},rigidbody:{type:"static"}},position:[0,.8,0],scale:[6,1.6,2],tags:["surgical_table"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.9,.9,.9),e.metalness=.3,e.update(),a.model.meshInstances.forEach(t=>t.material=e)}e.addChild(a);const r=i.create("Patient",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2.5,.3,.8]}},position:[0,1.9,0],scale:[5,.6,1.6],tags:["patient"]});if(r.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.7,.8,.9),e.update(),r.model.meshInstances.forEach(t=>t.material=e)}e.addChild(r),[{name:"ScalpelTray",pos:[-8,1.2,-2],scale:[1.5,.3,1],color:[.8,.8,.9]},{name:"RetractorSet",pos:[-8,1.2,2],scale:[1.5,.3,1.2],color:[.7,.8,.8]},{name:"HemostatClamps",pos:[-6,1.2,-6],scale:[1,.3,.8],color:[.9,.8,.7]}].forEach(e=>{const t=i.create(e.name,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[e.scale[0]/2,e.scale[1]/2,e.scale[2]/2]},rigidbody:{type:"static"}},position:e.pos,scale:e.scale,tags:["interactive","surgical_instrument"]});if(t.model){const i=new pc.StandardMaterial;i.diffuse=new pc.Color(e.color[0],e.color[1],e.color[2]),i.metalness=.6,i.update(),t.model.meshInstances.forEach(e=>e.material=i)}o.addEntityToRoom(t,"or_suite")}),[{name:"ReductionClamps",pos:[8,1.2,-4],scale:[1.2,.6,.4],color:[.6,.7,.8]},{name:"TractionDevice",pos:[8,1.5,0],scale:[1,2,.8],color:[.7,.6,.8]},{name:"AlignmentGuide",pos:[8,1.2,4],scale:[.8,.8,1.2],color:[.8,.7,.6]}].forEach(e=>{const t=i.create(e.name,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[e.scale[0]/2,e.scale[1]/2,e.scale[2]/2]},rigidbody:{type:"static"}},position:e.pos,scale:e.scale,tags:["interactive","reduction_tool"]});if(t.model){const i=new pc.StandardMaterial;i.diffuse=new pc.Color(e.color[0],e.color[1],e.color[2]),i.metalness=.7,i.update(),t.model.meshInstances.forEach(e=>e.material=i)}o.addEntityToRoom(t,"or_suite")}),[{name:"FixationHardware_Plate",pos:[6,1.2,-8],scale:[1.5,.2,.3],color:[.9,.9,.95]},{name:"FixationHardware_Screw1",pos:[4,1.2,-8],scale:[.3,.2,.3],color:[.95,.95,1]},{name:"FixationHardware_Screw2",pos:[2,1.2,-8],scale:[.3,.2,.3],color:[.95,.95,1]},{name:"FixationHardware_Screw3",pos:[0,1.2,-8],scale:[.3,.2,.3],color:[.95,.95,1]},{name:"FixationHardware_Screw4",pos:[-2,1.2,-8],scale:[.3,.2,.3],color:[.95,.95,1]},{name:"FixationHardware_Wire",pos:[-4,1.2,-8],scale:[.2,.1,.8],color:[.8,.9,.9]}].forEach(e=>{const t=i.create(e.name,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[e.scale[0]/2,e.scale[1]/2,e.scale[2]/2]},rigidbody:{type:"static"}},position:e.pos,scale:e.scale,tags:["interactive","fixation_hardware"]});if(t.model){const i=new pc.StandardMaterial;i.diffuse=new pc.Color(e.color[0],e.color[1],e.color[2]),i.metalness=.9,i.update(),t.model.meshInstances.forEach(e=>e.material=i)}o.addEntityToRoom(t,"or_suite")}),[{pos:[0,4.8,0],color:new pc.Color(1,1,1)},{pos:[-3,4.5,0],color:new pc.Color(.98,.98,1)},{pos:[3,4.5,0],color:new pc.Color(.98,.98,1)},{pos:[0,4.5,-3],color:new pc.Color(.95,.95,1)},{pos:[0,4.5,3],color:new pc.Color(.95,.95,1)}].forEach(t=>{const o=i.createLight("point",{color:t.color,intensity:2.8,range:15,position:t.pos});e.addChild(o)})}async function c(e,t,i,o){console.log("[FractureReduction] Building fluoroscopy imaging suite...");const a=i.create("FluoroscopyUnit",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,3,2]},rigidbody:{type:"static"}},position:[0,3,0],scale:[4,6,4],tags:["interactive","imaging_equipment"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.2,.2),e.emissive=new pc.Color(0,.1,.2),e.metalness=.8,e.update(),a.model.meshInstances.forEach(t=>t.material=e)}o.addEntityToRoom(a,"fluoroscopy_suite");const r=i.create("ImagingControl",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,1.2,.8]}},position:[-5,1.2,-6],scale:[3,2.4,1.6],tags:["imaging_control"]});if(r.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.3,.3,.3),e.emissive=new pc.Color(.1,.1,0),e.update(),r.model.meshInstances.forEach(t=>t.material=e)}e.addChild(r),[{name:"ImagingAngle_AP",pos:[5,1.2,-4],label:"AP View"},{name:"ImagingAngle_Lateral",pos:[5,1.2,0],label:"Lateral View"},{name:"ImagingAngle_Oblique",pos:[5,1.2,4],label:"Oblique View"}].forEach(e=>{const t=i.create(e.name,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.8,.8,.4]},rigidbody:{type:"static"}},position:e.pos,scale:[1.6,1.6,.8],tags:["interactive","imaging_angle"]});if(t.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.1,.3,.6),e.emissive=new pc.Color(0,.1,.2),e.update(),t.model.meshInstances.forEach(t=>t.material=e)}o.addEntityToRoom(t,"fluoroscopy_suite")});const n=i.create("HardwareCheck",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,.8,.6]},rigidbody:{type:"static"}},position:[0,.8,6],scale:[2,1.6,1.2],tags:["interactive","verification"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.6,.3),e.emissive=new pc.Color(.1,.2,.1),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}o.addEntityToRoom(n,"fluoroscopy_suite"),[{pos:[0,3.5,-6],color:new pc.Color(.6,.7,.8)},{pos:[-6,3,0],color:new pc.Color(.5,.6,.7)},{pos:[6,3,0],color:new pc.Color(.5,.6,.7)}].forEach(t=>{const o=i.createLight("point",{color:t.color,intensity:1.5,range:10,position:t.pos});e.addChild(o)})}async function s(e,t,i,o){console.log("[FractureReduction] Building post-operative recovery room...");const a=i.create("ClosureStation",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,.8,1]}},position:[0,.8,-2],scale:[3,1.6,2],tags:["surgical_station"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.9,.9,.9),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}e.addChild(a),[{name:"DeepSutures",pos:[-3,1.2,-2],scale:[.6,.3,.4],color:[.7,.8,.9]},{name:"SubcutaneousSutures",pos:[0,1.2,-2],scale:[.6,.3,.4],color:[.8,.7,.9]},{name:"SkinClosure",pos:[3,1.2,-2],scale:[.6,.3,.4],color:[.9,.8,.7]}].forEach(e=>{const t=i.create(e.name,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[e.scale[0]/2,e.scale[1]/2,e.scale[2]/2]},rigidbody:{type:"static"}},position:e.pos,scale:e.scale,tags:["interactive","suture_material"]});if(t.model){const i=new pc.StandardMaterial;i.diffuse=new pc.Color(e.color[0],e.color[1],e.color[2]),i.update(),t.model.meshInstances.forEach(e=>e.material=i)}o.addEntityToRoom(t,"post_op_recovery")});const r=i.create("PostOpImaging",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,1.5,1]},rigidbody:{type:"static"}},position:[0,1.5,3],scale:[4,3,2],tags:["interactive","post_op_imaging"]});if(r.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.3,.4),e.emissive=new pc.Color(.1,.15,.2),e.update(),r.model.meshInstances.forEach(t=>t.material=e)}o.addEntityToRoom(r,"post_op_recovery"),[{name:"PatientMonitor",pos:[-4,1.5,0],scale:[1.2,1.8,.4],color:[.2,.2,.3]},{name:"VitalSigns",pos:[4,1.2,0],scale:[1,1.4,.6],color:[.3,.4,.2]}].forEach(t=>{const o=i.create(t.name,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[t.scale[0]/2,t.scale[1]/2,t.scale[2]/2]}},position:t.pos,scale:t.scale,tags:["monitoring_equipment"]});if(o.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(t.color[0],t.color[1],t.color[2]),e.emissive=new pc.Color(0,.1,0),e.update(),o.model.meshInstances.forEach(t=>t.material=e)}e.addChild(o)}),[{pos:[0,3.5,0],color:new pc.Color(.9,.85,.8)},{pos:[-3,3,2],color:new pc.Color(.85,.9,.8)},{pos:[3,3,-2],color:new pc.Color(.8,.85,.9)}].forEach(t=>{const o=i.createLight("point",{color:t.color,intensity:1.8,range:8,position:t.pos});e.addChild(o)})}async function l(e,t,i,o){console.log("[FractureReduction] Building student practice room for geometric fracture mechanics...");const a=i.create("LearningTable",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[3,.6,2]}},position:[0,.6,0],scale:[6,1.2,4],tags:["learning_equipment"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.8,.7,.6),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}e.addChild(a),[{name:"GeometricSquare",pos:[-4,1.5,-4],shape:"box",scale:[2,.2,2],color:[.8,.2,.2]},{name:"GeometricTriangle",pos:[0,1.5,-4],shape:"cone",scale:[2,2,2],color:[.2,.8,.2]},{name:"GeometricCircle",pos:[4,1.5,-4],shape:"sphere",scale:[1,1,1],color:[.2,.2,.8]}].forEach(e=>{const t=i.create(e.name,{components:{model:{type:e.shape},collision:{type:"box",halfExtents:[1,1,1]},rigidbody:{type:"static"}},position:e.pos,scale:e.scale,tags:["interactive","geometric_shape"]});if(t.model){const i=new pc.StandardMaterial;i.diffuse=new pc.Color(e.color[0],e.color[1],e.color[2]),i.emissive=new pc.Color(.2*e.color[0],.2*e.color[1],.2*e.color[2]),i.update(),t.model.meshInstances.forEach(e=>e.material=i)}o.addEntityToRoom(t,"student_practice")}),[{name:"GeometricPattern_rectangles",pos:[-6,1.2,0],label:"Rectangle\nSplit",color:[.9,.6,.2]},{name:"GeometricPattern_triangles",pos:[-6,1.2,2],label:"Triangle\nBreak",color:[.6,.9,.2]},{name:"GeometricPattern_mini_squares",pos:[6,1.2,0],label:"Mini\nSquares",color:[.2,.9,.6]},{name:"GeometricPattern_center_circle",pos:[6,1.2,2],label:"Center\nCircle",color:[.9,.2,.6]},{name:"GeometricPattern_half_circles",pos:[-6,1.2,-2],label:"Half\nCircles",color:[.6,.2,.9]},{name:"GeometricPattern_pie_slices",pos:[6,1.2,-2],label:"Pie\nSlices",color:[.9,.9,.2]}].forEach(e=>{const t=i.create(e.name,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,.6,.8]},rigidbody:{type:"static"}},position:e.pos,scale:[2,1.2,1.6],tags:["interactive","geometric_pattern"]});if(t.model){const i=new pc.StandardMaterial;i.diffuse=new pc.Color(e.color[0],e.color[1],e.color[2]),i.emissive=new pc.Color(.3*e.color[0],.3*e.color[1],.3*e.color[2]),i.update(),t.model.meshInstances.forEach(e=>e.material=i)}o.addEntityToRoom(t,"student_practice")}),[[-2,1,4],[-1,1,4],[0,1,4],[1,1,4],[2,1,4],[-2,1,5],[-1,1,5],[0,1,5],[1,1,5],[2,1,5]].forEach((e,t)=>{const a=i.create(`GeometricFragment_${t}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.4,.3,.4]},rigidbody:{type:"static"}},position:e,scale:[.8,.6,.8],tags:["interactive","geometric_fragment"]});if(a.model){const e=[[1,.2,.2],[.2,1,.2],[.2,.2,1],[1,1,.2],[1,.2,1],[.2,1,1],[1,.6,.2],[.6,.2,1],[.2,1,.6],[1,.2,.6]],i=t%e.length,[o,r,n]=e[i],c=new pc.StandardMaterial;c.diffuse=new pc.Color(o,r,n),c.emissive=new pc.Color(.2*o,.2*r,.2*n),c.update(),a.model.meshInstances.forEach(e=>e.material=c)}o.addEntityToRoom(a,"student_practice")});const r=i.create("RandomGeometricFracture",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,1,.5]},rigidbody:{type:"static"}},position:[0,1,-6],scale:[3,2,1],tags:["interactive","random_generator"]});if(r.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(1,.5,0),e.emissive=new pc.Color(.3,.15,0),e.emissiveIntensity=.5,e.update(),r.model.meshInstances.forEach(t=>t.material=e)}o.addEntityToRoom(r,"student_practice");const n=i.create("ResetGeometricPuzzle",{components:{model:{type:"sphere"},collision:{type:"sphere",radius:1},rigidbody:{type:"static"}},position:[0,1,6],scale:[2,2,2],tags:["interactive","reset_button"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.7,.7,.7),e.emissive=new pc.Color(.2,.2,.2),e.metalness=.8,e.update(),n.model.meshInstances.forEach(t=>t.material=e)}o.addEntityToRoom(n,"student_practice"),[{pos:[0,3.5,0],color:new pc.Color(1,1,1)},{pos:[-4,3,-4],color:new pc.Color(.95,.95,1)},{pos:[4,3,-4],color:new pc.Color(.95,.95,1)},{pos:[0,3,4],color:new pc.Color(1,.95,.9)},{pos:[0,3,-6],color:new pc.Color(1,.9,.8)}].forEach(t=>{const o=i.createLight("point",{color:t.color,intensity:2.2,range:10,position:t.pos});e.addChild(o)})}}}]);
543.bundle.js ADDED
@@ -0,0 +1 @@
 
 
1
+ "use strict";(self.webpackChunkplaycanvas_game=self.webpackChunkplaycanvas_game||[]).push([[543],{543:(e,t,i)=>{i.r(t),i.d(t,{default:()=>o});var s=i(96);const o={id:"systems_testing_lab",title:"Systems Testing Laboratory",type:"scene",async build(e,t,i){console.log("[SystemsTestingLab] Building comprehensive systems testing environment...");const o=new s.n(e,e.systems?.core);o.sceneBuilder=t,o.entityFactory=i,this.testingData={playerStats:{health:85,maxHealth:100,stamina:65,maxStamina:100,experience:150,level:2,skillPoints:3},resources:{water:75,power:60,fuel:30,materials:45},inventoryItems:["test_tool_1","test_consumable_1","test_equipment_1","test_resource_water","test_resource_power","test_material_steel","test_stat_booster","test_health_potion","test_stamina_booster","test_attribute_tome"]},o.createRoom("main_lab",{size:[24,5,20],lighting:"laboratory",position:[0,0,0],buildFunction:n,objectives:[{id:"lab_orientation",name:"Laboratory Orientation",description:"Welcome to the Systems Testing Lab! Click on the Information Terminal to begin",type:"simple",autoStart:!0,rewards:{experience:25}},{id:"test_ui_systems",name:"Test Player Stats UI",description:"Click on Stats Testing Console to test health, stamina, and attribute displays",type:"simple",rewards:{experience:50}},{id:"test_inventory_systems",name:"Test Inventory System",description:"Use Inventory Testing Station to test item management and UI interactions",type:"simple",rewards:{experience:50}}]}),o.createRoom("resource_lab",{size:[18,4,16],lighting:"industrial",position:[0,0,0],buildFunction:a,objectives:[{id:"test_water_system",name:"Test Water Management",description:"Interact with Water Tap and Sink to test resource flow mechanics",type:"simple",rewards:{experience:40}},{id:"test_power_system",name:"Test Power Grid",description:"Use Power Generator and Electrical Panel to test energy distribution",type:"simple",rewards:{experience:40}},{id:"test_resource_monitoring",name:"Test Resource Monitoring",description:"Check Resource Monitor Display to verify all systems are tracking properly",type:"simple",rewards:{experience:60}}]}),o.createRoom("equipment_lab",{size:[20,4,18],lighting:"workshop",position:[0,0,0],buildFunction:r,objectives:[{id:"test_equipment_slots",name:"Test Equipment Slots",description:"Use Equipment Testing Bench to test item equipping and stat bonuses",type:"counter",target:3,progress:0,rewards:{experience:75}},{id:"test_item_durability",name:"Test Item Durability",description:"Use Durability Testing Machine to test item condition tracking",type:"simple",rewards:{experience:50}},{id:"test_item_combinations",name:"Test Item Combinations",description:"Use Crafting Workbench to test item combination and creation systems",type:"counter",target:2,progress:0,rewards:{experience:100}}]}),o.createRoom("feedback_center",{size:[16,4,14],lighting:"office",position:[0,0,0],buildFunction:c,objectives:[{id:"provide_feedback",name:"Provide System Feedback",description:"Use Feedback Terminal to document your testing experience and suggestions",type:"simple",rewards:{experience:75,skillPoints:1}},{id:"review_test_results",name:"Review Test Results",description:"Check Test Results Display to see overall system performance metrics",type:"simple",rewards:{experience:50}}]}),o.createDoor("main_lab","resource_lab",{type:"none",position:[8,0,-8]},{playerSpawnPosition:[-6,0,0]}),o.createDoor("resource_lab","equipment_lab",{type:"objective",id:"test_resource_monitoring"},{playerSpawnPosition:[0,0,-6]}),o.createDoor("equipment_lab","feedback_center",{type:"objective",id:"test_item_combinations"},{playerSpawnPosition:[4,0,0]}),o.createDoor("main_lab","feedback_center",{type:"none",position:[-8,0,8]},{playerSpawnPosition:[0,0,4]});const l=await o.build(e,t,i);return l.multiRoom=o,l.testingData=this.testingData,l},onLoad(e,t){console.log("[SystemsTestingLab] Systems testing laboratory initiated...");const i=e.root.findByTag("scene_root")[0];i&&i.multiRoom&&(i.multiRoom.onLoad(e,t),this.multiRoom=i.multiRoom,this.testingData=i.testingData,this.interactionHandler=i=>{this.handleTestingInteraction(i,e,t)},this.multiRoom.setSceneInteractionHandler(this.interactionHandler),this.initializeTestingEnvironment(e,t)),e.fire("ui:notification",{text:"SYSTEMS TESTING LAB: Environment ready for comprehensive system evaluation",type:"info",duration:5e3}),console.log("[SystemsTestingLab] Testing environment loaded, all systems ready for evaluation")},initializeTestingEnvironment(e,t){const i=t.getSystem("statsmanager");i&&(i.modifyStat("health",this.testingData.playerStats.health,!1),i.modifyStat("maxHealth",this.testingData.playerStats.maxHealth,!1),i.modifyStat("stamina",this.testingData.playerStats.stamina,!1),i.modifyStat("maxStamina",this.testingData.playerStats.maxStamina,!1),i.modifyStat("experience",this.testingData.playerStats.experience,!1),i.modifyStat("level",this.testingData.playerStats.level,!1),i.modifyStat("skillPoints",this.testingData.playerStats.skillPoints,!1),console.log("[SystemsTestingLab] Stats initialized for testing"));const s=t.getSystem("inventorymanager");s&&(this.testingData.inventoryItems.forEach(e=>{s.hasItem(e)||s.pickupItem(e,1)}),console.log("[SystemsTestingLab] Test inventory initialized")),this.setupInventoryUsage(e,t),e.fire("resources:changed",this.testingData.resources),console.log("[SystemsTestingLab] Resource testing data prepared and UI updated")},handleTestingInteraction(e,t,i){console.log("[SystemsTestingLab] Testing interaction:",e);const s=i.getSystem("objectivemanager"),o=this.multiRoom.currentRoom;if("InformationTerminal"===e&&"main_lab"===o)s.isObjectiveActive("lab_orientation")&&(s.completeObjective("lab_orientation"),s.startObjective("test_ui_systems"),s.startObjective("test_inventory_systems"),t.fire("ui:notification",{text:"SYSTEMS TESTING LAB: Multi-system evaluation environment for UI, resources, and inventory",type:"info",duration:4e3}),this.checkGuidanceObjectives(t,i));else if("StatsTestingConsole"===e&&"main_lab"===o)s.isObjectiveActive("test_ui_systems")&&(this.testStatsUISystem(t,i),s.completeObjective("test_ui_systems"),t.fire("ui:notification",{text:"STATS UI TEST: Health, stamina, and attribute displays evaluated",type:"success",duration:3500}),this.checkGuidanceObjectives(t,i));else if("InventoryTestingStation"===e&&"main_lab"===o)s.isObjectiveActive("test_inventory_systems")&&(this.testInventorySystem(t,i),s.completeObjective("test_inventory_systems"),t.fire("ui:notification",{text:"INVENTORY TEST: Item management and UI interactions evaluated",type:"success",duration:3500}),this.checkGuidanceObjectives(t,i));else if(["HealthPotion","StaminaPotion","TestKeycard","ToolKit","DataChip"].includes(e)&&"main_lab"===o)this.handleCollectiblePickup(e,t,i);else if(["WaterTap","WaterSink"].includes(e)&&"resource_lab"===o)this.testResourceFlow(e,"water",t,i),s.isObjectiveActive("test_water_system")&&(s.completeObjective("test_water_system"),s.startObjective("test_power_system"),t.fire("ui:notification",{text:"WATER SYSTEM TEST: Resource flow and sink mechanics evaluated",type:"success",duration:3e3}),this.checkGuidanceObjectives(t,i));else if(["PowerGenerator","ElectricalPanel"].includes(e)&&"resource_lab"===o)this.testResourceFlow(e,"power",t,i),s.isObjectiveActive("test_power_system")&&(s.completeObjective("test_power_system"),s.startObjective("test_resource_monitoring"),t.fire("ui:notification",{text:"POWER SYSTEM TEST: Energy generation and distribution evaluated",type:"success",duration:3e3}),this.checkGuidanceObjectives(t,i));else if("ResourceMonitorDisplay"===e&&"resource_lab"===o)s.isObjectiveActive("test_resource_monitoring")&&(this.testResourceMonitoring(t,i),s.completeObjective("test_resource_monitoring"),t.fire("ui:notification",{text:"RESOURCE MONITORING TEST: All resource tracking systems evaluated",type:"success",duration:4e3}),this.checkGuidanceObjectives(t,i));else if("EquipmentTestingBench"===e&&"equipment_lab"===o){if(s.isObjectiveActive("test_equipment_slots")){this.testEquipmentSlots(t,i);const e=s.objectives.get("test_equipment_slots")?.progress||0;s.updateObjective("test_equipment_slots",e+1),s.isComplete("test_equipment_slots")&&(s.startObjective("test_item_durability"),t.fire("ui:notification",{text:"EQUIPMENT SLOTS TEST: Item equipping and stat bonuses evaluated",type:"success",duration:3500}),this.checkGuidanceObjectives(t,i))}}else if("DurabilityTestingMachine"===e&&"equipment_lab"===o)s.isObjectiveActive("test_item_durability")&&(this.testItemDurability(t,i),s.completeObjective("test_item_durability"),s.startObjective("test_item_combinations"),t.fire("ui:notification",{text:"DURABILITY TEST: Item condition tracking evaluated",type:"success",duration:3e3}),this.checkGuidanceObjectives(t,i));else if("CraftingWorkbench"===e&&"equipment_lab"===o){if(s.isObjectiveActive("test_item_combinations")){this.testItemCombinations(t,i);const e=s.objectives.get("test_item_combinations")?.progress||0;s.updateObjective("test_item_combinations",e+1),s.isComplete("test_item_combinations")&&(t.fire("ui:notification",{text:"CRAFTING TEST: Item combination and creation systems evaluated",type:"success",duration:4e3}),this.checkGuidanceObjectives(t,i))}}else"FeedbackTerminal"===e&&"feedback_center"===o?s.isObjectiveActive("provide_feedback")&&(this.collectFeedback(t,i),s.completeObjective("provide_feedback"),s.startObjective("review_test_results"),t.fire("ui:notification",{text:"FEEDBACK COLLECTED: System evaluation feedback recorded for analysis",type:"success",duration:4e3}),this.checkGuidanceObjectives(t,i)):"TestResultsDisplay"===e&&"feedback_center"===o&&s.isObjectiveActive("review_test_results")&&(this.displayTestResults(t,i),s.completeObjective("review_test_results"),t.fire("ui:notification",{text:"TEST COMPLETE: All system evaluations completed successfully",type:"success",duration:5e3}),this.checkGuidanceObjectives(t,i))},checkGuidanceObjectives(e,t){this.multiRoom&&this.multiRoom.currentRoom&&setTimeout(()=>{this.multiRoom.checkAndActivateGuidanceObjectives(this.multiRoom.currentRoom)},50)},testStatsUISystem(e,t){const i=t.getSystem("statsmanager");i&&(e.fire("ui:notification",{text:"STATS UI TEST: Testing health, stamina, and experience changes...",type:"info",duration:2e3}),i.modifyStat("health",-10,!0),setTimeout(()=>{i.modifyStat("health",15,!0),e.fire("ui:notification",{text:"HEALTH TEST: Health decreased then increased - UI should update",type:"success",duration:2e3})},1e3),setTimeout(()=>{i.modifyStat("stamina",-15,!0),e.fire("ui:notification",{text:"STAMINA TEST: Stamina decreased - bar should update",type:"info",duration:2e3})},2e3),setTimeout(()=>{i.modifyStat("experience",25,!0),e.fire("ui:notification",{text:"EXPERIENCE TEST: XP gained - stats display should update",type:"success",duration:2e3})},3e3),setTimeout(()=>{i.modifyStat("strength",2,!0),e.fire("ui:notification",{text:"ATTRIBUTE TEST: Strength increased - attributes display should update",type:"success",duration:2e3})},4e3),console.log("[SystemsTestingLab] Comprehensive stats UI system tested - health, stamina, experience, and attributes"))},testInventorySystem(e,t){const i=t.getSystem("inventorymanager");i&&(i.pickupItem("test_feedback_item",1),setTimeout(()=>{i.hasItem("test_consumable_1")&&i.removeItem("test_consumable_1",1)},500),console.log("[SystemsTestingLab] Inventory system tested - item addition and removal"))},handleCollectiblePickup(e,t,i){const s=i.getSystem("inventorymanager");if(!s)return;const o={HealthPotion:"health_potion",StaminaPotion:"stamina_potion",TestKeycard:"test_keycard",ToolKit:"test_toolkit",DataChip:"test_data_chip"}[e];if(o)if(s.pickupItem(o,1)){const i=t.root.findByName(e);i&&i.destroy(),t.fire("ui:notification",{text:`COLLECTED: ${e.replace(/([A-Z])/g," $1").trim()}`,type:"success",duration:2e3}),console.log(`[SystemsTestingLab] Collected ${e} -> ${o}`)}else t.fire("ui:notification",{text:"Inventory full - cannot pickup item",type:"warning",duration:2e3})},testResourceFlow(e,t,i,s){console.log(`[SystemsTestingLab] Testing ${t} flow via ${e}`);const o=s.getSystem("statsmanager");let n=0,a=0;if("WaterTap"===e?(this.testingData.resources.water=Math.max(0,this.testingData.resources.water-10),n=3,i.fire("ui:notification",{text:`WATER TAP: Water usage detected - level decreased to ${Math.floor(this.testingData.resources.water)}% (Stamina -${n})`,type:"info",duration:3e3})):"WaterSink"===e?(this.testingData.resources.water=Math.max(0,this.testingData.resources.water-5),n=2,i.fire("ui:notification",{text:`WATER SINK: Water processing - level decreased to ${Math.floor(this.testingData.resources.water)}% (Stamina -${n})`,type:"info",duration:3e3})):"PowerGenerator"===e?(this.testingData.resources.power=Math.min(100,this.testingData.resources.power+15),n=8,i.fire("ui:notification",{text:`POWER GENERATOR: Energy generation - level increased to ${Math.floor(this.testingData.resources.power)}% (Stamina -${n})`,type:"success",duration:3e3})):"ElectricalPanel"===e&&(this.testingData.resources.power=Math.max(0,this.testingData.resources.power-8),n=4,this.testingData.resources.power<30?(a=-2,i.fire("ui:notification",{text:`ELECTRICAL PANEL: Power consumption - level decreased to ${Math.floor(this.testingData.resources.power)}% (Stamina -${n}, Health -2 due to low power stress)`,type:"warning",duration:4e3})):i.fire("ui:notification",{text:`ELECTRICAL PANEL: Power consumption - level decreased to ${Math.floor(this.testingData.resources.power)}% (Stamina -${n})`,type:"info",duration:3e3})),o&&n>0){const e=o.getStat("stamina");if(e<n){const s=e/n;this.testingData.resources[t]=Math.max(0,this.testingData.resources[t]-10*(1-s)),a-=3,i.fire("ui:notification",{text:"LOW STAMINA: Reduced efficiency due to fatigue - additional health impact!",type:"error",duration:3e3})}o.modifyStat("stamina",-n,!0),console.log(`[SystemsTestingLab] Applied stamina cost: ${n}, new stamina: ${o.getStat("stamina")}`)}o&&0!==a&&(o.modifyStat("health",a,!0),console.log(`[SystemsTestingLab] Applied health impact: ${a}, new health: ${o.getStat("health")}`)),i.fire("resources:changed",this.testingData.resources),setTimeout(()=>{if(o){const e=o.getStat("health"),t=o.getStat("maxHealth"),s=o.getStat("stamina"),a=o.getStat("maxStamina");i.fire("player:healthChanged",{current:e,max:t}),i.fire("stats:changed","stamina",s+n,s),console.log(`[SystemsTestingLab] Forced UI update - Health: ${e}/${t}, Stamina: ${s}/${a}`)}},50),console.log(`[SystemsTestingLab] Resource interaction complete - Stamina cost: ${n}, Health impact: ${a}`)},testResourceMonitoring(e,t){console.log("[SystemsTestingLab] Testing resource monitoring systems");const i=this.testingData.resources.water,s=this.testingData.resources.power,o=this.testingData.resources.fuel;this.testingData.resources.materials,e.fire("ui:notification",{text:`RESOURCE STATUS: Water ${i}%, Power ${s}%, Fuel ${o}%`,type:"info",duration:4e3})},testEquipmentSlots(e,t){console.log("[SystemsTestingLab] Testing equipment slot system"),e.fire("ui:notification",{text:"EQUIPMENT SLOTS: Head, Body, Hands, Feet, Weapon slots ready for implementation",type:"info",duration:3e3})},testItemDurability(e,t){console.log("[SystemsTestingLab] Testing item durability system"),e.fire("ui:notification",{text:"ITEM DURABILITY: Condition tracking and degradation systems evaluated",type:"info",duration:3e3})},testItemCombinations(e,t){console.log("[SystemsTestingLab] Testing item combination system"),e.fire("ui:notification",{text:"ITEM CRAFTING: Combination recipes and creation mechanics evaluated",type:"info",duration:3e3})},collectFeedback(e,t){console.log("[SystemsTestingLab] Collecting system feedback");const i={timestamp:(new Date).toISOString(),systems_tested:["stats_ui","inventory","resources","equipment"],overall_rating:"positive",suggestions:["implement_visual_inventory","add_resource_meters","enhance_stat_displays"]};console.log("[SystemsTestingLab] Feedback collected:",i),e.fire("ui:notification",{text:"FEEDBACK: System evaluation data recorded - thank you for testing!",type:"success",duration:3500})},displayTestResults(e,t){console.log("[SystemsTestingLab] Displaying comprehensive test results"),e.fire("ui:notification",{text:"TEST RESULTS: UI Stats (Ready), Inventory (Needs Work), Resources (Not Implemented)",type:"info",duration:6e3}),console.log("[SystemsTestingLab] Test results summary:",{stats_ui:"READY - Minor enhancements needed",inventory:"NEEDS_WORK - Visual overhaul required",resources:"NOT_IMPLEMENTED - System design complete",equipment:"PLANNING - Architecture defined"})},setupInventoryUsage(e,t){e.keyboard&&(this.keyboardHandler=i=>{if(i.key>=pc.KEY_1&&i.key<=pc.KEY_9){const s=i.key-pc.KEY_1;this.useInventoryItem(s,e,t)}},e.keyboard.on(pc.EVENT_KEYDOWN,this.keyboardHandler),e.fire("ui:notification",{text:"INVENTORY USAGE: Press 1-9 to use items in inventory slots",type:"info",duration:4e3}))},useInventoryItem(e,t,i){const s=i.getSystem("inventorymanager");if(!s)return void console.warn("[SystemsTestingLab] No inventory manager found");const o=s.getItemAt(e);if(!o)return void t.fire("ui:notification",{text:`No item in slot ${e+1}`,type:"warning",duration:2e3});const n=s.getItemInfo(o.id);n?n.onUse||n.reusable?this.handleTestItemUsage(o,n,t,i)||s.useItem(e)&&(t.fire("ui:notification",{text:`Used ${n.name}`,type:"success",duration:2e3}),console.log(`[SystemsTestingLab] Used item: ${n.name} from slot ${e}`)):t.fire("ui:notification",{text:`${n.name} cannot be used in this context`,type:"warning",duration:2e3}):t.fire("ui:notification",{text:"Unknown item - cannot use",type:"error",duration:2e3})},handleTestItemUsage(e,t,i,s){const o=s.getSystem("statsmanager");switch(e.id){case"test_tool_1":return i.fire("ui:notification",{text:"TEST TOOL: Performing diagnostic scan... Systems nominal!",type:"success",duration:3e3}),o&&o.modifyStat("experience",10,!0),!0;case"test_consumable_1":return i.fire("ui:notification",{text:"TEST CONSUMABLE: Emergency rations consumed - health restored!",type:"success",duration:3e3}),o&&o.modifyStat("health",25,!0),!1;case"test_equipment_1":return i.fire("ui:notification",{text:"TEST EQUIPMENT: Environmental suit activated - protection enhanced!",type:"success",duration:3e3}),o&&(o.modifyStat("strength",2,!0),setTimeout(()=>{o.modifyStat("strength",-2,!0),i.fire("ui:notification",{text:"Equipment effect wore off",type:"info",duration:2e3})},3e4)),!0;case"test_resource_water":return this.testingData.resources.water=Math.min(100,this.testingData.resources.water+20),i.fire("resources:changed",this.testingData.resources),i.fire("ui:notification",{text:"WATER RESOURCE: Added 20% to water reserves",type:"success",duration:3e3}),!1;case"test_resource_power":return this.testingData.resources.power=Math.min(100,this.testingData.resources.power+25),i.fire("resources:changed",this.testingData.resources),i.fire("ui:notification",{text:"POWER RESOURCE: Added 25% to power grid",type:"success",duration:3e3}),!1;case"test_material_steel":return this.testingData.resources.materials=Math.min(100,this.testingData.resources.materials+15),i.fire("resources:changed",this.testingData.resources),i.fire("ui:notification",{text:"MATERIAL RESOURCE: Added 15% to material stockpile",type:"success",duration:3e3}),!1;case"test_stat_booster":return o&&(o.modifyStat("strength",1,!0),o.modifyStat("agility",1,!0),o.modifyStat("intelligence",1,!0),o.modifyStat("charisma",1,!0),i.fire("ui:notification",{text:"STAT BOOSTER: All attributes increased by 1!",type:"success",duration:3e3})),!1;case"test_health_potion":return o&&(o.setStat("health",o.getMaxStat("health")),i.fire("ui:notification",{text:"HEALTH POTION: Full health restored!",type:"success",duration:3e3})),!1;case"test_stamina_booster":return o&&(o.modifyStat("maxStamina",10,!0),o.setStat("stamina",o.getMaxStat("stamina")),i.fire("ui:notification",{text:"STAMINA BOOSTER: Max stamina +10, stamina restored!",type:"success",duration:3e3})),!1;case"test_attribute_tome":return o&&(o.modifyStat("experience",200,!0),o.modifyStat("skillPoints",5,!0),i.fire("ui:notification",{text:"ATTRIBUTE TOME: +200 XP, +5 Skill Points gained!",type:"success",duration:3e3})),!1;default:return!1}},onUnload(e,t){console.log("[SystemsTestingLab] Systems testing laboratory concluded"),this.keyboardHandler&&e.keyboard&&(e.keyboard.off(pc.EVENT_KEYDOWN,this.keyboardHandler),this.keyboardHandler=null),this.multiRoom&&this.multiRoom.setSceneInteractionHandler(null),this.interactionHandler=null,this.multiRoom&&this.multiRoom.onUnload(e,t)}};async function n(e,t,i,s){console.log("[SystemsTestingLab] Building main testing laboratory...");const o=i.create("InformationTerminal",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,1.5,.5]},rigidbody:{type:"static"}},position:[0,1.5,-8],scale:[2,3,1],tags:["interactive","terminal"]});if(o.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.4,.8),e.emissive=new pc.Color(.1,.2,.4),e.update(),o.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(o,"main_lab");const n=i.create("StatsTestingConsole",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,1,1]},rigidbody:{type:"static"}},position:[-6,1,-4],scale:[3,2,2],tags:["interactive","testing_console"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.8,.2,.2),e.emissive=new pc.Color(.3,.1,.1),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(n,"main_lab");const a=i.create("InventoryTestingStation",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,1,1]},rigidbody:{type:"static"}},position:[6,1,-4],scale:[3,2,2],tags:["interactive","testing_station"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.8,.2),e.emissive=new pc.Color(.1,.3,.1),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(a,"main_lab");const r=i.create("CentralPlatform",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[4,.2,4]}},position:[0,.2,2],scale:[8,.4,8],tags:["platform"]});if(r.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.7,.7,.8),e.metalness=.6,e.update(),r.model.meshInstances.forEach(t=>t.material=e)}e.addChild(r),[{pos:[0,4.5,0],color:new pc.Color(1,1,1)},{pos:[-6,4,-4],color:new pc.Color(.9,.7,.7)},{pos:[6,4,-4],color:new pc.Color(.7,.9,.7)},{pos:[0,4,-8],color:new pc.Color(.7,.8,1)}].forEach(t=>{const s=i.createLight("point",{color:t.color,intensity:2.5,range:12,position:t.pos});e.addChild(s)}),[{name:"HealthPotion",position:[-8,.5,2],itemId:"health_potion",color:new pc.Color(1,.2,.2),emissive:new pc.Color(.3,.05,.05)},{name:"StaminaPotion",position:[8,.5,2],itemId:"stamina_potion",color:new pc.Color(.2,.8,.2),emissive:new pc.Color(.05,.2,.05)},{name:"TestKeycard",position:[0,.5,6],itemId:"test_keycard",color:new pc.Color(.8,.8,.2),emissive:new pc.Color(.2,.2,.05)},{name:"ToolKit",position:[-4,.5,6],itemId:"test_toolkit",color:new pc.Color(.6,.4,.2),emissive:new pc.Color(.15,.1,.05)},{name:"DataChip",position:[4,.5,6],itemId:"test_data_chip",color:new pc.Color(.2,.4,.8),emissive:new pc.Color(.05,.1,.2)}].forEach(e=>{const t=i.create(e.name,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.3,.3,.3]},rigidbody:{type:"static"}},position:e.position,scale:[.6,.6,.6],tags:["interactive","collectible"]});if(t.itemId=e.itemId,t.model){const i=new pc.StandardMaterial;i.diffuse=e.color,i.emissive=e.emissive,i.update(),t.model.meshInstances.forEach(e=>e.material=i)}s.addEntityToRoom(t,"main_lab")})}async function a(e,t,i,s){console.log("[SystemsTestingLab] Building resource testing laboratory...");const o=i.create("WaterTap",{components:{model:{type:"cylinder"},collision:{type:"cylinder",radius:.5,height:2},rigidbody:{type:"static"}},position:[-6,1,-4],scale:[1,2,1],tags:["interactive","water_source"]});if(o.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.3,.6,.9),e.metalness=.7,e.update(),o.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(o,"resource_lab");const n=i.create("WaterSink",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,.8,1]},rigidbody:{type:"static"}},position:[-6,.8,0],scale:[2,1.6,2],tags:["interactive","water_sink"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.5,.7,.9),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(n,"resource_lab");const a=i.create("PowerGenerator",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,1.5,1]},rigidbody:{type:"static"}},position:[6,1.5,-4],scale:[3,3,2],tags:["interactive","power_source"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.9,.7,.2),e.emissive=new pc.Color(.3,.2,0),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(a,"resource_lab");const r=i.create("ElectricalPanel",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,1.5,.3]},rigidbody:{type:"static"}},position:[6,1.5,2],scale:[2,3,.6],tags:["interactive","electrical_panel"]});if(r.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.3,.3,.3),e.emissive=new pc.Color(.1,.4,.1),e.update(),r.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(r,"resource_lab");const c=i.create("ResourceMonitorDisplay",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,1.5,.2]},rigidbody:{type:"static"}},position:[0,1.5,6],scale:[4,3,.4],tags:["interactive","monitor_display"]});if(c.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.1,.1,.2),e.emissive=new pc.Color(0,.3,.6),e.update(),c.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(c,"resource_lab"),[{pos:[0,3.5,0],color:new pc.Color(.9,.9,1)},{pos:[-6,3,-2],color:new pc.Color(.8,.9,1)},{pos:[6,3,-2],color:new pc.Color(1,.9,.8)}].forEach(t=>{const s=i.createLight("point",{color:t.color,intensity:2.2,range:10,position:t.pos});e.addChild(s)})}async function r(e,t,i,s){console.log("[SystemsTestingLab] Building equipment testing laboratory...");const o=i.create("EquipmentTestingBench",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,1,1.5]},rigidbody:{type:"static"}},position:[-6,1,0],scale:[4,2,3],tags:["interactive","equipment_bench"]});if(o.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.6,.4,.2),e.update(),o.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(o,"equipment_lab");const n=i.create("DurabilityTestingMachine",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,2,1.5]},rigidbody:{type:"static"}},position:[0,2,-6],scale:[3,4,3],tags:["interactive","durability_machine"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.4,.4,.4),e.emissive=new pc.Color(.2,0,0),e.metalness=.8,e.update(),n.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(n,"equipment_lab");const a=i.create("CraftingWorkbench",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,1,2]},rigidbody:{type:"static"}},position:[6,1,2],scale:[4,2,4],tags:["interactive","crafting_bench"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.5,.3,.1),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(a,"equipment_lab"),[{pos:[0,3.5,0],color:new pc.Color(1,.95,.8)},{pos:[-6,3,0],color:new pc.Color(.9,.9,.8)},{pos:[6,3,2],color:new pc.Color(.95,.9,.7)},{pos:[0,3.5,-6],color:new pc.Color(1,.8,.8)}].forEach(t=>{const s=i.createLight("point",{color:t.color,intensity:2.3,range:9,position:t.pos});e.addChild(s)})}async function c(e,t,i,s){console.log("[SystemsTestingLab] Building feedback collection center...");const o=i.create("FeedbackTerminal",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.2,1.5,.8]},rigidbody:{type:"static"}},position:[-4,1.5,0],scale:[2.4,3,1.6],tags:["interactive","feedback_terminal"]});if(o.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.3,.7,.3),e.emissive=new pc.Color(.1,.3,.1),e.update(),o.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(o,"feedback_center");const n=i.create("TestResultsDisplay",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2.5,2,.3]},rigidbody:{type:"static"}},position:[4,2,-4],scale:[5,4,.6],tags:["interactive","results_display"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.1,.2,.1),e.emissive=new pc.Color(0,.4,.2),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}s.addEntityToRoom(n,"feedback_center");const a=i.create("OfficeDeck",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,.8,1]}},position:[0,.8,4],scale:[3,1.6,2],tags:["furniture"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.6,.5,.4),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}e.addChild(a),[{pos:[0,3.5,0],color:new pc.Color(.95,.95,1)},{pos:[-4,3,0],color:new pc.Color(.9,1,.9)},{pos:[4,3,-4],color:new pc.Color(.9,.95,.9)}].forEach(t=>{const s=i.createLight("point",{color:t.color,intensity:2,range:8,position:t.pos});e.addChild(s)})}}}]);
565.bundle.js CHANGED
@@ -1 +1 @@
1
- "use strict";(self.webpackChunkplaycanvas_game=self.webpackChunkplaycanvas_game||[]).push([[565],{565:(e,i,t)=>{t.r(i),t.d(i,{default:()=>n});class r{constructor(e){this.app=e,this.spinners=[],this.oscillators=[],this.drivers=[],this.textureCache=new Map,this.app.on("update",e=>this.updateAnimations(e))}build(e,i){const t=new pc.Entity(e.name||"ProceduralObject");return i.addChild(t),this.buildNode(e,t),t}buildNode(e,i){if(!e)return null;const t=(e.op||e.type||"prim").toLowerCase();if("group"===t){const t=new pc.Entity(e.name||"group");return i.addChild(t),this.applyTransform(t,e),(e.children||[]).forEach(e=>this.buildNode(e,t)),e.spin&&this.addSpinner(t,e.spin),t}if("mirror"===t){const t=(e.axis||"x").toLowerCase(),r=new pc.Entity(e.name||`mirror_${t}`);i.addChild(r),(e.children||[]).forEach(e=>this.buildNode(e,r));const o=r.clone();r.parent.addChild(o);const n=new pc.Vec3(1,1,1);return"x"===t&&(n.x=-1),"y"===t&&(n.y=-1),"z"===t&&(n.z=-1),o.setLocalScale(n),r}if("radial"===t){const t=e.count||4,r=e.radius||1,o=e.phase||0,n=(e.axis||"y").toLowerCase(),a=new pc.Entity(e.name||`radial_${n}`);i.addChild(a),e.center&&a.setLocalPosition(this.vec3(e.center));for(let i=0;i<t;i++){const l=new pc.Entity(`r_${i}`);a.addChild(l);const s=360/t*i+o;"x"===n?l.setLocalEulerAngles(s,0,0):"y"===n?l.setLocalEulerAngles(0,s,0):l.setLocalEulerAngles(0,0,s);const c=new pc.Entity(`o_${i}`);l.addChild(c),"z"===n?c.setLocalPosition(r,0,0):c.setLocalPosition(0,0,r),(e.children||[]).forEach(e=>this.buildNode(e,c))}return e.spin&&this.addSpinner(a,e.spin),a}if("corners"===t){const t=e.dx||1,r=e.dz||1,o=new pc.Entity(e.name||"corners");i.addChild(o),e.center&&o.setLocalPosition(this.vec3(e.center));const n=[[t,0,r],[t,0,-r],[-t,0,r],[-t,0,-r]];for(let i=0;i<n.length;i++){const t=new pc.Entity(`c_${i}`);o.addChild(t),t.setLocalPosition(n[i][0],n[i][1],n[i][2]),(e.children||[]).forEach(e=>this.buildNode(e,t))}return e.spin&&this.addSpinner(o,e.spin),o}const r=new pc.Entity(e.name||"part"),o=(e.primitive||"box").toLowerCase();return r.addComponent("render",{type:o}),this.applyTransform(r,e),r.render.material=this.makeMaterial(e),i.addChild(r),e.spin&&this.addSpinner(r,e.spin),r}applyTransform(e,i){i.pos&&e.setLocalPosition(this.vec3(i.pos)),i.rot&&e.setLocalEulerAngles(this.vec3(i.rot)),i.scl&&e.setLocalScale(this.vec3(i.scl))}vec3(e){return new pc.Vec3(e[0]||0,e[1]||0,e[2]||0)}color(e){return new pc.Color(e[0]||1,e[1]||1,e[2]||1)}makeMaterial(e){const i=new pc.StandardMaterial;if(e&&e.color){const t=this.color(e.color);i.diffuse=t.clone(),i.emissive=t.clone(),i.emissiveIntensity=.35}else i.diffuse=new pc.Color(.75,.75,.78),i.emissive=new pc.Color(.75,.75,.78),i.emissiveIntensity=.35;return i.useMetalness=!1,i.shadingModel=pc.SPECULAR_BLINN,i.roughness=1,i.update(),i}addSpinner(e,i){const t=i.rpm||24,r=new pc.Vec3(i.axis?.[0]||0,i.axis?.[1]||1,i.axis?.[2]||0);this.spinners.push({entity:e,rpm:t,axis:r})}updateAnimations(e){for(let i=0;i<this.spinners.length;i++){const t=this.spinners[i];if(t.entity&&!t.entity.destroyed){const i=6*t.rpm*e;t.entity.rotate(t.axis.x*i,t.axis.y*i,t.axis.z*i)}}for(let i=0;i<this.oscillators.length;i++){const t=this.oscillators[i];if(t.entity&&!t.entity.destroyed){t.t+=e;const i=t.base+Math.sin(2*t.t*Math.PI*t.speed+t.phase)*t.amp,r=t.entity.getLocalEulerAngles();"x"===t.axis&&t.entity.setLocalEulerAngles(i,r.y,r.z),"y"===t.axis&&t.entity.setLocalEulerAngles(r.x,i,r.z),"z"===t.axis&&t.entity.setLocalEulerAngles(r.x,r.y,i)}}for(let i=0;i<this.drivers.length;i++)try{this.drivers[i](e)}catch(e){console.warn("Driver error:",e)}}addOscillator(e,i,t,r,o=0,n=0){this.oscillators.push({entity:e,axis:i,amp:t,speed:r,base:o,phase:n,t:0})}addDriver(e){this.drivers.push(e)}cleanup(){this.spinners.length=0,this.oscillators.length=0,this.drivers.length=0,this.textureCache.clear()}createTexture(e){if(!this.app.graphicsDevice)return console.warn("[ProceduralBuilder] Graphics device not available for texture creation"),null;const i=document.createElement("canvas");i.width=128,i.height=128,e(i.getContext("2d"),i.width,i.height);const t=new pc.Texture(this.app.graphicsDevice,{width:i.width,height:i.height,format:pc.PIXELFORMAT_R8_G8_B8_A8});return t.setSource(i),t}applyMaterialPreset(e,i){if(!e||!e.render)return;const t=new pc.StandardMaterial;switch(i){case"wood":const e=this.getWoodTexture();e&&(t.diffuseMap=e,t.emissiveMap=e),t.roughness=.85,t.useMetalness=!0,t.metalness=.05;break;case"metal":const i=this.getMetalTexture();i&&(t.diffuseMap=i,t.emissiveMap=i),t.roughness=.35,t.useMetalness=!0,t.metalness=.8;break;case"plastic":const r=this.getPlasticTexture();r&&(t.diffuseMap=r,t.emissiveMap=r),t.roughness=.6,t.useMetalness=!0,t.metalness=.2}t.emissive=new pc.Color(1,1,1),t.update(),e.render.material=t}getWoodTexture(){if(!this.textureCache.has("wood")){const e=this.createTexture((e,i,t)=>{e.fillStyle="#7a5533",e.fillRect(0,0,i,t);for(let r=0;r<10;r++)e.fillStyle=r%2?"#6d4b2d":"#805a38",e.fillRect(0,r*(t/10),i,t/20);e.globalAlpha=.15,e.fillStyle="#000";for(let r=0;r<24;r++)e.beginPath(),e.arc(13*r%i,29*r%t,6+7*r%5,0,2*Math.PI),e.fill();e.globalAlpha=1});e&&this.textureCache.set("wood",e)}return this.textureCache.get("wood")||null}getMetalTexture(){if(!this.textureCache.has("metal")){const e=this.createTexture((e,i,t)=>{const r=e.createLinearGradient(0,0,i,t);r.addColorStop(0,"#d7dbe0"),r.addColorStop(.5,"#aeb6bf"),r.addColorStop(1,"#e3e6ea"),e.fillStyle=r,e.fillRect(0,0,i,t),e.globalAlpha=.15,e.fillStyle="#fff";for(let r=0;r<40;r++)e.fillRect(11*r%i,23*r%t,2,2);e.globalAlpha=1});e&&this.textureCache.set("metal",e)}return this.textureCache.get("metal")||null}getPlasticTexture(){if(!this.textureCache.has("plastic")){const e=this.createTexture((e,i,t)=>{e.fillStyle="#2a3b58",e.fillRect(0,0,i,t),e.globalAlpha=.25,e.fillStyle="#3f567f";for(let r=0;r<80;r++)e.fillRect(17*r%i,13*r%t,3,3);e.globalAlpha=1});e&&this.textureCache.set("plastic",e)}return this.textureCache.get("plastic")||null}}const o={windmill:{op:"group",name:"Windmill",children:[{type:"prim",primitive:"cylinder",name:"Tower",pos:[0,3,0],scl:[1.2,6,1.2],color:[.85,.85,.82]},{type:"prim",primitive:"cone",name:"Cap",pos:[0,6.8,0],scl:[1.4,1.1,1.4],color:[.6,.6,.6]},{op:"group",name:"Hub",pos:[0,6.4,1],children:[{type:"prim",primitive:"sphere",scl:[.35,.35,.35],color:[.4,.4,.45]},{op:"radial",name:"Blades",count:4,radius:.2,axis:"z",children:[{type:"prim",primitive:"box",name:"Blade",pos:[1.5,0,0],scl:[3,.2,.1],color:[.9,.9,.9]}]}],spin:{rpm:12,axis:[0,0,1]}}]},ferrisWheel:{op:"group",name:"FerrisWheel",children:[{type:"prim",primitive:"box",name:"StandL",pos:[-2,1,0],scl:[.3,2,.3],color:[.4,.42,.45]},{type:"prim",primitive:"box",name:"StandR",pos:[2,1,0],scl:[.3,2,.3],color:[.4,.42,.45]},{op:"group",name:"Wheel",pos:[0,2.2,0],rot:[0,0,0],children:[{op:"radial",name:"RimSegs",count:20,radius:2,axis:"x",children:[{type:"prim",primitive:"box",name:"RimSeg",pos:[0,0,0],scl:[.1,.15,.4],color:[.8,.2,.2]}]},{op:"radial",name:"Spokes",count:8,radius:0,axis:"x",children:[{type:"prim",primitive:"box",name:"Spoke",pos:[0,1,0],scl:[.05,2,.05],color:[.7,.7,.75]}]},{op:"radial",name:"Cabins",count:8,radius:2,axis:"x",children:[{type:"prim",primitive:"box",name:"Cabin",pos:[0,-.4,0],scl:[.3,.4,.5],color:[.1,.4,.85]}]},{type:"prim",primitive:"cylinder",name:"Hub",pos:[0,0,0],rot:[0,0,90],scl:[.2,.3,.2],color:[.3,.3,.35]}],spin:{rpm:2,axis:[1,0,0]}}]},workbench:{op:"group",name:"Workbench",children:[{type:"prim",primitive:"box",name:"Top",pos:[0,1,0],scl:[2,.12,1],color:[.7,.55,.4]},{op:"corners",name:"Legs",dx:.9,dz:.45,children:[{type:"prim",primitive:"cylinder",pos:[0,.47,0],scl:[.12,.94,.12],color:[.4,.3,.2]}]}]},industrialFan:{op:"group",name:"IndustrialFan",children:[{type:"prim",primitive:"cylinder",name:"Base",pos:[0,.4,0],scl:[.4,.1,.4],color:[.25,.25,.28]},{type:"prim",primitive:"cylinder",name:"Pole",pos:[0,1.2,0],scl:[.05,.8,.05],color:[.3,.3,.34]},{op:"group",name:"Head",pos:[0,1.9,0],children:[{type:"prim",primitive:"sphere",name:"Hub",scl:[.18,.18,.18],color:[.6,.6,.65]},{op:"radial",name:"FanBlades",count:5,radius:.15,axis:"z",children:[{type:"prim",primitive:"box",name:"Blade",pos:[.9,0,0],scl:[1.8,.05,.18],color:[.9,.9,.95]}]}],spin:{rpm:15,axis:[0,0,1]}}]},toolRack:{op:"group",name:"ToolRack",children:[{type:"prim",primitive:"box",name:"Frame",pos:[0,1,0],scl:[.1,2,1.5],color:[.3,.3,.3]},{op:"radial",name:"Tools",count:8,radius:.6,axis:"y",center:[0,1,0],children:[{type:"prim",primitive:"cylinder",name:"Tool",pos:[0,0,.3],scl:[.05,.8,.05],color:[.8,.6,.2]}]}]}},n={id:"kitbash_gallery",title:"Kitbash Gallery",type:"scene",async build(e,i,t){console.log("[KitbashGallery] Building procedural object gallery...");const o=i.createRoom({size:[30,6,20],lighting:"bright",position:[0,0,0]});o.name="KitbashGallery";const n=new r(e);return this.proceduralBuilder=n,this.createGalleryDisplays(o,n,t),this.createInfoPanels(o,t),this.addGalleryLighting(o,t),console.log("[KitbashGallery] Gallery construction complete"),o},createGalleryDisplays(e,i,t){[{recipe:"windmill",position:[-8,0,-5],label:"Windmill\nRadial Blades + Spin"},{recipe:"ferrisWheel",position:[0,0,-5],label:"Ferris Wheel\nComplex Radial + Animation"},{recipe:"industrialFan",position:[8,0,-5],label:"Industrial Fan\nSpinner System"},{recipe:"workbench",position:[-8,0,2],label:"Workbench\nCorners Operation"},{recipe:"toolRack",position:[0,0,2],label:"Tool Rack\nRadial Tool Array"},{recipe:"hydraulicLift",position:[8,0,2],label:"Hydraulic Lift\nMirror + Group Operations"},{recipe:"serverRack",position:[-8,0,8],label:"Server Rack\nStacked Components"},{recipe:"assemblyLine",position:[0,0,8],label:"Assembly Line\nLinear Arrangement"},{recipe:"controlPanel",position:[8,0,8],label:"Control Panel\nInterface Elements"}].forEach((r,n)=>{const a=this.createDisplayPlatform(e,t,r.position,n);let l=o[r.recipe];if(l||(l=this.getCustomRecipe(r.recipe)),l){const e=i.build(l,a);e.addComponent("collision",{type:"box",halfExtents:[2,2,2]}),e.tags.add("interactive"),e.tags.add("procedural_object"),console.log(`[KitbashGallery] Created ${r.recipe} at ${r.position}`)}this.createLabel(a,t,r.label,[0,3,0])})},createDisplayPlatform(e,i,t,r){const o=i.create(`Platform_${r}`,{components:{render:{type:"cylinder"},collision:{type:"cylinder",radius:2,height:.2}},position:[t[0],t[1]-.1,t[2]],scale:[2,.2,2],tags:["platform"]}),n=new pc.StandardMaterial;return n.diffuse=new pc.Color(.8,.8,.9),n.emissive=new pc.Color(.1,.1,.15),n.emissiveIntensity=.2,n.metalness=.3,n.roughness=.7,n.update(),o.render.material=n,e.addChild(o),o},createInfoPanels(e,i){const t=i.create("InfoPanel",{components:{render:{type:"plane"}},position:[0,4,-8],rotation:[0,0,0],scale:[6,2,1],tags:["info_panel"]});this.createInfoMaterial(t,"PROCEDURAL KITBASH GALLERY","Explore complex 3D objects built from simple operations:\nβ€’ Group: Container hierarchy\nβ€’ Mirror: Symmetric copies\nβ€’ Radial: Circular arrangements\nβ€’ Corners: 4-point placement\nβ€’ Spinning animations and materials"),e.addChild(t);const r=i.create("ControlsPanel",{components:{render:{type:"plane"}},position:[0,2,-8],rotation:[0,0,0],scale:[4,1,1],tags:["controls_panel"]});this.createInfoMaterial(r,"CONTROLS","F - Inspect procedural object details\nWASD - Move around gallery\nMouse - Look around"),e.addChild(r)},createLabel(e,i,t,r){const o=i.create("Label",{components:{render:{type:"plane"}},position:r,rotation:[-15,0,0],scale:[2,1,1],tags:["label"]});return this.createInfoMaterial(o,"",t,"#000","#fff"),e.addChild(o),o},createInfoMaterial(e,i,t,r="#1a1a2e",o="#00ff88"){const n=new pc.StandardMaterial;try{const a=document.createElement("canvas");a.width=512,a.height=256;const l=a.getContext("2d");l.fillStyle=r,l.fillRect(0,0,a.width,a.height),i&&(l.fillStyle=o,l.font="bold 24px Arial, sans-serif",l.textAlign="center",l.fillText(i,a.width/2,40)),l.fillStyle=o,l.font="16px Arial, sans-serif",l.textAlign="center";const s=t.split("\\n"),c=i?80:40,p=20;s.forEach((e,i)=>{l.fillText(e,a.width/2,c+i*p)});let d=null;if(e&&e.element&&e.element.app&&(d=e.element.app.graphicsDevice),!d&&window.app&&(d=window.app.graphicsDevice),d){const e=new pc.Texture(d,{width:a.width,height:a.height,format:pc.PIXELFORMAT_R8_G8_B8_A8});e.setSource(a),n.diffuseMap=e,n.emissiveMap=e,n.emissive=new pc.Color(1,1,1),n.emissiveIntensity=.8}else console.warn("[KitbashGallery] Graphics device not available, using simple material"),n.diffuse=new pc.Color(.1,.1,.2),n.emissive=new pc.Color(0,1,.5),n.emissiveIntensity=.5}catch(e){console.warn("[KitbashGallery] Error creating info material:",e),n.diffuse=new pc.Color(.1,.1,.2),n.emissive=new pc.Color(0,1,.5),n.emissiveIntensity=.5}n.cull=pc.CULLFACE_NONE,n.update(),e.render.material=n},addGalleryLighting(e,i){for(let t=0;t<3;t++)for(let r=0;r<3;r++){const o=i.create(`SpotLight_${t}_${r}`,{components:{light:{type:"spot",color:new pc.Color(1,1,.9),intensity:1.2,range:15,innerConeAngle:20,outerConeAngle:35,castShadows:!1}},position:[8*t-8,5,6.5*r-5],rotation:[60,0,0],tags:["gallery_light"]});e.addChild(o)}},getCustomRecipe:e=>({hydraulicLift:{op:"group",name:"HydraulicLift",children:[{type:"prim",primitive:"box",name:"Base",pos:[0,.2,0],scl:[2,.4,1.5],color:[.3,.3,.35]},{op:"mirror",axis:"x",children:[{type:"prim",primitive:"cylinder",name:"Piston",pos:[.8,1.5,0],scl:[.15,3,.15],color:[.8,.8,.85]}]},{type:"prim",primitive:"box",name:"Platform",pos:[0,2.8,0],scl:[2,.2,1.5],color:[.7,.7,.75]}]},serverRack:{op:"group",name:"ServerRack",children:[{type:"prim",primitive:"box",name:"Frame",pos:[0,1,0],scl:[1,2,.6],color:[.1,.1,.12]},{op:"group",name:"Servers",children:Array.from({length:8},(e,i)=>({type:"prim",primitive:"box",name:`Server_${i}`,pos:[0,.3+.2*i,.25],scl:[.9,.08,.4],color:[.2,.2,.25]}))}]},assemblyLine:{op:"group",name:"AssemblyLine",children:[{type:"prim",primitive:"box",name:"ConveyorBelt",pos:[0,.8,0],scl:[6,.1,1],color:[.2,.2,.2]},{op:"group",name:"Supports",children:Array.from({length:4},(e,i)=>({type:"prim",primitive:"cylinder",name:`Support_${i}`,pos:[1.67*i-2.5,.4,0],scl:[.08,.8,.08],color:[.4,.4,.45]}))},{op:"radial",name:"RollerDrum",count:12,radius:.3,axis:"x",center:[2.5,.8,0],children:[{type:"prim",primitive:"cylinder",name:"Roller",pos:[0,0,.25],scl:[.02,.02,.5],color:[.6,.6,.65]}],spin:{rpm:10,axis:[1,0,0]}}]},controlPanel:{op:"group",name:"ControlPanel",children:[{type:"prim",primitive:"box",name:"MainPanel",pos:[0,1.2,0],scl:[2,1.5,.2],color:[.15,.15,.18]},{op:"radial",name:"ButtonArray",count:12,radius:.4,axis:"z",center:[0,1.2,.15],children:[{type:"prim",primitive:"cylinder",name:"Button",scl:[.05,.05,.05],color:[.8,.2,.2]}]},{type:"prim",primitive:"box",name:"Screen",pos:[0,1.5,.12],scl:[.8,.4,.02],color:[.1,.3,.1]},{type:"prim",primitive:"cylinder",name:"Base",pos:[0,.3,0],scl:[.8,.6,.8],color:[.25,.25,.28]}]}}[e]||null),onLoad(e,i){this.setupGalleryInteractions(e,i),e.fire("ui:notification",{text:"Welcome to the Procedural Kitbash Gallery! Press F to inspect objects.",type:"info",duration:4e3}),console.log("[KitbashGallery] Gallery loaded and ready for exploration")},setupGalleryInteractions(e,i){this.interactionHandler=i=>{const t=e.root.findByName(i);t&&t.tags.has("procedural_object")?this.showObjectDetails(e,t):e.fire("ui:notification",{text:`Inspected: ${i}`,type:"info",duration:2e3})},e.on("interaction:triggered",this.interactionHandler)},showObjectDetails(e,i){let t=0;const r=e=>{e.render&&t++,e.children.forEach(e=>r(e))};r(i);let o="Static object";if(this.proceduralBuilder){const e=this.proceduralBuilder.spinners.filter(e=>e.entity===i||this.isChildOf(e.entity,i)).length;e>0&&(o=`${e} spinning component(s)`)}e.fire("ui:notification",{text:`${i.name}: ${t} components, ${o}`,type:"success",duration:3e3}),console.log("[KitbashGallery] Object details:",{name:i.name,components:t,position:i.getPosition().toString(),children:i.children.length})},isChildOf(e,i){let t=e.parent;for(;t;){if(t===i)return!0;t=t.parent}return!1},onUnload(e,i){console.log("[KitbashGallery] Cleaning up gallery..."),this.interactionHandler&&(e.off("interaction:triggered",this.interactionHandler),this.interactionHandler=null),this.proceduralBuilder&&(this.proceduralBuilder.cleanup(),this.proceduralBuilder=null)}}}}]);
 
1
+ "use strict";(self.webpackChunkplaycanvas_game=self.webpackChunkplaycanvas_game||[]).push([[565],{565:(e,i,r)=>{r.r(i),r.d(i,{default:()=>t});class o{constructor(e){this.app=e,this.spinners=[],this.oscillators=[],this.drivers=[],this.textureCache=new Map,this.app.on("update",e=>this.updateAnimations(e))}build(e,i){const r=new pc.Entity(e.name||"ProceduralObject");return i.addChild(r),this.buildNode(e,r),r}buildNode(e,i){if(!e)return null;const r=(e.op||e.type||"prim").toLowerCase();if("group"===r){const r=new pc.Entity(e.name||"group");return i.addChild(r),this.applyTransform(r,e),(e.children||[]).forEach(e=>this.buildNode(e,r)),e.spin&&this.addSpinner(r,e.spin),r}if("mirror"===r){const r=(e.axis||"x").toLowerCase(),o=new pc.Entity(e.name||`mirror_${r}`);i.addChild(o),(e.children||[]).forEach(e=>this.buildNode(e,o));const n=o.clone();o.parent.addChild(n);const t=new pc.Vec3(1,1,1);return"x"===r&&(t.x=-1),"y"===r&&(t.y=-1),"z"===r&&(t.z=-1),n.setLocalScale(t),o}if("radial"===r){const r=e.count||4,o=e.radius||1,n=e.phase||0,t=(e.axis||"y").toLowerCase(),a=new pc.Entity(e.name||`radial_${t}`);i.addChild(a),e.center&&a.setLocalPosition(this.vec3(e.center));for(let i=0;i<r;i++){const l=new pc.Entity(`r_${i}`);a.addChild(l);const s=360/r*i+n;"x"===t?l.setLocalEulerAngles(s,0,0):"y"===t?l.setLocalEulerAngles(0,s,0):l.setLocalEulerAngles(0,0,s);const p=new pc.Entity(`o_${i}`);l.addChild(p),"z"===t?p.setLocalPosition(o,0,0):p.setLocalPosition(0,0,o),(e.children||[]).forEach(e=>this.buildNode(e,p))}return e.spin&&this.addSpinner(a,e.spin),a}if("corners"===r){const r=e.dx||1,o=e.dz||1,n=new pc.Entity(e.name||"corners");i.addChild(n),e.center&&n.setLocalPosition(this.vec3(e.center));const t=[[r,0,o],[r,0,-o],[-r,0,o],[-r,0,-o]];for(let i=0;i<t.length;i++){const r=new pc.Entity(`c_${i}`);n.addChild(r),r.setLocalPosition(t[i][0],t[i][1],t[i][2]),(e.children||[]).forEach(e=>this.buildNode(e,r))}return e.spin&&this.addSpinner(n,e.spin),n}const o=new pc.Entity(e.name||"part"),n=(e.primitive||"box").toLowerCase();return o.addComponent("render",{type:n}),this.applyTransform(o,e),o.render.material=this.makeMaterial(e),i.addChild(o),e.spin&&this.addSpinner(o,e.spin),o}applyTransform(e,i){i.pos&&e.setLocalPosition(this.vec3(i.pos)),i.rot&&e.setLocalEulerAngles(this.vec3(i.rot)),i.scl&&e.setLocalScale(this.vec3(i.scl))}vec3(e){return new pc.Vec3(e[0]||0,e[1]||0,e[2]||0)}color(e){return new pc.Color(e[0]||1,e[1]||1,e[2]||1)}makeMaterial(e){const i=new pc.StandardMaterial;if(e&&e.color){const r=this.color(e.color);i.diffuse=r.clone(),i.emissive=r.clone(),i.emissiveIntensity=.35}else i.diffuse=new pc.Color(.75,.75,.78),i.emissive=new pc.Color(.75,.75,.78),i.emissiveIntensity=.35;return i.useMetalness=!1,i.shadingModel=pc.SPECULAR_BLINN,i.roughness=1,i.update(),i}addSpinner(e,i){const r=i.rpm||24,o=new pc.Vec3(i.axis?.[0]||0,i.axis?.[1]||1,i.axis?.[2]||0);o.lengthSq()<1e-6&&o.set(0,1,0);const n=o.normalize(),t=e.getLocalRotation().clone();this.spinners.push({entity:e,rpm:r,axis:n,base:t,angle:0})}updateAnimations(e){for(let i=0;i<this.spinners.length;i++){const r=this.spinners[i],o=r.entity;if(o&&!o.destroyed){r.angle+=6*r.rpm*e;const i=new pc.Quat;i.setFromAxisAngle(r.axis,r.angle);const n=r.base.clone();n.mul(i),o.setLocalRotation(n)}}for(let i=0;i<this.oscillators.length;i++){const r=this.oscillators[i];if(r.entity&&!r.entity.destroyed){r.t+=e;const i=r.base+Math.sin(2*r.t*Math.PI*r.speed+r.phase)*r.amp,o=r.entity.getLocalEulerAngles();"x"===r.axis&&r.entity.setLocalEulerAngles(i,o.y,o.z),"y"===r.axis&&r.entity.setLocalEulerAngles(o.x,i,o.z),"z"===r.axis&&r.entity.setLocalEulerAngles(o.x,o.y,i)}}for(let i=0;i<this.drivers.length;i++)try{this.drivers[i](e)}catch(e){console.warn("Driver error:",e)}}addOscillator(e,i,r,o,n=0,t=0){this.oscillators.push({entity:e,axis:i,amp:r,speed:o,base:n,phase:t,t:0})}addDriver(e){this.drivers.push(e)}cleanup(){this.spinners.length=0,this.oscillators.length=0,this.drivers.length=0,this.textureCache.clear()}createTexture(e){if(!this.app.graphicsDevice)return console.warn("[ProceduralBuilder] Graphics device not available for texture creation"),null;const i=document.createElement("canvas");i.width=128,i.height=128,e(i.getContext("2d"),i.width,i.height);const r=new pc.Texture(this.app.graphicsDevice,{width:i.width,height:i.height,format:pc.PIXELFORMAT_R8_G8_B8_A8});return r.setSource(i),r}applyMaterialPreset(e,i){if(!e||!e.render)return;const r=new pc.StandardMaterial;switch(i){case"wood":const e=this.getWoodTexture();e&&(r.diffuseMap=e,r.emissiveMap=e),r.roughness=.85,r.useMetalness=!0,r.metalness=.05;break;case"metal":const i=this.getMetalTexture();i&&(r.diffuseMap=i,r.emissiveMap=i),r.roughness=.35,r.useMetalness=!0,r.metalness=.8;break;case"plastic":const o=this.getPlasticTexture();o&&(r.diffuseMap=o,r.emissiveMap=o),r.roughness=.6,r.useMetalness=!0,r.metalness=.2}r.emissive=new pc.Color(1,1,1),r.update(),e.render.material=r}getWoodTexture(){if(!this.textureCache.has("wood")){const e=this.createTexture((e,i,r)=>{e.fillStyle="#7a5533",e.fillRect(0,0,i,r);for(let o=0;o<10;o++)e.fillStyle=o%2?"#6d4b2d":"#805a38",e.fillRect(0,o*(r/10),i,r/20);e.globalAlpha=.15,e.fillStyle="#000";for(let o=0;o<24;o++)e.beginPath(),e.arc(13*o%i,29*o%r,6+7*o%5,0,2*Math.PI),e.fill();e.globalAlpha=1});e&&this.textureCache.set("wood",e)}return this.textureCache.get("wood")||null}getMetalTexture(){if(!this.textureCache.has("metal")){const e=this.createTexture((e,i,r)=>{const o=e.createLinearGradient(0,0,i,r);o.addColorStop(0,"#d7dbe0"),o.addColorStop(.5,"#aeb6bf"),o.addColorStop(1,"#e3e6ea"),e.fillStyle=o,e.fillRect(0,0,i,r),e.globalAlpha=.15,e.fillStyle="#fff";for(let o=0;o<40;o++)e.fillRect(11*o%i,23*o%r,2,2);e.globalAlpha=1});e&&this.textureCache.set("metal",e)}return this.textureCache.get("metal")||null}getPlasticTexture(){if(!this.textureCache.has("plastic")){const e=this.createTexture((e,i,r)=>{e.fillStyle="#2a3b58",e.fillRect(0,0,i,r),e.globalAlpha=.25,e.fillStyle="#3f567f";for(let o=0;o<80;o++)e.fillRect(17*o%i,13*o%r,3,3);e.globalAlpha=1});e&&this.textureCache.set("plastic",e)}return this.textureCache.get("plastic")||null}}const n={windmill:{op:"group",name:"Windmill",children:[{type:"prim",primitive:"cylinder",name:"Tower",pos:[0,3,0],scl:[1.2,6,1.2],color:[.85,.85,.82]},{type:"prim",primitive:"cone",name:"Cap",pos:[0,6.8,0],scl:[1.4,1.1,1.4],color:[.6,.6,.6]},{op:"group",name:"Hub",pos:[0,6.4,1],children:[{type:"prim",primitive:"sphere",scl:[.35,.35,.35],color:[.4,.4,.45]},{op:"radial",name:"Blades",count:4,radius:.2,axis:"x",children:[{type:"prim",primitive:"box",name:"Blade",pos:[0,1.5,0],scl:[.1,3,.2],color:[.9,.9,.9]}]}],spin:{rpm:12,axis:[1,0,0]}}]},ferrisWheel:{op:"group",name:"FerrisWheel",children:[{type:"prim",primitive:"box",name:"StandL",pos:[-2,1,0],scl:[.3,2,.3],color:[.4,.42,.45]},{type:"prim",primitive:"box",name:"StandR",pos:[2,1,0],scl:[.3,2,.3],color:[.4,.42,.45]},{op:"group",name:"Wheel",pos:[0,2.2,0],rot:[0,0,0],children:[{op:"radial",name:"RimSegs",count:20,radius:2,axis:"x",children:[{type:"prim",primitive:"box",name:"RimSeg",pos:[0,0,0],scl:[.1,.15,.4],color:[.8,.2,.2]}]},{op:"radial",name:"Spokes",count:8,radius:0,axis:"x",children:[{type:"prim",primitive:"box",name:"Spoke",pos:[0,1,0],scl:[.05,2,.05],color:[.7,.7,.75]}]},{op:"radial",name:"Cabins",count:8,radius:2,axis:"x",children:[{type:"prim",primitive:"box",name:"Cabin",pos:[0,-.4,0],scl:[.3,.4,.5],color:[.1,.4,.85]}]},{type:"prim",primitive:"cylinder",name:"Hub",pos:[0,0,0],rot:[0,0,90],scl:[.2,.3,.2],color:[.3,.3,.35]}],spin:{rpm:2,axis:[1,0,0]}}]},workbench:{op:"group",name:"Workbench",children:[{type:"prim",primitive:"box",name:"Top",pos:[0,1,0],scl:[2,.12,1],color:[.7,.55,.4]},{op:"corners",name:"Legs",dx:.9,dz:.45,children:[{type:"prim",primitive:"cylinder",pos:[0,.47,0],scl:[.12,.94,.12],color:[.4,.3,.2]}]}]},industrialFan:{op:"group",name:"IndustrialFan",children:[{type:"prim",primitive:"cylinder",name:"Base",pos:[0,.4,0],scl:[.4,.1,.4],color:[.25,.25,.28]},{type:"prim",primitive:"cylinder",name:"Pole",pos:[0,1.2,0],scl:[.05,.8,.05],color:[.3,.3,.34]},{op:"group",name:"Head",pos:[0,1.9,0],children:[{type:"prim",primitive:"sphere",name:"Hub",scl:[.18,.18,.18],color:[.6,.6,.65]},{op:"radial",name:"FanBlades",count:5,radius:.15,axis:"z",children:[{type:"prim",primitive:"box",name:"Blade",pos:[.9,0,0],scl:[1.8,.05,.18],color:[.9,.9,.95]}]}],spin:{rpm:15,axis:[0,0,1]}}]},toolRack:{op:"group",name:"ToolRack",children:[{type:"prim",primitive:"box",name:"Frame",pos:[0,1,0],scl:[.1,2,1.5],color:[.3,.3,.3]},{op:"radial",name:"Tools",count:8,radius:.6,axis:"y",center:[0,1,0],children:[{type:"prim",primitive:"cylinder",name:"Tool",pos:[0,0,.3],scl:[.05,.8,.05],color:[.8,.6,.2]}]}]},kitchenBlender:{op:"group",name:"KitchenBlender",children:[{type:"prim",primitive:"cylinder",name:"Base",pos:[0,.6,0],scl:[.6,1.2,.6],color:[.25,.25,.28]},{type:"prim",primitive:"cylinder",name:"Jar",pos:[0,1.6,0],scl:[.45,.9,.45],color:[.7,.9,1]},{op:"group",name:"BladeHub",pos:[0,1.2,0],children:[{op:"radial",name:"Blades",count:4,radius:.08,axis:"z",children:[{type:"prim",primitive:"box",name:"Blade",pos:[.4,0,0],scl:[.8,.05,.15],color:[.9,.9,.95]}]}],spin:{rpm:45,axis:[0,0,1]}}]},kitchenMicrowave:{op:"group",name:"KitchenMicrowave",children:[{type:"prim",primitive:"box",name:"Body",pos:[0,1.2,0],scl:[1.6,.9,1.2],color:[.2,.2,.22]},{op:"group",name:"DoorPivot",pos:[.85,1.2,.55],children:[{type:"prim",primitive:"box",name:"Door",pos:[0,0,0],scl:[.05,.8,1.1],color:[.35,.35,.38]}]}]},kitchenRangeHood:{op:"group",name:"KitchenRangeHood",children:[{type:"prim",primitive:"box",name:"Hood",pos:[0,2.2,0],scl:[2,.4,1.2],color:[.3,.3,.35]},{op:"group",name:"Fan",pos:[0,2.2,.3],children:[{type:"prim",primitive:"sphere",scl:[.12,.12,.12],color:[.6,.6,.65]},{op:"radial",name:"FanBlades",count:5,radius:.1,axis:"z",children:[{type:"prim",primitive:"box",name:"Blade",pos:[.8,0,0],scl:[1.6,.08,.18],color:[.9,.9,.95]}]}],spin:{rpm:20,axis:[0,0,1]}}]},bathExhaustFan:{op:"group",name:"BathExhaustFan",children:[{type:"prim",primitive:"box",name:"Mount",pos:[0,2.6,0],scl:[1.2,.2,1.2],color:[.3,.3,.35]},{op:"group",name:"FanHead",pos:[0,2.6,.01],children:[{type:"prim",primitive:"sphere",scl:[.1,.1,.1],color:[.6,.6,.65]},{op:"radial",name:"FanBlades",count:4,radius:.08,axis:"z",children:[{type:"prim",primitive:"box",name:"Blade",pos:[.7,0,0],scl:[1.4,.08,.18],color:[.9,.9,.95]}]}],spin:{rpm:18,axis:[0,0,1]}}]},bathShowerHead:{op:"group",name:"BathShowerHead",children:[{type:"prim",primitive:"cylinder",name:"Pipe",pos:[0,2,0],scl:[.08,1.2,.08],color:[.6,.6,.65]},{op:"group",name:"HeadPivot",pos:[0,2.6,0],children:[{type:"prim",primitive:"cone",name:"Head",pos:[.25,0,0],rot:[0,90,0],scl:[.35,.25,.35],color:[.75,.75,.78]}]}]},bathWasher:{op:"group",name:"BathWasher",children:[{type:"prim",primitive:"box",name:"Cabinet",pos:[0,1.2,0],scl:[1.6,1.6,1.4],color:[.85,.85,.9]},{op:"group",name:"Drum",pos:[0,1.2,.75],children:[{type:"prim",primitive:"cylinder",name:"DrumBody",rot:[0,0,90],scl:[.5,.5,.5],color:[.6,.6,.65]},{op:"radial",name:"DrumFins",count:8,radius:.45,axis:"z",children:[{type:"prim",primitive:"box",name:"Fin",pos:[.3,0,0],scl:[.6,.05,.1],color:[.8,.8,.85]}]}],spin:{rpm:12,axis:[0,0,1]}}]},livingPendulumClock:{op:"group",name:"PendulumClock",children:[{type:"prim",primitive:"box",name:"Case",pos:[0,1.8,0],scl:[1.2,3.6,.6],color:[.35,.25,.15]},{op:"group",name:"Pendulum",pos:[0,1.2,.25],children:[{type:"prim",primitive:"cylinder",name:"Rod",pos:[0,-.6,0],scl:[.04,1.2,.04],color:[.6,.6,.65]},{type:"prim",primitive:"sphere",name:"Bob",pos:[0,-1.3,0],scl:[.25,.25,.25],color:[.8,.7,.2]}]}]},livingRecordPlayer:{op:"group",name:"RecordPlayer",children:[{type:"prim",primitive:"box",name:"Plinth",pos:[0,.8,0],scl:[1.6,.2,1.6],color:[.15,.15,.18]},{op:"group",name:"Turntable",pos:[0,.95,0],children:[{type:"prim",primitive:"cylinder",name:"Disc",scl:[1,.05,1],color:[.05,.05,.05]}],spin:{rpm:33,axis:[0,1,0]}}]},livingTableFan:{op:"group",name:"TableFan",children:[{type:"prim",primitive:"cylinder",name:"Stand",pos:[0,1.1,0],scl:[.12,.9,.12],color:[.3,.3,.35]},{op:"group",name:"Head",pos:[0,1.8,0],children:[{type:"prim",primitive:"sphere",name:"Hub",scl:[.12,.12,.12],color:[.6,.6,.65]},{op:"radial",name:"Blades",count:4,radius:.1,axis:"z",children:[{type:"prim",primitive:"box",name:"Blade",pos:[.9,0,0],scl:[1.8,.08,.2],color:[.9,.9,.95]}]}],spin:{rpm:16,axis:[0,0,1]}}]},garageDrillPress:{op:"group",name:"DrillPress",children:[{type:"prim",primitive:"box",name:"Base",pos:[0,.5,0],scl:[1.2,.2,1.2],color:[.2,.2,.22]},{type:"prim",primitive:"cylinder",name:"Column",pos:[0,1.6,0],scl:[.18,2.2,.18],color:[.35,.35,.38]},{type:"prim",primitive:"box",name:"Table",pos:[0,1.2,0],scl:[1.2,.12,1],color:[.25,.25,.28]},{op:"group",name:"Spindle",pos:[0,2.2,0],children:[{type:"prim",primitive:"cylinder",name:"Chuck",scl:[.15,.25,.15],color:[.6,.6,.65]}],spin:{rpm:40,axis:[0,-1,0]}}]},garageBenchGrinder:{op:"group",name:"BenchGrinder",children:[{type:"prim",primitive:"box",name:"Body",pos:[0,1.2,0],scl:[1.6,.8,.6],color:[.2,.2,.22]},{op:"group",name:"WheelL",pos:[-.9,1.2,0],children:[{type:"prim",primitive:"cylinder",name:"DiscL",rot:[0,0,90],scl:[.5,.25,.5],color:[.7,.7,.75]}],spin:{rpm:25,axis:[1,0,0]}},{op:"group",name:"WheelR",pos:[.9,1.2,0],children:[{type:"prim",primitive:"cylinder",name:"DiscR",rot:[0,0,90],scl:[.5,.25,.5],color:[.7,.7,.75]}],spin:{rpm:25,axis:[-1,0,0]}}]},garageShopLight:{op:"group",name:"ShopLight",children:[{type:"prim",primitive:"cylinder",name:"Mount",pos:[0,2.6,0],scl:[.08,.3,.08],color:[.35,.35,.38]},{op:"group",name:"LightBar",pos:[0,2.8,0],children:[{type:"prim",primitive:"box",name:"Bar",scl:[2.4,.12,.3],color:[.95,.95,.85]}]}]}},t={id:"kitbash_gallery",title:"Kitbash Gallery",type:"scene",async build(e,i,r){console.log("[KitbashGallery] Building procedural object gallery...");const t=i.createRoom({size:[48,8,32],lighting:"bright",position:[0,0,0]});t.name="KitbashGallery";const a=new o(e);return this.proceduralBuilder=a,[{recipe:"windmill",position:[-8,0,-5],label:"Windmill\nRadial Blades + Spin"},{recipe:"ferrisWheel",position:[0,0,-5],label:"Ferris Wheel\nComplex Radial + Animation"},{recipe:"industrialFan",position:[8,0,-5],label:"Industrial Fan\nSpinner System"},{recipe:"workbench",position:[-8,0,2],label:"Workbench\nCorners Operation"},{recipe:"toolRack",position:[0,0,2],label:"Tool Rack\nRadial Tool Array"},{recipe:"hydraulicLift",position:[8,0,2],label:"Hydraulic Lift\nMirror + Group Operations"},{recipe:"serverRack",position:[-8,0,8],label:"Server Rack\nStacked Components"},{recipe:"assemblyLine",position:[0,0,8],label:"Assembly Line\nLinear Arrangement"},{recipe:"controlPanel",position:[8,0,8],label:"Control Panel\nInterface Elements"},{recipe:"ceilingFan",position:[-16,0,12],label:"Ceiling Fan\nSpin (Y axis)"},{recipe:"radarDish",position:[-6,0,12],label:"Radar Dish\nOscillate (Yaw)"},{recipe:"gearPair",position:[4,0,12],label:"Gear Pair\nOpposed Spin"},{recipe:"propeller",position:[14,0,12],label:"Propeller\nSpin (Z axis)"},{recipe:"kitchenBlender",position:[-18,0,-12],label:"Kitchen: Blender\nBlade Spin"},{recipe:"kitchenMicrowave",position:[-10,0,-12],label:"Kitchen: Microwave\nDoor Oscillation"},{recipe:"kitchenRangeHood",position:[-2,0,-12],label:"Kitchen: Range Hood\nFan Spin"},{recipe:"bathExhaustFan",position:[6,0,-12],label:"Bathroom: Exhaust Fan\nSpin"},{recipe:"bathShowerHead",position:[14,0,-12],label:"Bathroom: Shower Head\nOscillate"},{recipe:"bathWasher",position:[22,0,-12],label:"Bathroom: Washing Machine\nDrum Spin"},{recipe:"livingPendulumClock",position:[-18,0,-2],label:"Living: Pendulum Clock\nPendulum Swing"},{recipe:"livingRecordPlayer",position:[-10,0,-2],label:"Living: Record Player\nTurntable Spin"},{recipe:"livingTableFan",position:[-2,0,-2],label:"Living: Table Fan\nSpin"},{recipe:"garageDrillPress",position:[6,0,-2],label:"Garage: Drill Press\nSpindle Spin"},{recipe:"garageBenchGrinder",position:[14,0,-2],label:"Garage: Bench Grinder\nDual Wheels"},{recipe:"garageShopLight",position:[22,0,-2],label:"Garage: Shop Light\nOscillate"}].forEach((e,i)=>{const o=r.create(`Platform_${i}`,{components:{render:{type:"cylinder"}},position:[e.position[0],e.position[1]-.1,e.position[2]],scale:[2,.2,2],tags:["platform"]});t.addChild(o);const l=new pc.Entity(`Display_${i}_Anchor`);l.setLocalPosition(e.position[0],e.position[1],e.position[2]),l.setLocalEulerAngles(0,0,0),l.setLocalScale(1,1,1),t.addChild(l);const s=r.create(`Spot_${i}`,{components:{light:{type:"point",color:new pc.Color(1,.98,.9),intensity:1.4,range:10,castShadows:!1}},position:[e.position[0],5,e.position[2]]});t.addChild(s);const p=this.getCustomRecipe(e.recipe)||n[e.recipe];if(!p)return void console.warn("[KitbashGallery] Missing recipe:",e.recipe);const c=a.build(p,l);c.addComponent("collision",{type:"box",halfExtents:[2,2,2]}),c.tags.add("interactive"),c.tags.add("procedural_object");try{if(this.proceduralBuilder){if("radarDish"===e.recipe){const e=c.findByName("DishPivot");e&&this.proceduralBuilder.addOscillator(e,"y",25,.2,0,0)}if("kitchenMicrowave"===e.recipe){const e=c.findByName("DoorPivot");e&&this.proceduralBuilder.addOscillator(e,"y",20,.25,0,0)}if("bathShowerHead"===e.recipe){const e=c.findByName("HeadPivot");e&&this.proceduralBuilder.addOscillator(e,"y",15,.35,0,0)}if("livingPendulumClock"===e.recipe){const e=c.findByName("Pendulum");e&&this.proceduralBuilder.addOscillator(e,"z",20,.5,0,0)}if("garageShopLight"===e.recipe){const e=c.findByName("LightBar");e&&this.proceduralBuilder.addOscillator(e,"y",10,.25,0,0)}}}catch(e){console.warn("[KitbashGallery] Oscillator attach skipped:",e)}this.createLabel(o,r,e.label,[0,3,0])}),this.createInfoPanels(t,r),this.addGalleryLighting(t,r),console.log("[KitbashGallery] Gallery construction complete"),t},createInfoPanels(e,i){const r=i.create("InfoPanel",{components:{render:{type:"plane"}},position:[0,4,-8],rotation:[0,0,0],scale:[6,2,1],tags:["info_panel"]});this.createInfoMaterial(r,"PROCEDURAL KITBASH GALLERY","Explore complex 3D objects built from simple ops:\n Group / Mirror / Radial / Corners\n Spinning animations and materials"),e.addChild(r);const o=i.create("ControlsPanel",{components:{render:{type:"plane"}},position:[0,2.5,-8],rotation:[0,0,0],scale:[4,1,1],tags:["controls_panel"]});this.createInfoMaterial(o,"CONTROLS","F - Inspect object details\nWASD - Move\nMouse - Look"),e.addChild(o)},createLabel(e,i,r,o){const n=i.create("Label",{components:{render:{type:"plane"}},position:o,rotation:[-15,0,0],scale:[2,1,1],tags:["label"]});return this.createInfoMaterial(n,"",r,"#000","#fff"),e.addChild(n),n},createInfoMaterial(e,i,r,o="#1a1a2e",n="#00ff88"){const t=new pc.StandardMaterial;try{const a=document.createElement("canvas");a.width=512,a.height=256;const l=a.getContext("2d");l.fillStyle=o,l.fillRect(0,0,a.width,a.height),i&&(l.fillStyle=n,l.font="bold 24px Arial, sans-serif",l.textAlign="center",l.fillText(i,a.width/2,40)),l.fillStyle=n,l.font="16px Arial, sans-serif",l.textAlign="center";const s=(r||"").split("\n"),p=i?80:40,c=20;s.forEach((e,i)=>l.fillText(e,a.width/2,p+i*c));const d=e?.element?.app?.graphicsDevice||window.app?.graphicsDevice;if(d){const e=new pc.Texture(d,{width:a.width,height:a.height,format:pc.PIXELFORMAT_R8_G8_B8_A8});e.setSource(a),t.diffuseMap=e,t.emissiveMap=e,t.emissive=new pc.Color(1,1,1),t.emissiveIntensity=.8}else t.diffuse=new pc.Color(.1,.1,.2),t.emissive=new pc.Color(0,1,.5),t.emissiveIntensity=.5}catch(e){t.diffuse=new pc.Color(.1,.1,.2),t.emissive=new pc.Color(0,1,.5),t.emissiveIntensity=.5}t.cull=pc.CULLFACE_NONE,t.update(),e.render.material=t},addGalleryLighting(e,i){for(let r=0;r<3;r++)for(let o=0;o<3;o++){const n=i.create(`GridLight_${r}_${o}`,{components:{light:{type:"spot",color:new pc.Color(1,1,.9),intensity:1.1,range:18,innerConeAngle:20,outerConeAngle:35,castShadows:!1}},position:[8*r-8,6,6.5*o-5],rotation:[60,0,0],tags:["gallery_light"]});e.addChild(n)}},getCustomRecipe:e=>({windmill:{op:"group",name:"Windmill",children:[{type:"prim",primitive:"cylinder",name:"Tower",pos:[0,3,0],scl:[1.2,6,1.2],color:[.85,.85,.82]},{type:"prim",primitive:"cone",name:"Cap",pos:[0,6.8,0],scl:[1.4,1.1,1.4],color:[.6,.6,.6]},{op:"group",name:"Hub",pos:[0,6.4,1],children:[{type:"prim",primitive:"sphere",scl:[.35,.35,.35],color:[.4,.4,.45]},{op:"radial",name:"Blades",count:4,radius:.2,axis:"x",children:[{type:"prim",primitive:"box",name:"Blade",pos:[0,1.5,0],scl:[.1,3,.2],color:[.9,.9,.9]}]}],spin:{rpm:12,axis:[1,0,0]}}]},ferrisWheel:{op:"group",name:"FerrisWheel",children:[{type:"prim",primitive:"box",name:"StandL",pos:[-2,1,0],scl:[.3,2,.3],color:[.4,.42,.45]},{type:"prim",primitive:"box",name:"StandR",pos:[2,1,0],scl:[.3,2,.3],color:[.4,.42,.45]},{op:"group",name:"Wheel",pos:[0,2.2,0],children:[{op:"radial",name:"RimSegs",count:20,radius:2,axis:"x",children:[{type:"prim",primitive:"box",name:"RimSeg",pos:[0,0,0],scl:[.1,.15,.4],color:[.8,.2,.2]}]},{op:"radial",name:"Spokes",count:8,radius:0,axis:"x",children:[{type:"prim",primitive:"box",name:"Spoke",pos:[0,1,0],scl:[.05,2,.05],color:[.7,.7,.75]}]},{op:"radial",name:"Cabins",count:8,radius:2,axis:"x",children:[{type:"prim",primitive:"box",name:"Cabin",pos:[0,-.4,0],scl:[.3,.4,.5],color:[.1,.4,.85]}]},{type:"prim",primitive:"cylinder",name:"Hub",pos:[0,0,0],rot:[0,0,90],scl:[.2,.3,.2],color:[.3,.3,.35]}],spin:{rpm:2,axis:[1,0,0]}}]},industrialFan:{op:"group",name:"IndustrialFan",children:[{type:"prim",primitive:"cylinder",name:"Base",pos:[0,.4,0],scl:[.4,.1,.4],color:[.25,.25,.28]},{type:"prim",primitive:"cylinder",name:"Pole",pos:[0,1.2,0],scl:[.05,.8,.05],color:[.3,.3,.34]},{op:"group",name:"Head",pos:[0,1.9,0],children:[{type:"prim",primitive:"sphere",name:"Hub",scl:[.18,.18,.18],color:[.6,.6,.65]},{op:"radial",name:"FanBlades",count:5,radius:.15,axis:"z",children:[{type:"prim",primitive:"box",name:"Blade",pos:[.9,0,0],scl:[1.8,.05,.18],color:[.9,.9,.95]}]}],spin:{rpm:15,axis:[0,0,1]}}]},workbench:{op:"group",name:"Workbench",children:[{type:"prim",primitive:"box",name:"Top",pos:[0,1,0],scl:[2,.12,1],color:[.7,.55,.4]},{op:"corners",name:"Legs",dx:.9,dz:.45,children:[{type:"prim",primitive:"cylinder",pos:[0,.47,0],scl:[.12,.94,.12],color:[.4,.3,.2]}]}]},toolRack:{op:"group",name:"ToolRack",children:[{type:"prim",primitive:"box",name:"Frame",pos:[0,1,0],scl:[.1,2,1.5],color:[.3,.3,.3]},{op:"radial",name:"Tools",count:8,radius:.6,axis:"y",center:[0,1,0],children:[{type:"prim",primitive:"cylinder",name:"Tool",pos:[0,0,.3],scl:[.05,.8,.05],color:[.8,.6,.2]}]}]},serverRack:{op:"group",name:"ServerRack",children:[{type:"prim",primitive:"box",name:"Frame",pos:[0,1,0],scl:[1,2,.6],color:[.1,.1,.12]},{op:"group",name:"Servers",children:Array.from({length:8},(e,i)=>({type:"prim",primitive:"box",name:`Server_${i}`,pos:[0,.3+.2*i,.25],scl:[.9,.08,.4],color:[.2,.2,.25]}))}]},assemblyLine:{op:"group",name:"AssemblyLine",children:[{type:"prim",primitive:"box",name:"ConveyorBelt",pos:[0,.8,0],scl:[6,.1,1],color:[.2,.2,.2]},{op:"group",name:"Supports",children:Array.from({length:4},(e,i)=>({type:"prim",primitive:"cylinder",name:`Support_${i}`,pos:[1.67*i-2.5,.4,0],scl:[.08,.8,.08],color:[.4,.4,.45]}))},{op:"radial",name:"RollerDrum",count:12,radius:.3,axis:"x",center:[2.5,.8,0],children:[{type:"prim",primitive:"cylinder",name:"Roller",pos:[0,0,.25],scl:[.02,.02,.5],color:[.6,.6,.65]}],spin:{rpm:10,axis:[1,0,0]}}]},controlPanel:{op:"group",name:"ControlPanel",children:[{op:"radial",name:"ButtonArray",count:12,radius:.4,axis:"z",center:[0,1.2,.15],children:[{type:"prim",primitive:"cylinder",name:"Button",scl:[.05,.05,.05],color:[.8,.2,.2]}]},{type:"prim",primitive:"box",name:"Screen",pos:[0,1.5,.12],scl:[.8,.4,.02],color:[.1,.3,.1]},{type:"prim",primitive:"cylinder",name:"Base",pos:[0,.3,0],scl:[.8,.6,.8],color:[.25,.25,.28]}]}}[e]||null)}}}]);
713.bundle.js CHANGED
@@ -1 +1 @@
1
- "use strict";(self.webpackChunkplaycanvas_game=self.webpackChunkplaycanvas_game||[]).push([[713],{713:(e,t,o)=>{o.r(t),o.d(t,{default:()=>n});var i=o(96);const n={id:"data_center",title:"Corporate Data Center",type:"scene",async build(e,t,o){console.log("[DataCenter] Building corporate data center...");const n=new i.n(e,e.systems?.core||{});n.sceneBuilder=t,n.entityFactory=o,this.securityLevel="HIGH",this.detectionTimer=null,n.createRoom("reception_lobby",{size:[20,4,16],lighting:"corporate",position:[0,0,0],buildFunction:a,objectives:[{id:"infiltration_start",name:"Corporate Infiltration",description:"Gain access to the corporate data systems",type:"simple",autoStart:!0,rewards:{experience:30}},{id:"social_engineer",name:"Social Engineering",description:"Obtain security credentials from reception",type:"simple",rewards:{experience:50,items:[{id:"temp_badge",quantity:1}]}}]}),n.createRoom("technical_floor",{size:[35,5,25],lighting:"technical",position:[0,0,0],buildFunction:s,objectives:[{id:"network_mapping",name:"Network Reconnaissance",description:"Map the internal network infrastructure",type:"counter",target:6,progress:0,rewards:{experience:80},nextObjective:"bypass_firewall"},{id:"bypass_firewall",name:"Firewall Bypass",description:"Exploit network vulnerabilities to gain system access",type:"simple",prerequisites:["network_mapping"],rewards:{experience:120,skillPoints:2,items:[{id:"network_access",quantity:1}]}}]}),n.createRoom("server_core",{size:[30,8,20],lighting:"server_room",position:[0,0,0],buildFunction:r,objectives:[{id:"data_extraction",name:"Corporate Espionage",description:"Extract sensitive corporate intelligence",type:"sequence",target:["database_access","encryption_bypass","data_download"],progress:0,rewards:{experience:200,skillPoints:4}}]}),n.createRoom("executive_vault",{size:[18,6,14],lighting:"vault",position:[0,0,0],buildFunction:c,objectives:[{id:"executive_files",name:"Executive Intelligence",description:"Access the most classified corporate documents",type:"simple",rewards:{experience:300,skillPoints:5,items:[{id:"classified_docs",quantity:1}]}}]}),n.createDoor("reception_lobby","technical_floor",{type:"keycard",item:"temp_badge"},{playerSpawnPosition:[-12,0,0]}),n.createDoor("technical_floor","server_core",{type:"objective",id:"bypass_firewall"},{playerSpawnPosition:[0,0,8]}),n.createDoor("server_core","executive_vault",{type:"objective",id:"data_extraction"},{playerSpawnPosition:[0,0,5]});const l=await n.build(e,t,o);return l.multiRoom=n,l},onLoad(e,t){console.log("[DataCenter] Corporate systems online - Security Level: HIGH");const o=e.root.findByTag("scene_root")[0];o&&o.multiRoom&&(o.multiRoom.onLoad(e,t),this.multiRoom=o.multiRoom,this.interactionHandler=o=>{this.handleDataCenterInteraction(o,e,t)},this.multiRoom.setSceneInteractionHandler(this.interactionHandler)),e.fire("ui:notification",{text:"INFILTRATION ACTIVE: Corporate espionage mission commenced",type:"info",duration:4e3})},handleDataCenterInteraction(e,t,o){console.log("[DataCenter] Interaction triggered with:",e);const i=o.getSystem("objectivemanager");if("ReceptionDesk"===e)i&&i.isObjectiveActive("infiltration_start")&&(i.completeObjective("infiltration_start"),i.startObjective("social_engineer")),t.fire("ui:notification",{text:'Receptionist: "Welcome to SecureCorp! Are you here for the IT consultation?"',type:"info",duration:4e3});else if("BadgePrinter"===e)i&&i.isObjectiveActive("social_engineer")&&(this.multiRoom.addItem("temp_badge"),i.completeObjective("social_engineer"),t.fire("ui:notification",{text:"Temporary security badge obtained - Technical floor access granted",type:"success",duration:3e3}));else if(e.startsWith("NetworkTerminal_")){if("technical_floor"===this.multiRoom.currentRoom&&i&&i.isObjectiveActive("network_mapping")){const o=parseInt(e.split("_")[1]),n=["Database cluster: 10.0.1.0/24","Executive subnet: 192.168.100.0/24","DMZ services: 172.16.0.0/16","Backup systems: 10.0.2.0/24","Security monitoring: 192.168.200.0/24","Administrative tools: 10.0.3.0/24"];t.fire("ui:notification",{text:`Network scan: ${n[o]}`,type:"info",duration:2500}),i.updateObjective("network_mapping")}}else if("FirewallConsole"===e)i&&i.isComplete("network_mapping")&&i.isObjectiveActive("bypass_firewall")&&(this.multiRoom.addItem("network_access"),i.completeObjective("bypass_firewall"),i.startObjective("data_extraction"),t.fire("ui:notification",{text:"FIREWALL COMPROMISED: Full network access obtained",type:"success",duration:4e3}));else if(["DatabaseServer","EncryptionModule","DownloadTerminal"].includes(e)){if("server_core"===this.multiRoom.currentRoom&&i&&i.isObjectiveActive("data_extraction")){const o={DatabaseServer:"database_access",EncryptionModule:"encryption_bypass",DownloadTerminal:"data_download"}[e],n={database_access:"Database access established",encryption_bypass:"Encryption protocols bypassed",data_download:"Corporate data extraction complete"};t.fire("ui:notification",{text:n[o],type:"success",duration:2500}),i.updateObjective("data_extraction",o)}}else"ExecutiveTerminal"===e&&"executive_vault"===this.multiRoom.currentRoom&&i&&i.isObjectiveActive("executive_files")&&(this.multiRoom.addItem("classified_docs"),i.completeObjective("executive_files"),t.fire("ui:notification",{text:"MISSION COMPLETE: Highest level corporate intelligence acquired",type:"success",duration:5e3}))},onUnload(e,t){console.log("[DataCenter] Disconnecting from corporate systems..."),this.multiRoom&&this.multiRoom.setSceneInteractionHandler(null),this.interactionHandler=null,this.multiRoom&&this.multiRoom.onUnload(e,t)}};async function a(e,t,o,i){console.log("[DataCenter] Building reception lobby...");const n=o.create("ReceptionDesk",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[3,.8,1.5]},rigidbody:{type:"static"}},position:[0,.8,-6],scale:[6,1.6,3],tags:["interactive","reception"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.6,.4,.2),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}e.addChild(n);const a=o.create("BadgePrinter",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.4,.3,.3]},rigidbody:{type:"static"}},position:[2,1.6,-5.5],scale:[.8,.6,.6],tags:["interactive","printer"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.2,.2),e.emissive=new pc.Color(0,.1,0),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(a,"reception_lobby"),[[-6,.5,0],[-3,.5,0],[3,.5,0],[6,.5,0]].forEach((t,i)=>{const n=o.create(`CorporateChair_${i}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.8,.8,.8]}},position:t,scale:[1.6,1.6,1.6],tags:["furniture"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.3,.3,.4),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}e.addChild(n)});const s=o.createNPC("receptionist_jenny",{position:[0,0,-5],displayName:"Jenny (Receptionist)",dialogue:{greeting:"Hi there! Welcome to SecureCorp Technologies. How can I help you today?",topics:{consultation:"Oh yes, the IT consultation! Let me print you a temporary access badge.",building:"Our technical teams work on the second floor - you'll need a badge for elevator access.",security:"Don't worry about security - our systems are state of the art. Completely impenetrable!"}}});i.addEntityToRoom(s,"reception_lobby"),[{pos:[0,3.5,0],color:new pc.Color(.9,.9,.85)},{pos:[-8,3.5,0],color:new pc.Color(.9,.9,.85)},{pos:[8,3.5,0],color:new pc.Color(.9,.9,.85)}].forEach((t,i)=>{const n=o.createLight("point",{color:t.color,intensity:1.8,range:10,position:t.pos});e.addChild(n)})}async function s(e,t,o,i){console.log("[DataCenter] Building technical floor..."),[[-14,1,-10],[-7,1,-10],[7,1,-10],[14,1,-10],[-10,1,10],[10,1,10]].forEach((e,t)=>{const n=o.create(`NetworkTerminal_${t}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,1,.5]},rigidbody:{type:"static"}},position:e,scale:[2,2,1],tags:["interactive","terminal"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.1,.1,.1),e.emissive=new pc.Color(0,.2,.1),e.emissiveIntensity=.5,e.update(),n.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(n,"technical_floor")});const n=o.create("FirewallConsole",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,1.5,1]},rigidbody:{type:"static"}},position:[0,1.5,0],scale:[4,3,2],tags:["interactive","firewall"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.3,.1,.1),e.emissive=new pc.Color(.4,.1,.1),e.emissiveIntensity=.6,e.update(),n.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(n,"technical_floor"),[[-10,1.5,-5],[-10,1.5,5],[10,1.5,-5],[10,1.5,5]].forEach((t,i)=>{const n=o.create(`NetworkRack_${i}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,1.5,.5]}},position:t,scale:[2,3,1],tags:["equipment"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.2,.2),e.emissive=new pc.Color(.1,.1,.05),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}e.addChild(n)});const a=o.createNPC("it_admin_david",{position:[5,0,-3],displayName:"David (IT Admin)",dialogue:{greeting:"Hey, you must be the new security consultant. Pretty impressive setup we have here.",topics:{network:"Our network spans multiple subnets - enterprise grade security throughout.",firewall:"That red console is our main firewall management system. Only senior admins have access.",servers:"The real magic happens in the server core - that's where all our critical data lives."}}});i.addEntityToRoom(a,"technical_floor"),[{pos:[0,4.5,0],color:new pc.Color(.8,.9,1)},{pos:[-12,4.5,0],color:new pc.Color(.8,.9,1)},{pos:[12,4.5,0],color:new pc.Color(.8,.9,1)}].forEach((t,i)=>{const n=o.createLight("point",{color:t.color,intensity:2,range:12,position:t.pos});e.addChild(n)})}async function r(e,t,o,i){console.log("[DataCenter] Building server core...");const n=o.create("DatabaseServer",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,2.5,1]},rigidbody:{type:"static"}},position:[-8,2.5,0],scale:[4,5,2],tags:["interactive","database"]}),a=o.create("EncryptionModule",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,2,1]},rigidbody:{type:"static"}},position:[0,2,0],scale:[3,4,2],tags:["interactive","encryption"]}),s=o.create("DownloadTerminal",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,1.5,.8]},rigidbody:{type:"static"}},position:[8,1.5,0],scale:[4,3,1.6],tags:["interactive","download"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.4,.6),e.emissive=new pc.Color(.1,.2,.3),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.6,.3,.1),e.emissive=new pc.Color(.3,.15,.05),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}if(s.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.1,.5,.2),e.emissive=new pc.Color(.05,.25,.1),e.update(),s.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(n,"server_core"),i.addEntityToRoom(a,"server_core"),i.addEntityToRoom(s,"server_core"),[[-12,2,-8],[-4,2,-8],[4,2,-8],[12,2,-8],[-12,2,8],[-4,2,8],[4,2,8],[12,2,8]].forEach((t,i)=>{const n=o.create(`ServerArray_${i}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,2,1]}},position:t,scale:[4,4,2],tags:["server"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.1,.1,.1),e.emissive=new pc.Color(.05,.1,.05),e.emissiveIntensity=.3,e.update(),n.model.meshInstances.forEach(t=>t.material=e)}e.addChild(n)}),[{pos:[0,7.5,0],color:new pc.Color(.6,.8,1)},{pos:[-10,6,0],color:new pc.Color(.6,.8,1)},{pos:[10,6,0],color:new pc.Color(.6,.8,1)}].forEach((t,i)=>{const n=o.createLight("point",{color:t.color,intensity:2.2,range:15,position:t.pos});e.addChild(n)})}async function c(e,t,o,i){console.log("[DataCenter] Building executive vault...");const n=o.create("ExecutiveTerminal",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2.5,2,1.5]},rigidbody:{type:"static"}},position:[0,2,-5],scale:[5,4,3],tags:["interactive","executive"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.4,.2,.6),e.emissive=new pc.Color(.2,.1,.3),e.emissiveIntensity=.7,e.metalness=.8,e.update(),n.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(n,"executive_vault"),[[-6,1.5,0],[6,1.5,0],[-6,1.5,4],[6,1.5,4]].forEach((t,i)=>{const n=o.create(`SecureStorage_${i}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,1.5,1]}},position:t,scale:[3,3,2],tags:["storage"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.6,.6,.7),e.metalness=.9,e.update(),n.model.meshInstances.forEach(t=>t.material=e)}e.addChild(n)}),[{pos:[0,5.5,0],color:new pc.Color(1,.9,.7)},{pos:[-6,4.5,0],color:new pc.Color(1,.9,.7)},{pos:[6,4.5,0],color:new pc.Color(1,.9,.7)}].forEach((t,i)=>{const n=o.createLight("point",{color:t.color,intensity:1.8,range:10,position:t.pos});e.addChild(n)})}}}]);
 
1
+ "use strict";(self.webpackChunkplaycanvas_game=self.webpackChunkplaycanvas_game||[]).push([[713],{713:(e,t,o)=>{o.r(t),o.d(t,{default:()=>a});var i=o(96);const a={id:"data_center",title:"Corporate Data Center",type:"scene",async build(e,t,o){console.log("[DataCenter] Building corporate data center...");const a=new i.n(e,e.systems?.core);a.sceneBuilder=t,a.entityFactory=o,this.securityLevel="HIGH",this.detectionTimer=null,a.createRoom("reception_lobby",{size:[20,4,16],lighting:"corporate",position:[0,0,0],buildFunction:n,objectives:[{id:"infiltration_start",name:"Corporate Infiltration",description:"Gain access to the corporate data systems",type:"simple",autoStart:!0,rewards:{experience:30}},{id:"social_engineer",name:"Social Engineering",description:"Obtain security credentials from reception",type:"simple",rewards:{experience:50,items:[{id:"temp_badge",quantity:1}]}}]}),a.createRoom("technical_floor",{size:[35,5,25],lighting:"technical",position:[0,0,0],buildFunction:s,objectives:[{id:"network_mapping",name:"Network Reconnaissance",description:"Map the internal network infrastructure",type:"counter",target:6,progress:0,autoStart:!0,rewards:{experience:80},nextObjective:"bypass_firewall"},{id:"bypass_firewall",name:"Firewall Bypass",description:"Exploit network vulnerabilities to gain system access",type:"simple",prerequisites:["network_mapping"],rewards:{experience:120,skillPoints:2,items:[{id:"network_access",quantity:1}]}}]}),a.createRoom("server_core",{size:[30,8,20],lighting:"server_room",position:[0,0,0],buildFunction:r,objectives:[{id:"data_extraction",name:"Corporate Espionage",description:"Extract sensitive corporate intelligence",type:"sequence",target:["database_access","encryption_bypass","data_download"],progress:0,rewards:{experience:200,skillPoints:4}}]}),a.createRoom("executive_vault",{size:[18,6,14],lighting:"vault",position:[0,0,0],buildFunction:c,objectives:[{id:"executive_files",name:"Executive Intelligence",description:"Access the most classified corporate documents",type:"simple",rewards:{experience:300,skillPoints:5,items:[{id:"classified_docs",quantity:1}]}}]}),a.createDoor("reception_lobby","technical_floor",{type:"keycard",item:"temp_badge"},{playerSpawnPosition:[-12,0,0]}),a.createDoor("technical_floor","server_core",{type:"objective",id:"bypass_firewall"},{playerSpawnPosition:[0,0,8]}),a.createDoor("server_core","executive_vault",{type:"objective",id:"data_extraction"},{playerSpawnPosition:[0,0,5]});const l=await a.build(e,t,o);return l.multiRoom=a,l},onLoad(e,t){console.log("[DataCenter] Corporate systems online - Security Level: HIGH");const o=e.root.findByTag("scene_root")[0];o&&o.multiRoom&&(o.multiRoom.onLoad(e,t),this.multiRoom=o.multiRoom,this.interactionHandler=o=>{this.handleDataCenterInteraction(o,e,t)},this.multiRoom.setSceneInteractionHandler(this.interactionHandler)),e.fire("ui:notification",{text:"INFILTRATION ACTIVE: Corporate espionage mission commenced",type:"info",duration:4e3})},handleDataCenterInteraction(e,t,o){console.log("[DataCenter] Interaction triggered with:",e);const i=o.getSystem("objectivemanager");if("ReceptionDesk"===e)i.isObjectiveActive("infiltration_start")&&(i.completeObjective("infiltration_start"),i.startObjective("social_engineer")),t.fire("ui:notification",{text:'Receptionist: "Welcome to SecureCorp! Are you here for the IT consultation?"',type:"info",duration:4e3});else if("BadgePrinter"===e)i.isObjectiveActive("social_engineer")&&(this.multiRoom.addItem("temp_badge"),i.completeObjective("social_engineer"),t.fire("ui:notification",{text:"Temporary security badge obtained - Technical floor access granted",type:"success",duration:3e3}));else if(e.startsWith("NetworkTerminal_")){const o=this.multiRoom.currentRoom;if(console.log(`[DataCenter] NetworkTerminal interaction - Room: ${o}, network_mapping active: ${i.isObjectiveActive("network_mapping")}`),"technical_floor"===o&&i.isObjectiveActive("network_mapping")){const o=parseInt(e.split("_")[1]),a=["Database cluster: 10.0.1.0/24","Executive subnet: 192.168.100.0/24","DMZ services: 172.16.0.0/16","Backup systems: 10.0.2.0/24","Security monitoring: 192.168.200.0/24","Administrative tools: 10.0.3.0/24"];t.fire("ui:notification",{text:`Network scan: ${a[o]}`,type:"info",duration:2500}),i.updateObjective("network_mapping"),i.isComplete("network_mapping")&&(i.startObjective("bypass_firewall"),t.fire("ui:notification",{text:"Network mapping complete - Firewall bypass available",type:"success",duration:3e3}))}}else if("FirewallConsole"===e)i.isComplete("network_mapping")&&i.isObjectiveActive("bypass_firewall")&&(this.multiRoom.addItem("network_access"),i.completeObjective("bypass_firewall"),i.startObjective("data_extraction"),t.fire("ui:notification",{text:"FIREWALL COMPROMISED: Full network access obtained",type:"success",duration:4e3}));else if(["DatabaseServer","EncryptionModule","DownloadTerminal"].includes(e)){if("server_core"===this.multiRoom.currentRoom&&i.isObjectiveActive("data_extraction")){const o={DatabaseServer:"database_access",EncryptionModule:"encryption_bypass",DownloadTerminal:"data_download"}[e],a={database_access:"Database access established",encryption_bypass:"Encryption protocols bypassed",data_download:"Corporate data extraction complete"};t.fire("ui:notification",{text:a[o],type:"success",duration:2500}),i.updateObjective("data_extraction",o)}}else"ExecutiveTerminal"===e&&"executive_vault"===this.multiRoom.currentRoom&&i.isObjectiveActive("executive_files")&&(this.multiRoom.addItem("classified_docs"),i.completeObjective("executive_files"),t.fire("ui:notification",{text:"MISSION COMPLETE: Highest level corporate intelligence acquired",type:"success",duration:5e3}))},onUnload(e,t){console.log("[DataCenter] Disconnecting from corporate systems..."),this.multiRoom&&this.multiRoom.setSceneInteractionHandler(null),this.interactionHandler=null,this.multiRoom&&this.multiRoom.onUnload(e,t)}};async function n(e,t,o,i){console.log("[DataCenter] Building reception lobby...");const a=o.create("ReceptionDesk",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[3,.8,1.5]},rigidbody:{type:"static"}},position:[0,.8,-6],scale:[6,1.6,3],tags:["interactive","reception"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.6,.4,.2),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}e.addChild(a);const n=o.create("BadgePrinter",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.4,.3,.3]},rigidbody:{type:"static"}},position:[2,1.6,-5.5],scale:[.8,.6,.6],tags:["interactive","printer"]});if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.2,.2),e.emissive=new pc.Color(0,.1,0),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(n,"reception_lobby"),[[-6,.5,0],[-3,.5,0],[3,.5,0],[6,.5,0]].forEach((t,i)=>{const a=o.create(`CorporateChair_${i}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.8,.8,.8]}},position:t,scale:[1.6,1.6,1.6],tags:["furniture"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.3,.3,.4),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}e.addChild(a)});const s=o.createNPC("receptionist_jenny",{position:[0,0,-5],displayName:"Jenny (Receptionist)",dialogue:{greeting:"Hi there! Welcome to SecureCorp Technologies. How can I help you today?",topics:{consultation:"Oh yes, the IT consultation! Let me print you a temporary access badge.",building:"Our technical teams work on the second floor - you'll need a badge for elevator access.",security:"Don't worry about security - our systems are state of the art. Completely impenetrable!"}}});i.addEntityToRoom(s,"reception_lobby"),[{pos:[0,3.5,0],color:new pc.Color(.9,.9,.85)},{pos:[-8,3.5,0],color:new pc.Color(.9,.9,.85)},{pos:[8,3.5,0],color:new pc.Color(.9,.9,.85)}].forEach((t,i)=>{const a=o.createLight("point",{color:t.color,intensity:1.8,range:10,position:t.pos});e.addChild(a)})}async function s(e,t,o,i){console.log("[DataCenter] Building technical floor..."),[[-14,1,-10],[-7,1,-10],[7,1,-10],[14,1,-10],[-10,1,10],[10,1,10]].forEach((e,t)=>{const a=o.create(`NetworkTerminal_${t}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,1,.5]},rigidbody:{type:"static"}},position:e,scale:[2,2,1],tags:["interactive","terminal"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.1,.1,.1),e.emissive=new pc.Color(0,.2,.1),e.emissiveIntensity=.5,e.update(),a.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(a,"technical_floor")});const a=o.create("FirewallConsole",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,1.5,1]},rigidbody:{type:"static"}},position:[0,1.5,0],scale:[4,3,2],tags:["interactive","firewall"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.3,.1,.1),e.emissive=new pc.Color(.4,.1,.1),e.emissiveIntensity=.6,e.update(),a.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(a,"technical_floor"),[[-10,1.5,-5],[-10,1.5,5],[10,1.5,-5],[10,1.5,5]].forEach((t,i)=>{const a=o.create(`NetworkRack_${i}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1,1.5,.5]}},position:t,scale:[2,3,1],tags:["equipment"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.2,.2),e.emissive=new pc.Color(.1,.1,.05),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}e.addChild(a)});const n=o.createNPC("it_admin_david",{position:[5,0,-3],displayName:"David (IT Admin)",dialogue:{greeting:"Hey, you must be the new security consultant. Pretty impressive setup we have here.",topics:{network:"Our network spans multiple subnets - enterprise grade security throughout.",firewall:"That red console is our main firewall management system. Only senior admins have access.",servers:"The real magic happens in the server core - that's where all our critical data lives."}}});i.addEntityToRoom(n,"technical_floor"),[{pos:[0,4.5,0],color:new pc.Color(.8,.9,1)},{pos:[-12,4.5,0],color:new pc.Color(.8,.9,1)},{pos:[12,4.5,0],color:new pc.Color(.8,.9,1)}].forEach((t,i)=>{const a=o.createLight("point",{color:t.color,intensity:2,range:12,position:t.pos});e.addChild(a)})}async function r(e,t,o,i){console.log("[DataCenter] Building server core...");const a=o.create("DatabaseServer",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,2.5,1]},rigidbody:{type:"static"}},position:[-8,2.5,0],scale:[4,5,2],tags:["interactive","database"]}),n=o.create("EncryptionModule",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,2,1]},rigidbody:{type:"static"}},position:[0,2,0],scale:[3,4,2],tags:["interactive","encryption"]}),s=o.create("DownloadTerminal",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,1.5,.8]},rigidbody:{type:"static"}},position:[8,1.5,0],scale:[4,3,1.6],tags:["interactive","download"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.2,.4,.6),e.emissive=new pc.Color(.1,.2,.3),e.update(),a.model.meshInstances.forEach(t=>t.material=e)}if(n.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.6,.3,.1),e.emissive=new pc.Color(.3,.15,.05),e.update(),n.model.meshInstances.forEach(t=>t.material=e)}if(s.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.1,.5,.2),e.emissive=new pc.Color(.05,.25,.1),e.update(),s.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(a,"server_core"),i.addEntityToRoom(n,"server_core"),i.addEntityToRoom(s,"server_core"),[[-12,2,-8],[-4,2,-8],[4,2,-8],[12,2,-8],[-12,2,8],[-4,2,8],[4,2,8],[12,2,8]].forEach((t,i)=>{const a=o.create(`ServerArray_${i}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2,2,1]}},position:t,scale:[4,4,2],tags:["server"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.1,.1,.1),e.emissive=new pc.Color(.05,.1,.05),e.emissiveIntensity=.3,e.update(),a.model.meshInstances.forEach(t=>t.material=e)}e.addChild(a)}),[{pos:[0,7.5,0],color:new pc.Color(.6,.8,1)},{pos:[-10,6,0],color:new pc.Color(.6,.8,1)},{pos:[10,6,0],color:new pc.Color(.6,.8,1)}].forEach((t,i)=>{const a=o.createLight("point",{color:t.color,intensity:2.2,range:15,position:t.pos});e.addChild(a)})}async function c(e,t,o,i){console.log("[DataCenter] Building executive vault...");const a=o.create("ExecutiveTerminal",{components:{model:{type:"box"},collision:{type:"box",halfExtents:[2.5,2,1.5]},rigidbody:{type:"static"}},position:[0,2,-5],scale:[5,4,3],tags:["interactive","executive"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.4,.2,.6),e.emissive=new pc.Color(.2,.1,.3),e.emissiveIntensity=.7,e.metalness=.8,e.update(),a.model.meshInstances.forEach(t=>t.material=e)}i.addEntityToRoom(a,"executive_vault"),[[-6,1.5,0],[6,1.5,0],[-6,1.5,4],[6,1.5,4]].forEach((t,i)=>{const a=o.create(`SecureStorage_${i}`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[1.5,1.5,1]}},position:t,scale:[3,3,2],tags:["storage"]});if(a.model){const e=new pc.StandardMaterial;e.diffuse=new pc.Color(.6,.6,.7),e.metalness=.9,e.update(),a.model.meshInstances.forEach(t=>t.material=e)}e.addChild(a)}),[{pos:[0,5.5,0],color:new pc.Color(1,.9,.7)},{pos:[-6,4.5,0],color:new pc.Color(1,.9,.7)},{pos:[6,4.5,0],color:new pc.Color(1,.9,.7)}].forEach((t,i)=>{const a=o.createLight("point",{color:t.color,intensity:1.8,range:10,position:t.pos});e.addChild(a)})}}}]);
96.bundle.js CHANGED
@@ -1 +1 @@
1
- "use strict";(self.webpackChunkplaycanvas_game=self.webpackChunkplaycanvas_game||[]).push([[96],{96:(t,e,o)=>{o.d(e,{n:()=>r});class i{constructor(t,e){this.app=t,this.id=e.id,this.state="locked",this.requirement=e.requirement||{type:"none"},this.fromRoom=e.fromRoom,this.toRoom=e.toRoom,this.animation=e.animation||"slide",this.entity=null,this.playerSpawnPosition=e.playerSpawnPosition||[0,0,0],this.onTransition=e.onTransition||null}createEntity(t,e,o=[0,0,0]){return this.entity=t.create(this.id,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.1,1.5,1]},rigidbody:{type:"kinematic"}},position:e,rotation:o,scale:[.2,3,2],tags:["door","interactive"]}),this.entity.name=this.id,this.updateDoorAppearance(),this.entity}updateDoorAppearance(){if(!this.entity||!this.entity.model)return;const t=new pc.StandardMaterial;switch(this.state){case"locked":t.diffuse=new pc.Color(.7,.3,.3),t.emissive=new pc.Color(.2,0,0);break;case"unlocked":case"open":t.diffuse=new pc.Color(.3,.7,.3),t.emissive=new pc.Color(0,.2,0);break;default:t.diffuse=new pc.Color(.5,.5,.5)}t.metalness=.8,t.update(),this.entity.model.meshInstances.forEach(e=>e.material=t)}canUnlock(t){if(!this.requirement||!t)return!0;switch(this.requirement.type){case"keycard":return t.inventory?.has(this.requirement.item)||!1;case"objective":try{const e=t.core?.getSystem("objectivemanager");return e?.isComplete(this.requirement.id)||!1}catch(t){return console.warn(`[DoorEntity] Error checking objective: ${t.message}`),!1}case"skill":return t.skills?.hasLevel?.(this.requirement.skill,this.requirement.level||1)||!1;case"sequence":return this.checkInteractionSequence(t.lastInteractions);case"none":return!0;default:return!1}}checkInteractionSequence(t){if(!this.requirement.sequence||!t)return!1;const e=this.requirement.sequence,o=t.slice(-e.length);return e.every((t,e)=>o[e]===t)}async unlock(t){if(!t)return console.error("[DoorEntity] No gameState provided to unlock()"),!1;if(!this.canUnlock(t)){const e=this.getUnlockFailureReason(t);return this.app.fire("ui:notification",{text:`Door locked: ${e}`,type:"warning",duration:3e3}),!1}return this.state="opening",this.updateDoorAppearance(),await this.playAnimation("open"),this.state="open",this.updateDoorAppearance(),this.app.fire("ui:notification",{text:`Access granted to ${this.toRoom.replace("_"," ")}`,type:"success",duration:2e3}),!0}getUnlockFailureReason(t){switch(this.requirement.type){case"keycard":return`Requires ${this.requirement.item.replace("_"," ")}`;case"objective":return`Complete "${this.requirement.id}" first`;case"skill":return`Requires ${this.requirement.skill} level ${this.requirement.level}`;case"sequence":return"Requires specific interaction sequence";default:return"Unknown requirement"}}async playAnimation(t){if(!this.entity)return;const e=this.entity.getLocalPosition().clone(),o=this.entity.getLocalScale().clone(),i=this.entity.getLocalEulerAngles().clone();let n,s;switch(this.createDoorEffects(t),this.animation){case"slide":n="open"===t?new pc.Vec3(e.x,e.y+3.2,e.z):new pc.Vec3(e.x,e.y-3.2,e.z),s=i.clone();break;case"swing":n=e.clone(),s="open"===t?new pc.Vec3(i.x,i.y+90,i.z):new pc.Vec3(i.x,i.y-90,i.z);break;default:n=e.clone(),s=i.clone()}return new Promise(r=>{const a=Date.now(),c=()=>{const l=Date.now()-a;let m=Math.min(l/1500,1);m=1-Math.pow(1-m,3);const h=new pc.Vec3;h.lerp(e,n,m),this.entity.setLocalPosition(h);const d=new pc.Vec3;if(d.lerp(i,s,m),this.entity.setLocalEulerAngles(d),"iris"===this.animation){const e="open"===t?1-.8*m:.2+.8*m;this.entity.setLocalScale(o.x*e,o.y,o.z*e)}if(m>.8){const t=(m-.8)/.2,e=.05*Math.sin(t*Math.PI*3)*(1-t);h.y+=e,this.entity.setLocalPosition(h)}m>=1?r():requestAnimationFrame(c)};c()})}createDoorEffects(t){this.playDoorSound(t),this.createLightFlash(),"slide"===this.animation&&this.createScreenShake()}playDoorSound(t){this.app.fire("audio:play",{sound:"open"===t?"door_open":"door_close",volume:.3,pitch:"slide"===this.animation?.8:1})}createLightFlash(){if(!this.entity)return;const t=new pc.Entity("door_flash");t.addComponent("light",{type:"point",color:new pc.Color(1,1,.8),intensity:2,range:8,castShadows:!1});const e=this.entity.getPosition();t.setPosition(e.x,e.y+1,e.z+1),this.entity.parent.addChild(t);let o=2;const i=()=>{o*=.9,t.light&&(t.light.intensity=o),o>.1?requestAnimationFrame(i):t.destroy()};setTimeout(i,100)}createScreenShake(){this.app.fire("camera:shake",{intensity:.5,duration:300})}async close(){"locked"!==this.state&&"closing"!==this.state&&(this.state="closing",await this.playAnimation("close"),this.state="locked",this.updateDoorAppearance())}async onInteract(t){"locked"===this.state?await this.unlock(t)&&this.onTransition&&setTimeout(()=>{this.onTransition(this.toRoom,this.playerSpawnPosition)},1e3):"open"===this.state&&this.onTransition&&this.onTransition(this.toRoom,this.playerSpawnPosition)}}class n{constructor(t,e){this.app=t,this.multiRoom=e,this.element=null,this.roomElements=new Map,this.doorElements=new Map,this.playerDot=null,this.scale=.15,this.init()}init(){this.element=document.createElement("div"),this.element.id="mini-map",this.element.style.cssText="\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 220px;\n height: 160px;\n background: rgba(0, 0, 0, 0.8);\n border: 2px solid #00ff88;\n border-radius: 8px;\n padding: 10px;\n z-index: 900;\n font-family: 'Courier New', monospace;\n color: #00ff88;\n box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);\n ";const t=document.createElement("div");t.textContent="FACILITY MAP",t.style.cssText="\n font-size: 10px;\n font-weight: bold;\n text-align: center;\n margin-bottom: 8px;\n color: #00ff88;\n border-bottom: 1px solid #00ff88;\n padding-bottom: 4px;\n ",this.element.appendChild(t),this.mapContainer=document.createElement("div"),this.mapContainer.style.cssText="\n position: relative;\n width: 200px;\n height: 120px;\n background: rgba(0, 20, 10, 0.5);\n border: 1px solid #004422;\n border-radius: 4px;\n overflow: hidden;\n ",this.element.appendChild(this.mapContainer),document.body.appendChild(this.element),this.buildMap(),this.app.on("room:changed",this.onRoomChanged,this)}buildMap(){if(!this.multiRoom||!this.multiRoom.rooms)return;const t=this.calculateRoomLayout();for(const[e,o]of this.multiRoom.rooms)this.createRoomElement(e,o,t[e]);for(const[e,o]of this.multiRoom.doors)this.createDoorElement(e,o,t);this.createPlayerDot(),this.updateCurrentRoom()}calculateRoomLayout(){const t={},e=Array.from(this.multiRoom.rooms.keys());return e.includes("reception")&&e.includes("emergency_ward")?(t.reception={x:30,y:60},t.emergency_ward={x:100,y:60},t.surgery_suite={x:170,y:60}):e.includes("entry_hall")&&e.includes("control_room")?(t.entry_hall={x:30,y:80},t.control_room={x:100,y:80},t.generator_bay={x:100,y:40},t.maintenance_tunnel={x:170,y:40}):e.includes("security_office")&&e.includes("main_corridor")?(t.security_office={x:30,y:90},t.main_corridor={x:100,y:90},t.camera_control={x:100,y:60},t.basement_storage={x:170,y:90},t.server_room={x:170,y:60}):e.forEach((e,o)=>{t[e]={x:30+50*o,y:60}}),t}createRoomElement(t,e,o){if(!o)return;const i=document.createElement("div");i.className="minimap-room",i.id=`minimap-${t}`,i.style.cssText=`\n position: absolute;\n left: ${o.x-15}px;\n top: ${o.y-10}px;\n width: 30px;\n height: 20px;\n border: 1px solid #006644;\n background: rgba(0, 100, 50, 0.3);\n border-radius: 3px;\n cursor: pointer;\n transition: all 0.3s ease;\n `;const n=document.createElement("div");n.textContent=t.replace("_"," ").substring(0,4).toUpperCase(),n.style.cssText="\n font-size: 7px;\n text-align: center;\n line-height: 20px;\n color: #00ff88;\n pointer-events: none;\n ",i.appendChild(n),i.onclick=()=>this.onRoomClick(t),this.mapContainer.appendChild(i),this.roomElements.set(t,i)}createDoorElement(t,e,o){const i=o[e.fromRoom],n=o[e.toRoom];if(!i||!n)return;const s=document.createElement("div");s.className="minimap-door",s.id=`minimap-${t}`;const r=(i.x+n.x)/2,a=(i.y+n.y)/2,c=180*Math.atan2(n.y-i.y,n.x-i.x)/Math.PI,l=Math.sqrt(Math.pow(n.x-i.x,2)+Math.pow(n.y-i.y,2))-30;s.style.cssText=`\n position: absolute;\n left: ${r-l/2}px;\n top: ${a-1}px;\n width: ${l}px;\n height: 2px;\n background: #ff6600;\n transform: rotate(${c}deg);\n transform-origin: center;\n transition: all 0.3s ease;\n `;const m=document.createElement("div");m.style.cssText="\n position: absolute;\n left: 50%;\n top: -4px;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #ff3333;\n transform: translateX(-50%);\n transition: background 0.3s ease;\n ",s.appendChild(m),this.mapContainer.appendChild(s),this.doorElements.set(t,{element:s,status:m,door:e})}createPlayerDot(){this.playerDot=document.createElement("div"),this.playerDot.style.cssText="\n position: absolute;\n width: 6px;\n height: 6px;\n background: #00ffff;\n border-radius: 50%;\n box-shadow: 0 0 8px #00ffff;\n transition: all 0.5s ease;\n z-index: 10;\n ",this.mapContainer.appendChild(this.playerDot)}updateCurrentRoom(){this.multiRoom.currentRoom&&(this.roomElements.forEach((t,e)=>{e===this.multiRoom.currentRoom?(t.style.background="rgba(0, 255, 136, 0.6)",t.style.borderColor="#00ff88",t.style.boxShadow="0 0 10px rgba(0, 255, 136, 0.5)",t.getBoundingClientRect(),this.mapContainer.getBoundingClientRect(),this.playerDot.style.left=t.offsetLeft+15-3+"px",this.playerDot.style.top=t.offsetTop+10-3+"px"):(t.style.background="rgba(0, 100, 50, 0.3)",t.style.borderColor="#006644",t.style.boxShadow="none")}),this.updateDoorStates())}updateDoorStates(){this.doorElements.forEach((t,e)=>{try{const{element:e,status:o,door:i}=t,n=!(!i.canUnlock||!this.multiRoom.gameState)&&i.canUnlock(this.multiRoom.gameState);"open"===i.state?(o.style.background="#00ff00",e.style.background="#00ff88"):n?(o.style.background="#ffff00",e.style.background="#ffaa00"):(o.style.background="#ff3333",e.style.background="#ff6600")}catch(t){console.warn(`[MiniMap] Error updating door state ${e}:`,t.message)}})}onRoomChanged(t){this.updateCurrentRoom()}onRoomClick(t){const e=this.multiRoom.currentRoom;if(t!==e){for(const[o,i]of this.multiRoom.doors)if(i.fromRoom===e&&i.toRoom===t)return void(i.canUnlock(this.multiRoom.gameState)?this.app.fire("ui:notification",{text:`Navigate to ${t.replace("_"," ")}`,type:"info",duration:2e3}):this.app.fire("ui:notification",{text:`${t.replace("_"," ")} is locked`,type:"warning",duration:2e3}));this.app.fire("ui:notification",{text:`No direct access to ${t.replace("_"," ")}`,type:"warning",duration:2e3})}}show(){this.element&&(this.element.style.display="block")}hide(){this.element&&(this.element.style.display="none")}destroy(){this.element&&(document.body.removeChild(this.element),this.element=null),this.app.off("room:changed",this.onRoomChanged,this),this.roomElements.clear(),this.doorElements.clear()}}class s{constructor(t,e){this.app=t,this.multiRoom=e,this.doorItems=new Map,this.setupDoorItems()}setupDoorItems(){this.itemConfigs={keycard:{model:"card",color:"#00ff88",emissive:"#004422",animation:"glow",scale:[.1,.05,.15],hoverOffset:.3,description:"Access Card Required"},objective:{model:"screen",color:"#ff8800",emissive:"#442200",animation:"pulse",scale:[.2,.3,.05],hoverOffset:.2,description:"Objective Completion Required"},skill:{model:"tool",color:"#8800ff",emissive:"#220044",animation:"rotate",scale:[.15,.15,.15],hoverOffset:.25,description:"Skill Level Required"},sequence:{model:"complex",color:"#ff0088",emissive:"#440022",animation:"complex",scale:[.12,.2,.12],hoverOffset:.4,description:"Sequence Completion Required"},none:{model:"simple",color:"#888888",emissive:"#222222",animation:"none",scale:[.08,.08,.08],hoverOffset:.1,description:"Access Granted"}}}createDoorItem(t,e,o){const i=this.itemConfigs[t.requirement?.type||"none"];if(!i)return null;const n=o.create(`${t.id}_item`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.1,.1,.1]}},position:[e[0]+.3,e[1]+1.5,e[2]],scale:i.scale,tags:["door_item","interactive"]});return this.applyDoorItemMaterial(n,i,t),this.addFloatingAnimation(n,i),this.doorItems.set(t.id,{entity:n,config:i,door:t,basePosition:n.getPosition().clone()}),n}applyDoorItemMaterial(t,e,o){if(!t.model)return;const i=new pc.StandardMaterial;i.diffuse=(new pc.Color).fromString(e.color),i.emissive=(new pc.Color).fromString(e.emissive),i.metalness=.7,i.shininess=80,"open"===o.state?(i.emissive=new pc.Color(.1,.8,.1),i.diffuse=new pc.Color(.3,1,.3)):o.canUnlock&&o.canUnlock(this.multiRoom.gameState)&&(i.emissive=(new pc.Color).fromString(e.emissive).mulScalar(2)),i.update(),t.model.meshInstances.forEach(t=>t.material=i)}addFloatingAnimation(t,e){const o=Date.now(),i=t.getPosition().clone();switch(e.animation){case"glow":this.addGlowAnimation(t,o);break;case"pulse":this.addPulseAnimation(t,o);break;case"rotate":this.addRotateAnimation(t,o);break;case"complex":this.addComplexAnimation(t,o);break;default:this.addFloatAnimation(t,i,e.hoverOffset)}}addFloatAnimation(t,e,o){const i=()=>{if(!t||!t.enabled)return;const n=.001*Date.now(),s=e.clone();s.y+=Math.sin(2*n)*o,t.setPosition(s),requestAnimationFrame(i)};i()}addGlowAnimation(t,e){const o=t.getPosition().clone(),i=()=>{if(!t||!t.enabled)return;const n=.001*(Date.now()-e),s=o.clone();s.y+=.2*Math.sin(1.5*n),t.setPosition(s);const r=1+.1*Math.sin(3*n),a=t.getLocalScale();t.setLocalScale(a.x*r,a.y*r,a.z*r),requestAnimationFrame(i)};i()}addPulseAnimation(t,e){const o=t.getPosition().clone(),i=t.getLocalScale().clone(),n=()=>{if(!t||!t.enabled)return;const s=.001*(Date.now()-e),r=1+.2*Math.sin(4*s);t.setLocalScale(i.x*r,i.y*r,i.z*r);const a=o.clone();a.y+=.15*Math.sin(2*s),t.setPosition(a),requestAnimationFrame(n)};n()}addRotateAnimation(t,e){const o=t.getPosition().clone(),i=()=>{if(!t||!t.enabled)return;const n=.001*(Date.now()-e);t.setEulerAngles(0,45*n,0);const s=o.clone();s.y+=.25*Math.sin(1.8*n),t.setPosition(s),requestAnimationFrame(i)};i()}addComplexAnimation(t,e){const o=t.getPosition().clone(),i=t.getLocalScale().clone(),n=()=>{if(!t||!t.enabled)return;const s=.001*(Date.now()-e),r=o.clone();r.y+=.3*Math.sin(2*s),r.x+=.1*Math.sin(1.5*s),r.z+=.1*Math.cos(1.2*s),t.setPosition(r),t.setEulerAngles(15*Math.sin(s),30*s,10*Math.cos(1.5*s));const a=1+.15*Math.sin(3*s);t.setLocalScale(i.x*a,i.y*a,i.z*a),requestAnimationFrame(n)};n()}updateDoorItemState(t){const e=this.doorItems.get(t);if(!e)return;const{entity:o,config:i,door:n}=e;this.applyDoorItemMaterial(o,i,n);try{n.canUnlock&&this.multiRoom.gameState&&n.canUnlock(this.multiRoom.gameState)?this.showInteractionPrompt(o,i.description):this.hideInteractionPrompt(o)}catch(e){console.warn(`[DoorItems] Error updating door item ${t}:`,e.message)}}showInteractionPrompt(t,e){let o=t.findByName("interaction_prompt");o||(o=new pc.Entity("interaction_prompt"),o.addComponent("element",{type:pc.ELEMENTTYPE_TEXT,text:`[F] ${e}`,fontSize:12,color:new pc.Color(1,1,1),outlineColor:new pc.Color(0,0,0),outlineThickness:.3,fontAsset:null,autoWidth:!0,autoHeight:!0,pivot:[.5,.5],anchor:[.5,.5]}),o.setLocalPosition(0,.8,0),o.setLocalEulerAngles(0,0,0),t.addChild(o));const i=this.app.root.findByTag("MainCamera")[0];i&&o.lookAt(i.getPosition())}hideInteractionPrompt(t){const e=t.findByName("interaction_prompt");e&&e.destroy()}onDoorInteraction(t){const e=this.doorItems.get(t);if(!e)return;const{entity:o,door:i,config:n}=e;this.playInteractionEffect(o),"locked"!==i.state||i.canUnlock(this.multiRoom.gameState)||this.showRequirementMessage(i,n)}playInteractionEffect(t){const e=t.getLocalScale().clone(),o=e.clone().mulScalar(1.3);t.setLocalScale(o),setTimeout(()=>{t&&t.enabled&&t.setLocalScale(e)},200)}showRequirementMessage(t,e){let o=e.description;switch(t.requirement?.type){case"keycard":o=`Requires: ${t.requirement.item.replace("_"," ")}`;break;case"objective":o=`Complete: ${t.requirement.id.replace("_"," ")}`;break;case"skill":o=`Requires: ${t.requirement.skill} level ${t.requirement.level}`;break;case"sequence":o="Complete interaction sequence first"}this.app.fire("ui:notification",{text:o,type:"warning",duration:3e3})}updateAllDoorItems(){this.doorItems.forEach((t,e)=>{this.updateDoorItemState(e)})}cleanup(){this.doorItems.forEach(t=>{t.entity&&t.entity.destroy()}),this.doorItems.clear()}}class r{constructor(t,e){this.app=t,this.core=e,this.rooms=new Map,this.doors=new Map,this.roomEntities=new Map,this.currentRoom=null,this.sceneRoot=null,this.miniMap=null,this.doorItems=null,this.gameState={inventory:new Set,lastInteractions:[],skills:new Map,core:e},this.gameState.skills.hasLevel=(t,e)=>(this.gameState.skills.get(t)||0)>=e,this.onDoorInteract=this.onDoorInteract.bind(this),this.transitionToRoom=this.transitionToRoom.bind(this)}async build(t,e,o){if(this.sceneBuilder=e,this.entityFactory=o,this.sceneRoot=o.create("MultiRoomRoot",{position:[0,0,0],tags:["scene_root"]}),this.rooms.size>0){const t=this.rooms.keys().next().value;this.currentRoom=t,await this.buildRoom(t,e,o),this.activateRoom(this.rooms.get(t))}return this.doorItems=new s(t,this),this.sceneRoot}deactivateRoom(t){console.log(`[MultiRoom] Deactivating room: ${t.entity.name}`);for(const[e,o]of this.doors)if(o.fromRoom===t.id&&o.entity&&("open"===o.state||"opening"===o.state)){if(o.state="locked",o.updateDoorAppearance(),o.entity){const e=this.getDoorPosition(o.fromRoom,o.toRoom,t.size);o.entity.setLocalPosition(e),o.entity.setLocalRotation(0,0,0),o.entity.setLocalScale(.2,3,2)}console.log(`[MultiRoom] Reset door ${e} state and position`)}t.entity.enabled=!1,t.active=!1,this.disableEntityAndChildren(t.entity),setTimeout(()=>{this.enforceEntityVisibility(t.entity,!1)},0)}disableEntityAndChildren(t){t.enabled=!1,t.children.forEach(t=>{this.disableEntityAndChildren(t)})}enforceEntityVisibility(t,e){t.enabled=e,t.render&&(t.render.enabled=e),t.model&&(t.model.enabled=e),t.children.forEach(t=>{this.enforceEntityVisibility(t,e)})}activateRoom(t){console.log(`[MultiRoom] Activating room: ${t.entity.name}`),t.entity.enabled=!0,t.active=!0,this.enforceEntityVisibility(t.entity,!0)}createRoom(t,e){const o={id:t,entity:null,lighting:e.lighting||"normal",size:e.size||[20,4,15],position:e.position||[0,0,0],active:!1,buildFunction:e.buildFunction||null,npcs:e.npcs||[],interactables:e.interactables||[],objectives:e.objectives||[]};return this.rooms.set(t,o),o}createDoor(t,e,o,n={}){const s=`${t}_to_${e}`,r=new i(this.app,{id:s,fromRoom:t,toRoom:e,requirement:o,animation:n.animation||"slide",playerSpawnPosition:n.playerSpawnPosition||[0,0,5],onTransition:this.transitionToRoom});this.doors.set(s,r);const a=this.rooms.get(t);if(a&&a.entity&&this.entityFactory){const o=this.getDoorPosition(t,e,a.size),i=r.createEntity(this.entityFactory,o);if(a.entity.addChild(i),i.doorInstance=r,this.doorItems){const t=this.doorItems.createDoorItem(r,o,this.entityFactory);t&&a.entity.addChild(t)}console.log(`[MultiRoom] Added door ${s} to existing room ${t}`)}return r}async buildRoom(t,e,o){const i=this.rooms.get(t);if(!i||i.entity)return;console.log(`[MultiRoom] Building room: ${t}`);const n=e.createRoom({size:i.size,position:i.position,lighting:i.lighting});n.name=`${t}_Room`,i.entity=n,i.buildFunction&&await i.buildFunction(n,e,o,this);for(const[e,s]of this.doors)if(s.fromRoom===t){const e=this.getDoorPosition(t,s.toRoom,i.size),r=s.createEntity(o,e);if(n.addChild(r),r.doorInstance=s,this.doorItems){const t=this.doorItems.createDoorItem(s,e,o);t&&n.addChild(t)}}return n.enabled=t===this.currentRoom,this.sceneRoot.addChild(n),n}getDoorPosition(t,e,o){const[i,n,s]=o;return[.3*i,.5*n,.35*s]}addEntityToRoom(t,e){this.roomEntities.has(e)||this.roomEntities.set(e,new Set),this.roomEntities.get(e).add(t),this.sceneRoot.addChild(t),t.enabled=e===this.currentRoom,console.log(`[MultiRoom] Added entity ${t.name} to room ${e}, enabled: ${t.enabled}`)}updateEntityVisibilityForRoom(t){console.log(`[MultiRoom] Updating entity visibility for room: ${t}`);for(const[e,o]of this.roomEntities)e!==t&&o.forEach(t=>{t.enabled=!1,this.enforceEntityVisibility(t,!1)});this.roomEntities.has(t)&&this.roomEntities.get(t).forEach(t=>{t.enabled=!0,this.enforceEntityVisibility(t,!0)})}async transitionToRoom(t,e){if(!this.rooms.has(t))return console.error(`[MultiRoom] Room ${t} not found`),!1;if(t===this.currentRoom)return console.log(`[MultiRoom] Already in room ${t}`),!0;console.log(`[MultiRoom] Transitioning from ${this.currentRoom} to ${t}`),this.app.fire("ui:notification",{text:`Entering ${t.replace("_"," ")}...`,type:"info",duration:2e3});try{const o=this.rooms.get(t);if(!o.entity){if(!this.sceneBuilder||!this.entityFactory)return console.error("[MultiRoom] Missing sceneBuilder or entityFactory for room building"),!1;await this.buildRoom(t,this.sceneBuilder,this.entityFactory)}if(this.currentRoom){const t=this.rooms.get(this.currentRoom);t.entity&&this.deactivateRoom(t)}this.activateRoom(o),this.currentRoom=t,this.updateEntityVisibilityForRoom(t);const i=this.app.root.findByTag("player")[0];return i&&e&&i.setPosition(e),this.updateRoomObjectives(t),this.app.fire("room:changed",{roomId:t,playerPosition:e,roomConfig:o}),console.log(`[MultiRoom] Successfully transitioned to ${t}`),!0}catch(e){return console.error(`[MultiRoom] Error transitioning to room ${t}:`,e),!1}}updateRoomObjectives(t){const e=this.rooms.get(t);if(!e||!e.objectives)return void console.log(`[MultiRoom] No objectives found for room: ${t}`);const o=this.core.getSystem("objectivemanager");o?(console.log(`[MultiRoom] Updating objectives for room: ${t} (${e.objectives.length} objectives)`),e.objectives.forEach(t=>{console.log(`[MultiRoom] Processing objective: ${t.id} (type: ${t.type})`),o.objectives.has(t.id)||(o.defineObjective(t.id,t),console.log(`[MultiRoom] Defined objective: ${t.id}`)),t.prerequisites&&0!==t.prerequisites.length?(console.log(`[MultiRoom] Checking prerequisites for ${t.id}: ${t.prerequisites}`),!t.prerequisites.every(t=>{const e=o.isComplete(t);return console.log(`[MultiRoom] Prerequisite ${t} complete: ${e}`),e})||o.isObjectiveActive(t.id)||o.isComplete(t.id)?console.log(`[MultiRoom] Prerequisites not met for ${t.id} or objective already active/complete`):(console.log(`[MultiRoom] Starting prerequisite objective: ${t.id}`),o.startObjective(t.id),console.log(`[MultiRoom] Successfully started prerequisite objective: ${t.id}`))):o.isObjectiveActive(t.id)||o.isComplete(t.id)?console.log(`[MultiRoom] Objective ${t.id} already active or completed: active=${o.isObjectiveActive(t.id)}, complete=${o.isComplete(t.id)}`):(console.log(`[MultiRoom] Starting room objective: ${t.id} (no prerequisites)`),o.startObjective(t.id),console.log(`[MultiRoom] Successfully started room objective: ${t.id}`))})):console.warn(`[MultiRoom] ObjectiveManager not available for room: ${t}`)}addItem(t){this.gameState.inventory.add(t);const e=this.core?this.core.getSystem("inventorymanager"):null;e?e.pickupItem(t,1):this.app.fire("ui:notification",{text:`Acquired: ${t.replace("_"," ")}`,type:"success"}),this.doorItems&&this.doorItems.updateAllDoorItems()}hasItem(t){if(this.gameState.inventory.has(t))return!0;const e=this.core?this.core.getSystem("inventorymanager"):null;return!!e&&e.hasItem(t)}recordInteraction(t){this.gameState.lastInteractions.push(t),this.gameState.lastInteractions.length>10&&this.gameState.lastInteractions.shift()}onDoorInteract(t){for(const[e,o]of this.doors)if(e===t||o.entity&&o.entity.name===t)return void o.onInteract(this.gameState);console.warn(`[MultiRoom] Door not found: ${t}`)}setupInteractions(t,e){this.interactionHandler=t=>{if(this.recordInteraction(t),t.includes("_to_")){if(this.onDoorInteract(t),this.doorItems){const e=Array.from(this.doors.values()).find(e=>e.id===t);e&&this.doorItems.onDoorInteraction(e.id)}}else this.handleRoomInteraction(t)},this.npcInteractionHandler=t=>{console.log(`[MultiRoom] NPC interaction with ${t}`),this.recordInteraction(t),this.sceneInteractionHandler?this.sceneInteractionHandler(t):console.log(`[MultiRoom] No scene interaction handler registered for NPC: ${t}`)},t.on("interaction:triggered",this.interactionHandler),t.on("npc:interact",this.npcInteractionHandler)}handleRoomInteraction(t){console.log(`[MultiRoom] Room interaction: ${t} in room ${this.currentRoom}`),this.sceneInteractionHandler?(console.log("[MultiRoom] Calling scene interaction handler"),this.sceneInteractionHandler(t)):console.log("[MultiRoom] No scene interaction handler registered")}setSceneInteractionHandler(t){this.sceneInteractionHandler=t}syncWithGlobalInventory(t){const e=t?t.getSystem("inventorymanager"):null;if(e){for(const[t,o]of e.items.entries())o&&o.id&&this.gameState.inventory.add(o.id);console.log(`[MultiRoom] Synced ${this.gameState.inventory.size} items from global inventory`)}else console.warn("[MultiRoom] No InventoryManager available for sync")}onLoad(t,e){this.setupInteractions(t,e),this.app=t,this.core=e,this.gameState&&e&&(this.gameState.core=e),this.syncWithGlobalInventory(e),this.setupObjectives(t,e),this.miniMap=new n(t,this),setTimeout(()=>{t.fire("ui:notification",{text:`Multi-room scene loaded: ${this.currentRoom}. All interactions available.`,type:"info",duration:3e3})},500),console.log(`[MultiRoom] Multi-room scene loaded, starting in ${this.currentRoom}`),console.log("[MultiRoom] GameState inventory:",this.gameState.inventory.size,"items"),setTimeout(()=>{const t=this.sceneRoot.find(t=>t.tags&&t.tags.has("interactive"));console.log(`[MultiRoom] Found ${t.length} interactive items:`),t.forEach(t=>{console.log(` - ${t.name}: enabled=${t.enabled}, position=${t.getPosition().toString()}`)})},1e3)}setupObjectives(t,e){const o=e.getSystem("objectivemanager");if(!o)return void console.warn("[MultiRoom] ObjectiveManager not available");console.log("[MultiRoom] Setting up objectives for multi-room scene"),o.clearAllObjectives();const i=[];for(const[t,e]of this.rooms)e.objectives&&Array.isArray(e.objectives)&&(console.log(`[MultiRoom] Found ${e.objectives.length} objectives in room ${t}`),i.push(...e.objectives));i.forEach(t=>{o.defineObjective(t.id,t),console.log(`[MultiRoom] Registered objective: ${t.id} - ${t.name}`)}),i.forEach(t=>{t.autoStart&&(o.startObjective(t.id),console.log(`[MultiRoom] Auto-started objective: ${t.id}`))}),this.currentRoom&&(console.log(`[MultiRoom] Starting objectives for initial room: ${this.currentRoom}`),this.updateRoomObjectives(this.currentRoom)),console.log(`[MultiRoom] Objective setup complete - ${i.length} objectives registered`)}onUnload(t,e){console.log("[MultiRoom] Cleaning up multi-room scene..."),this.interactionHandler&&(t.off("interaction:triggered",this.interactionHandler),this.interactionHandler=null),this.npcInteractionHandler&&(t.off("npc:interact",this.npcInteractionHandler),this.npcInteractionHandler=null),this.miniMap&&(this.miniMap.destroy(),this.miniMap=null),this.doorItems&&(this.doorItems.cleanup(),this.doorItems=null);for(const[t,e]of this.roomEntities)e.forEach(t=>{t&&t.destroy&&t.destroy()});this.roomEntities.clear();for(const[t,e]of this.rooms)e.entity&&e.entity.findByTag("npc").forEach(t=>{t.cleanupLabel&&t.cleanupLabel()}),e.active=!1;this.gameState.inventory.clear(),this.gameState.lastInteractions=[],this.gameState.skills.clear(),this.currentRoom=null}}}}]);
 
1
+ "use strict";(self.webpackChunkplaycanvas_game=self.webpackChunkplaycanvas_game||[]).push([[96],{96:(t,e,o)=>{o.d(e,{n:()=>r});class i{constructor(t,e){this.app=t,this.id=e.id,this.state="locked",this.requirement=e.requirement||{type:"none"},this.fromRoom=e.fromRoom,this.toRoom=e.toRoom,this.animation=e.animation||"slide",this.entity=null,this.playerSpawnPosition=e.playerSpawnPosition||[0,0,0],this.onTransition=e.onTransition||null}createEntity(t,e,o=[0,0,0]){return this.entity=t.create(this.id,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.1,1.5,1]},rigidbody:{type:"kinematic"}},position:e,rotation:o,scale:[.2,3,2],tags:["door","interactive"]}),this.entity.name=this.id,this.updateDoorAppearance(),this.entity}updateDoorAppearance(){if(!this.entity||!this.entity.model)return;const t=new pc.StandardMaterial;switch(this.state){case"locked":t.diffuse=new pc.Color(.7,.3,.3),t.emissive=new pc.Color(.2,0,0);break;case"unlocked":case"open":t.diffuse=new pc.Color(.3,.7,.3),t.emissive=new pc.Color(0,.2,0);break;default:t.diffuse=new pc.Color(.5,.5,.5)}t.metalness=.8,t.update(),this.entity.model.meshInstances.forEach(e=>e.material=t)}canUnlock(t){if(!this.requirement||!t)return!0;switch(this.requirement.type){case"keycard":return t.inventory?.has(this.requirement.item)||!1;case"objective":try{const e=t.core?.getSystem("objectivemanager");return e?.isComplete(this.requirement.id)||!1}catch(t){return console.warn(`[DoorEntity] Error checking objective: ${t.message}`),!1}case"skill":return t.skills?.hasLevel?.(this.requirement.skill,this.requirement.level||1)||!1;case"sequence":return this.checkInteractionSequence(t.lastInteractions);case"none":return!0;default:return!1}}checkInteractionSequence(t){if(!this.requirement.sequence||!t)return!1;const e=this.requirement.sequence,o=t.slice(-e.length);return e.every((t,e)=>o[e]===t)}async unlock(t){if(!t)return console.error("[DoorEntity] No gameState provided to unlock()"),!1;if(!this.canUnlock(t)){const e=this.getUnlockFailureReason(t);return this.app.fire("ui:notification",{text:`Door locked: ${e}`,type:"warning",duration:3e3}),!1}return this.state="opening",this.updateDoorAppearance(),await this.playAnimation("open"),this.state="open",this.updateDoorAppearance(),this.app.fire("ui:notification",{text:`Access granted to ${this.toRoom.replace("_"," ")}`,type:"success",duration:2e3}),!0}getUnlockFailureReason(t){switch(this.requirement.type){case"keycard":return`Requires ${this.requirement.item.replace("_"," ")}`;case"objective":return`Complete "${this.requirement.id}" first`;case"skill":return`Requires ${this.requirement.skill} level ${this.requirement.level}`;case"sequence":return"Requires specific interaction sequence";default:return"Unknown requirement"}}async playAnimation(t){if(!this.entity)return;const e=this.entity.getLocalPosition().clone(),o=this.entity.getLocalScale().clone(),i=this.entity.getLocalEulerAngles().clone();let n,s;switch(this.createDoorEffects(t),this.animation){case"slide":n="open"===t?new pc.Vec3(e.x,e.y+3.2,e.z):new pc.Vec3(e.x,e.y-3.2,e.z),s=i.clone();break;case"swing":n=e.clone(),s="open"===t?new pc.Vec3(i.x,i.y+90,i.z):new pc.Vec3(i.x,i.y-90,i.z);break;default:n=e.clone(),s=i.clone()}return new Promise(r=>{const a=Date.now(),c=()=>{const l=Date.now()-a;let m=Math.min(l/1500,1);m=1-Math.pow(1-m,3);const h=new pc.Vec3;h.lerp(e,n,m),this.entity.setLocalPosition(h);const d=new pc.Vec3;if(d.lerp(i,s,m),this.entity.setLocalEulerAngles(d),"iris"===this.animation){const e="open"===t?1-.8*m:.2+.8*m;this.entity.setLocalScale(o.x*e,o.y,o.z*e)}if(m>.8){const t=(m-.8)/.2,e=.05*Math.sin(t*Math.PI*3)*(1-t);h.y+=e,this.entity.setLocalPosition(h)}m>=1?r():requestAnimationFrame(c)};c()})}createDoorEffects(t){this.playDoorSound(t),this.createLightFlash(),"slide"===this.animation&&this.createScreenShake()}playDoorSound(t){this.app.fire("audio:play",{sound:"open"===t?"door_open":"door_close",volume:.3,pitch:"slide"===this.animation?.8:1})}createLightFlash(){if(!this.entity)return;const t=new pc.Entity("door_flash");t.addComponent("light",{type:"point",color:new pc.Color(1,1,.8),intensity:2,range:8,castShadows:!1});const e=this.entity.getPosition();t.setPosition(e.x,e.y+1,e.z+1),this.entity.parent.addChild(t);let o=2;const i=()=>{o*=.9,t.light&&(t.light.intensity=o),o>.1?requestAnimationFrame(i):t.destroy()};setTimeout(i,100)}createScreenShake(){this.app.fire("camera:shake",{intensity:.5,duration:300})}async close(){"locked"!==this.state&&"closing"!==this.state&&(this.state="closing",await this.playAnimation("close"),this.state="locked",this.updateDoorAppearance())}async onInteract(t){"locked"===this.state?await this.unlock(t)&&this.onTransition&&setTimeout(()=>{this.onTransition(this.toRoom,this.playerSpawnPosition)},1e3):"open"===this.state&&this.onTransition&&this.onTransition(this.toRoom,this.playerSpawnPosition)}}class n{constructor(t,e){this.app=t,this.multiRoom=e,this.element=null,this.roomElements=new Map,this.doorElements=new Map,this.playerDot=null,this.scale=.15,this.init()}init(){this.element=document.createElement("div"),this.element.id="mini-map",this.element.style.cssText="\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 220px;\n height: 160px;\n background: rgba(0, 0, 0, 0.8);\n border: 2px solid #00ff88;\n border-radius: 8px;\n padding: 10px;\n z-index: 900;\n font-family: 'Courier New', monospace;\n color: #00ff88;\n box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);\n ";const t=document.createElement("div");t.textContent="FACILITY MAP",t.style.cssText="\n font-size: 10px;\n font-weight: bold;\n text-align: center;\n margin-bottom: 8px;\n color: #00ff88;\n border-bottom: 1px solid #00ff88;\n padding-bottom: 4px;\n ",this.element.appendChild(t),this.mapContainer=document.createElement("div"),this.mapContainer.style.cssText="\n position: relative;\n width: 200px;\n height: 120px;\n background: rgba(0, 20, 10, 0.5);\n border: 1px solid #004422;\n border-radius: 4px;\n overflow: hidden;\n ",this.element.appendChild(this.mapContainer),document.body.appendChild(this.element),this.buildMap(),this.app.on("room:changed",this.onRoomChanged,this)}buildMap(){if(!this.multiRoom||!this.multiRoom.rooms)return;const t=this.calculateRoomLayout();for(const[e,o]of this.multiRoom.rooms)this.createRoomElement(e,o,t[e]);for(const[e,o]of this.multiRoom.doors)this.createDoorElement(e,o,t);this.createPlayerDot(),this.updateCurrentRoom()}calculateRoomLayout(){const t={},e=Array.from(this.multiRoom.rooms.keys());return e.includes("reception")&&e.includes("emergency_ward")?(t.reception={x:30,y:60},t.emergency_ward={x:100,y:60},t.surgery_suite={x:170,y:60}):e.includes("entry_hall")&&e.includes("control_room")?(t.entry_hall={x:30,y:80},t.control_room={x:100,y:80},t.generator_bay={x:100,y:40},t.maintenance_tunnel={x:170,y:40}):e.includes("security_office")&&e.includes("main_corridor")?(t.security_office={x:30,y:90},t.main_corridor={x:100,y:90},t.camera_control={x:100,y:60},t.basement_storage={x:170,y:90},t.server_room={x:170,y:60}):e.forEach((e,o)=>{t[e]={x:30+50*o,y:60}}),t}createRoomElement(t,e,o){if(!o)return;const i=document.createElement("div");i.className="minimap-room",i.id=`minimap-${t}`,i.style.cssText=`\n position: absolute;\n left: ${o.x-15}px;\n top: ${o.y-10}px;\n width: 30px;\n height: 20px;\n border: 1px solid #006644;\n background: rgba(0, 100, 50, 0.3);\n border-radius: 3px;\n cursor: pointer;\n transition: all 0.3s ease;\n `;const n=document.createElement("div");n.textContent=t.replace("_"," ").substring(0,4).toUpperCase(),n.style.cssText="\n font-size: 7px;\n text-align: center;\n line-height: 20px;\n color: #00ff88;\n pointer-events: none;\n ",i.appendChild(n),i.onclick=()=>this.onRoomClick(t),this.mapContainer.appendChild(i),this.roomElements.set(t,i)}createDoorElement(t,e,o){const i=o[e.fromRoom],n=o[e.toRoom];if(!i||!n)return;const s=document.createElement("div");s.className="minimap-door",s.id=`minimap-${t}`;const r=(i.x+n.x)/2,a=(i.y+n.y)/2,c=180*Math.atan2(n.y-i.y,n.x-i.x)/Math.PI,l=Math.sqrt(Math.pow(n.x-i.x,2)+Math.pow(n.y-i.y,2))-30;s.style.cssText=`\n position: absolute;\n left: ${r-l/2}px;\n top: ${a-1}px;\n width: ${l}px;\n height: 2px;\n background: #ff6600;\n transform: rotate(${c}deg);\n transform-origin: center;\n transition: all 0.3s ease;\n `;const m=document.createElement("div");m.style.cssText="\n position: absolute;\n left: 50%;\n top: -4px;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #ff3333;\n transform: translateX(-50%);\n transition: background 0.3s ease;\n ",s.appendChild(m),this.mapContainer.appendChild(s),this.doorElements.set(t,{element:s,status:m,door:e})}createPlayerDot(){this.playerDot=document.createElement("div"),this.playerDot.style.cssText="\n position: absolute;\n width: 6px;\n height: 6px;\n background: #00ffff;\n border-radius: 50%;\n box-shadow: 0 0 8px #00ffff;\n transition: all 0.5s ease;\n z-index: 10;\n ",this.mapContainer.appendChild(this.playerDot)}updateCurrentRoom(){this.multiRoom.currentRoom&&(this.roomElements.forEach((t,e)=>{e===this.multiRoom.currentRoom?(t.style.background="rgba(0, 255, 136, 0.6)",t.style.borderColor="#00ff88",t.style.boxShadow="0 0 10px rgba(0, 255, 136, 0.5)",t.getBoundingClientRect(),this.mapContainer.getBoundingClientRect(),this.playerDot.style.left=t.offsetLeft+15-3+"px",this.playerDot.style.top=t.offsetTop+10-3+"px"):(t.style.background="rgba(0, 100, 50, 0.3)",t.style.borderColor="#006644",t.style.boxShadow="none")}),this.updateDoorStates())}updateDoorStates(){this.doorElements.forEach((t,e)=>{try{const{element:e,status:o,door:i}=t,n=!(!i.canUnlock||!this.multiRoom.gameState)&&i.canUnlock(this.multiRoom.gameState);"open"===i.state?(o.style.background="#00ff00",e.style.background="#00ff88"):n?(o.style.background="#ffff00",e.style.background="#ffaa00"):(o.style.background="#ff3333",e.style.background="#ff6600")}catch(t){console.warn(`[MiniMap] Error updating door state ${e}:`,t.message)}})}onRoomChanged(t){this.updateCurrentRoom()}onRoomClick(t){const e=this.multiRoom.currentRoom;if(t!==e){for(const[o,i]of this.multiRoom.doors)if(i.fromRoom===e&&i.toRoom===t)return void(i.canUnlock(this.multiRoom.gameState)?this.app.fire("ui:notification",{text:`Navigate to ${t.replace("_"," ")}`,type:"info",duration:2e3}):this.app.fire("ui:notification",{text:`${t.replace("_"," ")} is locked`,type:"warning",duration:2e3}));this.app.fire("ui:notification",{text:`No direct access to ${t.replace("_"," ")}`,type:"warning",duration:2e3})}}show(){this.element&&(this.element.style.display="block")}hide(){this.element&&(this.element.style.display="none")}destroy(){this.element&&(document.body.removeChild(this.element),this.element=null),this.app.off("room:changed",this.onRoomChanged,this),this.roomElements.clear(),this.doorElements.clear()}}class s{constructor(t,e){this.app=t,this.multiRoom=e,this.doorItems=new Map,this.setupDoorItems()}setupDoorItems(){this.itemConfigs={keycard:{model:"card",color:"#00ff88",emissive:"#004422",animation:"glow",scale:[.1,.05,.15],hoverOffset:.3,description:"Access Card Required"},objective:{model:"screen",color:"#ff8800",emissive:"#442200",animation:"pulse",scale:[.2,.3,.05],hoverOffset:.2,description:"Objective Completion Required"},skill:{model:"tool",color:"#8800ff",emissive:"#220044",animation:"rotate",scale:[.15,.15,.15],hoverOffset:.25,description:"Skill Level Required"},sequence:{model:"complex",color:"#ff0088",emissive:"#440022",animation:"complex",scale:[.12,.2,.12],hoverOffset:.4,description:"Sequence Completion Required"},none:{model:"simple",color:"#888888",emissive:"#222222",animation:"none",scale:[.08,.08,.08],hoverOffset:.1,description:"Access Granted"}}}createDoorItem(t,e,o){const i=this.itemConfigs[t.requirement?.type||"none"];if(!i)return null;const n=o.create(`${t.id}_item`,{components:{model:{type:"box"},collision:{type:"box",halfExtents:[.1,.1,.1]}},position:[e[0]+.3,e[1]+1.5,e[2]],scale:i.scale,tags:["door_item","interactive"]});return this.applyDoorItemMaterial(n,i,t),this.addFloatingAnimation(n,i),this.doorItems.set(t.id,{entity:n,config:i,door:t,basePosition:n.getPosition().clone()}),n}applyDoorItemMaterial(t,e,o){if(!t.model)return;const i=new pc.StandardMaterial;i.diffuse=(new pc.Color).fromString(e.color),i.emissive=(new pc.Color).fromString(e.emissive),i.metalness=.7,i.shininess=80,"open"===o.state?(i.emissive=new pc.Color(.1,.8,.1),i.diffuse=new pc.Color(.3,1,.3)):o.canUnlock&&o.canUnlock(this.multiRoom.gameState)&&(i.emissive=(new pc.Color).fromString(e.emissive).mulScalar(2)),i.update(),t.model.meshInstances.forEach(t=>t.material=i)}addFloatingAnimation(t,e){const o=Date.now(),i=t.getPosition().clone();switch(e.animation){case"glow":this.addGlowAnimation(t,o);break;case"pulse":this.addPulseAnimation(t,o);break;case"rotate":this.addRotateAnimation(t,o);break;case"complex":this.addComplexAnimation(t,o);break;default:this.addFloatAnimation(t,i,e.hoverOffset)}}addFloatAnimation(t,e,o){const i=()=>{if(!t||!t.enabled)return;const n=.001*Date.now(),s=e.clone();s.y+=Math.sin(2*n)*o,t.setPosition(s),requestAnimationFrame(i)};i()}addGlowAnimation(t,e){const o=t.getPosition().clone(),i=()=>{if(!t||!t.enabled)return;const n=.001*(Date.now()-e),s=o.clone();s.y+=.2*Math.sin(1.5*n),t.setPosition(s);const r=1+.1*Math.sin(3*n),a=t.getLocalScale();t.setLocalScale(a.x*r,a.y*r,a.z*r),requestAnimationFrame(i)};i()}addPulseAnimation(t,e){const o=t.getPosition().clone(),i=t.getLocalScale().clone(),n=()=>{if(!t||!t.enabled)return;const s=.001*(Date.now()-e),r=1+.2*Math.sin(4*s);t.setLocalScale(i.x*r,i.y*r,i.z*r);const a=o.clone();a.y+=.15*Math.sin(2*s),t.setPosition(a),requestAnimationFrame(n)};n()}addRotateAnimation(t,e){const o=t.getPosition().clone(),i=()=>{if(!t||!t.enabled)return;const n=.001*(Date.now()-e);t.setEulerAngles(0,45*n,0);const s=o.clone();s.y+=.25*Math.sin(1.8*n),t.setPosition(s),requestAnimationFrame(i)};i()}addComplexAnimation(t,e){const o=t.getPosition().clone(),i=t.getLocalScale().clone(),n=()=>{if(!t||!t.enabled)return;const s=.001*(Date.now()-e),r=o.clone();r.y+=.3*Math.sin(2*s),r.x+=.1*Math.sin(1.5*s),r.z+=.1*Math.cos(1.2*s),t.setPosition(r),t.setEulerAngles(15*Math.sin(s),30*s,10*Math.cos(1.5*s));const a=1+.15*Math.sin(3*s);t.setLocalScale(i.x*a,i.y*a,i.z*a),requestAnimationFrame(n)};n()}updateDoorItemState(t){const e=this.doorItems.get(t);if(!e)return;const{entity:o,config:i,door:n}=e;this.applyDoorItemMaterial(o,i,n);try{n.canUnlock&&this.multiRoom.gameState&&n.canUnlock(this.multiRoom.gameState)?this.showInteractionPrompt(o,i.description):this.hideInteractionPrompt(o)}catch(e){console.warn(`[DoorItems] Error updating door item ${t}:`,e.message)}}showInteractionPrompt(t,e){let o=t.findByName("interaction_prompt");o||(o=new pc.Entity("interaction_prompt"),o.addComponent("element",{type:pc.ELEMENTTYPE_TEXT,text:`[F] ${e}`,fontSize:12,color:new pc.Color(1,1,1),outlineColor:new pc.Color(0,0,0),outlineThickness:.3,fontAsset:null,autoWidth:!0,autoHeight:!0,pivot:[.5,.5],anchor:[.5,.5]}),o.setLocalPosition(0,.8,0),o.setLocalEulerAngles(0,0,0),t.addChild(o));const i=this.app.root.findByTag("MainCamera")[0];i&&o.lookAt(i.getPosition())}hideInteractionPrompt(t){const e=t.findByName("interaction_prompt");e&&e.destroy()}onDoorInteraction(t){const e=this.doorItems.get(t);if(!e)return;const{entity:o,door:i,config:n}=e;this.playInteractionEffect(o),"locked"!==i.state||i.canUnlock(this.multiRoom.gameState)||this.showRequirementMessage(i,n)}playInteractionEffect(t){const e=t.getLocalScale().clone(),o=e.clone().mulScalar(1.3);t.setLocalScale(o),setTimeout(()=>{t&&t.enabled&&t.setLocalScale(e)},200)}showRequirementMessage(t,e){let o=e.description;switch(t.requirement?.type){case"keycard":o=`Requires: ${t.requirement.item.replace("_"," ")}`;break;case"objective":o=`Complete: ${t.requirement.id.replace("_"," ")}`;break;case"skill":o=`Requires: ${t.requirement.skill} level ${t.requirement.level}`;break;case"sequence":o="Complete interaction sequence first"}this.app.fire("ui:notification",{text:o,type:"warning",duration:3e3})}updateAllDoorItems(){this.doorItems.forEach((t,e)=>{this.updateDoorItemState(e)})}cleanup(){this.doorItems.forEach(t=>{t.entity&&t.entity.destroy()}),this.doorItems.clear()}}class r{constructor(t,e){this.app=t,this.core=e,this.DOOR_HALF_HEIGHT=1.5,this.rooms=new Map,this.doors=new Map,this.roomEntities=new Map,this.currentRoom=null,this.sceneRoot=null,this.miniMap=null,this.doorItems=null,this.gameState={inventory:new Set,lastInteractions:[],skills:new Map,core:e},this.gameState.skills.hasLevel=(t,e)=>(this.gameState.skills.get(t)||0)>=e,this.onDoorInteract=this.onDoorInteract.bind(this),this.transitionToRoom=this.transitionToRoom.bind(this)}async build(t,e,o){if(this.sceneBuilder=e,this.entityFactory=o,this.sceneRoot=o.create("MultiRoomRoot",{position:[0,0,0],tags:["scene_root"]}),this.rooms.size>0){const t=this.rooms.keys().next().value;this.currentRoom=t,await this.buildRoom(t,e,o),this.activateRoom(this.rooms.get(t))}return this.doorItems=new s(t,this),this.sceneRoot}deactivateRoom(t){console.log(`[MultiRoom] Deactivating room: ${t.entity.name}`);for(const[e,o]of this.doors)if(o.fromRoom===t.id&&o.entity&&("open"===o.state||"opening"===o.state)){if(o.state="locked",o.updateDoorAppearance(),o.entity){const e=this.computeDoorPlacement(t,o);o.entity.setLocalPosition(e),o.entity.setLocalRotation(0,0,0),o.entity.setLocalScale(.2,3,2)}console.log(`[MultiRoom] Reset door ${e} state and position`)}t.entity.enabled=!1,t.active=!1,this.disableEntityAndChildren(t.entity),setTimeout(()=>{this.enforceEntityVisibility(t.entity,!1)},0)}disableEntityAndChildren(t){t.enabled=!1,t.children.forEach(t=>{this.disableEntityAndChildren(t)})}enforceEntityVisibility(t,e){t.enabled=e,t.render&&(t.render.enabled=e),t.model&&(t.model.enabled=e),t.children.forEach(t=>{this.enforceEntityVisibility(t,e)})}activateRoom(t){console.log(`[MultiRoom] Activating room: ${t.entity.name}`),t.entity.enabled=!0,t.active=!0,this.enforceEntityVisibility(t.entity,!0)}createRoom(t,e){const o={id:t,entity:null,lighting:e.lighting||"normal",size:e.size||[20,4,15],position:e.position||[0,0,0],active:!1,buildFunction:e.buildFunction||null,npcs:e.npcs||[],interactables:e.interactables||[],objectives:e.objectives||[]};return this.rooms.set(t,o),o}createDoor(t,e,o,n={}){const s=`${t}_to_${e}`,r=new i(this.app,{id:s,fromRoom:t,toRoom:e,requirement:o,animation:n.animation||"slide",playerSpawnPosition:n.playerSpawnPosition||[0,0,5],onTransition:this.transitionToRoom});this.doors.set(s,r);const a=this.rooms.get(t);if(a&&a.entity&&this.entityFactory){const e=this.computeDoorPlacement(a,r),o=r.createEntity(this.entityFactory,e);if(a.entity.addChild(o),o.doorInstance=r,this.doorItems){const t=this.doorItems.createDoorItem(r,e,this.entityFactory);t&&a.entity.addChild(t)}console.log(`[MultiRoom] Added door ${s} to existing room ${t}`)}return r}async buildRoom(t,e,o){const i=this.rooms.get(t);if(!i||i.entity)return;console.log(`[MultiRoom] Building room: ${t}`);const n=e.createRoom({size:i.size,position:i.position,lighting:i.lighting});n.name=`${t}_Room`,i.entity=n,i.buildFunction&&await i.buildFunction(n,e,o,this);for(const[e,s]of this.doors)if(s.fromRoom===t){const t=this.computeDoorPlacement(i,s),e=s.createEntity(o,t);if(n.addChild(e),e.doorInstance=s,this.doorItems){const e=this.doorItems.createDoorItem(s,t,o);e&&n.addChild(e)}}return n.enabled=t===this.currentRoom,this.sceneRoot.addChild(n),n}getDoorPosition(t,e,o){const[i,n,s]=o;return"student_practice"===e?[.35*i,.5*n,.15*s]:"or_suite"===e?[.35*-i,.5*n,.15*s]:[.3*i,.5*n,.35*s]}computeDoorPlacement(t,e){const o=e.requirement&&e.requirement.position?e.requirement.position.slice():null;let i=o||this.getDoorPosition(e.fromRoom,e.toRoom,t.size);if((!o||"number"==typeof o[1]&&o[1]<=.05)&&(i[1]=Math.max(i[1]||0,this.DOOR_HALF_HEIGHT)),!o){const o=Array.from(this.doors.values()).filter(e=>e.fromRoom===t.id&&!(e.requirement&&e.requirement.position)).sort((t,e)=>t.id.localeCompare(e.id)),n=Math.max(o.length,1),s=Math.max(0,o.findIndex(t=>t.id===e.id));if(n>1){const[e,,o]=t.size,r=Math.abs(i[0])>Math.abs(i[2]),a=n-1,c=(a>0?(s-a/2)/a:0)*(r?.4*o:.4*e);r?i[2]+=c:i[0]+=c}}return i}addEntityToRoom(t,e){this.roomEntities.has(e)||this.roomEntities.set(e,new Set),this.roomEntities.get(e).add(t),this.sceneRoot.addChild(t),t.enabled=e===this.currentRoom,console.log(`[MultiRoom] Added entity ${t.name} to room ${e}, enabled: ${t.enabled}`)}updateEntityVisibilityForRoom(t){console.log(`[MultiRoom] Updating entity visibility for room: ${t}`);for(const[e,o]of this.roomEntities)e!==t&&o.forEach(t=>{t.enabled=!1,this.enforceEntityVisibility(t,!1)});this.roomEntities.has(t)&&this.roomEntities.get(t).forEach(t=>{t.enabled=!0,this.enforceEntityVisibility(t,!0)})}async transitionToRoom(t,e){if(!this.rooms.has(t))return console.error(`[MultiRoom] Room ${t} not found`),!1;if(t===this.currentRoom)return console.log(`[MultiRoom] Already in room ${t}`),!0;console.log(`[MultiRoom] Transitioning from ${this.currentRoom} to ${t}`),this.app.fire("ui:notification",{text:`Entering ${t.replace("_"," ")}...`,type:"info",duration:2e3});try{const o=this.rooms.get(t);if(!o.entity){if(!this.sceneBuilder||!this.entityFactory)return console.error("[MultiRoom] Missing sceneBuilder or entityFactory for room building"),!1;await this.buildRoom(t,this.sceneBuilder,this.entityFactory)}if(this.currentRoom){const t=this.rooms.get(this.currentRoom);t.entity&&this.deactivateRoom(t)}this.activateRoom(o),this.currentRoom=t,this.updateEntityVisibilityForRoom(t);const i=this.app.root.findByTag("player")[0];return i&&e&&i.setPosition(e),this.updateRoomObjectives(t),this.app.fire("room:changed",{roomId:t,playerPosition:e,roomConfig:o}),console.log(`[MultiRoom] Successfully transitioned to ${t}`),!0}catch(e){return console.error(`[MultiRoom] Error transitioning to room ${t}:`,e),!1}}updateRoomObjectives(t){const e=this.rooms.get(t);if(!e||!e.objectives)return void console.log(`[MultiRoom] No objectives found for room: ${t}`);const o=this.core.getSystem("objectivemanager");o.clearAllObjectives(),console.log(`[MultiRoom] Updating objectives for room: ${t} (${e.objectives.length} objectives)`),e.objectives.forEach(t=>{console.log(`[MultiRoom] Processing objective: ${t.id} (type: ${t.type})`),o.objectives.has(t.id)||(o.defineObjective(t.id,t),console.log(`[MultiRoom] Defined objective: ${t.id}`));const e=t.prerequisites&&t.prerequisites.length>0,i=!e||t.prerequisites.every(t=>o.isComplete(t)),n="guidance"===t.type;!t.autoStart&&!n||!i||o.isObjectiveActive(t.id)||o.isComplete(t.id)?t.autoStart||n?o.isObjectiveActive(t.id)?console.log(`[MultiRoom] Objective ${t.id} already active`):o.isComplete(t.id)?console.log(`[MultiRoom] Objective ${t.id} already completed`):e&&!i&&(console.log(`[MultiRoom] Prerequisites not met for ${t.id}: ${t.prerequisites}`),t.prerequisites.forEach(t=>{console.log(`[MultiRoom] Prerequisite ${t} complete: ${o.isComplete(t)}`)})):console.log(`[MultiRoom] Objective ${t.id} is manual start - skipping`):(console.log(`[MultiRoom] Starting ${n?"guidance":"auto-start"} objective: ${t.id}`),o.startObjective(t.id),console.log(`[MultiRoom] Successfully started objective: ${t.id}`))}),this.checkAndActivateGuidanceObjectives(t)}checkAndActivateGuidanceObjectives(t){const e=this.rooms.get(t);if(!e||!e.objectives)return;const o=this.core.getSystem("objectivemanager");o&&e.objectives.forEach(t=>{"guidance"!==t.type||o.isObjectiveActive(t.id)||o.isComplete(t.id)||(!(t.prerequisites&&t.prerequisites.length>0)||t.prerequisites.every(t=>o.isComplete(t)))&&(console.log(`[MultiRoom] Activating guidance objective: ${t.id}`),o.startObjective(t.id),setTimeout(()=>{o.isObjectiveActive(t.id)&&(o.completeObjective(t.id),console.log(`[MultiRoom] Auto-completed guidance objective: ${t.id}`))},100))})}addItem(t){this.gameState.inventory.add(t);const e=this.core?this.core.getSystem("inventorymanager"):null;e?e.pickupItem(t,1):this.app.fire("ui:notification",{text:`Acquired: ${t.replace("_"," ")}`,type:"success"}),this.doorItems&&this.doorItems.updateAllDoorItems()}hasItem(t){if(this.gameState.inventory.has(t))return!0;const e=this.core?this.core.getSystem("inventorymanager"):null;return!!e&&e.hasItem(t)}recordInteraction(t){this.gameState.lastInteractions.push(t),this.gameState.lastInteractions.length>10&&this.gameState.lastInteractions.shift()}onDoorInteract(t){for(const[e,o]of this.doors)if(e===t||o.entity&&o.entity.name===t)return void o.onInteract(this.gameState);console.warn(`[MultiRoom] Door not found: ${t}`)}setupInteractions(t,e){this.interactionHandler=t=>{if(this.recordInteraction(t),t.includes("_to_")){if(this.onDoorInteract(t),this.doorItems){const e=Array.from(this.doors.values()).find(e=>e.id===t);e&&this.doorItems.onDoorInteraction(e.id)}}else this.handleRoomInteraction(t)},this.npcInteractionHandler=t=>{console.log(`[MultiRoom] NPC interaction with ${t}`),this.recordInteraction(t),this.sceneInteractionHandler?this.sceneInteractionHandler(t):console.log(`[MultiRoom] No scene interaction handler registered for NPC: ${t}`)},t.on("interaction:triggered",this.interactionHandler),t.on("npc:interact",this.npcInteractionHandler)}handleRoomInteraction(t){console.log(`[MultiRoom] Room interaction: ${t} in room ${this.currentRoom}`),this.sceneInteractionHandler?(console.log("[MultiRoom] Calling scene interaction handler"),this.sceneInteractionHandler(t)):console.log("[MultiRoom] No scene interaction handler registered")}setSceneInteractionHandler(t){this.sceneInteractionHandler=t}syncWithGlobalInventory(t){const e=t?t.getSystem("inventorymanager"):null;if(e){for(const[t,o]of e.items.entries())o&&o.id&&this.gameState.inventory.add(o.id);console.log(`[MultiRoom] Synced ${this.gameState.inventory.size} items from global inventory`)}else console.warn("[MultiRoom] No InventoryManager available for sync")}onLoad(t,e){this.setupInteractions(t,e),this.app=t,this.core=e,this.gameState&&e&&(this.gameState.core=e),this.syncWithGlobalInventory(e),this.setupObjectives(t,e),this.miniMap=new n(t,this),setTimeout(()=>{t.fire("ui:notification",{text:`Multi-room scene loaded: ${this.currentRoom}. All interactions available.`,type:"info",duration:3e3})},500),console.log(`[MultiRoom] Multi-room scene loaded, starting in ${this.currentRoom}`),console.log("[MultiRoom] GameState inventory:",this.gameState.inventory.size,"items"),setTimeout(()=>{const t=this.sceneRoot.find(t=>t.tags&&t.tags.has("interactive"));console.log(`[MultiRoom] Found ${t.length} interactive items:`),t.forEach(t=>{console.log(` - ${t.name}: enabled=${t.enabled}, position=${t.getPosition().toString()}`)})},1e3)}setupObjectives(t,e){const o=e.getSystem("objectivemanager");o?(console.log("[MultiRoom] Setting up objectives for multi-room scene"),o.clearAllObjectives(),this.currentRoom&&(console.log(`[MultiRoom] Starting objectives for initial room: ${this.currentRoom}`),this.updateRoomObjectives(this.currentRoom)),console.log(`[MultiRoom] Objective setup complete for current room: ${this.currentRoom}`)):console.warn("[MultiRoom] ObjectiveManager not available")}onUnload(t,e){console.log("[MultiRoom] Cleaning up multi-room scene..."),this.interactionHandler&&(t.off("interaction:triggered",this.interactionHandler),this.interactionHandler=null),this.npcInteractionHandler&&(t.off("npc:interact",this.npcInteractionHandler),this.npcInteractionHandler=null),this.miniMap&&(this.miniMap.destroy(),this.miniMap=null),this.doorItems&&(this.doorItems.cleanup(),this.doorItems=null);for(const[t,e]of this.roomEntities)e.forEach(t=>{t&&t.destroy&&t.destroy()});this.roomEntities.clear();for(const[t,e]of this.rooms)e.entity&&e.entity.findByTag("npc").forEach(t=>{t.cleanupLabel&&t.cleanupLabel()}),e.active=!1;this.gameState.inventory.clear(),this.gameState.lastInteractions=[],this.gameState.skills.clear(),this.currentRoom=null}}}}]);
assets/data/catalog.json CHANGED
@@ -486,6 +486,97 @@
486
  "Objects with spinning parts demonstrate animation integration"
487
  ]
488
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  }
490
  ]
491
- }
 
486
  "Objects with spinning parts demonstrate animation integration"
487
  ]
488
  }
489
+ },
490
+ {
491
+ "id": 17,
492
+ "title": "17 - Fracture Reduction Surgery: Orthopedic Challenge",
493
+ "description": "Complex medical simulation of fracture reduction surgery with realistic bone mechanics",
494
+ "type": "scene",
495
+ "preview": "fracture_surgery_preview.jpg",
496
+ "script": "FractureReductionSurgery",
497
+ "briefing": {
498
+ "setting": "Four-room surgical complex during complex fracture reduction surgery - realistic orthopedic simulation",
499
+ "role": "Orthopedic surgeon performing fracture reduction and internal fixation",
500
+ "objectives": [
501
+ "Complete pre-operative assessment and surgical preparation checklist",
502
+ "Perform surgical exposure and achieve anatomical fracture reduction",
503
+ "Apply internal fixation hardware with proper placement technique",
504
+ "Verify reduction quality with fluoroscopy and complete surgical closure"
505
+ ],
506
+ "controls": [
507
+ "F - Interact with surgical instruments, reduction tools, and imaging equipment",
508
+ "Navigate Pre-Op Prep β†’ OR Suite β†’ Fluoroscopy Suite β†’ Post-Op Recovery",
509
+ "Progressive surgical skill requirements unlock advanced procedures"
510
+ ],
511
+ "tips": [
512
+ "Patient chart review reveals complex comminuted femur fracture pattern",
513
+ "Fracture reduction requires precise alignment: <2mm displacement, <5Β° angulation",
514
+ "Multiple fluoroscopy angles needed for complete imaging verification",
515
+ "Surgical time affects patient vitals - work efficiently but precisely",
516
+ "85% reduction quality required for successful internal fixation"
517
+ ]
518
+ }
519
+ },
520
+ {
521
+ "id": 18,
522
+ "title": "18 - Systems Testing Laboratory: Implementation Feedback Hub",
523
+ "description": "Comprehensive testing environment for UI stats, inventory, and resource management systems",
524
+ "type": "scene",
525
+ "preview": "systems_lab_preview.jpg",
526
+ "script": "SystemsTestingLab",
527
+ "briefing": {
528
+ "setting": "Advanced systems testing laboratory with four specialized testing environments",
529
+ "role": "Systems tester evaluating new UI and gameplay mechanics",
530
+ "objectives": [
531
+ "Complete laboratory orientation and familiarize with testing protocols",
532
+ "Test player stats UI enhancements including health, stamina, and attribute displays",
533
+ "Evaluate inventory system interactions and management interfaces",
534
+ "Test resource management systems including water taps, power generation, and monitoring",
535
+ "Evaluate equipment slot systems, item durability, and crafting mechanics",
536
+ "Provide comprehensive feedback on system usability and performance"
537
+ ],
538
+ "controls": [
539
+ "F - Interact with testing stations, consoles, and evaluation equipment",
540
+ "Navigate Main Lab β†’ Resource Lab β†’ Equipment Lab β†’ Feedback Center",
541
+ "Progressive testing unlocks advanced system evaluation areas"
542
+ ],
543
+ "tips": [
544
+ "Each testing room focuses on specific system implementations",
545
+ "Resource systems include water flow, power distribution, and monitoring displays",
546
+ "Equipment testing covers slots, durability tracking, and item combinations",
547
+ "Feedback collection helps prioritize development and improvements",
548
+ "Testing progression unlocks increasingly complex system interactions"
549
+ ]
550
+ }
551
+ }
552
+ ,
553
+ {
554
+ "id": 19,
555
+ "title": "19 - NPC System Test: Patrols, Dialogue, Proximity",
556
+ "description": "Focused sandbox to verify NPC patrol waypoints with waits, cleanup, dialogue fallbacks, and interaction ranges.",
557
+ "type": "scene",
558
+ "preview": "npc_test_preview.jpg",
559
+ "script": "NPCSystemTest19",
560
+ "briefing": {
561
+ "setting": "Open test arena with two patrolling guards and two social NPCs",
562
+ "role": "QA tester validating NPC behavior",
563
+ "objectives": [
564
+ "Observe Guard A and B following waypoint patrols with wait times",
565
+ "Interact with Merchant (3m range) and Questgiver (3.5m range)",
566
+ "Verify dialogue fallback when no dialogue id is provided",
567
+ "Exit and reload scene to confirm NPCManager.clearAll cleanup"
568
+ ],
569
+ "controls": [
570
+ "F - Interact with NPCs",
571
+ "WASD - Move; Mouse - Look",
572
+ "Use debug.loadContent(19) or catalog to load this scene"
573
+ ],
574
+ "tips": [
575
+ "Guards pause briefly at each waypoint",
576
+ "Merchant uses merchant_default; questgiver falls back to questgiver_default",
577
+ "Too-far interactions show a warning based on per-NPC distance"
578
+ ]
579
+ }
580
  }
581
  ]
582
+ }
bundle.js CHANGED
The diff for this file is too large to render. See raw diff
 
index.html CHANGED
@@ -23,11 +23,11 @@
23
  bottom: 0;
24
  }
25
  </style>
26
- <script src="https://code.playcanvas.com/playcanvas-stable.min.js"></script>
27
- <script src="https://code.playcanvas.com/ammo.wasm.js"></script>
28
  </head>
29
  <body>
30
  <canvas id="application-canvas"></canvas>
31
  <script src="bundle.js"></script>
32
  </body>
33
- </html>
 
23
  bottom: 0;
24
  }
25
  </style>
26
+ <script crossorigin="anonymous" src="https://code.playcanvas.com/playcanvas-stable.min.js"></script>
27
+ <script crossorigin="anonymous" src="https://code.playcanvas.com/ammo.wasm.js"></script>
28
  </head>
29
  <body>
30
  <canvas id="application-canvas"></canvas>
31
  <script src="bundle.js"></script>
32
  </body>
33
+ </html>