Compare commits

...

151 Commits

Author SHA1 Message Date
97ed29fc23 changed something. 2026-05-20 20:18:22 +02:00
1c03653fee todo slop 2026-05-19 21:58:35 +02:00
c016cebb0f accuracy slop 2026-05-19 21:51:20 +02:00
885f594ca0 dutch translation but it sucks 2026-05-19 21:46:52 +02:00
45831b780a FRENCH ew 2026-05-19 21:42:16 +02:00
6b0c4468ec FRENCH 2026-05-19 21:37:51 +02:00
d15b34939e models and shi 2026-05-08 03:47:03 +03:00
3f95944314 rack stateslop 2026-05-07 22:43:42 +02:00
21493fec04 add proper container and do some syncy stuff and save 2026-05-07 00:30:08 +02:00
42331390e7 clicky 2026-05-06 17:00:31 +02:00
cdb98bd85e all rack rendering (afaik) and basic right click logic stuff and refactors nobody asked for 2026-05-05 21:39:40 +02:00
7f58fdf55b server hit stuff 2026-05-03 19:06:37 +02:00
17bc614eb9 textures 2026-05-03 18:24:07 +02:00
b78cc44d89 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-05-03 17:21:34 +02:00
4d37483057 render a singular server 2026-05-03 17:21:24 +02:00
4d84ec2ed4 todo stuff 2026-05-03 16:04:38 +03:00
5249832bd6 work on optimized networking 2026-05-02 23:28:02 +03:00
e9885940e2 robot stuff wip i hate minecraft 2026-05-02 22:01:49 +02:00
c63912c1f3 fix 2026-05-02 09:00:51 +02:00
323d213291 render robot model with blockentrenderer 2026-05-01 23:25:22 +02:00
b8968942f8 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-05-01 00:27:16 +02:00
0eee9f5163 robot model 2026-05-01 00:27:09 +02:00
9fd6ea8881 transistor and some random card textures 2026-04-30 22:20:24 +02:00
c6fc9ed9e9 made relay no longer violate physics 2026-04-30 18:14:21 +03:00
9ea3727dfb cables are fully cabling now 2026-04-30 17:47:30 +03:00
21516ee923 FIXED THE EAST BUG! WE ARE SO BACK!! 2026-04-30 07:18:26 +03:00
d8e79d8719 almost fully fixing cables
fixes 1 edge-case, but the east bug is still goofed
2026-04-30 00:50:12 +03:00
5dbd0b8734 bugfix 2026-04-30 00:04:20 +03:00
710547a014 todo: stuff 2026-04-29 21:58:43 +03:00
424f77ba10 relay renderer go brrr 2026-04-29 14:20:45 +03:00
02c0c5d60c relay bugfix and graphics 2026-04-29 14:15:26 +03:00
0b33eda8a8 oopsies 2026-04-29 12:22:57 +03:00
cd909dbd80 half the todo is done 2026-04-28 23:21:34 +03:00
8fb4921e30 cases can do stuff now 2026-04-28 21:35:58 +03:00
86b3486622 fixed the worst oversight of mankind 2026-04-28 21:34:18 +03:00
9a733e0a81 fixed a fixable unfixed problem 2026-04-28 21:14:16 +03:00
740318c020 linguistics or smth idfk 2026-04-28 21:00:34 +03:00
149cfec3ed got rid of some warnings 2026-04-28 20:52:22 +03:00
4d2cb14e45 Merge remote-tracking branch 'origin/main' 2026-04-28 20:50:19 +03:00
7c27955c14 relay blockstates and capacitor magic 2026-04-28 20:50:14 +03:00
71899c0013 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-28 19:38:35 +02:00
ced17282f9 new relay textures 2026-04-28 19:38:29 +02:00
a57fca82e5 Merge remote-tracking branch 'origin/main' 2026-04-28 20:32:16 +03:00
3b7e2b02b6 experimental relay bullshit 2026-04-28 20:32:11 +03:00
c968b97faf added oc relay textures 2026-04-28 19:29:42 +02:00
6ef6826483 fuck you have this 2026-04-28 17:13:31 +02:00
ba826b47a8 fuck you have this 2026-04-28 17:05:25 +02:00
02114fc02a reduced network bill by 99.99999% trust 2026-04-28 17:57:48 +03:00
9ac53a0f0e atom how did you miss another one 2026-04-28 16:18:19 +02:00
1f1a456f1d damn you atom 2026-04-28 16:17:31 +02:00
43255b1caf NodeBlockEntity is dead, long live SingleDeviceBlockEntity + cables function 2026-04-28 17:16:33 +03:00
464584877c block textures :) 2026-04-28 15:15:29 +02:00
79053b277a textures 2026-04-28 15:07:55 +02:00
80130a68e1 rip capacitors (WIP refactor)
if anyone touches this while I'm working on it, I will find you, and I will refactor you too
2026-04-27 19:04:26 +03:00
fc8a4aaa58 linguistics 2026-04-27 17:42:02 +03:00
cb380880e4 optimize cable and fix 2026-04-27 11:45:28 +02:00
26ec989a4c Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-27 11:21:17 +02:00
883833351c cable stuff 2026-04-27 11:20:22 +02:00
400a773a04 get out of here, missing texture 2026-04-27 01:30:44 +03:00
6532fea543 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-26 23:32:04 +02:00
b39d016feb cable model 2026-04-26 23:31:19 +02:00
477abfd80b we got bullshit todo 2026-04-26 17:45:10 +00:00
8edd36124b clean code, acceptable performance 2026-04-26 16:05:46 +00:00
ed6ddddf98 le screen is le synchronized 2026-04-26 06:20:02 +00:00
83749bf3ba CPU no longer boils 2026-04-25 22:37:23 +00:00
f3280f83d1 better stuff 2026-04-25 22:30:31 +00:00
88ffaf7a22 Trăiască România! 2026-04-25 21:43:13 +00:00
8004658a0a get internationalized 2026-04-25 20:58:13 +00:00
a8b45c6565 I plead oopsie daisy 2026-04-25 19:32:33 +00:00
ab0a484021 merged 2026-04-25 19:21:30 +00:00
f9ddc36611 madness of men 2026-04-25 19:19:40 +00:00
a591c9da6f separate cardinal directions from up and down 2026-04-25 13:35:34 +02:00
471921c774 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-24 15:07:17 +02:00
cbeb29eeb3 CaseEntity renderer 2026-04-24 15:07:12 +02:00
40f3ce9083 case item model 2026-04-24 10:34:03 +00:00
3bfc07c392 screen progress 2026-04-23 20:23:52 +02:00
a8ca4b1e35 follow advice and fix tiny thnigs 2026-04-23 10:14:27 +02:00
8ccdb76829 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-23 10:08:04 +02:00
cb7be27464 fix screen entity renderer 2026-04-23 10:07:58 +02:00
6d20d204a6 case model 2026-04-23 00:52:59 +02:00
b6afa13e72 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-22 22:34:33 +02:00
66097fdd6c fixes to node ticking and beeping 2026-04-22 22:30:37 +02:00
55f30283c3 Render screen texture on block 2026-04-22 22:30:23 +02:00
c98ec2fc99 more work on computer stuff 2026-04-22 18:02:45 +02:00
faa1be7175 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-22 17:57:09 +02:00
c8da0aba68 power button and machine buzz 2026-04-21 21:44:39 +02:00
35a659475a Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers
# Conflicts:
#	src/main/kotlin/org/neoflock/neocomputers/gui/screen/CaseScreen.kt
2026-04-21 20:53:45 +02:00
7542052d1e innovation 2026-04-21 20:51:30 +02:00
mewhenthe
a14d3a2b73 screen directioned 2026-04-21 20:07:59 +02:00
mewhenthe
f3ebc4f684 save water by using parchmentmc 2026-04-21 12:35:03 +02:00
mewhenthe
8b10960612 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-21 10:24:51 +02:00
mewhenthe
d731a2bc13 more stuff, case button except it sucks, I HATE MINECRAFT 2026-04-21 10:24:38 +02:00
f2f79244e6 Merge remote-tracking branch 'origin/main' 2026-04-21 00:00:48 +02:00
6adce7d3b1 machine interface WIP 2026-04-21 00:00:08 +02:00
mewhenthe
00b0014456 something more 2026-04-20 23:56:34 +02:00
mewhenthe
9de31e8c30 fix bug, I HATE MINECRAFT 2026-04-20 23:55:32 +02:00
mewhenthe
dd5d33cb19 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-20 19:42:22 +02:00
mewhenthe
51a5d4d4ce ignore 2026-04-20 19:40:55 +02:00
mewhenthe
2baaee741f progressbar stuff 2026-04-20 19:40:03 +02:00
f9e33f9f05 Redstone cards 2026-04-19 21:34:18 +02:00
0416ddd4a5 bunch of cards 2026-04-19 21:22:12 +02:00
b025159791 Lazy loading 2026-04-19 15:22:45 +02:00
f1d39aa12c EEPROM texture 2026-04-19 15:16:16 +02:00
5c44ee6c86 EEPROMs 2026-04-19 15:13:27 +02:00
74d1015bcd component slots 2026-04-19 12:50:13 +02:00
d3fb4a65cb use the proper syntax in the datagen here 2026-04-18 22:43:30 -03:00
9d57926432 add generated src folder to .gitignore we do not need it where we are going 2026-04-18 22:41:28 -03:00
2c97ab6409 add fabric data generation we are SO back 2026-04-18 22:40:14 -03:00
4fb35c5080 add some placeholder textures because my ass needs a visual indicator i cannot work w/ these purple guy looking ahh blocks 2026-04-18 21:05:24 -03:00
62ff4dfa96 add warning to screenblock if there's not enough power and change up FontProvider to work on windows 2026-04-18 20:10:51 -03:00
mewhenthe
5ddafd2d84 fix merge 2026-04-18 19:51:01 +02:00
dc16cebc25 Merge remote-tracking branch 'origin/main' 2026-04-18 19:49:25 +02:00
mewhenthe
15b6b2c5ad Dynamic slot 2026-04-18 19:48:40 +02:00
df86086d10 redstone I/O 2026-04-18 19:43:35 +02:00
mewhenthe
07c8dc914d oopsie 2026-04-18 18:34:36 +02:00
mewhenthe
d48c7c3035 screens stuffs 2026-04-18 16:24:51 +02:00
mewhenthe
8634bdb4ce combustgui.png 2026-04-18 13:29:45 +02:00
mewhenthe
a53a633b33 unimportant stuff 2026-04-18 12:56:20 +02:00
7d4ee8593c combustion gen drops items and such 2026-04-18 12:12:31 +02:00
93a4c4318f made the particles better 2026-04-18 12:00:42 +02:00
a352b8f6e8 fixed a warning 2026-04-18 11:54:18 +02:00
a4533e03bb synchronization and persistant state 2026-04-18 11:53:18 +02:00
834a9c7ed8 capacitor debug textures 2026-04-16 18:14:42 +02:00
8dc774e5ab teto 2026-04-15 20:58:08 +02:00
462a6d1bdf block models and stuff 2026-04-15 20:57:59 +02:00
61a1c52106 Do not question these. 2026-04-14 23:24:16 +02:00
21746422f6 a menu for combustion generator, but sprites dont work.
Don't quest the test sprites.
2026-04-14 23:23:59 +02:00
ab9d1baa36 fixes and made solar power also nodes 2026-04-14 00:40:41 +02:00
8d146fbd17 combustible generator but no screen 2026-04-13 23:07:02 +02:00
407e0b44ac made it compile 2026-04-13 10:37:57 +02:00
94e6c505bd initial work on combustion 2026-04-12 20:39:46 +02:00
ae9a3fd2c7 solar power 2026-04-12 20:04:31 +02:00
e38184d3a3 1.21.1 is so back 2026-04-12 19:31:40 +02:00
db2cbbbe8d networking bullshit 2026-04-12 15:15:16 +02:00
59358e6b08 subnode bullshit 2026-04-12 00:16:52 +02:00
mewhenthe
f456b9b04e bkjadsl 2026-04-11 22:46:50 +02:00
mewhenthe
e6dfc1adc5 merge 2026-04-11 22:45:36 +02:00
mewhenthe
7adaf4d8df buffer renderer 2026-04-11 22:44:23 +02:00
71b6d3a606 power bullshit 2026-04-11 21:48:45 +02:00
mewhenthe
f5b5a30299 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers 2026-04-11 19:15:20 +02:00
mewhenthe
3485467ca4 clientlifecycle event 2026-04-11 19:15:00 +02:00
fe7bad2b88 small oopsie in the test log 2026-04-11 19:13:54 +02:00
a687e4d4d4 power networks 2026-04-11 19:11:26 +02:00
33d0ae7097 Merge branch 'main' of https://gitea.codersquack.nl/NeoFlock/NeoComputers
# Conflicts:
#	src/main/kotlin/org/neoflock/neocomputers/NeoComputers.kt
2026-04-11 18:08:42 +02:00
591972d913 basic networking impl
it is currently very minimal, does not handle power.
2026-04-11 18:07:09 +02:00
mewhenthe
0d92d59958 Why so screenious 2026-04-11 17:45:43 +02:00
mewhenthe
9af5b77a1f add test stuff, may or may not have totally cooked the project for you guys 2026-04-11 14:00:05 +02:00
dd21f21c2a change stonecutter target version to 1.21.11-neoforge since we're developing in that anyway 2026-04-07 20:16:51 -03:00
7f9abfb8ca change stonecutter target version to 1.21.11-neoforge since we're developing in that anyway 2026-04-07 20:16:25 -03:00
e21ee3057a you'd never guess who forgot to add the license file 2026-04-07 20:11:08 -03:00
60ca91c79c disable workflows and change the issueTrackerURL to the right one 2026-04-07 20:06:45 -03:00
397 changed files with 72879 additions and 323 deletions

6
.config/lite-xl/init.lua Normal file
View File

@@ -0,0 +1,6 @@
local core = require "core"
local config = require "config"
config.ignore_files = { "^build$", "^buildSrc$", "^gradle$", "^\.kotlin$", "^\.idea$", "^\.vscode$", "^\.gradle$", "^\.architectury-transformer$" }

View File

@@ -4,7 +4,8 @@
# against bad commits. # against bad commits.
name: build name: build
on: [pull_request, push] #on: [pull_request, push]
on: []
jobs: jobs:
build: build:

6
.gitignore vendored
View File

@@ -453,3 +453,9 @@ FodyWeavers.xsd
# JetBrains Rider # JetBrains Rider
*.sln.iml *.sln.iml
/.kotlin/ /.kotlin/
/versions/*/src
.lite_project.lua
.config/lite-xl/**

37
.lite_project.lua Normal file
View File

@@ -0,0 +1,37 @@
-- Put project's module settings here.
-- This module will be loaded when opening a project, after the user module
-- configuration.
-- It will be automatically reloaded when saved.
local config = require "core.config"
config.ignore_files = {"^buildSrc$", "^gradle$", "\\.kotlin", "\\.idea", "\\.vscode", "\\.gradle", "\\.architectury-transformer" }
-- you can add some patterns to ignore files within the project
-- config.ignore_files = {"^%.", <some-patterns>}
-- Patterns are normally applied to the file's or directory's name, without
-- its path. See below about how to apply filters on a path.
--
-- Here some examples:
--
-- "^%." matches any file of directory whose basename begins with a dot.
--
-- When there is an '/' or a '/$' at the end, the pattern will only match
-- directories. When using such a pattern a final '/' will be added to the name
-- of any directory entry before checking if it matches.
--
-- "^%.git/" matches any directory named ".git" anywhere in the project.
--
-- If a "/" appears anywhere in the pattern (except when it appears at the end or
-- is immediately followed by a '$'), then the pattern will be applied to the full
-- path of the file or directory. An initial "/" will be prepended to the file's
-- or directory's path to indicate the project's root.
--
-- "^/node_modules/" will match a directory named "node_modules" at the project's root.
-- "^/build.*/" will match any top level directory whose name begins with "build".
-- "^/subprojects/.+/" will match any directory inside a top-level folder named "subprojects".
-- You may activate some plugins on a per-project basis to override the user's settings.
-- config.plugins.trimwitespace = true

601
LICENSE
View File

@@ -1,121 +1,504 @@
Creative Commons Legal Code GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
CC0 1.0 Universal Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE [This is the first released version of the Lesser GPL. It also counts
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN as the successor of the GNU Library Public License, version 2, hence
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS the version number 2.1.]
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose Preamble
The laws of most jurisdictions throughout the world automatically confer The licenses for most software are designed to take away your
exclusive Copyright and Related Rights (defined below) upon the creator freedom to share and change it. By contrast, the GNU General Public
and subsequent owner(s) (each and all, an "owner") of an original work of Licenses are intended to guarantee your freedom to share and change
authorship and/or a database (each, a "Work"). free software--to make sure the software is free for all its users.
Certain owners wish to permanently relinquish those rights to a Work for This license, the Lesser General Public License, applies to some
the purpose of contributing to a commons of creative, cultural and specially designated software packages--typically libraries--of the
scientific works ("Commons") that the public can reliably and without fear Free Software Foundation and other authors who decide to use it. You
of later claims of infringement build upon, modify, incorporate in other can use it too, but we suggest you first think carefully about whether
works, reuse and redistribute as freely as possible in any form whatsoever this license or the ordinary General Public License is the better
and for any purposes, including without limitation commercial purposes. strategy to use in any particular case, based on the explanations below.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any When we speak of free software, we are referring to freedom of use,
expectation of additional consideration or compensation, the person not price. Our General Public Licenses are designed to make sure that
associating CC0 with a Work (the "Affirmer"), to the extent that he or she you have the freedom to distribute copies of free software (and charge
is an owner of Copyright and Related Rights in the Work, voluntarily for this service if you wish); that you receive source code or can get
elects to apply CC0 to the Work and publicly distribute the Work under its it if you want it; that you can change the software and use pieces of
terms, with knowledge of his or her Copyright and Related Rights in the it in new free programs; and that you are informed that you can do
Work and the meaning and intended legal effect of CC0 on those rights. these things.
1. Copyright and Related Rights. A Work made available under CC0 may be To protect your rights, we need to make restrictions that forbid
protected by copyright and related or neighboring rights ("Copyright and distributors to deny you these rights or to ask you to surrender these
Related Rights"). Copyright and Related Rights include, but are not rights. These restrictions translate to certain responsibilities for
limited to, the following: you if you distribute copies of the library or if you modify it.
i. the right to reproduce, adapt, distribute, perform, display, For example, if you distribute copies of the library, whether gratis
communicate, and translate a Work; or for a fee, you must give the recipients all the rights that we gave
ii. moral rights retained by the original author(s) and/or performer(s); you. You must make sure that they, too, receive or can get the source
iii. publicity and privacy rights pertaining to a person's image or code. If you link other code with the library, you must provide
likeness depicted in a Work; complete object files to the recipients, so that they can relink them
iv. rights protecting against unfair competition in regards to a Work, with the library after making changes to the library and recompiling
subject to the limitations in paragraph 4(a), below; it. And you must show them these terms so they know their rights.
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention We protect your rights with a two-step method: (1) we copyright the
of, applicable law, Affirmer hereby overtly, fully, permanently, library, and (2) we offer you this license, which gives you legal
irrevocably and unconditionally waives, abandons, and surrenders all of permission to copy, distribute and/or modify the library.
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason To protect each distributor, we want to make it very clear that
be judged legally invalid or ineffective under applicable law, then the there is no warranty for the free library. Also, if the library is
Waiver shall be preserved to the maximum extent permitted taking into modified by someone else and passed on, the recipients should know
account Affirmer's express Statement of Purpose. In addition, to the that what they have is not the original version, so that the original
extent the Waiver is so judged Affirmer hereby grants to each affected author's reputation will not be affected by problems that might be
person a royalty-free, non transferable, non sublicensable, non exclusive, introduced by others.
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers. Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
a. No trademark or patent rights held by Affirmer are waived, abandoned, Most GNU software, including some libraries, is covered by the
surrendered, licensed or otherwise affected by this document. ordinary GNU General Public License. This license, the GNU Lesser
b. Affirmer offers the Work as-is and makes no representations or General Public License, applies to certain designated libraries, and
warranties of any kind concerning the Work, express, implied, is quite different from the ordinary General Public License. We use
statutory or otherwise, including without limitation warranties of this license for certain libraries in order to permit linking those
title, merchantability, fitness for a particular purpose, non libraries into non-free programs.
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to When a program is linked with a library, whether statically or using
the greatest extent permissible under applicable law. a shared library, the combination of the two is legally speaking a
c. Affirmer disclaims responsibility for clearing rights of other persons combined work, a derivative of the original library. The ordinary
that may apply to the Work or any use thereof, including without General Public License therefore permits such linking only if the
limitation any person's Copyright and Related Rights in the Work. entire combination fits its criteria of freedom. The Lesser General
Further, Affirmer disclaims responsibility for obtaining any necessary Public License permits more lax criteria for linking other code with
consents, permissions or other rights required for any use of the the library.
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a We call this license the "Lesser" General Public License because it
party to this document and has no duty or obligation with respect to does Less to protect the user's freedom than the ordinary General
this CC0 or use of the Work. Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random
Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@@ -8,8 +8,7 @@ the mod for that version!
Also, try reading about how stonecutter's conditional macros work (those can be seen as the `//?` statements in the code). Also, try reading about how stonecutter's conditional macros work (those can be seen as the `//?` statements in the code).
Stonecutter automatically comments and uncomments them when you switch between versions or loaders, you shouldn't do it yourself. Stonecutter automatically comments and uncomments them when you switch between versions or loaders, you shouldn't do it yourself.
The minecraft version this mod is currently being developed on is 1.21.11 neoforge or fabric. Although the project stonecutter.gradle.kts The minecraft version this mod is currently being developed on is 1.21.11 neoforge or fabric.
is currently using 1.21.9-fabric, you can easily change it with the gradle task.
The recommended IDE for this is IntelliJ IDEA 2026.1, and the JDK used is Eclipse Temurin 25.0.2 (from Adoptium), although The recommended IDE for this is IntelliJ IDEA 2026.1, and the JDK used is Eclipse Temurin 25.0.2 (from Adoptium), although
you should be able to use any other build of OpenJDK 26. you should be able to use any other build of OpenJDK 26.

35
TODO.md Normal file
View File

@@ -0,0 +1,35 @@
# Networking
> All that is left is optimization
## Mergeable device nodes
For optimization of screen grouping and cables, we should add a system that allows merging nodes
## Copy the networking optimizations of OC
> https://github.com/MightyPirates/OpenComputers/blob/master-MC1.7.10/src/main/scala/li/cil/oc/server/network/Network.scala
We should implement this, as it not only makes behavior match better, but also optimizes
it a lot. We do need to replace the NEIGHBORS rule with implementing the SOME visibility.
A direct port is not needed, just taking heavy inspiration from OC.
## Optimize power balancing
Use a smarter algorithm to prevent N storage nodes from iterating N^2 nodes each tick
to balance power.
# Computation
> Pretty important for a computer mod
## JNI
We need the JNI system so we can salvage our hard labor thrown into NeoNucleus.
Also because it is a capable engine and has a good API for architectures, and NCL is very capable.
## Worker threads
Computers need worker threads for running non-synchronized code, because otherwise we're cooked
## Entities as machines
Aside from blocks like cases and robots, we should also support entities like drones.
Not only for OC parity, but also as addons would def love that.

View File

@@ -11,6 +11,15 @@ plugins {
val minecraft = stonecutter.current.version val minecraft = stonecutter.current.version
val loader = loom.platform.get().name.lowercase() val loader = loom.platform.get().name.lowercase()
fun minecraftVersionToNum(ver: String): Int {
val parts = ver.split(".")
if(parts.size == 2) {
// 26.1 and above
return parts[0].toInt() * 10000 + parts[1].toInt()
}
return parts[1].toInt() * 100 + parts[2].toInt()
}
version = "${mod.version}+$minecraft" version = "${mod.version}+$minecraft"
group = mod.group group = mod.group
base { base {
@@ -21,6 +30,7 @@ architectury.common(stonecutter.tree.branches.mapNotNull {
if (stonecutter.current.project !in it) null if (stonecutter.current.project !in it) null
else it.prop("loom.platform") else it.prop("loom.platform")
}) })
repositories { repositories {
maven("https://maven.neoforged.net/releases/") maven("https://maven.neoforged.net/releases/")
@@ -32,12 +42,41 @@ repositories {
name = "Kotlin for Forge" name = "Kotlin for Forge"
setUrl("https://thedarkcolour.github.io/KotlinForForge/") setUrl("https://thedarkcolour.github.io/KotlinForForge/")
} }
maven {
name = "ParchmentMC"
setUrl("https://maven.parchmentmc.org")
}
} }
dependencies { dependencies {
minecraft("com.mojang:minecraft:$minecraft") minecraft("com.mojang:minecraft:$minecraft")
mappings(loom.officialMojangMappings()) // mappings(loom.officialMojangMappings())
modApi("dev.architectury:architectury:${mod.dep("architectury_version")}") mappings(loom.layered(){
var date = ""
when (minecraft) {
"1.20.1" -> date = "2023.09.03"
"1.20.4" -> date = "2024.04.14"
"1.21.1" -> date = "2024.11.17"
"1.21.9" -> date = "2025.10.05"
"1.21.11" -> date = "2025.12.20"
else -> date="idk lol 67"
}
officialMojangMappings() // TODO: versions
parchment("org.parchmentmc.data:parchment-${minecraft}:${date}@zip")
})
var archversion = "idk"
when(minecraft) { // NOTE: add more entries if you want to add more versions
"1.20.1" -> archversion = "9.2.14"
"1.20.4" -> archversion = "11.1.17"
"1.21.1" -> archversion = "13.0.8"
"1.21.9" -> archversion = "18.0.5"
else -> archversion = "19.0.1"
}
// modImplementation("dev.architectury:architectury:$rootProject.architectury_api_version")
// modImplementation("dev.architectury:architectury:${archversion}")
modImplementation("dev.architectury:architectury:${archversion}")
if (loader == "fabric") { if (loader == "fabric") {
modImplementation("net.fabricmc:fabric-loader:${mod.dep("fabric_loader")}") modImplementation("net.fabricmc:fabric-loader:${mod.dep("fabric_loader")}")
// mappings("net.fabricmc:yarn:$minecraft+build.${mod.dep("yarn_build")}:v2") // mappings("net.fabricmc:yarn:$minecraft+build.${mod.dep("yarn_build")}:v2")
@@ -45,16 +84,26 @@ dependencies {
//some features (like automatic resource loading from non vanilla namespaces) work only with fabric API installed //some features (like automatic resource loading from non vanilla namespaces) work only with fabric API installed
//for example translations from assets/modid/lang/en_us.json won't be working, same stuff with textures //for example translations from assets/modid/lang/en_us.json won't be working, same stuff with textures
//but we keep runtime only to not accidentally depend on fabric's api, because it doesn't exist in neo/forge //but we keep runtime only to not accidentally depend on fabric's api, because it doesn't exist in neo/forge
modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${mod.dep("fabric_version")}") //modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${mod.dep("fabric_version")}")
modImplementation("net.fabricmc.fabric-api:fabric-api:${mod.dep("fabric_version")}")
modImplementation("net.fabricmc:fabric-language-kotlin:1.13.10+kotlin.2.3.20") modImplementation("net.fabricmc:fabric-language-kotlin:1.13.10+kotlin.2.3.20")
modApi("dev.architectury:architectury-fabric:${mod.dep("architectury_version")}") modApi("dev.architectury:architectury-fabric:${archversion}")
fun getTeamRebornEnergy(): String {
val curVer = minecraftVersionToNum(minecraft)
if (curVer < minecraftVersionToNum("1.20.5")) { return "3.0.0" }
if (curVer < minecraftVersionToNum("1.21.0")) { return "4.0.0" }
if (curVer < minecraftVersionToNum("1.21.5")) { return "4.1.0" }
if (curVer < minecraftVersionToNum("26.1")) { return "4.2.0" }
return "5.0.0"
}
modApi("teamreborn:energy:${getTeamRebornEnergy()}")
} }
if (loader == "forge") { if (loader == "forge") {
"forge"("net.minecraftforge:forge:${minecraft}-${mod.dep("forge_loader")}") "forge"("net.minecraftforge:forge:${minecraft}-${mod.dep("forge_loader")}")
//implementation("thedarkcolour:kotlinforforge:1.16.0") //implementation("thedarkcolour:kotlinforforge:1.16.0")
// mappings("net.fabricmc:yarn:$minecraft+build.${mod.dep("yarn_build")}:v2") // mappings("net.fabricmc:yarn:$minecraft+build.${mod.dep("yarn_build")}:v2")
modApi("dev.architectury:architectury-forge:${mod.dep("architectury_version")}") modApi("dev.architectury:architectury-forge:${archversion}")
"io.github.llamalad7:mixinextras-forge:${mod.dep("mixin_extras")}".let { "io.github.llamalad7:mixinextras-forge:${mod.dep("mixin_extras")}".let {
implementation(it) implementation(it)
include(it) include(it)
@@ -69,8 +118,10 @@ dependencies {
// } // }
// }) // })
modApi("dev.architectury:architectury-forge:${mod.dep("architectury_version")}") if (minecraft=="1.21.9" || minecraft=="1.21.11") modApi("dev.architectury:architectury-neoforge:${archversion}")
else modApi("dev.architectury:architectury-forge:${archversion}") // NOTE: this could be wrong
implementation("thedarkcolour:kotlinforforge-neoforge:6.0.0") implementation("thedarkcolour:kotlinforforge-neoforge:6.0.0")
} }
} }
buildscript { buildscript {
@@ -225,3 +276,14 @@ tasks.build {
group = "versioned" group = "versioned"
description = "Must run through 'chiseledBuild'" description = "Must run through 'chiseledBuild'"
} }
if (loader == "fabric") {
fabricApi {
if (minecraftVersionToNum(minecraft) > minecraftVersionToNum("1.21.4")) {
configureDataGeneration() {
client = true
}
} else {
configureDataGeneration()
}
}
}

View File

@@ -24,7 +24,6 @@ mod.mc_targets=[VERSIONED]
deps.mixin_extras=0.4.1 deps.mixin_extras=0.4.1
deps.fabric_loader=0.18.3 deps.fabric_loader=0.18.3
deps.fabric_version=[VERSIONED] deps.fabric_version=[VERSIONED]
deps.architectury_version=9.1.12
deps.forge_loader=[VERSIONED] deps.forge_loader=[VERSIONED]
deps.neoforge_loader=[VERSIONED] deps.neoforge_loader=[VERSIONED]

Binary file not shown.

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

310
gradlew vendored Normal file → Executable file
View File

@@ -1,78 +1,128 @@
#!/usr/bin/env sh #!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" # This is normally unused
APP_BASE_NAME=`basename "$0"` # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
DEFAULT_JVM_OPTS="" APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,96 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
MAX_FD="$MAX_FD_LIMIT" # shellcheck disable=SC2039,SC3045
fi MAX_FD=$( ulimit -H -n ) ||
ulimit -n $MAX_FD warn "Could not query maximum file descriptor limit"
if [ $? -ne 0 ] ; then esac
warn "Could not set maximum file descriptor limit: $MAX_FD" case $MAX_FD in #(
fi '' | soft) :;; #(
else *)
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
fi # shellcheck disable=SC2039,SC3045
fi ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=$(save "$@") # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
if $JAVACMD --add-opens java.base/java.lang=ALL-UNNAMED -version ; then if "$cygwin" || "$msys" ; then
DEFAULT_JVM_OPTS="--add-opens java.base/java.lang=ALL-UNNAMED $DEFAULT_JVM_OPTS" APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi fi
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
cd "$(dirname "$0")"
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

75
gradlew.bat vendored
View File

@@ -1,3 +1,21 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@@ -10,24 +28,28 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS= set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -35,48 +57,35 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

BIN
rack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -23,7 +23,7 @@ stonecutter {
} }
//i would recommend to use neoforge for mc > 1.20.1, i haven't tested neocomputers for forge on versions higher than that //i would recommend to use neoforge for mc > 1.20.1, i haven't tested neocomputers for forge on versions higher than that
//mc("fabric","1.20.1","1.20.4", "1.21.1", "1.21.3", "1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11") //mc("fabric","1.20.1","1.20.4", "1.21.1", "1.21.3", "1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11")
mc("fabric", "1.20.1", "1.20.4", "1.21.9", "1.21.11") mc("fabric", "1.20.1", "1.20.4", "1.21.1", "1.21.9", "1.21.11")
mc("forge", "1.20.1") mc("forge", "1.20.1")
//WARNING: neoforge uses mods.toml instead of neoforge.mods.toml for versions 1.20.4 (?) and earlier //WARNING: neoforge uses mods.toml instead of neoforge.mods.toml for versions 1.20.4 (?) and earlier
//mc("neoforge", "1.20.4", "1.21.1", "1.21.3", "1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11") //mc("neoforge", "1.20.4", "1.21.1", "1.21.3", "1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11")

View File

@@ -0,0 +1,42 @@
package org.neoflock.neocomputers.platforms.fabric.client;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
import net.minecraft.client.color.item.ItemColor;
import net.minecraft.client.color.item.ItemColors;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.ItemLike;
import org.neoflock.neocomputers.NeoComputers;
import org.neoflock.neocomputers.block.Blocks;
import org.neoflock.neocomputers.block.CableBlock;
import org.neoflock.neocomputers.entity.BlockEntities;
import org.neoflock.neocomputers.entity.render.*;
import org.neoflock.neocomputers.item.Items;
import org.neoflock.neocomputers.platforms.fabric.client.model.ModelLoader;
public class NeoComputersFabricClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ModelLoadingPlugin.register(new ModelLoader());
BlockEntityRenderers.register(BlockEntities.INSTANCE.getSCREEN_ENTITY().get(), ScreenEntityRenderer::new); // TODO: put this in common
BlockEntityRenderers.register(BlockEntities.INSTANCE.getCASE_ENTITY().get(), CaseEntityRenderer::new);
BlockEntityRenderers.register(BlockEntities.INSTANCE.getRELAY_ENTITY().get(), RelayEntityRenderer::new);
BlockEntityRenderers.register(BlockEntities.INSTANCE.getROBOT_ENTITY().get(), RobotEntityRenderer::new);
BlockEntityRenderers.register(BlockEntities.INSTANCE.getRACK_ENTITY().get(), RackEntityRenderer::new);
ColorProviderRegistry.BLOCK.register((state, world, pos, index) -> {
if (index == 0) {
return state.getValue(CableBlock.Companion.getCOLOR()).getTextureDiffuseColor();
} else {
return 0xFFFFFF;
}
}, Blocks.INSTANCE.getCABLE_BLOCK().get());
ColorProviderRegistry.ITEM.register((stack, index)-> {
return DyeColor.LIGHT_GRAY.getTextureDiffuseColor();
}, Items.INSTANCE.getITEMS().getRegistrar().get(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "cable")));
}
}

