Differences From
Artifact [fe75c1df99]:
55 55 weapon = {primary = nil, secondary = nil};
56 56 psi = {primary = nil, secondary = nil};
57 57 maneuver = nil;
58 58 };
59 59 pref = {
60 60 calendar = 'commune';
61 61 };
62 + overlays = {};
62 63 }
63 64 end;
64 65 __index = {
66 + --------------
67 + -- overlays --
68 + --------------
69 + updateOverlays = function(self)
70 + local phys = {
71 + speed = self.pheno:trait('speed',1);
72 + jump = self.pheno:trait('jump',1);
73 + gravity = 1;
74 + speed_climb = 1;
75 + speed_crouch = 1;
76 + speed_walk = 1;
77 + acceleration_default = 1;
78 + acceleration_air = 1;
79 + }
80 + for i, o in ipairs(self.overlays) do o(phys) end
81 + self.entity:set_physics_override(phys)
82 + end;
83 + overlay = function(self, o)
84 + local id = #self.overlays+1
85 + self.overlays[id] = o
86 + self:updateOverlays()
87 + return id
88 + end;
89 + deleteOverlay = function(self, id)
90 + table.remove(self.overlays, id)
91 + self:updateOverlays()
92 + end;
93 +
94 + --------------
95 + -- personae --
96 + --------------
65 97 pullPersona = function(self)
66 98 -- if later records are added in public updates, extend this function to merge them
67 99 -- into one object
68 100 local s = userStore(self.entity)
69 101 self.persona = s.read 'persona'
70 102 self.pheno = starlit.world.species.pheno(self.persona.species, self.persona.speciesVariant)
71 103 end;
72 104 pushPersona = function(self)
73 105 local s = userStore(self.entity)
74 106 s.write('persona', self.persona)
75 107 end;
108 +
76 109 uiColor = function(self) return lib.color {hue=238,sat=.5,lum=.5} end;
110 +
111 + -----------
112 + -- stats --
113 + -----------
77 114 statDelta = function(self, stat, d, cause, abs)
78 115 local dt = self.persona.statDeltas
79 116 local min, max, base = self:statRange(stat)
80 117 if abs then
81 118 if d == true then d = max
82 119 elseif d == false then d = min end
83 120 end
................................................................................
97 134 self:pushPersona()
98 135 end
99 136
100 137
101 138 self:updateHUD()
102 139 -- TODO trigger relevant animations?
103 140 end;
104 - lookupSpecies = function(self)
105 - return starlit.world.species.lookup(self.persona.species, self.persona.speciesVariant)
106 - end;
107 - phenoTrait = function(self, trait, dflt)
108 --- local s,v = self:lookupSpecies()
109 --- return v.traits[trait] or s.traits[trait] or 0
110 - return self.pheno:trait(trait, dflt)
111 - end;
112 141 statRange = function(self, stat) --> min, max, base
113 142 return starlit.world.species.statRange(
114 143 self.persona.species, self.persona.speciesVariant, stat)
115 144 end;
116 145 effectiveStat = function(self, stat)
117 146 local val
118 147 local min, max, base = self:statRange(stat)
................................................................................
124 153 else
125 154 val = base + self.persona.statDeltas[stat] or 0
126 155 end
127 156
128 157 local d = max - min
129 158 return val, (val - min) / d
130 159 end;
160 +
161 + ---------------
162 + -- phenotype --
163 + ---------------
164 + lookupSpecies = function(self)
165 + return starlit.world.species.lookup(self.persona.species, self.persona.speciesVariant)
166 + end;
167 + phenoTrait = function(self, trait, dflt)
168 +-- local s,v = self:lookupSpecies()
169 +-- return v.traits[trait] or s.traits[trait] or 0
170 + return self.pheno:trait(trait, dflt)
171 + end;
131 172 damageModifier = function(self, kind, amt)
132 173 if kind == 'bluntForceTrauma' then
133 174 local std = self:phenoTrait 'sturdiness'
134 175 if std < 0 then
135 176 amt = amt / 1+std
136 177 else
137 178 amt = amt * 1-std
138 179 end
139 180 end
140 181 return amt
141 182 end;
183 +
184 + ---------
185 + -- HUD --
186 + ---------
142 187 attachImage = function(self, def)
143 188 local user = self.entity
144 189 local img = {}
145 190 img.id = user:hud_add {
146 191 type = 'image';
147 192 text = def.tex;
148 193 scale = def.scale;
................................................................................
380 425 align = {x=0, y=-1};
381 426 z = -1;
382 427 update = function(user, set)
383 428 set('text', hudAdjustBacklight(hudCenterBG):render())
384 429 end;
385 430 };
386 431 end;
387 - -- horrible horrible HACK
388 - setModeHand = function(self)
389 - local inv = self.entity:get_inventory()
390 - local hnd
391 - if self.actMode == 'off'
392 - then hnd = ItemStack('starlit:_hand_dig')
393 - else hnd = ItemStack()
432 + deleteHUD = function(self)
433 + for name, e in pairs(self.hud.elt) do
434 + self:hud_delete(e.id)
394 435 end
395 - inv:set_stack('hand', 1, hnd)
396 436 end;
437 + updateHUD = function(self)
438 + for name, e in pairs(self.hud.elt) do
439 + if e.update then e.update() end
440 + end
441 + end;
442 +
443 + ---------------------
444 + -- actions & modes --
445 + ---------------------
397 446 onModeChange = function(self, oldMode, silent)
398 447 self.hud.elt.crosshair.update()
399 448 if not silent then
400 449 local sfxt = {
401 450 off = 'starlit-mode-off';
402 451 nano = 'starlit-mode-nano';
403 452 psi = 'starlit-mode-psi';
................................................................................
413 462 local oldMode = self.actMode
414 463 self.actMode = mode
415 464 self:onModeChange(oldMode, silent)
416 465 if mode ~= oldMode then
417 466 starlit.ui.setupForUser(self)
418 467 end
419 468 end;
420 - deleteHUD = function(self)
421 - for name, e in pairs(self.hud.elt) do
422 - self:hud_delete(e.id)
469 + setModeHand = function(self) -- horrible horrible HACK
470 + local inv = self.entity:get_inventory()
471 + local hnd
472 + if self.actMode == 'off'
473 + then hnd = ItemStack('starlit:_hand_dig')
474 + else hnd = ItemStack()
423 475 end
476 + inv:set_stack('hand', 1, hnd)
424 477 end;
425 - updateHUD = function(self)
426 - for name, e in pairs(self.hud.elt) do
427 - if e.update then e.update() end
428 - end
429 - end;
478 +
479 + ---------------------
480 + -- intel-gathering --
481 + ---------------------
430 482 clientInfo = function(self)
431 483 return minetest.get_player_information(self.name)
432 484 end;
485 + species = function(self)
486 + return starlit.world.species.index[self.persona.species]
487 + end;
488 +
489 + --------------------
490 + -- event handlers --
491 + --------------------
433 492 onSignup = function(self)
434 493 local meta = self.entity:get_meta()
435 494 local inv = self.entity:get_inventory()
436 495 -- the sizes indicated here are MAXIMA. limitations on e.g. the number of elements that may be carried are defined by your suit and enforced through callbacks and UI generation code, not inventory size
437 496 inv:set_size('main', 6) -- carried items and tools. main hotbar.
438 497 inv:set_size('hand', 1) -- horrible hack to allow both tools and intrinsics
439 498
................................................................................
471 530 giveGifts('starlit_suit_canisters', gifts.suitCans)
472 531
473 532 giveGifts('main', gifts.carry)
474 533
475 534 self:reconfigureSuit()
476 535
477 536 -- i feel like there has to be a better way
478 - local cx = math.random(-500,500)
537 + local posrng = starlit.world.seedbank[0x13f19] -- TODO player-specific seed
538 + local cx = posrng:int(-500,500) --math.random(-500,500)
479 539 local iter, startPoint = 1
480 540 repeat local temp = -100
481 - local cz = math.random(-500,500)
541 + local cz = posrng:int(-500,500)
482 542 local cy = minetest.get_spawn_level(cx, cz)
483 543 if cy then
484 544 startPoint = vector.new(cx,cy,cz)
485 545 temp = starlit.world.climate.eval(startPoint,.5,.5).surfaceTemp
486 546 end
487 547 iter = iter + 1
488 548 if iter > 100 then break end -- avoid infiniloop in pathological conditions
................................................................................
597 657 }
598 658 me:set_eye_offset(nil, vector.new(3,-.2,10))
599 659 -- TODO set_clouds speed in accordance with wind
600 660 starlit.world.species.setupEntity(me, self.persona)
601 661 starlit.ui.setupForUser(self)
602 662 self:createHUD()
603 663 self:updateSuit()
664 + self:updateOverlays()
604 665 end;
666 + onPart = function(self)
667 + starlit.liveUI [self.name] = nil
668 + starlit.activeUI [self.name] = nil
669 + starlit.activeUsers[self.name] = nil
670 + end;
671 +
672 + -----------------------------
673 + -- environment suit & body --
674 + -----------------------------
605 675 suitStack = function(self)
606 676 return self.entity:get_inventory():get_stack('starlit_suit', 1)
607 677 end;
608 678 suitSound = function(self, sfx)
609 679 -- trigger a sound effect from the player's suit computer
610 680 minetest.sound_play(sfx, {object=self.entity, max_hear_distance=4}, true)
611 681 end;
................................................................................
630 700 sfx = 'starlit-power-up'
631 701 elseif state == 'powerSave' or os == 'powerSave' then
632 702 sfx = 'starlit-configure'
633 703 end
634 704 if sfx then self:suitSound(sfx) end
635 705 end
636 706 end;
637 - species = function(self)
638 - return starlit.world.species.index[self.persona.species]
639 - end;
640 707 updateBody = function(self)
641 708 local adornment = {}
642 709 local suitStack = self:suitStack()
643 710 if suitStack and not suitStack:is_empty() then
644 711 local suit = suitStack:get_definition()._starlit.suit
645 712 suit.adorn(adornment, suitStack, self.persona)
646 713 end
................................................................................
726 793 -- TODO display power use icon
727 794 end
728 795 return supply, wasteHeat
729 796 end;
730 797 naked = function(self)
731 798 return self:suitStack():is_empty()
732 799 end;
733 - onPart = function(self)
734 - starlit.liveUI [self.name] = nil
735 - starlit.activeUI [self.name] = nil
736 - starlit.activeUsers[self.name] = nil
737 - end;
800 +
801 + --------
802 + -- ui --
803 + --------
738 804 openUI = function(self, id, page, ...)
739 805 local ui = assert(starlit.interface.db[id])
740 806 ui:open(self, page, ...)
741 807 end;
742 808 onRespond = function(self, ui, state, resp)
743 809 ui:action(self, state, resp)
744 810 end;
745 -
746 - updateWeather = function(self)
747 - end;
748 -
749 - canInteract = function(self, with)
750 - return true; -- TODO
751 - end;
752 -
753 811 trigger = function(self, which, how)
754 812 local p
755 813 local wld = self.entity:get_wielded_item()
756 814 if which == 'maneuver' then
757 815 p = self.power.maneuver
758 816 elseif which == 'retarget' then
759 817 self.action.prog = {}
................................................................................
783 841 end
784 842 local sw = starlit.item.sw.db[pgm.body.pgmId]
785 843 run = assert(sw.run, 'missing run() for active software ability ' .. pgm.body.pgmId)
786 844 break
787 845 end
788 846 end
789 847 end
848 + elseif p.ref then
849 + run = p.ref.run
790 850 else
791 851 error('bad ability pointer ' .. dump(p))
792 852 end
793 853 if run then
794 854 run(self, ctx)
795 855 return true
796 856 end
797 857 return false
798 858 end;
859 +
860 + -------------
861 + -- weather --
862 + -------------
863 + updateWeather = function(self)
864 + end;
865 +
866 + canInteract = function(self, with)
867 + return true; -- TODO
868 + end;
869 +
870 + ---------------
871 + -- inventory --
872 + ---------------
799 873 give = function(self, item)
800 874 local inv = self.entity:get_inventory()
801 875 local function is(grp)
802 876 return minetest.get_item_group(item:get_name(), grp) ~= 0
803 877 end
804 878 -- TODO notif popups
805 879 if is 'specialInventory' then
................................................................................
847 921 local dehydration = p:trait 'dehydration' * biointerval
848 922 -- you dehydrate faster in higher temp
849 923 dehydration = dehydration * math.max(1, starlit.world.climate.temp(u.entity:get_pos()) / 10)
850 924
851 925 u:statDelta('nutrition', -bmr)
852 926 u:statDelta('hydration', -dehydration)
853 927
854 - if u:effectiveStat 'nutrition' == 0 then
855 - -- starvation
856 - end
928 + local moralePenalty = -1 -- 1min/min
929 + local fatiguePenalty = 1 -- 1min/min
930 + local heatPenalty = 1 -- stamina regen is divided by this
857 931
858 - if u:effectiveStat 'hydration' == 0 then
859 - -- dying of thirst
932 + do local warmth = u:effectiveStat 'warmth'
933 + local tempRange = u:species().tempRange
934 + local tComfMin, tComfMax = tempRange.comfort[1], tempRange.comfort[2]
935 + local tempDiff = 0
936 + if warmth < tComfMin then
937 + tempDiff = math.abs(warmth-tComfMin)
938 + elseif warmth > tComfMax then
939 + tempDiff = math.abs(warmth-tComfMax)
940 + end
941 + moralePenalty = moralePenalty + tempDiff
942 + heatPenalty = heatPenalty + tempDiff
860 943 end
861 944
945 + -- penalize heavy phys. activity
946 + local stamina, sp = u:effectiveStat 'stamina'
947 + fatiguePenalty = fatiguePenalty * (1 + 9*(1-sp))
948 +
949 + local food = u:effectiveStat 'nutrition'
950 + local water = u:effectiveStat 'hydration'
862 951 local rads = u:effectiveStat 'irradiation'
952 + if food < 1000 then moralePenalty = moralePenalty + (1 - (food/1000)) * 5 end
953 + if water < 1 then moralePenalty = moralePenalty + (1 - (water/1)) * 10 end
954 +
863 955 if rads > 0 then
864 956 u:statDelta('irradiation', -0.0001 * biointerval)
957 + local moraleDrainFac = 2^(rads / 2)
958 + moralePenalty = moralePenalty * moraleDrainFac
959 + end
960 +
961 + u:statDelta('morale', moralePenalty * biointerval)
962 + u:statDelta('fatigue', fatiguePenalty * biointerval)
963 +
964 + if food == 0 then -- starvation
965 + u:statDelta('health', -5*biointerval)
966 + end
967 +
968 + if water == 0 then -- dying of thirst
969 + u:statDelta('health', -20*biointerval)
865 970 end
866 971
972 + if sp < 1.0 then
973 + u:statDelta('stamina', u:effectiveStat 'staminaRegen' / heatPenalty)
974 + end
867 975 end
868 976 end)
869 977
870 978 local cbit = {
871 979 up = 0x001;
872 980 down = 0x002;
873 981 left = 0x004;
................................................................................
893 1001 return mustHalt
894 1002 else return doNothing end
895 1003 end
896 1004 local skipBits = 0
897 1005 if user.action.bits ~= bits then
898 1006 local mPrimary = what(cbit.dig)
899 1007 local mSecondary = what(cbit.put)
1008 + local mManeuver = what(cbit.manv)
900 1009 if mPrimary == mustInit then -- ENGINE-BUG
901 1010 user.action.tgt = {type='nothing'}
902 1011 user.action.prog = {}
903 1012 elseif mPrimary == mustHalt then
904 1013 user:trigger('primary', {state='halt'})
905 1014 end
906 1015 if mSecondary == mustHalt then
907 1016 user:trigger('secondary', {state='halt'})
908 1017 end
1018 + if mManeuver == mustInit then
1019 + user:trigger('maneuver', {state='init'})
1020 + elseif mManeuver == mustHalt then
1021 + user:trigger('maneuver', {state='halt'})
1022 + end
909 1023 end
910 1024 --bits = bit.band(bits, bit.bnot(skipBits))
911 - if bit.band(bits, cbit.dig)~=0 then
912 - user:trigger('primary', {state='prog', delta=delta})
1025 + local function prog(what)
1026 + user:trigger(what, {state='prog', delta=delta})
913 1027 end
914 - if bit.band(bits, cbit.put)~=0 then
915 - user:trigger('secondary', {state='prog', delta=delta})
916 - end
1028 + if bit.band(bits, cbit.dig)~=0 then prog 'primary' end
1029 + if bit.band(bits, cbit.put)~=0 then prog 'secondary' end
1030 + if bit.band(bits, cbit.manv)~=0 then prog 'maneuver' end
917 1031 user.action.bits = bits
918 1032 -- ENGINE-BUG: dig and put are not handled equally in the
919 1033 -- engine. it is possible for the put bit to get stuck on
920 1034 -- if the key is hammered while the player is not moving.
921 1035 -- the bit will release as soon as the player looks or turns
922 1036 -- nonetheless this is obnoxious
923 1037 end
924 1038 end)