├── .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 |
--------------------------------------------------------------------------------