View File

@@ -0,0 +1,59 @@
package org.neoflock.neocomputers.platforms.fabric.client.model;
import kotlin.jvm.functions.Function1;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.impl.renderer.VanillaModelEncoder;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import org.neoflock.neocomputers.NeoComputers;
import org.neoflock.neocomputers.block.model.AbstractModel;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
public class FabricModelWrapper implements FabricBakedModel, UnbakedModel {
private AbstractModel model;
public FabricModelWrapper(AbstractModel model) {
this.model = model;
}
@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
VanillaModelEncoder.emitBlockQuads(model, state, randomSupplier, context);
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
VanillaModelEncoder.emitItemQuads(model, null, randomSupplier, context);
}
@Override
public Collection<ResourceLocation> getDependencies() {
return List.of();
}
@Override
public void resolveParents(Function<ResourceLocation, UnbakedModel> resolver) {
}
@Override
public @Nullable BakedModel bake(ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState state) {
// NeoComputers.INSTANCE.getLOGGER().info("{}", spriteGetter.apply(new Material()));
model._bake(spriteGetter);
// model.bake((Function1<? super ResourceLocation, ? extends TextureAtlasSprite>) Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS));
return model;
}
}

View File

@@ -0,0 +1,23 @@
package org.neoflock.neocomputers.platforms.fabric.client.model;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import org.neoflock.neocomputers.NeoComputers;
import org.neoflock.neocomputers.block.model.RobotModel;
public class ModelLoader implements ModelLoadingPlugin {
public static final ResourceLocation ROBOT = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "robot");
@Override
public void onInitializeModelLoader(Context pluginContext) {
pluginContext.modifyModelOnLoad().register((original, context) -> {
final ModelResourceLocation id = context.topLevelId();
if (id != null && id.id().equals(ROBOT)) {
return new FabricModelWrapper(new RobotModel());
}
return original;
});
}
}

View File

