├── .gitattributes ├── .gitignore ├── Data ├── Localization │ └── MyTexts.override.resx └── Scripts │ └── Examples │ ├── AttachedLights │ ├── AttachedLightsSession.cs │ ├── BlockLogic.cs │ ├── Setup.cs │ ├── SimpleLog.cs │ ├── Usage.txt │ └── Utils.cs │ ├── BasicExample_GameLogicAndSession │ ├── GameLogic.cs │ └── Session.cs │ ├── Debug_DrawSolarBlocks.cs │ ├── Debug_ShowShotPositions.cs │ ├── ExampleWorkaround_GridLogicHook.cs │ ├── Example_AppendToCharacterStatComp.cs │ ├── Example_AppendToSkin.cs │ ├── Example_AssemblerForceMode.cs │ ├── Example_AsyncDrawHook.cs │ ├── Example_CustomUseObject.cs │ ├── Example_DetectGunBlockFire.cs │ ├── Example_DrawingIn3D.cs │ ├── Example_EditBlockPowerUsage.cs │ ├── Example_EditCharacterDef.cs │ ├── Example_GasOutput.cs │ ├── Example_HighlightingEntities.cs │ ├── Example_HudStatOverride.cs │ ├── Example_InputReading.cs │ ├── Example_MessageOnJoin.cs │ ├── Example_ModifyBlockVariants.cs │ ├── Example_ModifyContainerTypes.cs │ ├── Example_ModifyProjectileExplosion.cs │ ├── Example_ModifyTimerLimits.cs │ ├── Example_ModifyWarheadExplosion.cs │ ├── Example_NetworkProtobuf │ ├── ExampleNetwork_Session.cs │ ├── NetworkLib │ │ ├── Network.cs │ │ ├── PacketBase.cs │ │ └── Usage.txt │ ├── PacketSimpleExample.cs │ └── RegisterPackets.cs │ ├── Example_OverrideLocalizationKeys.cs │ ├── Example_PreventBlockDamageOnCharacters.cs │ ├── Example_ProtectionBlock │ ├── Block.cs │ └── Session.cs │ ├── Example_RemoveCategory.cs │ ├── Example_RemoveFromBPClass.cs │ ├── Example_ServerConfig_Basic.cs │ ├── Example_SetFactionColors.cs │ ├── Example_SetInventoryConstraintIcon.cs │ ├── Example_ShipDrillHarvestMultiplier.cs │ ├── Example_ShipGrinderSpeed.cs │ ├── Example_SpinningSubpart.cs │ ├── Example_SubpartMoveAdvanced.cs │ ├── Example_SurvivalKitDisableRespawn.cs │ ├── Example_TSS.cs │ ├── Example_TargetDummyFixSlider.cs │ ├── Example_UpgradeModuleSupport.cs │ ├── Example_WriteToDetailedInfo.cs │ ├── Fast Solo DS Testing │ └── copy to DS and client.bat │ ├── MissileParticleReplacer │ └── MissileParticleReplacer.cs │ ├── MissileTurretProjectileSoundFix │ └── MissileTurretProjectileSoundFix.cs │ ├── ParticleAtDummy │ ├── Conditions.cs │ ├── Gamelogic.cs │ ├── Readme.txt │ └── Setup.cs │ ├── RealisticHoloSight │ └── RealisticHoloSight.cs │ └── TerminalControls │ ├── Adding │ ├── GyroLogic.cs │ └── GyroTerminalControls.cs │ ├── Events │ └── TerminalEventsExamples.cs │ └── Hiding │ ├── HideControlsExample.cs │ ├── SensorLogic.cs │ └── TerminalChainedDelegate.cs ├── Examples.csproj ├── Examples.sln └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | 7 | 8 | Ignored/ 9 | /Data/Scripts/Examples/\#Meh 10 | 11 | 12 | 13 | # User-specific files 14 | *.rsuser 15 | *.suo 16 | *.user 17 | *.userosscache 18 | *.sln.docstates 19 | 20 | # User-specific files (MonoDevelop/Xamarin Studio) 21 | *.userprefs 22 | 23 | # Build results 24 | [Dd]ebug/ 25 | [Dd]ebugPublic/ 26 | [Rr]elease/ 27 | [Rr]eleases/ 28 | x64/ 29 | x86/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 33 | [Ll]og/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUNIT 48 | *.VisualState.xml 49 | TestResult.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # JustCode is a .NET coding add-in 131 | .JustCode 132 | 133 | # TeamCity is a build add-in 134 | _TeamCity* 135 | 136 | # DotCover is a Code Coverage Tool 137 | *.dotCover 138 | 139 | # AxoCover is a Code Coverage Tool 140 | .axoCover/* 141 | !.axoCover/settings.json 142 | 143 | # Visual Studio code coverage results 144 | *.coverage 145 | *.coveragexml 146 | 147 | # NCrunch 148 | _NCrunch_* 149 | .*crunch*.local.xml 150 | nCrunchTemp_* 151 | 152 | # MightyMoose 153 | *.mm.* 154 | AutoTest.Net/ 155 | 156 | # Web workbench (sass) 157 | .sass-cache/ 158 | 159 | # Installshield output folder 160 | [Ee]xpress/ 161 | 162 | # DocProject is a documentation generator add-in 163 | DocProject/buildhelp/ 164 | DocProject/Help/*.HxT 165 | DocProject/Help/*.HxC 166 | DocProject/Help/*.hhc 167 | DocProject/Help/*.hhk 168 | DocProject/Help/*.hhp 169 | DocProject/Help/Html2 170 | DocProject/Help/html 171 | 172 | # Click-Once directory 173 | publish/ 174 | 175 | # Publish Web Output 176 | *.[Pp]ublish.xml 177 | *.azurePubxml 178 | # Note: Comment the next line if you want to checkin your web deploy settings, 179 | # but database connection strings (with potential passwords) will be unencrypted 180 | *.pubxml 181 | *.publishproj 182 | 183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 184 | # checkin your Azure Web App publish settings, but sensitive information contained 185 | # in these scripts will be unencrypted 186 | PublishScripts/ 187 | 188 | # NuGet Packages 189 | *.nupkg 190 | # The packages folder can be ignored because of Package Restore 191 | **/[Pp]ackages/* 192 | # except build/, which is used as an MSBuild target. 193 | !**/[Pp]ackages/build/ 194 | # Uncomment if necessary however generally it will be regenerated when needed 195 | #!**/[Pp]ackages/repositories.config 196 | # NuGet v3's project.json files produces more ignorable files 197 | *.nuget.props 198 | *.nuget.targets 199 | 200 | # Microsoft Azure Build Output 201 | csx/ 202 | *.build.csdef 203 | 204 | # Microsoft Azure Emulator 205 | ecf/ 206 | rcf/ 207 | 208 | # Windows Store app package directories and files 209 | AppPackages/ 210 | BundleArtifacts/ 211 | Package.StoreAssociation.xml 212 | _pkginfo.txt 213 | *.appx 214 | 215 | # Visual Studio cache files 216 | # files ending in .cache can be ignored 217 | *.[Cc]ache 218 | # but keep track of directories ending in .cache 219 | !*.[Cc]ache/ 220 | 221 | # Others 222 | ClientBin/ 223 | ~$* 224 | *~ 225 | *.dbmdl 226 | *.dbproj.schemaview 227 | *.jfm 228 | *.pfx 229 | *.publishsettings 230 | orleans.codegen.cs 231 | 232 | # Including strong name files can present a security risk 233 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 234 | #*.snk 235 | 236 | # Since there are multiple workflows, uncomment next line to ignore bower_components 237 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 238 | #bower_components/ 239 | 240 | # RIA/Silverlight projects 241 | Generated_Code/ 242 | 243 | # Backup & report files from converting an old project file 244 | # to a newer Visual Studio version. Backup files are not needed, 245 | # because we have git ;-) 246 | _UpgradeReport_Files/ 247 | Backup*/ 248 | UpgradeLog*.XML 249 | UpgradeLog*.htm 250 | ServiceFabricBackup/ 251 | *.rptproj.bak 252 | 253 | # SQL Server files 254 | *.mdf 255 | *.ldf 256 | *.ndf 257 | 258 | # Business Intelligence projects 259 | *.rdl.data 260 | *.bim.layout 261 | *.bim_*.settings 262 | *.rptproj.rsuser 263 | 264 | # Microsoft Fakes 265 | FakesAssemblies/ 266 | 267 | # GhostDoc plugin setting file 268 | *.GhostDoc.xml 269 | 270 | # Node.js Tools for Visual Studio 271 | .ntvs_analysis.dat 272 | node_modules/ 273 | 274 | # Visual Studio 6 build log 275 | *.plg 276 | 277 | # Visual Studio 6 workspace options file 278 | *.opt 279 | 280 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 281 | *.vbw 282 | 283 | # Visual Studio LightSwitch build output 284 | **/*.HTMLClient/GeneratedArtifacts 285 | **/*.DesktopClient/GeneratedArtifacts 286 | **/*.DesktopClient/ModelManifest.xml 287 | **/*.Server/GeneratedArtifacts 288 | **/*.Server/ModelManifest.xml 289 | _Pvt_Extensions 290 | 291 | # Paket dependency manager 292 | .paket/paket.exe 293 | paket-files/ 294 | 295 | # FAKE - F# Make 296 | .fake/ 297 | 298 | # JetBrains Rider 299 | .idea/ 300 | *.sln.iml 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | -------------------------------------------------------------------------------- /Data/Localization/MyTexts.override.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | text/microsoft-resx 9 | 10 | 11 | 2.0 12 | 13 | 14 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 15 | 16 | 17 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 18 | 19 | 20 | 21 | Ironman 22 | 23 | 24 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/AttachedLights/AttachedLightsSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Sandbox.Definitions; 4 | using Sandbox.Game.Entities; 5 | using Sandbox.Game.Lights; 6 | using Sandbox.ModAPI; 7 | using VRage.Game; 8 | using VRage.Game.Components; 9 | using VRage.Game.ModAPI; 10 | using VRage.ModAPI; 11 | using VRage.ObjectBuilders; 12 | using VRageMath; 13 | 14 | namespace Digi.AttachedLights 15 | { 16 | public delegate void LightConfigurator(string dummyName, MyLight light, BlockLogic blockLogic); 17 | 18 | public class BlockHandling 19 | { 20 | public LightConfigurator ConfiguratorForAll = null; 21 | public Dictionary ConfiguratorPerSubtype = null; 22 | } 23 | 24 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 25 | public partial class AttachedLightsSession : MySessionComponentBase 26 | { 27 | public const string DUMMY_PREFIX = "customlight_"; 28 | public const int CHECK_VIEW_DIST_EVERY_TICKS = 15; 29 | 30 | public static AttachedLightsSession Instance; 31 | 32 | public List UpdateOnce; 33 | public Dictionary> ViewDistanceChecks; 34 | public Dictionary TempDummies; 35 | 36 | Dictionary monitorTypes; 37 | Dictionary blockLogics; 38 | int tick; 39 | 40 | public override void LoadData() 41 | { 42 | try 43 | { 44 | if(MyAPIGateway.Utilities.IsDedicated) // DS side doesn't need lights 45 | return; 46 | 47 | Instance = this; 48 | UpdateOnce = new List(); 49 | monitorTypes = new Dictionary(); 50 | blockLogics = new Dictionary(); 51 | ViewDistanceChecks = new Dictionary>(); 52 | 53 | TempDummies = new Dictionary(); 54 | 55 | MyAPIGateway.Entities.OnEntityAdd += EntitySpawned; 56 | 57 | SetUpdateOrder(MyUpdateOrder.AfterSimulation); 58 | 59 | Setup(); 60 | } 61 | catch(Exception e) 62 | { 63 | SimpleLog.Error(this, e); 64 | UnloadData(); 65 | throw; 66 | } 67 | } 68 | 69 | protected override void UnloadData() 70 | { 71 | Instance = null; 72 | 73 | if(MyAPIGateway.Utilities.IsDedicated) 74 | return; 75 | 76 | MyAPIGateway.Entities.OnEntityAdd -= EntitySpawned; 77 | 78 | monitorTypes?.Clear(); 79 | monitorTypes = null; 80 | 81 | blockLogics?.Clear(); 82 | blockLogics = null; 83 | 84 | ViewDistanceChecks?.Clear(); 85 | ViewDistanceChecks = null; 86 | 87 | TempDummies?.Clear(); 88 | TempDummies = null; 89 | } 90 | 91 | void Add(LightConfigurator settings, MyObjectBuilderType blockType, params string[] subtypes) 92 | { 93 | BlockHandling blockHandling; 94 | 95 | if(!monitorTypes.TryGetValue(blockType, out blockHandling)) 96 | { 97 | blockHandling = new BlockHandling(); 98 | monitorTypes.Add(blockType, blockHandling); 99 | } 100 | 101 | if(subtypes == null || subtypes.Length == 0) 102 | { 103 | blockHandling.ConfiguratorForAll = settings; 104 | } 105 | else 106 | { 107 | if(blockHandling.ConfiguratorPerSubtype == null) 108 | { 109 | blockHandling.ConfiguratorPerSubtype = new Dictionary(); 110 | } 111 | 112 | foreach(var subtype in subtypes) 113 | { 114 | if(blockHandling.ConfiguratorPerSubtype.ContainsKey(subtype)) 115 | { 116 | SimpleLog.Error(this, $"Subtype '{subtype}' for type {blockType.ToString()} was already previously registered!"); 117 | continue; 118 | } 119 | 120 | blockHandling.ConfiguratorPerSubtype.Add(subtype, settings); 121 | } 122 | } 123 | } 124 | 125 | void EntitySpawned(IMyEntity ent) 126 | { 127 | try 128 | { 129 | var grid = ent as MyCubeGrid; 130 | 131 | if(grid == null || grid.Physics == null || grid.IsPreview) 132 | return; 133 | 134 | grid.OnBlockAdded += BlockAdded; 135 | grid.OnClose += GridClosed; 136 | 137 | foreach(IMySlimBlock slim in grid.GetBlocks()) 138 | { 139 | BlockAdded(slim); 140 | } 141 | } 142 | catch(Exception e) 143 | { 144 | SimpleLog.Error(this, e); 145 | } 146 | } 147 | 148 | void GridClosed(IMyEntity ent) 149 | { 150 | try 151 | { 152 | var grid = (IMyCubeGrid)ent; 153 | grid.OnBlockAdded -= BlockAdded; 154 | grid.OnClose -= GridClosed; 155 | } 156 | catch(Exception e) 157 | { 158 | SimpleLog.Error(this, e); 159 | } 160 | } 161 | 162 | void BlockAdded(IMySlimBlock slimBlock) 163 | { 164 | try 165 | { 166 | var defId = slimBlock.BlockDefinition.Id; 167 | BlockHandling blockHandling; 168 | 169 | if(monitorTypes.TryGetValue(defId.TypeId, out blockHandling)) 170 | { 171 | LightConfigurator settings; 172 | 173 | if(blockHandling.ConfiguratorPerSubtype != null && blockHandling.ConfiguratorPerSubtype.TryGetValue(defId.SubtypeName, out settings)) 174 | { 175 | CreateLogicFor(slimBlock, settings); 176 | } 177 | else if(blockHandling.ConfiguratorForAll != null) 178 | { 179 | CreateLogicFor(slimBlock, blockHandling.ConfiguratorForAll); 180 | } 181 | } 182 | } 183 | catch(Exception e) 184 | { 185 | SimpleLog.Error(this, e); 186 | } 187 | } 188 | 189 | void CreateLogicFor(IMySlimBlock slimBlock, LightConfigurator settings) 190 | { 191 | var def = (MyCubeBlockDefinition)slimBlock.BlockDefinition; 192 | 193 | if(def.BlockTopology == MyBlockTopology.Cube && def.Model == null) 194 | { 195 | // deformable armor not supported. 196 | return; 197 | } 198 | 199 | var block = slimBlock.FatBlock; 200 | 201 | if(block == null) 202 | { 203 | SimpleLog.Error(this, $"{slimBlock.BlockDefinition.Id.ToString()} has no fatblock?! buildRatio={slimBlock.BuildLevelRatio.ToString()}; damageRatio={slimBlock.DamageRatio.ToString()}"); 204 | return; 205 | } 206 | 207 | if(blockLogics.ContainsKey(block.EntityId)) 208 | { 209 | BlockMarkedForClose(block); 210 | } 211 | 212 | var logic = new BlockLogic(this, block, settings); 213 | block.OnMarkForClose += BlockMarkedForClose; 214 | blockLogics[block.EntityId] = logic; 215 | } 216 | 217 | void BlockMarkedForClose(IMyEntity ent) 218 | { 219 | try 220 | { 221 | var block = (IMyCubeBlock)ent; 222 | block.OnMarkForClose -= BlockMarkedForClose; 223 | 224 | blockLogics.GetValueOrDefault(block.EntityId, null)?.Close(); 225 | blockLogics.Remove(block.EntityId); 226 | ViewDistanceChecks.Remove(block.EntityId); 227 | } 228 | catch(Exception e) 229 | { 230 | SimpleLog.Error(this, e); 231 | } 232 | } 233 | 234 | public override void UpdateAfterSimulation() 235 | { 236 | try 237 | { 238 | if(ViewDistanceChecks.Count > 0 && ++tick % CHECK_VIEW_DIST_EVERY_TICKS == 0) 239 | { 240 | var cameraPos = MyAPIGateway.Session.Camera.WorldMatrix.Translation; 241 | 242 | foreach(var action in ViewDistanceChecks.Values) 243 | { 244 | action(cameraPos); 245 | } 246 | } 247 | 248 | if(UpdateOnce.Count > 0) 249 | { 250 | foreach(var logic in UpdateOnce) 251 | { 252 | if(logic.Block.MarkedForClose) 253 | continue; 254 | 255 | logic.UpdateOnce(); 256 | } 257 | 258 | UpdateOnce.Clear(); 259 | } 260 | } 261 | catch(Exception e) 262 | { 263 | SimpleLog.Error(this, e); 264 | } 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/AttachedLights/BlockLogic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Sandbox.Definitions; 4 | using Sandbox.Game.Entities; 5 | using Sandbox.Game.Lights; 6 | using Sandbox.ModAPI; 7 | using VRage.Game; 8 | using VRage.Game.Components; 9 | using VRage.Game.Entity; 10 | using VRage.Game.ModAPI; 11 | using VRage.ModAPI; 12 | using VRageMath; 13 | 14 | namespace Digi.AttachedLights 15 | { 16 | public class BlockLogic 17 | { 18 | public readonly IMyCubeBlock Block; 19 | public readonly AttachedLightsSession Session; 20 | 21 | /// 22 | /// View range limiter, only used if > 0. 23 | /// 24 | public float MaxViewRange 25 | { 26 | set { maxViewRangeSq = value * value; } 27 | } 28 | 29 | private readonly LightConfigurator configurator; 30 | private Dictionary lights; 31 | private bool scannedForDummies = false; 32 | private bool inViewRange = true; 33 | private float maxViewRangeSq; 34 | 35 | public BlockLogic(AttachedLightsSession session, IMyCubeBlock block, LightConfigurator configurator) 36 | { 37 | Session = session; 38 | Block = block; 39 | this.configurator = configurator; 40 | 41 | Block.IsWorkingChanged += WorkingChanged; 42 | WorkingChanged(Block); 43 | 44 | if(maxViewRangeSq > 0) 45 | session.ViewDistanceChecks.Add(Block.EntityId, RangeCheck); 46 | } 47 | 48 | void FindLightDummies() 49 | { 50 | if(scannedForDummies || !Block.IsFunctional) 51 | return; // only scan once and only if block is functional (has its main model) 52 | 53 | var def = (MyCubeBlockDefinition)Block.SlimBlock.BlockDefinition; 54 | if(!def.Model.EndsWith(Block.Model.AssetName)) 55 | { 56 | SimpleLog.Error(this, $"ERROR: block {Block.BlockDefinition.ToString()} is functional model is not the main one...\nBlock model='{Block.Model.AssetName}'\nDefinition model='{def.Model}'"); 57 | return; 58 | } 59 | 60 | scannedForDummies = true; 61 | 62 | ScanSubparts(Block); 63 | 64 | if(lights == null) 65 | { 66 | SimpleLog.Error(this, $"{Block.BlockDefinition.ToString()} has no dummies with '{AttachedLightsSession.DUMMY_PREFIX}' prefix!"); 67 | } 68 | else 69 | { 70 | if(inViewRange) 71 | SetLights(Block.IsWorking); 72 | } 73 | } 74 | 75 | void ScanSubparts(IMyEntity entity) 76 | { 77 | ScanDummiesForEntity(entity); 78 | 79 | var internalEntity = (MyEntity)entity; 80 | foreach(var subpart in internalEntity.Subparts.Values) 81 | { 82 | ScanSubparts(subpart); 83 | } 84 | } 85 | 86 | void ScanDummiesForEntity(IMyEntity entity) 87 | { 88 | var dummies = Session.TempDummies; 89 | dummies.Clear(); 90 | entity.Model.GetDummies(dummies); 91 | 92 | foreach(var dummy in dummies.Values) 93 | { 94 | if(dummy.Name.StartsWith(AttachedLightsSession.DUMMY_PREFIX, StringComparison.OrdinalIgnoreCase)) 95 | { 96 | if(lights == null) 97 | lights = new Dictionary(); 98 | 99 | CreateLight(entity, dummy.Name, dummy.Matrix); 100 | } 101 | } 102 | } 103 | 104 | void CreateLight(IMyEntity entity, string dummyName, Matrix dummyMatrix) 105 | { 106 | var light = MyLights.AddLight(); 107 | light.Start(dummyName); 108 | light.Color = Color.White; 109 | light.Range = Block.CubeGrid.GridSize; 110 | light.Falloff = 1f; 111 | light.Intensity = 2f; 112 | light.ParentID = Block.CubeGrid.Render.GetRenderObjectID(); 113 | light.Position = Vector3D.Transform(Vector3D.Transform(dummyMatrix.Translation, Block.WorldMatrix), Block.CubeGrid.WorldMatrixInvScaled); 114 | light.ReflectorDirection = Vector3D.TransformNormal(Vector3D.TransformNormal(dummyMatrix.Forward, Block.WorldMatrix), Block.CubeGrid.WorldMatrixInvScaled); 115 | light.ReflectorUp = Vector3D.TransformNormal(Vector3D.TransformNormal(dummyMatrix.Up, Block.WorldMatrix), Block.CubeGrid.WorldMatrixInvScaled); 116 | lights.Add(dummyName, light); 117 | 118 | configurator(dummyName, light, this); 119 | 120 | light.UpdateLight(); 121 | } 122 | 123 | public void Close() 124 | { 125 | try 126 | { 127 | if(lights != null) 128 | { 129 | foreach(var light in lights.Values) 130 | { 131 | MyLights.RemoveLight(light); 132 | } 133 | 134 | lights.Clear(); 135 | } 136 | } 137 | catch(Exception e) 138 | { 139 | SimpleLog.Error(this, e); 140 | } 141 | } 142 | 143 | void WorkingChanged(IMyCubeBlock block) 144 | { 145 | try 146 | { 147 | if(!inViewRange) 148 | return; 149 | 150 | Session.UpdateOnce.Add(this); // update next frame 151 | SetLights(block.IsWorking); 152 | } 153 | catch(Exception e) 154 | { 155 | SimpleLog.Error(this, e); 156 | } 157 | } 158 | 159 | public void UpdateOnce() 160 | { 161 | FindLightDummies(); 162 | } 163 | 164 | void SetLights(bool on) 165 | { 166 | if(lights != null) 167 | { 168 | foreach(var light in lights.Values) 169 | { 170 | light.LightOn = on; 171 | light.GlareOn = on; 172 | 173 | if(light.LightType == MyLightType.SPOTLIGHT) 174 | light.ReflectorOn = on; 175 | 176 | light.UpdateLight(); 177 | } 178 | } 179 | } 180 | 181 | void RangeCheck(Vector3D cameraPosition) 182 | { 183 | var inRange = (Vector3D.DistanceSquared(cameraPosition, Block.WorldMatrix.Translation) <= maxViewRangeSq); 184 | 185 | if(inViewRange == inRange) 186 | return; 187 | 188 | inViewRange = inRange; 189 | SetLights(inViewRange ? Block.IsWorking : false); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/AttachedLights/Setup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Common.ObjectBuilders; 3 | using Sandbox.Game.Lights; 4 | using Sandbox.ModAPI; 5 | using VRage.Game; 6 | using VRage.Game.Components; 7 | using VRageMath; 8 | using VRageRender.Lights; 9 | 10 | namespace Digi.AttachedLights 11 | { 12 | public partial class AttachedLightsSession 13 | { 14 | // First off your models need at least one 'customlight_' prefixed dummy for this script to add lights at. 15 | // The dummy orientation is also used for spotlights. 16 | // 17 | // Examples of defining your blocks: 18 | // 19 | // Add(Configurator, typeof(MyObjectBuilder_Passage)); 20 | // that adds all Passage type blocks with the specified Configurator. 21 | // 22 | // Add(Configurator, typeof(MyObjectBuilder_TerminalBlock), "ControlPanel", "SmallControlPanel"); 23 | // that TerminalBlock types with the specified subtypes and specified Configurator. 24 | // 25 | // You can also define same type multiple times but it has to be with different subtypes. 26 | // If you define one type with no subtypes and the same type again with specified subtypes, 27 | // the specified subtypes will get the defined configurator and everything else will get the configurator 28 | // that was defined on the no-subtype one. 29 | // 30 | // To find out what `MyObjectBuilder_` you need just look at the block TypeId and append that. 31 | // 32 | // 33 | // Next the config itself, you can define as many as you want and even call others inside it. 34 | // Format, things in <> are editable. 35 | // 36 | // LightConfigurator = (dummyName, light, blockLogic) => 37 | // { 38 | // 39 | // }; 40 | // 41 | // see the example configurator below for all available properties and sample conditions. 42 | 43 | void Setup() 44 | { 45 | Add(ExampleConfig, typeof(MyObjectBuilder_TerminalBlock), "ControlPanel", "SmallControlPanel"); // vanilla control panels only 46 | } 47 | 48 | // These are functions that get called for every dummy in the block so you can configure each dummy differently 49 | LightConfigurator ExampleConfig = (dummyName, light, blockLogic) => 50 | { 51 | //blockLogic.MaxViewRange = 50; // defines at which range light turns off; it's faster computationally to not define this if you don't need it. 52 | 53 | // comment out any section/property you don't want to set 54 | 55 | // Point light properties 56 | light.LightOn = true; 57 | light.Color = new Color(0, 255, 0); // RGB 58 | light.Range = 5f; 59 | light.Falloff = 1f; 60 | light.Intensity = 5f; 61 | light.PointLightOffset = 0f; // offset light source towards block forward(?), I don't think it moves the glare too. 62 | //light.DiffuseFactor = 1f; // not sure what numbers do in this 63 | 64 | // Spotlight properties 65 | light.LightType = MyLightType.SPOTLIGHT; 66 | light.ReflectorOn = true; 67 | light.ReflectorColor = new Color(255, 155, 0); // RGB 68 | light.ReflectorIntensity = 10f; 69 | light.ReflectorRange = 100; // how far the projected light goes 70 | light.ReflectorConeDegrees = 90; // projected light angle in degrees, max 179. 71 | light.ReflectorTexture = @"Textures\Lights\reflector_large.dds"; // NOTE: for textures inside your mod you need to use: Utils.GetModTextureFullPath(@"Textures\someFile.dds"); 72 | light.CastShadows = true; 73 | //light.ReflectorGlossFactor = f; // affects gloss in some way 74 | 75 | // Glare properties... which don't seem to work... 76 | light.GlareOn = true; 77 | light.GlareSize = new Vector2(1, 1); // glare size in X and Y. 78 | light.GlareIntensity = 2; 79 | light.GlareMaxDistance = 50; 80 | light.SubGlares = Utils.GetFlareDefinition("InteriorLight").SubGlares; // subtype name from flares.sbc 81 | light.GlareType = MyGlareTypeEnum.Normal; // usable values: MyGlareTypeEnum.Normal, MyGlareTypeEnum.Distant, MyGlareTypeEnum.Directional 82 | light.GlareQuerySize = 0.5f; // glare "box" size, affects occlusion and fade occlussion 83 | light.GlareQueryShift = 1f; // no idea 84 | 85 | 86 | 87 | // Examples of differentiating between dummies 88 | // 89 | // Contents of this condition only apply to 'customlight_light1'. 90 | // if(dummyName.Equals(DUMMY_PREFIX + "light1", StringComparison.OrdinalIgnoreCase)) 91 | // { 92 | // 93 | // } 94 | // 95 | // Contents of this condition apply to any dummy name that starts with 'customlight_num'. 96 | // if(dummyName.StartsWith(DUMMY_PREFIX + "num", StringComparison.OrdinalIgnoreCase)) 97 | // { 98 | // 99 | // } 100 | 101 | 102 | // You can also call other configurators inside configurators to apply their changes and then do minor tweaks afterwards. 103 | // The way to do that is (without the <> ofc): 104 | // 105 | // blockLogic.Session..Invoke(dummyName, light, blockLogic); 106 | // 107 | // NOTE: don't call the same configurator inside itself or you'll freeze/crash the game. 108 | 109 | 110 | // Properties that are automatically computed. 111 | // Do NOT set these unless you know what you're doing. 112 | //light.ParentID 113 | //light.Position 114 | //light.ReflectorDirection 115 | //light.ReflectorUp 116 | }; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/AttachedLights/SimpleLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.ModAPI; 3 | using VRage.Game; 4 | using VRage.Utils; 5 | 6 | namespace Digi.AttachedLights 7 | { 8 | public static class SimpleLog 9 | { 10 | public static void Error(object caller, Exception e) 11 | { 12 | MyLog.Default.WriteLineAndConsole($"ERROR {caller.GetType().FullName}: {e.ToString()}"); 13 | MyLog.Default.Flush(); 14 | 15 | if(MyAPIGateway.Session?.Player != null) 16 | MyAPIGateway.Utilities.ShowNotification($"[ERROR: {caller.GetType().FullName}: {e.Message}] | Send SpaceEngineers.Log to mod author", 10000, MyFontEnum.Red); 17 | } 18 | 19 | public static void Error(object caller, string message) 20 | { 21 | MyLog.Default.WriteLineAndConsole($"ERROR {caller.GetType().FullName}: {message}"); 22 | MyLog.Default.Flush(); 23 | 24 | if(MyAPIGateway.Session?.Player != null) 25 | MyAPIGateway.Utilities.ShowNotification($"[ERROR: {caller.GetType().FullName}: {message}] | Send SpaceEngineers.Log to mod author", 10000, MyFontEnum.Red); 26 | } 27 | 28 | public static void Info(object caller, string message, bool notify = false, int notifyTime = 5000) 29 | { 30 | MyLog.Default.WriteLineAndConsole($"WARNING {caller.GetType().FullName}: {message}"); 31 | MyLog.Default.Flush(); 32 | 33 | if(notify) 34 | MyAPIGateway.Utilities?.ShowNotification($"[WARNING {caller.GetType().FullName}: {message}]", notifyTime, MyFontEnum.Green); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/AttachedLights/Usage.txt: -------------------------------------------------------------------------------- 1 | Open Setup.cs and follow its indications. 2 | 3 | To update, you only need to update the other files without Setup.cs. -------------------------------------------------------------------------------- /Data/Scripts/Examples/AttachedLights/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Sandbox.Definitions; 4 | using Sandbox.ModAPI; 5 | using VRage.Game; 6 | 7 | namespace Digi.AttachedLights 8 | { 9 | public static class Utils 10 | { 11 | /// 12 | /// Gets the flare definition for getting SubGlares into lights. 13 | /// NOTE: The subglare type is prohibited so I can't return that directly, which is why I'm returning the definition. 14 | /// 15 | public static MyFlareDefinition GetFlareDefinition(string flareSubtypeId) 16 | { 17 | if(string.IsNullOrEmpty(flareSubtypeId)) 18 | throw new ArgumentException("flareSubtypeId must not be null or empty!"); 19 | 20 | var flareDefId = new MyDefinitionId(typeof(MyObjectBuilder_FlareDefinition), flareSubtypeId); 21 | var flareDef = MyDefinitionManager.Static.GetDefinition(flareDefId) as MyFlareDefinition; 22 | 23 | if(flareDef == null) 24 | throw new Exception($"Couldn't find flare subtype {flareSubtypeId}"); 25 | 26 | return flareDef; 27 | } 28 | 29 | /// 30 | /// Converts a relative mod path into a full path for local machine. 31 | /// NOTE: Do not start with a slash! 32 | /// 33 | public static string GetModTextureFullPath(string relativeTexturePath) 34 | { 35 | return Path.Combine(AttachedLightsSession.Instance.ModContext.ModPath, relativeTexturePath); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/BasicExample_GameLogicAndSession/GameLogic.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Common.ObjectBuilders; 2 | using VRage.Game; 3 | using VRage.Game.Components; 4 | using VRage.Game.ModAPI; 5 | using VRage.Game.ObjectBuilders.ComponentSystem; 6 | using VRage.ModAPI; 7 | using VRage.ObjectBuilders; 8 | 9 | namespace Digi.Examples 10 | { 11 | // This object gets created and given to the entity type you specify in the attribute, optionally the subtype aswell. 12 | 13 | 14 | // It is not entirely reliable for some entity types, for example: 15 | // - for grids it can not attach at all for MP clients; 16 | // - characters it can sometimes not get added; 17 | // - CTC, solar panels and oxygen farms overwrite their gamelogic comp so it breaks any mods trying to add to them; 18 | // - and probably more... 19 | // Workaround for these is to use a session comp to track the entity additions&removals, storing them in a list/dictionary then update them yourself. 20 | 21 | 22 | // The MyEntityComponentDescriptor parameters: 23 | // 24 | // 1. The typeof(MyObjectBuilder_BatteryBlock) represents the BatteryBlock from the SBC. 25 | // Never use the OBs that end with "Definition" as those are not entities. 26 | // 27 | // 2. Entity-controlled updates, always use false. For more info: https://forum.keenswh.com/threads/modapi-changes-jan-26.7392280/ 28 | // 29 | // 3+. Subtype strings, you can add as many or few as you want. 30 | // You can also remove them entirely if you want it to attach to all entities of that type regardless of subtype, like so: 31 | // [MyEntityComponentDescriptor(typeof(MyObjectBuilder_BatteryBlock), false)] 32 | 33 | 34 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_BatteryBlock), false, "SubTypeIdHere", "more if needed...")] 35 | public class Example_GameLogic : MyGameLogicComponent 36 | { 37 | private IMyCubeBlock block; // storing the entity as a block reference to avoid re-casting it every time it's needed, this is the lowest type a block entity can be. 38 | 39 | 40 | // NOTE: All methods are optional, I'm just presenting the options and you can remove any you don't actually need. 41 | 42 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 43 | { 44 | // the base methods are usually empty, except for OnAddedToContainer()'s, which has some sync stuff making it required to be called. 45 | base.Init(objectBuilder); 46 | 47 | // this method is called async! always do stuff in the first update instead. 48 | // unless you're sure it must be in this one (like initializing resource sink/source components would need to be here). 49 | 50 | // the objectBuilder arg is sometimes the serialized version of the entity. 51 | // it works for hand tools for example but not for blocks (probably because MyObjectBuilder_CubeBlock does not extend MyObjectBuilder_EntityBase). 52 | 53 | 54 | block = (IMyCubeBlock)Entity; 55 | 56 | 57 | // makes UpdateOnceBeforeFrame() execute. 58 | // this is a special flag that gets self-removed after the method is called. 59 | // it can be used multiple times but mind that there is overhead to setting this so avoid using it for continuous updates. 60 | NeedsUpdate |= MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 61 | } 62 | 63 | public override void UpdateOnceBeforeFrame() 64 | { 65 | base.UpdateOnceBeforeFrame(); 66 | 67 | if(block?.CubeGrid?.Physics == null) // ignore projected and other non-physical grids 68 | return; 69 | 70 | 71 | // do stuff... 72 | // you can access things from session via Example_Session.Instance.[...] 73 | 74 | 75 | // in other places (session, terminal control callbacks, TSS, etc) where you have an entity and you want to get this gamelogic, you can use: 76 | // ent.GameLogic?.GetAs() 77 | // which will simply return null if it's not found. 78 | 79 | 80 | // allow UpdateAfterSimulation() and UpdateAfterSimulation100() to execute, remove if not needed 81 | NeedsUpdate |= MyEntityUpdateEnum.EACH_FRAME; 82 | NeedsUpdate |= MyEntityUpdateEnum.EACH_100TH_FRAME; 83 | } 84 | 85 | public override void MarkForClose() 86 | { 87 | base.MarkForClose(); 88 | 89 | // called when entity is about to be removed for whatever reason (block destroyed, entity deleted, ship despawn because of sync range, etc) 90 | // override Close() also works but it's a tiny bit later 91 | } 92 | 93 | public override void UpdateAfterSimulation() 94 | { 95 | base.UpdateAfterSimulation(); 96 | 97 | // this and UpdateBeforeSimulation() require NeedsUpdate to contain MyEntityUpdateEnum.EACH_FRAME. 98 | // gets executed 60 times a second after physics simulation, unless game is paused. 99 | } 100 | 101 | public override void UpdateAfterSimulation100() 102 | { 103 | base.UpdateAfterSimulation100(); 104 | 105 | // this and UpdateBeforeSimulation100() require NeedsUpdate to contain EACH_100TH_FRAME. 106 | // executed approximately every 100 ticks (~1.66s), unless game is paused. 107 | // why approximately? Explained at the "Important information" in: https://forum.keenswh.com/threads/pb-scripting-guide-how-to-use-self-updating.7398267/ 108 | 109 | // there's also 10-tick variants, UpdateBeforeSimulation10() and UpdateAfterSimulation10() 110 | // which require NeedsUpdate to contain EACH_10TH_FRAME 111 | } 112 | 113 | 114 | // less commonly used methods: 115 | 116 | public override bool IsSerialized() 117 | { 118 | // executed when the entity gets serialized (saved, blueprinted, streamed, etc) and asks all 119 | // its components whether to be serialized too or not (calling GetObjectBuilder()) 120 | 121 | // this can be used for serializing to Storage dictionary for example, 122 | // and for reliability I recommend that Storage has at least one key in it before this runs (by adding yours in first update). 123 | 124 | // you cannot add custom OBs to the game so this should always return the base (which currently is always false). 125 | return base.IsSerialized(); 126 | } 127 | 128 | public override void UpdatingStopped() 129 | { 130 | base.UpdatingStopped(); 131 | 132 | // only called when game is paused. 133 | } 134 | 135 | // WARNING: OnAddedToScene() and OnRemovedFromScene() never trigger if the block has more than one gamelogic comp. 136 | // I advise not using these to avoid surprises down the line. 137 | // Reason is Entity.GameLogic turns into a MyCompositeGameLogicComponent which holds an inner list of the actual gamelogic components, 138 | // but it does not override those 2 methods to pass their call along to the held components. 139 | // 140 | // Also advised to not use OnAddedToContainer() and OnBeforeRemovedFromContainer() 141 | } 142 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/BasicExample_GameLogicAndSession/Session.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Definitions; 3 | using Sandbox.Game; 4 | using Sandbox.Game.Entities; 5 | using Sandbox.ModAPI; 6 | using VRage.Game; 7 | using VRage.Game.Components; 8 | using VRage.Utils; 9 | using BlendTypeEnum = VRageRender.MyBillboard.BlendTypeEnum; // required for MyTransparentGeometry/MySimpleObjectDraw to be able to set blend type. 10 | 11 | namespace Digi.Examples 12 | { 13 | // This object is always present, from the world load to world unload. 14 | // NOTE: all clients and server run mod scripts, keep that in mind. 15 | // NOTE: this and gamelogic comp's update methods run on the main game thread, don't do too much in a tick or you'll lower sim speed. 16 | // NOTE: also mind allocations, avoid realtime allocations, re-use collections/ref-objects (except value types like structs, integers, etc). 17 | // 18 | // The MyUpdateOrder arg determines what update overrides are actually called. 19 | // Remove any method that you don't need, none of them are required, they're only there to show what you can use. 20 | // Also remove all comments you've read to avoid the overload of comments that is this file. 21 | [MySessionComponentDescriptor(MyUpdateOrder.BeforeSimulation | MyUpdateOrder.AfterSimulation)] 22 | public class Example_Session : MySessionComponentBase 23 | { 24 | public static Example_Session Instance; // the only way to access session comp from other classes and the only accepted static field. 25 | 26 | public override void LoadData() 27 | { 28 | // amogst the earliest execution points, but not everything is available at this point. 29 | 30 | // These can be used anywhere, not just in this method/class: 31 | // MyAPIGateway. - main entry point for the API 32 | // MyDefinitionManager.Static. - reading/editing definitions 33 | // MyGamePruningStructure. - fast way of finding entities in an area 34 | // MyTransparentGeometry. and MySimpleObjectDraw. - to draw sprites (from TransparentMaterials.sbc) in world (they usually live a single tick) 35 | // MyVisualScriptLogicProvider. - mainly designed for VST but has its uses, use as a last resort. 36 | // System.Diagnostics.Stopwatch - for measuring code execution time. 37 | // ...and many more things, ask in #programming-modding in keen's discord for what you want to do to be pointed at the available things to use. 38 | 39 | Instance = this; 40 | } 41 | 42 | public override void BeforeStart() 43 | { 44 | // executed before the world starts updating 45 | } 46 | 47 | protected override void UnloadData() 48 | { 49 | // executed when world is exited to unregister events and stuff 50 | 51 | Instance = null; // important for avoiding this object to remain allocated in memory 52 | } 53 | 54 | public override void HandleInput() 55 | { 56 | // gets called 60 times a second before all other update methods, regardless of framerate, game pause or MyUpdateOrder. 57 | } 58 | 59 | public override void UpdateBeforeSimulation() 60 | { 61 | // executed every tick, 60 times a second, before physics simulation and only if game is not paused. 62 | } 63 | 64 | public override void Simulate() 65 | { 66 | // executed every tick, 60 times a second, during physics simulation and only if game is not paused. 67 | // NOTE in this example this won't actually be called because of the lack of MyUpdateOrder.Simulation argument in MySessionComponentDescriptor 68 | } 69 | 70 | public override void UpdateAfterSimulation() 71 | { 72 | // executed every tick, 60 times a second, after physics simulation and only if game is not paused. 73 | 74 | try // example try-catch for catching errors and notifying player, use only for non-critical code! 75 | { 76 | // ... 77 | } 78 | catch(Exception e) // NOTE: never use try-catch for code flow or to ignore errors! catching has a noticeable performance impact. 79 | { 80 | MyLog.Default.WriteLineAndConsole($"{e.Message}\n{e.StackTrace}"); 81 | 82 | if(MyAPIGateway.Session?.Player != null) 83 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {GetType().FullName}: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 84 | } 85 | } 86 | 87 | public override void Draw() 88 | { 89 | // gets called 60 times a second after all other update methods, regardless of framerate, game pause or MyUpdateOrder. 90 | // NOTE: this is the only place where the camera matrix (MyAPIGateway.Session.Camera.WorldMatrix) is accurate, everywhere else it's 1 frame behind. 91 | } 92 | 93 | public override void SaveData() 94 | { 95 | // executed AFTER world was saved 96 | } 97 | 98 | public override MyObjectBuilder_SessionComponent GetObjectBuilder() 99 | { 100 | // executed during world save, most likely before entities. 101 | 102 | return base.GetObjectBuilder(); // leave as-is. 103 | } 104 | 105 | public override void UpdatingStopped() 106 | { 107 | // executed when game is paused 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Debug_ShowShotPositions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Sandbox.ModAPI; 3 | using VRage.Game; 4 | using VRage.Game.Components; 5 | using VRage.Utils; 6 | using VRageMath; 7 | using VRageRender; 8 | 9 | namespace Digi.Examples 10 | { 11 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 12 | public class Debug_ShowShotPositions : MySessionComponentBase 13 | { 14 | List DrawShots = new List(); 15 | 16 | struct DrawShot 17 | { 18 | public readonly Vector3D Position; 19 | public readonly Vector3D DirectionAndDistance; 20 | public readonly int ExpiresAt; 21 | 22 | public DrawShot(Vector3D position, Vector3D directionAndDistance) 23 | { 24 | Position = position; 25 | DirectionAndDistance = directionAndDistance; 26 | ExpiresAt = MyAPIGateway.Session.GameplayFrameCounter + (60 * 10); // 10 seconds 27 | } 28 | } 29 | 30 | MyStringId MaterialDot = MyStringId.GetOrCompute("WhiteDot"); 31 | MyStringId MaterialSquare = MyStringId.GetOrCompute("Square"); 32 | 33 | public override void BeforeStart() 34 | { 35 | MyAPIGateway.Projectiles.OnProjectileAdded += ProjectileAdded; 36 | MyAPIGateway.Missiles.OnMissileAdded += MissileAdded; 37 | } 38 | 39 | protected override void UnloadData() 40 | { 41 | if(MyAPIGateway.Projectiles != null) 42 | MyAPIGateway.Projectiles.OnProjectileAdded -= ProjectileAdded; 43 | 44 | if(MyAPIGateway.Missiles != null) 45 | MyAPIGateway.Missiles.OnMissileAdded -= MissileAdded; 46 | } 47 | 48 | void ProjectileAdded(ref MyProjectileInfo projectile, int index) 49 | { 50 | AddLine(projectile.Position, projectile.Velocity); 51 | } 52 | 53 | void MissileAdded(IMyMissile missile) 54 | { 55 | AddLine(missile.GetPosition(), missile.LinearVelocity); 56 | } 57 | 58 | void AddLine(Vector3D position, Vector3D directionAndDistance) 59 | { 60 | if(Vector3D.IsZero(directionAndDistance, 0.001)) 61 | directionAndDistance = Vector3D.Forward * 10000000; 62 | 63 | DrawShots.Add(new DrawShot(position, directionAndDistance)); 64 | } 65 | 66 | public override void Draw() 67 | { 68 | int tick = MyAPIGateway.Session.GameplayFrameCounter; 69 | 70 | for(int i = DrawShots.Count - 1; i >= 0; i--) 71 | { 72 | DrawShot shotInfo = DrawShots[i]; 73 | 74 | if(tick >= shotInfo.ExpiresAt) 75 | { 76 | DrawShots.RemoveAtFast(i); 77 | continue; 78 | } 79 | 80 | MyTransparentGeometry.AddPointBillboard(MaterialDot, Color.Red, shotInfo.Position, radius: 0.25f, angle: 0, 81 | blendType: MyBillboard.BlendTypeEnum.AdditiveTop); 82 | 83 | MyTransparentGeometry.AddLineBillboard(MaterialSquare, Color.Red, shotInfo.Position, shotInfo.DirectionAndDistance, length: 1f, thickness: 0.1f, 84 | blendType: MyBillboard.BlendTypeEnum.AdditiveTop); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/ExampleWorkaround_GridLogicHook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Sandbox.ModAPI; 4 | using VRage.Game; 5 | using VRage.Game.Components; 6 | using VRage.Game.ModAPI; 7 | using VRage.ModAPI; 8 | using VRage.Utils; 9 | 10 | namespace Digi.Examples 11 | { 12 | // This shows how to find all grids and execute code on them as if it was a gamelogic component. 13 | // This is needed because attaching gamelogic to grids does not work reliably, like not working at all for clients in MP. 14 | [MySessionComponentDescriptor(MyUpdateOrder.BeforeSimulation)] 15 | public class ExampleWorkaround_GridLogicSession : MySessionComponentBase 16 | { 17 | private readonly Dictionary grids = new Dictionary(); 18 | 19 | public override void LoadData() 20 | { 21 | MyAPIGateway.Entities.OnEntityAdd += EntityAdded; 22 | } 23 | 24 | protected override void UnloadData() 25 | { 26 | MyAPIGateway.Entities.OnEntityAdd -= EntityAdded; 27 | 28 | grids.Clear(); 29 | } 30 | 31 | private void EntityAdded(IMyEntity ent) 32 | { 33 | var grid = ent as IMyCubeGrid; 34 | 35 | if(grid != null) 36 | { 37 | grids.Add(grid.EntityId, grid); 38 | grid.OnMarkForClose += GridMarkedForClose; 39 | } 40 | } 41 | 42 | private void GridMarkedForClose(IMyEntity ent) 43 | { 44 | grids.Remove(ent.EntityId); 45 | } 46 | 47 | public override void UpdateBeforeSimulation() 48 | { 49 | try 50 | { 51 | foreach(var grid in grids.Values) 52 | { 53 | if(grid.MarkedForClose) 54 | continue; 55 | 56 | // do your thing 57 | } 58 | } 59 | catch(Exception e) 60 | { 61 | MyLog.Default.WriteLineAndConsole($"{e.Message}\n{e.StackTrace}"); 62 | 63 | if(MyAPIGateway.Session?.Player != null) 64 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {GetType().FullName}: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_AppendToCharacterStatComp.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Definitions; 2 | using Sandbox.Game.EntityComponents; 3 | using VRage.Game; 4 | using VRage.Game.Components; 5 | using VRage.Game.ObjectBuilders; 6 | using VRage.Game.ObjectBuilders.ComponentSystem; 7 | 8 | namespace Digi.Examples 9 | { 10 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 11 | public class Example_AppendToCharacterStatComp : MySessionComponentBase 12 | { 13 | public override void LoadData() 14 | { 15 | AddStatToComp("Default_Astronaut", "whateverstat"); 16 | } 17 | 18 | void AddStatToComp(string compSubtype, string statSubtype) 19 | { 20 | MyEntityStatComponentDefinition def = MyDefinitionManager.Static.GetEntityComponentDefinition(new MyDefinitionId(typeof(MyObjectBuilder_CharacterStatComponent), compSubtype)) as MyEntityStatComponentDefinition; 21 | 22 | if(def != null) 23 | { 24 | def.Stats.Add(new MyDefinitionId(typeof(MyObjectBuilder_EntityStat), statSubtype)); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_AssemblerForceMode.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Common.ObjectBuilders; 2 | using Sandbox.ModAPI; 3 | using VRage.Game.Components; 4 | using VRage.ModAPI; 5 | using VRage.ObjectBuilders; 6 | 7 | // avoid including ingame namespaces because they cause ambiguity errors, instead, do aliases like this: 8 | using MyAssemblerMode = Sandbox.ModAPI.Ingame.MyAssemblerMode; 9 | 10 | namespace Digi.Examples 11 | { 12 | // Edit the block subtypes to match your custom block(s). 13 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_Assembler), false, "YourSubtypeHere", "More if needed", "etc...")] 14 | public class Example_AssemblerForceMode : MyGameLogicComponent 15 | { 16 | const MyAssemblerMode ForceModeTo = MyAssemblerMode.Assembly; 17 | 18 | IMyAssembler Assembler; 19 | 20 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 21 | { 22 | if(MyAPIGateway.Session.IsServer) 23 | { 24 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 25 | } 26 | } 27 | 28 | public override void UpdateOnceBeforeFrame() 29 | { 30 | Assembler = Entity as IMyAssembler; 31 | 32 | if(Assembler?.CubeGrid?.Physics == null) 33 | return; // ignore non-assemblers, physicsless grids and whatever other cases would cause any of those things to be null 34 | 35 | NeedsUpdate = MyEntityUpdateEnum.EACH_FRAME; 36 | } 37 | 38 | public override void UpdateBeforeSimulation() 39 | { 40 | if(Assembler.Mode != ForceModeTo) 41 | { 42 | Assembler.Mode = ForceModeTo; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_AsyncDrawHook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.ModAPI; 3 | using VRage.Game; 4 | using VRage.Game.Components; 5 | using VRage.Game.ModAPI; 6 | 7 | namespace Digi.Examples 8 | { 9 | // Keen's session components have their Draw() method called in a thread to be parallel to other things. 10 | // (See MyGuiScreenGamePlay.Draw(), the very first Parallel.Start() will iterate their session Draw() calls) 11 | // 12 | // Mods' are forced to be sync because that would break things for existing mods, but they also didn't offer it as an option... 13 | // 14 | // This here is a very hacky way of getting a session component's Draw() to be async like the Keen ones, if you need such a thing. 15 | // Use with caution! 16 | // 17 | // NOTE: This whole thing relies on Assembly.GetTypes() to give these 2 objects in alphanumerical order, otherwise this can fail. 18 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 19 | public class SessionTest1 : MySessionComponentBase 20 | { 21 | public static SessionTest1 Instance; 22 | public IMyModContext OriginalContext; 23 | 24 | // Each session comp is instanced then their .ModContext is assigned which means any changes here would be lost. 25 | // However if you can get a second object right after this one (which is what SessionTest2 is intended for), 26 | // then you can change ModContext in the very short window available (at MySession.RegisterComponentsFromAssemblies's end). 27 | public SessionTest1() 28 | { 29 | Instance = this; 30 | } 31 | 32 | public override void Draw() 33 | { 34 | if(!MyParticlesManager.Paused) // doing notifications when game is paused will glitch/break them 35 | MyAPIGateway.Utilities.ShowNotification($"{GetType().Name} :: threadId={Environment.CurrentManagedThreadId}", 16); 36 | } 37 | 38 | public override void LoadData() 39 | { 40 | Instance = null; 41 | ModContext = OriginalContext; // play nice and set it back ASAP 42 | } 43 | } 44 | 45 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 46 | public class SessionTest2 : MySessionComponentBase 47 | { 48 | public SessionTest2() 49 | { 50 | SessionTest1 st1 = SessionTest1.Instance; 51 | if(st1 != null) 52 | { 53 | st1.OriginalContext = st1.ModContext; 54 | st1.ModContext = null; 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_CustomUseObject.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.ModAPI; 2 | using VRage.Game.Entity.UseObject; 3 | using VRage.Game.ModAPI; 4 | using VRage.ModAPI; 5 | using VRage.Utils; 6 | 7 | namespace Digi.Examples 8 | { 9 | // UseObjects are interactive areas on entities (primarily used on blocks). 10 | 11 | // First you need an empty on the model. 12 | 13 | // You can OVERRIDE game's useobjects too, for example entering "terminal", but use it with caution because it will affect ALL blocks. 14 | // This can also be used for some quick prototyping without a custom model. 15 | 16 | // For your own model you need empties prefixed with detector_ and the name of the useobject is up until the next _ (if any). 17 | // Examples: detector_YourUseObjectName_IgnoredStuff -> [MyUseObject("YourUseObjectName")] 18 | // detector_YourUseObjectName_2 -> [MyUseObject("YourUseObjectName")] 19 | // detector_SomethingElse -> [MyUseObject("SomethingElse")] 20 | // The names are not case-sensitive. 21 | // Recommended to use a very unique name, like your mod name. 22 | // For a different explanation see the useobjects guide: https://steamcommunity.com/sharedfiles/filedetails/?id=2560048279 23 | [MyUseObject("YourUseObjectName")] 24 | public class Example_CustomUseObject : MyUseObjectBase 25 | { 26 | // Probably determines what actions to show as hints? Experiment! 27 | public override UseActionEnum SupportedActions => UseActionEnum.Manipulate 28 | | UseActionEnum.Close 29 | | UseActionEnum.BuildPlanner 30 | | UseActionEnum.OpenInventory 31 | | UseActionEnum.OpenTerminal 32 | | UseActionEnum.PickUp 33 | | UseActionEnum.UseFinished; // gets called when releasing manipulate 34 | 35 | // What action gets sent to Use() when interacted with PrimaryAttack or Use binds. 36 | public override UseActionEnum PrimaryAction => UseActionEnum.Manipulate; 37 | 38 | // What action gets sent to Use() when interacted with SecondaryAttack or Inventory/Terminal binds. 39 | public override UseActionEnum SecondaryAction => UseActionEnum.OpenTerminal; 40 | 41 | public Example_CustomUseObject(IMyEntity owner, string dummyName, IMyModelDummy dummyData, uint shapeKey) : base(owner, dummyData) 42 | { 43 | // This class gets instanced per entity that has this detector useobject on it. 44 | // NOTE: this exact constructor signature is required, will throw errors mid-loading (and prevent world from loading) otherwise. 45 | } 46 | 47 | public override MyActionDescription GetActionInfo(UseActionEnum actionEnum) 48 | { 49 | // Called when aiming at this useobject to get what to print on screen 50 | 51 | MyAPIGateway.Utilities.ShowNotification($"GetActionInfo() action={actionEnum}", 1000); 52 | 53 | switch(actionEnum) 54 | { 55 | default: 56 | return default(MyActionDescription); 57 | 58 | case UseActionEnum.Manipulate: 59 | return new MyActionDescription() 60 | { 61 | Text = MyStringId.GetOrCompute("You could do something with this with your kb/m..."), 62 | IsTextControlHint = true, 63 | JoystickText = MyStringId.GetOrCompute("You could do something with this with your gamepad..."), 64 | ShowForGamepad = true 65 | }; 66 | 67 | // ...more cases if needed 68 | } 69 | } 70 | 71 | public override void Use(UseActionEnum actionEnum, IMyEntity user) 72 | { 73 | // Called when a supported input is used while aiming at this useobject 74 | 75 | MyAPIGateway.Utilities.ShowNotification($"Use() action={actionEnum}; user={user}"); 76 | 77 | switch(actionEnum) 78 | { 79 | case UseActionEnum.Manipulate: 80 | MyAPIGateway.Utilities.ShowNotification("Oh you pressed it!"); 81 | break; 82 | 83 | // ...more cases if needed 84 | } 85 | } 86 | 87 | // there's a few more things you can optionally override, like OnSelectionLost() 88 | } 89 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_DetectGunBlockFire.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Common.ObjectBuilders; 3 | using Sandbox.Game.Entities; 4 | using Sandbox.Game.Weapons; 5 | using Sandbox.ModAPI; 6 | using VRage.Game; 7 | using VRage.Game.Components; 8 | using VRage.Game.ModAPI; 9 | using VRage.ModAPI; 10 | using VRage.ObjectBuilders; 11 | using VRage.Utils; 12 | 13 | namespace Digi.Examples 14 | { 15 | // Edit the block type and subtypes to match your custom block. 16 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_LargeMissileTurret), false, "YourSubtypeHere", "More if needed")] 17 | public class Example_DetectGunBlockFire : MyGameLogicComponent 18 | { 19 | private IMyFunctionalBlock block; 20 | private IMyGunObject gun; 21 | private long lastShotTime; 22 | 23 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 24 | { 25 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 26 | } 27 | 28 | public override void UpdateOnceBeforeFrame() 29 | { 30 | block = (IMyFunctionalBlock)Entity; 31 | 32 | if(block.CubeGrid?.Physics == null) 33 | return; 34 | 35 | gun = (IMyGunObject)Entity; 36 | lastShotTime = gun.GunBase.LastShootTime.Ticks; 37 | 38 | NeedsUpdate = MyEntityUpdateEnum.EACH_FRAME; 39 | } 40 | 41 | public override void UpdateBeforeSimulation() 42 | { 43 | try 44 | { 45 | if(!block.IsFunctional) 46 | return; 47 | 48 | var shotTime = gun.GunBase.LastShootTime.Ticks; 49 | 50 | if(shotTime > lastShotTime) 51 | { 52 | lastShotTime = shotTime; 53 | 54 | // do stuff 55 | MyAPIGateway.Utilities.ShowNotification($"{block.CustomName} has fired!", 1000); 56 | } 57 | } 58 | catch(Exception e) 59 | { 60 | AddToLog(e); 61 | } 62 | } 63 | 64 | private void AddToLog(Exception e) 65 | { 66 | MyLog.Default.WriteLineAndConsole($"{e.Message}\n{e.StackTrace}"); 67 | 68 | if(MyAPIGateway.Session?.Player != null) 69 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {GetType().FullName}: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_DrawingIn3D.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Game; 3 | using Sandbox.ModAPI; 4 | using VRage.Game; 5 | using VRage.Game.Components; 6 | using VRage.Utils; 7 | using VRageMath; 8 | using BlendTypeEnum = VRageRender.MyBillboard.BlendTypeEnum; // required for MyTransparentGeometry/MySimpleObjectDraw to be able to set blend type. 9 | 10 | namespace Digi.Examples 11 | { 12 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 13 | public class Example_DrawingIn3D : MySessionComponentBase 14 | { 15 | // material subtype from transparent material SBC. 16 | // go-to general purpose materials: Square, WhiteDot, WeaponLaser. 17 | private MyStringId Material = MyStringId.GetOrCompute("Square"); 18 | 19 | public override void Draw() 20 | { 21 | // remember that this method gets called even when game is paused 22 | // it's not necessary to draw here in particular, you can draw in any of the update methods but your inputs might be too old, for example if using camera matrix. 23 | 24 | LineExamples(); 25 | CylinderExample(); 26 | } 27 | 28 | void LineExamples() 29 | { 30 | // this matrix is updated at Draw() stage, in any other update methods it's 1 frame behind. 31 | MatrixD camMatrix = MyAPIGateway.Session.Camera.WorldMatrix; 32 | 33 | // Color can implicitly convert to Vector4 that 2nd param wants. 34 | // Color * float makes it transparent. 35 | // If you define it as Vector4 you can set values past 1 to apply more intensity, making it bloom (if post processing is enabled). 36 | Color color = Color.Red * 0.5f; 37 | 38 | Vector3D start = camMatrix.Translation + camMatrix.Forward * 3 + camMatrix.Down * 1f; 39 | Vector3D target = Vector3D.Zero; 40 | Vector3 direction = (target - start); // not normalized can work too if you give 1 to the line length 41 | float lineLength = 1f; 42 | float lineThickness = 0.05f; 43 | 44 | // how the billboard interacts with world/lighting. 45 | // Standard is affected by tonemapping and post processing 46 | // AdditiveBottom always gets rendered under objets. 47 | // AdditiveTop always gets rendered over objects. 48 | // SDR & LDR (they're the same value) ignore tonemapping. 49 | // PostPP ignores tonemapping and post processing. 50 | BlendTypeEnum blendType = BlendTypeEnum.Standard; 51 | 52 | // all billboard methods accessible right now only live one tick 53 | MyTransparentGeometry.AddLineBillboard(Material, color, start, direction, lineLength, lineThickness, blendType); 54 | 55 | // glowling line example (requires post processing to see the bloom) 56 | MyTransparentGeometry.AddLineBillboard(Material, Color.White.ToVector4() * 100, Vector3D.Zero + Vector3.Forward * 1, Vector3.Forward, 10f, 0.05f, BlendTypeEnum.Standard); 57 | 58 | // fake "glowing" line example (no post processing required) 59 | MyTransparentGeometry.AddLineBillboard(MyStringId.GetOrCompute("WeaponLaser"), Color.White.ToVector4(), Vector3D.Zero + Vector3.Backward * 1, Vector3.Backward, 10f, 0.3f, BlendTypeEnum.SDR); 60 | } 61 | 62 | void CylinderExample() 63 | { 64 | MatrixD matrix = MatrixD.CreateWorld(Vector3D.Zero, Vector3.Forward, Vector3.Up); 65 | Vector4 color = (Color.Lime * 0.75f).ToVector4(); 66 | 67 | float baseRadius = 0.25f; 68 | float topRadius = 2f; 69 | float height = 10f; 70 | 71 | // how many subdivisions it does, for round objects it's 360/wireDivRatio so it must be a number that can divide properly. 72 | // best to use 360/deg to input the degrees that each rotation step is done at. 73 | int wireDivRatio = 360 / 15; 74 | 75 | bool wireframe = true; // DrawTransparentCylinder() only has wireframe internally so this is a pointless param 76 | float wireframeThickness = 0.05f; 77 | 78 | MySimpleObjectDraw.DrawTransparentCylinder(ref matrix, baseRadius, topRadius, height, ref color, wireframe, wireDivRatio, wireframeThickness, Material); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_EditBlockPowerUsage.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Common.ObjectBuilders; 2 | using Sandbox.Game.EntityComponents; 3 | using Sandbox.ModAPI; 4 | using VRage.Game.Components; 5 | using VRage.Game.ModAPI; 6 | using VRage.ModAPI; 7 | using VRage.ObjectBuilders; 8 | 9 | namespace Digi.Examples 10 | { 11 | // MyObjectBuilder_OreDetector would be the block type, the suffix can be found in TypeId in the block definition. 12 | // No subtypes defined, it will attach to all subtypes of that type. 13 | // To define specific subtypes, see this format: 14 | // [MyEntityComponentDescriptor(typeof(MyObjectBuilder_OreDetector), false, "Subtype here", "More subtypes if needed", "etc")] 15 | // 16 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_OreDetector), false)] 17 | public class Example_OreDetector : MyGameLogicComponent 18 | { 19 | const float POWER_REQUIRED_MW = 10.0f; 20 | 21 | private IMyFunctionalBlock Block; 22 | 23 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 24 | { 25 | // WARNING: this cast will fail and prevent the block from spawning if the block does not have the on/off capability. 26 | // Cockpits/cryo/RC can use power but can't be turned off, for example. 27 | // If you do have such a block, replace IMyFunctionalBlock with IMyCubeBlock in both places and remove the Block.Enabled condition in the power method. 28 | Block = (IMyFunctionalBlock)Entity; 29 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 30 | } 31 | 32 | public override void UpdateOnceBeforeFrame() 33 | { 34 | var sink = Entity.Components.Get(); 35 | 36 | if(sink != null) 37 | { 38 | sink.SetRequiredInputFuncByType(MyResourceDistributorComponent.ElectricityId, ComputePowerRequired); 39 | sink.Update(); 40 | } 41 | } 42 | 43 | private float ComputePowerRequired() 44 | { 45 | if(!Block.Enabled || !Block.IsFunctional) 46 | return 0f; 47 | 48 | // You can of course add some more complicated logic here. 49 | // However you need to call sink.Update() whenever you think you need the power to update. 50 | // Updating sink will call sink.SetRequiredInputByType() for every resource type. 51 | // One way to keep it topped up at a reasonable rate is to use Update100. 52 | // The game will call Update() when it feels like it too so do some tests. 53 | 54 | return POWER_REQUIRED_MW; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_EditCharacterDef.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Sandbox.Definitions; 3 | using VRage.Game; 4 | using VRage.Game.Components; 5 | 6 | namespace Digi.Examples 7 | { 8 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 9 | public class Example_EditCharacterDef : MySessionComponentBase 10 | { 11 | private const float SetJetpackForce = 1000f; 12 | private readonly List CharacterSubtypeIDs = new List() 13 | { 14 | "Default_Astronaut", 15 | "Default_Astronaut_Female", 16 | }; 17 | 18 | // need to store original values and reset definitions on unload as you're basically editing the vanilla ones (but only in memory, not permanently). 19 | private readonly Dictionary OriginalForceMagnitude = new Dictionary(); 20 | 21 | public override void LoadData() 22 | { 23 | var charDefs = MyDefinitionManager.Static.Characters; 24 | 25 | foreach(var charDef in charDefs) 26 | { 27 | if(CharacterSubtypeIDs.Contains(charDef.Id.SubtypeName)) 28 | { 29 | charDef.Context = (MyModContext)ModContext; // mark it as edited by this mod, not really necessary but nice to inform. 30 | 31 | OriginalForceMagnitude[charDef.Id.SubtypeName] = charDef.Jetpack.ThrustProperties.ForceMagnitude; 32 | 33 | charDef.Jetpack.ThrustProperties.ForceMagnitude = SetJetpackForce; 34 | } 35 | } 36 | } 37 | 38 | protected override void UnloadData() 39 | { 40 | var charDefs = MyDefinitionManager.Static.Characters; 41 | 42 | foreach(var charDef in charDefs) 43 | { 44 | if(CharacterSubtypeIDs.Contains(charDef.Id.SubtypeName)) 45 | { 46 | charDef.Context = MyModContext.BaseGame; // reset to base game regardless, if it was from a mod then it gets reprocessed on next load anyway. 47 | 48 | charDef.Jetpack.ThrustProperties.ForceMagnitude = OriginalForceMagnitude[charDef.Id.SubtypeName]; 49 | } 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_GasOutput.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Common.ObjectBuilders; 2 | using Sandbox.Game.EntityComponents; 3 | using Sandbox.ModAPI; 4 | using VRage.Game.Components; 5 | using VRage.ModAPI; 6 | using VRage.ObjectBuilders; 7 | using VRage.Utils; 8 | 9 | namespace Digi.Examples 10 | { 11 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_Collector), false, "YourSubtypeHere", "More if needed")] 12 | public class Example_GasOutput : MyGameLogicComponent 13 | { 14 | IMyFunctionalBlock Block; 15 | 16 | MyResourceSourceComponent SourceComp; 17 | 18 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 19 | { 20 | Block = (IMyFunctionalBlock)Entity; 21 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 22 | 23 | SourceComp = new MyResourceSourceComponent(); 24 | 25 | // ResourceDistributionGroups.sbc at the bottom for sources 26 | SourceComp.Init(MyStringHash.GetOrCompute("Reactors"), new MyResourceSourceInfo() 27 | { 28 | DefinedOutput = 0, 29 | ProductionToCapacityMultiplier = 1f, 30 | ResourceTypeId = MyResourceDistributorComponent.HydrogenId, 31 | IsInfiniteCapacity = true, // ignore the capacity aspect 32 | }); 33 | 34 | Block.Components.Add(SourceComp); 35 | } 36 | 37 | public override void UpdateOnceBeforeFrame() 38 | { 39 | if(Block?.CubeGrid?.Physics == null) 40 | return; // ignore ghost grids 41 | 42 | NeedsUpdate = MyEntityUpdateEnum.EACH_100TH_FRAME; 43 | } 44 | 45 | public override void UpdateAfterSimulation100() 46 | { 47 | float output = 0f; 48 | 49 | if(Block.IsWorking && Block.Enabled) 50 | { 51 | output = 10f; // m3/s 52 | } 53 | 54 | SourceComp.Enabled = (output > 0); 55 | SourceComp.SetMaxOutputByType(MyResourceDistributorComponent.HydrogenId, output); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_HighlightingEntities.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Game; 2 | using Sandbox.Game.EntityComponents; 3 | using Sandbox.ModAPI; 4 | using VRage.Game.Components; 5 | using VRage.Game.ModAPI; 6 | using VRage.Input; 7 | using VRage.ModAPI; 8 | using VRageMath; 9 | 10 | namespace Digi.Examples 11 | { 12 | [MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)] 13 | public class Example_HighlightingEntities : MySessionComponentBase 14 | { 15 | IMyEntity CurrentlyHighlighted; 16 | 17 | public override void UpdateAfterSimulation() 18 | { 19 | // The highlight stuff are in MyVisualScriptLogicProvider (VSLP) 20 | // There's many highlight methods but they're designed for serverside scenario stuff so they're synchronized. 21 | // It is recommended to use the Local one unless you know you absolutely need the synchronized one. 22 | // MyVisualScriptLogicProvider.SetHighlightLocal() 23 | 24 | // All methods in VSLP (MyVisualScriptLogicProvider) require entity names as input, that one is simply Name prop from [I]MyEntity 25 | // which is automatically assigned with entityId.ToString(), but can be custom names in some cases too. 26 | 27 | // Thickness greatly affects how bright it gets, but you can also multiply the color to make it less bright aswell if you want to mix&match. 28 | // To remove highlight, set thickness to -1 or lower. 29 | // To disable pulsing, set pulseTimeInFrames to 0 or negative. 30 | // Leave the playerId as -1 if you use the local method. 31 | 32 | // Now to a practical example, pressing R while holding a welder/grinder and aiming at a block, will highlight it and will remember that. 33 | // There is unfortunately no way to get an entity's highlighted state. 34 | if(MyAPIGateway.Input.IsNewKeyPressed(MyKeys.R)) 35 | { 36 | if(CurrentlyHighlighted != null) 37 | { 38 | MyVisualScriptLogicProvider.SetHighlightLocal(CurrentlyHighlighted.Name, thickness: -1); 39 | 40 | MyAPIGateway.Utilities.ShowNotification("Highlight unset."); 41 | 42 | CurrentlyHighlighted = null; 43 | } 44 | else 45 | { 46 | IMyCharacter chr = MyAPIGateway.Session?.Player?.Character; 47 | IMySlimBlock aimed = chr?.EquippedTool?.Components?.Get()?.HitBlock as IMySlimBlock; 48 | if(aimed?.FatBlock != null) 49 | { 50 | CurrentlyHighlighted = aimed.FatBlock; // slimblocks aren't entities, so deformable armor won't be highlightable this way. 51 | // you can instead highlight the entire grid by giving the grid name. 52 | 53 | MyVisualScriptLogicProvider.SetHighlightLocal(CurrentlyHighlighted.Name, thickness: 2, pulseTimeInFrames: 6, color: Color.SkyBlue); 54 | 55 | MyAPIGateway.Utilities.ShowNotification("Highlight set. Press again to unset."); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_HudStatOverride.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.ModAPI; 3 | using VRage.Game; 4 | using VRage.ModAPI; 5 | using VRage.Utils; 6 | 7 | namespace Digi.Examples 8 | { 9 | // HudStat classes are used by the game to compute data to be written to the various HUD elements. 10 | // Implementing IMyHudStat would trigger the game to use your class for that purpose aswell, and the Id property determines the stat it overrides (or creates if it's unique). 11 | // You can make new Ids and use them in a HUD definition SBC to have custom behaviors on the HUD. 12 | // However, do note that currently the HUD definition is very unfriendly to multiple mods changing it, only one mod's edits will remain depending on mod order. 13 | // 14 | public class Example_HudStatOverride : IMyHudStat 15 | { 16 | public MyStringHash Id { get; private set; } = MyStringHash.GetOrCompute("controlled_mass"); // the stat's ID to override, this one is the ship's mass number 17 | public float MinValue => 0f; // these being used or not depend on the how the stat is used in the HUD definition. 18 | public float MaxValue => 1f; 19 | public float CurrentValue { get; private set; } 20 | public string GetValueString() => CurrentValue.ToString("0.00"); // NOTE: must never return null! 21 | 22 | public Example_HudStatOverride() 23 | { 24 | // initialization stuff 25 | } 26 | 27 | public void Update() // gets executed every tick (60/s) 28 | { 29 | try 30 | { 31 | // do stuff... 32 | 33 | CurrentValue = 5; 34 | } 35 | catch(Exception e) 36 | { 37 | MyLog.Default.WriteLineAndConsole($"{e.Message}\n{e.StackTrace}"); 38 | 39 | if(MyAPIGateway.Session?.Player != null) 40 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {GetType().FullName}: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_InputReading.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Sandbox.Game; 4 | using Sandbox.ModAPI; 5 | using VRage.Game; 6 | using VRage.Game.Components; 7 | using VRage.Input; 8 | using VRage.Utils; 9 | using VRageMath; 10 | 11 | namespace Digi.Examples 12 | { 13 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 14 | public class Example_InputReading : MySessionComponentBase 15 | { 16 | public override void HandleInput() 17 | { 18 | // you can read inputs in update methods too, or pretty much anywhere. 19 | // this method however only runs on players (not DS-side) and runs even if game is paused. 20 | 21 | 22 | if(MyParticlesManager.Paused) 23 | return; // stop here if game is paused, optional, depends on what you're doing. 24 | 25 | 26 | // the majority of input reading is in: MyAPIGateway.Input 27 | 28 | // dividing it up into methods for different levels/usecases of examples: 29 | 30 | // all input methods will read regardless of being in menus or having chat open. 31 | // these checks are the simplest way to ignore those menu & chat states. 32 | if(!MyAPIGateway.Gui.IsCursorVisible && !MyAPIGateway.Gui.ChatEntryVisible) 33 | { 34 | BasicExamples(); 35 | 36 | GamepadInclusiveExamples(); 37 | } 38 | } 39 | 40 | 41 | 42 | void BasicExamples() 43 | { 44 | // example of detecting when current player presses the USE bind on keyboard or mouse, does not react to gamepad binds. 45 | if(MyAPIGateway.Input.IsNewGameControlPressed(MyControlsSpace.USE)) 46 | { 47 | MyAPIGateway.Utilities.ShowNotification("You pressed USE [:o]"); 48 | } 49 | // also the "New" in the method names means it will only return true in the frame that it started to be pressed (or released). 50 | // useful for a single action instead of it repeating while player holds it (which they will even as they tap it, it is inevitable). 51 | 52 | if(MyAPIGateway.Input.IsGameControlPressed(MyControlsSpace.JUMP)) 53 | { 54 | MyAPIGateway.Utilities.ShowNotification("You're holding jump...", 17); 55 | } 56 | } 57 | 58 | 59 | // copied from MyControllerHelper because it is not whitelisted 60 | // because gamepad is limited in buttons it has to be split up into contexts, these are those. 61 | MyStringId CX_BASE = MyStringId.GetOrCompute("BASE"); 62 | MyStringId CX_GUI = MyStringId.GetOrCompute("GUI"); 63 | MyStringId CX_CHARACTER = MyStringId.GetOrCompute("CHARACTER"); 64 | MyStringId CX_SPACESHIP = MyStringId.GetOrCompute("SPACESHIP"); 65 | MyStringId CX_JETPACK = MyStringId.GetOrCompute("JETPACK"); 66 | 67 | void GamepadInclusiveExamples() 68 | { 69 | if(MyAPIGateway.Input.IsControl(CX_CHARACTER, MyControlsSpace.USE, MyControlStateType.NEW_PRESSED)) 70 | { 71 | MyAPIGateway.Utilities.ShowNotification("You pressed USE (+gamepad support)"); 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_MessageOnJoin.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.ModAPI; 2 | using VRage.Game.Components; 3 | 4 | namespace Digi.Examples 5 | { 6 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 7 | public class Example_MessageOnJoin : MySessionComponentBase 8 | { 9 | const string From = "Nigerian Prince"; 10 | const string Message = "Hello, I give you lots of money if you give me few money."; 11 | 12 | bool SeenMessage = false; 13 | 14 | public override void LoadData() 15 | { 16 | if(MyAPIGateway.Session.IsServer && MyAPIGateway.Utilities.IsDedicated) // DS side does not need this 17 | return; 18 | 19 | SetUpdateOrder(MyUpdateOrder.AfterSimulation); 20 | } 21 | 22 | public override void UpdateAfterSimulation() 23 | { 24 | if(!SeenMessage && MyAPIGateway.Session?.Player?.Character != null) 25 | { 26 | SeenMessage = true; 27 | MyAPIGateway.Utilities.ShowMessage(From, Message); 28 | 29 | // required delayed like this because it modifies the list that iterates components to trigger this update method, causing list modified exception. 30 | MyAPIGateway.Utilities.InvokeOnGameThread(() => SetUpdateOrder(MyUpdateOrder.NoUpdate)); 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ModifyBlockVariants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Sandbox.Definitions; 5 | using VRage.Game; 6 | using VRage.Game.Components; 7 | using VRage.ObjectBuilders; 8 | using VRage.Utils; 9 | 10 | namespace Digi.Experiments 11 | { 12 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 13 | public class Example_ModifyBlockVariants : MySessionComponentBase 14 | { 15 | // if you expect blocks or groups to be missing then set this to false to not report them in log. 16 | const bool ReportMissingDefinitions = true; 17 | 18 | const bool ReportInF11Menu = true; 19 | 20 | void SetupGroups() 21 | { 22 | // A few notes about groups: 23 | // - group has to exist with SBC, C# mods cannot add definitions in general. 24 | // - has to have at least one valid block or game will remove the group. 25 | // - blocks can only link to a single group, ideally don't use blocks that are already in a group. 26 | 27 | 28 | // usage example: 29 | 30 | using(var group = new GroupChange("CockpitGroup")) 31 | { 32 | group.AddBlock("Assembler", "LargeAssembler"); 33 | group.AddBlock("Assembler", "LargeAssemblerIndustrial"); 34 | // etc... 35 | 36 | group.RemoveBlock("Cockpit", "DBSmallBlockFighterCockpit"); 37 | group.RemoveBlock("Cockpit", "SmallBlockStandingCockpit"); 38 | group.RemoveBlock("Cockpit", "LargeBlockStandingCockpit"); 39 | // etc... 40 | } 41 | 42 | // then repeat the above chunk all groups you want to edit 43 | } 44 | 45 | 46 | 47 | 48 | static Example_ModifyBlockVariants Instance; 49 | List NewDefs = new List(32); 50 | List AppendBlocks = new List(32); 51 | HashSet RemoveBlocks = new HashSet(MyDefinitionId.Comparer); 52 | StringBuilder SB = new StringBuilder(1024); 53 | 54 | public override void LoadData() 55 | { 56 | try 57 | { 58 | Instance = this; 59 | SetupGroups(); 60 | } 61 | finally 62 | { 63 | AppendBlocks = null; 64 | RemoveBlocks = null; 65 | NewDefs = null; 66 | Instance = null; 67 | SB = null; 68 | } 69 | } 70 | 71 | struct GroupChange : IDisposable 72 | { 73 | readonly string VariantsId; 74 | 75 | public GroupChange(string variantsId) 76 | { 77 | VariantsId = variantsId; 78 | 79 | Instance.AppendBlocks?.Clear(); 80 | Instance.RemoveBlocks?.Clear(); 81 | } 82 | 83 | public void AddBlock(string typeName, string subtypeName) 84 | { 85 | MyObjectBuilderType type; 86 | if(ValidateType(typeName, out type)) 87 | Instance.AppendBlocks.Add(new MyDefinitionId(type, subtypeName)); 88 | } 89 | 90 | public void RemoveBlock(string typeName, string subtypeName) 91 | { 92 | MyObjectBuilderType type; 93 | if(ValidateType(typeName, out type)) 94 | Instance.RemoveBlocks.Add(new MyDefinitionId(type, subtypeName)); 95 | } 96 | 97 | static bool ValidateType(string typeName, out MyObjectBuilderType type) 98 | { 99 | if(!MyObjectBuilderType.TryParse(typeName, out type)) 100 | { 101 | // not ignoring this one because mods cannot add block types. 102 | LogError($"Type '{typeName}' does not exist, you must use a block type that exists in the game."); 103 | return false; 104 | } 105 | 106 | return true; 107 | } 108 | 109 | public void Dispose() 110 | { 111 | if(Instance.RemoveBlocks == null || Instance.AppendBlocks == null || Instance.NewDefs == null) 112 | { 113 | LogError($"Cannot modify `{VariantsId}`, script is already finished setup!"); 114 | return; 115 | } 116 | 117 | MyBlockVariantGroup group; 118 | if(!MyDefinitionManager.Static.GetBlockVariantGroupDefinitions().TryGetValue(VariantsId, out group)) 119 | { 120 | if(ReportMissingDefinitions) 121 | LogError($"Cannot find BlockVariantsGroup subtypeId: `{VariantsId}`"); 122 | 123 | return; 124 | } 125 | 126 | Instance.NewDefs.Clear(); 127 | 128 | foreach(MyCubeBlockDefinition blockDef in group.Blocks) 129 | { 130 | if(Instance.RemoveBlocks.Contains(blockDef.Id)) 131 | { 132 | Instance.RemoveBlocks.Remove(blockDef.Id); 133 | 134 | // detach block from group properly 135 | blockDef.BlockStages = null; 136 | blockDef.BlockVariantsGroup = null; 137 | blockDef.GuiVisible = true; // this was assigned by the group so we have to reset it 138 | 139 | continue; 140 | } 141 | 142 | Instance.NewDefs.Add(blockDef); 143 | } 144 | 145 | if(Instance.RemoveBlocks.Count > 0) 146 | { 147 | foreach(MyDefinitionId blockId in Instance.RemoveBlocks) 148 | { 149 | if(ReportMissingDefinitions) 150 | LogError($"Cannot find block id to remove: `{blockId}`"); 151 | } 152 | } 153 | 154 | foreach(MyDefinitionId blockId in Instance.AppendBlocks) 155 | { 156 | MyCubeBlockDefinition blockDef = MyDefinitionManager.Static.GetCubeBlockDefinition(blockId); 157 | if(blockDef == null) 158 | { 159 | if(ReportMissingDefinitions) 160 | LogError($"Cannot find block id to add: `{blockId}`"); 161 | 162 | continue; 163 | } 164 | 165 | Instance.NewDefs.Add(blockDef); 166 | } 167 | 168 | group.Context = (MyModContext)Instance.ModContext; // mark it modified by this mod. 169 | 170 | group.Blocks = Instance.NewDefs.ToArray(); 171 | 172 | group.DisplayNameEnum = null; 173 | group.Icons = null; 174 | group.Postprocess(); 175 | 176 | // not handled by Postprocess() but by MyDefinitionManager.InitBlockGroups() 177 | foreach(MyCubeBlockDefinition blockDef in group.Blocks) 178 | { 179 | blockDef.BlockVariantsGroup = group; 180 | } 181 | 182 | StringBuilder sb = Instance.SB.Clear().Append("Modified block variants group '").Append(group.Id.SubtypeName).Append("', final blocks:"); 183 | foreach(MyCubeBlockDefinition blockDef in group.Blocks) 184 | { 185 | sb.Append("\n ").Append(blockDef.Id.ToString()); 186 | 187 | if(blockDef == group.PrimaryGUIBlock) 188 | sb.Append(" (Primary GUI block)"); 189 | } 190 | LogInfo(sb.ToString()); 191 | 192 | Instance.AppendBlocks.Clear(); 193 | Instance.RemoveBlocks.Clear(); 194 | } 195 | } 196 | 197 | static void LogError(string message) 198 | { 199 | if(ReportInF11Menu) 200 | MyDefinitionErrors.Add((MyModContext)Instance.ModContext, message, TErrorSeverity.Error, writeToLog: true); 201 | else 202 | MyLog.Default.WriteLineAndConsole($"Mod '{Instance.ModContext.ModName}' Error: {message}"); 203 | } 204 | 205 | static void LogInfo(string message) 206 | { 207 | //if(ReportInF11Menu) 208 | // MyDefinitionErrors.Add((MyModContext)Instance.ModContext, message, TErrorSeverity.Notice, writeToLog: true); 209 | //else 210 | MyLog.Default.WriteLineAndConsole($"Mod '{Instance.ModContext.ModName}': {message}"); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ModifyContainerTypes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Sandbox.Definitions; 3 | using VRage.Game; 4 | using VRage.Game.Components; 5 | 6 | namespace Digi.Examples 7 | { 8 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 9 | public class Example_ModifyContainerTypes : MySessionComponentBase 10 | { 11 | public override void LoadData() 12 | { 13 | { 14 | // example of adding a new item to it 15 | AddItem("Component/Computer", frequency: 2.5, min: 10, max: 20); 16 | 17 | // example of removing one 18 | RemoveItem("Ore/Iron"); 19 | // and you can even add it back with changes 20 | AddItem("Ore/Iron", frequency: 1.0, min: 500, max: 600); 21 | 22 | // you can also change the CountMin and CountMax of the container type if you wish: 23 | //SetCountMin = 2; 24 | //SetCountMax = 5; 25 | 26 | ToContainerType("CargoLargeMining1A"); // this finalizes the changes to the given container subtype 27 | } 28 | 29 | // and more chunks like this if you need to edit more containertypes... 30 | //{ 31 | // AddItem("Component/Computer", frequency: 2.5, min: 10, max: 20); 32 | // RemoveItem("Ore/Iron"); 33 | // ToContainerType("CargoLargeMining1B"); 34 | //} 35 | 36 | 37 | 38 | CleanUp(); // do not remove 39 | } 40 | 41 | // no need to modify anything below 42 | 43 | int? SetCountMin = null; 44 | int? SetCountMax = null; 45 | 46 | HashSet RemoveItems = new HashSet(MyDefinitionId.Comparer); 47 | List AddItems = new List(); 48 | 49 | void CleanUp() 50 | { 51 | RemoveItems = null; 52 | AddItems = null; 53 | } 54 | 55 | void AddItem(string id, double frequency, double min, double max) 56 | { 57 | MyDefinitionId defId; 58 | if(!MyDefinitionId.TryParse(id, out defId)) 59 | return; 60 | 61 | AddItems.Add(new MyObjectBuilder_ContainerTypeDefinition.ContainerTypeItem() 62 | { 63 | Id = defId, 64 | Frequency = (float)frequency, 65 | AmountMin = min.ToString(), 66 | AmountMax = max.ToString(), 67 | }); 68 | } 69 | 70 | void RemoveItem(string id) 71 | { 72 | MyDefinitionId defId; 73 | if(!MyDefinitionId.TryParse(id, out defId)) 74 | return; 75 | 76 | RemoveItems.Add(defId); 77 | } 78 | 79 | void ToContainerType(string containerTypeSubtypeId) 80 | { 81 | try 82 | { 83 | MyContainerTypeDefinition ctDef = MyDefinitionManager.Static.GetContainerTypeDefinition(containerTypeSubtypeId); 84 | if(ctDef != null) 85 | { 86 | for(int i = 0; i < ctDef.Items.Length; i++) 87 | { 88 | MyContainerTypeDefinition.ContainerTypeItem item = ctDef.Items[i]; 89 | if(RemoveItems.Contains(item.DefinitionId)) 90 | continue; 91 | 92 | AddItems.Add(new MyObjectBuilder_ContainerTypeDefinition.ContainerTypeItem() 93 | { 94 | Id = item.DefinitionId, 95 | Frequency = item.Frequency, 96 | AmountMin = item.AmountMin.SerializeString(), 97 | AmountMax = item.AmountMax.SerializeString(), 98 | }); 99 | } 100 | 101 | // cannot edit the live definition because it has a private array that needs resizing, but cannot be because it is private. 102 | MyObjectBuilder_ContainerTypeDefinition OB = (MyObjectBuilder_ContainerTypeDefinition)ctDef.GetObjectBuilder(); 103 | 104 | // base stuff missed by GetObjectBuilder() 105 | OB.DLCs = ctDef.DLCs; 106 | 107 | OB.CountMin = SetCountMin ?? ctDef.CountMin; 108 | OB.CountMax = SetCountMax ?? ctDef.CountMax; 109 | OB.Items = AddItems.ToArray(); 110 | 111 | ctDef.Init(OB, (MyModContext)ModContext); // also marking this def as modified by this mod 112 | } 113 | } 114 | finally 115 | { 116 | RemoveItems.Clear(); 117 | AddItems.Clear(); 118 | SetCountMin = null; 119 | SetCountMax = null; 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ModifyProjectileExplosion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Game; 3 | using Sandbox.Game.Entities; 4 | using Sandbox.Game.Weapons; 5 | using Sandbox.ModAPI; 6 | using VRage.Game; 7 | using VRage.Game.Components; 8 | using VRage.Game.ModAPI; 9 | using VRage.ModAPI; 10 | using VRage.Utils; 11 | 12 | namespace Digi.Examples 13 | { 14 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 15 | public class Example_ModifyProjectileExplosion : MySessionComponentBase 16 | { 17 | public override void LoadData() 18 | { 19 | MyExplosions.OnExplosion += OnExplosion; 20 | } 21 | 22 | protected override void UnloadData() 23 | { 24 | MyExplosions.OnExplosion -= OnExplosion; 25 | } 26 | 27 | void OnExplosion(ref MyExplosionInfo info) 28 | { 29 | try 30 | { 31 | // disclaimer: not thoroughly tested, up to you to do so and feedback with findings :P 32 | 33 | if(info.ExplosionType != MyExplosionTypeEnum.ProjectileExplosion) 34 | return; 35 | 36 | IMyEntity originEntity = MyAPIGateway.Entities.GetEntityById(info.OriginEntity); 37 | if(originEntity == null) 38 | return; 39 | 40 | IMyCubeBlock block = originEntity as IMyCubeBlock; 41 | if(block != null) 42 | { 43 | switch(block.BlockDefinition.SubtypeName) 44 | { 45 | case "SomeWeaponId": 46 | case "MoreIfYouWant": 47 | info.ExplosionType = MyExplosionTypeEnum.CUSTOM; 48 | info.CustomEffect = "SomeParticleId"; 49 | break; 50 | 51 | // as many subtypes as you want 52 | } 53 | return; 54 | } 55 | 56 | IMyHandheldGunObject handHeld = originEntity as IMyHandheldGunObject; 57 | if(handHeld?.PhysicalItemDefinition != null) 58 | { 59 | switch(handHeld.PhysicalItemDefinition.Id.SubtypeName) 60 | { 61 | case "SomeItemId": 62 | case "Etc...": 63 | info.ExplosionType = MyExplosionTypeEnum.CUSTOM; 64 | info.CustomEffect = "SomeParticleId"; 65 | break; 66 | 67 | // as many subtypes as you want 68 | } 69 | return; 70 | } 71 | } 72 | catch(Exception e) 73 | { 74 | AddToLog(e); 75 | } 76 | } 77 | 78 | void AddToLog(Exception e) 79 | { 80 | MyLog.Default.WriteLineAndConsole($"ERROR {GetType().FullName}: {e.ToString()}"); 81 | 82 | if(MyAPIGateway.Session?.Player != null) 83 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {GetType().FullName}: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ModifyTimerLimits.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Sandbox.Common.ObjectBuilders; 5 | using Sandbox.Definitions; 6 | using Sandbox.ModAPI; 7 | using Sandbox.ModAPI.Interfaces.Terminal; 8 | using SpaceEngineers.Game.ModAPI; 9 | using VRage.Game; 10 | using VRage.Game.Components; 11 | using VRage.ModAPI; 12 | using VRage.ObjectBuilders; 13 | 14 | namespace Digi.Examples 15 | { 16 | [MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)] 17 | public class ModifyTimerLimits : MySessionComponentBase 18 | { 19 | public override void LoadData() 20 | { 21 | //SetForAll(500, 6000); 22 | 23 | // change only specific timers, use the TypeId and SubtypeId from tag! 24 | SetFor("TimerBlock/TimerBlockLarge", 500, 6000); 25 | SetFor("TimerBlock/TimerBlockSmall", 500, 6000); 26 | } 27 | 28 | protected override void UnloadData() 29 | { 30 | } 31 | 32 | void SetForAll(int minMs, int maxMs) 33 | { 34 | foreach(var def in MyDefinitionManager.Static.GetAllDefinitions()) 35 | { 36 | var timerDef = def as MyTimerBlockDefinition; 37 | if(timerDef != null) 38 | { 39 | timerDef.MinDelay = minMs; 40 | timerDef.MaxDelay = maxMs; 41 | 42 | //MyLog.Default.WriteLine($"{defId} modified delay limits to {minMs} to {maxMs} ms."); 43 | } 44 | } 45 | } 46 | 47 | void SetFor(string id, int minMs, int maxMs) 48 | { 49 | MyDefinitionId defId; 50 | if(!MyDefinitionId.TryParse(id, out defId)) 51 | { 52 | MyDefinitionErrors.Add((MyModContext)ModContext, $"Invalid definition typeId: '{id}' (subtype isn't checked here)", TErrorSeverity.Warning); 53 | return; 54 | } 55 | 56 | var timerDef = MyDefinitionManager.Static.GetCubeBlockDefinition(defId) as MyTimerBlockDefinition; 57 | if(timerDef == null) 58 | { 59 | MyDefinitionErrors.Add((MyModContext)ModContext, $"Cannot find definition with id: '{id}' (or it exists but it's not a timer block definition)", TErrorSeverity.Warning); 60 | return; 61 | } 62 | 63 | timerDef.MinDelay = minMs; 64 | timerDef.MaxDelay = maxMs; 65 | timerDef.Context = (MyModContext)ModContext; 66 | 67 | //MyLog.Default.WriteLine($"{defId} modified delay limits to {minMs} to {maxMs} ms."); 68 | } 69 | } 70 | 71 | // the vanilla Delay slider ignores the Min/MaxDelay tags but everything else seems to not 72 | // so this piece of code changes its limits to respect those tags. 73 | static class TerminalControls 74 | { 75 | static bool ControlsModified = false; 76 | 77 | public static void Setup() 78 | { 79 | if(ControlsModified) 80 | return; 81 | 82 | ControlsModified = true; 83 | 84 | List controls; 85 | MyAPIGateway.TerminalControls.GetControls(out controls); 86 | 87 | foreach(var c in controls) 88 | { 89 | var cs = c as IMyTerminalControlSlider; 90 | if(cs != null && c.Id == "TriggerDelay") 91 | { 92 | cs.SetLimits(TimerDelayMin, TimerDelayMax); 93 | cs.Writer = TimerDelayWriter; 94 | } 95 | } 96 | } 97 | 98 | static void TimerDelayWriter(IMyTerminalBlock block, StringBuilder sb) 99 | { 100 | IMyTimerBlock timer = block as IMyTimerBlock; 101 | if(timer == null) 102 | return; 103 | 104 | TimeSpan span = TimeSpan.FromSeconds(timer.TriggerDelay); 105 | 106 | if(span.Days >= 1) 107 | sb.Append(span.Days).Append("d "); 108 | 109 | sb.Append(span.Hours.ToString("00")).Append(":"); 110 | sb.Append(span.Minutes.ToString("00")).Append(":"); 111 | sb.Append(span.Seconds.ToString("00")).Append("."); 112 | sb.Append(span.Milliseconds.ToString("000")).Append(""); 113 | } 114 | 115 | static float TimerDelayMin(IMyTerminalBlock block) 116 | { 117 | var timerDef = block?.SlimBlock?.BlockDefinition as MyTimerBlockDefinition; 118 | 119 | if(timerDef != null) 120 | return timerDef.MinDelay / 1000f; 121 | else 122 | return 1f; // default from MyTimerBlock.CreateTerminalControls() 123 | } 124 | 125 | static float TimerDelayMax(IMyTerminalBlock block) 126 | { 127 | var timerDef = block?.SlimBlock?.BlockDefinition as MyTimerBlockDefinition; 128 | 129 | if(timerDef != null) 130 | return timerDef.MaxDelay / 1000f; 131 | else 132 | return 3600f; // default from MyTimerBlock.CreateTerminalControls() 133 | } 134 | } 135 | 136 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_TimerBlock), false)] 137 | public class TimerBlock : MyGameLogicComponent 138 | { 139 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 140 | { 141 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 142 | } 143 | 144 | public override void UpdateOnceBeforeFrame() 145 | { 146 | TerminalControls.Setup(); // HACK: because terminal controls are weird 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ModifyWarheadExplosion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Game; 3 | using Sandbox.ModAPI; 4 | using VRage.Game; 5 | using VRage.Game.Components; 6 | using VRage.Game.ModAPI; 7 | using VRage.Utils; 8 | 9 | namespace Digi.Examples 10 | { 11 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 12 | public class Example_ModifyWarheadExplosion : MySessionComponentBase 13 | { 14 | public override void LoadData() 15 | { 16 | MyExplosions.OnExplosion += OnExplosion; 17 | } 18 | 19 | protected override void UnloadData() 20 | { 21 | MyExplosions.OnExplosion -= OnExplosion; 22 | } 23 | 24 | void OnExplosion(ref MyExplosionInfo info) 25 | { 26 | try 27 | { 28 | // disclaimer: not thoroughly tested, up to you to do so and feedback with findings :P 29 | 30 | switch(info.ExplosionType) 31 | { 32 | case MyExplosionTypeEnum.WARHEAD_EXPLOSION_02: 33 | case MyExplosionTypeEnum.WARHEAD_EXPLOSION_15: 34 | case MyExplosionTypeEnum.WARHEAD_EXPLOSION_30: 35 | case MyExplosionTypeEnum.WARHEAD_EXPLOSION_50: 36 | break; // continue onward 37 | 38 | default: 39 | return; // end function for any other type 40 | } 41 | 42 | IMyWarhead warhead = info.HitEntity as IMyWarhead; 43 | IMyCubeGrid grid = info.OwnerEntity as IMyCubeGrid; 44 | if(warhead == null || grid == null || warhead.CubeGrid != grid) 45 | return; 46 | 47 | switch(warhead.BlockDefinition.SubtypeId) 48 | { 49 | case "WarheadA": 50 | case "WarheadB": 51 | info.ExplosionType = MyExplosionTypeEnum.CUSTOM; 52 | info.CustomEffect = "FancyParticle"; 53 | break; 54 | 55 | case "WarheadC": 56 | info.ExplosionType = MyExplosionTypeEnum.CUSTOM; 57 | info.CustomEffect = "SomeLessFancyParticlexD"; 58 | break; 59 | 60 | // as many subtypes as you want 61 | } 62 | } 63 | catch(Exception e) 64 | { 65 | AddToLog(e); 66 | } 67 | } 68 | 69 | void AddToLog(Exception e) 70 | { 71 | MyLog.Default.WriteLineAndConsole($"ERROR {GetType().FullName}: {e.ToString()}"); 72 | 73 | if(MyAPIGateway.Session?.Player != null) 74 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {GetType().FullName}: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_NetworkProtobuf/ExampleNetwork_Session.cs: -------------------------------------------------------------------------------- 1 | using Digi.NetworkLib; 2 | using Sandbox.Game.Entities; 3 | using Sandbox.ModAPI; 4 | using VRage.Game.Components; 5 | using VRage.Game.ModAPI; 6 | using VRage.Input; 7 | using VRage.Library.Utils; 8 | using VRage.ModAPI; 9 | using VRage.Utils; 10 | 11 | namespace Digi.Examples.NetworkProtobuf 12 | { 13 | [MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)] 14 | public class ExampleNetwork_Session : MySessionComponentBase 15 | { 16 | // IMPORTANT: other mods using the same ID will send packets to you and receive your packets, which likely means deserialization errors. 17 | // Therefore pick a unique ID, one way is your workshopID % ushort.MaxValue, or just pick a random one that's higher than the low numbers (keen might be using those). 18 | public const ushort NetworkId = (ushort)(777777777777 % ushort.MaxValue); 19 | 20 | public Network Net; 21 | 22 | PacketSimpleExample PacketExample; 23 | 24 | public override void LoadData() 25 | { 26 | // The ID in this must be unique between other mods. 27 | // Usually suggested to be the last few numbers of your workshopId. 28 | Net = new Network(NetworkId, ModContext.ModName); 29 | // Also don't create multiple instances of Network (like instancing it in gamelogic, that would be very bad). 30 | 31 | // If you want errors to use your logger then you can do: 32 | //Net.ExceptionHandler = (e) => Log.Error(e); 33 | //Net.ErrorHandler = (msg) => Log.Error(msg); 34 | 35 | // To test if serialization works in singleplayer when using SendToServer(). 36 | Net.SerializeTest = true; 37 | 38 | 39 | // Re-usable for sending 40 | PacketExample = new PacketSimpleExample(); 41 | 42 | // For receiving (will be a different instance than the sending one because the receiver code creates it from bytes) 43 | // because this is a global event you should only hook it in global cases 44 | PacketSimpleExample.OnReceive += PacketSimpleExample_OnReceive; 45 | 46 | // For packets that are for a specific entity you still should do the event hooking here in session comp, 47 | // but you can still trigger code on the entity once you have its instance. 48 | // Refer to the commented-out example at the end of this file. 49 | } 50 | 51 | protected override void UnloadData() 52 | { 53 | Net?.Dispose(); 54 | Net = null; 55 | 56 | PacketSimpleExample.OnReceive -= PacketSimpleExample_OnReceive; 57 | } 58 | 59 | public override void UpdateAfterSimulation() 60 | { 61 | // example for testing in-game, press L in a world with this mod loaded 62 | if(MyAPIGateway.Input.IsNewKeyPressed(MyKeys.L)) 63 | { 64 | PacketExample.Setup("L was pressed", MyRandom.Instance.Next()); 65 | 66 | MyAPIGateway.Utilities.ShowNotification($"[Example] Sent: text={PacketExample.Text}; number={PacketExample.Number}"); 67 | 68 | Net.SendToServer(PacketExample); 69 | // always send to server even if you are server, from there you can decide in the receive method if you want to relay it to other players. 70 | // Net.SendToPlayer() and Net.SendToEveryone() are more for niche uses. 71 | } 72 | } 73 | 74 | void PacketSimpleExample_OnReceive(PacketSimpleExample packet, ref PacketInfo packetInfo, ulong senderSteamId) 75 | { 76 | // This is called on everyone that receives the packet. 77 | // 78 | // packet.OriginalSenderSteamId is the original sender of the packet and validated serverside to ensure it's not spoofed. 79 | // Your defined data is in the packet. variable, in this example would be Text and Number fields. 80 | // 81 | // Things in packetInfo. can be set depdending on what you want to happen when server receives this packet: 82 | // 83 | // packetInfo.Relay = RelayMode. -- to decide if the packet is sent to other players automatically. 84 | // The way you do stuff in packets depends on how the action works. 85 | // A few practical examples: 86 | // - an action that only works serverside and from there the game automatically synchronizes it, for this you'd use Relay.None (or just not set it, this is the default). 87 | // - an action that is needed locally on all players: 88 | // - you did the action on sender: Relay.ToOthers 89 | // - you only do the action in here: Relay.ToEveryone - which will send to sender too; this way is also nice to validate if sync works while alone in a DS. 90 | // - an action that needs to be done on a specific player, leave relaying off and use Net.SendToPlayer(), you do need to have the target player's steamId as part of the packet. 91 | // 92 | // packetInfo.Reserialize -- set true you modified the packet, niche purpose. 93 | 94 | 95 | string msg = $"[Example] Received {packet.GetType().Name}: text={packet.Text}; number={packet.Number}"; 96 | MyLog.Default.WriteLineAndConsole(msg); 97 | 98 | if(MyAPIGateway.Session.Player != null) 99 | { 100 | MyAPIGateway.Utilities.ShowNotification(msg); 101 | } 102 | 103 | 104 | // to see how this works in practice, try it in both singleplayer (you're the server) and as a MP client in a dedicated server (you can start one from steam tools). 105 | packetInfo.Relay = RelayMode.ToEveryone; 106 | 107 | 108 | // example of changing the data serverside before relaying to clients. 109 | packet.Text = "modified text"; 110 | packetInfo.Reserialize = true; 111 | } 112 | 113 | /* 114 | void PacketForSomeEntity_OnReceive(PacketForSomeEntity packet, ref PacketInfo packetInfo, ulong senderSteamId) 115 | { 116 | IMyEntity ent = MyEntities.GetEntityById(packet.EntityId); 117 | if(ent == null) 118 | { 119 | // log some error if this is unexpected, but do remember that clients do NOT have all entities available to them, only server does. 120 | return; 121 | } 122 | 123 | // from here if you have a gamelogic component on that entity you can do something like: 124 | var logic = ent.GameLogic?.GetAs(); 125 | if(logic == null) 126 | { 127 | return; 128 | } 129 | 130 | logic.ReceivedStuff(packet.Stuff); 131 | } 132 | */ 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_NetworkProtobuf/NetworkLib/PacketBase.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | using Sandbox.ModAPI; 3 | 4 | namespace Digi.NetworkLib 5 | { 6 | [ProtoContract(UseProtoMembersOnly = true)] 7 | public abstract partial class PacketBase 8 | { 9 | /// 10 | /// Automatically assigned to original sender's SteamId, validated when it reaches server. 11 | /// 12 | [ProtoMember(1)] 13 | public ulong OriginalSenderSteamId; 14 | 15 | public PacketBase() 16 | { 17 | if(MyAPIGateway.Multiplayer == null) 18 | Network.CrashAfterLoad($"Cannot instantiate packets in fields ({GetType().Name}), too early! Do it in one of the methods where MyAPIGateway.Multiplayer is not null."); 19 | else 20 | OriginalSenderSteamId = MyAPIGateway.Multiplayer.MyId; 21 | } 22 | 23 | /// 24 | /// Called when this packet is received on this machine.
25 | /// can be modified serverside to setup automatic relay. 26 | ///
27 | public abstract void Received(ref PacketInfo packetInfo, ulong senderSteamId); 28 | } 29 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_NetworkProtobuf/NetworkLib/Usage.txt: -------------------------------------------------------------------------------- 1 | Network library for handling packets easier. 2 | 3 | Working example: https://github.com/THDigi/SE-ModScript-Examples/tree/master/Data/Scripts/Examples/Example_NetworkProtobuf 4 | 5 | 6 | Alternate setup instructions: 7 | 8 | 1. Do not edit anything in the library folder, only copy it to your mod. 9 | 10 | 2. Create an instance of Network and give it the channel ID for your mod (must be unique to your mod). 11 | 12 | 3. In UnloadData() call Dispose() on the network instance. 13 | 14 | 4. Create packets by extending PacketBase, it will ask you what to override. 15 | 16 | 5. To register packets, create a "public abstract partial class PacketBase" in "Digi.NetworkLib" namespace but placed in your mod files. 17 | Next give it ProtoInclude attributes with tags starting from 10 and the types of your packets. 18 | 19 | Example: 20 | 21 | namespace Digi.NetworkLib 22 | { 23 | [ProtoInclude(10, typeof(PacketPaint))] 24 | [ProtoInclude(11, typeof(PacketReplacePaint))] 25 | 26 | [ProtoInclude(20, typeof(PacketPaletteUpdate))] 27 | [ProtoInclude(21, typeof(PacketPaletteSetColor))] 28 | [ProtoInclude(22, typeof(PacketJoinSharePalette))] 29 | 30 | [ProtoInclude(30, typeof(PacketToolSpraying))] 31 | 32 | [ProtoInclude(250, typeof(PacketOwnershipTestRequest))] 33 | [ProtoInclude(251, typeof(PacketOwnershipTestResults))] 34 | public abstract partial class PacketBase 35 | { 36 | // you can also add helper properties to be accessible in your packets 37 | protected PaintGunMod Main => PaintGunMod.Instance; 38 | protected Network Network => Main.NetworkLibHandler.Lib; 39 | } 40 | } 41 | 42 | 6. Recommended but not required: Create an instance of every packet and store them as fields. 43 | NOTE: do not instance them in the fields, they call MyAPIGateway.Multiplayer.MyId which is not available at that point. 44 | 45 | 7. Recommended but not required: Declare static events in the packet to shift the handling outside of the packet. 46 | 47 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_NetworkProtobuf/PacketSimpleExample.cs: -------------------------------------------------------------------------------- 1 | using Digi.NetworkLib; 2 | using ProtoBuf; 3 | 4 | namespace Digi.Examples.NetworkProtobuf 5 | { 6 | // An example packet with a string and a number. 7 | // Note that it must be ProtoIncluded in RegisterPackets.cs! 8 | [ProtoContract] 9 | public class PacketSimpleExample : PacketBase 10 | { 11 | public PacketSimpleExample() { } // Empty constructor required for deserialization 12 | 13 | // Each field has to have a unique ProtoMember number. 14 | // And ideally don't change its type after mod is released, instead give it a new number and comment out the old one. 15 | 16 | // A protomember's value will only be sent if it's not the default value, which saves on bandwidth. 17 | // WARNING: default value is not being sent and protobuf can't tell between default or null. 18 | // Therefore to keep it simple, do not give fields any predetermined value. 19 | // If you must have an, for example, integer with a defalut value, use nullable and use that value if it's null. 20 | 21 | [ProtoMember(1)] 22 | public string Text; 23 | 24 | [ProtoMember(2)] 25 | public int Number; 26 | 27 | public void Setup(string text, int number) 28 | { 29 | // Ensure you assign ALL the protomember fields here to avoid problems. 30 | Text = text; 31 | Number = number; 32 | } 33 | 34 | // Alternative way of handling the data elsewhere. 35 | // Or you can handle it in the Received() method below and remove this event, up to you. 36 | public static event ReceiveDelegate OnReceive; 37 | 38 | public override void Received(ref PacketInfo packetInfo, ulong senderSteamId) 39 | { 40 | OnReceive?.Invoke(this, ref packetInfo, senderSteamId); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_NetworkProtobuf/RegisterPackets.cs: -------------------------------------------------------------------------------- 1 | using Digi.Examples.NetworkProtobuf; 2 | using ProtoBuf; 3 | 4 | namespace Digi.NetworkLib 5 | { 6 | [ProtoInclude(10, typeof(PacketSimpleExample))] 7 | //[ProtoInclude(11, typeof(SomeOtherPacketClass))] 8 | //[ProtoInclude(12, typeof(Etc...))] 9 | public abstract partial class PacketBase 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_OverrideLocalizationKeys.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Sandbox.ModAPI; 3 | using VRage; 4 | using VRage.Game.Components; 5 | 6 | namespace Digi.Examples 7 | { 8 | // It loads Data\Localization\MyTexts.override.resx overriding the keys declared there regardless of current language. 9 | 10 | // Common use case for this is to change ore names seen in HUD from hand-drill/ore-detector. 11 | // Because MinedOre from material is also used as lang-key lookup but it doesn't work with keen's mod localization support. 12 | 13 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 14 | public class Example_OverrideLocalizationKeys : MySessionComponentBase 15 | { 16 | public override void LoadData() 17 | { 18 | LoadLangOverrides(); 19 | 20 | MyAPIGateway.Gui.GuiControlRemoved += GuiControlRemoved; 21 | } 22 | 23 | protected override void UnloadData() 24 | { 25 | MyAPIGateway.Gui.GuiControlRemoved -= GuiControlRemoved; 26 | } 27 | 28 | void LoadLangOverrides() 29 | { 30 | string folder = Path.Combine(ModContext.ModPathData, "Localization"); 31 | 32 | // this method loads all MyCommonTexts/MyCoreTexts/MyTexts prefixed files from the given folder. 33 | // if culture is not null it would also load the same prefixed files with `Prefix.Culture.resx` 34 | // if culture and subculture are not null, aside from loading the culture one it also loads `Prefix.Culture-Subculture.resx`. 35 | MyTexts.LoadTexts(folder, cultureName: "override", subcultureName: null); 36 | } 37 | 38 | void GuiControlRemoved(object screen) 39 | { 40 | if(screen == null) 41 | return; 42 | 43 | // detect when options menu is closed in case player changes language 44 | if(screen.ToString().EndsWith("ScreenOptionsSpace")) 45 | { 46 | LoadLangOverrides(); 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_PreventBlockDamageOnCharacters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Sandbox.Common.ObjectBuilders; 4 | using Sandbox.Game.Entities; 5 | using Sandbox.ModAPI; 6 | using VRage.Game; 7 | using VRage.Game.Components; 8 | using VRage.Game.ModAPI; 9 | using VRage.ObjectBuilders; 10 | using VRage.Utils; 11 | 12 | namespace Digi.Examples 13 | { 14 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 15 | public class Example_PreventBlockDamageOnCharacters : MySessionComponentBase 16 | { 17 | HashSet BlockTypes = new HashSet() 18 | { 19 | // prevent an entire block type from doing damage to characters. 20 | //typeof(MyObjectBuilder_ShipWelder), 21 | //typeof(MyObjectBuilder_ShipGrinder), 22 | //typeof(MyObjectBuilder_Drill), 23 | }; 24 | 25 | HashSet BlockIDs = new HashSet() 26 | { 27 | // add your block's tag formatted as "TypeId/SubtypeId", like shown below. 28 | // the list can also be empty, especially if you want to use the above BlockTypes instead. 29 | MyDefinitionId.Parse("ShipGrinder/SomeSafeGrinder"), 30 | MyDefinitionId.Parse("ShipWelder/Idunno"), 31 | MyDefinitionId.Parse("ShipWelder/Whatever"), 32 | }; 33 | 34 | public override void BeforeStart() 35 | { 36 | // damage is done only serverside, this also means this script can work for DS that allow console players to join. 37 | if(MyAPIGateway.Session.IsServer) 38 | { 39 | MyAPIGateway.Session.DamageSystem.RegisterBeforeDamageHandler(100, DamageHandler); 40 | } 41 | } 42 | 43 | protected override void UnloadData() 44 | { 45 | // damage system does not have unregister 46 | } 47 | 48 | void DamageHandler(object victim, ref MyDamageInformation info) 49 | { 50 | try 51 | { 52 | if(info.IsDeformation || info.Amount <= 0 || info.AttackerId == 0) 53 | return; 54 | 55 | IMyCharacter chr = victim as IMyCharacter; 56 | if(chr == null) 57 | return; 58 | 59 | IMyCubeBlock block = MyEntities.GetEntityById(info.AttackerId) as IMyCubeBlock; 60 | if(block == null) 61 | return; 62 | 63 | if(BlockTypes.Contains(block.BlockDefinition.TypeId) || BlockIDs.Contains(block.BlockDefinition)) 64 | { 65 | info.Amount = 0f; 66 | 67 | //MyLog.Default.WriteLine($"{ModContext?.ModName ?? GetType().FullName}: Prevented damage from {block.BlockDefinition} to {chr.DisplayName}"); 68 | } 69 | } 70 | catch(Exception e) 71 | { 72 | AddToLog(e); 73 | } 74 | } 75 | 76 | void AddToLog(Exception e) 77 | { 78 | string modName = ModContext?.ModName ?? GetType().FullName; 79 | MyLog.Default.WriteLineAndConsole($"{modName} ERROR: {e.ToString()}"); 80 | if(MyAPIGateway.Session?.Player != null) 81 | MyAPIGateway.Utilities.ShowNotification($"[ {modName} ERROR: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ProtectionBlock/Block.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Common.ObjectBuilders; 2 | using Sandbox.ModAPI; 3 | using VRage.Game.Components; 4 | using VRage.Game.ModAPI; 5 | using VRage.ModAPI; 6 | using VRage.ObjectBuilders; 7 | 8 | namespace Digi.Example_ProtectionBlock 9 | { 10 | // change MyObjectBuilder_BatteryBlock to the block type you're using, it must be the exact type, no inheritence. 11 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_BatteryBlock), false, "BlockSubtypeHere", "more if needed...")] 12 | public class ProtectionBlock : MyGameLogicComponent 13 | { 14 | // this method is called async! always do stuff in the first update unless you're sure it must be in Init(). 15 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 16 | { 17 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 18 | } 19 | 20 | public override void UpdateOnceBeforeFrame() // first update of the block 21 | { 22 | var block = (IMyCubeBlock)Entity; 23 | 24 | if(block.CubeGrid?.Physics == null) // ignore projected and other non-physical grids 25 | return; 26 | 27 | ProtectionSession.Instance?.ProtectionBlocks.Add(block); 28 | } 29 | 30 | public override void Close() // called when block is removed for whatever reason (including ship despawn) 31 | { 32 | ProtectionSession.Instance?.ProtectionBlocks.Remove((IMyFunctionalBlock)Entity); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ProtectionBlock/Session.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Sandbox.ModAPI; 3 | using VRage.Game.Components; 4 | using VRage.Game.ModAPI; 5 | 6 | namespace Digi.Example_ProtectionBlock 7 | { 8 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 9 | public class ProtectionSession : MySessionComponentBase 10 | { 11 | public static ProtectionSession Instance; // NOTE: this is the only acceptable static if you nullify it afterwards. 12 | 13 | public List ProtectionBlocks = new List(); 14 | 15 | public override void LoadData() 16 | { 17 | Instance = this; 18 | } 19 | 20 | public override void BeforeStart() 21 | { 22 | MyAPIGateway.Session.DamageSystem.RegisterBeforeDamageHandler(0, BeforeDamage); 23 | } 24 | 25 | protected override void UnloadData() 26 | { 27 | Instance = null; // important to avoid this object instance from remaining in memory on world unload/reload 28 | } 29 | 30 | private void BeforeDamage(object target, ref MyDamageInformation info) 31 | { 32 | if(info.Amount == 0) 33 | return; 34 | 35 | var slim = target as IMySlimBlock; 36 | 37 | if(slim == null) 38 | return; 39 | 40 | // if any of the protection blocks are on this grid then protect it 41 | foreach(var block in ProtectionBlocks) 42 | { 43 | // checks for same grid-group to extend protection to piston/rotors/wheels but no connectors (change link type to Physical to include those) 44 | // same grid only check: block.CubeGrid == slim.CubeGrid 45 | if(MyAPIGateway.GridGroups.HasConnection(block.CubeGrid, slim.CubeGrid, GridLinkTypeEnum.Mechanical)) 46 | { 47 | info.Amount = 0; 48 | return; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_RemoveCategory.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Definitions; 2 | using VRage.Game.Components; 3 | 4 | namespace Digi.Experiments 5 | { 6 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 7 | public class Example_RemoveCategory : MySessionComponentBase 8 | { 9 | public override void LoadData() 10 | { 11 | RemoveCategory("LargeBlocks"); 12 | // can repeat the above line to remove more 13 | } 14 | 15 | void RemoveCategory(string categoryName) 16 | { 17 | MyGuiBlockCategoryDefinition categoryDef; 18 | if(MyDefinitionManager.Static.GetCategories().TryGetValue(categoryName, out categoryDef)) 19 | { 20 | categoryDef.ItemIds.Clear(); 21 | 22 | categoryDef.IsBlockCategory = true; 23 | categoryDef.IsAnimationCategory = false; 24 | categoryDef.IsShipCategory = false; 25 | categoryDef.IsToolCategory = false; 26 | } 27 | } 28 | 29 | protected override void UnloadData() 30 | { 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_RemoveFromBPClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Sandbox.Definitions; 4 | using VRage.Game; 5 | using VRage.Game.Components; 6 | using VRage.Utils; 7 | 8 | namespace Digi.Examples 9 | { 10 | // NOTE: not really necessary as a script anymore, it can be done with SBC! 11 | // see: https://github.com/THDigi/SE-ModScript-Examples/wiki/Hidden-SBC-tags-features#remove-blueprints-from-bpclass 12 | 13 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 14 | public class Example_RemoveFromBPClass : MySessionComponentBase 15 | { 16 | List NewBlueprints = new List(); 17 | HashSet ModifiedBpClasses = new HashSet(); 18 | 19 | public override void LoadData() 20 | { 21 | RemoveBlueprintsFromBlueprintClass("bpClassSubtype", new HashSet() 22 | { 23 | "blueprintSubtype", 24 | // more like the above line if needed 25 | }); 26 | 27 | // more like the above code chunk if needed for removing from multiple classes 28 | 29 | 30 | 31 | Finish(); // this must always be last 32 | } 33 | 34 | void RemoveBlueprintsFromBlueprintClass(string bpClassName, HashSet removeBlueprintIds) 35 | { 36 | MyBlueprintClassDefinition bpClass = MyDefinitionManager.Static.GetBlueprintClass(bpClassName); 37 | if(bpClass == null) 38 | throw new Exception($"{ModContext.ModName} :: ERROR: Cannot find blueprint class '{bpClassName}'"); 39 | 40 | NewBlueprints.Clear(); 41 | 42 | int bpCount = 0; 43 | 44 | foreach(MyBlueprintDefinitionBase bp in bpClass) 45 | { 46 | bpCount++; 47 | 48 | if(removeBlueprintIds.Contains(bp.Id.SubtypeName)) 49 | MyLog.Default.WriteLine($"{ModContext.ModName} :: Removed {bp.Id.SubtypeName} from blueprint class '{bpClassName}'"); 50 | else 51 | NewBlueprints.Add(bp); 52 | } 53 | 54 | if(NewBlueprints.Count == bpCount) 55 | { 56 | MyLog.Default.WriteLine($"{ModContext.ModName} :: WARNING: Blueprint class '{bpClassName}' does not contain any of these blueprints: {string.Join(", ", removeBlueprintIds)}"); 57 | return; 58 | } 59 | 60 | bpClass.ClearBlueprints(); 61 | 62 | foreach(MyBlueprintDefinitionBase bp in NewBlueprints) 63 | { 64 | bpClass.AddBlueprint(bp); 65 | } 66 | 67 | ModifiedBpClasses.Add(bpClass); 68 | } 69 | 70 | void Finish() 71 | { 72 | PostProcessProductionBlocks(); // required to make production blocks aware of the blueprint changes, to adjust their inventory constraints and whatever else 73 | NewBlueprints = null; 74 | ModifiedBpClasses = null; 75 | } 76 | 77 | void PostProcessProductionBlocks() 78 | { 79 | foreach(MyDefinitionBase def in MyDefinitionManager.Static.GetAllDefinitions()) 80 | { 81 | MyProductionBlockDefinition productionDef = def as MyProductionBlockDefinition; 82 | if(productionDef == null) 83 | continue; 84 | 85 | // only post-process if it has one of the affected classes to reduce disruption of other mod's changes 86 | foreach(MyBlueprintClassDefinition bpClass in productionDef.BlueprintClasses) 87 | { 88 | if(ModifiedBpClasses.Contains(bpClass)) 89 | { 90 | productionDef.LoadPostProcess(); 91 | break; // exit bpclass loop. the all definition loop is unaffected 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ServerConfig_Basic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Sandbox.ModAPI; 4 | using VRage.Game.Components; 5 | using VRage.Game.ModAPI; 6 | using VRage.Game.ModAPI.Ingame.Utilities; // this ingame namespace is safe to use in mods as it has nothing to collide with 7 | using VRage.Utils; 8 | 9 | namespace Digi.Examples 10 | { 11 | // This example is minimal code required for it to work and with comments so you can better understand what is going on. 12 | 13 | // The gist of it is: ini file is loaded/created that admin can edit, SetVariable is used to store that data in sandbox.sbc which gets automatically sent to joining clients. 14 | // Benefit of this is clients will be getting this data before they join, very good if you need it during LoadData() 15 | // This example does not support reloading config while server runs, you can however implement that by sending a packet to all online players with the ini data for them to parse. 16 | 17 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 18 | public class Example_ServerConfig_Basic : MySessionComponentBase 19 | { 20 | ExampleSettings Settings = new ExampleSettings(); 21 | 22 | public override void LoadData() 23 | { 24 | Settings.Load(ModContext); 25 | 26 | // example usage/debug 27 | MyLog.Default.WriteLineAndConsole($"### DEBUG {ModContext.ModName} :: SomeNumber value={Settings.SomeNumber}"); 28 | MyLog.Default.WriteLineAndConsole($"### DEBUG {ModContext.ModName} :: ToggleThings value={Settings.ToggleThings}"); 29 | } 30 | } 31 | 32 | public class ExampleSettings 33 | { 34 | const string VariableId = nameof(Example_ServerConfig_Basic); // IMPORTANT: must be unique as it gets written in a shared space (sandbox.sbc) 35 | const string FileName = "Config.ini"; // the file that gets saved to world storage under your mod's folder 36 | const string IniSection = "Config"; 37 | 38 | // settings you'd be reading, and their defaults. 39 | public float SomeNumber = 1f; 40 | public bool ToggleThings = true; 41 | 42 | IMyModContext Mod; 43 | 44 | void LoadConfig(MyIni iniParser) 45 | { 46 | // repeat for each setting field 47 | SomeNumber = iniParser.Get(IniSection, nameof(SomeNumber)).ToSingle(SomeNumber); 48 | 49 | ToggleThings = iniParser.Get(IniSection, nameof(ToggleThings)).ToBoolean(ToggleThings); 50 | } 51 | 52 | void SaveConfig(MyIni iniParser) 53 | { 54 | // repeat for each setting field 55 | iniParser.Set(IniSection, nameof(SomeNumber), SomeNumber); 56 | iniParser.SetComment(IniSection, nameof(SomeNumber), "This number does something for sure"); // optional 57 | 58 | iniParser.Set(IniSection, nameof(ToggleThings), ToggleThings); 59 | } 60 | 61 | // nothing to edit below this point 62 | 63 | public ExampleSettings() 64 | { 65 | } 66 | 67 | public void Load(IMyModContext mod) 68 | { 69 | Mod = mod; 70 | 71 | if(MyAPIGateway.Session.IsServer) 72 | LoadOnHost(); 73 | else 74 | LoadOnClient(); 75 | } 76 | 77 | void LoadOnHost() 78 | { 79 | // HACK: Fix for files created in game's CustomWorlds folder when world is created with this mod present. 80 | // bugreport: https://support.keenswh.com/spaceengineers/pc/topic/47762-modapi-write-to-world-storage-can-write-to-game-folder 81 | string savePath = MyAPIGateway.Session?.CurrentPath; 82 | string gamePath = MyAPIGateway.Utilities?.GamePaths?.ContentPath; 83 | if(savePath == null || gamePath == null || savePath.StartsWith(MyAPIGateway.Utilities.GamePaths.ContentPath)) 84 | { 85 | Log("Delaying world config loading/creating because of world creation bugs..."); 86 | MyAPIGateway.Utilities.InvokeOnGameThread(LoadOnHost); 87 | return; 88 | } 89 | 90 | MyIni iniParser = new MyIni(); 91 | 92 | // load file if exists then save it regardless so that it can be sanitized and updated 93 | 94 | if(MyAPIGateway.Utilities.FileExistsInWorldStorage(FileName, typeof(ExampleSettings))) 95 | { 96 | using(TextReader file = MyAPIGateway.Utilities.ReadFileInWorldStorage(FileName, typeof(ExampleSettings))) 97 | { 98 | string text = file.ReadToEnd(); 99 | 100 | MyIniParseResult result; 101 | if(!iniParser.TryParse(text, out result)) 102 | throw new Exception($"Config error: {result.ToString()}"); 103 | 104 | LoadConfig(iniParser); 105 | Log("World config loaded!"); 106 | } 107 | } 108 | 109 | iniParser.Clear(); // remove any existing settings that might no longer exist 110 | 111 | SaveConfig(iniParser); 112 | 113 | string saveText = iniParser.ToString(); 114 | 115 | MyAPIGateway.Utilities.SetVariable(VariableId, saveText); 116 | 117 | using(TextWriter file = MyAPIGateway.Utilities.WriteFileInWorldStorage(FileName, typeof(ExampleSettings))) 118 | { 119 | file.Write(saveText); 120 | } 121 | 122 | Log("World config created/updated."); 123 | } 124 | 125 | void LoadOnClient() 126 | { 127 | string text; 128 | if(!MyAPIGateway.Utilities.GetVariable(VariableId, out text)) 129 | throw new Exception("No config found in sandbox.sbc!"); 130 | 131 | MyIni iniParser = new MyIni(); 132 | MyIniParseResult result; 133 | if(!iniParser.TryParse(text, out result)) 134 | throw new Exception($"Config error: {result.ToString()}"); 135 | 136 | LoadConfig(iniParser); 137 | Log("World config loaded!"); 138 | } 139 | 140 | void Log(string msg) 141 | { 142 | MyLog.Default.WriteLineAndConsole($"Mod {Mod.ModName}: {msg}"); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_SetFactionColors.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.ModAPI; 2 | using VRage.Game; 3 | using VRage.Game.Components; 4 | using VRage.Utils; 5 | using VRageMath; 6 | 7 | namespace Digi.Examples 8 | { 9 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 10 | public class Example_SetFactionColors : MySessionComponentBase 11 | { 12 | public override void BeforeStart() 13 | { 14 | // example usage, first is faction tag, second is faction color in RGB, third is icon color in RGB 15 | // SetFactionColor("SPRT", new Color(255,0,255), new Color(0,0,255)); 16 | } 17 | 18 | void SetFactionColor(string tag, Color factionColor, Color iconColor) 19 | { 20 | var faction = MyAPIGateway.Session.Factions.TryGetFactionByTag(tag); 21 | if(faction == null) 22 | { 23 | MyLog.Default.WriteLine($"ERROR: Can't find faction by tag '{tag}' to change colors."); 24 | return; 25 | } 26 | 27 | MyAPIGateway.Session.Factions.EditFaction(faction.FactionId, faction.Tag, faction.Name, faction.Description, faction.PrivateInfo, 28 | faction.FactionIcon.ToString(), MyColorPickerConstants.HSVToHSVOffset(factionColor.ColorToHSV()), MyColorPickerConstants.HSVToHSVOffset(iconColor.ColorToHSV())); 29 | 30 | MyLog.Default.WriteLine($"Edited faction {faction.Name}'s faction color to {factionColor} and icon color to {iconColor}."); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_SetInventoryConstraintIcon.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Sandbox.Common.ObjectBuilders; 3 | using Sandbox.Definitions; 4 | using Sandbox.ModAPI; 5 | using VRage.Game; 6 | using VRage.Game.Components; 7 | 8 | namespace Digi.Examples 9 | { 10 | [MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)] 11 | public class Example_SetInventoryConstraintIcon : MySessionComponentBase 12 | { 13 | public override void LoadData() 14 | { 15 | SetReactorConstraintIcon("YourReactorSubtype", @"Textures\GUI\Icons\filter_ingot.dds"); 16 | 17 | // a vanilla example to try out right away: 18 | SetReactorConstraintIcon("SmallBlockSmallGenerator", @"Textures\GUI\Icons\Bug.dds"); 19 | } 20 | 21 | void SetReactorConstraintIcon(string subtypeId, string iconPath) 22 | { 23 | var def = MyDefinitionManager.Static.GetCubeBlockDefinition(new MyDefinitionId(typeof(MyObjectBuilder_Reactor), subtypeId)) as MyReactorDefinition; 24 | if(def == null) 25 | { 26 | MyDefinitionErrors.Add((MyModContext)ModContext, $"Couldn't find Reactor with subtype: {subtypeId}", TErrorSeverity.Warning); 27 | return; 28 | } 29 | 30 | if(MyAPIGateway.Utilities.FileExistsInModLocation(iconPath, ModContext.ModItem)) 31 | { 32 | iconPath = Path.Combine(ModContext.ModPath, iconPath); // turn it into full path 33 | } 34 | else if(!MyAPIGateway.Utilities.FileExistsInGameContent(iconPath)) 35 | { 36 | MyDefinitionErrors.Add((MyModContext)ModContext, $"Couldn't find icon in mod folder nor in game folder: {iconPath}", TErrorSeverity.Warning); 37 | return; 38 | } 39 | 40 | def.InventoryConstraint.Icon = iconPath; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ShipDrillHarvestMultiplier.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Common.ObjectBuilders; 2 | using Sandbox.ModAPI; 3 | using VRage.Game.Components; 4 | using VRage.ObjectBuilders; 5 | 6 | namespace Digi.Examples 7 | { 8 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_Drill), false, "Yoursubtypes", "or more", "add as many as you want")] 9 | public class ShipDrillHarvestMultiplier : MyGameLogicComponent 10 | { 11 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 12 | { 13 | var block = (IMyShipDrill)Entity; 14 | block.DrillHarvestMultiplier = 2.5f; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_ShipGrinderSpeed.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Sandbox.Common.ObjectBuilders; 3 | using Sandbox.Game.Entities; 4 | using Sandbox.ModAPI; 5 | using VRage.Game; 6 | using VRage.Game.Components; 7 | using VRage.Game.ModAPI; 8 | 9 | namespace Digi.Examples 10 | { 11 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 12 | public class Example_ShipGrinderSpeed : MySessionComponentBase 13 | { 14 | private readonly Dictionary perToolAdjust = new Dictionary(MyDefinitionId.Comparer) 15 | { 16 | [new MyDefinitionId(typeof(MyObjectBuilder_ShipGrinder), "LargeShipGrinder")] = 3f, 17 | [new MyDefinitionId(typeof(MyObjectBuilder_ShipGrinder), "SmallShipGrinder")] = 0.1f, 18 | }; 19 | 20 | public override void BeforeStart() 21 | { 22 | MyAPIGateway.Session.DamageSystem.RegisterBeforeDamageHandler(0, BeforeDamageApplied); 23 | } 24 | 25 | protected override void UnloadData() 26 | { 27 | // damage system doesn't have an unregister method 28 | } 29 | 30 | private void BeforeDamageApplied(object target, ref MyDamageInformation info) 31 | { 32 | if(info.IsDeformation || info.AttackerId == 0 || !(target is IMySlimBlock)) 33 | return; // fastest checks first to exit ASAP as this method is quite frequently called 34 | 35 | var shipTool = MyEntities.GetEntityById(info.AttackerId) as IMyShipToolBase; 36 | 37 | if(shipTool == null) 38 | return; 39 | 40 | float adjust; 41 | 42 | if(!perToolAdjust.TryGetValue(shipTool.BlockDefinition, out adjust)) 43 | return; 44 | 45 | info.Amount *= adjust; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_SpinningSubpart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Common.ObjectBuilders; 3 | using Sandbox.ModAPI; 4 | using VRage.Game; 5 | using VRage.Game.Components; 6 | using VRage.Game.Entity; 7 | using VRage.ModAPI; 8 | using VRage.ObjectBuilders; 9 | using VRage.Utils; 10 | using VRageMath; 11 | 12 | namespace Digi.Examples 13 | { 14 | // This shows the basics, recommended to look at the newer Example_SubpartMoveAdvanced.cs for animating multiple subparts linearly and/or rotationally 15 | 16 | // Edit the block type and subtypes to match your custom block. 17 | // For type always use the same name as the and append "MyObjectBuilder_" to it, don't use the one from xsi:type. 18 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_Reactor), false, "YourSubtypeHere", "More if needed")] 19 | public class Example_SpinningSubpart : MyGameLogicComponent 20 | { 21 | private const string SUBPART_NAME = "thing"; // dummy name without the "subpart_" prefix 22 | private const float DEGREES_PER_TICK = 1.5f; // rotation per tick in degrees (60 ticks per second) 23 | private const float ACCELERATE_PERCENT_PER_TICK = 0.05f; // aceleration percent of "DEGREES_PER_TICK" per tick. 24 | private const float DEACCELERATE_PERCENT_PER_TICK = 0.01f; // deaccleration percent of "DEGREES_PER_TICK" per tick. 25 | private readonly Vector3 ROTATION_AXIS = Vector3.Forward; // rotation axis for the subpart, you can do new Vector3(0.0f, 0.0f, 0.0f) for custom values 26 | private const float MAX_DISTANCE_SQ = 1000 * 1000; // player camera must be under this distance (squared) to see the subpart spinning 27 | 28 | private IMyFunctionalBlock block; 29 | private bool subpartFirstFind = true; 30 | private Matrix subpartLocalMatrix; // keeping the matrix here because subparts are being re-created on paint, resetting their orientations 31 | private float targetSpeedMultiplier; // used for smooth transition 32 | 33 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 34 | { 35 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 36 | } 37 | 38 | public override void UpdateOnceBeforeFrame() 39 | { 40 | if(MyAPIGateway.Utilities.IsDedicated) 41 | return; 42 | 43 | block = (IMyFunctionalBlock)Entity; 44 | 45 | if(block.CubeGrid?.Physics == null) 46 | return; 47 | 48 | NeedsUpdate = MyEntityUpdateEnum.EACH_FRAME; 49 | } 50 | 51 | public override void UpdateBeforeSimulation() 52 | { 53 | try 54 | { 55 | bool shouldSpin = block.IsWorking; // if block is functional and enabled and powered. 56 | 57 | if(!shouldSpin && Math.Abs(targetSpeedMultiplier) < 0.00001f) 58 | return; 59 | 60 | if(shouldSpin && targetSpeedMultiplier < 1) 61 | { 62 | targetSpeedMultiplier = Math.Min(targetSpeedMultiplier + ACCELERATE_PERCENT_PER_TICK, 1); 63 | } 64 | else if(!shouldSpin && targetSpeedMultiplier > 0) 65 | { 66 | targetSpeedMultiplier = Math.Max(targetSpeedMultiplier - DEACCELERATE_PERCENT_PER_TICK, 0); 67 | } 68 | 69 | var camPos = MyAPIGateway.Session.Camera.WorldMatrix.Translation; // local machine camera position 70 | 71 | if(Vector3D.DistanceSquared(camPos, block.GetPosition()) > MAX_DISTANCE_SQ) 72 | return; 73 | 74 | MyEntitySubpart subpart; 75 | if(Entity.TryGetSubpart(SUBPART_NAME, out subpart)) // subpart does not exist when block is in build stage 76 | { 77 | if(subpartFirstFind) // first time the subpart was found 78 | { 79 | subpartFirstFind = false; 80 | subpartLocalMatrix = subpart.PositionComp.LocalMatrixRef; 81 | } 82 | 83 | if(targetSpeedMultiplier > 0) 84 | { 85 | subpartLocalMatrix = Matrix.CreateFromAxisAngle(ROTATION_AXIS, MathHelper.ToRadians(targetSpeedMultiplier * DEGREES_PER_TICK)) * subpartLocalMatrix; 86 | subpartLocalMatrix = Matrix.Normalize(subpartLocalMatrix); // normalize to avoid any rotation inaccuracies over time resulting in weird scaling 87 | } 88 | 89 | subpart.PositionComp.SetLocalMatrix(ref subpartLocalMatrix); 90 | } 91 | } 92 | catch(Exception e) 93 | { 94 | AddToLog(e); 95 | } 96 | } 97 | 98 | private void AddToLog(Exception e) 99 | { 100 | MyLog.Default.WriteLineAndConsole($"ERROR {GetType().FullName}: {e.ToString()}"); 101 | 102 | if(MyAPIGateway.Session?.Player != null) 103 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {GetType().FullName}: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_SurvivalKitDisableRespawn.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Sandbox.Common.ObjectBuilders; 3 | using Sandbox.Game.EntityComponents; 4 | using Sandbox.ModAPI; 5 | using VRage.Game.Components; 6 | using VRage.ModAPI; 7 | using VRage.ObjectBuilders; 8 | 9 | namespace Digi.Examples 10 | { 11 | // this applies to all survival kits. 12 | // if you want to limit it to specific subtypes, after false you can add a comma and list the subtypes separated by comma, with quotes. 13 | // example: 14 | //[MyEntityComponentDescriptor(typeof(MyObjectBuilder_SurvivalKit), false, "subtype here", "and more...", "etc...")] 15 | // you can also have new lines after each comma if you have too many subtypes to comfortably fit on one line 16 | 17 | 18 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_SurvivalKit), false)] 19 | public class Example_SurvivalKitDisableRespawn : MyGameLogicComponent 20 | { 21 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 22 | { 23 | NeedsUpdate |= MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 24 | } 25 | 26 | public override void UpdateOnceBeforeFrame() 27 | { 28 | Entity.Components.Remove(); 29 | 30 | IMyTerminalBlock tb = Entity as IMyTerminalBlock; 31 | if(tb != null) 32 | { 33 | tb.AppendingCustomInfo += AppendCustomInfo; 34 | tb.RefreshCustomInfo(); 35 | } 36 | } 37 | 38 | void AppendCustomInfo(IMyTerminalBlock block, StringBuilder info) 39 | { 40 | info.AppendLine("NOTE: Cannot respawn on this block."); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_TSS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Game.GameSystems.TextSurfaceScripts; 3 | using Sandbox.ModAPI; 4 | using VRage.Game; 5 | using VRage.Game.GUI.TextPanel; 6 | using VRage.Game.ModAPI; 7 | using VRage.ModAPI; 8 | using VRage.Utils; 9 | using VRageMath; 10 | 11 | namespace Digi.Examples 12 | { 13 | // Text Surface Scripts (TSS) can be selected in any LCD's scripts list. 14 | // These are meant as fast no-sync (sprites are not sent over network) display scripts, and the Run() method only executes player-side (no DS). 15 | // You can still use a session comp and access it through this to use for caches/shared data/etc. 16 | // 17 | // The display name has localization support aswell, same as a block's DisplayName in SBC. 18 | [MyTextSurfaceScript("InternalNameHere", "Display Name Here")] 19 | public class SomeClassName : MyTSSCommon 20 | { 21 | public override ScriptUpdate NeedsUpdate => ScriptUpdate.Update10; // frequency that Run() is called. 22 | 23 | private readonly IMyTerminalBlock TerminalBlock; 24 | 25 | public SomeClassName(IMyTextSurface surface, IMyCubeBlock block, Vector2 size) : base(surface, block, size) 26 | { 27 | TerminalBlock = (IMyTerminalBlock)block; // internal stored m_block is the ingame interface which has no events, so can't unhook later on, therefore this field is required. 28 | TerminalBlock.OnMarkForClose += BlockMarkedForClose; // required if you're gonna make use of Dispose() as it won't get called when block is removed or grid is cut/unloaded. 29 | 30 | // Called when script is created. 31 | // This class is instanced per LCD that uses it, which means the same block can have multiple instances of this script aswell (e.g. a cockpit with all its screens set to use this script). 32 | } 33 | 34 | public override void Dispose() 35 | { 36 | base.Dispose(); // do not remove 37 | TerminalBlock.OnMarkForClose -= BlockMarkedForClose; 38 | 39 | // Called when script is removed for any reason, so that you can clean up stuff if you need to. 40 | } 41 | 42 | void BlockMarkedForClose(IMyEntity ent) 43 | { 44 | Dispose(); 45 | } 46 | 47 | // gets called at the rate specified by NeedsUpdate 48 | // it can't run every tick because the LCD is capped at 6fps anyway. 49 | public override void Run() 50 | { 51 | try 52 | { 53 | base.Run(); // do not remove 54 | 55 | // hold L key to see how the error is shown, remove this after you've played around with it =) 56 | if(MyAPIGateway.Input.IsKeyPress(VRage.Input.MyKeys.L)) 57 | throw new Exception("Oh noes an error :}"); 58 | 59 | Draw(); 60 | } 61 | catch(Exception e) // no reason to crash the entire game just for an LCD script, but do NOT ignore them either, nag user so they report it :} 62 | { 63 | DrawError(e); 64 | } 65 | } 66 | 67 | void Draw() // this is a custom method which is called in Run(). 68 | { 69 | Vector2 screenSize = Surface.SurfaceSize; 70 | Vector2 screenCorner = (Surface.TextureSize - screenSize) * 0.5f; 71 | 72 | var frame = Surface.DrawFrame(); 73 | 74 | // Drawing sprites works exactly like in PB API. 75 | // Therefore this guide applies: https://github.com/malware-dev/MDK-SE/wiki/Text-Panels-and-Drawing-Sprites 76 | 77 | // there are also some helper methods from the MyTSSCommon that this extends. 78 | // like: AddBackground(frame, Surface.ScriptBackgroundColor); - a grid-textured background 79 | 80 | // the colors in the terminal are Surface.ScriptBackgroundColor and Surface.ScriptForegroundColor, the other ones without Script in name are for text/image mode. 81 | 82 | var text = MySprite.CreateText("Hi!", "Monospace", Surface.ScriptForegroundColor, 1f, TextAlignment.LEFT); 83 | text.Position = screenCorner + new Vector2(16, 16); // 16px from topleft corner of the visible surface 84 | frame.Add(text); 85 | 86 | // add more sprites and stuff 87 | 88 | frame.Dispose(); // send sprites to the screen 89 | } 90 | 91 | void DrawError(Exception e) 92 | { 93 | MyLog.Default.WriteLineAndConsole($"{e.Message}\n{e.StackTrace}"); 94 | 95 | try // first try printing the error on the LCD 96 | { 97 | Vector2 screenSize = Surface.SurfaceSize; 98 | Vector2 screenCorner = (Surface.TextureSize - screenSize) * 0.5f; 99 | 100 | var frame = Surface.DrawFrame(); 101 | 102 | var bg = new MySprite(SpriteType.TEXTURE, "SquareSimple", null, null, Color.Black); 103 | frame.Add(bg); 104 | 105 | var text = MySprite.CreateText($"ERROR: {e.Message}\n{e.StackTrace}\n\nPlease send screenshot of this to mod author.\n{MyAPIGateway.Utilities.GamePaths.ModScopeName}", "White", Color.Red, 0.7f, TextAlignment.LEFT); 106 | text.Position = screenCorner + new Vector2(16, 16); 107 | frame.Add(text); 108 | 109 | frame.Dispose(); 110 | } 111 | catch(Exception e2) 112 | { 113 | MyLog.Default.WriteLineAndConsole($"Also failed to draw error on screen: {e2.Message}\n{e2.StackTrace}"); 114 | 115 | if(MyAPIGateway.Session?.Player != null) 116 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {GetType().FullName}: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 117 | } 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_TargetDummyFixSlider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Game.Entities; 3 | using Sandbox.ModAPI; 4 | using Sandbox.ModAPI.Interfaces; 5 | using VRage.Collections; 6 | using VRage.Game; 7 | using VRage.Game.Components; 8 | using VRage.Game.Entity; 9 | using VRage.Utils; 10 | using VRageMath; 11 | 12 | namespace Digi.Examples 13 | { 14 | // fixes Delay slider in target dummy block by limiting it on spawn between the actual limits (defined by sbc's Min/MaxRegenerationTimeInS) 15 | 16 | // HACK: having to use session comp to find spawning target dummy because MyObjectBuilder_TargetDummyBlock is not whitelisted. 17 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 18 | public class Example_TargetDummyFixSlider : MySessionComponentBase 19 | { 20 | MyConcurrentList Blocks = new MyConcurrentList(); 21 | 22 | public override void LoadData() 23 | { 24 | // property changes synchronize so not necessary MP-client-side (also would unnecessarily spam) 25 | if(MyAPIGateway.Session.IsServer) 26 | { 27 | SetUpdateOrder(MyUpdateOrder.AfterSimulation); 28 | MyEntities.OnEntityCreate += MyEntities_OnEntityCreate; 29 | } 30 | } 31 | 32 | protected override void UnloadData() 33 | { 34 | MyEntities.OnEntityCreate -= MyEntities_OnEntityCreate; 35 | } 36 | 37 | void MyEntities_OnEntityCreate(MyEntity entity) 38 | { 39 | try 40 | { 41 | IMyTargetDummyBlock targetDummy = entity as IMyTargetDummyBlock; 42 | if(targetDummy != null) 43 | { 44 | Blocks.Add(targetDummy); 45 | } 46 | } 47 | catch(Exception e) 48 | { 49 | LogError(e); 50 | } 51 | } 52 | 53 | public override void UpdateAfterSimulation() 54 | { 55 | try 56 | { 57 | if(Blocks.Count > 0) 58 | { 59 | foreach(IMyTargetDummyBlock block in Blocks) 60 | { 61 | ITerminalProperty propDelay = block.GetProperty("Delay")?.AsFloat(); 62 | if(propDelay != null) 63 | { 64 | float delay = propDelay.GetValue(block); 65 | float fixedDelay = MathHelper.Clamp(delay, propDelay.GetMinimum(block), propDelay.GetMaximum(block)); 66 | 67 | if(Math.Abs(delay - fixedDelay) > 0.0001f) 68 | { 69 | propDelay.SetValue(block, fixedDelay); 70 | } 71 | } 72 | } 73 | 74 | Blocks.Clear(); 75 | } 76 | } 77 | catch(Exception e) 78 | { 79 | LogError(e); 80 | Blocks.Clear(); 81 | } 82 | } 83 | 84 | void LogError(Exception e) 85 | { 86 | MyLog.Default.WriteLineAndConsole($"{ModContext.ModName}: {e.Message}\n{e.StackTrace}"); 87 | if(MyAPIGateway.Session?.Player != null) 88 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {ModContext.ModName}: error at {GetType().Name} ]", 10000, MyFontEnum.Red); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_UpgradeModuleSupport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Common.ObjectBuilders; 3 | using Sandbox.Game.Entities; 4 | using Sandbox.Game.EntityComponents; 5 | using Sandbox.ModAPI; 6 | using VRage; 7 | using VRage.Game; 8 | using VRage.Game.Components; 9 | using VRage.Game.Entity; 10 | using VRage.Game.ModAPI; 11 | using VRage.ModAPI; 12 | using VRage.ObjectBuilders; 13 | using VRage.Utils; 14 | using VRageMath; 15 | 16 | namespace Digi.Examples 17 | { 18 | // This example shows how to read upgrade module info by making a reactor generate ice if it has a certain upgrade attached. 19 | // 20 | // The script is only required on the host block, the upgrade module detection and math is built-in to the game. 21 | // 22 | // You do however need to have an upgrade module SBC with your required values. 23 | // Also the model of both this block and the module requires upgrade empties to allow the connection to happen on keen's code. 24 | 25 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_Reactor), false, "YourSubtypeHere", "More if needed")] 26 | public class Example_UpgradeModuleSupport : MyGameLogicComponent 27 | { 28 | const string IcePerSecondKey = "IcePerSecond"; 29 | 30 | IMyTerminalBlock Block; 31 | int LastRunTick = -1; 32 | 33 | static readonly MyObjectBuilder_PhysicalObject ItemOB = new MyObjectBuilder_Ore() 34 | { 35 | SubtypeName = "Ice", 36 | }; 37 | 38 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 39 | { 40 | Block = (IMyTerminalBlock)Entity; 41 | 42 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; // enables UpdateOnceBeforeFrame() to be called once in the next update 43 | 44 | // can do this Add() multiple times if you need multiple upgrade values 45 | 46 | // the 0 is the starting value, depends on how you want the upgrade math to work. 47 | // for example you could default to 1 and modules can multiply by 1.2 which will get way stronger with more modules 48 | Block.UpgradeValues.Add(IcePerSecondKey, 0f); 49 | 50 | // the key name is what you give to the upgrade module's SBC to affect 51 | /* 52 | 53 | 54 | IcePerSecond 55 | Additive 56 | 0.2 57 | 58 | 59 | */ 60 | // adding this tag to upgrade module and using it with this script would result in 0.2 ice per second, per upgrade module attached. 61 | } 62 | 63 | public override void UpdateOnceBeforeFrame() 64 | { 65 | if(Block?.CubeGrid?.Physics == null) 66 | return; // ignore ghost grids like projections or in-paste ones 67 | 68 | NeedsUpdate = MyEntityUpdateEnum.EACH_100TH_FRAME; // enables UpdateAfterSimulation100() to get called almost every 100 ticks, not guaranteed 100. 69 | 70 | LastRunTick = MyAPIGateway.Session.GameplayFrameCounter; // used by the item adding, can remove if you removed that part 71 | 72 | // this is only if you want to cache the value, not really necessary. 73 | /* 74 | Block.OnUpgradeValuesChanged += UpgradesChanged; 75 | UpgradesChanged(); // required to read the value on spawn as it likely doesn't trigger the event this late. 76 | */ 77 | } 78 | 79 | /* 80 | void UpgradesChanged() 81 | { 82 | // read Block.UpgradeValues[IcePerSecondKey] 83 | } 84 | */ 85 | 86 | public override void UpdateAfterSimulation100() 87 | { 88 | // this has the final value already modified by upgrade modules, you just read it 89 | float icePerSecond = Block.UpgradeValues[IcePerSecondKey]; 90 | 91 | MyAPIGateway.Utilities.ShowNotification($"{Block.CustomName}: icePerSecond={icePerSecond.ToString("N2")}", 16 * 100, "Debug"); 92 | 93 | // and this is just some random usage example, by adding an item to inventory 94 | // inventory stuff should only be done serverside (and can be optimized by choosing NeedsUpdate to be set only serverside too) 95 | if(MyAPIGateway.Session.IsServer && icePerSecond > 0) 96 | { 97 | int tick = MyAPIGateway.Session.GameplayFrameCounter; 98 | float deltaTime = (tick - LastRunTick) / MyEngineConstants.UPDATE_STEPS_PER_SECOND; 99 | LastRunTick = tick; 100 | 101 | float addAmount = icePerSecond * deltaTime; 102 | Block.GetInventory().AddItems((MyFixedPoint)addAmount, ItemOB); 103 | } 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/Example_WriteToDetailedInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Sandbox.Common.ObjectBuilders; 4 | using Sandbox.ModAPI; 5 | using VRage.Game; 6 | using VRage.Game.Components; 7 | using VRage.Game.ModAPI; 8 | using VRage.ModAPI; 9 | using VRage.ObjectBuilders; 10 | using VRage.Utils; 11 | 12 | namespace Digi.Examples 13 | { 14 | // for info on the component, refer to: https://github.com/THDigi/SE-ModScript-Examples/blob/master/Data/Scripts/Examples/BasicExample_GameLogicAndSession/GameLogic.cs 15 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_Reactor), false)] 16 | public class Example_WriteToDetailedInfo : MyGameLogicComponent 17 | { 18 | IMyTerminalBlock Block; 19 | 20 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 21 | { 22 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 23 | } 24 | 25 | public override void UpdateOnceBeforeFrame() 26 | { 27 | Block = (IMyTerminalBlock)Entity; 28 | 29 | if(Block.CubeGrid?.Physics == null) 30 | return; // ignore ghost grids 31 | 32 | // this event gets invoked (from all mods) by calling block.RefreshCustomInfo() 33 | // which does not automatically refresh the block's detailedinfo, that one depends on the block type. 34 | // in a recent major we got block.SetDetailedInfoDirty() which does just that! 35 | Block.AppendingCustomInfo += AppendingCustomInfo; 36 | 37 | NeedsUpdate = MyEntityUpdateEnum.EACH_100TH_FRAME; 38 | } 39 | 40 | void AppendingCustomInfo(IMyTerminalBlock block, StringBuilder sb) 41 | { 42 | try // only for non-critical code 43 | { 44 | // NOTE: don't Clear() the StringBuilder, it's the same instance given to all mods. 45 | 46 | sb.Append("ETA: ").Append(60 - DateTime.Now.Second).Append("\n"); 47 | } 48 | catch(Exception e) 49 | { 50 | LogError(e); 51 | } 52 | } 53 | 54 | public override void UpdateAfterSimulation100() 55 | { 56 | try // only for non-critical code 57 | { 58 | // ideally you want to refresh this only when necessary but this is a good compromise to only refresh it if player is in the terminal. 59 | // this check still doesn't mean you're looking at even the same grid's terminal as this block, for that there's other ways to check it if needed. 60 | if(MyAPIGateway.Gui.GetCurrentScreen == MyTerminalPageEnum.ControlPanel) 61 | { 62 | Block.RefreshCustomInfo(); 63 | Block.SetDetailedInfoDirty(); 64 | } 65 | } 66 | catch(Exception e) 67 | { 68 | LogError(e); 69 | } 70 | } 71 | 72 | void LogError(Exception e) 73 | { 74 | MyLog.Default.WriteLineAndConsole($"ERROR on {GetType().FullName}: {e}"); 75 | 76 | if(MyAPIGateway.Session?.Player != null) 77 | MyAPIGateway.Utilities.ShowNotification($"[ERROR on {GetType().FullName}: Send SpaceEngineers.Log to mod author]", 10000, MyFontEnum.Red); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/Fast Solo DS Testing/copy to DS and client.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem Testing mods in DS by yourself can be done without the need to re-publish every time. 3 | rem You can simply update the files that are on your machine! 4 | rem This will only work for you, anyone else joining the server will of course download the mod from the workshop. 5 | 6 | rem To use: 7 | rem 1. Copy this .bat file in the ROOT folder of your local mod (e.g. %appdata%/SpaceEngineers/Mods/YourLocalMod/) 8 | 9 | rem 2. Edit this variable if applicable (do not add quotes or end with backslash). 10 | set STEAM_PATH=C:\Program Files (x86)\Steam 11 | 12 | rem 3. Edit this with your mod's workshop id. 13 | set WORKSHOP_ID=NumberHere 14 | 15 | rem Now you can run it every time you want to update the mod on DS and client. 16 | 17 | 18 | 19 | rem Don't edit the below unless you really need different paths. 20 | rem NOTE: don't add quotes and don't end with a backslash! 21 | 22 | set CLIENT_PATH=%STEAM_PATH%\steamapps\workshop\content\244850\%WORKSHOP_ID% 23 | set DS_PATH=%APPDATA%\SpaceEngineersDedicated\content\244850\%WORKSHOP_ID% 24 | 25 | rmdir "%CLIENT_PATH%" /S /Q 26 | rmdir "%DS_PATH%" /S /Q 27 | 28 | robocopy.exe .\ "%DS_PATH%" *.* /S /xd .git bin obj .vs ignored /xf *.lnk *.git* *.bat *.zip *.7z *.blend* *.md *.log *.sln *.csproj *.csproj.user *.ruleset modinfo.sbmi 29 | robocopy.exe "%DS_PATH%" "%CLIENT_PATH%" *.* /S 30 | 31 | pause -------------------------------------------------------------------------------- /Data/Scripts/Examples/MissileParticleReplacer/MissileParticleReplacer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Sandbox.Common.ObjectBuilders; 4 | using Sandbox.Engine.Physics; 5 | using Sandbox.Game.Entities; 6 | using Sandbox.ModAPI; 7 | using VRage.Game; 8 | using VRage.Game.Components; 9 | using VRage.Game.Entity; 10 | using VRage.Game.ModAPI; 11 | using VRage.ObjectBuilders; 12 | using VRage.Utils; 13 | using VRageMath; 14 | using CollisionLayers = Sandbox.Engine.Physics.MyPhysics.CollisionLayers; 15 | 16 | namespace Digi.Examples 17 | { 18 | // NOTE: No longer needed! Ammo definition has now. 19 | // Left this code up for reference. 20 | 21 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 22 | public class MissileParticleReplacer : MySessionComponentBase 23 | { 24 | readonly Dictionary AssignedParticles = new Dictionary() 25 | { 26 | // editable, you can also add more lines similar to this: 27 | // ["MagazineSubtypeIdHere"] = "ParticleSubtypeIdHere", 28 | ["Missile200mm"] = "Tree_Drill", 29 | }; 30 | 31 | // this changes the above dictionary to use model path name suffix as key. 32 | // if you have a unique model path then this is ideal for performance. 33 | readonly bool DetectBasedOnModelSuffix = false; 34 | 35 | 36 | MyPhysicsComponentBase TemporaryPhysics; 37 | readonly Dictionary MissileEffects = new Dictionary(); 38 | readonly MyObjectBuilderType MissileTypeId = typeof(MyObjectBuilder_Missile); 39 | 40 | public override void LoadData() 41 | { 42 | // missile instances are re-used so they'll only be added/removed from scene. 43 | MyEntities.OnEntityAdd += EntityAddedToScene; 44 | MyEntities.OnEntityRemove += EntityRemovedFromScene; 45 | 46 | // create a physics object to give to the missile before getting the OB 47 | var tempEnt = new MyEntity(); 48 | MyPhysicsHelper.InitSpherePhysics(tempEnt, MyMaterialType.MISSILE, Vector3.Zero, 0.01f, 0.01f, 0f, 0f, CollisionLayers.NoCollisionLayer, RigidBodyFlag.RBF_DEBRIS); 49 | TemporaryPhysics = tempEnt.Physics; 50 | tempEnt.Physics = null; // no need to keep the entity in memory anymore 51 | } 52 | 53 | protected override void UnloadData() 54 | { 55 | MyEntities.OnEntityAdd -= EntityAddedToScene; 56 | MyEntities.OnEntityRemove -= EntityRemovedFromScene; 57 | } 58 | 59 | private void EntityAddedToScene(MyEntity ent) 60 | { 61 | try 62 | { 63 | if(!ent.DefinitionId.HasValue || ent.DefinitionId.Value.TypeId != MissileTypeId) 64 | return; 65 | 66 | if(DetectBasedOnModelSuffix) 67 | { 68 | string modelName = ((IMyModel)ent.Model).AssetName; 69 | 70 | foreach(var kv in AssignedParticles) 71 | { 72 | if(modelName.EndsWith(kv.Key)) 73 | { 74 | ReplaceEffectForEntity(ent, kv.Value); 75 | break; 76 | } 77 | } 78 | } 79 | else 80 | { 81 | MyObjectBuilder_Missile ob; 82 | if(ent.Physics == null) 83 | { 84 | // HACK: missiles don't have Physics on MP clients and GetObjectBuilder() accesses the Physics field, therefore it needs to have that field valid... 85 | ent.Physics = TemporaryPhysics; 86 | ob = (MyObjectBuilder_Missile)ent.GetObjectBuilder(); 87 | ent.Physics = null; 88 | } 89 | else 90 | { 91 | ob = (MyObjectBuilder_Missile)ent.GetObjectBuilder(); 92 | } 93 | 94 | string particleName; 95 | if(AssignedParticles.TryGetValue(ob.AmmoMagazineId.SubtypeName, out particleName)) 96 | { 97 | ReplaceEffectForEntity(ent, particleName); 98 | } 99 | } 100 | } 101 | catch(Exception e) 102 | { 103 | Error(this, e); 104 | } 105 | } 106 | 107 | private void ReplaceEffectForEntity(MyEntity ent, string particleName) 108 | { 109 | Vector3D missilePos = ent.WorldMatrix.Translation; 110 | 111 | // HACK: find the right particle by getting closest one to the missile, with the expected name 112 | MyParticleEffect closestEffect = null; 113 | double closestDistanceSq = double.MaxValue; 114 | 115 | foreach(var effect in MyParticlesManager.Effects) 116 | { 117 | if(effect.GetName() != "Smoke_Missile") 118 | continue; 119 | 120 | double distSq = Vector3D.DistanceSquared(effect.WorldMatrix.Translation, missilePos); 121 | if(distSq < closestDistanceSq) 122 | { 123 | closestDistanceSq = distSq; 124 | closestEffect = effect; 125 | } 126 | } 127 | 128 | if(closestEffect != null) 129 | { 130 | MyParticlesManager.RemoveParticleEffect(closestEffect); 131 | } 132 | 133 | if(!string.IsNullOrEmpty(particleName)) 134 | { 135 | // spawn new particle effect 136 | Vector3D worldPos = ent.WorldMatrix.Translation; 137 | MyParticleEffect newEffect; 138 | if(MyParticlesManager.TryCreateParticleEffect(particleName, ref MatrixD.Identity, ref worldPos, ent.Render.GetRenderObjectID(), out newEffect)) 139 | { 140 | // track missiles to stop their particle effects when removed from scene 141 | MissileEffects.Add(ent.EntityId, newEffect); 142 | } 143 | else 144 | { 145 | Error(this, $"Failed to spawn particle effect called: {particleName}"); 146 | } 147 | } 148 | } 149 | 150 | private void EntityRemovedFromScene(MyEntity ent) 151 | { 152 | try 153 | { 154 | if(!ent.DefinitionId.HasValue || ent.DefinitionId.Value.TypeId != MissileTypeId) 155 | return; 156 | 157 | MyParticleEffect effect; 158 | if(MissileEffects.TryGetValue(ent.EntityId, out effect)) 159 | { 160 | effect.Stop(true); 161 | MissileEffects.Remove(ent.EntityId); 162 | } 163 | } 164 | catch(Exception e) 165 | { 166 | Error(this, e); 167 | } 168 | } 169 | 170 | public void Error(object caller, Exception e) 171 | { 172 | MyLog.Default.WriteLineAndConsole($"ERROR {caller.GetType().FullName}: {e.Message}\n{e.StackTrace}"); 173 | 174 | if(MyAPIGateway.Session?.Player != null) 175 | MyAPIGateway.Utilities.ShowNotification($"[ERROR in {ModContext.ModName}, Send SpaceEngineers.Log to mod author", 10000, MyFontEnum.Red); 176 | } 177 | 178 | public void Error(object caller, string message) 179 | { 180 | MyLog.Default.WriteLineAndConsole($"ERROR {caller.GetType().FullName}: {message}"); 181 | 182 | if(MyAPIGateway.Session?.Player != null) 183 | MyAPIGateway.Utilities.ShowNotification($"[ERROR in {ModContext.ModName}, Send SpaceEngineers.Log to mod author", 10000, MyFontEnum.Red); 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/MissileTurretProjectileSoundFix/MissileTurretProjectileSoundFix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.Common.ObjectBuilders; 3 | using Sandbox.Definitions; 4 | using Sandbox.Game.Entities; 5 | using Sandbox.Game.Weapons; 6 | using Sandbox.ModAPI; 7 | using VRage.Game; 8 | using VRage.Game.Components; 9 | using VRage.Game.Entity; 10 | using VRage.Game.ModAPI; 11 | using VRage.ModAPI; 12 | using VRage.ObjectBuilders; 13 | using VRage.Utils; 14 | 15 | namespace Digi.Examples 16 | { 17 | // add your subtypes > 18 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_LargeMissileTurret), false, "YourTurretSubtypeId", "CanAddMoreIfNeeded", "etc...")] 19 | public class MissileTurretSoundFix : MyGameLogicComponent 20 | { 21 | private IMyFunctionalBlock block; 22 | private IMyGunObject gun; 23 | private MyEntity3DSoundEmitter soundEmitter; 24 | private MySoundPair soundPair; 25 | private long lastShotTime; 26 | 27 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 28 | { 29 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 30 | } 31 | 32 | public override void UpdateOnceBeforeFrame() 33 | { 34 | try 35 | { 36 | if(MyAPIGateway.Session.IsServer && MyAPIGateway.Utilities.IsDedicated) 37 | return; // DS doesn't need to play sounds 38 | 39 | block = (IMyFunctionalBlock)Entity; 40 | 41 | if(block?.CubeGrid?.Physics == null) 42 | return; // ignore projected grids 43 | 44 | gun = (IMyGunObject)Entity; 45 | 46 | if(!gun.GunBase.HasProjectileAmmoDefined) 47 | return; // ignore missile turrets that don't have projectile ammo 48 | 49 | var def = (MyWeaponBlockDefinition)block.SlimBlock.BlockDefinition; 50 | var weaponDef = MyDefinitionManager.Static.GetWeaponDefinition(def.WeaponDefinitionId); 51 | soundPair = weaponDef.WeaponAmmoDatas[0].ShootSound; 52 | 53 | lastShotTime = gun.GunBase.LastShootTime.Ticks; 54 | 55 | soundEmitter = new MyEntity3DSoundEmitter((MyEntity)Entity); // create a sound emitter following this block entity 56 | 57 | block.IsWorkingChanged += BlockWorkingChanged; 58 | BlockWorkingChanged(block); 59 | } 60 | catch(Exception e) 61 | { 62 | LogError(e); 63 | } 64 | } 65 | 66 | public override void Close() 67 | { 68 | soundEmitter?.StopSound(true, true); 69 | soundEmitter = null; 70 | } 71 | 72 | private void BlockWorkingChanged(IMyCubeBlock block) 73 | { 74 | if(block.IsWorking) 75 | NeedsUpdate |= MyEntityUpdateEnum.EACH_FRAME; 76 | else 77 | NeedsUpdate &= ~MyEntityUpdateEnum.EACH_FRAME; 78 | } 79 | 80 | public override void UpdateBeforeSimulation() 81 | { 82 | try 83 | { 84 | var shotTime = gun.GunBase.LastShootTime.Ticks; 85 | 86 | if(shotTime > lastShotTime) 87 | { 88 | lastShotTime = shotTime; 89 | 90 | if(gun.GunBase.IsAmmoProjectile) 91 | { 92 | soundEmitter.PlaySound(soundPair); 93 | 94 | //MyAPIGateway.Utilities.ShowNotification("[DEBUG] shot projectile", 500); 95 | } 96 | else 97 | { 98 | //MyAPIGateway.Utilities.ShowNotification("[DEBUG] shot missile", 500); 99 | } 100 | } 101 | } 102 | catch(Exception e) 103 | { 104 | LogError(e); 105 | } 106 | } 107 | 108 | private void LogError(Exception e) 109 | { 110 | MyLog.Default.WriteLineAndConsole($"{e.Message}\n{e.StackTrace}"); 111 | 112 | if(MyAPIGateway.Session?.Player != null) 113 | MyAPIGateway.Utilities.ShowNotification($"[ ERROR: {GetType().FullName}: {e.Message} | Send SpaceEngineers.Log to mod author ]", 10000, MyFontEnum.Red); 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/ParticleAtDummy/Conditions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using Sandbox.Common.ObjectBuilders; 5 | using SpaceEngineers.ObjectBuilders.ObjectBuilders; 6 | using Sandbox.Definitions; 7 | using Sandbox.Game; 8 | using Sandbox.Game.Entities; 9 | using Sandbox.Game.EntityComponents; 10 | using Sandbox.ModAPI; 11 | using Sandbox.ModAPI.Interfaces; 12 | using Sandbox.ModAPI.Interfaces.Terminal; 13 | using SpaceEngineers.Game.ModAPI; 14 | using VRage.Game; 15 | using VRage.Game.Entity; 16 | using VRage.Game.Components; 17 | using VRage.Game.ModAPI; 18 | using VRage.Game.ObjectBuilders.Definitions; 19 | using VRage.Input; 20 | using VRage.ModAPI; 21 | using VRage.ObjectBuilders; 22 | using VRage.Utils; 23 | using VRageMath; 24 | 25 | namespace Digi.Examples 26 | { 27 | // this is where the conditions are registered with the gamelogic 28 | public abstract partial class StandardParticleGamelogic 29 | { 30 | ParticleBase CreateParticleHolder(IMyEntity parent, IMyModelDummy dummy, string particleSubtypeId, string condition = null) 31 | { 32 | switch(condition) 33 | { 34 | case "working": // only shows particle if block is built above red line and if it has on/off then if it's on as well. 35 | return new ParticleOnWorking(this, parent, particleSubtypeId, dummy.Matrix); 36 | 37 | case "producing": // only show particle if block is a refinery/assembler/survivalkit/gasgenerator and it's currently producing 38 | return new ParticleOnProducing(this, parent, particleSubtypeId, dummy.Matrix); 39 | 40 | // add more here if you make more custom condition classes 41 | } 42 | 43 | return new ParticleBase(this, parent, particleSubtypeId, dummy.Matrix); 44 | } 45 | } 46 | 47 | // just a basic particle container that holds it until model changes, nothing to edit here. 48 | public class ParticleBase 49 | { 50 | public readonly IMyEntity Parent; 51 | public readonly StandardParticleGamelogic GameLogic; 52 | public readonly string SubtypeId; 53 | 54 | public MatrixD LocalMatrix; 55 | public MyParticleEffect Effect; 56 | 57 | public ParticleBase(StandardParticleGamelogic gamelogic, IMyEntity parent, string subtypeId, MatrixD localMatrix) 58 | { 59 | GameLogic = gamelogic; 60 | Parent = parent; 61 | SubtypeId = subtypeId; 62 | LocalMatrix = localMatrix; 63 | 64 | Effect = SpawnParticle(); 65 | if(Effect == null) 66 | throw new Exception($"Couldn't spawn particle: {subtypeId}"); 67 | } 68 | 69 | public virtual void Close() 70 | { 71 | if(Effect != null) 72 | MyParticlesManager.RemoveParticleEffect(Effect); 73 | } 74 | 75 | protected virtual MyParticleEffect SpawnParticle() 76 | { 77 | MyParticleEffect effect; 78 | Vector3D worldPos = Parent.GetPosition(); 79 | uint parentId = Parent.Render.GetRenderObjectID(); 80 | if(!MyParticlesManager.TryCreateParticleEffect(SubtypeId, ref LocalMatrix, ref worldPos, parentId, out effect)) 81 | return null; 82 | 83 | return effect; 84 | } 85 | } 86 | 87 | // example particle with condition 88 | // you can clone+rename+change to have some custom conditions, just remember to add it to CreateParticleHolder() above. 89 | public class ParticleOnWorking : ParticleBase 90 | { 91 | public readonly IMyCubeBlock Block; 92 | 93 | public ParticleOnWorking(StandardParticleGamelogic gamelogic, IMyEntity parent, string subtypeId, MatrixD localMatrix) : base(gamelogic, parent, subtypeId, localMatrix) 94 | { 95 | Block = (IMyCubeBlock)gamelogic.Entity; 96 | Block.IsWorkingChanged += BlockWorkingChanged; 97 | BlockWorkingChanged(Block); 98 | } 99 | 100 | public override void Close() 101 | { 102 | base.Close(); 103 | Block.IsWorkingChanged -= BlockWorkingChanged; 104 | } 105 | 106 | void BlockWorkingChanged(IMyCubeBlock _) 107 | { 108 | bool currentState = Effect != null; 109 | bool targetState = Block.IsWorking; 110 | 111 | if(targetState != currentState) 112 | { 113 | if(targetState) 114 | { 115 | Effect = SpawnParticle(); 116 | } 117 | else 118 | { 119 | Effect.Stop(); 120 | Effect = null; 121 | } 122 | } 123 | } 124 | } 125 | 126 | public class ParticleOnProducing : ParticleBase, IUpdateable 127 | { 128 | public readonly IMyProductionBlock Production; 129 | public readonly IMyGasGenerator GasGenerator; 130 | 131 | public ParticleOnProducing(StandardParticleGamelogic gamelogic, IMyEntity parent, string subtypeId, MatrixD localMatrix) : base(gamelogic, parent, subtypeId, localMatrix) 132 | { 133 | Production = gamelogic.Entity as IMyProductionBlock; 134 | GasGenerator = gamelogic.Entity as IMyGasGenerator; 135 | 136 | if(Production == null && GasGenerator == null) 137 | throw new Exception($"{GetType().Name}: Unsupported block type, needs to be: assembler, survivalkit, refinery, gasgenerator"); 138 | } 139 | 140 | MyEntityUpdateEnum IUpdateable.Frequency { get; } = MyEntityUpdateEnum.EACH_100TH_FRAME; 141 | 142 | void IUpdateable.Update() 143 | { 144 | bool currentState = Effect != null; 145 | bool targetState = false; 146 | 147 | if(Production != null) 148 | { 149 | targetState = Production.IsProducing; 150 | } 151 | else if(GasGenerator != null) 152 | { 153 | MyResourceSourceComponent sourceComp = GasGenerator.Components.Get(); 154 | foreach(MyDefinitionId resourceType in sourceComp.ResourceTypes) 155 | { 156 | if(sourceComp.CurrentOutputByType(resourceType) > 0f) 157 | { 158 | targetState = true; 159 | break; 160 | } 161 | } 162 | } 163 | 164 | if(targetState != currentState) 165 | { 166 | if(targetState) 167 | { 168 | Effect = SpawnParticle(); 169 | } 170 | else 171 | { 172 | Effect.Stop(); 173 | Effect = null; 174 | } 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/ParticleAtDummy/Readme.txt: -------------------------------------------------------------------------------- 1 | Open Setup.cs and follow the instructions. 2 | 3 | Conditions.cs is for programming additional behaviors. 4 | 5 | Gamelogic.cs is the backbone of the entire system, no edits necessary there. -------------------------------------------------------------------------------- /Data/Scripts/Examples/ParticleAtDummy/Setup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ObjectBuilders.SafeZone; 4 | using Sandbox.Game.EntityComponents; 5 | using Sandbox.Common.ObjectBuilders; 6 | using SpaceEngineers.ObjectBuilders.ObjectBuilders; 7 | using Sandbox.ModAPI; 8 | using VRage.Game; 9 | using VRage.Game.Components; 10 | using VRage.Game.ModAPI; 11 | using VRage.ModAPI; 12 | using VRage.ObjectBuilders; 13 | using VRage.Utils; 14 | using VRageMath; 15 | 16 | namespace Digi.Examples 17 | { 18 | // A generic script to attach particles to dummies in models. 19 | 20 | // First you need a class like the example one below, one per block type. 21 | // If you need more types just duplicate the entire code chunk (or more advanced way is to make classes with attributes inheriting a common one) 22 | 23 | // The MyEntityComponentDescriptor line is what block type and subtype(s). 24 | // - for type use the and add MyObjectBuilder_ prefix. 25 | // - the subtypes can be removed entirely if you want it to affect all blocks of that type. 26 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_Reactor), false, "SpecificSubtype", "MoreIfNeeded", "etc...")] 27 | public class ParticleOnReactor : StandardParticleGamelogic 28 | { 29 | protected override void Setup() 30 | { 31 | Declare(dummy: "some_dummy_name", // The exact name of an in-model dummy, can be in subparts as well. 32 | // NOTE: do not declare the same dummy name multiple times, it will override. 33 | particle: "ExhaustFire", // Particle effect subtypeId, must exist and must be a loop. 34 | // The particle will be parented to the block or subpart at the dummy location and orientation. 35 | condition: "working" // Logic that determines behavior, see all options in Conditions.cs at the CreateParticleHolder()'s switch. 36 | ); 37 | 38 | // can repeat the above declaration for more particles on other dummies 39 | 40 | 41 | DebugMode = false; // turn on to enable debug chat messages for this block 42 | } 43 | } 44 | 45 | // a few more examples below: 46 | 47 | //[MyEntityComponentDescriptor(typeof(MyObjectBuilder_BatteryBlock), false)] 48 | //public class ParticleOnBattery : StandardParticleGamelogic 49 | //{ 50 | // then have the Setup() stuff here 51 | //} 52 | 53 | // the subtypes don't have to be in one line, after every comma you can add a new line, like so: 54 | 55 | //[MyEntityComponentDescriptor(typeof(MyObjectBuilder_Warhead), false, 56 | // "too", 57 | // "many", 58 | // "subtypes", 59 | // "for", 60 | // "one", 61 | // "line")] 62 | } 63 | -------------------------------------------------------------------------------- /Data/Scripts/Examples/TerminalControls/Adding/GyroLogic.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Common.ObjectBuilders; 2 | using Sandbox.ModAPI; 3 | using VRage.Game.Components; 4 | using VRage.ModAPI; 5 | using VRage.ObjectBuilders; 6 | 7 | namespace Digi.Examples.TerminalControls.Adding 8 | { 9 | // For more info about the gamelogic comp see https://github.com/THDigi/SE-ModScript-Examples/blob/master/Data/Scripts/Examples/BasicExample_GameLogicAndSession/GameLogic.cs 10 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_Gyro), false, "SmallBlockGyro")] 11 | public class GyroLogic : MyGameLogicComponent 12 | { 13 | IMyGyro Gyro; 14 | 15 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 16 | { 17 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 18 | } 19 | 20 | public override void UpdateOnceBeforeFrame() 21 | { 22 | GyroTerminalControls.DoOnce(ModContext); 23 | 24 | Gyro = (IMyGyro)Entity; 25 | if(Gyro.CubeGrid?.Physics == null) 26 | return; // ignore ghost/projected grids 27 | 28 | // stuff and things 29 | } 30 | 31 | // these are going to be set or retrieved by the terminal controls (as seen in the terminal control's Getter and Setter). 32 | 33 | // as mentioned in the other .cs file, the terminal stuff are only GUI. 34 | // if you want the values to persist over world reloads and be sent to clients you'll need to implement that yourself. 35 | // see: https://github.com/THDigi/SE-ModScript-Examples/wiki/Save-&-Sync-ways 36 | 37 | public bool Terminal_ExampleToggle 38 | { 39 | get 40 | { 41 | return Gyro?.Enabled ?? false; 42 | } 43 | set 44 | { 45 | if(Gyro != null) 46 | Gyro.Enabled = value; 47 | } 48 | } 49 | 50 | public float Terminal_ExampleFloat { get; set; } 51 | } 52 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/TerminalControls/Events/TerminalEventsExamples.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Sandbox.ModAPI; 3 | using Sandbox.ModAPI.Interfaces.Terminal; 4 | using VRage.Game; 5 | using VRage.Game.Components; 6 | using VRage.Utils; 7 | 8 | namespace Digi.Examples.TerminalControls.Events 9 | { 10 | [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] 11 | public class TerminalEventsExamples : MySessionComponentBase 12 | { 13 | public override void BeforeStart() 14 | { 15 | MyAPIGateway.TerminalControls.CustomControlGetter += CustomControlGetter; 16 | MyAPIGateway.TerminalControls.CustomActionGetter += CustomActionGetter; 17 | } 18 | 19 | protected override void UnloadData() 20 | { 21 | MyAPIGateway.TerminalControls.CustomControlGetter -= CustomControlGetter; 22 | MyAPIGateway.TerminalControls.CustomActionGetter -= CustomActionGetter; 23 | } 24 | 25 | IMyTerminalControlButton SampleButton; // for the add example below 26 | 27 | // gets called every time player has to see controls (or actions), and also gets called per selected block if multiple. 28 | // the list of controls (or actions) in the parameters is filled with the ones that are going to be shown for this block instance. 29 | // you can modify the list by moving things around, removing or even adding (see caution below). 30 | // however, you should NOT modify the instances from the list, they're still the same global controls and doing so will affect any block that uses those controls. 31 | void CustomControlGetter(IMyTerminalBlock block, List controls) 32 | { 33 | // Let's say we want to move CustomData button to be first... for ALL blocks! 34 | { 35 | int index = -1; 36 | for(int i = 0; i < controls.Count; i++) 37 | { 38 | IMyTerminalControl control = controls[i]; 39 | if(control.Id == "CustomData") 40 | { 41 | index = i; 42 | break; 43 | } 44 | } 45 | 46 | if(index != -1) 47 | { 48 | IMyTerminalControl control = controls[index]; 49 | controls.RemoveAt(index); 50 | controls.Insert(0, control); 51 | } 52 | } 53 | 54 | // Or let's say PB just has too many controls, get rid of everything except Edit, but only on smallgrid =) 55 | // Downside of this is that it's undetectable by other mods, like build vision for example. 56 | // Ideal way to remove controls is to append a condition to their Visible callback, see the /Hiding/ folder example. 57 | // Neither of these methods prevent mods or PBs from using them, unless permanently removed... and even then, the block interface likely has a way around. 58 | if(block is IMyProgrammableBlock && block.CubeGrid.GridSizeEnum == MyCubeSize.Small) 59 | { 60 | for(int i = controls.Count - 1; i >= 0; i--) 61 | { 62 | IMyTerminalControl control = controls[i]; 63 | if(control.Id != "Edit") 64 | { 65 | controls.RemoveAt(i); 66 | } 67 | } 68 | } 69 | 70 | // Can also add controls: 71 | // Downside or upside is that PB cannot see these. Mods can but only if they specifically hook this event and read the list. 72 | // CAUTION: Don't create new controls every time! Not just because allocation but mods could be storing the references (like buildinfo does for actions), effectively leading to memory leaks. 73 | if(block is IMyCockpit) 74 | { 75 | if(SampleButton == null) 76 | { 77 | SampleButton = MyAPIGateway.TerminalControls.CreateControl("YourMod_SomeUniqueId"); 78 | SampleButton.Action = (b) => 79 | { 80 | var cockpit = b as IMyCockpit; 81 | if(cockpit != null) 82 | { 83 | cockpit.RemovePilot(); 84 | // its xmldoc says "call on server" so it wouldn't work outside of singleplayer. 85 | // in cases like this you have to synchronize it manually by sending a packet to server with the block entityId and have it call the method. 86 | } 87 | }; 88 | SampleButton.Title = MyStringId.GetOrCompute("Clickbait!"); 89 | 90 | // Don't use MyAPIGateway.TerminalControls.AddControl() on controls meant to be added using this event. 91 | } 92 | 93 | controls.AddOrInsert(SampleButton, 4); 94 | } 95 | } 96 | 97 | // similar to the above really, can sort, remove and even add (with caution) 98 | void CustomActionGetter(IMyTerminalBlock block, List actions) 99 | { 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/TerminalControls/Hiding/HideControlsExample.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Sandbox.ModAPI; 3 | using Sandbox.ModAPI.Interfaces; 4 | using Sandbox.ModAPI.Interfaces.Terminal; 5 | using VRage; 6 | using VRage.Utils; 7 | 8 | namespace Digi.Examples.TerminalControls.Hiding 9 | { 10 | // In this example we're hiding the "Detect asteroids" terminal control and terminal action. Also bonus, enforcing it to stay false. 11 | // All this only on a specific sensor block to show doing it properly without breaking other mods trying to do the same. 12 | // 13 | // This is also compatible with multiple mods doing the same thing on the same type, but for different subtypes. 14 | // For example another mod could have the same on largegrid sensor to hide a different control, or even the same control, it would work properly. 15 | 16 | 17 | // You will need the internal IDs for the terminal properties and/or actions you wish to edit, pick one: 18 | // - Use the commented-out code I left in the Edit*() methods below and run the game, it will write to SE's log. 19 | // - For vanilla: With a decompiler go to the block's class and find CreateTerminalControls(). 20 | // - For mods: Open the mod's downloaded folder (by searching its workshopid in the steam folder) and dive through its .cs files to find where they're declaring them. 21 | 22 | // For important notes about terminal controls see: https://github.com/THDigi/SE-ModScript-Examples/blob/master/Data/Scripts/Examples/TerminalControls/Adding/GyroTerminalControls.cs#L21-L35 23 | public static class HideControlsExample 24 | { 25 | static bool Done = false; 26 | 27 | public static void DoOnce() // called by SensorLogic.cs 28 | { 29 | if(Done) 30 | return; 31 | 32 | Done = true; 33 | 34 | EditControls(); 35 | EditActions(); 36 | } 37 | 38 | static bool AppendedCondition(IMyTerminalBlock block) 39 | { 40 | // if block has this gamelogic component then return false to hide the control/action. 41 | return block?.GameLogic?.GetAs() == null; 42 | } 43 | 44 | static void EditControls() 45 | { 46 | List controls; 47 | 48 | // mind the IMySensorBlock input 49 | MyAPIGateway.TerminalControls.GetControls(out controls); 50 | 51 | foreach(IMyTerminalControl c in controls) 52 | { 53 | // a quick way to dump all IDs to SE's log 54 | //string name = MyTexts.GetString((c as IMyTerminalControlTitleTooltip)?.Title.String ?? "N/A"); 55 | //string valueType = (c as ITerminalProperty)?.TypeName ?? "N/A"; 56 | //MyLog.Default.WriteLine($"[DEV] terminal property: id='{c.Id}'; type='{c.GetType().Name}'; valueType='{valueType}'; displayName='{name}'"); 57 | 58 | switch(c.Id) 59 | { 60 | case "Detect Asteroids": // for IDs uncomment above or for alternatives see at the very top of the file 61 | { 62 | // appends a custom condition after the original condition with an AND. 63 | 64 | // pick which way you want it to work: 65 | c.Enabled = TerminalChainedDelegate.Create(c.Enabled, AppendedCondition); // grays out 66 | //c.Visible = TerminalChainedDelegate.Create(c.Visible, AppendedCondition); // hides 67 | break; 68 | } 69 | } 70 | } 71 | } 72 | 73 | static void EditActions() 74 | { 75 | List actions; 76 | 77 | // mind the IMySensorBlock input 78 | MyAPIGateway.TerminalControls.GetActions(out actions); 79 | 80 | foreach(IMyTerminalAction a in actions) 81 | { 82 | // a quick way to dump all IDs to SE's log 83 | //MyLog.Default.WriteLine($"[DEV] toolbar action: id='{a.Id}'; displayName='{a.Name}'"); 84 | 85 | switch(a.Id) 86 | { 87 | case "Detect Asteroids": // for IDs uncomment above or for alternatives see at the very top of the file 88 | case "Detect Asteroids_On": 89 | case "Detect Asteroids_Off": 90 | { 91 | // appends a custom condition after the original condition with an AND. 92 | 93 | a.Enabled = TerminalChainedDelegate.Create(a.Enabled, AppendedCondition); 94 | // action.Enabled hides it, there is no grayed-out for actions. 95 | 96 | break; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/TerminalControls/Hiding/SensorLogic.cs: -------------------------------------------------------------------------------- 1 | using Sandbox.Common.ObjectBuilders; 2 | using Sandbox.ModAPI; 3 | using VRage.Game.Components; 4 | using VRage.ModAPI; 5 | using VRage.ObjectBuilders; 6 | 7 | namespace Digi.Examples.TerminalControls.Hiding 8 | { 9 | // For more info about the gamelogic comp see https://github.com/THDigi/SE-ModScript-Examples/blob/master/Data/Scripts/Examples/BasicExample_GameLogicAndSession/GameLogic.cs 10 | [MyEntityComponentDescriptor(typeof(MyObjectBuilder_SensorBlock), false, "SmallBlockSensor")] 11 | public class SensorLogic : MyGameLogicComponent 12 | { 13 | IMySensorBlock Sensor; 14 | 15 | public override void Init(MyObjectBuilder_EntityBase objectBuilder) 16 | { 17 | NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; 18 | } 19 | 20 | public override void UpdateOnceBeforeFrame() 21 | { 22 | HideControlsExample.DoOnce(); 23 | 24 | Sensor = (IMySensorBlock)Entity; 25 | 26 | if(Sensor.CubeGrid?.Physics == null) 27 | return; // ignore ghost/projected grids 28 | 29 | // the bonus part, enforcing it to stay a specific value. 30 | if(MyAPIGateway.Multiplayer.IsServer) // serverside only to avoid network spam 31 | { 32 | NeedsUpdate = MyEntityUpdateEnum.EACH_FRAME; 33 | } 34 | } 35 | 36 | public override void UpdateAfterSimulation() 37 | { 38 | if(Sensor.DetectAsteroids) 39 | { 40 | Sensor.DetectAsteroids = false; 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Data/Scripts/Examples/TerminalControls/Hiding/TerminalChainedDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sandbox.ModAPI; 3 | 4 | namespace Digi 5 | { 6 | /// 7 | /// Designed for appending custom conditions to Visible/Enabled of terminal controls or toolbar actions so that they can be hidden for specific conditions/subtypes/whatever. 8 | /// 9 | public class TerminalChainedDelegate 10 | { 11 | /// 12 | /// should always be the delegate this replaces, to properly chain with other mods doing the same. 13 | /// should be your custom condition to append to the chain. 14 | /// As for , leave false if you want to hide controls by returning false with your . 15 | /// Otherwise set to true if you want to force-show otherwise hidden controls by returning true with your . 16 | /// 17 | public static Func Create(Func originalFunc, Func customFunc, bool checkOR = false) 18 | { 19 | return new TerminalChainedDelegate(originalFunc, customFunc, checkOR).ResultFunc; 20 | } 21 | 22 | readonly Func OriginalFunc; 23 | readonly Func CustomFunc; 24 | readonly bool CheckOR; 25 | 26 | TerminalChainedDelegate(Func originalFunc, Func customFunc, bool checkOR) 27 | { 28 | OriginalFunc = originalFunc; 29 | CustomFunc = customFunc; 30 | CheckOR = checkOR; 31 | } 32 | 33 | bool ResultFunc(IMyTerminalBlock block) 34 | { 35 | if(block?.CubeGrid == null) 36 | return false; 37 | 38 | bool originalCondition = (OriginalFunc == null ? true : OriginalFunc.Invoke(block)); 39 | bool customCondition = (CustomFunc == null ? true : CustomFunc.Invoke(block)); 40 | 41 | if(CheckOR) 42 | return originalCondition || customCondition; 43 | else 44 | return originalCondition && customCondition; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Digi.Examples 5 | net48 6 | x64 7 | 6 8 | false 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\netstandard.dll 19 | false 20 | 21 | 22 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\ProtoBuf.Net.Core.dll 23 | false 24 | 25 | 26 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Common.dll 27 | false 28 | 29 | 30 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Game.dll 31 | false 32 | 33 | 34 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Graphics.dll 35 | false 36 | 37 | 38 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\SpaceEngineers.Game.dll 39 | false 40 | 41 | 42 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\SpaceEngineers.ObjectBuilders.dll 43 | false 44 | 45 | 46 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.dll 47 | false 48 | 49 | 50 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Game.dll 51 | false 52 | 53 | 54 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Input.dll 55 | false 56 | 57 | 58 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Library.dll 59 | false 60 | 61 | 62 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Math.dll 63 | false 64 | 65 | 66 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Render.dll 67 | false 68 | 69 | 70 | C:\Steam\steamapps\common\SpaceEngineers\Bin64\System.Collections.Immutable.dll 71 | false 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /Examples.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.33801.447 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples", "Examples.csproj", "{9AF63C2C-A8BD-48E9-979D-0DAC07978AB9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Release|Any CPU = Release|Any CPU 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {9AF63C2C-A8BD-48E9-979D-0DAC07978AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {9AF63C2C-A8BD-48E9-979D-0DAC07978AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {9AF63C2C-A8BD-48E9-979D-0DAC07978AB9}.Debug|x64.ActiveCfg = Debug|x64 19 | {9AF63C2C-A8BD-48E9-979D-0DAC07978AB9}.Debug|x64.Build.0 = Debug|x64 20 | {9AF63C2C-A8BD-48E9-979D-0DAC07978AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {9AF63C2C-A8BD-48E9-979D-0DAC07978AB9}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {9AF63C2C-A8BD-48E9-979D-0DAC07978AB9}.Release|x64.ActiveCfg = Release|x64 23 | {9AF63C2C-A8BD-48E9-979D-0DAC07978AB9}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {5B51D48F-4D8A-48C9-AA82-493A6B52A095} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | A collection of SE mod script examples and some ready-to-use code libraries. 4 | 5 | I recommend to start from [Quick Intro to Space Engineers Modding](https://github.com/THDigi/SE-ModScript-Examples/wiki/Quick-Intro-to-Space-Engineers-Modding) which is in the Wiki tab with other guides aswell. 6 | 7 | Feel free to use these scripts in free mods. 8 | 9 | License: https://creativecommons.org/licenses/by-nc-sa/4.0/ (applies to the wiki info as well) 10 | --------------------------------------------------------------------------------