@@ -1,8 +1,7 @@
//? if neoforge { //? if neoforge {
/*package org.neoflock.neocomputers.neocomputers.platforms.neoforge; /*package org.neoflock.neocomputers.platforms.neoforge;
import org.neoflock.neocomputers.ConfigScreen; import org.neoflock.neocomputers.ConfigScreen;
import org.neoflock.neocomputers.NeoComputersInit;
import org.neoflock.neocomputers.ModPlatform; import org.neoflock.neocomputers.ModPlatform;
import net.neoforged.fml.ModList; import net.neoforged.fml.ModList;
import net.neoforged.fml.ModLoadingContext; import net.neoforged.fml.ModLoadingContext;

View File

@@ -1,19 +1,164 @@
package org.neoflock.neocomputers package org.neoflock.neocomputers
import dev.architectury.event.events.client.ClientLifecycleEvent
import dev.architectury.event.events.common.LifecycleEvent
import dev.architectury.event.events.common.PlayerEvent
import dev.architectury.event.events.common.TickEvent
import dev.architectury.networking.NetworkManager
import dev.architectury.platform.Platform
import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.block.Blocks
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.gui.menu.Menus
import dev.architectury.utils.Env
import dev.architectury.utils.EnvExecutor
import net.minecraft.client.Minecraft
import net.minecraft.server.level.ServerPlayer
import org.neoflock.neocomputers.block.DeviceBlockEntity
import org.neoflock.neocomputers.gui.render.ScreenRenderer
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.item.Items
import org.neoflock.neocomputers.item.Tabs
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.NodeSynchronizer
import org.neoflock.neocomputers.sounds.Sounds
import org.neoflock.neocomputers.utils.FontProvider
import org.neoflock.neocomputers.utils.GenericContainerScreen
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
object NeoComputers { object NeoComputers {
const val MODID: String = "neocomputers" const val MODID: String = "neocomputers"
val LOGGER: Logger = LoggerFactory.getLogger("NeoComputers") val LOGGER: Logger = LoggerFactory.getLogger("NeoComputers")
var PLATFORM: ModPlatform? = null var PLATFORM: ModPlatform? = null
// val BlockEntityRenderType: RenderType = RenderType.create(
// "nc_blockentities",
// DefaultVertexFormat.POSITION_TEX,
// VertexFormat.Mode.QUADS,
// 0xc000, RenderType.CompositeState.builder().setShaderState(RenderStateShard.POSITION_TEX_SHADER).createCompositeState(false)) // TODO: figure out correct buffer size and composite state
fun entrypoint(platform: ModPlatform?) { fun entrypoint(platform: ModPlatform?) {
PLATFORM = platform PLATFORM = platform
Blocks.BLOCKS.register()
Blocks.registerBlockItems()
Items.ITEMS.register()
BlockEntities.BLOCKENTITIES.register()
BlockEntities.registerPowerBlocks()
Menus.MENUS.register()
Tabs.TABS.register()
Sounds.SOUNDS.register()
ComponentRoles.mapDefaultTextures()
// I don't know why architectury wants two lambdas but whatever
EnvExecutor.runInEnv(Env.CLIENT) {{
ClientLifecycleEvent.CLIENT_SETUP.register {
Menus.registerScreens()
Networking.allNodes.remove()
Networking.wirelessNodes.remove()
Networking.channels.remove()
}
ClientLifecycleEvent.CLIENT_STARTED.register {
FontProvider.load(ResourceLocation.fromNamespaceAndPath(MODID, "font/unscii.hex"))
}
ClientLifecycleEvent.CLIENT_STOPPING.register {
ScreenRenderer.cleanUnboundTex()
}
}}
TickEvent.SERVER_POST.register {
Networking.tickAllNodes()
NodeSynchronizer.syncScreens()
}
TickEvent.PLAYER_POST.register {
Sounds.tickCustomSounds()
}
LifecycleEvent.SERVER_STARTING.register {
Networking.allNodes.remove()
Networking.wirelessNodes.remove()
Networking.channels.remove()
}
PlayerEvent.CLOSE_MENU.register {
player, menu ->
if(player is ServerPlayer) NodeSynchronizer.playerScreenClosed(player)
}
PlayerEvent.PLAYER_QUIT.register {
player ->
NodeSynchronizer.playerScreenClosed(player)
}
// networking has no way to define a C2S packet type, so we need the listener on both
// however, defining it separately on both breaks both ends
// so we define it once, but on both platforms
if(Platform.getEnvironment() == Env.CLIENT || Platform.getEnvironment() == Env.SERVER) {
NetworkManager.registerReceiver(NetworkManager.c2s(),NodeSynchronizer.ScreenDataPayload.TYPE, NodeSynchronizer.ScreenDataPayload.CODEC, {
packet, ctx ->
val player = ctx.player
if(player is ServerPlayer) {
val ent = NodeSynchronizer.screenMap[player]
if(ent is DeviceNode) {
ent.processScreenInteraction(player, packet.buffer)
}
}
})
NetworkManager.registerReceiver(NetworkManager.c2s(),NodeSynchronizer.DeviceBlockStateRequest.TYPE, NodeSynchronizer.DeviceBlockStateRequest.CODEC, {
packet, ctx ->
val player = ctx.player
val level = player.level()
val dist = packet.blockPos.center.distanceTo(player.position())
if(player is ServerPlayer && dist <= NodeSynchronizer.MAX_STATE_DISTANCE_ALLOWED) {
val ent = level.getBlockEntity(packet.blockPos)
if(ent is DeviceBlockEntity) {
ent.sendStateToPlayer(player)
}
}
})
}
// we have to do this because the datagen task runs in the physical server
EnvExecutor.runInEnv(Env.CLIENT) {{
NetworkManager.registerReceiver(NetworkManager.s2c(),NodeSynchronizer.DeviceBlockStatePayload.TYPE, NodeSynchronizer.DeviceBlockStatePayload.CODEC, {
packet, ctx ->
val level = ctx.player.level()
val ent = level.getBlockEntity(packet.blockPos)
if(ent is DeviceBlockEntity) {
ent.processCommits(packet.buffers)
}
})
NetworkManager.registerReceiver(NetworkManager.s2c(),NodeSynchronizer.ScreenPayload.TYPE, NodeSynchronizer.ScreenPayload.CODEC, {
packet, ctx ->
val scr = Minecraft.getInstance().screen
if(scr is GenericContainerScreen<*>) {
scr.processScreenStatePacket(packet.buffer)
}
})
NetworkManager.registerReceiver(NetworkManager.s2c(),NodeSynchronizer.BeepDataPayload.TYPE, NodeSynchronizer.BeepDataPayload.CODEC, {
packet, ctx ->
// TODO: implement volume
Sounds.beep(packet.pos.center, packet.pattern, packet.freq, packet.duration.toMillis().toInt())
})
}}
EnvExecutor.runInEnv(Env.SERVER) {{
// https://github.com/architectury/architectury-api/issues/518
NetworkManager.registerS2CPayloadType(NodeSynchronizer.DeviceBlockStatePayload.TYPE, NodeSynchronizer.DeviceBlockStatePayload.CODEC)
NetworkManager.registerS2CPayloadType(NodeSynchronizer.ScreenPayload.TYPE, NodeSynchronizer.ScreenPayload.CODEC)
NetworkManager.registerS2CPayloadType(NodeSynchronizer.BeepDataPayload.TYPE, NodeSynchronizer.BeepDataPayload.CODEC)
}}
LOGGER.info("Registered!")
//LOGGER.info("Started mod in %s loader".formatted(NeoComputersInit.PLATFORM.getModloader())) //LOGGER.info("Started mod in %s loader".formatted(NeoComputersInit.PLATFORM.getModloader()))
//LOGGER.info("Kotlin: %s".formatted(NeoComputers.hello())) //LOGGER.info("Kotlin: %s".formatted(NeoComputers.hello()))
LOGGER.info("Started mod in ${NeoComputers.PLATFORM?.modloader} loader") LOGGER.info("Started mod in ${PLATFORM?.modloader} loader")
LOGGER.info("Hello from kotlin!") LOGGER.info("Hello from kotlin!")
} }
} }

View File

@@ -1,14 +1,19 @@
package org.neoflock.neocomputers.block package org.neoflock.neocomputers.block
import dev.architectury.registry.registries.RegistrySupplier
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.resources.ResourceKey
import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockBehaviour
import org.neoflock.neocomputers.NeoComputers
import java.util.function.Supplier
import com.google.common.base.Suppliers
class BaseBlock : Block { open class BaseBlock(properties: Properties = Properties.of()) : Block(properties) { // TODO: create a TieredBaseBlock class that extends this or something
protected val tier: Int // val tier: Int
constructor(tier: Int): super(Properties.of()) {
this.tier = tier
}
public fun getTier(): Int { companion object Registry {
return tier fun register(name: String, sup: Supplier<BaseBlock>): RegistrySupplier<Block> = Blocks.BLOCKS.register(name, sup);
} }
} }

View File

@@ -1,50 +1,48 @@
package org.libreflock.neocomputers.block package org.neoflock.neocomputers.block
import com.google.common.base.Suppliers
import dev.architectury.registry.registries.DeferredRegister import dev.architectury.registry.registries.DeferredRegister
import dev.architectury.registry.registries.Registrar
import dev.architectury.registry.registries.RegistrarManager
import dev.architectury.registry.registries.RegistrySupplier import dev.architectury.registry.registries.RegistrySupplier
import net.minecraft.core.registries.Registries import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.resources.ResourceKey
import net.minecraft.world.item.BlockItem import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Block
import org.libreflock.neocomputers.item.Items import net.minecraft.world.level.block.state.BlockBehaviour
import org.libreflock.neocomputers.item.Tabs // import org.neoflock.neocomputers.item.Tabs
// import org.neoflock.neocomputers.item.Items
import org.neoflock.neocomputers.NeoComputers import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.BaseBlock import org.neoflock.neocomputers.block.BaseBlock
import org.neoflock.neocomputers.item.Items
import org.neoflock.neocomputers.item.Tabs
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Supplier import java.util.function.Supplier
object Blocks { object Blocks {
val BLOCKS: DeferredRegister<Block?> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK)
val CASE: MutableList<RegistrySupplier<Block?>?>? = val BLOCKS: DeferredRegister<Block> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK)
BaseBlock.register(intArrayOf(0, 1, 2), "case", { tier -> CaseBlock(tier) }) val SCREEN_BLOCK: RegistrySupplier<Block> = BaseBlock.register("screen") { ScreenBlock() }
val CAPACITOR_BLOCK: RegistrySupplier<Block> = BaseBlock.register("capacitor") { CapacitorBlock(1) }
// public static final RegistrySupplier<Block> CASE0 = BLOCKS.register("case0", () -> new CaseBlock(0)); val CAPACITOR_BLOCK2: RegistrySupplier<Block> = BaseBlock.register("capacitor2") { CapacitorBlock(2) }
// public static final RegistrySupplier<Block> CASE1 = BLOCKS.register("case1", () -> new CaseBlock(1)); val CAPACITOR_BLOCK3: RegistrySupplier<Block> = BaseBlock.register("capacitor3") { CapacitorBlock(3) }
// public static final RegistrySupplier<Block> CASE2 = BLOCKS.register("case2", () -> new CaseBlock(2)); val SOLARGEN_BLOCK: RegistrySupplier<Block> = BaseBlock.register("solargen") { SolarGeneratorBlock() }
// public static final RegistrySupplier<Block> CABLE = BLOCKS.register("cable", () -> new CableBlock()); val COMBUSTGEN_BLOCK: RegistrySupplier<Block> = BaseBlock.register("combustgen") { CombustionGeneratorBlock() }
val SCREEN: RegistrySupplier<Block?>? = BLOCKS.register<Block?>("screen", Supplier { ScreenBlock() }) val CASE_BLOCK: RegistrySupplier<Block> = BaseBlock.register("case") { CaseBlock() }
val CABLE: RegistrySupplier<Block?>? = BLOCKS.register<Block?>("cable", Supplier { CableBlock() }) val REDSTONEIO_BLOCK: RegistrySupplier<Block> = BaseBlock.register("redio") { RedstoneIOBlock() }
val CABLE_BLOCK: RegistrySupplier<Block> = BaseBlock.register("cable") { CableBlock() }
val RELAY_BLOCK: RegistrySupplier<Block> = BaseBlock.register("relay") { RelayBlock() }
val ROBOT_BLOCK: RegistrySupplier<Block> = BaseBlock.register("robot") { RobotBlock() }
val RACK_BLOCK: RegistrySupplier<Block> = BaseBlock.register("rack") { RackBlock() }
fun registerBlockItems() { fun registerBlockItems() {
BLOCKS.forEach(Consumer { sup: RegistrySupplier<Block?>? -> BLOCKS.forEach(Consumer { sup: RegistrySupplier<Block> ->
// sup.pre NeoComputers.LOGGER.info(sup.id.toString())
// sup.((blk) -> { val id = ResourceKey.create(Registries.ITEM, sup.id)
// NeoComputers.LOGGER.info(blk.getDescriptionId()); Items.ITEMS.register(sup.id.path) { BlockItem(sup.get()!!, Item.Properties().`arch$tab`(Tabs.TAB))}
// if (blk instanceof BaseBlock) {
// Items.ITEMS.register(sup.getId().getPath(), () -> new BaseBlock.BaseBlockItem(blk, new Item.Properties().arch$tab(Tabs.TAB)));
// } else {
// Items.ITEMS.register(sup.getId().getPath(), () -> new BlockItem(blk, new Item.Properties().arch$tab(Tabs.TAB)));
// }
// });
Items.ITEMS.register(sup!!.getId().getPath(), {
if (sup.get() is BaseBlock) {
return@register BaseBlockItem(sup.get(), Item.Properties().`arch$tab`(Tabs.TAB))
} else {
return@register BlockItem(sup.get(), Item.Properties().`arch$tab`(Tabs.TAB))
}
})
}) })
} }
} }

View File

@@ -0,0 +1,176 @@
package org.neoflock.neocomputers.block
import net.minecraft.client.renderer.blockentity.PistonHeadRenderer
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.InteractionHand
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.DyeItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.BooleanOp
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.CableEntity
class CableBlock() : DeviceBlock(Properties.of()), EntityBlock {
companion object {
val NORTH = BooleanProperty.create("north")
val EAST = BooleanProperty.create("east")
val WEST = BooleanProperty.create("west")
val SOUTH = BooleanProperty.create("south")
val UP = BooleanProperty.create("up")
val DOWN = BooleanProperty.create("down")
val COLOR = EnumProperty<DyeColor>.create("color", DyeColor::class.java)
val MIN = 0.375
val MAX = 1-MIN
val shapeCache: Array<VoxelShape?> = arrayOfNulls(Direction.entries.size*Direction.entries.size*Direction.entries.size*Direction.entries.size*Direction.entries.size*Direction.entries.size)
fun calcIdx(north: Boolean, south: Boolean, east: Boolean, west: Boolean, up: Boolean, down: Boolean): Int {
var idx = if (down) 1 else 0
idx += 2*(if (up) 1 else 0)
idx += 4*(if (west) 1 else 0)
idx += 8*(if (east) 1 else 0)
idx += 16*(if (south) 1 else 0)
idx += 32*(if (north) 1 else 0)
return idx
}
fun makeShapes() {
NeoComputers.LOGGER.info("[CABLE] recomputing shapes")
for (north in arrayOf(false, true)) { // shut up
for (south in arrayOf(false, true)) {
for (east in arrayOf(false, true)) {
for (west in arrayOf(false, true)) {
for (up in arrayOf(false, true)) {
for (down in arrayOf(false, true)) {
val shape = makeShape(north, south, east, west, up, down)
val idx = calcIdx(north, south, east,west, up, down)
shapeCache[idx] = shape;
}
}
}
}
}
}
}
fun makeShape(north: Boolean, south: Boolean, east: Boolean, west: Boolean, up: Boolean, down: Boolean): VoxelShape {
var shape = Shapes.box(MIN, MIN, MIN, MAX, MAX, MAX)
if (north) shape = Shapes.join(shape, Shapes.box(MIN, MIN, 0.0, MAX, MAX, MIN ), BooleanOp.OR)
if (south) shape = Shapes.join(shape, Shapes.box(MIN, MIN, MAX, MAX, MAX, 1.0 ), BooleanOp.OR)
if (east) shape = Shapes.join(shape, Shapes.box(MAX, MIN, MIN, 1.0, MAX, MAX), BooleanOp.OR)
if (west) shape = Shapes.join(shape, Shapes.box(0.0, MIN, MIN, MIN, MAX, MAX ), BooleanOp.OR)
if (up) shape = Shapes.join(shape, Shapes.box(MIN, MAX, MIN, MAX, 1.0, MAX), BooleanOp.OR)
if (down) shape = Shapes.join(shape, Shapes.box(MIN, 0.0, MIN, MAX, MIN, MAX ), BooleanOp.OR)
return shape
}
fun getPropByDirection(direction: Direction): BooleanProperty {
return when (direction) {
Direction.NORTH -> NORTH
Direction.SOUTH -> SOUTH
Direction.WEST -> WEST
Direction.EAST -> EAST
Direction.UP -> UP
Direction.DOWN -> DOWN
}
}
fun shouldConnect(pos: BlockPos, npos: BlockPos, level: Level): Boolean {
val ent = level.getBlockEntity(npos)
val blockState = level.getBlockState(pos)
val theirState = level.getBlockState(npos)
val universal = DyeColor.LIGHT_GRAY
if(ent is CableEntity) {
val ourColor = blockState.getValue(COLOR)
val theirColor = theirState.getValue(COLOR)
if(ourColor.equals(universal)) return true
if(theirColor.equals(universal)) return true
if(ourColor.equals(theirColor)) return true
return false
}
return ent is DeviceBlockEntity
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(NORTH, false)
.setValue(EAST, false)
.setValue(WEST, false)
.setValue(SOUTH, false)
.setValue(UP, false)
.setValue(DOWN, false)
.setValue(COLOR, DyeColor.LIGHT_GRAY)
)
makeShapes()
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
super.createBlockStateDefinition(builder
.add(NORTH)
.add(EAST)
.add(SOUTH)
.add(WEST)
.add(UP)
.add(DOWN)
.add(COLOR))
}
override fun newBlockEntity(pos: BlockPos, state: BlockState) = CableEntity(pos, state)
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape? {
val idx = calcIdx(state.getValue(NORTH), state.getValue(SOUTH), state.getValue(EAST), state.getValue(WEST), state.getValue(UP), state.getValue(DOWN))
return shapeCache[idx];
// return makeShape(state.getValue(NORTH), state.getValue(SOUTH), state.getValue(EAST), state.getValue(WEST), state.getValue(UP), state.getValue(DOWN))
}
override fun neighborChanged(state: BlockState, level: Level, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, movedByPiston: Boolean) {
// val neighbors = getNeighbourEntities(blockPos, level)
// for (dir in Direction.entries) {
// val ent = level.getBlockEntity(blockPos.relative(dir))
// level.setBlockAndUpdate(blockPos, blockState.setValue(getPropByDirection(dir), (ent is NodeBlockEntity || ent is CableEntity)))
// }
super.neighborChanged(state, level, pos, neighborBlock, neighborPos, movedByPiston)
val diff = pos.subtract(neighborPos)
val dir = Direction.fromDelta(diff.x, diff.y, diff.z)!!.opposite
val ent = level.getBlockEntity(neighborPos)
// val value = ent is NodeBlockEntity || (ent is CableEntity && (level.getBlockState(neighborPos).getValue(COLOR).equals(state.getValue(COLOR)) || state.getValue(COLOR).equals(DyeColor.LIGHT_GRAY)))
level.setBlockAndUpdate(pos, state.setValue(getPropByDirection(dir), shouldConnect(pos, neighborPos, level)))
}
override fun useItemOn(stack: ItemStack, state: BlockState, level: Level, pos: BlockPos, player: Player, hand: InteractionHand, hitResult: BlockHitResult): ItemInteractionResult? {
// return super.useItemOn(stack, state, level, pos, player, hand, hitResult)
if (stack.item is DyeItem) {
val dyeitem = stack.item as DyeItem
level.setBlockAndUpdate(pos, state.setValue(COLOR, dyeitem.dyeColor))
val ent = level.getBlockEntity(pos)
if(ent is CableEntity) {
ent.connectionsAreDirty = true
}
return ItemInteractionResult.SUCCESS
}
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
}
}

View File

@@ -0,0 +1,86 @@
package org.neoflock.neocomputers.block
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.OutgoingChatMessage
import net.minecraft.network.chat.PlayerChatMessage
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.PowerRole
import kotlin.math.min
open class CapacitorEntity(val capacity: Long, type: BlockEntityType<*>, pos: BlockPos, state: BlockState) : DeviceBlockEntity(type, pos, state) {
val deviceNode = object : DeviceNode() {
override var powerRole = PowerRole.STORAGE
override var energyCapacity: Long = capacity
override fun writeFullStateCommit(buf: FriendlyByteBuf) {
super.writeFullStateCommit(buf)
buf.writeVarLong(energy)
}
override fun processCommit(buf: FriendlyByteBuf) {
super.processCommit(buf)
energy = buf.readVarLong()
}
}
// TODO: cache list
override fun getDeviceNodes() = listOf(deviceNode)
override fun getNodeFromSide(directionToRequester: Direction) = deviceNode
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
deviceNode.energy = min(tag.getLong("energy"), deviceNode.energyCapacity)
}
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.saveAdditional(compoundTag, provider)
compoundTag.putLong("energy", deviceNode.energy)
}
}
class CapacitorEntityTier1(pos: BlockPos, state: BlockState): CapacitorEntity(20000, BlockEntities.CAPACITOR_ENTITY.get(), pos, state)
class CapacitorEntityTier2(pos: BlockPos, state: BlockState): CapacitorEntity(50000, BlockEntities.CAPACITOR2_ENTITY.get(), pos, state)
class CapacitorEntityTier3(pos: BlockPos, state: BlockState): CapacitorEntity(100000, BlockEntities.CAPACITOR3_ENTITY.get(), pos, state)
class CapacitorBlock(val tier: Int) : DeviceBlock() {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity {
val cap: CapacitorEntity = when(tier) {
1 -> CapacitorEntityTier1(blockPos, blockState)
2 -> CapacitorEntityTier2(blockPos, blockState)
3 -> CapacitorEntityTier3(blockPos, blockState)
else -> throw UnsupportedOperationException("unsupported tier: $tier")
}
return cap
}
override fun useWithoutItem(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
if(level.isClientSide()) {
val ent = level.getBlockEntity(blockPos)
if(ent is CapacitorEntity) {
if(player.isCrouching) ent.deviceNode.giveEnergy(1)
val msg = PlayerChatMessage.system("energy: ${ent.deviceNode.energy} / ${ent.capacity} (${ent.deviceNode.connections.size} connections, ${ent.deviceNode.getReachable().size} reachable)")
player.sendSystemMessage(OutgoingChatMessage.create(msg).content())
}
}
return InteractionResult.SUCCESS
}
}

View File

@@ -0,0 +1,137 @@
package org.neoflock.neocomputers.block;
import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.FurnaceBlock
import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.CombustionGeneratorBlock.Companion.COMBUSTGEN_ACTIVE
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.CaseBlockEntity
import org.neoflock.neocomputers.entity.MachineEntity
import org.neoflock.neocomputers.network.NodeSynchronizer
import org.neoflock.neocomputers.sounds.Sounds
class CaseBlock() : DeviceBlock(Properties.of().sound(SoundType.METAL).lightLevel(CaseBlock::getLuminance).noOcclusion()) { // placeholder stuff
companion object {
val FACING: EnumProperty<Direction> = EnumProperty.create<Direction>("facing", Direction::class.java)
val COMPUTER_RUNNING = BooleanProperty.create("running")!!
fun getLuminance(blockState: BlockState): Int {
return if(blockState.getValue(COMPUTER_RUNNING)) 3 else 0
}
}
init {
registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(COMPUTER_RUNNING, false))
}
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState) = CaseBlockEntity(blockPos, blockState)
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(COMPUTER_RUNNING)
builder.add(FACING)
}
fun getMachine(level: BlockGetter, blockPos: BlockPos): CaseBlockEntity {
return level.getBlockEntity(blockPos) as CaseBlockEntity
}
override fun isSignalSource(state: BlockState): Boolean = true
override fun getSignal(
blockState: BlockState,
blockGetter: BlockGetter,
blockPos: BlockPos,
direction: Direction
): Int {
return getMachine(blockGetter, blockPos).redstoneOut[direction.opposite.ordinal]
}
override fun onPlace(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
blockState2: BlockState,
bl: Boolean
) {
if(!level.isClientSide) {
level.updateNeighborsAt(blockPos, this)
getMachine(level, blockPos).refetchAllRedstone()
}
super.onPlace(blockState, level, blockPos, blockState2, bl)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
return defaultBlockState().setValue(FACING, context.horizontalDirection.opposite)
}
override fun neighborChanged(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
block: Block,
blockPos2: BlockPos,
bl: Boolean
) {
super.neighborChanged(blockState, level, blockPos, block, blockPos2, bl)
if(!level.isClientSide) {
val dir = Direction.getNearest(blockPos2.center.subtract(blockPos.center))
getMachine(level, blockPos).refetchRedstone(dir)
}
}
override fun useWithoutItem(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
if(!level.isClientSide) {
val ent = level.getBlockEntity(blockPos, BlockEntities.CASE_ENTITY.get()).get()
if(player.isCrouching) {
// Quickstat
ent.start()
} else {
// Open menu
MenuRegistry.openMenu(player as ServerPlayer, ent)
NodeSynchronizer.registerPlayerScreen(player, ent.deviceNode)
}
}
return InteractionResult.SUCCESS
}
override fun onRemove(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
blockState2: BlockState,
bl: Boolean
) {
Containers.dropContentsOnDestroy(blockState, blockState2, level, blockPos)
super.onRemove(blockState, level, blockPos, blockState2, bl)
}
}

View File

@@ -0,0 +1,254 @@
package org.neoflock.neocomputers.block
import dev.architectury.networking.NetworkManager
import io.netty.buffer.Unpooled
import net.minecraft.client.Minecraft
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.NodeSynchronizer
abstract class SingleDeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): DeviceBlockEntity(type, pos, state) {
abstract val deviceNode: DeviceNode
override fun getDeviceNodes() = listOf(deviceNode)
override fun getNodeFromSide(directionToRequester: Direction): DeviceNode? = deviceNode
}
abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): BlockEntity(type, pos, state) {
val connetionsInDir = MutableList<DeviceNode?>(Direction.entries.size) { null }
val nodesInDir = MutableList<DeviceNode?>(Direction.entries.size) { null }
var alreadySetup = false
var receivedServerState = false
var connectionsAreDirty = true
abstract fun getDeviceNodes(): List<DeviceNode>
// Gets, if applicable, the node from a direction.
// The direction is from this block entity to the original requester,
// so it is Direction.UP if we asked from the one on the top side.
abstract fun getNodeFromSide(directionToRequester: Direction): DeviceNode?
open fun processCommits(commits: Iterable<FriendlyByteBuf>) {
receivedServerState = true
val devs = getDeviceNodes()
for (buf in commits) {
val idx = buf.readVarInt()
if(idx >= 0 && idx < devs.size) {
devs[idx].processCommit(buf)
}
}
}
open fun initNetworking(): DeviceBlockEntity {
if(hasLevel()) {
alreadySetup = true
Networking.addNodes(getDeviceNodes())
Direction.entries.forEach { handleConnectionsFor(it) }
}
return this
}
// Cables are 1 node
open fun getCurrentlyConnectedNodeIn(direction: Direction): DeviceNode? {
val ent = level?.getBlockEntity(blockPos.relative(direction))
if(ent is DeviceBlockEntity) {
return ent.getNodeFromSide(direction.opposite)
}
return null
}
open fun handleConnectionsFor(direction: Direction) {
// refuse connections on no node to reduce CPU load
val node = getNodeFromSide(direction)
val oldNode = nodesInDir[direction.ordinal]
nodesInDir[direction.ordinal] = node
val old = connetionsInDir[direction.ordinal]
val now = getCurrentlyConnectedNodeIn(direction)
if(node == null) {
if(old != null) oldNode?.disconnectFrom(old)
if(now != null) oldNode?.disconnectFrom(now)
return
}
if(oldNode != null && oldNode.address != node.address) {
if(old != null) oldNode.disconnectFrom(old)
if(now != null) oldNode.disconnectFrom(now)
}
if(old?.address != now?.address || oldNode == null) {
if(old != null) node.disconnectFrom(old)
if(now != null) node.connectTo(now)
}
// bullshit hack
if(now != null) {
node.connectTo(now)
}
connetionsInDir[direction.ordinal] = now
}
// TODO: optimize this sometime before our test computers melt
open fun tickDevice(level: Level) {
// Handles device connections
// we do it like this because stinky MC will call stuff before world is fully setup
// and then not notify us of neighbour changes
// this is because MC is considered shit
if(!alreadySetup) {
initNetworking()
}
if(connectionsAreDirty) {
connectionsAreDirty = false
Direction.entries.forEach { handleConnectionsFor(it) }
}
}
open fun sendCommitsToClient(level: Level) {
if(level !is ServerLevel) return
// synchronization!
val commits = mutableListOf<FriendlyByteBuf>()
val devs = getDeviceNodes()
for((i, dev) in devs.withIndex()) {
if(dev.outOfSync) {
dev.outOfSync = false
val buf = FriendlyByteBuf(Unpooled.buffer())
buf.writeVarInt(i)
dev.writeFullStateCommit(buf)
commits.addLast(buf)
}
}
if(commits.isNotEmpty()) {
level.players().forEach {
val dist = it.position().distanceTo(blockPos.center)
if(dist < 100) NetworkManager.sendToPlayer(it, NodeSynchronizer.DeviceBlockStatePayload(blockPos, commits))
}
}
}
open fun sendStateToPlayer(player: ServerPlayer) {
val world = level!!
if(world !is ServerLevel) return
// synchronization!
val commits = mutableListOf<FriendlyByteBuf>()
val devs = getDeviceNodes()
for((i, dev) in devs.withIndex()) {
val buf = FriendlyByteBuf(Unpooled.buffer())
buf.writeVarInt(i)
dev.writeFullStateCommit(buf)
commits.addLast(buf)
}
if(commits.isNotEmpty()) {
world.players().forEach {
val dist = it.position().distanceTo(blockPos.center)
if(dist <= NodeSynchronizer.MAX_STATE_DISTANCE_ALLOWED) NetworkManager.sendToPlayer(it, NodeSynchronizer.DeviceBlockStatePayload(blockPos, commits))
}
}
}
open fun requestServerState() {
// no point
if(receivedServerState) return
// we're the server bro :sob:
if(level?.isClientSide != true) return
val player = Minecraft.getInstance().player ?: return
// we assume the player will just reject, so we save on bandwidth
if(player.position().distanceTo(blockPos.center) > NodeSynchronizer.MAX_STATE_DISTANCE_ALLOWED) return
NetworkManager.sendToServer(NodeSynchronizer.DeviceBlockStateRequest(blockPos))
}
override fun setRemoved() {
super.setRemoved()
alreadySetup = false
Networking.removeNodes(getDeviceNodes())
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
for (node in getDeviceNodes()) {
node.markChanged()
}
connectionsAreDirty = true
receivedServerState = false
}
}
abstract class DeviceBlock(properties: Properties = Properties.of()): BaseBlock(properties), EntityBlock {
override fun <T : BlockEntity?> getTicker(
level: Level,
state: BlockState,
blockEntityType: BlockEntityType<T?>
): BlockEntityTicker<T> {
return object : BlockEntityTicker<T> {
override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T & Any) {
if(blockEntity !is DeviceBlockEntity) return
blockEntity.tickDevice(level)
blockEntity.sendCommitsToClient(level)
if(level.isClientSide) {
blockEntity.requestServerState()
}
}
}
}
override fun onPlace(state: BlockState, level: Level, pos: BlockPos, oldState: BlockState, movedByPiston: Boolean) {
super.onPlace(state, level, pos, oldState, movedByPiston)
val ent = level.getBlockEntity(pos)
if(ent is DeviceBlockEntity) {
ent.initNetworking()
ent.connectionsAreDirty = true
}
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult? {
val ent = level.getBlockEntity(pos)
if(ent is DeviceBlockEntity && player is ServerPlayer) {
val dir = hitResult.direction
val node = ent.getNodeFromSide(dir)
if(node == null) {
player.sendSystemMessage(Component.literal("no node for dir $dir"))
} else {
player.sendSystemMessage(Component.literal("dir: $dir, address: ${node.address}, connections: ${node.connections.joinToString(", ")}"))
}
}
return super.useWithoutItem(state, level, pos, player, hitResult)
}
override fun neighborChanged(
state: BlockState,
level: Level,
pos: BlockPos,
neighborBlock: Block,
neighborPos: BlockPos,
movedByPiston: Boolean
) {
super.neighborChanged(state, level, pos, neighborBlock, neighborPos, movedByPiston)
val ent = level.getBlockEntity(pos)
if(ent is DeviceBlockEntity) {
ent.handleConnectionsFor(Direction.getNearest(neighborPos.center.subtract(pos.center)))
}
}
}

View File

@@ -0,0 +1,91 @@
package org.neoflock.neocomputers.block
import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.FurnaceBlock
import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.SolarGeneratorBlockEntity
import org.neoflock.neocomputers.entity.CombustionGeneratorBlockEntity
import org.neoflock.neocomputers.network.NodeSynchronizer
class SolarGeneratorBlock : DeviceBlock(), EntityBlock {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = SolarGeneratorBlockEntity(blockPos, blockState)
}
// TODO: make it glow when burning
class CombustionGeneratorBlock : DeviceBlock, EntityBlock {
companion object {
val COMBUSTGEN_ACTIVE = BooleanProperty.create("active")
fun getLuminance(blockState: BlockState): Int {
return if(blockState.getValue(COMBUSTGEN_ACTIVE)) 5 else 0
}
}
constructor(): super(Properties.of().sound(SoundType.STONE).lightLevel(CombustionGeneratorBlock::getLuminance)) {
registerDefaultState(defaultBlockState().setValue(COMBUSTGEN_ACTIVE, false))
}
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = CombustionGeneratorBlockEntity(blockPos, blockState)
override fun useWithoutItem(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
if(!level.isClientSide()) {
val sp = player as ServerPlayer
val ent = level.getBlockEntity(blockPos, BlockEntities.COMBUSTGEN_ENTITY.get()).get()
NodeSynchronizer.registerPlayerScreen(sp, ent.deviceNode)
MenuRegistry.openMenu(sp, ent)
}
return InteractionResult.SUCCESS
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(COMBUSTGEN_ACTIVE)
}
override fun animateTick(blockState: BlockState, level: Level, blockPos: BlockPos, randomSource: RandomSource) {
if(blockState.getValue(COMBUSTGEN_ACTIVE)) {
if(randomSource.nextDouble() < 0.1) level.playLocalSound(blockPos, SoundEvents.FURNACE_FIRE_CRACKLE, SoundSource.AMBIENT, 1F, 1F, false)
val x = blockPos.x.toDouble()
val y = blockPos.y.toDouble()
val z = blockPos.z.toDouble()
level.addParticle(ParticleTypes.SMOKE, x+0.5, y+1, z+0.5, 0.0, 0.0, 0.0)
level.addParticle(ParticleTypes.FLAME, x+0.5, y+1, z+0.5, 0.0, 0.0, 0.0)
}
}
override fun onRemove(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
blockState2: BlockState,
bl: Boolean
) {
Containers.dropContentsOnDestroy(blockState, blockState2, level, blockPos)
super.onRemove(blockState, level, blockPos, blockState2, bl)
}
}

View File

@@ -0,0 +1,69 @@
package org.neoflock.neocomputers.block
import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResult
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.RackEntity
import org.neoflock.neocomputers.network.NodeSynchronizer
class RackBlock : DeviceBlock(Properties.of().noOcclusion()), EntityBlock {
override fun newBlockEntity(
pos: BlockPos,
state: BlockState
): BlockEntity? {
return RackEntity(pos, state)
}
// override fun getShape(
// state: BlockState,
// level: BlockGetter,
// pos: BlockPos,
// context: CollisionContext
// ): VoxelShape? {
// return Shapes.box(0.0,0.0,0.0,0.01,0.01,0.01)
// }
// override fun getRenderShape(state: BlockState): RenderShape? {
// return RenderShape
// }
override fun useWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hitResult: BlockHitResult): InteractionResult? {
val res = hitResult.location
val ent = level.getBlockEntity(pos, BlockEntities.RACK_ENTITY.get()).get()
if(res.x == 18.0) { // TODO: handle rotation
NeoComputers.LOGGER.info("{} > {} > {}, {} > {} > {}", pos.z+15/16f, res.z, pos.z+1/16f, pos.y+14/16f, res.y, pos.y+2/16f)
if (pos.z + 15 / 16f > res.z && res.z > pos.z + 1 / 16f && pos.y + 14 / 16f > res.y && res.y > pos.y + 2 / 16f) {
var rack = 0
rack += if(res.y < pos.y+5/16f) 1 else 0
rack += if(res.y < pos.y+8/16f) 1 else 0
rack += if(res.y < pos.y+12/16f) 1 else 0
player.sendSystemMessage(Component.literal(String.format("Hit server #%d", rack))) // TODO: call some RackItem method
return InteractionResult.SUCCESS
}
}
if (!level.isClientSide) {
MenuRegistry.openExtendedMenu(player as ServerPlayer, ent)
NodeSynchronizer.registerPlayerScreen(player as ServerPlayer, ent.node)
}
return InteractionResult.SUCCESS
}
}

View File

@@ -0,0 +1,135 @@
package org.neoflock.neocomputers.block
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.DeviceNode
class RedstoneIOEntity(blockPos: BlockPos, blockState: BlockState): SingleDeviceBlockEntity(BlockEntities.REDSTONEIO_ENTITY.get(), blockPos, blockState) {
val redstoneIn = Array<Int>(Direction.entries.size) {0}
val redstoneOut = Array<Int>(Direction.entries.size) {0}
// TODO: have redstone I/O node for component and shi
override val deviceNode = object : DeviceNode() {
}
fun refetch(dir: Direction) {
val src = blockPos.offset(dir.stepX, dir.stepY, dir.stepZ)
val cur = level?.getSignal(src, dir) ?: 0
val idx = dir.ordinal
if(redstoneIn[idx] != cur) {
onRedstoneSignalChanged(dir, redstoneIn[idx], cur)
}
redstoneIn[idx] = cur
}
fun refetchAll() {
Direction.entries.forEach { refetch(it) }
}
fun onRedstoneSignalChanged(dir: Direction, oldValue: Int, newValue: Int) {
Networking.emitMessage(deviceNode, Networking.ComputerUncheckedSignal(deviceNode, "redstone_changed", arrayOf(deviceNode.address.toString(), dir.ordinal, oldValue, newValue)))
NeoComputers.LOGGER.info("redstone in direction ${dir.name} changed from $oldValue to $newValue")
}
}
class RedstoneIOBlock(): DeviceBlock(Properties.of().isRedstoneConductor { state, getter, pos -> true }) {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = RedstoneIOEntity(blockPos, blockState)
fun getRedstoneIO(level: BlockGetter, blockPos: BlockPos): RedstoneIOEntity? {
val ent = level.getBlockEntity(blockPos)
if(ent is RedstoneIOEntity) return ent
return null
}
override fun isSignalSource(blockState: BlockState): Boolean {
return true
}
override fun getSignal(
blockState: BlockState,
blockGetter: BlockGetter,
blockPos: BlockPos,
direction: Direction
): Int {
val redstoneIO = getRedstoneIO(blockGetter, blockPos)
if(redstoneIO != null) {
return redstoneIO.redstoneOut[direction.opposite.ordinal]
}
return super.getSignal(blockState, blockGetter, blockPos, direction)
}
override fun onPlace(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
blockState2: BlockState,
bl: Boolean
) {
if(!level.isClientSide) {
level.updateNeighborsAt(blockPos, this)
getRedstoneIO(level, blockPos)?.refetchAll()
}
super.onPlace(blockState, level, blockPos, blockState2, bl)
}
override fun onRemove(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
blockState2: BlockState,
bl: Boolean
) {
if(!level.isClientSide) {
level.updateNeighborsAt(blockPos, this)
}
super.onRemove(blockState, level, blockPos, blockState2, bl)
}
override fun neighborChanged(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
block: Block,
blockPos2: BlockPos,
bl: Boolean
) {
super.neighborChanged(blockState, level, blockPos, block, blockPos2, bl)
if(!level.isClientSide) {
val dir = Direction.getNearest(blockPos2.center.subtract(blockPos.center))
getRedstoneIO(level, blockPos)?.refetch(dir)
}
}
override fun useWithoutItem(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult? {
if(!level.isClientSide) {
val redio = getRedstoneIO(level, blockPos)
val dir = blockHitResult.direction
if (redio != null) {
val idx = dir.ordinal
redio.redstoneOut[idx]++
redio.redstoneOut[idx] %= 16
NeoComputers.LOGGER.info("outputting redstone level ${redio.redstoneOut[idx]} on ${dir.name}")
}
level.updateNeighborsAt(blockPos, this)
}
return super.useWithoutItem(blockState, level, blockPos, player, blockHitResult)
}
}

View File

@@ -0,0 +1,194 @@
package org.neoflock.neocomputers.block
import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.ContainerHelper
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.menu.RelayMenu
import org.neoflock.neocomputers.item.RelayUpgrade
import org.neoflock.neocomputers.network.ConventionalNetworkDevice
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.NNComponent
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.NodeSynchronizer
import org.neoflock.neocomputers.utils.GenericContainer
class RelayEntity(blockPos: BlockPos, blockState: BlockState): SingleDeviceBlockEntity(BlockEntities.RELAY_ENTITY.get(), blockPos, blockState),
GenericContainer, ComponentUser, MenuProvider {
companion object RelaySlots {
val CARD = 0
val CPU = 1
val MEM = 2
val STORAGE = 3
val SLOT_COUNT = 4
}
val slots = NonNullList.withSize(SLOT_COUNT, ItemStack.EMPTY)!!
override fun getItems(): NonNullList<ItemStack> = slots
override fun stillValid(player: Player): Boolean = true
fun getUpgrade(slot: Int) = slots[slot].item as? RelayUpgrade
fun computeRelayInterval(): Int = getUpgrade(CPU)?.getRelayInterval(slots[CPU]) ?: 5
fun computeRelayBufferSize(): Int = getUpgrade(MEM)?.getRelayBufferSize(slots[MEM]) ?: 1
fun computeRelayQueueSize(): Int = getUpgrade(STORAGE)?.getRelayQueueSize(slots[STORAGE]) ?: 20
fun getRelaySender(): DeviceNode? = getUpgrade(CARD)?.getComponentNode(slots[CARD])
fun computeRelayCapacity(): Int = computeRelayBufferSize() + computeRelayQueueSize()
val queue = mutableListOf<Networking.ClassicPacket>()
val justReceived = mutableListOf<Networking.ClassicPacket>()
var activityTickLeft = 0
var ticksUntilQueue = 0
override val deviceNode = object : DeviceNode() {
override var reachability = Networking.Visibility.NONE
override fun received(message: Networking.Message) {
super.received(message)
if(message.sender == this) return
if(message is Networking.ClassicPacket && message.hopCount < 5 && (queue.size + justReceived.size) < computeRelayCapacity()) {
justReceived.addLast(message)
activityTickLeft = 20
markChanged()
}
}
override fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) {
super.encodeScreenData(player, buf)
buf.writeVarInt(computeRelayInterval())
buf.writeVarInt(computeRelayBufferSize())
buf.writeVarInt(computeRelayQueueSize())
buf.writeVarInt(queue.size + justReceived.size)
}
override fun writeFullStateCommit(buf: FriendlyByteBuf) {
super.writeFullStateCommit(buf)
buf.writeVarInt(activityTickLeft)
}
override fun processCommit(buf: FriendlyByteBuf) {
super.processCommit(buf)
activityTickLeft = buf.readVarInt()
}
override fun getComponent() = NNComponent("relay")
}
fun sendQueuedPacket() {
if(queue.isEmpty()) return
val pack = queue.removeFirst()
for(connection in deviceNode.connections) {
// skip unwanted loopback
if(connection in pack.sender.getReachable()) continue
val hopped = pack.hop(pack.sender)
if(connection is ConventionalNetworkDevice) {
connection.sendClassicPacket(hopped)
} else {
Networking.emitMessage(connection, hopped, setOf(deviceNode))
}
}
}
override fun tickDevice(level: Level) {
super.tickDevice(level)
if(level !is ServerLevel) return
if(activityTickLeft > 0) {
activityTickLeft--
deviceNode.markChanged()
}
queue.addAll(justReceived)
justReceived.clear()
val cap = computeRelayCapacity()
while(queue.size > cap) queue.removeLast()
ticksUntilQueue--
if(ticksUntilQueue <= 0) {
ticksUntilQueue = computeRelayInterval()
val toSend = computeRelayBufferSize()
for(i in 0..<toSend) {
sendQueuedPacket()
}
}
deviceNode.markChanged()
}
override fun getMachineBlockPosition() = blockPos!!
override fun getMachineLevel() = level!!
override fun getMachineNode() = deviceNode
override fun getDisplayName() = Component.translatable("block.neocomputers.relay")!!
override fun createMenu(
i: Int,
inventory: Inventory,
player: Player
) = RelayMenu(i, inventory, this)
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
ContainerHelper.loadAllItems(tag, slots, registries)
}
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
ContainerHelper.saveAllItems(tag, slots, registries)
}
}
class RelayBlock: DeviceBlock(Properties.of().sound(SoundType.METAL)) {
override fun newBlockEntity(pos: BlockPos, state: BlockState) = RelayEntity(pos, state)
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
if(!level.isClientSide) {
val ent = level.getBlockEntity(pos, BlockEntities.RELAY_ENTITY.get()).get()
// Open menu
MenuRegistry.openMenu(player as ServerPlayer, ent)
NodeSynchronizer.registerPlayerScreen(player, ent.deviceNode)
}
return InteractionResult.SUCCESS
}
override fun onRemove(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
blockState2: BlockState,
bl: Boolean
) {
Containers.dropContentsOnDestroy(blockState, blockState2, level, blockPos)
super.onRemove(blockState, level, blockPos, blockState2, bl)
}
}

View File

@@ -0,0 +1,33 @@
package org.neoflock.neocomputers.block
import net.minecraft.core.BlockPos
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.RobotEntity
class RobotBlock : BaseBlock(Properties.of().noOcclusion()), EntityBlock { // todo: node stuff
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
NeoComputers.LOGGER.info("block entity created..")
return RobotEntity(pos, state)
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape? {
return Shapes.box(0.1, 0.1, 0.1, 0.9, 0.9, 0.9)
}
override fun getRenderShape(state: BlockState): RenderShape {
return RenderShape.INVISIBLE // this is so not good
}
// public RenderShape getRenderShape(BlockState state) {
// return RenderShape.ENTITYBLOCK_ANIMATED;
// }
}

View File

@@ -0,0 +1,92 @@
package org.neoflock.neocomputers.block;
import dev.architectury.registry.menu.ExtendedMenuProvider
import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.EnumProperty.*
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.ScreenEntity
import org.neoflock.neocomputers.gui.menu.ScreenMenu
import kotlin.math.abs
import kotlin.math.max
import org.neoflock.neocomputers.network.NodeSynchronizer
class ScreenBlock() : DeviceBlock() {
companion object {
val FACING_HORIZ: EnumProperty<Direction> = EnumProperty.create<Direction>("facing_horiz", Direction::class.java)
val FACING_VERTI: IntegerProperty = IntegerProperty.create("facing_verti", 0, 2) // "Integer" property doesnt accept values below 0, also death to enums, long live magic numbers
}
init {
registerDefaultState(stateDefinition.any().setValue(FACING_HORIZ, Direction.NORTH).setValue(FACING_VERTI, 1))
}
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState) = ScreenEntity(blockPos, blockState)
override fun useWithoutItem(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
if(!level.isClientSide) {
val sp = player as ServerPlayer
val ent = level.getBlockEntity(blockPos, BlockEntities.SCREEN_ENTITY.get()).get()
NodeSynchronizer.registerPlayerScreen(sp, ent.deviceNode)
MenuRegistry.openExtendedMenu(sp, object : ExtendedMenuProvider {
override fun getDisplayName(): Component = Component.literal("SCREEEEEN!")
override fun createMenu(i: Int, inventory: Inventory, player: Player): AbstractContainerMenu {
// return Menus.SCREEN_MENU.get().create(i, inventory);
return ScreenMenu(i, inventory, ent)
}
override fun saveExtraData(buf: FriendlyByteBuf?) {
buf!!.writeBlockPos(blockPos)
}
})
}
return InteractionResult.SUCCESS
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(FACING_HORIZ)
builder.add(FACING_VERTI)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val horiz = context.horizontalDirection
val looking = context.player!!.lookAngle
val biggest = max(max(abs(looking.y), abs(looking.z)), abs(looking.x))
return super.getStateForPlacement(context)!!
.setValue(FACING_HORIZ, horiz.opposite)
.setValue(FACING_VERTI, if (biggest != abs(looking.y)) 1 else if (looking.y < 0) 2 else 0 )
// val dirs = context.nearestLookingDirections
// context.
// return when (face) {
// Direction.UP -> super.getStateForPlacement(context)!!.setValue(FACING_HORIZ, looking.opposite).setValue(FACING_VERTI, 2)
// Direction.DOWN -> super.getStateForPlacement(context)!!.setValue(FACING_HORIZ, looking.opposite).setValue(FACING_VERTI, 0)
// else -> super.getStateForPlacement(context)!!.setValue(FACING_HORIZ, looking.opposite).setValue(FACING_VERTI, 1)
// }
}
}

View File

@@ -0,0 +1,111 @@
package org.neoflock.neocomputers.block.model
import com.mojang.blaze3d.vertex.VertexConsumer
import net.minecraft.client.Minecraft
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.renderer.block.model.BakedQuad
import net.minecraft.client.renderer.block.model.ItemOverrides
import net.minecraft.client.renderer.block.model.ItemTransforms
import net.minecraft.client.renderer.texture.TextureAtlas
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.resources.model.BakedModel
import net.minecraft.client.resources.model.Material
import net.minecraft.client.resources.model.ModelBaker
import net.minecraft.client.resources.model.ModelState
import net.minecraft.client.resources.model.UnbakedModel
import net.minecraft.core.Direction
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.RandomSource
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import java.util.function.Function
abstract class AbstractModel : BakedModel {
val atlas = Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS)
var baker: ModelBaker? = null;
var mesh: Map<Direction, List<BakedQuad>> = mapOf(
Direction.UP to listOf(),
Direction.DOWN to listOf(),
Direction.EAST to listOf(),
Direction.WEST to listOf(),
Direction.SOUTH to listOf(),
Direction.NORTH to listOf(),
);
override fun getQuads(state: BlockState?, direction: Direction?, random: RandomSource): List<BakedQuad?>? { // perf? what's that?
if (direction != null) {
return mesh[direction]
} else {
var allquads: MutableList<BakedQuad> = mutableListOf()
mesh.forEach { (_, quads) ->
allquads.addAll(quads)
}
return allquads
}
}
fun _bake(spriteGetter: Function<Material, TextureAtlasSprite>): BakedModel {
bake { r: Material -> spriteGetter.apply(r) }
return this
}
override fun getParticleIcon(): TextureAtlasSprite? {
return atlas.apply(particle())
}
abstract fun bake(atlas: (Material) -> TextureAtlasSprite)
abstract fun particle(): ResourceLocation
}
class SchizoConsumer {
var mesh: Map<Direction, MutableList<BakedQuad>> = mapOf(
Direction.UP to mutableListOf(),
Direction.DOWN to mutableListOf(),
Direction.EAST to mutableListOf(),
Direction.WEST to mutableListOf(),
Direction.SOUTH to mutableListOf(),
Direction.NORTH to mutableListOf(),
);
var vertices: MutableList<Int> = mutableListOf();
var sprite: TextureAtlasSprite? = null;
var normal: Direction? = null;
var tint_index = -1
var shade = false
fun startQuad(normal: Direction, sprite: TextureAtlasSprite, tint_index: Int = -1, shade: Boolean = false): SchizoConsumer {
this.sprite = sprite
this.normal = normal
this.tint_index = tint_index
this.shade = shade
return this
}
fun vertex(x: Float, y: Float, z:Float, colorABGR: Int, u: Float, v: Float) { // uv are normalized
if (sprite == null || normal == null) {
throw Error("quad not started")
}
// NeoComputers.LOGGER.info("{} {} -> {} {} ", u, v, sprite!!.getU(u), sprite!!.getV(v))
vertices.add(x.toBits())
vertices.add(y.toBits())
vertices.add(z.toBits())
vertices.add(colorABGR)
vertices.add(sprite!!.getU(u).toBits())
vertices.add(sprite!!.getV(v).toBits())
vertices.add(0)
vertices.add(0)
if (vertices.size == 4 * 8) {
var quad = BakedQuad(vertices.toIntArray(), tint_index, normal!!, sprite!!, shade)
mesh.get(normal)!!.add(quad)
normal = null
sprite = null
vertices.clear()
}
} // TODO: add stuff like squares
}

View File

@@ -0,0 +1,83 @@
package org.neoflock.neocomputers.block.model
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.block.model.BakedQuad
import net.minecraft.client.renderer.block.model.ItemOverrides
import net.minecraft.client.renderer.block.model.ItemTransforms
import net.minecraft.client.renderer.texture.TextureAtlas
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.resources.model.BakedModel
import net.minecraft.client.resources.model.Material
import net.minecraft.client.resources.model.ModelBaker
import net.minecraft.client.resources.model.ModelState
import net.minecraft.client.resources.model.UnbakedModel
import net.minecraft.core.Direction
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.RandomSource
import net.minecraft.util.ResourceLocationPattern
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import java.util.function.Function
class RobotModel() : AbstractModel() {
val size = 0.4f
val l = 0.5f-size;
val h = 0.5f+size;
// TODO: fix dimensions (i eyeballed it)
override fun bake(atlas: (Material) -> TextureAtlasSprite) {
// override fun bake(atlas: Function<Material?, TextureAtlasSprite?>, state: ModelState): BakedModel {
val sprite = atlas(Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "block/robot")))
// val sprite = atlas.apply(Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "robot")))!!
var verts: SchizoConsumer = SchizoConsumer()
// top pyramid, enjoy reading this schizo mess
bakeTri(verts, Direction.WEST, sprite, l,l, l, h, 9f/16f,1f, 0f, 0f, 0f, 16f/32f)
bakeTri(verts, Direction.EAST, sprite, h, h, h, l, 9f/16f, 1f, 16/32f, 16/32f, 16/32f, 0f)
bakeTri(verts, Direction.SOUTH, sprite, l, h, h, h, 9f/16f, 1f, 0f, 16f/32f, 16f/32f, 16f/32f)
bakeTri(verts, Direction.NORTH, sprite, h, l, l,l, 9f/16f, 1f, 16/32f, 0f, 0f, 0f)
verts.startQuad(Direction.DOWN, sprite, 0)
verts.vertex(l, 9f/16F, h, 0xFFFFFFFF.toInt(), 16f/32f, 16/32F)
verts.vertex(l, 9f/16f, l, 0xFFFFFFFF.toInt(), 0F, 16/32F)
verts.vertex(h, 9f/16f, l, 0xFFFFFFFF.toInt(), 0F, 1F)
verts.vertex(h, 9f/16f, h, 0xFFFFFFFF.toInt(), 16/32F, 1F)
// bottom
bakeTri(verts, Direction.WEST, sprite, l, h, l, l, 8f/16f, 1/16f, 0f, 0f, 0f, 16f/32f)
bakeTri(verts, Direction.EAST, sprite, h, l, h, h, 8f/16f, 1/16f, 0f, 0f, 0f, 16f/32f)
bakeTri(verts, Direction.SOUTH, sprite, h, h, l, h, 8f/16f, 1/16f, 0f, 0f, 0f, 16f/32f)
bakeTri(verts, Direction.NORTH, sprite, l, l, h,l, 8f/16f, 1/16f, 0f, 0f, 0f, 16f/32f)
verts.startQuad(Direction.UP, sprite, 0)
verts.vertex(h, 8f/16F, l, 0xFFFFFFFF.toInt(), 16/32F, 16/32F)
verts.vertex(l, 8f/16f, l, 0xFFFFFFFF.toInt(), 0F, 16/32F)
verts.vertex(l, 8f/16f, h, 0xFFFFFFFF.toInt(), 0F, 1F)
verts.vertex(h, 8f/16f, h, 0xFFFFFFFF.toInt(), 16/32F, 1F)
this.mesh = verts.mesh
}
fun bakeTri(verts: SchizoConsumer, normal: Direction, sprite: TextureAtlasSprite, lx: Float, lz: Float, rx: Float, rz: Float, dy: Float, uy: Float, lu: Float, lv: Float, ru: Float, rv: Float) {
verts.startQuad(normal, sprite, 0)
verts.vertex(0.5F, uy, 0.5F, 0xFFFFFFFF.toInt(), 8F/32F, 8F/32F)
verts.vertex(0.5F, uy, 0.5F, 0xFFFFFFFF.toInt(), 8F/32F, 8F/32F)
verts.vertex(lx, dy, lz, 0xFFFFFFFF.toInt(), lu, lv)
verts.vertex(rx, dy, rz, 0xFFFFFFFF.toInt(), ru, rv)
}
override fun particle(): ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "block/teto")
override fun useAmbientOcclusion(): Boolean = true
override fun isGui3d(): Boolean = true
override fun usesBlockLight(): Boolean = true
override fun isCustomRenderer(): Boolean = false
override fun getTransforms(): ItemTransforms? = ItemTransforms.NO_TRANSFORMS
override fun getOverrides(): ItemOverrides? = ItemOverrides.EMPTY // TODO: item
}

View File

@@ -0,0 +1,22 @@
package org.neoflock.neocomputers.datagen
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput
import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider
import net.minecraft.data.models.BlockModelGenerators
import net.minecraft.data.models.ItemModelGenerators
import net.minecraft.data.models.model.ModelTemplate
import net.minecraft.data.models.model.ModelTemplates
import org.neoflock.neocomputers.block.Blocks
import org.neoflock.neocomputers.item.Items
class ModelGenerator(output: FabricDataOutput) : FabricModelProvider(output) {
override fun generateBlockStateModels(blockStateModelGenerator: BlockModelGenerators) {
// blockStateModelGenerator.createGenericCube(Blocks.CASE_BLOCK.get())
}
override fun generateItemModels(itemModelGenerator: ItemModelGenerators) {
itemModelGenerator.generateFlatItem(Items.SERVER0.get(), ModelTemplates.FLAT_ITEM)
}
}

View File

@@ -0,0 +1,13 @@
package org.neoflock.neocomputers.datagen
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput
import org.neoflock.neocomputers.NeoComputers
class NeoComputersDataGenerator : DataGeneratorEntrypoint {
override fun onInitializeDataGenerator(generator: FabricDataGenerator) {
val pack: FabricDataGenerator.Pack = generator.createPack()
pack.addProvider(::ModelGenerator)
}
}

View File

@@ -0,0 +1,117 @@
package org.neoflock.neocomputers.entity;
import com.mojang.datafixers.types.templates.TypeTemplate
import com.mojang.serialization.Codec
import com.mojang.datafixers.types.Type as DataFixType
import dev.architectury.registry.registries.DeferredRegister
import dev.architectury.registry.registries.RegistrySupplier
import net.minecraft.core.registries.Registries
import net.minecraft.util.datafix.DataFixTypes
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntityType
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.Blocks
import org.neoflock.neocomputers.block.CapacitorEntity
import org.neoflock.neocomputers.block.CapacitorEntityTier1
import org.neoflock.neocomputers.block.CapacitorEntityTier2
import org.neoflock.neocomputers.block.CapacitorEntityTier3
import org.neoflock.neocomputers.block.RelayEntity
import org.neoflock.neocomputers.network.PowerManager
// complete fucking bullshit btw
class BullshitFix: DataFixType<Unit>() {
override fun buildTemplate(): TypeTemplate? {
return null
}
override fun buildCodec(): Codec<Unit?>? {
return null
}
override fun equals(
o: Any?,
ignoreRecursionPoints: Boolean,
checkIndex: Boolean
): Boolean {
return o == this
}
}
object BlockEntities {
val BLOCKENTITIES: DeferredRegister<BlockEntityType<*>> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK_ENTITY_TYPE);
val SCREEN_ENTITY: RegistrySupplier<BlockEntityType<ScreenEntity>> = BLOCKENTITIES.register("screen") {
BlockEntityType(
::ScreenEntity, setOf(Blocks.SCREEN_BLOCK.get()), BullshitFix()
)
}
val CAPACITOR_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntityTier1>> = BLOCKENTITIES.register("capacitor") {
BlockEntityType(
::CapacitorEntityTier1, setOf(Blocks.CAPACITOR_BLOCK.get()), BullshitFix()
)
}
val CAPACITOR2_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntityTier2>> = BLOCKENTITIES.register("capacitor2") {
BlockEntityType(
::CapacitorEntityTier2, setOf(Blocks.CAPACITOR_BLOCK2.get()), BullshitFix()
)
}
val CAPACITOR3_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntityTier3>> = BLOCKENTITIES.register("capacitor3") {
BlockEntityType(
::CapacitorEntityTier3, setOf(Blocks.CAPACITOR_BLOCK3.get()), BullshitFix()
)
}
val SOLARGEN_ENTITY: RegistrySupplier<BlockEntityType<SolarGeneratorBlockEntity>> = BLOCKENTITIES.register("solargen") {
BlockEntityType(
::SolarGeneratorBlockEntity, setOf(Blocks.SOLARGEN_BLOCK.get()), BullshitFix()
)
}
val COMBUSTGEN_ENTITY: RegistrySupplier<BlockEntityType<CombustionGeneratorBlockEntity>> = BLOCKENTITIES.register("combustgen") {
BlockEntityType(
::CombustionGeneratorBlockEntity, setOf(Blocks.COMBUSTGEN_BLOCK.get()), BullshitFix()
)
}
val REDSTONEIO_ENTITY: RegistrySupplier<BlockEntityType<CombustionGeneratorBlockEntity>> = BLOCKENTITIES.register("redio") {
BlockEntityType(
::CombustionGeneratorBlockEntity, setOf(Blocks.REDSTONEIO_BLOCK.get()), BullshitFix()
)
}
val CASE_ENTITY: RegistrySupplier<BlockEntityType<CaseBlockEntity>> = BLOCKENTITIES.register("case") {
BlockEntityType(
::CaseBlockEntity, setOf(Blocks.CASE_BLOCK.get()), BullshitFix()
)
}
val CABLE_ENTITY: RegistrySupplier<BlockEntityType<CableEntity>> = BLOCKENTITIES.register("cable") {
BlockEntityType(
::CableEntity, setOf(Blocks.CABLE_BLOCK.get()), BullshitFix()
)
}
val RELAY_ENTITY: RegistrySupplier<BlockEntityType<RelayEntity>> = BLOCKENTITIES.register("relay") {
BlockEntityType(
::RelayEntity, setOf(Blocks.RELAY_BLOCK.get()), BullshitFix()
)
}
val ROBOT_ENTITY: RegistrySupplier<BlockEntityType<RobotEntity>> = BLOCKENTITIES.register("robot") {
BlockEntityType(
::RobotEntity, setOf(Blocks.ROBOT_BLOCK.get()), BullshitFix()
)
}
val RACK_ENTITY: RegistrySupplier<BlockEntityType<RackEntity>> = BLOCKENTITIES.register("rack") {
BlockEntityType(
::RackEntity, setOf(Blocks.RACK_BLOCK.get()), BullshitFix()
)
}
fun registerPowerBlocks() {
PowerManager.registerPowerDevice(CAPACITOR_ENTITY.get())
PowerManager.registerPowerDevice(CAPACITOR2_ENTITY.get())
PowerManager.registerPowerDevice(CAPACITOR3_ENTITY.get())
PowerManager.registerPowerDevice(SOLARGEN_ENTITY.get())
PowerManager.registerPowerDevice(COMBUSTGEN_ENTITY.get())
PowerManager.registerPowerDevice(CASE_ENTITY.get())
}
}

View File

@@ -0,0 +1,47 @@
package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.world.item.DyeColor
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.CableBlock
import org.neoflock.neocomputers.block.CableBlock.Companion.COLOR
import org.neoflock.neocomputers.block.CableBlock.Companion.getPropByDirection
import org.neoflock.neocomputers.block.DeviceBlockEntity
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.network.DeviceNode
class CableEntity(pos: BlockPos, state: BlockState) : SingleDeviceBlockEntity(BlockEntities.CABLE_ENTITY.get(), pos, state) {
override val deviceNode = object : DeviceNode(){}
override fun sendCommitsToClient(level: Level) {
// we have nothing to commit lol
return
}
override fun requestServerState() {
// no state, we don't bother
return
}
override fun getNodeFromSide(directionToRequester: Direction): DeviceNode? {
if(CableBlock.shouldConnect(blockPos, blockPos.relative(directionToRequester), level!!)) {
return deviceNode
}
return null
}
override fun setChanged() {
super.setChanged()
for (dir in Direction.entries) {
val ent = level!!.getBlockEntity(blockPos.relative(dir))
level!!.setBlockAndUpdate(blockPos, blockState.setValue(getPropByDirection(dir), CableBlock.shouldConnect(blockPos, blockPos.relative(dir), level!!)))
if(ent is DeviceBlockEntity) {
ent.connectionsAreDirty = true
}
}
}
}

View File

@@ -0,0 +1,307 @@
package org.neoflock.neocomputers.entity
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.world.Container
import net.minecraft.world.ContainerHelper
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.CaseBlock
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.gui.menu.CaseMenu
import org.neoflock.neocomputers.item.ComponentItem
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.NNComponent
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
import org.neoflock.neocomputers.sounds.ComputerRunningSoundInstance
import org.neoflock.neocomputers.sounds.Sounds
import org.neoflock.neocomputers.utils.GenericContainer
import java.time.Duration
import kotlin.math.max
import kotlin.math.min
import org.neoflock.neocomputers.network.NodeSynchronizer
import kotlin.text.ifEmpty
class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): SingleDeviceBlockEntity(BlockEntities.CASE_ENTITY.get(), blockPos, blockState), MachineEntity, GenericContainer, MenuProvider {
val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(7, ItemStack.EMPTY)
var isOn = false
var diskActivityTime = 0 // TOOD: writing writers and reading readers
var networkActivityTime = 0
var err: String? = null
var arch = "Lua 5.3"
var soundInstance: SoundInstance? = null
override val deviceNode = object : DeviceNode() {
override var powerRole = PowerRole.CONSUMER
override var energyCapacity: Long = 500
override fun writeFullStateCommit(buf: FriendlyByteBuf) {
super.writeFullStateCommit(buf)
buf.writeUUID(address)
buf.writeBoolean(isOn)
buf.writeVarInt(diskActivityTime)
buf.writeVarInt(networkActivityTime)
buf.writeUtf(err ?: "")
}
override fun processCommit(buf: FriendlyByteBuf) {
super.processCommit(buf)
Networking.changeNodeAddress(this, buf.readUUID())
setRunning(buf.readBoolean())
diskActivityTime = buf.readVarInt()
networkActivityTime = buf.readVarInt()
err = buf.readUtf().ifEmpty { null }
}
override fun processScreenInteraction(player: ServerPlayer, buf: FriendlyByteBuf) {
super.processScreenInteraction(player, buf)
val c = buf.readByte().toInt()
if(c == 0x01) {
start()
}
if(c == 0x02) {
stop()
}
}
override fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) {
super.encodeScreenData(player, buf)
buf.writeBoolean(isOn)
buf.writeByteArray((err ?: "").encodeToByteArray())
buf.writeLong(energy)
buf.writeLong(energyCapacity)
buf.writeLong(getMachineMemoryUsed())
buf.writeLong(getMachineMemoryTotal())
buf.writeLong(getMachineComponentsUsed())
buf.writeLong(getMachineComponentsTotal())
buf.writeUtf(arch)
}
override fun tick() {
super.tick()
if (isRunning()) {
if(diskActivityTime > 0) diskActivityTime--
if(networkActivityTime > 0) networkActivityTime--
if(getMachineArchitectures().isEmpty()) {
crash("@neocomputers.errors.ENOCPU")
} else if(getMachineComponentsUsed() > getMachineComponentsTotal()) {
crash("@neocomputers.errors.E2BIG")
} else if (!consumeEnergy(1)) {
crash("@neocomputers.errors.ENOENJ")
}
}
}
override fun received(message: Networking.Message) {
super.received(message)
if(message is Networking.ClassicPacket) {
NeoComputers.LOGGER.info("machine $address got $message from ${message.sender.address}")
}
}
override fun getComponent() = NNComponent("computer")
}
val redstoneIn = Array(Direction.entries.size) {0}
val redstoneOut = Array(Direction.entries.size) {0}
fun refetchRedstone(dir: Direction) {
val src = blockPos.offset(dir.stepX, dir.stepY, dir.stepZ)
val cur = level?.getSignal(src, dir) ?: 0
val idx = dir.ordinal
if(redstoneIn[idx] != cur) {
onRedstoneSignalChanged(dir, redstoneIn[idx], cur)
}
redstoneIn[idx] = cur
}
fun refetchAllRedstone() {
Direction.entries.forEach { refetchRedstone(it) }
}
fun sendMachineEvent(event: MachineEvent) {
stacks.forEach {
val item = it.item
if(item is ComponentItem) {
item.onMachineEvent(it, this, event)
}
}
Networking.emitMessage(deviceNode, Networking.ComputerEvent(deviceNode, event))
}
fun onRedstoneSignalChanged(dir: Direction, oldValue: Int, newValue: Int) {
sendMachineEvent(MachineRedstoneEvent(this, dir, oldValue, newValue))
Networking.emitMessage(deviceNode, Networking.ComputerUncheckedSignal(deviceNode, "redstone_changed", arrayOf(deviceNode.address.toString(), dir.ordinal, oldValue, newValue)))
NeoComputers.LOGGER.info("redstone in direction ${dir.name} changed from $oldValue to $newValue")
if(oldValue == 0) {
// Rising edge
start()
}
setChanged()
}
override fun getMachineBlockPosition(): BlockPos = blockPos
override fun getMachineLevel(): Level = level!!
override fun isRunning(): Boolean = isOn
fun setRunning(value: Boolean) {
if(isOn == value) return
deviceNode.markChanged()
NeoComputers.LOGGER.info("[${deviceNode.address}] Going from $isOn to $value")
isOn = value
val world = level ?: return
blockState?.setValue(CaseBlock.COMPUTER_RUNNING, isOn)
if(world.isClientSide) {
if(value) {
soundInstance = ComputerRunningSoundInstance(this, Sounds.COMPUTER_RUNNING.get(), SoundSource.AMBIENT)
Minecraft.getInstance().soundManager.play(soundInstance!!)
} else {
Minecraft.getInstance().soundManager.stop(soundInstance!!)
soundInstance = null
}
return
}
// Server-side stuff!!
sendMachineEvent(MachinePowerEvent(this, isOn))
Networking.emitMessage(deviceNode, Networking.ClassicPacket(deviceNode, deviceNode.address.toString(), null, 1, listOf(), 0))
}
override fun start(): Boolean {
if(isOn) return true
err = null
val architectures = getMachineArchitectures()
// Beep patterns taken from https://github.com/MightyPirates/OpenComputers/blob/571482db88080d56329e8f8cf0db2a90825bf1d7/src/main/scala/li/cil/oc/server/machine/Machine.scala
if(architectures.isEmpty()) {
crash("@neocomputers.errors.ENOCPU")
beepAsync("-..")
return false
}
if(getMachineComponentsUsed() > getMachineComponentsTotal()) {
crash("@neocomputers.errors.E2BIG")
beepAsync("-..")
return false
}
// less than 20% energy is bad
if(deviceNode.energy < deviceNode.energyCapacity/5) {
crash("@neocomputers.errors.ENOENJ")
// we add a beep for the special case where we do have a little bit of energy :P
if(deviceNode.energy > 0) beepAsync("..")
return false
}
if(getMachineMemoryTotal() == 0L) {
crash("@neocomputers.errors.ENOMEM")
beepAsync("-.")
return false
}
if(arch !in architectures) {
// Just pick one! TODO: consult EEPROM first
arch = architectures.first()
}
beepAsync(".")
setRunning(true)
return isOn
}
override fun stop(): Boolean {
if(!isOn) return false
setRunning(false)
return isOn
}
override fun crash(error: String): Boolean {
beepAsync("--")
sendMachineEvent(MachineCrashEvent(this, error))
setRunning(false)
err = error
return true
}
override fun getLastError(): String? = err
override fun getMachineNode() = deviceNode
override fun getRedstoneInput(direction: Direction): Int = redstoneIn[direction.ordinal]
override fun getRedstoneOutput(direction: Direction): Int = redstoneOut[direction.ordinal]
override fun setRedstoneOutput(direction: Direction, newValue: Int): Int {
val idx = direction.ordinal
val old = redstoneOut[idx]
redstoneOut[idx] = newValue
return old
}
override fun beepAsync(pattern: String, frequency: Int, duration: Duration, volume: Double): Boolean {
NodeSynchronizer.emitBeep(level!!, NodeSynchronizer.BeepDataPayload(getMachineBlockPosition(), pattern, frequency, duration, volume))
return true
}
override fun signalDiskActivity(delay: Int) {
diskActivityTime = max(delay, diskActivityTime)
}
override fun signalNetworkActivity(delay: Int) {
networkActivityTime = max(delay, networkActivityTime)
}
override fun getMachineMemoryTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getMemoryCapacity(it) }.sum().toLong()
override fun getMachineMemoryUsed(): Long = 0
override fun getMachineComponentsUsed(): Long = deviceNode.getReachable().count { it.getComponent() != null }.toLong()
override fun getMachineComponentsTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getComponentCapacity(it) }.sum().toLong()
override fun getMachineArchitecture() = arch
override fun getMachineArchitectures() = stacks.mapNotNull { (it.item as? ComponentItem)?.getArchitecturesProvided(it) }.flatten().toSet()
override fun setMachineArchitecture(arch: String) {
if(this.arch == arch) return
this.arch = arch
if(isRunning()) {
stop()
start()
}
}
override fun getItems(): NonNullList<ItemStack> = stacks
override fun stillValid(player: Player): Boolean = !this.isRemoved
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
deviceNode.energy = min(deviceNode.energyCapacity, tag.getLong("energy"))
//isOn = compoundTag.getBoolean("powerOn")
ContainerHelper.loadAllItems(tag, getItems(), registries)
}
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.saveAdditional(compoundTag, provider)
compoundTag.putLong("energy", deviceNode.energy)
//compoundTag.putBoolean("powerOn", isOn)
ContainerHelper.saveAllItems(compoundTag, getItems(), provider)
}
override fun getDisplayName(): Component? = Component.literal("Computer")
override fun createMenu(i: Int, inventory: Inventory, player: Player) = CaseMenu(i, inventory, this)
override fun setRemoved() {
setRunning(false)
super.setRemoved()
}
override fun canPlaceItem(slot: Int, stack: ItemStack): Boolean = false
override fun canTakeItem(target: Container, slot: Int, stack: ItemStack): Boolean = false
}

View File

@@ -0,0 +1,100 @@
package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.ContainerHelper
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.CombustionGeneratorBlock
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.gui.menu.CombustionGeneratorMenu
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.PowerRole
import org.neoflock.neocomputers.utils.GenericContainer
import org.neoflock.neocomputers.utils.ContainerUtils
import kotlin.math.min
class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : SingleDeviceBlockEntity(BlockEntities.COMBUSTGEN_ENTITY.get(), blockPos, blockState), GenericContainer, MenuProvider {
val energyPerTick: Long = 50
var burningTimeRemaining: Int = 0
override val deviceNode = object : DeviceNode() {
override var powerRole = PowerRole.GENERATOR
override var energyCapacity: Long = 100000
override fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) {
buf.writeLong(energy)
buf.writeLong(energyCapacity)
}
}
val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(1, ItemStack.EMPTY)
override fun canPlaceItem(i: Int, itemStack: ItemStack): Boolean {
return ContainerUtils.isBurningFuel(itemStack)
}
override fun getItems(): NonNullList<ItemStack> = stacks
override fun stillValid(player: Player): Boolean {
return !this.isRemoved
}
override fun tickDevice(level: Level) {
super.tickDevice(level)
// TODO: give us a block state tag for active
// keep combusting and shi
if(burningTimeRemaining > 0) {
burningTimeRemaining--
deviceNode.giveEnergy(energyPerTick)
setChanged()
return
}
// no point
if(deviceNode.energy >= deviceNode.energyCapacity) return;
// :fire:
val fuel = stacks[0]
if(fuel.isEmpty) return
burningTimeRemaining = ContainerUtils.getBurningTime(fuel) ?: 0
setChanged()
fuel.count--
}
override fun getDisplayName(): Component? = Component.translatable("block.neocomputers.combustgen")
override fun createMenu(i: Int, inventory: Inventory, player: Player) = CombustionGeneratorMenu(i, inventory, this)
override fun setChanged() {
super.setChanged()
level?.setBlockAndUpdate(blockPos, blockState.setValue(CombustionGeneratorBlock.COMBUSTGEN_ACTIVE, burningTimeRemaining > 0))
}
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider)
deviceNode.energy = min(deviceNode.energyCapacity, compoundTag.getLong("energy"))
burningTimeRemaining = compoundTag.getInt("burningTimeRemaining")
ContainerHelper.loadAllItems(compoundTag, getItems(), provider)
}
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.saveAdditional(compoundTag, provider)
compoundTag.putLong("energy", deviceNode.energy)
compoundTag.putInt("burningTimeRemaining", burningTimeRemaining)
ContainerHelper.saveAllItems(compoundTag, getItems(), provider)
}
}

View File

@@ -0,0 +1,61 @@
package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.Level
import net.minecraft.world.phys.Vec3
import org.neoflock.neocomputers.network.DeviceNode
import java.time.Duration
abstract class MachineEvent {
abstract val machine: MachineEntity
}
data class MachineRedstoneEvent(override val machine: MachineEntity, val side: Direction, val oldValue: Int, val newValue: Int): MachineEvent()
data class MachinePowerEvent(override val machine: MachineEntity, val nowRunning: Boolean): MachineEvent()
data class MachineCrashEvent(override val machine: MachineEntity, val error: String): MachineEvent()
interface ComponentUser {
// Block position of machine, for wireless tech
fun getMachineBlockPosition(): BlockPos
fun getMachinePrecisePosition(): Vec3 = getMachineBlockPosition().center
fun getMachineLevel(): Level
fun getMachineNode(): DeviceNode
}
interface MachineEntity: ComponentUser {
// Pattern can have dots (.), dashes (-) and spaces ( ).
// Each character is duration long, and has a 50ms break.
// For non-short ones, which are typically reserved only for hardware interactions,
// the duration is doubled.
// Architectures should only use short ones.
fun beepAsync(pattern: String, frequency: Int = 1000, duration: Duration = Duration.ofMillis(200), volume: Double = 1.0): Boolean
// Signals that disk activity is happening for at least that delay
fun signalDiskActivity(delay: Int)
// signal network activity to a machine
fun signalNetworkActivity(delay: Int)
fun isRunning(): Boolean
fun start(): Boolean
fun stop(): Boolean
fun crash(error: String): Boolean
fun getLastError(): String?
// Some metadata
fun getMachineMemoryTotal(): Long
fun getMachineMemoryUsed(): Long
fun getMachineComponentsUsed(): Long
fun getMachineComponentsTotal(): Long
fun getMachineArchitecture(): String
fun getMachineArchitectures(): Set<String>
fun setMachineArchitecture(arch: String)
// Redstone signals
fun getRedstoneInput(direction: Direction): Int
fun getRedstoneOutput(direction: Direction): Int
// returns the old one
fun setRedstoneOutput(direction: Direction, newValue: Int): Int
}
// TODO: add dummy machine class which implements the machine entity interface meant to be used on the client

View File

@@ -0,0 +1,134 @@
package org.neoflock.neocomputers.entity
import dev.architectury.registry.menu.ExtendedMenuProvider
import net.minecraft.client.renderer.RenderType
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.ContainerHelper
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.ChestBlockEntity
import net.minecraft.world.level.block.entity.FurnaceBlockEntity
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.DeviceBlockEntity
import org.neoflock.neocomputers.gui.menu.RackMenu
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.utils.ContainerUtils
import org.neoflock.neocomputers.utils.GenericContainer
class RackEntity(pos: BlockPos, state: BlockState) : DeviceBlockEntity(BlockEntities.RACK_ENTITY.get(), pos, state), ExtendedMenuProvider, GenericContainer {
val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(4, ItemStack.EMPTY)
var conns = mutableListOf(
-1, -1, -1, -1,
-1, -1, -1, -1,
-1, -1, -1, -1,
-1, -1, -1, -1
)
var relayMode = false
val node: DeviceNode = object : DeviceNode() {
override var reachability: Networking.Visibility = Networking.Visibility.NONE
override fun writeFullStateCommit(buf: FriendlyByteBuf) {
super.writeFullStateCommit(buf)
buf.writeBoolean(relayMode)
val tag = CompoundTag() // better way to do this, a better way i do not care to find atm
ContainerHelper.saveAllItems(tag, stacks, level!!.registryAccess())
buf.writeNbt(tag)
for (conn in conns) {
buf.writeInt(conn)
}
}
override fun processCommit(buf: FriendlyByteBuf) {
super.processCommit(buf)
relayMode = buf.readBoolean()
val tag = buf.readNbt()
ContainerHelper.loadAllItems(tag!!, stacks, level!!.registryAccess())
for (i in 0..15) {
conns[i] = buf.readInt()
}
markChanged()
setChanged()
}
override fun processScreenInteraction(player: ServerPlayer, buf: FriendlyByteBuf) {
super.processScreenInteraction(player, buf)
relayMode = buf.readBoolean()
val slot = buf.readInt()
conns[slot*4+0] = buf.readInt()
conns[slot*4+1] = buf.readInt()
conns[slot*4+2] = buf.readInt()
conns[slot*4+3] = buf.readInt()
markChanged()
setChanged()
}
override fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) { // TODO: set this up so other players can mess with racks
super.encodeScreenData(player, buf)
}
}
override fun setChanged() {
super.setChanged()
node.markChanged()
}
override fun getDisplayName(): Component? = Component.literal("Rack")
override fun createMenu(i: Int, inventory: Inventory, player: Player): AbstractContainerMenu {
return RackMenu(i, inventory, this)
}
override fun getDeviceNodes(): List<DeviceNode> = listOf(node)
override fun getNodeFromSide(directionToRequester: Direction): DeviceNode? = node
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
ContainerHelper.loadAllItems(tag, getItems(), registries)
relayMode = tag.getBoolean("relay")
val connarray = tag.getIntArray("conns")
if (connarray.size == 16) conns = connarray.toMutableList()
setChanged()
}
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
ContainerHelper.saveAllItems(tag, getItems(), registries)
tag.putBoolean("relay", relayMode)
tag.putIntArray("conns", conns.toIntArray())
}
// override fun getItems(): NonNullList<ItemStack> = items
override fun getItems(): NonNullList<ItemStack> = stacks
override fun stillValid(player: Player): Boolean = true
override fun saveExtraData(buf: FriendlyByteBuf?) {
buf!!.writeBlockPos(blockPos)
}
}

View File

@@ -0,0 +1,16 @@
package org.neoflock.neocomputers.entity
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
class RobotEntity(pos: BlockPos, state: BlockState) : BlockEntity(BlockEntities.ROBOT_ENTITY.get(), pos, state,) {
val body: ModelPart? = null
val name = "Diddyx" //TODO: names
init {
NeoComputers.LOGGER.info("yooo")
}
}

View File

@@ -0,0 +1,117 @@
package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos
import net.minecraft.locale.Language
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.gui.buffer.BufferRenderer
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.NNComponent
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.utils.GPUChar
import org.neoflock.neocomputers.utils.TextBuffer
import kotlin.text.ifEmpty
class ScreenEntity(blockPos: BlockPos, blockState: BlockState) :
SingleDeviceBlockEntity(BlockEntities.SCREEN_ENTITY.get(), blockPos, blockState) {
var lastError: String? = null
var isOn: Boolean = false
override val deviceNode = object : DeviceNode() {
override fun received(message: Networking.Message) {
super.received(message)
if(message is Networking.ComputerEvent) {
val mEnv = message.machineEvent
NeoComputers.LOGGER.info("Got message $mEnv!")
if(mEnv is MachinePowerEvent) {
if(mEnv.nowRunning) {
lastError = null
textBuf.fill(0, 0, textBuf.width, textBuf.height, GPUChar(' '))
textBuf.set(0, 0, address.toString())
}
isOn = mEnv.nowRunning
markChanged()
}
if(mEnv is MachineCrashEvent) {
lastError = mEnv.error
markChanged()
}
}
}
override fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) {
super.encodeScreenData(player, buf)
textBuf.encodeContents(buf)
}
override fun writeFullStateCommit(buf: FriendlyByteBuf) {
super.writeFullStateCommit(buf)
buf.writeUUID(address)
buf.writeBoolean(isOn)
buf.writeUtf(lastError ?: "")
textBuf.encodeContents(buf)
}
override fun processCommit(buf: FriendlyByteBuf) {
super.processCommit(buf)
if(Networking.changeNodeAddress(this, buf.readUUID())) createScreenTexture()
isOn = buf.readBoolean()
lastError = buf.readUtf().ifEmpty { null }
textBuf.decodeContents(buf)
}
override fun getComponent() = NNComponent("screen")
}
var bound = "screen/unbound"
val textBuf = TextBuffer(50, 16)
private var cleanRenderer: () -> Unit = { } // TODO: THIS SUCKS, FIND A BETTER WAY
override fun tickDevice(level: Level) {
super.tickDevice(level)
cleanRenderer()
createScreenTexture()
}
override fun setRemoved() {
super.setRemoved()
bound = "screen/unbound" // ensure no missing texture is displayed
cleanRenderer()
}
private fun createScreenTexture() {
bound = "screen/"+deviceNode.address.toString().replace("-", "_")
if (level!!.isClientSide) {
if(lastError == null) {
if(!isOn) {
textBuf.fill(0, 0, textBuf.width, textBuf.height)
}
val renderer = BufferRenderer(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, bound), textBuf)
renderer.drawBuffer()
cleanRenderer = { renderer.clean() }
} else {
var trueError = lastError!!
if(trueError.startsWith("@")) {
val trans = trueError.substring(1)
val lang = Language.getInstance()
trueError = lang.getOrDefault("neocomputers.computer.errorNoMsg", "Error: ") + lang.getOrDefault(trans)
}
val throwAwayBuf = TextBuffer(50, 16)
val fg = 0xFFFFFF
val bg = 0x2B68A6
throwAwayBuf.fill(0, 0, throwAwayBuf.width, throwAwayBuf.height, GPUChar(' ', fg, bg))
throwAwayBuf.set((throwAwayBuf.width - trueError.length) / 2, throwAwayBuf.height/2, trueError, fg, bg)
val renderer = BufferRenderer(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, bound), throwAwayBuf)
renderer.drawBuffer()
cleanRenderer = { renderer.clean() }
}
}
}
}

View File

@@ -0,0 +1,38 @@
package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.PowerRole
class SolarGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : SingleDeviceBlockEntity(BlockEntities.SOLARGEN_ENTITY.get(), blockPos, blockState) {
val energyPerTick: Long = 10
override val deviceNode = object : DeviceNode() {
override var powerRole: PowerRole = PowerRole.GENERATOR
override var energyCapacity: Long = 50000
}
override fun tickDevice(level: Level) {
super.tickDevice(level)
val l = level
if(l.isDay) {
deviceNode.giveEnergy(energyPerTick)
}
}
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider)
deviceNode.energy = compoundTag.getLong("energy")
}
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.saveAdditional(compoundTag, provider)
compoundTag.putLong("energy", deviceNode.energy)
}
}

View File

@@ -0,0 +1,69 @@
package org.neoflock.neocomputers.entity.render
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.math.Axis
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.core.Direction
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.CaseBlock
import org.neoflock.neocomputers.entity.CaseBlockEntity
class CaseEntityRenderer(private val context: BlockEntityRendererProvider.Context?) :
BlockEntityRenderer<CaseBlockEntity> {
val OFF = 0xFF5F855E.toInt()
val GREEN = 0xFF4EDC5E.toInt()
val RED = 0xFFff102B.toInt()
val BLINKTIME: Long = 10 // in ticks
val RENDER_TYPE = RenderType.create("nc_case", DefaultVertexFormat.POSITION_COLOR_LIGHTMAP, VertexFormat.Mode.QUADS,
RenderType.TRANSIENT_BUFFER_SIZE, RenderType.CompositeState.builder()
.setShaderState(RenderStateShard.POSITION_COLOR_LIGHTMAP_SHADER)
.setLightmapState(RenderStateShard.LightmapStateShard.LIGHTMAP)
.createCompositeState(false))
override fun render(ent: CaseBlockEntity, partialTick: Float, mat: PoseStack, bufferSource: MultiBufferSource, packedLight: Int, packedOverlay: Int) {
val buffer = bufferSource.getBuffer(RENDER_TYPE);
mat.pushPose()
handleDirection(ent.blockState.getValue(CaseBlock.FACING), mat)
mat.translate(5/16F, 14/16F, 0.0001F)
if (ent.isOn) drawLED(buffer, mat.last(), 3F)
else if (ent.getLastError() != null) { // if else hell
if ((ent.level!!.dayTime/BLINKTIME) % 2 == 1.toLong()) drawLED(buffer, mat.last(), 3F, RED)
else drawLED(buffer, mat.last(), 3F, OFF, packedLight)
} else drawLED(buffer, mat.last(), 3F, OFF, packedLight)
mat.translate(6/16F, 0F, 0F)
drawLED(buffer, mat.last(), 2F, if (ent.diskActivityTime > 0) GREEN else OFF, if (ent.diskActivityTime > 0) LightTexture.FULL_BRIGHT else packedLight)
mat.popPose()
}
private fun drawLED(buffer: VertexConsumer, mat: PoseStack.Pose, width: Float, color: Int = GREEN, light: Int = LightTexture.FULL_BRIGHT) {
buffer.addVertex(mat, width/16F, 0F, 0F).setColor(color).setLight(light)
buffer.addVertex(mat, width/16F, 1/16F, 0F).setColor(color).setLight(light)
buffer.addVertex(mat, 0F, 1/16F, 0F).setColor(color).setLight(light)
buffer.addVertex(mat, 0F, 0F, 0F).setColor(color).setLight(light)
}
private fun handleDirection(facing: Direction, mat: PoseStack) {
when (facing) {
Direction.SOUTH -> { mat.translate(0F, 0F, 1F) }
Direction.EAST -> { mat.mulPose(Axis.YP.rotationDegrees(90F)); mat.translate(-1F, 0F, 1F) }
Direction.WEST -> { mat.mulPose(Axis.YN.rotationDegrees(90F)); }
Direction.NORTH -> {mat.mulPose(Axis.YP.rotationDegrees(180F)); mat.translate(-1F, 0F, 0F) }
else -> return
}
}
}

View File

@@ -0,0 +1,39 @@
package org.neoflock.neocomputers.entity.render
import com.mojang.authlib.minecraft.client.MinecraftClient
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.LevelRenderer
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.block.ModelBlockRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.world.level.LightLayer
import net.minecraft.world.level.dimension.DimensionType
import net.minecraft.world.level.lighting.BlockLightEngine
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.RackEntity
import org.neoflock.neocomputers.item.RackItem
class RackEntityRenderer(val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer<RackEntity> {
override fun render(ent: RackEntity, partialTick: Float, poseStack: PoseStack, source: MultiBufferSource, packedLight: Int, packedOverlay: Int) {
poseStack.pushPose()
poseStack.translate(1/16f, 11/16f, 1/16f)
val items = ent.stacks
for (i in 0..3) {
if (items[i].item is RackItem) {
val item = items[i].item as RackItem
item.render(source, poseStack, packedLight, 2f+(3*i))
}
poseStack.translate(0f, -3/16f, 0f)
}
// val render_slot = (ent.level!!.dayTime/40)%4 // this is purely temporary type shit like true alpha shit, anyway it go to 0-3, change and test it if you want
// poseStack.translate(0f, (render_slot)*-3/16f, 0f)
// val server = object : RackItem { override fun render_lights(source: MultiBufferSource, stack: PoseStack, light: Int) { } }
// server.render(source, poseStack, packedLight, 2f+(3*render_slot)) // who knows atp
poseStack.popPose()
}
}

View File

@@ -0,0 +1,66 @@
package org.neoflock.neocomputers.entity.render
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.math.Axis
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.RelayEntity
import java.util.Timer
import kotlin.math.min
class RelayEntityRenderer(val context: BlockEntityRendererProvider.Context?): BlockEntityRenderer<RelayEntity> {
val RELAY_ON_TEX = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/block/relay_side_on.png")
val RENDER_TYPE: RenderType =
RenderType.create(
"nc_screen",
DefaultVertexFormat.POSITION_TEX_COLOR,
VertexFormat.Mode.QUADS,
RenderType.TRANSIENT_BUFFER_SIZE, RenderType.CompositeState.builder().
setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionTexColorShader)).
setTextureState(RenderStateShard.TextureStateShard(RELAY_ON_TEX, false, false))
.createCompositeState(false))
override fun render(
blockEntity: RelayEntity,
partialTick: Float,
mat: PoseStack,
bufferSource: MultiBufferSource,
packedLight: Int,
packedOverlay: Int
) {
if(blockEntity.activityTickLeft == 0) return
val alpha = min((blockEntity.activityTickLeft.toFloat() * 255 / 20).toInt(), 255)
for(i in 0..<4) {
mat.pushPose()
val antiZFight = 0.001F
mat.rotateAround(Axis.YN.rotationDegrees(90F * i), 0.5F, 0.5F, 0.5F)
mat.translate(0F, 0F, 1F + antiZFight)
val width = 1F
val height = 1F
val bx = 0F
val by = 0F
val buffer = bufferSource.getBuffer(RENDER_TYPE)
buffer.addVertex(mat.last(), bx + width, by, 0f).setUv(1f, 1f).setColor(alpha, alpha, alpha, alpha)
buffer.addVertex(mat.last(), bx + width, by + height, 0f).setUv(1f, 0f).setColor(alpha, alpha, alpha, alpha)
buffer.addVertex(mat.last(), bx, by + height, 0f).setUv(0f, 0f).setColor(alpha, alpha, alpha, alpha)
buffer.addVertex(mat.last(), bx, by, 0f).setUv(0f, 1f).setColor(alpha, alpha, alpha, alpha)
mat.popPose()
}
}
}

View File

@@ -0,0 +1,122 @@
package org.neoflock.neocomputers.entity.render
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.Font
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.ItemBlockRenderTypes
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderType.CompositeState
import net.minecraft.client.renderer.block.ModelBlockRenderer
import net.minecraft.client.renderer.block.model.BakedQuad
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.client.renderer.texture.TextureAtlas
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.resources.model.BakedModel
import net.minecraft.client.resources.model.Material
import net.minecraft.client.resources.model.ModelResourceLocation
import net.minecraft.client.resources.model.ModelState
import net.minecraft.core.Direction
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.RandomSource
import net.minecraft.world.item.DyeColor
import net.minecraft.world.phys.Vec3
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.model.RobotModel
import org.neoflock.neocomputers.entity.RobotEntity
import kotlin.math.sin
class RobotEntityRenderer(val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer<RobotEntity> {
val atlas: (Material) -> TextureAtlasSprite = { m ->
Minecraft.getInstance().getTextureAtlas(m.atlasLocation()).apply(m.texture())
}
val loc: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "robot")
var model: BakedModel? = Minecraft.getInstance().modelManager.getModel(ModelResourceLocation.inventory(loc))
val renderer: ModelBlockRenderer = ModelBlockRenderer(Minecraft.getInstance().blockColors) // so ass
val STREAK_RENDER_TYPE = RenderType.create("nc_robot_streak", DefaultVertexFormat.POSITION_TEX_COLOR, VertexFormat.Mode.QUADS, RenderType.TRANSIENT_BUFFER_SIZE,
CompositeState.builder()
.setShaderState(RenderStateShard.ShaderStateShard { GameRenderer.getPositionTexColorShader() })
.setTransparencyState(RenderStateShard.ADDITIVE_TRANSPARENCY)
.setTextureState(RenderStateShard.TextureStateShard(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/block/robot.png"), false, false))
.createCompositeState(false))
override fun render(ent: RobotEntity, partialTick: Float, poseStack: PoseStack, bufferSource: MultiBufferSource, packedLight: Int, packedOverlay: Int) {
poseStack.pushPose()
// poseStack.translate(0f, sin(ent.level!!.dayTime.toFloat()/20F)*0.02F, 0f)
val modelbuffer = bufferSource.getBuffer(RenderType.entitySolid(TextureAtlas.LOCATION_BLOCKS))
val colorbuffer = bufferSource.getBuffer(STREAK_RENDER_TYPE)
val col = DyeColor.LIGHT_GRAY.fireworkColor
val red = ((col and 0xFF0000) shr 8*2) / 255f
val green = ((col and 0xFF00) shr 8) / 255f
val blue = ((col and 0xFF)) / 255f
renderer.renderModel(poseStack.last(), modelbuffer, ent.blockState, model!!, red, green, blue, packedLight, packedOverlay)
renderLight(poseStack, colorbuffer, (ent.level!!.dayTime%16).toInt())
// TODO: crafting table and chest little models
poseStack.popPose()
renderTag(ent, Component.literal(ent.name), poseStack, bufferSource, packedLight, partialTick)
}
// offset is 0-15
fun renderLight(poseStack: PoseStack, buffer: VertexConsumer, offset: Int) {
poseStack.pushPose()
val u1 = 0.5f
val v1 = 0.5f + offset*1/32f
val u2 = 1F
val v2 = 17/32f + offset*1/32f
for (i in 0..3) {
poseStack.rotateAround(Axis.YP.rotationDegrees(90f), 0.5f, 0.5f, 0.5f)
buffer.addVertex(poseStack.last(),0.1f+2/16f, 7/16f, 0.9f-2/16f).setColor(1f, 0f, 0f, 1f).setUv(u2, v2)
buffer.addVertex(poseStack.last(),0.1f+2/16f, 9/16f, 0.9f-2/16f).setColor(1f, 0f, 0f, 1f).setUv(u2, v1)
buffer.addVertex(poseStack.last(),0.1f+2/16f, 9/16f, 0.1f+2/16f).setColor(1f, 0f, 0f, 1f).setUv(u1, v1)
buffer.addVertex(poseStack.last(),0.1f+2/16f, 7/16f, 0.1f+2/16f).setColor(1f, 0f, 0f, 1f).setUv(u1, v2)
}
poseStack.popPose()
}
fun renderTag(ent: RobotEntity, name: Component, stack: PoseStack, source: MultiBufferSource, light: Int, ptick: Float) {
val d = Minecraft.getInstance().cameraEntity!!.distanceToSqr(ent.blockPos.center)
if (d > 4096.0) return
val vec = Vec3(0.5, 20 / 16.0, 0.5)
stack.pushPose()
stack.translate(vec.x, vec.y, vec.z)
// stack.mulPose(context.entityRenderer.cameraOrientation())
stack.scale(0.025F, -0.025F, 0.025F)
val opacity = Minecraft.getInstance().options.getBackgroundOpacity(0.25F)
val alpha: Int = (opacity * 255.0f).toInt() shl 24
// val alpha = 255
val halfwidth = (-context.font.width(name)) / 2;
RenderSystem.disableDepthTest()
context.font.drawInBatch(name, halfwidth.toFloat(), 2f, 0xFFFFFF, false, stack.last().pose(), source, Font.DisplayMode.SEE_THROUGH, alpha, light)
RenderSystem.enableDepthTest()
stack.popPose()
}
}

View File

@@ -0,0 +1,71 @@
package org.neoflock.neocomputers.entity.render
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.math.Axis
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.core.Direction
import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.ScreenBlock
import org.neoflock.neocomputers.entity.ScreenEntity
class ScreenEntityRenderer(val context: BlockEntityRendererProvider.Context?) : BlockEntityRenderer<ScreenEntity> { // TODO: FORGE
val RENDER_TYPE: (ResourceLocation) -> RenderType = { t: ResourceLocation ->
RenderType.create(
"nc_screen",
DefaultVertexFormat.POSITION_TEX,
VertexFormat.Mode.QUADS,
RenderType.TRANSIENT_BUFFER_SIZE, RenderType.CompositeState.builder().
setShaderState(RenderStateShard.POSITION_TEX_SHADER).
setTextureState(RenderStateShard.TextureStateShard(t, false, false))
.createCompositeState(false))
}
override fun render(entity: ScreenEntity, partialTick: Float, mat: PoseStack, bufferSource: MultiBufferSource, packedLight: Int, packedOverlay: Int) {
if(!entity.isOn && entity.lastError == null) return
val facing = entity.blockState.getValue(ScreenBlock.FACING_HORIZ)
val vert = entity.blockState.getValue(ScreenBlock.FACING_VERTI)-1
mat.pushPose()
handleDirection(facing, vert, mat)
mat.translate(2 / 16f, 2 / 16f, 0.0001f) // am i epstein or am i just retarded
val width = 3/4F
val height = 3/4F
val bx = 0F
val by = 0F
val rendertype = RENDER_TYPE(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, entity.bound))
val buffer = bufferSource.getBuffer(rendertype)
buffer.addVertex(mat.last(), bx+width, by, 0f).setUv(1f, 1f)
buffer.addVertex(mat.last(), bx+width, by+height, 0f).setUv(1f, 0f)
buffer.addVertex(mat.last(), bx, by+height, 0f).setUv(0f, 0f)
buffer.addVertex(mat.last(), bx, by, 0f).setUv(0f, 1f)
mat.popPose()
}
private fun handleDirection(facing: Direction, vert: Int, mat: PoseStack) { // TODO: separate up and down from cardinal directions
// mat.mulPose(Axis.XN.rotationDegrees(vert.toFloat()*90F))
// if (vert==0) {
// mat.mulPose(Axis.YP.rotationDegrees(90F))
// return
// }
when (facing) {
Direction.SOUTH -> { mat.translate(0F, 0F, 1F) }
Direction.EAST -> { mat.mulPose(Axis.YP.rotationDegrees(90F)); mat.translate(-1F, 0F, 1F) }
Direction.WEST -> { mat.mulPose(Axis.YN.rotationDegrees(90F)); }
Direction.NORTH -> {mat.mulPose(Axis.YP.rotationDegrees(180F)); mat.translate(-1F, 0F, 0F) }
else -> {}
// Direction.UP -> { mat.mulPose(Axis.XN.rotationDegrees(90F)); mat.mulPose(Axis.ZP.rotationDegrees(180F)); mat.translate(-1F, 0F, 1F) } // idek
// Direction.DOWN -> { mat.mulPose(Axis.XP.rotationDegrees(90F)); mat.mulPose(Axis.ZN.rotationDegrees(180F)); mat.translate(-1F, -1F, 0F) }
}
mat.mulPose(Axis.XN.rotationDegrees(vert*90F))
mat.translate(0F, if (vert==-1) -1F else 0F, if (vert==1) 1F else 0F)
}
}

View File

@@ -0,0 +1,46 @@
package org.neoflock.neocomputers.gui.menu;
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import org.neoflock.neocomputers.entity.CaseBlockEntity
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.gui.widget.ComponentSlot
import org.neoflock.neocomputers.gui.widget.ComponentSlotRequirement
import org.neoflock.neocomputers.utils.GenericContainerMenu
open class CaseMenu : GenericContainerMenu {
open val eepromRequirement = ComponentSlotRequirement(1, ComponentRoles.FIRMWARE)
open val slotRequirements = listOf(
listOf(ComponentSlotRequirement(1, ComponentRoles.CARD), ComponentSlotRequirement(1, ComponentRoles.CARD)),
listOf(ComponentSlotRequirement(1, ComponentRoles.COMPUTE), ComponentSlotRequirement(1, ComponentRoles.MEMORY), ComponentSlotRequirement(1, ComponentRoles.MEMORY)),
listOf(ComponentSlotRequirement(1, ComponentRoles.STORAGE)),
)
constructor(i: Int, inv: Inventory) : this(i, inv, SimpleContainer(7))
constructor(i: Int, inv: Inventory, container: Container) : super(Menus.CASE_MENU.get(), i, container) {
val machine = container as? CaseBlockEntity
this.addSlot(ComponentSlot(container, 0, 20, 34, machine, eepromRequirement))
var i = 1
for ((col, slotCol) in slotRequirements.withIndex()) {
for ((row, slotReq) in slotCol.withIndex()) {
this.addSlot(ComponentSlot(container, i, 98+(col*22), 18*(row+1)-2, machine, slotReq))
i++
}
}
this.addInventorySlots(inv, 8, 84)
// for (int col=1; col<4; col++) {
// for (int row=1; row<4; row++) {
// int i = (row-1)*3+(col-1);
// if(slotmap[tier][i] != null) {
// this.addSlot(new ComponentSlot(entity.getContainer(), ((col-1)*3)+row, 98+((col-1)*22), 18*row-2, slotmap[tier][i], tiermap[tier][i]));
// }
// }
// }
}
}

View File

@@ -0,0 +1,32 @@
package org.neoflock.neocomputers.gui.menu
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.gui.widget.DynamicSlot
import org.neoflock.neocomputers.utils.ContainerUtils
import org.neoflock.neocomputers.utils.GenericContainerMenu
class CombustionFuelSlot(container: Container, slot: Int, x: Int, y: Int): DynamicSlot(container, slot, x, y) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return ContainerUtils.isBurningFuel(itemStack)
}
}
class CombustionGeneratorMenu: GenericContainerMenu {
// Client-side constructor, idk forge tells me to do this
constructor(id: Int, inventory: Inventory): this(id, inventory, SimpleContainer(1))
// Server-side constructor
constructor(id: Int, inventory: Inventory, container: Container): super(Menus.COMBUSTGEN_MENU.get(), id, container) {
container.startOpen(inventory.player)
this.addSlot(CombustionFuelSlot(container, 0, 80, 35))
this.addInventorySlots(inventory, 8, 84)
}
}

View File

@@ -0,0 +1,36 @@
package org.neoflock.neocomputers.gui.menu;
import dev.architectury.registry.menu.MenuRegistry
import dev.architectury.registry.registries.DeferredRegister
import dev.architectury.registry.registries.RegistrySupplier
import net.minecraft.client.gui.screens.MenuScreens
import net.minecraft.core.registries.Registries
import net.minecraft.world.flag.FeatureFlagSet
import net.minecraft.world.flag.FeatureFlags
import net.minecraft.world.inventory.MenuType
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.gui.menu.ScreenMenu
import org.neoflock.neocomputers.gui.screen.CaseScreen
import org.neoflock.neocomputers.gui.screen.CombustionGeneratorScreen
import org.neoflock.neocomputers.gui.screen.RackScreen
import org.neoflock.neocomputers.gui.screen.RelayScreen
import org.neoflock.neocomputers.gui.screen.ScreenScreen
object Menus {
val MENUS: DeferredRegister<MenuType<*>> = DeferredRegister.create(NeoComputers.MODID, Registries.MENU)
val SCREEN_MENU: RegistrySupplier<MenuType<ScreenMenu>> = MENUS.register("screen_menu") { MenuRegistry.ofExtended<ScreenMenu>(::ScreenMenu) }
val COMBUSTGEN_MENU: RegistrySupplier<MenuType<CombustionGeneratorMenu>> = MENUS.register("combustgen_menu") { MenuType(::CombustionGeneratorMenu, FeatureFlagSet.of() ) }
val CASE_MENU: RegistrySupplier<MenuType<CaseMenu>> = MENUS.register("case_menu") { MenuType(::CaseMenu, FeatureFlagSet.of() )}
val RELAY_MENU: RegistrySupplier<MenuType<RelayMenu>> = MENUS.register("relay_menu") { MenuType(::RelayMenu, FeatureFlagSet.of() )}
val RACK_MENU: RegistrySupplier<MenuType<RackMenu>> = MENUS.register("rack_menu") { MenuRegistry.ofExtended(::RackMenu) }
// val RACK_MENU: RegistrySupplier<MenuType<RackMenu>> = MENUS.register("rack_menu") { MenuType(::RackMenu, FeatureFlagSet.of() )}
fun registerScreens() {
MenuScreens.register(Menus.COMBUSTGEN_MENU.get()) { m: CombustionGeneratorMenu, u, comp ->CombustionGeneratorScreen(m,u,comp)}
MenuScreens.register(Menus.SCREEN_MENU.get(), ::ScreenScreen)
MenuScreens.register(Menus.CASE_MENU.get(), ::CaseScreen)
MenuScreens.register(Menus.RELAY_MENU.get(), ::RelayScreen)
MenuScreens.register(Menus.RACK_MENU.get(), ::RackScreen)
}
}

View File

@@ -0,0 +1,192 @@
package org.neoflock.neocomputers.gui.menu
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.BufferUploader
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import io.netty.buffer.Unpooled
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.RackEntity
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.gui.widget.DynamicSlot
import org.neoflock.neocomputers.item.ComponentItem
import org.neoflock.neocomputers.utils.GenericContainerMenu
class RackSlot(container: Container, slot: Int, x: Int, y: Int) : DynamicSlot(container, slot, x, y), GuiEventListener {
// i hate that i made this, my regret is immeasurable
val MAIN_COLOURS = listOf(0xff8382d8.toInt(), 0xff75bdc1.toInt(), 0xffc8ca5f.toInt(), 0xffdb7d75.toInt(), 0xff7ec95f.toInt())
val DARK_COLOURS = listOf(0xff6a6ab0.toInt(), 0xff60999d.toInt(), 0xffa2a44e.toInt(), 0xffb36660.toInt(), 0xff67a34e.toInt())
val LIGHT_COLOURS = listOf(0xffdcdcf0.toInt(), 0xffdcdcf0.toInt(), 0xffececd4.toInt(), 0xfff0dbd9.toInt(), 0xffdbecd4.toInt())
val secondaries = 2 // TODO: make this actually change depending on how many network cards
// todo: kotlin getters and setters
fun getSelected(i: Int): Int = (container as RackEntity).conns[containerSlot*4+i]
fun setSelected(i: Int, v: Int) { (container as RackEntity).conns[containerSlot*4+i] = v }
override fun draw(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
super.draw(graphics, mouseX, mouseY)
if (!hasItem()) { drawQuad(graphics, ComponentRoles.getTextureFor("rack"), x, y, 16, 16, 0f, 0f, 15f, 15f); return; }
for (i in 0..secondaries) {
if (getSelected(i) > -1) drawConnection(graphics, getSelected(i), i-1)
}
drawEndpoints(graphics, mouseX, mouseY, secondaries)
}
override fun mayPlace(stack: ItemStack): Boolean {
if (stack.item !is ComponentItem) return false
return (stack.item as ComponentItem).getComponentRoles(stack).contains(ComponentRoles.RACK_MOUNTABLE)
}
override fun onTake(player: Player, stack: ItemStack) {
super.onTake(player, stack)
setSelected(0, -1)
setSelected(1, -1)
setSelected(2, -1)
setSelected(3, -1)
(container as RackEntity).setChanged()
}
fun drawConnection(guiGraphics: GuiGraphics, side: Int, sec: Int = -1) {
val bufferSource = guiGraphics.bufferSource()
val buffer = bufferSource.getBuffer(RenderType.gui())
if (sec == -1) {
drawColor(guiGraphics, buffer, 18, 1, DARK_COLOURS[side], 6 + (11 * side))
drawColor(guiGraphics, buffer, 18, 2, MAIN_COLOURS[side], 6 + (11 * side))
drawColor(guiGraphics, buffer, 18, 3, LIGHT_COLOURS[side], 6 + (11 * side))
} else {
drawColor(guiGraphics, buffer, 18, 6+(4*sec), MAIN_COLOURS[side], 6+(11*side))
drawColor(guiGraphics, buffer, 18, 7+(4*sec), 0xff8f8f90.toInt(), 6+(11*side))
}
}
fun drawEndpoints(guiGraphics: GuiGraphics, mx: Int, my: Int, sec: Int =0) {
val bufferSource = guiGraphics.bufferSource()
val buffer = bufferSource.getBuffer(RenderType.gui())
// main slot endpoint
drawColor(guiGraphics, buffer, 17, 1, 0xff888888.toInt())
drawColor(guiGraphics, buffer, 17, 3, 0xffffffff.toInt())
// main cable endpoints
for (i in 0..4) {
drawColor(guiGraphics, buffer, 24+(11*i), 1, 0xff333333.toInt(), 1, 3)
drawColor(guiGraphics, buffer, 25+(11*i), 1, MAIN_COLOURS[i], 3, 3)
drawColor(guiGraphics, buffer, 28+(11*i), 1, 0xffffffff.toInt(), 1, 3)
// highlight
if (mx >= x+25+(11*i) && mx <= x+27+(11*i) && my >= y+1 && my <= y+3) {
drawColor(guiGraphics, buffer, 25+(11*i), 1, 0x80FFFFFF.toInt(), 3, 3)
}
}
// secondary endpoints
for (i in 0..<sec) {
// slot
drawColor(guiGraphics, buffer, 17, 6+(4*i), 0xffffffff.toInt())
drawColor(guiGraphics, buffer, 17, 7+(4*i), 0xff888888.toInt())
// cable
for (j in 0..4) {
drawColor(guiGraphics, buffer, 24+(11*j), 6+(4*i), 0xff333333.toInt(), 1, 2)
drawColor(guiGraphics, buffer, 25+(11*j), 6+(4*i), MAIN_COLOURS[j], 3, 2)
drawColor(guiGraphics, buffer, 28+(11*j), 6+(4*i), 0xffffffff.toInt(), 1, 2)
// highlight
if (mx >= x+25+(11*j) && mx <= x+27+(11*j) && my >= y+6+(4*i) && my <= y+8+(4*i)) {
drawColor(guiGraphics, buffer, 25+(11*j), 6+(4*i), 0x80FFFFFF.toInt(), 3, 2)
}
}
}
}
fun encode(buf: FriendlyByteBuf) { // client -> server
buf.writeInt(containerSlot)
buf.writeInt(getSelected(0))
buf.writeInt(getSelected(1))
buf.writeInt(getSelected(2))
buf.writeInt(getSelected(3))
}
// TODO: replace with graphics.fill (cant be assed atm)
fun drawColor(guiGraphics: GuiGraphics, buffer: VertexConsumer, _x:Int, _y: Int, col: Int, width: Int=1, height: Int=1) {
val pose = guiGraphics.pose().last()
// x+_x+1 is one im not proud of
buffer.addVertex(pose, x+_x+width.toFloat(), y+_y+height.toFloat(), 2f).setColor(col)
buffer.addVertex(pose, x+_x+width.toFloat(), y+_y.toFloat(), 2f).setColor(col)
buffer.addVertex(pose, x+_x.toFloat(), y+_y.toFloat(), 2f).setColor(col)
buffer.addVertex(pose, x+_x.toFloat(), y+_y+height.toFloat(), 2f).setColor(col)
}
fun clickynoise() {
val handler = Minecraft.getInstance().soundManager
handler.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
for (i in 0..4) { // main line
if (mouseX >= x+25+(11*i) && mouseX <= x+28+(11*i) && mouseY >= y+1 && mouseY <= y+4 && button == 0) {
setSelected(0, if (getSelected(0) != i) i else -1)
(container as RackEntity).setChanged()
clickynoise()
return true
}
}
for (i in 0..<secondaries) { // secondary lines
for (j in 0..4) {
if (mouseX >= x+25+(11*j) && mouseX <= x+28+(11*j) && mouseY >= y+6+(4*i) && mouseY <= y+8+(4*i) && button == 0) {
setSelected(i+1, if (getSelected(i+1) != j) j else -1)
(container as RackEntity).setChanged()
clickynoise()
return true
}
}
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun setFocused(focused: Boolean) {
}
override fun isFocused(): Boolean {
return false
}
}
class RackMenu : GenericContainerMenu {
constructor(i: Int, inv: Inventory, buf: FriendlyByteBuf) : this(i, inv, (inv.player.level().getBlockEntity(buf.readBlockPos()) as RackEntity))
constructor(i: Int, inv: Inventory, container: Container) : super(Menus.RACK_MENU.get(), i, container) {
for(i in 0..3) {
val slot = RackSlot(container, i, 20, 23+i*20)
this.addSlot(slot)
}
this.addInventorySlots(inv, 8, 128)
}
}

View File

@@ -0,0 +1,85 @@
package org.neoflock.neocomputers.gui.menu
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.RelayEntity
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.gui.widget.DynamicSlot
import org.neoflock.neocomputers.item.ComponentItem
import org.neoflock.neocomputers.item.RelayUpgrade
import org.neoflock.neocomputers.utils.GenericContainerMenu
class RelaySlot(container: Container, slot: Int, x: Int, y: Int, val role: String, val relay: RelayEntity?) : DynamicSlot(container, slot, x, y) {
override fun mayPlace(stack: ItemStack): Boolean {
if(stack.isEmpty) return true
val upgrade = stack.item as? RelayUpgrade ?: return false
if(containerSlot == RelayEntity.CARD) {
return upgrade.isRelayCompatibleCard(stack)
}
if(containerSlot == RelayEntity.CPU) {
return upgrade.getRelayInterval(stack) != null
}
if(containerSlot == RelayEntity.MEM) {
return upgrade.getRelayBufferSize(stack) != null
}
if(containerSlot == RelayEntity.STORAGE) {
return upgrade.getRelayQueueSize(stack) != null
}
return false
}
override fun set(stack: ItemStack) {
super.set(stack)
val item = stack.item
if(item is ComponentItem) {
item.whenComponentPlaced(stack, relay, role)
}
}
override fun onTake(player: Player, stack: ItemStack) {
super.onTake(player, stack)
val item = stack.item
if(item is ComponentItem) {
item.whenComponentTaken(stack, relay, role)
}
}
override fun getMaxStackSize() = 1
override fun getMaxStackSize(stack: ItemStack) = 1
override fun draw(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
super.draw(graphics, mouseX, mouseY)
if(!hasItem()) {
drawQuad(graphics, ComponentRoles.getTextureFor(role), x-1, y-1, 18, 18, 0f, 0f, 15f, 15f)
}
}
}
class RelayMenu : GenericContainerMenu {
constructor(i: Int, inv: Inventory) : this(i, inv, SimpleContainer(RelayEntity.SLOT_COUNT))
constructor(i: Int, inv: Inventory, container: Container) : super(Menus.RELAY_MENU.get(), i, container) {
val relay = container as? RelayEntity
val relayMenuWidth = 176
val itemX = 152
val itemRowDist = 16
val itemSpacing = 20
this.addSlot(RelaySlot(container, RelayEntity.CARD, relayMenuWidth+4, itemRowDist, ComponentRoles.NETWORK, relay))
this.addSlot(RelaySlot(container, RelayEntity.CPU, itemX, itemRowDist, ComponentRoles.COMPUTE, relay))
this.addSlot(RelaySlot(container, RelayEntity.MEM, itemX, itemRowDist+itemSpacing, ComponentRoles.MEMORY, relay))
this.addSlot(RelaySlot(container, RelayEntity.STORAGE, itemX, itemRowDist+itemSpacing*2, ComponentRoles.STORAGE, relay))
this.addInventorySlots(inv, 8, 84)
}
}

View File

@@ -0,0 +1,23 @@
package org.neoflock.neocomputers.gui.menu;
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import org.neoflock.neocomputers.gui.menu.Menus;
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
import org.neoflock.neocomputers.entity.ScreenEntity
import org.neoflock.neocomputers.utils.GenericContainerMenu
class ScreenMenu : GenericContainerMenu {
var entity: ScreenEntity? = null
constructor(i: Int, inv: Inventory, buf: FriendlyByteBuf) : this(i, inv, inv.player.level().getBlockEntity(buf.readBlockPos()))
constructor(i: Int, inv: Inventory, entity: BlockEntity?) : super(Menus.SCREEN_MENU.get(), i, SimpleContainer(0)) {
this.entity = entity as ScreenEntity
}
}

View File

@@ -0,0 +1,64 @@
package org.neoflock.neocomputers.gui.buffer;
import com.mojang.blaze3d.platform.NativeImage
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.texture.DynamicTexture
import net.minecraft.client.renderer.texture.TextureManager
import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.utils.FontProvider
import org.neoflock.neocomputers.utils.GPUChar
import org.neoflock.neocomputers.utils.TextBuffer
import java.io.File
import kotlin.experimental.and
import kotlin.experimental.xor
class BufferRenderer(private var id: ResourceLocation, private var buffer: TextBuffer) { // TODO: NN buffer
val CHARW = 8
val CHARH = 16
private var texwidth: Int = buffer.width*CHARW;
private var texheight: Int = buffer.height*CHARH;
private var image: NativeImage = NativeImage(texwidth, texheight, true); // idk what the boolean is
private var tex: DynamicTexture = DynamicTexture(image)
init {
Minecraft.getInstance().textureManager.register(this.id, tex)
}
fun toRGBA(color: Int): Int {
// Minecaft lies, its AGBR
return java.lang.Integer.reverseBytes((color.toLong() * 256 + 0xFF).toInt())
}
fun drawGlyph(x: Int, y: Int, c: Char, fg: Int) {
var glyph: ArrayList<Byte> = FontProvider.map[c]!!
for (j in 0..<CHARH) {
for (i in 0..<CHARW) {
// var pixel = ((glyph[j] and ((1 shl (CHARW - i - 1)).toByte())).toInt()) ushr (CHARW - i - 1) // retardation
var pixel = (glyph[j] and (0b10000000 ushr i).toByte()).toInt()
if (pixel > 0) image.setPixelRGBA(x+i, y+j, toRGBA(fg))
}
}
}
fun drawBuffer() {
for (i in 0..<buffer.width) {
for (j in 0..<buffer.height) {
var char: GPUChar = buffer.get(i, j)
var x = i*CHARW
var y = j*CHARH
image.fillRect(x, y, CHARW, CHARH, toRGBA(char.bg))
if (char.c != ' ' && char.c != '\u0000') drawGlyph(x, y, char.c, char.fg)
}
}
tex.upload()
}
fun clean() {
Minecraft.getInstance().textureManager.release(this.id)
image.close()
tex.close()
}
}

View File

@@ -0,0 +1,57 @@
package org.neoflock.neocomputers.gui.widget
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.BufferBuilder
import com.mojang.blaze3d.vertex.BufferUploader
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.RenderType
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.Item
import org.neoflock.neocomputers.NeoComputers
import java.util.function.Supplier
import kotlin.math.ceil
// #66CC66
object ProgressBar {
// NOTE: OC never uses a different width and height
fun render(guiGraphics: GuiGraphics, x: Int, y: Int, value: Long, max: Long, mouseX: Int, mouseY: Int, width: Int=142, height: Int=14, tooltipfunc: (Int) -> String?) {
RenderSystem.disableBlend()
guiGraphics.fill(x, y, x+width-1, y+1, 0xFF373737.toInt()) // top left corner + top edge
guiGraphics.fill(x, y+1, x+1, y+height-1, 0xFF373737.toInt()) // left edge
guiGraphics.fill(x, y+height-1, x+1, y+height, 0xFF8B8B8B.toInt()) // bottom left corner
guiGraphics.fill(x+width-1, y, x+width, y+1, 0xFF8B8B8B.toInt()) // top right corner
guiGraphics.fill(x+1, y+height-1, x+width, y+height, 0xFFFFFFFF.toInt()) // bottom right corner + bottom edge
guiGraphics.fill(x+width-1, y+height-1, x+width, y+1, 0xFFFFFFFF.toInt()) // right edge
val frac = if(max == 0L) 0.0f else value.toFloat() / max.toFloat()
val linew = ceil(frac*(width-2).toFloat())
guiGraphics.fill(
RenderType.gui(),
x + 1,
y + 1,
x + 1 + (linew.toInt()),
y + height-1,
0xFF66CC66.toInt()
)
val tooltip = tooltipfunc((ceil(frac * 100F)).toInt()) ?: return
if (mouseX > x && mouseX < x+width && mouseY > y && mouseY < y+height )
guiGraphics.renderTooltip(Minecraft.getInstance().font, Component.literal(tooltip), mouseX, mouseY)
}
}

View File

@@ -0,0 +1,96 @@
package org.neoflock.neocomputers.gui.render;
import com.mojang.blaze3d.platform.GlConst
import com.mojang.blaze3d.platform.NativeImage
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.BufferBuilder
import com.mojang.blaze3d.vertex.BufferUploader
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.texture.DynamicTexture
import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.NeoComputers
class ScreenRenderer {
val BORDERS: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/borders.png")
var bound: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen/unbound")
val bordersize = 10
fun render(graphics: GuiGraphics, x: Int, y: Int, width: Int, height: Int) {
RenderSystem.setShader { GameRenderer.getPositionTexShader() }
RenderSystem.setShaderTexture(0, BORDERS);
// borders
val bordersize = 8; // TODO: this should scale i think
// top left corner, uv (0, 0) to (6,6)
// graphics.blit
drawQuad(graphics, x-bordersize, y-bordersize, bordersize, bordersize, 0F, 0F, 6F, 6F);
// top border, uv (7,0) to (8,6)
drawQuad(graphics, x, y-bordersize, width, bordersize, 7F, 0F, 8F, 6F); // x+bordersize-bordersize
// top right corner, uv (9,0) to (15, 6)
drawQuad(graphics, x+width, y-bordersize, bordersize, bordersize, 9F, 0F, 15F, 6F);
// left border, uv (0,7) to (6, 8)
drawQuad(graphics, x-bordersize, y, bordersize, height, 0F, 7F, 6F, 8F);
// middle, uv (7, 7) to (8, 8)
// drawQuad(graphics, x+bordersize, y+bordersize, width-bordersize, height-bordersize, 7, 7, 8, 8);
// right border uv (9, 7) to (15, 8)
drawQuad(graphics, x+width, y, bordersize, height, 9F, 7F, 15F, 8F);
// bottom left corner, uv (0, 9) to (6, 15)
drawQuad(graphics, x-bordersize, y+height, bordersize, bordersize, 0F, 9F, 6F, 15F);
// bottom border, uv (7, 9) to (8, 15)
drawQuad(graphics, x, y+height, width, bordersize, 7F, 9F, 8F, 15F);
// bottom right corner, uv (9, 9) to (15, 15)
drawQuad(graphics, x+width, y+height, bordersize, bordersize, 9F, 9F, 15F, 15F);
RenderSystem.setShaderTexture(0, bound);
RenderSystem.texParameter(GlConst.GL_TEXTURE_2D, GlConst.GL_TEXTURE_MIN_FILTER, GlConst.GL_NEAREST);
RenderSystem.texParameter(GlConst.GL_TEXTURE_2D, GlConst.GL_TEXTURE_MAG_FILTER, GlConst.GL_NEAREST);
drawQuad(graphics, x, y, width, height, 0F, 0F, 15F, 15F);
}
private fun drawQuad(graphics: GuiGraphics, x: Int, y: Int, width: Int, height: Int, u1: Float, v1: Float, u2: Float, v2: Float) {
var t: Tesselator = Tesselator.getInstance()
var builder: BufferBuilder = t.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
builder.addVertex(x.toFloat(), (y+height).toFloat(), 1f).setUv(u1/15F, v2/15F)
builder.addVertex((x+width).toFloat(), (y+height).toFloat(), 1f).setUv(u2/15F, v2/15F)
builder.addVertex((x+width).toFloat(), y.toFloat(), 1f).setUv(u2/15F, v1/15F)
builder.addVertex(x.toFloat(), y.toFloat(), 1f).setUv(u1/15F,v1/15F)
BufferUploader.drawWithShader(builder.build()!!)
}
fun bind(id: ResourceLocation) {
bound = id
}
fun unbind() {
bound = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen/unbound")
}
companion object Static {
val img: NativeImage = NativeImage(1, 1, false)
val tex: DynamicTexture = DynamicTexture(img)
fun genUnboundTex() {
img.fillRect(0, 0, 1, 1, 0xFF000000.toInt())
tex.upload()
Minecraft.getInstance().textureManager.register(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen/unbound"), tex)
}
fun cleanUnboundTex() {
img.close()
tex.close()
Minecraft.getInstance().textureManager.release(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen/unbound"))
}
}
}

View File

@@ -0,0 +1,103 @@
package org.neoflock.neocomputers.gui.screen;
import io.netty.buffer.Unpooled
import net.minecraft.ChatFormatting
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.inventory.tooltip.TooltipComponent
import net.minecraft.world.phys.Vec3
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.network.NodeSynchronizer
import org.neoflock.neocomputers.gui.menu.CaseMenu
import org.neoflock.neocomputers.gui.widget.ButtonSprites
import org.neoflock.neocomputers.gui.widget.ImagerButton
import org.neoflock.neocomputers.sounds.Sounds
import org.neoflock.neocomputers.utils.Formatting
import org.neoflock.neocomputers.utils.GenericContainerScreen
import java.util.Optional
class CaseScreen : GenericContainerScreen<CaseMenu> {
private val PCB: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/computer.png")
private val BTN: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/button_power.png")
private var btn: ImagerButton? = null
override fun shouldCenterTitle(): Boolean = false
// override fun findMenuTexture(): ResourceLocation = BG
var isOn = false
var lastError: String? = null
var energy: Long = 0L
var maxEnergy: Long = 0L
var memory: Long = 0L
var maxMemory: Long = 0L
var components: Long = 0L
var maxComponents: Long = 0L
var arch = ""
override fun processScreenStatePacket(buf: FriendlyByteBuf) {
super.processScreenStatePacket(buf)
isOn = buf.readBoolean()
btn?.pressed = isOn
val error = buf.readByteArray().decodeToString()
if(error.isEmpty()) {
lastError = null
} else {
lastError = error
}
energy = buf.readLong()
maxEnergy = buf.readLong()
memory = buf.readLong()
maxMemory = buf.readLong()
components = buf.readLong()
maxComponents = buf.readLong()
arch = buf.readUtf()
}
fun getErrorComponent(err: String): Component = if(err.startsWith("@")) Component.translatable(err.substring(1)) else Component.literal(err)
fun computeButtonTooltip(): List<Component> {
val msgs = mutableListOf(Component.translatable(if(isOn) "neocomputers.computer.on" else "neocomputers.computer.off").withStyle(if(isOn) ChatFormatting.GREEN else ChatFormatting.RED))
if(lastError != null) {
msgs.addLast(Component.translatable("neocomputers.computer.errorNoMsg").withStyle(ChatFormatting.RED).append(getErrorComponent(lastError!!)))
}
if(arch.isNotEmpty()) {
msgs.addLast(Component.translatable("neocomputers.arch", arch))
}
if(hasShiftDown()) {
msgs.addLast(Component.translatable("neocomputers.computer.energy", energy, maxEnergy).withStyle(if(energy < maxEnergy/5) ChatFormatting.RED else ChatFormatting.WHITE))
msgs.addLast(Component.translatable("neocomputers.computer.memory", Formatting.formatMemory(memory), Formatting.formatMemory(maxMemory)))
msgs.addLast(Component.translatable("neocomputers.computer.components", components, maxComponents).withStyle(if(components <= maxComponents) ChatFormatting.WHITE else ChatFormatting.RED))
}
return msgs
}
constructor(abstractContainerMenu: CaseMenu, inventory: Inventory, component: Component) : super(abstractContainerMenu, inventory, component) {
btn = ImagerButton(
71, 33,
18, 18,
ButtonSprites(BTN, 18, 18, 36, 36)
) {
val buf = FriendlyByteBuf(Unpooled.buffer())
buf.writeByte(if(isOn) 0x02 else 0x01)
NodeSynchronizer.sendScreenInteraction(buf)
}
addWidget(btn!!)
}
override fun renderbg(guiGraphics: GuiGraphics, f: Float, i: Int, j: Int) {
guiGraphics.blit(PCB, 0, 0, 0, 0, this.imageWidth, this.imageHeight) // WE'RE FREE
}
override fun renderCustomOverlay(graphics: GuiGraphics, mouseX: Int, mouseY: Int, blend: Float) {
super.renderCustomOverlay(graphics, mouseX, mouseY, blend)
if(btn!!.isHovered) {
graphics.renderTooltip(this.font, computeButtonTooltip(), Optional.empty<TooltipComponent>(), mouseX, mouseY)
}
}
}

View File

@@ -0,0 +1,56 @@
package org.neoflock.neocomputers.gui.screen
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.RenderType
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.gui.menu.CombustionGeneratorMenu
import org.neoflock.neocomputers.gui.widget.ProgressBar
import org.neoflock.neocomputers.utils.GenericContainerScreen
import kotlin.math.ceil
class CombustionGeneratorScreen : GenericContainerScreen<CombustionGeneratorMenu> {
// val bar: ProgressBar = ProgressBar(x = -50, y = -50) { Pair(energy, energyCapacity) } // hide it type shi
constructor(abstractContainerMenu: CombustionGeneratorMenu, inventory: Inventory, component: Component) : super(abstractContainerMenu, inventory, component)
var energy: Long = 0
var energyCapacity: Long = 1
override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, something: Float) {
super.render(graphics, mouseX, mouseY, something)
var relX: Int = (this.width - this.imageWidth) / 2;
var relY: Int = (this.height - this.imageHeight) / 2;
ProgressBar.render(graphics, relX+17, relY+55, energy, energyCapacity, mouseX, mouseY) {
String.format("Energy: %d%% (%d/%d)", it, energy, energyCapacity)
}
// bar.x = imageX + 17
// bar.y = imageY + 50
// val lineBg = 0xFF002200.toInt()
// val lineFg = 0xFF00FF00.toInt()
// val lineX = imageX + 8
// val lineY = imageY + 6
// val lineHeight = 60
// val power = energy.toDouble() / energyCapacity
// graphics.fill(lineX, lineY, lineX + 2, lineY + lineHeight, lineFg)
// graphics.fill(lineX, lineY, lineX + 2, lineY + (lineHeight * (1.0 - power)).toInt(), lineBg)
}
override fun getBoundBlockEntityType() = setOf(BlockEntities.COMBUSTGEN_ENTITY.get())
override fun processScreenStatePacket(buf: FriendlyByteBuf) {
energy = buf.readLong()
energyCapacity = buf.readLong()
}
}

View File

@@ -0,0 +1,106 @@
package org.neoflock.neocomputers.gui.screen
import io.netty.buffer.Unpooled
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.SpriteIconButton
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.RackEntity
import org.neoflock.neocomputers.gui.menu.RackMenu
import org.neoflock.neocomputers.gui.menu.RackSlot
import org.neoflock.neocomputers.gui.widget.IconTextButton
import org.neoflock.neocomputers.network.NodeSynchronizer
import org.neoflock.neocomputers.utils.GenericContainerScreen
import java.util.function.Supplier
class RackScreen(menu: RackMenu, inventory: Inventory, component: Component) : GenericContainerScreen<RackMenu>(menu, inventory, component) {
override fun findMenuTexture(): ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/rack.png")
val RELAY = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/relay.png")
var relay_mode = if (menu.container is RackEntity) (menu.container as RackEntity).relayMode else false
val relaybtn = IconTextButton(100, 96, if(relay_mode) "Enabled" else "Disabled", RELAY, width = 64) {
if (relay_mode){
it.message = Component.literal("Disabled")
relay_mode = false
} else {
it.message = Component.literal("Enabled")
relay_mode = true
}
val buffer = FriendlyByteBuf(Unpooled.buffer())
buffer.writeBoolean(relay_mode)
(menu.slots[0] as RackSlot).encode(buffer)
NodeSynchronizer.sendScreenInteraction(buffer)
}
init {
this.imageWidth = 175
this.imageHeight = 209
this.inventoryLabelY = imageHeight - 93
addWidget(relaybtn)
}
override fun renderbg(guiGraphics: GuiGraphics, partialTick: Float, mouseX: Int, mouseY: Int) {
renderSideLabels(guiGraphics)
if(relay_mode) renderRelayConnections(guiGraphics)
}
fun renderRelayConnections(graphics: GuiGraphics) {
for(i in 0..3) {
val x = 50+(i*11)
graphics.fill(x, 104, x+4, 105, 0xffffffff.toInt())
graphics.fill(x, 105, x+4, 106, 0xff888888.toInt())
}
}
fun renderSideLabels(graphics: GuiGraphics) {
val x = 115+7
val y = 20
graphics.drawString(font, "Bottom", x, y, 0x404040, false)
graphics.drawString(font, "Top", x, y+11, 0x404040, false)
graphics.drawString(font, "Back", x, y+22, 0x404040, false)
graphics.drawString(font, "Right", x, y+33, 0x404040, false)
graphics.drawString(font, "Left", x, y+44, 0x404040, false)
}
// override fun processScreenStatePacket(buf: FriendlyByteBuf) {
// super.processScreenStatePacket(buf)
//// NeoComputers.LOGGER.info("porcessing screen state packet...")
//// relay_mode = buf.readBoolean()
//// if (relay_mode) relaybtn.message = Component.literal("Enabled")
//// else relaybtn.message = Component.literal("Disabled")
////
//// for (slot in menu.slots) {
//// if (slot is RackSlot) {
//// slot.processStateScreenPacket(buf)
//// }
//// }
//
// }
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (super.mouseClicked(mouseX, mouseY, button)) return true
for (slot in menu.slots) {
if (slot is RackSlot) {
if(slot.mouseClicked(mouseX-imageX, mouseY-imageY, button)) {
val buf = FriendlyByteBuf(Unpooled.buffer())
buf.writeBoolean(relay_mode)
slot.encode(buf)
NodeSynchronizer.sendScreenInteraction(buf)
return true
}
}
}
return false;
}
}

View File

@@ -0,0 +1,51 @@
package org.neoflock.neocomputers.gui.screen
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.Style
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.DyeColor
import org.neoflock.neocomputers.gui.menu.RelayMenu
import org.neoflock.neocomputers.utils.GenericContainerScreen
import kotlin.math.max
import kotlin.math.min
class RelayScreen(abstractContainerMenu: RelayMenu, inventory: Inventory, component: Component): GenericContainerScreen<RelayMenu>(abstractContainerMenu, inventory, component) {
var interval: Int = 5
var bufferSize: Int = 1
var queueSize: Int = 20
var inQueue: Int = 0
override fun shouldCenterTitle(): Boolean = false
override fun processScreenStatePacket(buf: FriendlyByteBuf) {
super.processScreenStatePacket(buf)
interval = buf.readVarInt()
bufferSize = buf.readVarInt()
queueSize = buf.readVarInt()
inQueue = buf.readVarInt()
}
override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, something: Float) {
super.render(graphics, mouseX, mouseY, something)
val textX = imageX + 12
val dataX = imageX + 100
val textSpacing = 20
val textY = imageY + 20
val clr = ChatFormatting.DARK_GRAY.color!!
graphics.drawString(this.font, "Cycle rate", textX, textY, clr, false)
graphics.drawString(this.font, "Packets / cycle", textX, textY + textSpacing, clr, false)
graphics.drawString(this.font, "Queue Size", textX, textY + textSpacing*2, clr, false)
val hz = if(interval == 0) 20 else 20 / interval
val buffered = min(inQueue, bufferSize)
val queued = max(inQueue - bufferSize, 0)
graphics.drawString(this.font, "$hz Hz", dataX, textY, clr, false)
graphics.drawString(this.font, "$buffered / $bufferSize", dataX, textY + textSpacing, clr, false)
graphics.drawString(this.font, "$queued / $queueSize", dataX, textY + textSpacing * 2, clr, false)
}
}

View File

@@ -0,0 +1,55 @@
package org.neoflock.neocomputers.gui.screen;
import com.mojang.blaze3d.vertex.BufferBuilder
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.ScreenEntity
import org.neoflock.neocomputers.gui.buffer.BufferRenderer
import org.neoflock.neocomputers.gui.menu.ScreenMenu
import org.neoflock.neocomputers.gui.render.ScreenRenderer
import org.neoflock.neocomputers.utils.GenericContainerScreen
import org.neoflock.neocomputers.utils.TextBuffer
import kotlin.math.min
class ScreenScreen : GenericContainerScreen<ScreenMenu>{
private var renderer: ScreenRenderer = ScreenRenderer();
var textBuf = TextBuffer(0, 0)
override fun shouldCenterTitle(): Boolean = false
override fun processScreenStatePacket(buf: FriendlyByteBuf) {
super.processScreenStatePacket(buf)
textBuf.decodeContents(buf)
}
constructor(abstractContainerMenu: ScreenMenu, inventory: Inventory, component: Component) : super(abstractContainerMenu, inventory, component) {
var ent: ScreenEntity = abstractContainerMenu.entity!!;
renderer.bind(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, ent.bound))
// advanced graphics programming
this.titleLabelX = Int.MAX_VALUE
this.inventoryLabelX = Int.MAX_VALUE
}
override fun renderBg(guiGraphics: GuiGraphics, f: Float, i: Int, j: Int) {
if(textBuf.width > 0) {
imageWidth = textBuf.width * 4
imageHeight = textBuf.height * 8
renderer.render(guiGraphics, imageX, imageY, imageWidth, imageHeight)
}
}
override fun renderLabels(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int) { }
// override fun onClose() {
// super.onClose()
// renderer.
// }
}

View File

@@ -0,0 +1,137 @@
package org.neoflock.neocomputers.gui.widget
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.Container
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.item.ComponentItem
// Sort of a mis-nomer, does not need to be associated with components specifically
object ComponentRoles {
// For card slots
val CARD = "card"
// For memory slots
val MEMORY = "memory"
// For storage slots, aside from EEPROMs and floppys
val STORAGE = "storage"
// For floppy drive slots
val FLOPPY = "floppy"
// For EEPROM slots
val FIRMWARE = "firmware"
// For CPU slot
val COMPUTE = "compute"
// For component bus
val BUS = "bus"
val TOOL = "tool"
val UPGRADE = "upgrade"
val CONTAINER = "container"
val TABLET = "tablet"
val RACK_MOUNTABLE = "rack"
// Conventional network cards, like LAN, WLAN0, WLAN1, etc.
val NETWORK = "network"
// Internet cards
val INET = "internet"
val MISSING_ROLE_TEXTURE = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/slots/na.png")
val textureMap = mutableMapOf<String, ResourceLocation>()
fun mapTexture(role: String, texture: ResourceLocation) {
textureMap[role] = texture
}
fun getTextureFor(role: String) = textureMap[role] ?: MISSING_ROLE_TEXTURE
fun mapDefaultTextures() {
val core = mapOf(
CARD to "card",
BUS to "component_bus",
CONTAINER to "container",
COMPUTE to "cpu",
FIRMWARE to "eeprom",
FLOPPY to "floppy",
STORAGE to "hdd",
MEMORY to "memory",
TABLET to "tablet",
TOOL to "tool",
UPGRADE to "upgrade",
RACK_MOUNTABLE to "rack_mountable",
// TODO: give them proper textures
NETWORK to "card",
INET to "card",
)
for((role, tex) in core) {
mapTexture(role, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/slots/$tex.png"))
}
}
}
data class ComponentSlotRequirement(val tier: Int, val role: String) {
fun allowsItemStack(itemStack: ItemStack): Boolean {
val item = itemStack.item
if(item !is ComponentItem) return false
if(tier > 0 && item.getComponentTier(itemStack) > tier) return false
return item.getComponentRoles(itemStack).contains(role)
}
}
// Tier 0 allows ALL tiers, making it completely untiered.
// Role determines what the role is.
class ComponentSlot(container: Container, slot: Int, x: Int, y: Int, val machine: ComponentUser?, val requirement: ComponentSlotRequirement): DynamicSlot(container, slot, x, y) {
override fun draw(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
super.draw(graphics, mouseX, mouseY)
if(!hasItem()) {
// RenderSystem.enableBlend()
// RenderSystem.setShaderTexture(0, ComponentRoles.getTextureFor(requirement.role))
// RenderSystem.setShader { GameRenderer.getPositionTexShader() }
drawQuad(graphics, ComponentRoles.getTextureFor(requirement.role), x - 1, y - 1, 18, 18, 0F, 0F, 15F, 15F)
if (requirement.tier > 0) {
RenderSystem.setShaderTexture(
0,
ResourceLocation.fromNamespaceAndPath(
NeoComputers.MODID,
"textures/gui/slots/tier${requirement.tier - 1}.png"
)
)
val tex = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/slots/tier${requirement.tier - 1}.png")
// RenderSystem.setShader { GameRenderer.getPositionTexShader() }
drawQuad(graphics, tex, x - 1, y - 1, 18, 18, 0F, 0F, 15F, 15F)
}
// RenderSystem.disableBlend()
}
}
override fun mayPlace(itemStack: ItemStack): Boolean {
if(!requirement.allowsItemStack(itemStack)) return false
return super.mayPlace(itemStack)
}
override fun set(itemStack: ItemStack) {
super.set(itemStack)
if(machine != null) {
val item = itemStack.item
if (item is ComponentItem) {
item.whenComponentPlaced(itemStack, machine, requirement.role)
}
}
}
override fun onTake(player: Player, itemStack: ItemStack) {
if(machine != null) {
val item = itemStack.item
if (item is ComponentItem) {
item.whenComponentTaken(itemStack, machine, requirement.role)
}
}
super.onTake(player, itemStack)
}
override fun getMaxStackSize(): Int = 1
override fun getMaxStackSize(itemStack: ItemStack): Int = 1
}

View File

@@ -0,0 +1,45 @@
package org.neoflock.neocomputers.gui.widget
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.BufferBuilder
import com.mojang.blaze3d.vertex.BufferUploader
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderStateShard.ShaderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.Container
import net.minecraft.world.inventory.Slot
import org.neoflock.neocomputers.NeoComputers
open class DynamicSlot(container: Container, slot: Int, x: Int, y: Int) : Slot(container, slot, x, y) {
val BACKGROUND: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/slots/slot.png")
val RENDER_TYPE = { r: ResourceLocation ->
RenderType.create("nc_gui_slot", DefaultVertexFormat.POSITION_TEX, VertexFormat.Mode.QUADS, RenderType.TRANSIENT_BUFFER_SIZE, RenderType.CompositeState.builder()
.setShaderState(ShaderStateShard.POSITION_TEX_SHADER)
.setTextureState(RenderStateShard.TextureStateShard(r, false, false))
.setTransparencyState(RenderStateShard.TransparencyStateShard.TRANSLUCENT_TRANSPARENCY)
.createCompositeState(false))
}
open fun draw(graphics: GuiGraphics, mouseX: Int, mouseY: Int) {
drawQuad(graphics, BACKGROUND, x-1, y-1, 18, 18, 0F, 0F, 15F, 15F)
}
fun drawQuad(guiGraphics: GuiGraphics, tex: ResourceLocation, x: Int, y: Int, width: Int, height: Int, u1: Float, v1: Float, u2: Float, v2: Float) {
val pose = guiGraphics.pose().last()
val builder = guiGraphics.bufferSource().getBuffer(RENDER_TYPE(tex))
builder.addVertex(pose, x.toFloat(), (y+height).toFloat(), 1f).setUv(u1/15F, v2/15F)
builder.addVertex(pose, (x+width).toFloat(), (y+height).toFloat(), 1f).setUv(u2/15F, v2/15F)
builder.addVertex(pose, (x+width).toFloat(), y.toFloat(), 1f).setUv(u2/15F, v1/15F)
builder.addVertex(pose, x.toFloat(), y.toFloat(), 1f).setUv(u1/15F,v1/15F)
}
}

View File

@@ -0,0 +1,49 @@
package org.neoflock.neocomputers.gui.widget
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.Font
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.renderer.texture.TextureAtlas
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.ResourceLocationPattern
import org.neoflock.neocomputers.NeoComputers
import java.util.function.Supplier
class press(val lambda: (IconTextButton) -> Unit) : Button.OnPress {
override fun onPress(button: Button) {
lambda(button as IconTextButton)
}
}
class narr : Button.CreateNarration {
override fun createNarrationMessage(supplier: Supplier<MutableComponent?>): MutableComponent? {
return supplier.get() // no narration for u
}
}
class IconTextButton(x: Int, y: Int, text: String, val icon: ResourceLocation, val iconw: Int =16, val iconh: Int = 16, width: Int=150, height: Int=20, lambda: (IconTextButton) -> Unit) :
Button(x, y, width, height, Component.literal(text),press(lambda), narr()) {
var xOffset = 2
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
super.renderWidget(guiGraphics, mouseX, mouseY, partialTick)
RenderSystem.disableBlend() // i hate this
val imx = x + xOffset
val imy = y + (height/2) - (iconh/2)
guiGraphics.blit(icon, imx, imy, 0f, 0f, iconw, iconh, iconw, iconh)
RenderSystem.enableBlend()
}
override fun renderString(guiGraphics: GuiGraphics, font: Font, color: Int) {
val startx = x + iconw/2 // idk why /2, might be coincidence thing
renderScrollingString(guiGraphics, font, message, startx, y, startx+width, y+height, color)
}
}

View File

@@ -0,0 +1,27 @@
package org.neoflock.neocomputers.gui.widget
import net.minecraft.client.gui.Gui
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.NeoComputers
data class ButtonSprites(val sheet: ResourceLocation, val spriteWidth: Int, val spriteHeight: Int, val texWidth: Int, val texHeight: Int)
// minecraft sux
class ImagerButton(x: Int, y: Int, width: Int, height: Int, val sprites: ButtonSprites, onPress: Button.OnPress) : Button(x, y, width, height, Component.literal(""), onPress, DEFAULT_NARRATION) {
// public void renderWidget(GuiGraphics guiGraphics, int i, int j, float f) {
// ResourceLocation resourceLocation = this.sprites.get(this.isActive(), this.isHoveredOrFocused());
// guiGraphics.blitSprite(resourceLocation, this.getX(), this.getY(), this.width, this.height);
// }
var pressed = false
override fun renderWidget(graphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val u = if (pressed) 18F else 0F // no clue why it's swapped? prob cooked the coordinates, we gotta get parchment so bad
val v = if (this.isHoveredOrFocused) 18F else 0F
graphics.blit(sprites.sheet, x, y, width, height, u, v, sprites.spriteWidth, sprites.spriteHeight, sprites.texWidth, sprites.texHeight)
}
}

View File

@@ -0,0 +1,20 @@
package org.neoflock.neocomputers.item
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
open class CBUSItem(val tier: Int, val maxComponents: Int): Item(Item.Properties()), ComponentItem {
override fun getComponentRoles(itemStack: ItemStack) = setOf(ComponentRoles.BUS)
override fun getComponentTier(itemStack: ItemStack) = tier
override fun getComponentCapacity(itemStack: ItemStack) = maxComponents
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
}
class CBUS0: CBUSItem(1, 8)
class CBUS1: CBUSItem(2, 12)
class CBUS2: CBUSItem(3, 16)
class CBUSCreative: CBUSItem(1, 1024)

View File

@@ -0,0 +1,24 @@
package org.neoflock.neocomputers.item
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
open class CPUItem(val tier: Int, val maxComponents: Int): Item(Item.Properties()), RelayUpgrade {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.COMPUTE)
override fun getComponentTier(itemStack: ItemStack): Int = tier
override fun getComponentCapacity(itemStack: ItemStack): Int = maxComponents
override fun getArchitecturesProvided(itemStack: ItemStack): Set<String> = setOf("Lua 5.3")
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun getRelayInterval(itemStack: ItemStack) = 4 / tier
}
class CPU0: CPUItem(1, 8)
class CPU1: CPUItem(2, 12)
class CPU2: CPUItem(3, 16)

View File

@@ -0,0 +1,63 @@
package org.neoflock.neocomputers.item
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.entity.MachineEntity
import org.neoflock.neocomputers.entity.MachineEvent
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import java.util.UUID
// need not necessarily be just a component, can be upgrades as well
interface ComponentItem {
fun getComponentRoles(itemStack: ItemStack): Set<String>
fun getComponentTier(itemStack: ItemStack): Int
// Get machine properties they can influence
fun getMemoryCapacity(itemStack: ItemStack): Int = 0
fun getComponentCapacity(itemStack: ItemStack): Int = 0
fun getArchitecturesProvided(itemStack: ItemStack): Set<String> = setOf()
// Component placed, node must now exist
fun whenComponentPlaced(itemStack: ItemStack, machine: ComponentUser?, newRole: String) {
val oldNode = getComponentNode(itemStack)
if(oldNode != null) Networking.removeNode(oldNode) // did a mod forget to call whenComponentTaken?
val node = toComponentNode(itemStack, machine) ?: return
Networking.addNode(node)
machine?.getMachineNode()?.connectTo(node)
}
// Component taken, and thus removed
fun whenComponentTaken(itemStack: ItemStack, machine: ComponentUser?, previousRole: String) {
val node = getComponentNode(itemStack) ?: return
// removing disconnects
Networking.removeNode(node)
}
// To node, if applicable. Meant to create the node, but not add it, as it will use the itemStack's address to find it again
fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?): DeviceNode?
// Gets the node associated to an item, if it exists
fun getComponentNode(itemStack: ItemStack): DeviceNode? {
val address = itemStack.get(DataComponents.ADDRESS) ?: return null
val uuid = UUID.fromString(address) ?: return null
return Networking.getNode(uuid)
}
fun ensureHasAddress(itemStack: ItemStack): UUID {
if(!itemStack.has(DataComponents.ADDRESS)) {
itemStack.set(DataComponents.ADDRESS, UUID.randomUUID().toString())
}
return UUID.fromString(itemStack.get(DataComponents.ADDRESS))
}
fun onMachineEvent(itemStack: ItemStack, machine: MachineEntity, event: MachineEvent) {}
}
// A special ComponentItem which specifies upgrades specific to the relay
interface RelayUpgrade: ComponentItem {
fun getRelayInterval(itemStack: ItemStack): Int? = null
fun getRelayBufferSize(itemStack: ItemStack): Int? = null
fun getRelayQueueSize(itemStack: ItemStack): Int? = null
fun isRelayCompatibleCard(itemStack: ItemStack): Boolean = false
}

View File

@@ -0,0 +1,45 @@
package org.neoflock.neocomputers.item
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.utils.Formatting
// Note: We'll prob want to replace them with NN component configs later on
open class DataCard(val tier: Int, val limit: Long): Item(Properties()), ComponentItem {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.CARD)
override fun getComponentTier(itemStack: ItemStack): Int = tier
override fun whenComponentPlaced(itemStack: ItemStack, machine: ComponentUser?, newRole: String) {
if(machine != null) ensureHasAddress(itemStack)
super.whenComponentPlaced(itemStack, machine, newRole)
}
// TODO: Modem Component
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun appendHoverText(
itemStack: ItemStack,
tooltipContext: TooltipContext,
list: MutableList<Component?>,
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
val addr = itemStack.get(DataComponents.ADDRESS)
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
list.addLast(addrComp)
list.addLast(Component.translatable("neocomputers.data.limit", Formatting.formatMemory(limit)))
// TODO: show encryption support
}
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)
}
}
class DataCard0: DataCard(1, 1 shl 20)
class DataCard1: DataCard(2, 1 shl 20)
class DataCard2: DataCard(3, 1 shl 20)

View File

@@ -0,0 +1,32 @@
package org.neoflock.neocomputers.item
import com.mojang.serialization.Codec
import net.minecraft.core.Registry
import net.minecraft.core.component.DataComponentType
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.NeoComputers
import java.nio.ByteBuffer
object DataComponents {
val ADDRESS = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "address"),
DataComponentType.builder<String>().persistent(Codec.STRING).build())
val LABEL = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "label"),
DataComponentType.builder<String>().persistent(Codec.STRING).build())
val READONLY = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "readonly"),
DataComponentType.builder<Boolean>().persistent(Codec.BOOL).build())
val ARCH = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "architecture"),
DataComponentType.builder<String>().persistent(Codec.STRING).build())
val EEPROM_CODE = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_code"),
DataComponentType.builder<ByteBuffer>().persistent(Codec.BYTE_BUFFER).build())
val EEPROM_DATA = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_data"),
DataComponentType.builder<ByteBuffer>().persistent(Codec.BYTE_BUFFER).build())
val EEPROM_CODESIZE = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_codesize"),
DataComponentType.builder<Int>().persistent(Codec.INT).build())
val EEPROM_DATASIZE = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_datasize"),
DataComponentType.builder<Int>().persistent(Codec.INT).build())
val TUNNEL_CHANNEL = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "tunnel_channel"),
DataComponentType.builder<String>().persistent(Codec.STRING).build())
}

View File

@@ -0,0 +1,71 @@
package org.neoflock.neocomputers.item
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.utils.Formatting
import java.nio.ByteBuffer
fun getEEPROMProperties(codeCap: Int, dataCap: Int): Item.Properties = Item.Properties()
.component(DataComponents.EEPROM_CODE, ByteBuffer.allocate(codeCap))
.component(DataComponents.EEPROM_DATA, ByteBuffer.allocate(dataCap))
.component(DataComponents.EEPROM_CODESIZE, 0)
.component(DataComponents.EEPROM_DATASIZE, 0)
.component(DataComponents.LABEL, "")
.component(DataComponents.READONLY, false)
open class EEPROMItem(val tier: Int, val codeCapacity: Int, val dataCapacity: Int): Item(getEEPROMProperties(codeCapacity, dataCapacity)), ComponentItem {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.FIRMWARE)
override fun getComponentTier(itemStack: ItemStack): Int = tier
override fun getMemoryCapacity(itemStack: ItemStack): Int = 0
override fun getComponentCapacity(itemStack: ItemStack): Int = 0
override fun whenComponentPlaced(itemStack: ItemStack, machine: ComponentUser?, newRole: String) {
if(machine != null) ensureHasAddress(itemStack)
super.whenComponentPlaced(itemStack, machine, newRole)
}
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun appendHoverText(
itemStack: ItemStack,
tooltipContext: TooltipContext,
list: MutableList<Component?>,
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
val codeSize = itemStack.get(DataComponents.EEPROM_CODESIZE) ?: 0
val dataSize = itemStack.get(DataComponents.EEPROM_DATASIZE) ?: 0
val arch = itemStack.get(DataComponents.ARCH)
val addr = itemStack.get(DataComponents.ADDRESS)
val readonly = itemStack.get(DataComponents.READONLY) ?: false
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
list.addLast(addrComp)
if(arch != null) {
list.addLast(Component.translatable("neocomputers.arch", arch))
}
list.addLast(Component.translatable("neocomputers.eeprom.codeused", Formatting.formatMemory(codeSize.toLong()),
Formatting.formatMemory(codeCapacity.toLong())))
list.addLast(Component.translatable("neocomputers.eeprom.dataused", Formatting.formatMemory(dataSize.toLong()),
Formatting.formatMemory(dataCapacity.toLong())))
list.addLast(Component.translatable(if(readonly) "neocomputers.readonly" else "neocomputers.readwrite"))
}
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)
}
override fun getName(itemStack: ItemStack): Component? {
if(itemStack.has(DataComponents.LABEL)) {
val label = itemStack.get(DataComponents.LABEL) ?: ""
if(label.isNotEmpty()) return Component.literal(label)
}
return super.getName(itemStack)
}
}
class EEPROM0: EEPROMItem(1, 4096, 256)

View File

@@ -0,0 +1,44 @@
package org.neoflock.neocomputers.item
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
// Note: We'll prob want to replace them with NN component configs later on
open class GPUCard(val tier: Int, val vram: Long): Item(Properties()), ComponentItem {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.CARD)
override fun getComponentTier(itemStack: ItemStack): Int = tier
override fun whenComponentPlaced(itemStack: ItemStack, machine: ComponentUser?, newRole: String) {
if(machine != null) ensureHasAddress(itemStack)
super.whenComponentPlaced(itemStack, machine, newRole)
}
// TODO: GPU Component
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun appendHoverText(
itemStack: ItemStack,
tooltipContext: TooltipContext,
list: MutableList<Component?>,
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
val addr = itemStack.get(DataComponents.ADDRESS)
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
list.addLast(addrComp)
list.addLast(Component.translatable("neocomputers.gpu.vram", vram))
// TODO: show VRAM usage and whatnot
}
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)
}
}
class GPUCard0: GPUCard(1, 5000)
class GPUCard1: GPUCard(2, 10000)
class GPUCard2: GPUCard(3, 20000)

View File

@@ -0,0 +1,63 @@
package org.neoflock.neocomputers.item
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.utils.Formatting
fun getDiskProperties(): Item.Properties = Item.Properties()
.component(DataComponents.LABEL, "")
.component(DataComponents.READONLY, false)
open class HardDiskItem(val tier: Int, val capacity: Long, val relayQueueSize: Int): Item(getDiskProperties()), RelayUpgrade {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.STORAGE)
override fun getComponentTier(itemStack: ItemStack): Int = tier
override fun getMemoryCapacity(itemStack: ItemStack): Int = 0
override fun getComponentCapacity(itemStack: ItemStack): Int = 0
override fun whenComponentPlaced(itemStack: ItemStack, machine: ComponentUser?, newRole: String) {
if(machine != null) ensureHasAddress(itemStack)
super.whenComponentPlaced(itemStack, machine, newRole)
}
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun appendHoverText(
itemStack: ItemStack,
tooltipContext: TooltipContext,
list: MutableList<Component?>,
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
val addr = itemStack.get(DataComponents.ADDRESS)
val readonly = itemStack.get(DataComponents.READONLY) ?: false
val spaceUsed: Long = 0
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
list.addLast(addrComp)
list.addLast(Component.translatable("neocomputers.disk.spaceused", Formatting.formatMemory(spaceUsed),
Formatting.formatMemory(capacity)))
list.addLast(Component.translatable(if(readonly) "neocomputers.readonly" else "neocomputers.readwrite"))
}
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)
}
override fun getName(itemStack: ItemStack): Component? {
if(itemStack.has(DataComponents.LABEL)) {
val label = itemStack.get(DataComponents.LABEL) ?: ""
if(label.isNotEmpty()) return Component.literal(label)
}
return super.getName(itemStack)
}
override fun getRelayQueueSize(itemStack: ItemStack) = relayQueueSize
}
class HardDisk0: HardDiskItem(1, 1 shl 20, 30)
class HardDisk1: HardDiskItem(2, 2 shl 20, 40)
class HardDisk2: HardDiskItem(3, 4 shl 20, 50)

View File

@@ -0,0 +1,37 @@
package org.neoflock.neocomputers.item
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
class InternetCard: Item(Item.Properties()), ComponentItem {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.CARD, ComponentRoles.INET)
override fun getComponentTier(itemStack: ItemStack): Int = 2
override fun whenComponentPlaced(itemStack: ItemStack, machine: ComponentUser?, newRole: String) {
if(machine != null) ensureHasAddress(itemStack)
super.whenComponentPlaced(itemStack, machine, newRole)
}
// TODO: Internet Component
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun appendHoverText(
itemStack: ItemStack,
tooltipContext: TooltipContext,
list: MutableList<Component?>,
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
val addr = itemStack.get(DataComponents.ADDRESS)
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
list.addLast(addrComp)
// TODO: show HTTP/TCP/TLS support
}
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)
}
}

View File

@@ -0,0 +1,51 @@
package org.neoflock.neocomputers.item
import dev.architectury.registry.registries.DeferredRegister
import net.minecraft.core.registries.Registries
import net.minecraft.world.item.Item
import org.neoflock.neocomputers.NeoComputers
object Items {
val ITEMS: DeferredRegister<Item> = DeferredRegister.create(NeoComputers.MODID, Registries.ITEM)
val MEM0 = ITEMS.register("memory0") { MemoryTier1() }
val MEM1 = ITEMS.register("memory1") { MemoryTier1_5() }
val MEM2 = ITEMS.register("memory2") { MemoryTier2() }
val MEM3 = ITEMS.register("memory3") { MemoryTier2_5() }
val MEM4 = ITEMS.register("memory4") { MemoryTier3() }
val MEM5 = ITEMS.register("memory5") { MemoryTier3_5() }
val EE0 = ITEMS.register("eeprom0") { EEPROM0() }
val CPU0 = ITEMS.register("cpu0") { CPU0() }
val CPU1 = ITEMS.register("cpu1") { CPU1() }
val CPU2 = ITEMS.register("cpu2") { CPU2() }
val CBUS0 = ITEMS.register("cbus0") { CBUS0() }
val CBUS1 = ITEMS.register("cbus1") { CBUS1() }
val CBUS2 = ITEMS.register("cbus2") { CBUS2() }
val CBUS_CREATIVE = ITEMS.register("cbus_creative") { CBUSCreative() }
val INET = ITEMS.register("inet") { InternetCard() }
val TUNNEL = ITEMS.register("tunnel") { TunnelCard() }
val LAN = ITEMS.register("lan") { LANCard() }
val WLAN0 = ITEMS.register("wlan0") { WLANCard0() }
val WLAN1 = ITEMS.register("wlan1") { WLANCard1() }
val DATA0 = ITEMS.register("data0") { DataCard0() }
val DATA1 = ITEMS.register("data1") { DataCard1() }
val DATA2 = ITEMS.register("data2") { DataCard2() }
val GPU0 = ITEMS.register("gpu0") { GPUCard0() }
val GPU1 = ITEMS.register("gpu1") { GPUCard1() }
val GPU2 = ITEMS.register("gpu2") { GPUCard2() }
val HDD0 = ITEMS.register("hdd0") { HardDisk0() }
val HDD1 = ITEMS.register("hdd1") { HardDisk1() }
val HDD2 = ITEMS.register("hdd2") { HardDisk2() }
val REDIO0 = ITEMS.register("redio0") { RedstoneCard0() }
val REDIO1 = ITEMS.register("redio1") { RedstoneCard1() }
val SERVER0 = ITEMS.register("server0") { ServerItem() }
}

View File

@@ -0,0 +1,43 @@
package org.neoflock.neocomputers.item
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.utils.Formatting
open class MemoryItem(val tier: Int, val capacity: Int, val relayBuf: Int): Item(Item.Properties().`arch$tab`(Tabs.TAB)), RelayUpgrade {
override fun getComponentRoles(itemStack: ItemStack) = setOf(ComponentRoles.MEMORY)
override fun getComponentTier(itemStack: ItemStack): Int = tier
override fun getMemoryCapacity(itemStack: ItemStack): Int = capacity
override fun getComponentCapacity(itemStack: ItemStack): Int = 0
// no node for memory
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun appendHoverText(
itemStack: ItemStack,
tooltipContext: TooltipContext,
list: MutableList<Component?>,
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
list.addLast(Component.translatable("neocomputers.memory.capacity", Formatting.formatMemory(capacity.toLong())))
}
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)
}
override fun getRelayBufferSize(itemStack: ItemStack) = relayBuf
}
class MemoryTier1(): MemoryItem(1, 192 shl 10, 2)
class MemoryTier1_5(): MemoryItem(1, 256 shl 10, 3)
class MemoryTier2(): MemoryItem(2, 384 shl 10, 4)
class MemoryTier2_5(): MemoryItem(2, 512 shl 10, 5)
class MemoryTier3(): MemoryItem(3, 768 shl 10, 6)
class MemoryTier3_5(): MemoryItem(3, 1 shl 20, 7)

View File

@@ -0,0 +1,44 @@
package org.neoflock.neocomputers.item
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
open class NetworkCard(val tier: Int, val maxRange: Int, val isWired: Boolean): Item(Properties()), ComponentItem {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.CARD, ComponentRoles.NETWORK)
override fun getComponentTier(itemStack: ItemStack): Int = tier
override fun whenComponentPlaced(itemStack: ItemStack, machine: ComponentUser?, newRole: String) {
if(machine != null) ensureHasAddress(itemStack)
super.whenComponentPlaced(itemStack, machine, newRole)
}
// TODO: Modem Component
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun appendHoverText(
itemStack: ItemStack,
tooltipContext: TooltipContext,
list: MutableList<Component?>,
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
val addr = itemStack.get(DataComponents.ADDRESS)
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
list.addLast(addrComp)
if(maxRange > 0) {
list.addLast(Component.translatable("neocomputers.wlan.range", maxRange))
}
// TODO: show max packet size and whatnot
}
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)
}
}
class LANCard: NetworkCard(1, 0, true)
class WLANCard0: NetworkCard(1, 16, true)
class WLANCard1: NetworkCard(1, 400, true)

View File

@@ -0,0 +1,62 @@
package org.neoflock.neocomputers.item
import com.mojang.blaze3d.shaders.Shader
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.NeoComputers
interface RackItem {
// companion object {
// val RENDER_TYPE = {l: ResourceLocation ->
// RenderType.create("nc_server", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, RenderType.SMALL_BUFFER_SIZE, RenderType.CompositeState.builder()
// .setShaderState(RenderStateShard.ShaderStateShard.POSITION_COLOR_TEX_LIGHTMAP_SHADER)
// .setLightmapState(RenderStateShard.LIGHTMAP)
// .setTextureState(RenderStateShard.TextureStateShard(l, false, false))
// .createCompositeState(false))
// }
// }
val TOP_TEX: ResourceLocation
get() = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/block/generic_top.png")
val FRONT_TEX: ResourceLocation
get() = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/block/rack_server.png")
fun render_lights(source: MultiBufferSource, stack: PoseStack, light: Int)
fun render(source: MultiBufferSource, stack: PoseStack, light: Int, v_offset: Float = 2f) {
val pose = stack.last()
// var buffer = source.getBuffer(RenderType.gui()) // TODO: correct rendertype
var buffer = source.getBuffer(RenderType.entitySolid(TOP_TEX))
// val u1 = 1/16f
buffer.addVertex(pose, 0f, 0f, 0f).setUv(1/16f, 15/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 0f, -1f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 14/16f, 0f, 0f).setUv(1/16f, 1/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 0f, -1f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 14/16f, 0f, 14/16f).setUv(15/16f, 1/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 0f, -1f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 0f, 0f, 14/16f).setUv(15/16f, 1/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 0f, -1f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 0f, 3/16f, 14/16f).setUv(15/16f, 1/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 0f, 1f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 14/16f, 3/16f, 14/16f).setUv(15/16f, 15/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 0f, 1f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 14/16f, 3/16f, 0f).setUv(1/16f, 15/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 0f, 1f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 0f, 3/16f, 0f).setUv(1/16f, 1/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 0f, 1f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer = source.getBuffer(RenderType.entitySolid(FRONT_TEX))
buffer.addVertex(pose, 14/16f, 3/16f, 14/16f).setUv(1/16f, v_offset/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 1f, 0f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 14/16f, 0f, 14/16f).setUv(1/16f, (v_offset+3)/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 1f, 0f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 14/16f, 0/16f, 0/16f).setUv(15/16f, (v_offset+3)/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 1f, 0f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
buffer.addVertex(pose, 14/16f, 3/16f, 0/16f).setUv(15/16f, v_offset/16f).setColor(1f, 1f, 1f, 1f).setLight(light).setNormal(pose, 1f, 0f, 0f).setOverlay(OverlayTexture.NO_OVERLAY)
stack.pushPose()
stack.translate((14/16f)+0.001F, 0f, 0f)
render_lights(source, stack, light)
stack.popPose()
}
}

View File

@@ -0,0 +1,41 @@
package org.neoflock.neocomputers.item
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
// Note: We'll prob want to replace them with NN component configs later on
open class RedstoneCard(val tier: Int): Item(Properties()), ComponentItem {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.CARD)
override fun getComponentTier(itemStack: ItemStack): Int = tier
override fun whenComponentPlaced(itemStack: ItemStack, machine: ComponentUser?, newRole: String) {
if(machine != null) ensureHasAddress(itemStack)
super.whenComponentPlaced(itemStack, machine, newRole)
}
// TODO: Redstone Component
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun appendHoverText(
itemStack: ItemStack,
tooltipContext: TooltipContext,
list: MutableList<Component?>,
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
val addr = itemStack.get(DataComponents.ADDRESS)
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
// TODO: show redstone and whatnot
}
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)
}
}
class RedstoneCard0: RedstoneCard(1)
class RedstoneCard1: RedstoneCard(2)

View File

@@ -0,0 +1,26 @@
package org.neoflock.neocomputers.item
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.item.ItemProperties
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.network.DeviceNode
class ServerItem() : Item(Properties()), ComponentItem, RackItem {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.RACK_MOUNTABLE)
override fun getComponentTier(itemStack: ItemStack): Int = 0
override fun toComponentNode(
itemStack: ItemStack,
machine: ComponentUser?
): DeviceNode? {
return null // TODO: atom machine item plz
}
override fun render_lights(source: MultiBufferSource, stack: PoseStack, light: Int) {
}
}

View File

@@ -0,0 +1,83 @@
package org.neoflock.neocomputers.item
import dev.architectury.registry.CreativeTabRegistry
import dev.architectury.registry.registries.DeferredRegister
import net.minecraft.client.Minecraft
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.CreativeModeTab
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.NeoComputers
import java.nio.ByteBuffer
object Tabs {
val TABS: DeferredRegister<CreativeModeTab> =
DeferredRegister.create(NeoComputers.MODID, Registries.CREATIVE_MODE_TAB)
val TAB = TABS.register("neocomputers_tab") {
// its only experimental once they change it
CreativeTabRegistry.create { builder ->
builder.title(Component.literal("NeoComputers"))
builder.icon {
ItemStack(Items.MEM0.get())
}
builder.displayItems { parameters, output ->
// TODO: get rid of arch$tab and this shi and replace with loop over items registry
output.accept(ItemStack(Items.CPU0.get()))
output.accept(ItemStack(Items.CPU1.get()))
output.accept(ItemStack(Items.CPU2.get()))
output.accept(ItemStack(Items.CBUS0.get()))
output.accept(ItemStack(Items.CBUS1.get()))
output.accept(ItemStack(Items.CBUS2.get()))
output.accept(ItemStack(Items.CBUS_CREATIVE.get()))
output.accept(ItemStack(Items.DATA0.get()))
output.accept(ItemStack(Items.DATA1.get()))
output.accept(ItemStack(Items.DATA2.get()))
output.accept(ItemStack(Items.GPU0.get()))
output.accept(ItemStack(Items.GPU1.get()))
output.accept(ItemStack(Items.GPU2.get()))
output.accept(ItemStack(Items.HDD0.get()))
output.accept(ItemStack(Items.HDD1.get()))
output.accept(ItemStack(Items.HDD2.get()))
output.accept(ItemStack(Items.INET.get()))
output.accept(ItemStack(Items.TUNNEL.get()))
output.accept(ItemStack(Items.LAN.get()))
output.accept(ItemStack(Items.WLAN0.get()))
output.accept(ItemStack(Items.WLAN1.get()))
output.accept(ItemStack(Items.REDIO0.get()))
output.accept(ItemStack(Items.REDIO1.get()))
output.accept(ItemStack(Items.EE0.get()))
output.accept(ItemStack(Items.SERVER0.get()))
// Criminal black magic to put LuaBIOS EEPROM in the tabs
do {
val luaBios = ItemStack(Items.EE0.get())
val res = Minecraft.getInstance().resourceManager.getResourceOrThrow(
ResourceLocation.fromNamespaceAndPath(
NeoComputers.MODID,
"lua/oc_bios.lua"
)
)
val stream = res.openAsReader()
val code = stream.readText().encodeToByteArray()
stream.close()
val codeBuf = ByteBuffer.allocate(code.size)
codeBuf.put(code)
luaBios.set(DataComponents.LABEL, "Lua BIOS")
luaBios.set(DataComponents.ARCH, "Lua 5.2")
luaBios.set(DataComponents.EEPROM_CODE, codeBuf)
luaBios.set(DataComponents.EEPROM_CODESIZE, code.size)
output.accept(luaBios)
} while(false)
}
}
}
}

View File

@@ -0,0 +1,39 @@
package org.neoflock.neocomputers.item
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.ComponentUser
import org.neoflock.neocomputers.gui.widget.ComponentRoles
class TunnelCard: Item(Properties().component(DataComponents.TUNNEL_CHANNEL, "creative")), ComponentItem {
// yes, we're counting TUNNEL as a conventional networking card
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.CARD, ComponentRoles.NETWORK)
override fun getComponentTier(itemStack: ItemStack): Int = 3
override fun whenComponentPlaced(itemStack: ItemStack, machine: ComponentUser?, newRole: String) {
if(machine != null) ensureHasAddress(itemStack)
super.whenComponentPlaced(itemStack, machine, newRole)
}
// TODO: Tunnel Component
override fun toComponentNode(itemStack: ItemStack, machine: ComponentUser?) = null
override fun appendHoverText(
itemStack: ItemStack,
tooltipContext: TooltipContext,
list: MutableList<Component?>,
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
val addr = itemStack.get(DataComponents.ADDRESS)
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
list.addLast(addrComp)
list.addLast(Component.translatable("neocomputers.tunnel.channel", itemStack.get(DataComponents.TUNNEL_CHANNEL) ?: "creative"))
// TODO: show max packet size and whatnot
}
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)
}
}

View File

@@ -0,0 +1,256 @@
package org.neoflock.neocomputers.network
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.Level
import net.minecraft.world.phys.Vec3
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.network.Networking.Message
import org.neoflock.neocomputers.network.Networking.Visibility
import org.neoflock.neocomputers.network.Networking.maxHopCount
import java.util.UUID
import kotlin.math.min
// tmp class until JNI bindings work
data class NNComponent(val type: String)
open class DeviceNode(_address: UUID? = null) {
val connections = HashSet<DeviceNode>()
private var reachableCache: Set<DeviceNode>? = null
var address = _address ?: UUID.randomUUID()
open var reachability = Visibility.NETWORK
open var powerRole = PowerRole.CONSUMER
open var energy: Long = 0
open var energyCapacity: Long = 0
// give energy, returns how much was actually given
// cannot exceed amount specified
open fun giveEnergy(amount: Long): Long {
val maximum = min(amount, energyCapacity - energy)
energy += maximum
markChanged()
return maximum
}
// take energy out, returns how much was actually taken
// cannot exceed amount specified
open fun withdrawEnergy(amount: Long): Long {
val maximum = min(amount, energy)
energy -= maximum
markChanged()
return maximum
}
fun getChargerNodes(): Set<DeviceNode> = getReachable().filter { it.powerRole != PowerRole.CONSUMER }.toSet()
fun totalEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energy }
fun maxEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energyCapacity }
// attempts to consume
fun consumeEnergy(energy: Long): Boolean {
// consumes energy, returns false if not enough
val total = totalEnergyInConnections() + this.energy
if(energy > total) return false
var remaining = energy
remaining -= withdrawEnergy(remaining)
if(remaining <= 0) return true
for (charger in getChargerNodes()) {
if(remaining <= 0) break
remaining -= charger.withdrawEnergy(remaining)
}
return true
}
// PLEASE only call if consumer, in the name of all that is holy
fun tryToChargeFully() {
var remaining = energyCapacity - energy
if(remaining <= 0) return
for (charger in getChargerNodes()) {
if(remaining <= 0) break
val amount = charger.withdrawEnergy(remaining)
val given = giveEnergy(amount)
remaining -= given
if(given < amount) {
val delta = amount - given // amount lost while given back
if(charger.giveEnergy(delta) < delta) {
NeoComputers.LOGGER.warn("LOSING ENERGY! Tried giving $delta back to $charger and we're losing our marbles!")
}
}
}
}
// only call if storage
fun balanceStorage() {
for(battery in getReachable()) {
if(battery.powerRole != PowerRole.STORAGE) continue
// its so if for example we have a battery with 2x the capacity
// we don't try to even the energy between them since that's just bad
// and might pointless delete energy over time
val capacityRatio = energyCapacity.toDouble() / battery.energyCapacity
val meaningfulSurplus = (battery.energy * capacityRatio - energy).toLong()
if(meaningfulSurplus <= 0) {
// WE'RE greedy (or negligible surplus)? Do nothing
continue
}
// steal from this greedy mf
val toSteal = meaningfulSurplus / 2
if(toSteal == 0L) continue // broke storahh
val stolen = battery.withdrawEnergy(toSteal)
if(giveEnergy(stolen) < stolen) {
NeoComputers.LOGGER.warn("LOSING ENERGY IN NODE $address!!!! THIS IS REALLY BAD!!!")
}
}
}
// rob the generators
fun stealGeneratorPower() {
var remaining = energyCapacity - energy
for(generator in getReachable()) {
if(generator.powerRole != PowerRole.GENERATOR) continue
// rob this mf
val robbed = generator.withdrawEnergy(remaining)
val taken = giveEnergy(robbed)
if(taken < robbed) {
NeoComputers.LOGGER.warn("energy caught being DELETED in the big 26")
}
remaining -= taken
}
}
open fun tick() {
if(powerRole == PowerRole.CONSUMER) tryToChargeFully()
if(powerRole == PowerRole.STORAGE) {
stealGeneratorPower()
balanceStorage()
}
}
// processes a received message
open fun received(message: Message) {}
// called when a new direct connection is made
open fun onConnect(deviceNode: DeviceNode) {}
// called when a direct connection is lost
open fun onDisconnect(deviceNode: DeviceNode) {}
// called when a new node is added globally
open fun onNodeAdded(deviceNode: DeviceNode) {
invalidateReachableCache()
}
// called when a node is removed globally
open fun onNodeRemoved(deviceNode: DeviceNode) {
invalidateReachableCache()
}
fun getReachable(): Set<DeviceNode> {
if(reachableCache == null) {
reachableCache = computeReachable()
}
return reachableCache!!
}
open fun invalidateReachableCache() {
reachableCache = null
}
// Returns a subset of connections, for a subset of direct
// meant for things like drives which dont want to accidentally fuse networks
open fun getPreferredFew() = setOf<DeviceNode>()
fun computeReachable(): Set<DeviceNode> {
if(reachability == Visibility.NONE) {
return setOf()
}
if(reachability == Visibility.SOME) {
return getPreferredFew()
}
if(reachability == Visibility.NETWORK) {
// absolute cinema
val working = HashSet<DeviceNode>()
val pending = mutableListOf(this)
var iterCount = 0
while(iterCount < maxHopCount && pending.isNotEmpty()) {
iterCount++
val subnode = pending.removeFirst()
if(subnode in working) continue
working.add(subnode)
if(subnode.reachability == Visibility.NETWORK) {
pending.addAll(subnode.connections)
} else if(subnode.reachability == Visibility.SOME) {
pending.addAll(subnode.getPreferredFew())
}
}
// cannot send to itself!
working.remove(this)
return working
}
throw NotImplementedError("visibility not implemented")
}
fun connectTo(other: DeviceNode) {
this.directConnectTo(other)
other.directConnectTo(this)
}
fun disconnectFrom(other: DeviceNode) {
this.directDisconnectFrom(other)
other.directDisconnectFrom(this)
}
fun directConnectTo(other: DeviceNode) {
if(other == this) return
if(other in connections) return
connections.add(other)
this.onConnect(other)
invalidateReachableCache()
}
fun directDisconnectFrom(other: DeviceNode) {
if(other !in connections) return
connections.remove(other)
this.onDisconnect(other)
invalidateReachableCache()
}
// Network synchronization with the NodeSynchronizer
// TODO: process shi
var outOfSync = false
fun markChanged() {
outOfSync = true
}
open fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) {}
open fun processScreenInteraction(player: ServerPlayer, buf: FriendlyByteBuf) {}
// Meant to write the entire state as a single commit
// for clients which say they have no fucking idea what the server is storing
open fun writeFullStateCommit(buf: FriendlyByteBuf) {}
// client-side, meant to bring state forward
open fun processCommit(buf: FriendlyByteBuf) {}
open fun getComponent(): NNComponent? = null
}
// Used by the relay
// If the ComponentItem in the card slot
interface ConventionalNetworkDevice {
fun sendClassicPacket(packet: Networking.ClassicPacket)
}
abstract class WirelessEndpoint(address: UUID?) : DeviceNode(address) {
abstract fun getEndpointRange(): Double
abstract fun getEndpointDimension(): Int
abstract fun getEndpointLevel(): Level
abstract fun getEndpointPosition(): Vec3
// separate from process for simplicity
abstract fun receiveWireless(message: Message, emitter: WirelessEndpoint)
}

View File

@@ -0,0 +1,161 @@
package org.neoflock.neocomputers.network
import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import org.neoflock.neocomputers.entity.MachineEvent
import java.util.UUID
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
enum class PowerRole {
// consumes energy, wants to be fully charged
// does not give energy to network nodes
CONSUMER,
// stores energy, will not care to charge itself
// will happily give energy to network nodes
STORAGE,
// only produces energy, thus obviously charges itself
// also happily gives energy
GENERATOR,
}
object Networking {
// maximum amount of hops between nodes
var maxHopCount = 32
var tickCount = 0
enum class Visibility {
// only it can see itself
NONE,
// some, as determined by getPreferredFew()
SOME,
// Can see everything network-wide
NETWORK,
}
abstract class Message(val sender: DeviceNode)
// null dst means broadcast
class ClassicPacket(sender: DeviceNode, val src: String, val dst: String?, val port: Int, val data: List<Any>, val hopCount: Int) : Message(sender) {
fun hop(sender: DeviceNode) = ClassicPacket(sender, src, dst, port, data, hopCount + 1);
}
// for plugins and shi
class ComputerCheckedSignal(sender: DeviceNode, val player: String?, val name: String, val data: Array<Any>): Message(sender)
class ComputerUncheckedSignal(sender: DeviceNode, val name: String, val data: Array<Any>): Message(sender)
class ComputerEvent(sender: DeviceNode, val machineEvent: MachineEvent): Message(sender)
val wirelessNodes = ThreadLocal.withInitial { HashSet<WirelessEndpoint>() }
val allNodes = ThreadLocal.withInitial { HashMap<UUID, DeviceNode>() }
// node may differ from message.sender in the case of relays,
// as they might have DIRECT reachability but
fun emitMessage(deviceNode: DeviceNode, message: Message, exclude: Set<DeviceNode> = setOf()) {
deviceNode.getReachable().forEach { if(it !in exclude) it.received(message) }
}
fun computeRangeAllowedByHardness(level: Level, src: BlockPos, dst: BlockPos): Double {
return Double.POSITIVE_INFINITY // TODO: math
}
fun emitWirelessMessage(starter: WirelessEndpoint, range: Double, message: Message) {
val startPos = starter.getEndpointPosition()
val startDim = starter.getEndpointDimension()
val level = starter.getEndpointLevel()
wirelessNodes.get().forEach {
if(it.getEndpointDimension() != startDim) return
val pos = it.getEndpointPosition()
val d = startPos.distanceTo(pos)
var trueRange = min(it.getEndpointRange(), range)
trueRange = min(trueRange, computeRangeAllowedByHardness(level, BlockPos.containing(startPos), BlockPos.containing(pos)))
if(d > trueRange) return
it.receiveWireless(message, starter)
}
}
fun tickAllNodes() {
allNodes.get().forEach { it.value.tick() }
tickCount++
}
fun getNode(address: UUID): DeviceNode? = allNodes.get()[address]
// TODO: use setter, more convenient
fun changeNodeAddress(deviceNode: DeviceNode, address: UUID): Boolean {
if(deviceNode.address.equals(address)) return false
if(deviceNode.address !in allNodes.get()) return false
if(address in allNodes.get()) return false
allNodes.get().remove(deviceNode.address)
deviceNode.address = address
allNodes.get()[address] = deviceNode
return true
}
fun addNode(deviceNode: DeviceNode) {
if(deviceNode.address in allNodes.get()) return
allNodes.get()[deviceNode.address] = deviceNode
if(deviceNode is WirelessEndpoint) {
wirelessNodes.get().add(deviceNode);
}
// notify at the end so it is notified of its own creation
allNodes.get().forEach { it.value.onNodeAdded(deviceNode) }
}
fun addNodes(deviceNodes: Iterable<DeviceNode>) {
deviceNodes.forEach { addNode(it) }
}
fun addNodes(vararg deviceNodes: DeviceNode) {
addNodes(deviceNodes.asIterable())
}
fun removeNode(deviceNode: DeviceNode) {
if(deviceNode.address !in allNodes.get()) return
NodeSynchronizer.nodeErased(deviceNode)
allNodes.get().forEach { it.value.onNodeRemoved(deviceNode) }
// toList() in order to copy it
deviceNode.connections.toList().forEach {
deviceNode.disconnectFrom(it)
}
// actually remove at the end so it can listen to its own removal
allNodes.get().remove(deviceNode.address)
if(deviceNode is WirelessEndpoint) {
wirelessNodes.get().remove(deviceNode);
}
}
fun removeNodes(deviceNodes: Iterable<DeviceNode>) {
deviceNodes.forEach { removeNode(it) }
}
fun removeNodes(vararg deviceNodes: DeviceNode) {
removeNodes(deviceNodes.asIterable())
}
val channels = ThreadLocal.withInitial { HashMap<String, MutableSet<DeviceNode>>() }
fun addToChannel(channel: String, deviceNode: DeviceNode) {
val localChannels = channels.get()
if(!localChannels.containsKey(channel)) {
localChannels[channel] = mutableSetOf();
}
localChannels[channel]!!.add(deviceNode);
}
fun removeFromChannel(channel: String, deviceNode: DeviceNode) {
val localChannels = channels.get()
if(!localChannels.containsKey(channel)) return;
localChannels[channel]?.remove(deviceNode);
if(localChannels[channel].isNullOrEmpty()) {
localChannels.remove(channel);
}
}
fun emitChannelMessage(starter: DeviceNode, channel: String, message: Message) {
val localChannels = channels.get()
val c = localChannels[channel] ?: return;
c.forEach { if(it != starter) it.received(message); };
}
}

View File

@@ -0,0 +1,171 @@
package org.neoflock.neocomputers.network
import dev.architectury.networking.NetworkManager
import io.netty.buffer.Unpooled
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import net.minecraft.network.protocol.common.custom.CustomPacketPayload
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.Level
import org.neoflock.neocomputers.NeoComputers
import java.time.Duration
object NodeSynchronizer {
val MAX_STATE_DISTANCE_ALLOWED = 128
class DeviceBlockStatePayload(var blockPos: BlockPos, var buffers: List<FriendlyByteBuf>): CustomPacketPayload {
companion object {
val BLOCKDEV_SYNC_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "blockdev_sync")
val TYPE = CustomPacketPayload.Type<DeviceBlockStatePayload>(BLOCKDEV_SYNC_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, DeviceBlockStatePayload> {
override fun decode(buf: RegistryFriendlyByteBuf): DeviceBlockStatePayload {
val blockPos = buf.readBlockPos()
val bufferCount = buf.readVarInt()
val buffers = List(bufferCount) {
val bytes = buf.readByteArray()
val rawBuf = Unpooled.buffer(bytes.size)
rawBuf.writeBytes(bytes)
FriendlyByteBuf(rawBuf)
}
return DeviceBlockStatePayload(blockPos, buffers)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: DeviceBlockStatePayload) {
buf.writeBlockPos(payload.blockPos)
buf.writeVarInt(payload.buffers.size)
payload.buffers.forEach {
buf.writeByteArray(it.array())
}
}
}
}
override fun type() = TYPE
}
class DeviceBlockStateRequest(var blockPos: BlockPos): CustomPacketPayload {
companion object {
val BLOCKDEV_REQ_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "blockdev_statereq")
val TYPE = CustomPacketPayload.Type<DeviceBlockStateRequest>(BLOCKDEV_REQ_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, DeviceBlockStateRequest> {
override fun decode(buf: RegistryFriendlyByteBuf): DeviceBlockStateRequest {
val blockPos = buf.readBlockPos()
return DeviceBlockStateRequest(blockPos)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: DeviceBlockStateRequest) {
buf.writeBlockPos(payload.blockPos)
}
}
}
override fun type() = TYPE
}
class ScreenPayload(var buffer: FriendlyByteBuf): CustomPacketPayload {
companion object {
val SCREEN_SYNC_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen_sync")
val TYPE = CustomPacketPayload.Type<ScreenPayload>(SCREEN_SYNC_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, ScreenPayload> {
override fun decode(buf: RegistryFriendlyByteBuf): ScreenPayload {
val buffer = FriendlyByteBuf(buf.copy(buf.readerIndex(), buf.readableBytes()))
return ScreenPayload(buffer)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: ScreenPayload) {
buf.writeBytes(payload.buffer)
}
}
}
override fun type() = TYPE
}
class ScreenDataPayload(var buffer: FriendlyByteBuf): CustomPacketPayload {
companion object {
val SCREEN_DATA_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen_data")
val TYPE = CustomPacketPayload.Type<ScreenDataPayload>(SCREEN_DATA_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, ScreenDataPayload> {
override fun decode(buf: RegistryFriendlyByteBuf): ScreenDataPayload {
val buffer = FriendlyByteBuf(buf.copy(buf.readerIndex(), buf.readableBytes()))
return ScreenDataPayload(buffer)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: ScreenDataPayload) {
buf.writeBytes(payload.buffer)
}
}
}
override fun type() = TYPE
}
class BeepDataPayload(val pos: BlockPos, val pattern: String, val freq: Int, val duration: Duration, val volume: Double): CustomPacketPayload {
companion object {
val BEEP_DATA_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "beep_data")
val TYPE = CustomPacketPayload.Type<BeepDataPayload>(BEEP_DATA_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, BeepDataPayload> {
override fun decode(buf: RegistryFriendlyByteBuf): BeepDataPayload {
val pos = buf.readBlockPos()
val pattern = buf.readUtf()
val freq = buf.readVarInt()
val duration = buf.readVarLong()
val volume = buf.readDouble()
return BeepDataPayload(pos, pattern, freq, Duration.ofMillis(duration), volume)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: BeepDataPayload) {
buf.writeBlockPos(payload.pos)
buf.writeUtf(payload.pattern)
buf.writeVarInt(payload.freq)
buf.writeVarLong(payload.duration.toMillis())
buf.writeDouble(payload.volume)
}
}
}
override fun type() = TYPE
}
val screenMap = HashMap<ServerPlayer, DeviceNode>()
fun playerScreenClosed(player: ServerPlayer) {
screenMap.remove(player)
}
fun registerPlayerScreen(player: ServerPlayer, devNode: DeviceNode) {
screenMap[player] = devNode
}
fun nodeErased(node: DeviceNode) {
var player: ServerPlayer? = null
for((p, n) in screenMap) {
if(n == node) player = p
}
if(player != null) screenMap.remove(player)
}
fun syncScreens() {
for((player, ent) in screenMap) {
val buf = FriendlyByteBuf(Unpooled.buffer())
ent.encodeScreenData(player, buf)
NetworkManager.sendToPlayer(player, ScreenPayload(buf))
}
}
fun sendScreenInteraction(friendlyByteBuf: FriendlyByteBuf) {
NetworkManager.sendToServer(ScreenDataPayload(friendlyByteBuf))
}
fun emitBeep(level: Level, beepDataPayload: BeepDataPayload) {
if(level is ServerLevel) {
level.players().forEach {
NetworkManager.sendToPlayer(it, beepDataPayload)
}
}
}
}

View File

@@ -0,0 +1,46 @@
package org.neoflock.neocomputers.network
import net.minecraft.world.level.block.entity.BlockEntityType
import org.neoflock.neocomputers.block.DeviceBlockEntity
//? if fabric {
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext
import net.minecraft.core.Direction
import team.reborn.energy.api.EnergyStorage;
//?}
// our soul purpose is to fuse bullshit power APIs together
// the NodeBlockEntity and Node given us a way to get power from a block, we just
// need to tell mods how to do it as well
object PowerManager {
fun<T: DeviceBlockEntity> registerPowerDevice(blockEntityType: BlockEntityType<T>) {
//? if fabric {
EnergyStorage.SIDED.registerForBlockEntity({
// TODO: as this is currently written, if the node instance changes and the mod cached the conversion, we're boned. Consider fixing it.
entity, dir ->
val node = entity.getNodeFromSide(dir ?: Direction.UP)
if(node == null) null else object : EnergyStorage {
override fun getAmount() = node.energy
override fun getCapacity() = node.energyCapacity
override fun supportsExtraction() = node.powerRole != PowerRole.CONSUMER && node.energyCapacity > 0
override fun supportsInsertion() = node.powerRole != PowerRole.GENERATOR
override fun extract(maxAmount: Long, transaction: TransactionContext?): Long {
if(node.powerRole == PowerRole.CONSUMER) return 0
val taken = node.withdrawEnergy(maxAmount)
transaction?.addCloseCallback {
ctx, res -> if(res.wasAborted() || !res.wasCommitted()) node.giveEnergy(taken)
}
return taken
}
override fun insert(maxAmount: Long, transaction: TransactionContext?): Long {
if(node.powerRole == PowerRole.GENERATOR) return 0
val given = node.giveEnergy(maxAmount)
transaction?.addCloseCallback { ctx, res ->
if (res.wasAborted() || !res.wasCommitted()) node.withdrawEnergy(given)
}
return given
}
}
}, blockEntityType);
//?}
}
}

View File

@@ -0,0 +1,37 @@
package org.neoflock.neocomputers.sounds
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
import net.minecraft.client.resources.sounds.EntityBoundSoundInstance
import net.minecraft.client.resources.sounds.MinecartSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import org.neoflock.neocomputers.entity.MachineEntity
class ComputerRunningSoundInstance: AbstractTickableSoundInstance {
val machine: MachineEntity
fun updatePosition() {
val pos = machine.getMachineBlockPosition()
this.x = pos.x.toDouble() + 0.5
this.y = pos.y.toDouble() + 0.5
this.z = pos.z.toDouble() + 0.5
}
constructor(machine: MachineEntity, soundEvent: SoundEvent, soundSource: SoundSource): super(soundEvent, soundSource, SoundInstance.createUnseededRandom()) {
this.machine = machine
this.looping = true
this.delay = 0
this.volume = 1.0F
this.pitch = 1.0F
updatePosition()
}
override fun tick() {
if(!machine.isRunning()) {
this.stop()
} else {
updatePosition()
}
}
}

View File

@@ -0,0 +1,163 @@
package org.neoflock.neocomputers.sounds
import dev.architectury.registry.registries.DeferredRegister
import net.minecraft.client.Minecraft
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.phys.Vec3
import org.lwjgl.BufferUtils
import org.neoflock.neocomputers.NeoComputers
import org.lwjgl.openal.AL10
import java.nio.ByteBuffer
import kotlin.experimental.xor
import kotlin.math.PI
import kotlin.math.max
import kotlin.math.sign
import kotlin.math.sin
object Sounds {
val SOUNDS = DeferredRegister.create(NeoComputers.MODID, Registries.SOUND_EVENT)!!
val COMPUTER_RUNNING = registerSound("computer_running")
fun registerSound(name: String) = SOUNDS.register(name) {
SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, name))
}!!
val BEEP_SAMPLERATE = 44100
val BEEP_AMPLITUDE = 32f
val BEEP_MAXDIST = 16f
// Also largely taken from https://github.com/MightyPirates/OpenComputers/blob/571482db88080d56329e8f8cf0db2a90825bf1d7/src/main/scala/li/cil/oc/util/Audio.scala
val allSounds = ThreadLocal.withInitial { mutableListOf<CustomSoundBuffer>() }
class CustomSoundBuffer {
var dead: Boolean = true
var buffer: Int = -1
var source: Int = -1
fun start(x: Float, y: Float, z: Float, data: ByteBuffer, gain: Float): Int? {
// clear errors or smth idk
AL10.alGetError()
// written in a C style by a C dev
// all this work on a JVM project and I'm still writing C
// would be better if Kotlin had goto btw just saying
val ok = AL10.AL_NO_ERROR
var err = ok
buffer = AL10.alGenBuffers()
err = AL10.alGetError()
if(err != ok) return err
AL10.alBufferData(buffer, AL10.AL_FORMAT_MONO8, data, BEEP_SAMPLERATE)
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
return err
}
source = AL10.alGenSources()
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
return err
}
AL10.alSourceQueueBuffers(source, buffer)
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
AL10.alDeleteSources(source)
return err
}
AL10.alSource3f(source, AL10.AL_POSITION, x, y, z)
AL10.alSourcef(source, AL10.AL_REFERENCE_DISTANCE, BEEP_MAXDIST)
AL10.alSourcef(source, AL10.AL_MAX_DISTANCE, BEEP_MAXDIST)
AL10.alSourcef(source, AL10.AL_GAIN, gain * 0.3f)
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
AL10.alDeleteSources(source)
return err
}
AL10.alSourcePlay(source)
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
AL10.alDeleteSources(source)
return err
}
dead = false
return null
}
fun checkDone(): Boolean {
if(dead) return true
if(AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING) return false
dead = true
AL10.alDeleteSources(source)
AL10.alDeleteBuffers(buffer)
return true
}
}
fun beep(pos: Vec3, pattern: String, frequency: Int = 1000, duration: Int = 200) {
NeoComputers.LOGGER.info("Beep: $pattern, $frequency Hz, $duration ms")
val mc = Minecraft.getInstance()
val playerPos = mc.player?.position() ?: pos
val distanceBasedGain = max(0.0, 1 - pos.distanceTo(playerPos) / BEEP_MAXDIST).toFloat()
val volume = 1.0
val gain = distanceBasedGain * volume
if (gain <= 0 || BEEP_AMPLITUDE <= 0) return
// Algorithm effectively ported over from https://github.com/MightyPirates/OpenComputers/blob/571482db88080d56329e8f8cf0db2a90825bf1d7/src/main/scala/li/cil/oc/util/Audio.scala
// We do add support for spaces tho
val charArr = pattern.toCharArray()
val sampleCounts = charArr.map { if(it == '.') duration else 2 * duration }.map { it * BEEP_SAMPLERATE / 1000 }
val pauseSample = 50 * BEEP_SAMPLERATE / 1000
val finalBuf = BufferUtils.createByteBuffer(sampleCounts.sum() + pauseSample * sampleCounts.lastIndex)
val step = frequency.toFloat() / BEEP_SAMPLERATE
var off = 0f
for((i, sampleCount) in sampleCounts.withIndex()) {
if(charArr[i] == ' ') {
for(sample in 0..<sampleCount) {
finalBuf.put(127)
}
} else {
for(sample in 0..<sampleCount) {
val angle = 2 * PI * off
val value = (sin(angle).sign * BEEP_AMPLITUDE).toInt().toByte().xor(0x80.toByte())
off += step
if(off > 1) off -= 1f
finalBuf.put(value)
}
}
if(finalBuf.hasRemaining()) {
for(sample in 0..<pauseSample) {
finalBuf.put(127)
}
}
}
finalBuf.rewind()
val sound = CustomSoundBuffer()
val soundErr = sound.start(pos.x.toFloat(), pos.y.toFloat(), pos.z.toFloat(), finalBuf, gain.toFloat())
if(soundErr != null) {
NeoComputers.LOGGER.error("Playing beep failed, OpenAL exit code of $soundErr")
return
}
NeoComputers.LOGGER.info("Beeping with ${finalBuf.capacity()} samples")
allSounds.get().addLast(sound)
}
fun tickCustomSounds() {
allSounds.get().removeIf { it.checkDone() }
}
}

View File

@@ -0,0 +1,16 @@
package org.neoflock.neocomputers.utils
import dev.architectury.registry.fuel.FuelRegistry
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
// mewhenthe, aka e, will have me publicly executed for this code
object ContainerUtils {
fun getBurningTime(itemStack: ItemStack): Int? {
val time = FuelRegistry.get(itemStack)
if(time == 0) return null
return time
}
fun isBurningFuel(itemStack: ItemStack) = getBurningTime(itemStack) != null
}

View File

@@ -0,0 +1,33 @@
package org.neoflock.neocomputers.utils;
import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.Resource
import net.minecraft.server.packs.resources.ResourceManager
import org.neoflock.neocomputers.NeoComputers
import java.nio.charset.StandardCharsets
/*
* OC hex font format:
* 5 character hex code .. ":" .. variable length hex code .. LF
* this is essentially a dictionary
* */
object FontProvider {
val map: MutableMap<Char, ArrayList<Byte>> = mutableMapOf();
fun load(loc: ResourceLocation) {
var man: ResourceManager = Minecraft.getInstance().resourceManager
var resource: Resource = man.getResourceOrThrow(loc)
var stream = resource.open()
var bfr = stream.bufferedReader();
while (stream.available() > 0) {
var line: String = bfr.readLine()
var splitLine = line.split(":");
var key = splitLine[0].hexToInt().toChar();
var value: ByteArray = splitLine[1].hexToByteArray(); // shout out to the kotlin stdlib for having ts
var bytes: ArrayList<Byte> = value.toCollection(ArrayList<Byte>());
map[key] = bytes
}
NeoComputers.LOGGER.info("[FontProvider] Loaded font!");
}
}

View File

@@ -0,0 +1,15 @@
package org.neoflock.neocomputers.utils
object Formatting {
fun formatMemory(size: Long, spacing: String = " "): String {
var unit = 0
val units = listOf("B", "KiB", "MiB", "GiB", "TiB", "PiB")
var num = size.toDouble()
while(unit < units.lastIndex && num >= 1024) {
num /= 1024
unit++
}
num = (num * 100).toInt().toDouble() / 100
return "$num$spacing${units[unit]}"
}
}

View File

@@ -0,0 +1,203 @@
package org.neoflock.neocomputers.utils
// based off the ImplementedContainer of https://docs.fabricmc.net/develop/blocks/block-containers
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.VertexFormat
import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderStateShard.ShaderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.world.Container;
import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.Resource
import net.minecraft.world.ContainerHelper
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntityType
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.gui.widget.DynamicSlot
// Common container interface, assumes the entire purpose is purely raw item storage
interface GenericContainer : Container {
fun getItems(): NonNullList<ItemStack>
override fun getContainerSize(): Int {
return getItems().size
}
override fun isEmpty(): Boolean {
return getItems().all { it.isEmpty }
}
override fun getItem(i: Int): ItemStack {
return getItems()[i]
}
override fun removeItem(slot: Int, count: Int): ItemStack {
val res = ContainerHelper.removeItem(getItems(), slot, count)
if (!res.isEmpty) setChanged()
return res
}
override fun setItem(slot: Int, itemStack: ItemStack) {
getItems()[slot] = itemStack
// in case of bullshit
if(itemStack.count > itemStack.maxStackSize) {
// rip items
itemStack.count = itemStack.maxStackSize
}
}
override fun removeItemNoUpdate(i: Int): ItemStack {
return ContainerHelper.takeItem(getItems(), i)
}
override fun clearContent() {
getItems().clear()
}
}
abstract class GenericContainerMenu(menuType: MenuType<*>, id: Int, var container: Container): AbstractContainerMenu(menuType, id) {
fun addInventorySlots(inventory: Inventory, x: Int, y: Int) {
// Based off the code in ChestMenu
for (i in 0..2) {
for (j in 0..8) {
this.addSlot(Slot(inventory, j + i * 9 + 9, 8 + j * 18, y + i * 18))
}
}
addInventoryHotbar(inventory, x, y + 3 * 18 + 4)
}
fun addInventoryHotbar(inventory: Inventory, x: Int, y: Int) {
for (i in 0..8) {
this.addSlot(Slot(inventory, i, x + i * 18, y))
}
}
// taken from https://docs.fabricmc.net/develop/blocks/container-menus
override fun quickMoveStack(player: Player, i: Int): ItemStack? {
val slot = slots[i]
if(!slot.hasItem()) return ItemStack.EMPTY
val stack = slot.item
val copied = stack.copy()
val contSize = container.containerSize
if(i < contSize) {
if(!this.moveItemStackTo(stack, contSize, slots.size, true)) {
return ItemStack.EMPTY
}
} else if(!this.moveItemStackTo(stack, 0, contSize, false)) {
return ItemStack.EMPTY
}
if(stack.isEmpty) {
slot.setByPlayer(ItemStack.EMPTY)
} else {
slot.setChanged()
}
return copied
}
override fun stillValid(player: Player): Boolean {
return container.stillValid(player)
}
}
abstract class GenericContainerScreen<T: GenericContainerMenu>(menu: T, inventory: Inventory, component: Component): AbstractContainerScreen<T>(menu, inventory, component) {
open fun shouldCenterTitle() = true
open fun shouldRenderTooltip() = true
open fun findMenuTexture(): ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/background.png")
open fun getBoundBlockEntityType(): Set<BlockEntityType<*>> = setOf()
open fun processScreenStatePacket(buf: FriendlyByteBuf) {}
val imageX: Int
get() = (width - imageWidth) / 2
val imageY: Int
get() = (height - imageHeight) / 2
var widgets = mutableListOf<AbstractWidget>()
val RENDER_TYPE = { r: ResourceLocation ->
RenderType.create("nc_gui_bg", DefaultVertexFormat.POSITION_TEX, VertexFormat.Mode.QUADS, RenderType.TRANSIENT_BUFFER_SIZE, RenderType.CompositeState.builder()
.setShaderState(ShaderStateShard.POSITION_TEX_SHADER)
.setTextureState(RenderStateShard.TextureStateShard(r, false, false))
.createCompositeState(false))
}
override fun init() {
super.init()
if(shouldCenterTitle()) this.titleLabelX = (this.imageWidth - this.font.width(this.title)) / 2
}
override fun renderBg(guiGraphics: GuiGraphics, f: Float, i: Int, j: Int) {
val menuTex = findMenuTexture()
val cx = (width - imageWidth) / 2
val cy = (height - imageHeight) / 2
guiGraphics.pose().pushPose()
guiGraphics.pose().translate(cx.toFloat(), cy.toFloat(), 0f)
guiGraphics.blit(menuTex, 0, 0, 0, 0, imageWidth, imageHeight)
renderbg(guiGraphics, f, i-cx, j-cy)
for (widget in widgets) {
widget.render(guiGraphics, i-cx, j-cy, f)
}
for (slot in menu.slots) {
if (slot is DynamicSlot) {
slot.draw(guiGraphics, i-cx, j-cy)
}
}
guiGraphics.pose().popPose()
}
open fun renderbg(guiGraphics: GuiGraphics, partialTick: Float, mouseX: Int, mouseY: Int) {}
open fun renderCustomOverlay(graphics: GuiGraphics, mouseX: Int, mouseY: Int, blend: Float) { }
override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, something: Float) {
super.render(graphics, mouseX, mouseY, something)
graphics.pose().pushPose()
graphics.pose().translate(imageX.toFloat(), imageY.toFloat(), 0f)
renderCustomOverlay(graphics, mouseX-imageX, mouseY-imageY, something)
graphics.pose().popPose() // not even doing this because it's better anymore, im just doing this because i dont want to change it back
if(shouldRenderTooltip()) super.renderTooltip(graphics, mouseX, mouseY)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
super.mouseClicked(mouseX, mouseY, button)
for (widget in widgets) {
if (widget.mouseClicked(mouseX-imageX, mouseY-imageY, button)) return true
}
return false
}
fun addWidget(widget: AbstractWidget) {
widgets.add(widget)
}
}

View File

@@ -0,0 +1,99 @@
package org.neoflock.neocomputers.utils
import net.minecraft.network.FriendlyByteBuf
import kotlin.math.min
data class GPUChar(val c: Char, val fg: Int =0xFFFFFF, val bg: Int = 0) // all is bgr
// TODO: wrapper over NN buffer
class TextBuffer(var width: Int, var height: Int) {
val blank = GPUChar(' ')
var buf = Array(width*height) { blank }
fun encodeContents(buf: FriendlyByteBuf) {
// 0x01 means set fg, 0x02 means set bg,
// 0x03 means set char+count
var lastFg = 0xFFFFFF
var lastBg = 0x000000
buf.writeVarInt(width)
buf.writeVarInt(height)
var i = 0
while(i < this.buf.size) {
val px = this.buf[i]
if(px.fg != lastFg) {
buf.writeByte(0x01)
buf.writeVarInt(px.fg)
lastFg = px.fg
}
if(px.bg != lastBg) {
buf.writeByte(0x02)
buf.writeVarInt(px.bg)
lastBg = px.bg
}
var charWritten = 1
while((i+charWritten) < this.buf.size && this.buf[i+charWritten].c == px.c) charWritten++
buf.writeByte(0x03)
buf.writeVarInt(px.c.code)
buf.writeVarInt(charWritten)
i += charWritten
}
}
fun decodeContents(buf: FriendlyByteBuf) {
var lastFg = 0xFFFFFF
var lastBg = 0x000000
width = buf.readVarInt()
height = buf.readVarInt()
if(width*height != this.buf.size) {
this.buf = Array(width * height) { blank }
}
var i = 0
while(i < width*height) {
val op = buf.readByte().toInt()
if(op == 0x01) {
lastFg = buf.readVarInt()
}
if(op == 0x02) {
lastBg = buf.readVarInt()
}
if(op == 0x03) {
val c = buf.readVarInt().toChar()
val n = buf.readVarInt()
for(o in 0..<n) {
this.buf[i+o] = GPUChar(c, lastFg, lastBg)
}
i += n
}
}
}
fun inBounds(x: Int, y: Int) = x >= 0 && y >= 0 && x < width && y < height
fun get(x: Int, y: Int) = if(inBounds(x, y)) buf[x+y*width] else blank
fun set(x: Int, y: Int, pixel: GPUChar) {
if(!inBounds(x, y)) return
buf[x+y*width] = pixel
}
fun set(x: Int, y: Int, text: String, fg: Int = 0xFFFFFF, bg: Int = 0x000000, vertical: Boolean = false) {
for ((i, c) in text.toCharArray().withIndex()) {
val cx = if(vertical) x else x + i
val cy = if(vertical) y + i else y
set(cx, cy, GPUChar(c, fg, bg))
}
}
fun fill(x: Int, y: Int, w: Int, h: Int, pixel: GPUChar = blank) {
// turn it into values we can fw
val fw = min(w, width)
val fh = min(h, height)
for(py in y..<y+fh) {
for (px in x..<x + fw) {
set(px, py, pixel)
}
}
}
}

View File

@@ -1,7 +1,7 @@
modLoader = "javafml" modLoader = "javafml"
loaderVersion = "*" loaderVersion = "*"
license = "LGPLv3" license = "LGPLv3"
issueTrackerURL="https://github.com/NeoFlock/NeoComputers/issues" issueTrackerURL="https://gitea.codersquack.nl/NeoFlock/NeoComputers/issues"
[[mods]] [[mods]]
modId = "neocomputers" modId = "neocomputers"

View File

@@ -1,7 +1,7 @@
modLoader = "javafml" modLoader = "javafml"
loaderVersion = "*" loaderVersion = "*"
license = "LGPLv3" license = "LGPLv3"
issueTrackerURL="https://github.com/NeoFlock/NeoComputers/issues" issueTrackerURL="https://gitea.codersquack.nl/NeoFlock/NeoComputers/issues"
[[mods]] [[mods]]
modId = "neocomputers" modId = "neocomputers"

View File

@@ -0,0 +1,8 @@
{
"sources": [
{
"type": "single",
"source": "neocomputers:block/relay_top"
}
]
}

View File

@@ -0,0 +1,94 @@
{
"multipart": [
{ "apply": { "model": "neocomputers:block/cable/cable_center" }},
{ "when": { "south": true },
"apply": { "model": "neocomputers:block/cable/cable_connection" }},
{ "when": { "east": true},
"apply": { "model": "neocomputers:block/cable/cable_connection", "y": -90}},
{ "when": { "west": true },
"apply": { "model": "neocomputers:block/cable/cable_connection", "y": 90}},
{ "when": { "north": true },
"apply": { "model": "neocomputers:block/cable/cable_connection", "y": 180}},
{ "when": { "up": true },
"apply": { "model": "neocomputers:block/cable/cable_connection", "x": 90}},
{ "when": { "down": true },
"apply": { "model": "neocomputers:block/cable/cable_connection", "x": -90}},
{
"when": {
"south": false,
"north": false,
"east": false,
"west": false,
"up": false,
"down": false
},
"apply": { "model": "neocomputers:block/cable/cable_caps" }
},
{
"when": {
"south": true,
"north": false,
"east": false,
"west": false,
"up": false,
"down": false
},
"apply": { "model": "neocomputers:block/cable/cable_cap" }
},
{
"when": {
"south": false,
"north": true,
"east": false,
"west": false,
"up": false,
"down": false
},
"apply": { "model": "neocomputers:block/cable/cable_cap", "y": 180 }
},
{
"when": {
"south": false,
"north": false,
"east": true,
"west": false,
"up": false,
"down": false
},
"apply": { "model": "neocomputers:block/cable/cable_cap", "y": -90 }
},
{
"when": {
"south": false,
"north": false,
"east": false,
"west": true,
"up": false,
"down": false
},
"apply": { "model": "neocomputers:block/cable/cable_cap", "y": 90 }
},
{
"when": {
"south": false,
"north": false,
"east": false,
"west": false,
"up": true,
"down": false
},
"apply": { "model": "neocomputers:block/cable/cable_cap", "x": 90 }
},
{
"when": {
"south": false,
"north": false,
"east": false,
"west": false,
"up": false,
"down": true
},
"apply": { "model": "neocomputers:block/cable/cable_cap", "x": -90 }
}
]
}

Some files were not shown because too many files have changed in this diff Show More