├── LICENSE
├── README.md
├── client
├── client.go
├── infomation.go
├── move.go
└── packets.go
├── game
├── chat.go
├── config.go
├── game.go
├── playerlist.go
└── tags.go
├── go.mod
├── go.sum
├── main.go
└── world
├── RegistryCodec.nbt
├── chat.go
├── entity.go
├── entity
└── metadata.go
├── internal
└── bvh
│ ├── bound.go
│ ├── bound_test.go
│ ├── bvh.go
│ ├── bvh_test.go
│ └── vector.go
├── loader.go
├── player.go
├── provider.go
├── registrycodec.go
├── tick.go
├── viewer.go
└── world.go
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # server
2 |
3 | A Minecraft server implement based on [go-mc](https://github.com/Tnze/go-mc)
--------------------------------------------------------------------------------
/client/client.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package client
18 |
19 | import (
20 | "go.uber.org/zap"
21 |
22 | "github.com/Tnze/go-mc/data/packetid"
23 | "github.com/Tnze/go-mc/net"
24 | pk "github.com/Tnze/go-mc/net/packet"
25 | "github.com/Tnze/go-mc/net/queue"
26 | "github.com/Tnze/go-mc/server"
27 | "github.com/go-mc/server/world"
28 | )
29 |
30 | type Client struct {
31 | log *zap.Logger
32 | conn *net.Conn
33 | player *world.Player
34 | queue server.PacketQueue
35 | handlers []PacketHandler
36 | // pointer to the Player.Input
37 | *world.Inputs
38 | }
39 |
40 | type PacketHandler func(p pk.Packet, c *Client) error
41 |
42 | func New(log *zap.Logger, conn *net.Conn, player *world.Player) *Client {
43 | return &Client{
44 | log: log,
45 | conn: conn,
46 | player: player,
47 | queue: queue.NewChannelQueue[pk.Packet](256),
48 | handlers: defaultHandlers[:],
49 | Inputs: &player.Inputs,
50 | }
51 | }
52 |
53 | func (c *Client) Start() {
54 | stopped := make(chan struct{}, 2)
55 | done := func() {
56 | stopped <- struct{}{}
57 | }
58 | // Exit when any error is thrown
59 | go c.startSend(done)
60 | go c.startReceive(done)
61 | <-stopped
62 | }
63 |
64 | func (c *Client) startSend(done func()) {
65 | defer done()
66 | for {
67 | p, ok := c.queue.Pull()
68 | if !ok {
69 | return
70 | }
71 | err := c.conn.WritePacket(p)
72 | if err != nil {
73 | c.log.Debug("Send packet fail", zap.Error(err))
74 | return
75 | }
76 | if packetid.ClientboundPacketID(p.ID) == packetid.ClientboundDisconnect {
77 | return
78 | }
79 | }
80 | }
81 |
82 | func (c *Client) startReceive(done func()) {
83 | defer done()
84 | var packet pk.Packet
85 | for {
86 | err := c.conn.ReadPacket(&packet)
87 | if err != nil {
88 | c.log.Debug("Receive packet fail", zap.Error(err))
89 | return
90 | }
91 | if packet.ID < 0 || packet.ID >= int32(len(c.handlers)) {
92 | c.log.Debug("Invalid packet id", zap.Int32("id", packet.ID), zap.Int("len", len(packet.Data)))
93 | return
94 | }
95 | if handler := c.handlers[packet.ID]; handler != nil {
96 | err = handler(packet, c)
97 | if err != nil {
98 | c.log.Error("Handle packet error", zap.Int32("id", packet.ID), zap.Error(err))
99 | return
100 | }
101 | }
102 | }
103 | }
104 |
105 | func (c *Client) AddHandler(id packetid.ServerboundPacketID, handler PacketHandler) {
106 | c.handlers[id] = handler
107 | }
108 | func (c *Client) GetPlayer() *world.Player { return c.player }
109 |
110 | var defaultHandlers = [packetid.ServerboundPacketIDGuard]PacketHandler{
111 | packetid.ServerboundAcceptTeleportation: clientAcceptTeleportation,
112 | packetid.ServerboundClientInformation: clientInformation,
113 | packetid.ServerboundMovePlayerPos: clientMovePlayerPos,
114 | packetid.ServerboundMovePlayerPosRot: clientMovePlayerPosRot,
115 | packetid.ServerboundMovePlayerRot: clientMovePlayerRot,
116 | packetid.ServerboundMovePlayerStatusOnly: clientMovePlayerStatusOnly,
117 | packetid.ServerboundMoveVehicle: clientMoveVehicle,
118 | }
119 |
--------------------------------------------------------------------------------
/client/infomation.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package client
18 |
19 | import (
20 | pk "github.com/Tnze/go-mc/net/packet"
21 | "github.com/go-mc/server/world"
22 | )
23 |
24 | func clientInformation(p pk.Packet, client *Client) error {
25 | var info world.ClientInfo
26 | if err := p.Scan(&info); err != nil {
27 | return err
28 | }
29 | client.Inputs.Lock()
30 | client.Inputs.ClientInfo = info
31 | client.Inputs.Unlock()
32 | return nil
33 | }
34 |
--------------------------------------------------------------------------------
/client/move.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package client
18 |
19 | import (
20 | "bytes"
21 |
22 | pk "github.com/Tnze/go-mc/net/packet"
23 | )
24 |
25 | func clientAcceptTeleportation(p pk.Packet, c *Client) error {
26 | var TeleportID pk.VarInt
27 | _, err := TeleportID.ReadFrom(bytes.NewReader(p.Data))
28 | if err != nil {
29 | return err
30 | }
31 | c.Inputs.Lock()
32 | c.Inputs.TeleportID = int32(TeleportID)
33 | c.Inputs.Unlock()
34 | return nil
35 | }
36 |
37 | func clientMovePlayerPos(p pk.Packet, c *Client) error {
38 | var X, FeetY, Z pk.Double
39 | var OnGround pk.Boolean
40 | if err := p.Scan(&X, &FeetY, &Z, &OnGround); err != nil {
41 | return err
42 | }
43 | c.Inputs.Lock()
44 | c.Inputs.Position = [3]float64{float64(X), float64(FeetY), float64(Z)}
45 | c.Inputs.Unlock()
46 | return nil
47 | }
48 |
49 | func clientMovePlayerPosRot(p pk.Packet, c *Client) error {
50 | var X, FeetY, Z pk.Double
51 | var Yaw, Pitch pk.Float
52 | var OnGround pk.Boolean
53 | if err := p.Scan(&X, &FeetY, &Z, &Yaw, &Pitch, &OnGround); err != nil {
54 | return err
55 | }
56 | c.Inputs.Lock()
57 | c.Inputs.Position = [3]float64{float64(X), float64(FeetY), float64(Z)}
58 | c.Inputs.Rotation = [2]float32{float32(Yaw), float32(Pitch)}
59 | c.Inputs.Unlock()
60 | return nil
61 | }
62 |
63 | func clientMovePlayerRot(p pk.Packet, c *Client) error {
64 | var Yaw, Pitch pk.Float
65 | var OnGround pk.Boolean
66 | if err := p.Scan(&Yaw, &Pitch, &OnGround); err != nil {
67 | return err
68 | }
69 | c.Inputs.Lock()
70 | c.Inputs.Rotation = [2]float32{float32(Yaw), float32(Pitch)}
71 | c.Inputs.Unlock()
72 | return nil
73 | }
74 |
75 | func clientMovePlayerStatusOnly(p pk.Packet, c *Client) error {
76 | var OnGround pk.UnsignedByte
77 | if err := p.Scan(&OnGround); err != nil {
78 | return err
79 | }
80 | c.Inputs.Lock()
81 | c.Inputs.OnGround = OnGround != 0
82 | c.Inputs.Unlock()
83 | return nil
84 | }
85 |
86 | func clientMoveVehicle(_ pk.Packet, _ *Client) error {
87 | return nil
88 | }
89 |
--------------------------------------------------------------------------------
/client/packets.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package client
18 |
19 | import (
20 | "bytes"
21 | "encoding/binary"
22 | "sync/atomic"
23 | "unsafe"
24 |
25 | "github.com/google/uuid"
26 | "go.uber.org/zap"
27 |
28 | "github.com/Tnze/go-mc/chat"
29 | "github.com/Tnze/go-mc/chat/sign"
30 | "github.com/Tnze/go-mc/data/packetid"
31 | "github.com/Tnze/go-mc/level"
32 | pk "github.com/Tnze/go-mc/net/packet"
33 | "github.com/go-mc/server/world"
34 | )
35 |
36 | func (c *Client) SendPacket(id packetid.ClientboundPacketID, fields ...pk.FieldEncoder) {
37 | var buffer bytes.Buffer
38 |
39 | // Write the packet fields
40 | for i := range fields {
41 | if _, err := fields[i].WriteTo(&buffer); err != nil {
42 | c.log.Panic("Marshal packet error", zap.Error(err))
43 | }
44 | }
45 |
46 | // Send the packet data
47 | c.queue.Push(pk.Packet{
48 | ID: int32(id),
49 | Data: buffer.Bytes(),
50 | })
51 | }
52 |
53 | func (c *Client) SendKeepAlive(id int64) {
54 | c.SendPacket(packetid.ClientboundKeepAlive, pk.Long(id))
55 | }
56 |
57 | // SendDisconnect send ClientboundDisconnect packet to client.
58 | // Once the packet is sent, the connection will be closed.
59 | func (c *Client) SendDisconnect(reason chat.Message) {
60 | c.log.Debug("Disconnect player", zap.String("reason", reason.ClearString()))
61 | c.SendPacket(packetid.ClientboundDisconnect, reason)
62 | }
63 |
64 | func (c *Client) SendLogin(w *world.World, p *world.Player) {
65 | hashedSeed := w.HashedSeed()
66 | c.SendPacket(
67 | packetid.ClientboundLogin,
68 | pk.Int(p.EntityID),
69 | pk.Boolean(false), // Is Hardcore
70 | pk.Byte(p.Gamemode),
71 | pk.Byte(-1),
72 | pk.Array([]pk.Identifier{
73 | pk.Identifier(w.Name()),
74 | }),
75 | pk.NBT(world.NetworkCodec),
76 | pk.Identifier("minecraft:overworld"),
77 | pk.Identifier(w.Name()),
78 | pk.Long(binary.BigEndian.Uint64(hashedSeed[:8])),
79 | pk.VarInt(0), // Max players (ignored by client)
80 | pk.VarInt(p.ViewDistance), // View Distance
81 | pk.VarInt(p.ViewDistance), // Simulation Distance
82 | pk.Boolean(false), // Reduced Debug Info
83 | pk.Boolean(false), // Enable respawn screen
84 | pk.Boolean(false), // Is Debug
85 | pk.Boolean(false), // Is Flat
86 | pk.Boolean(false), // Has Last Death Location
87 | )
88 | }
89 |
90 | func (c *Client) SendServerData(motd *chat.Message, favIcon string, enforceSecureProfile bool) {
91 | c.SendPacket(
92 | packetid.ClientboundServerData,
93 | motd,
94 | pk.Option[pk.String, *pk.String]{
95 | Has: favIcon != "",
96 | Val: pk.String(favIcon),
97 | },
98 | pk.Boolean(enforceSecureProfile),
99 | )
100 | }
101 |
102 | // Actions of [SendPlayerInfoUpdate]
103 | const (
104 | PlayerInfoAddPlayer = iota
105 | PlayerInfoInitializeChat
106 | PlayerInfoUpdateGameMode
107 | PlayerInfoUpdateListed
108 | PlayerInfoUpdateLatency
109 | PlayerInfoUpdateDisplayName
110 | // PlayerInfoEnumGuard is the number of the enums
111 | PlayerInfoEnumGuard
112 | )
113 |
114 | func NewPlayerInfoAction(actions ...int) pk.FixedBitSet {
115 | enumSet := pk.NewFixedBitSet(PlayerInfoEnumGuard)
116 | for _, action := range actions {
117 | enumSet.Set(action, true)
118 | }
119 | return enumSet
120 | }
121 |
122 | func (c *Client) SendPlayerInfoUpdate(actions pk.FixedBitSet, players []*world.Player) {
123 | var buf bytes.Buffer
124 | _, _ = actions.WriteTo(&buf)
125 | _, _ = pk.VarInt(len(players)).WriteTo(&buf)
126 | for _, player := range players {
127 | _, _ = pk.UUID(player.UUID).WriteTo(&buf)
128 | if actions.Get(PlayerInfoAddPlayer) {
129 | _, _ = pk.String(player.Name).WriteTo(&buf)
130 | _, _ = pk.Array(player.Properties).WriteTo(&buf)
131 | }
132 | if actions.Get(PlayerInfoInitializeChat) {
133 | panic("not yet support InitializeChat")
134 | }
135 | if actions.Get(PlayerInfoUpdateGameMode) {
136 | _, _ = pk.VarInt(player.Gamemode).WriteTo(&buf)
137 | }
138 | if actions.Get(PlayerInfoUpdateListed) {
139 | _, _ = pk.Boolean(true).WriteTo(&buf)
140 | }
141 | if actions.Get(PlayerInfoUpdateLatency) {
142 | _, _ = pk.VarInt(player.Latency.Milliseconds()).WriteTo(&buf)
143 | }
144 | if actions.Get(PlayerInfoUpdateDisplayName) {
145 | panic("not yet support DisplayName")
146 | }
147 | }
148 | c.queue.Push(pk.Packet{
149 | ID: int32(packetid.ClientboundPlayerInfoUpdate),
150 | Data: buf.Bytes(),
151 | })
152 | }
153 |
154 | func (c *Client) SendPlayerInfoRemove(players []*world.Player) {
155 | var buff bytes.Buffer
156 |
157 | if _, err := pk.VarInt(len(players)).WriteTo(&buff); err != nil {
158 | c.log.Panic("Marshal packet error", zap.Error(err))
159 | }
160 | for _, p := range players {
161 | if _, err := pk.UUID(p.UUID).WriteTo(&buff); err != nil {
162 | c.log.Panic("Marshal packet error", zap.Error(err))
163 | }
164 | }
165 |
166 | c.queue.Push(pk.Packet{
167 | ID: int32(packetid.ClientboundPlayerInfoRemove),
168 | Data: buff.Bytes(),
169 | })
170 | }
171 |
172 | func (c *Client) SendLevelChunkWithLight(pos level.ChunkPos, chunk *level.Chunk) {
173 | c.SendPacket(packetid.ClientboundLevelChunkWithLight, pos, chunk)
174 | }
175 |
176 | func (c *Client) SendForgetLevelChunk(pos level.ChunkPos) {
177 | c.SendPacket(packetid.ClientboundForgetLevelChunk, pos)
178 | }
179 |
180 | func (c *Client) SendAddPlayer(p *world.Player) {
181 | c.SendPacket(
182 | packetid.ClientboundAddPlayer,
183 | pk.VarInt(p.EntityID),
184 | pk.UUID(p.UUID),
185 | pk.Double(p.Position[0]),
186 | pk.Double(p.Position[1]),
187 | pk.Double(p.Position[2]),
188 | pk.Angle(p.Rotation[0]),
189 | pk.Angle(p.Rotation[1]),
190 | )
191 | }
192 |
193 | func (c *Client) SendMoveEntitiesPos(eid int32, delta [3]int16, onGround bool) {
194 | c.SendPacket(
195 | packetid.ClientboundMoveEntityPos,
196 | pk.VarInt(eid),
197 | pk.Short(delta[0]),
198 | pk.Short(delta[1]),
199 | pk.Short(delta[2]),
200 | pk.Boolean(onGround),
201 | )
202 | }
203 |
204 | func (c *Client) SendMoveEntitiesPosAndRot(eid int32, delta [3]int16, rot [2]int8, onGround bool) {
205 | c.SendPacket(
206 | packetid.ClientboundMoveEntityPosRot,
207 | pk.VarInt(eid),
208 | pk.Short(delta[0]),
209 | pk.Short(delta[1]),
210 | pk.Short(delta[2]),
211 | pk.Angle(rot[0]),
212 | pk.Angle(rot[1]),
213 | pk.Boolean(onGround),
214 | )
215 | }
216 |
217 | func (c *Client) SendMoveEntitiesRot(eid int32, rot [2]int8, onGround bool) {
218 | c.SendPacket(
219 | packetid.ClientboundMoveEntityRot,
220 | pk.VarInt(eid),
221 | pk.Angle(rot[0]),
222 | pk.Angle(rot[1]),
223 | pk.Boolean(onGround),
224 | )
225 | }
226 |
227 | func (c *Client) SendRotateHead(eid int32, yaw int8) {
228 | c.SendPacket(
229 | packetid.ClientboundRotateHead,
230 | pk.VarInt(eid),
231 | pk.Angle(yaw),
232 | )
233 | }
234 |
235 | func (c *Client) SendTeleportEntity(eid int32, pos [3]float64, rot [2]int8, onGround bool) {
236 | c.SendPacket(
237 | packetid.ClientboundTeleportEntity,
238 | pk.VarInt(eid),
239 | pk.Double(pos[0]),
240 | pk.Double(pos[1]),
241 | pk.Double(pos[2]),
242 | pk.Angle(rot[0]),
243 | pk.Angle(rot[1]),
244 | pk.Boolean(onGround),
245 | )
246 | }
247 |
248 | var teleportCounter atomic.Int32
249 |
250 | func (c *Client) SendPlayerPosition(pos [3]float64, rot [2]float32) (teleportID int32) {
251 | teleportID = teleportCounter.Add(1)
252 | c.SendPacket(
253 | packetid.ClientboundPlayerPosition,
254 | pk.Double(pos[0]),
255 | pk.Double(pos[1]),
256 | pk.Double(pos[2]),
257 | pk.Float(rot[0]),
258 | pk.Float(rot[1]),
259 | pk.Byte(0), // Absolute
260 | pk.VarInt(teleportID),
261 | )
262 | return
263 | }
264 |
265 | func (c *Client) SendSetDefaultSpawnPosition(xyz [3]int32, angle float32) {
266 | c.SendPacket(
267 | packetid.ClientboundSetDefaultSpawnPosition,
268 | pk.Position{X: int(xyz[0]), Y: int(xyz[1]), Z: int(xyz[2])},
269 | pk.Float(angle),
270 | )
271 | return
272 | }
273 |
274 | func (c *Client) SendRemoveEntities(entityIDs []int32) {
275 | c.SendPacket(
276 | packetid.ClientboundRemoveEntities,
277 | pk.Array(*(*[]pk.VarInt)(unsafe.Pointer(&entityIDs))),
278 | )
279 | }
280 |
281 | func (c *Client) SendSystemChat(msg chat.Message, overlay bool) {
282 | c.SendPacket(packetid.ClientboundSystemChat, msg, pk.Boolean(overlay))
283 | }
284 |
285 | func (c *Client) SendPlayerChat(
286 | sender uuid.UUID,
287 | index int32,
288 | signature pk.Option[sign.Signature, *sign.Signature],
289 | body *sign.PackedMessageBody,
290 | unsignedContent *chat.Message,
291 | filter *sign.FilterMask,
292 | chatType *chat.Type,
293 | ) {
294 | c.SendPacket(
295 | packetid.ClientboundPlayerChat,
296 | pk.UUID(sender),
297 | pk.VarInt(index),
298 | signature,
299 | body,
300 | pk.OptionEncoder[*chat.Message]{
301 | Has: unsignedContent != nil,
302 | Val: unsignedContent,
303 | },
304 | filter,
305 | chatType,
306 | )
307 | }
308 |
309 | func (c *Client) SendSetChunkCacheCenter(chunkPos [2]int32) {
310 | c.SendPacket(
311 | packetid.ClientboundSetChunkCacheCenter,
312 | pk.VarInt(chunkPos[0]),
313 | pk.VarInt(chunkPos[1]),
314 | )
315 | }
316 |
317 | func (c *Client) ViewChunkLoad(pos level.ChunkPos, chunk *level.Chunk) {
318 | c.SendLevelChunkWithLight(pos, chunk)
319 | }
320 | func (c *Client) ViewChunkUnload(pos level.ChunkPos) { c.SendForgetLevelChunk(pos) }
321 | func (c *Client) ViewAddPlayer(p *world.Player) { c.SendAddPlayer(p) }
322 | func (c *Client) ViewRemoveEntities(entityIDs []int32) { c.SendRemoveEntities(entityIDs) }
323 | func (c *Client) ViewMoveEntityPos(id int32, delta [3]int16, onGround bool) {
324 | c.SendMoveEntitiesPos(id, delta, onGround)
325 | }
326 |
327 | func (c *Client) ViewMoveEntityPosAndRot(id int32, delta [3]int16, rot [2]int8, onGround bool) {
328 | c.SendMoveEntitiesPosAndRot(id, delta, rot, onGround)
329 | }
330 |
331 | func (c *Client) ViewMoveEntityRot(id int32, rot [2]int8, onGround bool) {
332 | c.SendMoveEntitiesRot(id, rot, onGround)
333 | }
334 |
335 | func (c *Client) ViewRotateHead(id int32, yaw int8) {
336 | c.SendRotateHead(id, yaw)
337 | }
338 |
339 | func (c *Client) ViewTeleportEntity(id int32, pos [3]float64, rot [2]int8, onGround bool) {
340 | c.SendTeleportEntity(id, pos, rot, onGround)
341 | }
342 |
--------------------------------------------------------------------------------
/game/chat.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package game
18 |
19 | import (
20 | "time"
21 |
22 | "go.uber.org/zap"
23 |
24 | "github.com/Tnze/go-mc/chat"
25 | "github.com/Tnze/go-mc/chat/sign"
26 | pk "github.com/Tnze/go-mc/net/packet"
27 | "github.com/Tnze/go-mc/registry"
28 | "github.com/Tnze/go-mc/server"
29 | "github.com/go-mc/server/client"
30 | )
31 |
32 | const MsgExpiresTime = time.Minute * 5
33 |
34 | type globalChat struct {
35 | log *zap.Logger
36 | players *playerList
37 | chatTypeCodec *registry.Registry[registry.ChatType]
38 | }
39 |
40 | func (g *globalChat) broadcastSystemChat(msg chat.Message, overlay bool) {
41 | g.log.Info(msg.String(), zap.Bool("overlay", overlay))
42 | g.players.pingList.Range(func(c server.PlayerListClient, _ server.PlayerSample) {
43 | c.(*client.Client).SendSystemChat(msg, overlay)
44 | })
45 | }
46 |
47 | func (g *globalChat) Handle(p pk.Packet, c *client.Client) error {
48 | var (
49 | message pk.String
50 | timestampLong pk.Long
51 | salt pk.Long
52 | signature pk.Option[sign.Signature, *sign.Signature]
53 | lastSeen sign.HistoryUpdate
54 | )
55 | err := p.Scan(
56 | &message,
57 | ×tampLong,
58 | &salt,
59 | &signature,
60 | &lastSeen,
61 | )
62 | if err != nil {
63 | return err
64 | }
65 |
66 | player := c.GetPlayer()
67 | timestamp := time.UnixMilli(int64(timestampLong))
68 | logger := g.log.With(
69 | zap.String("sender", player.Name),
70 | zap.Time("timestamp", timestamp),
71 | )
72 |
73 | if existInvalidCharacter(string(message)) {
74 | c.SendDisconnect(chat.TranslateMsg("multiplayer.disconnect.illegal_characters"))
75 | return nil
76 | }
77 |
78 | if !player.SetLastChatTimestamp(timestamp) {
79 | c.SendDisconnect(chat.TranslateMsg("multiplayer.disconnect.out_of_order_chat"))
80 | return nil
81 | }
82 |
83 | // TODO: check if the client disable chatting
84 | if false {
85 | c.SendSystemChat(chat.TranslateMsg("chat.disabled.options").SetColor(chat.Red), false)
86 | return nil
87 | }
88 |
89 | // verify message
90 | //var playerMsg sign.PlayerMessage
91 | ////if player.PubKey != nil {
92 | ////}
93 |
94 | if time.Since(timestamp) > MsgExpiresTime {
95 | logger.Warn("Player send expired message", zap.String("msg", string(message)))
96 | return nil
97 | }
98 | chatTypeID, decorator := g.chatTypeCodec.Find("minecraft:chat")
99 | chatType := chat.Type{
100 | ID: chatTypeID,
101 | SenderName: chat.Text(player.Name),
102 | TargetName: nil,
103 | }
104 | decorated := chatType.Decorate(chat.Text(string(message)), &decorator.Chat)
105 | logger.Info(decorated.String())
106 |
107 | g.players.pingList.Range(func(c server.PlayerListClient, _ server.PlayerSample) {
108 | c.(*client.Client).SendPlayerChat(
109 | player.UUID,
110 | 0,
111 | signature,
112 | &sign.PackedMessageBody{
113 | PlainMsg: string(message),
114 | Timestamp: timestamp,
115 | Salt: int64(salt),
116 | LastSeen: []sign.PackedSignature{},
117 | },
118 | nil,
119 | &sign.FilterMask{Type: 0},
120 | &chatType,
121 | )
122 | })
123 | return nil
124 | }
125 |
126 | func existInvalidCharacter(msg string) bool {
127 | for _, c := range msg {
128 | if c == '§' || c < ' ' || c == '\x7F' {
129 | return true
130 | }
131 | }
132 | return false
133 | }
134 |
--------------------------------------------------------------------------------
/game/config.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package game
18 |
19 | import (
20 | "time"
21 |
22 | "golang.org/x/time/rate"
23 | )
24 |
25 | type Config struct {
26 | MaxPlayers int `toml:"max-players"`
27 | ViewDistance int32 `toml:"view-distance"`
28 | ListenAddress string `toml:"listen-address"`
29 | MessageOfTheDay string `toml:"motd"`
30 | NetworkCompressionThreshold int `toml:"network-compression-threshold"`
31 | OnlineMode bool `toml:"online-mode"`
32 | LevelName string `toml:"level-name"`
33 | EnforceSecureProfile bool `toml:"enforce-secure-profile"`
34 |
35 | ChunkLoadingLimiter Limiter `toml:"chunk-loading-limiter"`
36 | PlayerChunkLoadingLimiter Limiter `toml:"player-chunk-loading-limiter"`
37 | }
38 |
39 | type Limiter struct {
40 | Every duration `toml:"every"`
41 | N int
42 | }
43 |
44 | // Limiter convert this to *rate.Limiter
45 | func (l *Limiter) Limiter() *rate.Limiter {
46 | return rate.NewLimiter(rate.Every(l.Every.Duration), l.N)
47 | }
48 |
49 | type duration struct {
50 | time.Duration
51 | }
52 |
53 | func (d *duration) UnmarshalText(text []byte) (err error) {
54 | d.Duration, err = time.ParseDuration(string(text))
55 | return
56 | }
57 |
--------------------------------------------------------------------------------
/game/game.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package game
18 |
19 | import (
20 | "compress/gzip"
21 | "context"
22 | "errors"
23 | "os"
24 | "path/filepath"
25 | "time"
26 |
27 | "github.com/google/uuid"
28 | "go.uber.org/zap"
29 |
30 | "github.com/Tnze/go-mc/chat"
31 | "github.com/Tnze/go-mc/data/packetid"
32 | "github.com/Tnze/go-mc/net"
33 | pk "github.com/Tnze/go-mc/net/packet"
34 | "github.com/Tnze/go-mc/save"
35 | "github.com/Tnze/go-mc/server"
36 | "github.com/Tnze/go-mc/yggdrasil/user"
37 | "github.com/go-mc/server/client"
38 | "github.com/go-mc/server/world"
39 | )
40 |
41 | type Game struct {
42 | log *zap.Logger
43 |
44 | config Config
45 | serverInfo *server.PingInfo
46 |
47 | playerProvider world.PlayerProvider
48 | overworld *world.World
49 |
50 | globalChat globalChat
51 | *playerList
52 | }
53 |
54 | func NewGame(log *zap.Logger, config Config, pingList *server.PlayerList, serverInfo *server.PingInfo) *Game {
55 | // providers
56 | overworld, err := createWorld(log, filepath.Join(".", config.LevelName), &config)
57 | if err != nil {
58 | log.Fatal("cannot load overworld", zap.Error(err))
59 | }
60 | playerProvider := world.NewPlayerProvider(filepath.Join(".", config.LevelName, "playerdata"))
61 |
62 | // keepalive
63 | keepAlive := server.NewKeepAlive()
64 | pl := playerList{pingList: pingList, keepAlive: keepAlive}
65 | keepAlive.AddPlayerDelayUpdateHandler(func(c server.KeepAliveClient, latency time.Duration) {
66 | pl.updateLatency(c.(*client.Client), latency)
67 | })
68 | go keepAlive.Run(context.TODO())
69 |
70 | return &Game{
71 | log: log.Named("game"),
72 |
73 | config: config,
74 | serverInfo: serverInfo,
75 |
76 | playerProvider: playerProvider,
77 | overworld: overworld,
78 |
79 | globalChat: globalChat{
80 | log: log.Named("chat"),
81 | players: &pl,
82 | chatTypeCodec: &world.NetworkCodec.ChatType,
83 | },
84 | playerList: &pl,
85 | }
86 | }
87 |
88 | func createWorld(logger *zap.Logger, path string, config *Config) (*world.World, error) {
89 | f, err := os.Open(filepath.Join(path, "level.dat"))
90 | if err != nil {
91 | return nil, err
92 | }
93 | r, err := gzip.NewReader(f)
94 | if err != nil {
95 | return nil, err
96 | }
97 | lv, err := save.ReadLevel(r)
98 | if err != nil {
99 | return nil, err
100 | }
101 | overworld := world.New(
102 | logger.Named("overworld"),
103 | world.NewProvider(filepath.Join(path, "region"), config.ChunkLoadingLimiter.Limiter()),
104 | world.Config{
105 | ViewDistance: config.ViewDistance,
106 | SpawnAngle: lv.Data.SpawnAngle,
107 | SpawnPosition: [3]int32{lv.Data.SpawnX, lv.Data.SpawnY, lv.Data.SpawnZ},
108 | },
109 | )
110 | return overworld, nil
111 | }
112 |
113 | // AcceptPlayer will be called in an independent goroutine when new player login
114 | func (g *Game) AcceptPlayer(name string, id uuid.UUID, profilePubKey *user.PublicKey, properties []user.Property, protocol int32, conn *net.Conn) {
115 | logger := g.log.With(
116 | zap.String("name", name),
117 | zap.String("uuid", id.String()),
118 | zap.Int32("protocol", protocol),
119 | )
120 |
121 | p, err := g.playerProvider.GetPlayer(name, id, profilePubKey, properties)
122 | if errors.Is(err, os.ErrNotExist) {
123 | p = &world.Player{
124 | Entity: world.Entity{
125 | EntityID: world.NewEntityID(),
126 | Position: [3]float64{48, 100, 35},
127 | Rotation: [2]float32{},
128 | },
129 | Name: name,
130 | UUID: id,
131 | PubKey: profilePubKey,
132 | Properties: properties,
133 | Gamemode: 1,
134 | ChunkPos: [3]int32{48 >> 4, 64 >> 4, 35 >> 4},
135 | EntitiesInView: make(map[int32]*world.Entity),
136 | ViewDistance: 10,
137 | }
138 | } else if err != nil {
139 | logger.Error("Read player data error", zap.Error(err))
140 | return
141 | }
142 | c := client.New(logger, conn, p)
143 |
144 | logger.Info("Player join", zap.Int32("eid", p.EntityID))
145 | defer logger.Info("Player left")
146 |
147 | c.SendLogin(g.overworld, p)
148 | c.SendServerData(g.serverInfo.Description(), g.serverInfo.FavIcon(), g.config.EnforceSecureProfile)
149 |
150 | joinMsg := chat.TranslateMsg("multiplayer.player.joined", chat.Text(p.Name)).SetColor(chat.Yellow)
151 | leftMsg := chat.TranslateMsg("multiplayer.player.left", chat.Text(p.Name)).SetColor(chat.Yellow)
152 | g.globalChat.broadcastSystemChat(joinMsg, false)
153 | defer g.globalChat.broadcastSystemChat(leftMsg, false)
154 | c.AddHandler(packetid.ServerboundChat, g.globalChat.Handle)
155 |
156 | g.playerList.addPlayer(c, p)
157 | defer g.playerList.removePlayer(c)
158 |
159 | c.SendPlayerPosition(p.Position, p.Rotation)
160 | g.overworld.AddPlayer(c, p, g.config.PlayerChunkLoadingLimiter.Limiter())
161 | defer g.overworld.RemovePlayer(c, p)
162 | c.SendPacket(packetid.ClientboundUpdateTags, pk.Array(defaultTags))
163 | c.SendSetDefaultSpawnPosition(g.overworld.SpawnPositionAndAngle())
164 |
165 | c.Start()
166 | }
167 |
--------------------------------------------------------------------------------
/game/playerlist.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package game
18 |
19 | import (
20 | "time"
21 |
22 | "github.com/Tnze/go-mc/data/packetid"
23 | pk "github.com/Tnze/go-mc/net/packet"
24 | "github.com/Tnze/go-mc/server"
25 | "github.com/go-mc/server/client"
26 | "github.com/go-mc/server/world"
27 | )
28 |
29 | type playerList struct {
30 | keepAlive *server.KeepAlive
31 | pingList *server.PlayerList
32 | }
33 |
34 | func (pl *playerList) addPlayer(c *client.Client, p *world.Player) {
35 | pl.pingList.ClientJoin(c, server.PlayerSample{
36 | Name: p.Name,
37 | ID: p.UUID,
38 | })
39 | pl.keepAlive.ClientJoin(c)
40 | c.AddHandler(packetid.ServerboundKeepAlive, keepAliveHandler(pl.keepAlive))
41 | players := make([]*world.Player, 0, pl.pingList.Len()+1)
42 | players = append(players, p)
43 | addPlayerAction := client.NewPlayerInfoAction(
44 | client.PlayerInfoAddPlayer,
45 | client.PlayerInfoUpdateListed,
46 | )
47 | pl.pingList.Range(func(c server.PlayerListClient, _ server.PlayerSample) {
48 | cc := c.(*client.Client)
49 | cc.SendPlayerInfoUpdate(addPlayerAction, []*world.Player{p})
50 | players = append(players, cc.GetPlayer())
51 | })
52 | c.SendPlayerInfoUpdate(addPlayerAction, players)
53 | }
54 |
55 | func (pl *playerList) updateLatency(c *client.Client, latency time.Duration) {
56 | updateLatencyAction := client.NewPlayerInfoAction(client.PlayerInfoUpdateLatency)
57 | p := c.GetPlayer()
58 | p.Inputs.Lock()
59 | p.Inputs.Latency = latency
60 | p.Inputs.Unlock()
61 | pl.pingList.Range(func(c server.PlayerListClient, _ server.PlayerSample) {
62 | c.(*client.Client).SendPlayerInfoUpdate(updateLatencyAction, []*world.Player{p})
63 | })
64 | }
65 |
66 | func (pl *playerList) removePlayer(c *client.Client) {
67 | pl.pingList.ClientLeft(c)
68 | pl.keepAlive.ClientLeft(c)
69 | p := c.GetPlayer()
70 | pl.pingList.Range(func(c server.PlayerListClient, _ server.PlayerSample) {
71 | c.(*client.Client).SendPlayerInfoRemove([]*world.Player{p})
72 | })
73 | }
74 |
75 | func keepAliveHandler(k *server.KeepAlive) client.PacketHandler {
76 | return func(p pk.Packet, c *client.Client) error {
77 | var req pk.Long
78 | if err := p.Scan(&req); err != nil {
79 | return err
80 | }
81 | k.ClientTick(c)
82 | return nil
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/game/tags.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package game
18 |
19 | import (
20 | "io"
21 |
22 | pk "github.com/Tnze/go-mc/net/packet"
23 | )
24 |
25 | type Tag[T ~int32 | ~int] struct {
26 | Name string
27 | Values map[string][]T
28 | }
29 |
30 | func (t Tag[T]) WriteTo(w io.Writer) (n int64, err error) {
31 | n1, err := pk.Identifier(t.Name).WriteTo(w)
32 | if err != nil {
33 | return n1, err
34 | }
35 | n2, err := pk.VarInt(len(t.Values)).WriteTo(w)
36 | if err != nil {
37 | return n1 + n2, err
38 | }
39 | for k, v := range t.Values {
40 | n3, err := pk.Identifier(k).WriteTo(w)
41 | n += n3
42 | if err != nil {
43 | return n + n1 + n2, err
44 | }
45 | n4, err := pk.VarInt(len(v)).WriteTo(w)
46 | n += n4
47 | if err != nil {
48 | return n + n1 + n2, err
49 | }
50 | for _, v := range v {
51 | n5, err := pk.VarInt(v).WriteTo(w)
52 | n += n5
53 | if err != nil {
54 | return n + n1 + n2, err
55 | }
56 | }
57 | }
58 | return n + n1 + n2, err
59 | }
60 |
61 | var defaultTags = []pk.FieldEncoder{
62 | Tag[int32]{
63 | Name: "minecraft:fluid",
64 | Values: map[string][]int32{
65 | "minecraft:water": {1, 2},
66 | "minecraft:lava": {3, 4},
67 | },
68 | },
69 | }
70 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-mc/server
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/BurntSushi/toml v1.2.1
7 | github.com/Tnze/go-mc v1.19.4-0.20230422160805-5f06fa651019
8 | github.com/google/uuid v1.3.0
9 | go.uber.org/zap v1.24.0
10 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29
11 | golang.org/x/time v0.3.0
12 | )
13 |
14 | require (
15 | github.com/pkg/errors v0.9.1 // indirect
16 | go.uber.org/atomic v1.10.0 // indirect
17 | go.uber.org/multierr v1.11.0 // indirect
18 | )
19 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
2 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
3 | github.com/Tnze/go-mc v1.19.4-0.20230422160805-5f06fa651019 h1:DPIP23tmLknSyXFyTD/4gaMhmwbkZHPnvkk85XoYNgM=
4 | github.com/Tnze/go-mc v1.19.4-0.20230422160805-5f06fa651019/go.mod h1:c1znJQglgqa1Jjs3Dr29woN/msguiJrlNtWXhKedh2U=
5 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
8 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
9 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
10 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
12 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
13 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
14 | go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
15 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
16 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
17 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
18 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
19 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
20 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
21 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
22 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
23 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
24 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
25 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package main
18 |
19 | import (
20 | "flag"
21 | "runtime/debug"
22 | "strings"
23 |
24 | "github.com/BurntSushi/toml"
25 | "go.uber.org/zap"
26 |
27 | "github.com/Tnze/go-mc/chat"
28 | "github.com/Tnze/go-mc/server"
29 | "github.com/go-mc/server/game"
30 | )
31 |
32 | var isDebug = flag.Bool("debug", false, "Enable debug log output")
33 |
34 | func main() {
35 | flag.Parse()
36 | // initialize log library
37 | var logger *zap.Logger
38 | if *isDebug {
39 | logger = unwrap(zap.NewDevelopment())
40 | } else {
41 | logger = unwrap(zap.NewProduction())
42 | }
43 | defer func(logger *zap.Logger) {
44 | if err := logger.Sync(); err != nil {
45 | panic(err)
46 | }
47 | }(logger)
48 |
49 | logger.Info("Server start")
50 | printBuildInfo(logger)
51 | defer logger.Info("Server exit")
52 |
53 | // load server config
54 | config, err := readConfig()
55 | if err != nil {
56 | logger.Error("Read config fail", zap.Error(err))
57 | return
58 | }
59 |
60 | // initialize player list and server status module, the two modules work together to show server Ping&List information
61 | playerList := server.NewPlayerList(config.MaxPlayers)
62 | serverInfo := server.NewPingInfo(
63 | "Go-MC "+server.ProtocolName,
64 | server.ProtocolVersion,
65 | chat.Text(config.MessageOfTheDay),
66 | nil,
67 | )
68 | if err != nil {
69 | logger.Error("Init server info system fail", zap.Error(err))
70 | return
71 | }
72 |
73 | s := server.Server{
74 | Logger: zap.NewStdLog(logger),
75 | ListPingHandler: struct {
76 | *server.PlayerList
77 | *server.PingInfo
78 | }{playerList, serverInfo},
79 | LoginHandler: &server.MojangLoginHandler{
80 | OnlineMode: config.OnlineMode,
81 | EnforceSecureProfile: config.EnforceSecureProfile,
82 | Threshold: config.NetworkCompressionThreshold,
83 | LoginChecker: playerList, // playerList implement LoginChecker interface to limit the maximum number of online players
84 | },
85 | GamePlay: game.NewGame(logger, config, playerList, serverInfo),
86 | }
87 | logger.Info("Start listening", zap.String("address", config.ListenAddress))
88 | err = s.Listen(config.ListenAddress)
89 | if err != nil {
90 | logger.Error("Server listening error", zap.Error(err))
91 | }
92 | }
93 |
94 | // printBuildInfo reading compile information of the binary program with runtime/debug package,and print it to log
95 | func printBuildInfo(logger *zap.Logger) {
96 | binaryInfo, _ := debug.ReadBuildInfo()
97 | settings := make(map[string]string)
98 | for _, v := range binaryInfo.Settings {
99 | settings[v.Key] = v.Value
100 | }
101 | logger.Debug("Build info", zap.Any("settings", settings))
102 | }
103 |
104 | // readConfig read server config from config file. Throw error when meet unknown setting
105 | func readConfig() (game.Config, error) {
106 | var c game.Config
107 | meta, err := toml.DecodeFile("config.toml", &c)
108 | if err != nil {
109 | return game.Config{}, err
110 | }
111 | if undecoded := meta.Undecoded(); len(undecoded) > 0 {
112 | var err errUnknownConfig
113 | for _, key := range undecoded {
114 | err = append(err, key.String())
115 | }
116 | return game.Config{}, err
117 | }
118 |
119 | return c, nil
120 | }
121 |
122 | type errUnknownConfig []string
123 |
124 | func (e errUnknownConfig) Error() string {
125 | return "unknown config keys: [" + strings.Join(e, ", ") + "]"
126 | }
127 |
128 | func unwrap[T any](v T, err error) T {
129 | if err != nil {
130 | panic(err)
131 | }
132 | return v
133 | }
134 |
--------------------------------------------------------------------------------
/world/RegistryCodec.nbt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/go-mc/server/6f503ef920251bb55c4a6ba4597d25ce0e231391/world/RegistryCodec.nbt
--------------------------------------------------------------------------------
/world/chat.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package world
18 |
19 | import "time"
20 |
21 | // SetLastChatTimestamp update the lastChatTimestamp and return true if new timestamp is newer than last one.
22 | // Otherwise, didn't update the lastChatTimestamp and return false.
23 | func (p *Player) SetLastChatTimestamp(t time.Time) bool {
24 | if p.lastChatTimestamp.Before(t) {
25 | p.lastChatTimestamp = t
26 | return true
27 | }
28 | return false
29 | }
30 |
31 | func (p *Player) GetPrevChatSignature() []byte { return p.lastChatSignature }
32 | func (p *Player) SetPrevChatSignature(sig []byte) { p.lastChatSignature = sig }
33 |
--------------------------------------------------------------------------------
/world/entity.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package world
18 |
19 | import (
20 | "math"
21 | "sync/atomic"
22 | )
23 |
24 | var entityCounter atomic.Int32
25 |
26 | func NewEntityID() int32 {
27 | return entityCounter.Add(1)
28 | }
29 |
30 | type Entity struct {
31 | EntityID int32
32 | Position
33 | Rotation
34 | OnGround
35 | pos0 Position
36 | rot0 Rotation
37 | }
38 |
39 | type (
40 | Position [3]float64
41 | Rotation [2]float32
42 | OnGround bool
43 | )
44 |
45 | func (e *Entity) getPoint() [2]float64 {
46 | return [2]float64{e.Position[0], e.Position[2]}
47 | }
48 |
49 | func (p *Position) IsValid() bool {
50 | return !math.IsNaN((*p)[0]) && !math.IsNaN((*p)[1]) && !math.IsNaN((*p)[2]) &&
51 | !math.IsInf((*p)[0], 0) && !math.IsInf((*p)[1], 0) && !math.IsInf((*p)[2], 0)
52 | }
53 |
--------------------------------------------------------------------------------
/world/entity/metadata.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package entity
18 |
19 | import (
20 | "io"
21 |
22 | pk "github.com/Tnze/go-mc/net/packet"
23 | )
24 |
25 | type MetadataSet []MetadataField
26 |
27 | type MetadataField struct {
28 | Index byte
29 | MetadataValue
30 | }
31 |
32 | func (m MetadataSet) WriteTo(w io.Writer) (n int64, err error) {
33 | var tmpN int64
34 | for _, v := range m {
35 | tmpN, err = pk.UnsignedByte(v.Index).WriteTo(w)
36 | n += tmpN
37 | if err != nil {
38 | return
39 | }
40 | tmpN, err = v.WriteTo(w)
41 | if err != nil {
42 | return
43 | }
44 | }
45 | tmpN, err = pk.UnsignedByte(0xFF).WriteTo(w)
46 | return n + tmpN, err
47 | }
48 |
49 | func (m *MetadataField) WriteTo(w io.Writer) (n int64, err error) {
50 | n1, err := pk.VarInt(m.MetadataValue.TypeID()).WriteTo(w)
51 | if err != nil {
52 | return n1, err
53 | }
54 | n2, err := m.MetadataValue.WriteTo(w)
55 | return n1 + n2, err
56 | }
57 |
58 | type MetadataValue interface {
59 | TypeID() int32
60 | pk.Field
61 | }
62 |
63 | type (
64 | Byte struct{ pk.Byte }
65 | // VarInt struct{ pk.VarInt }
66 | // Float struct{ pk.Float }
67 | // String struct{ pk.String }
68 | // Chat struct{ chat.Message }
69 | // OptionalChat struct {
70 | // Exist bool
71 | // Message chat.Message
72 | // }
73 | // Slot struct{}
74 | // Boolean struct{ pk.Boolean }
75 | // Rotation [3]pk.Float
76 | // Position struct{ pk.Position }
77 |
78 | Pose int32
79 | )
80 |
81 | func (b *Byte) TypeID() int32 { return 0 }
82 | func (p *Pose) TypeID() int32 { return 18 }
83 |
84 | const (
85 | Standing Pose = iota
86 | FallFlying
87 | Sleeping
88 | Swimming
89 | SpinAttack
90 | Crouching
91 | LongJumping
92 | Dying
93 | Croaking
94 | UsingTongue
95 | Roaring
96 | Sniffing
97 | Emerging
98 | Digging
99 | )
100 |
101 | func (p Pose) WriteTo(w io.Writer) (n int64, err error) { return pk.VarInt(p).WriteTo(w) }
102 | func (p *Pose) ReadFrom(r io.Reader) (n int64, err error) { return (*pk.VarInt)(p).ReadFrom(r) }
103 |
--------------------------------------------------------------------------------
/world/internal/bvh/bound.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package bvh
18 |
19 | import (
20 | "math"
21 |
22 | "golang.org/x/exp/constraints"
23 | )
24 |
25 | type AABB[I constraints.Signed | constraints.Float, V interface {
26 | Add(V) V
27 | Sub(V) V
28 | Max(V) V
29 | Min(V) V
30 | Less(V) bool
31 | More(V) bool
32 | Sum() I
33 | }] struct{ Upper, Lower V }
34 |
35 | func (aabb AABB[I, V]) WithIn(point V) bool {
36 | return aabb.Lower.Less(point) && aabb.Upper.More(point)
37 | }
38 |
39 | func (aabb AABB[I, V]) Touch(other AABB[I, V]) bool {
40 | return aabb.Lower.Less(other.Upper) && other.Lower.Less(aabb.Upper) &&
41 | aabb.Upper.More(other.Lower) && other.Upper.More(aabb.Lower)
42 | }
43 |
44 | func (aabb AABB[I, V]) Union(other AABB[I, V]) AABB[I, V] {
45 | return AABB[I, V]{Upper: aabb.Upper.Max(other.Upper), Lower: aabb.Lower.Min(other.Lower)}
46 | }
47 | func (aabb AABB[I, V]) Surface() I { return aabb.Upper.Sub(aabb.Lower).Sum() * 2 }
48 |
49 | type Sphere[I constraints.Float, V interface {
50 | Add(V) V
51 | Sub(V) V
52 | Mul(I) V
53 | Max(V) V
54 | Min(V) V
55 | Less(V) bool
56 | More(V) bool
57 | Norm() I
58 | Sum() I
59 | }] struct {
60 | Center V
61 | R I
62 | }
63 |
64 | func (s Sphere[I, V]) WithIn(point V) bool {
65 | return s.Center.Sub(point).Norm() < s.R
66 | }
67 |
68 | func (s Sphere[I, V]) Touch(other Sphere[I, V]) bool {
69 | return s.Center.Sub(other.Center).Norm() < s.R+other.R
70 | }
71 |
72 | func (s Sphere[I, V]) Union(other Sphere[I, V]) Sphere[I, V] {
73 | d := other.Center.Sub(s.Center).Norm()
74 | r1r2d := (s.R - other.R) / d
75 | return Sphere[I, V]{
76 | Center: s.Center.Mul(1 + r1r2d).Add(other.Center.Mul(1 - r1r2d)),
77 | R: d + s.R + other.R,
78 | }
79 | }
80 | func (s Sphere[I, V]) Surface() I { return 2 * math.Pi * s.R }
81 |
--------------------------------------------------------------------------------
/world/internal/bvh/bound_test.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package bvh
18 |
19 | import "testing"
20 |
21 | func TestAABB_WithIn(t *testing.T) {
22 | aabb := AABB[float64, Vec2[float64]]{
23 | Upper: Vec2[float64]{2, 2},
24 | Lower: Vec2[float64]{-1, -1},
25 | }
26 | if !aabb.WithIn(Vec2[float64]{0, 0}) {
27 | panic("(0, 0) should included")
28 | }
29 | if aabb.WithIn(Vec2[float64]{-2, -2}) {
30 | panic("(-2, -2) shouldn't included")
31 | }
32 |
33 | aabb2 := AABB[int, Vec3[int]]{
34 | Upper: Vec3[int]{1, 1, 1},
35 | Lower: Vec3[int]{-1, -1, -1},
36 | }
37 | if !aabb2.WithIn(Vec3[int]{0, 0, 0}) {
38 | panic("(0, 0, 0) should included")
39 | }
40 | if aabb2.WithIn(Vec3[int]{-2, -2, 0}) {
41 | panic("(-2, -2, 0) shouldn't included")
42 | }
43 |
44 | sphere := Sphere[float64, Vec2[float64]]{
45 | Center: Vec2[float64]{0, 0},
46 | R: 1.0,
47 | }
48 | if !sphere.WithIn(Vec2[float64]{0, 0}) {
49 | t.Errorf("(0,0) is in")
50 | }
51 | if sphere.WithIn(Vec2[float64]{1, 1}) {
52 | t.Errorf("(1,1) isn't in")
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/world/internal/bvh/bvh.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package bvh
18 |
19 | import (
20 | "container/heap"
21 | "fmt"
22 |
23 | "golang.org/x/exp/constraints"
24 | )
25 |
26 | type Node[I constraints.Float, B interface {
27 | Union(B) B
28 | Surface() I
29 | }, V any] struct {
30 | Box B
31 | Value V
32 | parent *Node[I, B, V]
33 | children [2]*Node[I, B, V]
34 | isLeaf bool
35 | }
36 |
37 | func (n *Node[I, B, V]) findAnotherChild(not *Node[I, B, V]) *Node[I, B, V] {
38 | if n.children[0] == not {
39 | return n.children[1]
40 | } else if n.children[1] == not {
41 | return n.children[0]
42 | }
43 | panic("unreachable, please make sure the 'not' is the n's child")
44 | }
45 |
46 | func (n *Node[I, B, V]) findChildPointer(child *Node[I, B, V]) **Node[I, B, V] {
47 | if n.children[0] == child {
48 | return &n.children[0]
49 | } else if n.children[1] == child {
50 | return &n.children[1]
51 | }
52 | panic("unreachable, please make sure the 'not' is the n's child")
53 | }
54 |
55 | func (n *Node[I, B, V]) each(test func(bound B) bool, foreach func(n *Node[I, B, V]) bool) bool {
56 | if n == nil {
57 | return true
58 | }
59 | if n.isLeaf {
60 | return !test(n.Box) || foreach(n)
61 | } else {
62 | return n.children[0].each(test, foreach) && n.children[1].each(test, foreach)
63 | }
64 | }
65 |
66 | type Tree[I constraints.Float, B interface {
67 | Union(B) B
68 | Surface() I
69 | }, V any] struct {
70 | root *Node[I, B, V]
71 | }
72 |
73 | func (t *Tree[I, B, V]) Insert(leaf B, value V) (n *Node[I, B, V]) {
74 | n = &Node[I, B, V]{
75 | Box: leaf,
76 | Value: value,
77 | parent: nil,
78 | children: [2]*Node[I, B, V]{nil, nil},
79 | isLeaf: true,
80 | }
81 | if t.root == nil {
82 | t.root = n
83 | return
84 | }
85 |
86 | // Stage 1: find the best sibling for the new leaf
87 | sibling := t.root
88 | bestCost := t.root.Box.Union(leaf).Surface()
89 | parentTo := &t.root // the parent's children pointer which point to the sibling
90 |
91 | var queue searchHeap[I, Node[I, B, V]]
92 | queue.Push(searchItem[I, Node[I, B, V]]{pointer: t.root, parentTo: &t.root})
93 |
94 | leafCost := leaf.Surface()
95 | for queue.Len() > 0 {
96 | p := heap.Pop(&queue).(searchItem[I, Node[I, B, V]])
97 | // determine if node p has the best cost
98 | mergeSurface := p.pointer.Box.Union(leaf).Surface()
99 | deltaCost := mergeSurface - p.pointer.Box.Surface()
100 | cost := p.inheritedCost + mergeSurface
101 | if cost <= bestCost {
102 | bestCost = cost
103 | sibling = p.pointer
104 | parentTo = p.parentTo
105 | }
106 | // determine if it is worthwhile to explore the children of node p.
107 | inheritedCost := p.inheritedCost + deltaCost // lower bound
108 | if !p.pointer.isLeaf && inheritedCost+leafCost < bestCost {
109 | heap.Push(&queue, searchItem[I, Node[I, B, V]]{
110 | pointer: p.pointer.children[0],
111 | parentTo: &p.pointer.children[0],
112 | inheritedCost: inheritedCost,
113 | })
114 | heap.Push(&queue, searchItem[I, Node[I, B, V]]{
115 | pointer: p.pointer.children[1],
116 | parentTo: &p.pointer.children[1],
117 | inheritedCost: inheritedCost,
118 | })
119 | }
120 | }
121 |
122 | // Stage 2: create a new parent
123 | *parentTo = &Node[I, B, V]{
124 | Box: sibling.Box.Union(leaf), // we will calculate in Stage3
125 | parent: sibling.parent,
126 | children: [2]*Node[I, B, V]{sibling, n},
127 | isLeaf: false,
128 | }
129 | n.parent = *parentTo
130 | sibling.parent = *parentTo
131 |
132 | // Stage 3: walk back up the tree refitting AABBs
133 | for p := *parentTo; p != nil; p = p.parent {
134 | p.Box = p.children[0].Box.Union(p.children[1].Box)
135 | t.rotate(p)
136 | }
137 | return
138 | }
139 |
140 | func (t *Tree[I, B, V]) Delete(n *Node[I, B, V]) V {
141 | if n.parent == nil {
142 | // n is the root
143 | t.root = nil
144 | return n.Value
145 | }
146 | sibling := n.parent.findAnotherChild(n)
147 | grand := n.parent.parent
148 | if grand == nil {
149 | // n's parent is root
150 | t.root = sibling
151 | sibling.parent = nil
152 | } else {
153 | p := grand.findChildPointer(n.parent)
154 | *p = sibling
155 | sibling.parent = grand
156 | for p := sibling.parent; p.parent != nil; p = p.parent {
157 | p.Box = p.children[0].Box.Union(p.children[1].Box)
158 | t.rotate(p)
159 | }
160 | }
161 | return n.Value
162 | }
163 |
164 | func (t *Tree[I, B, V]) rotate(n *Node[I, B, V]) {
165 | if n.isLeaf || n.parent == nil {
166 | return
167 | }
168 | // trying to swap n's sibling and children
169 | sibling := n.parent.findAnotherChild(n)
170 | current := n.Box.Surface()
171 | if n.children[1].Box.Union(sibling.Box).Surface() < current {
172 | // swap n.children[0] and sibling
173 | t1 := [2]*Node[I, B, V]{n, n.children[0]}
174 | t2 := [2]*Node[I, B, V]{sibling, n.children[1]}
175 | n.parent.children, n.children, n.children[0].parent, sibling.parent = t1, t2, n.parent, n
176 | n.Box = n.children[0].Box.Union(n.children[1].Box)
177 | } else if n.children[0].Box.Union(sibling.Box).Surface() < current {
178 | // swap n.children[1] and sibling
179 | t1 := [2]*Node[I, B, V]{n, n.children[1]}
180 | t2 := [2]*Node[I, B, V]{sibling, n.children[0]}
181 | n.parent.children, n.children, n.children[1].parent, sibling.parent = t1, t2, n.parent, n
182 | n.Box = n.children[0].Box.Union(n.children[1].Box)
183 | }
184 | }
185 |
186 | func (t *Tree[I, B, V]) Find(test func(bound B) bool, foreach func(n *Node[I, B, V]) bool) {
187 | t.root.each(test, foreach)
188 | }
189 |
190 | func (t Tree[I, B, V]) String() string {
191 | return t.root.String()
192 | }
193 |
194 | func (n *Node[I, B, V]) String() string {
195 | if n.isLeaf {
196 | return fmt.Sprint(n.Value)
197 | } else {
198 | return fmt.Sprintf("{%v, %v}", n.children[0], n.children[1])
199 | }
200 | }
201 |
202 | func TouchPoint[Vec any, B interface{ WithIn(Vec) bool }](point Vec) func(bound B) bool {
203 | return func(bound B) bool {
204 | return bound.WithIn(point)
205 | }
206 | }
207 |
208 | func TouchBound[B interface{ Touch(B) bool }](other B) func(bound B) bool {
209 | return func(bound B) bool {
210 | return bound.Touch(other)
211 | }
212 | }
213 |
214 | type (
215 | searchHeap[I constraints.Float, V any] []searchItem[I, V]
216 | searchItem[I constraints.Float, V any] struct {
217 | pointer *V
218 | parentTo **V
219 | inheritedCost I
220 | }
221 | )
222 |
223 | func (h searchHeap[I, V]) Len() int { return len(h) }
224 | func (h searchHeap[I, V]) Less(i, j int) bool { return h[i].inheritedCost < h[j].inheritedCost }
225 | func (h searchHeap[I, V]) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
226 | func (h *searchHeap[I, V]) Push(x any) { *h = append(*h, x.(searchItem[I, V])) }
227 | func (h *searchHeap[I, V]) Pop() any {
228 | old := *h
229 | n := len(old)
230 | x := old[n-1]
231 | *h = old[0 : n-1]
232 | return x
233 | }
234 |
--------------------------------------------------------------------------------
/world/internal/bvh/bvh_test.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package bvh
18 |
19 | import (
20 | "math/rand"
21 | "testing"
22 | )
23 |
24 | func TestTree2_Insert(t *testing.T) {
25 | aabbs := []AABB[float64, Vec2[float64]]{
26 | {Upper: Vec2[float64]{1, 1}, Lower: Vec2[float64]{0, 0}},
27 | {Upper: Vec2[float64]{2, 1}, Lower: Vec2[float64]{1, 0}},
28 | {Upper: Vec2[float64]{11, 1}, Lower: Vec2[float64]{10, 0}},
29 | {Upper: Vec2[float64]{12, 1}, Lower: Vec2[float64]{11, 0}},
30 | {Upper: Vec2[float64]{101, 1}, Lower: Vec2[float64]{100, 0}},
31 | {Upper: Vec2[float64]{102, 1}, Lower: Vec2[float64]{101, 0}},
32 | {Upper: Vec2[float64]{111, 1}, Lower: Vec2[float64]{110, 0}},
33 | {Upper: Vec2[float64]{112, 1}, Lower: Vec2[float64]{111, 0}},
34 | {Upper: Vec2[float64]{1, 1}, Lower: Vec2[float64]{-1, -1}},
35 | }
36 | var bvh Tree[float64, AABB[float64, Vec2[float64]], int]
37 | for i, aabb := range aabbs {
38 | bvh.Insert(aabb, i)
39 | // visualize
40 | t.Log(bvh)
41 | }
42 | bvh.Find(TouchPoint[Vec2[float64], AABB[float64, Vec2[float64]]](Vec2[float64]{0.5, 0.5}), func(n *Node[float64, AABB[float64, Vec2[float64]], int]) bool {
43 | t.Logf("find! %v", n.Value)
44 | return true
45 | })
46 | }
47 |
48 | func TestTree2_Find_vec(t *testing.T) {
49 | type Vec2d = Vec2[float64]
50 | type AABBVec2d = AABB[float64, Vec2d]
51 | type TreeAABBVec2di = Tree[float64, AABBVec2d, int]
52 |
53 | aabbs := []AABBVec2d{
54 | {Upper: Vec2d{2, 2}, Lower: Vec2d{-1, -1}},
55 | {Upper: Vec2d{2, 1}, Lower: Vec2d{-1, -2}},
56 | {Upper: Vec2d{1, 1}, Lower: Vec2d{-2, -2}},
57 | {Upper: Vec2d{1, 2}, Lower: Vec2d{-2, -1}},
58 | }
59 | var bvh TreeAABBVec2di
60 | for i, aabb := range aabbs {
61 | bvh.Insert(aabb, i)
62 | t.Log(bvh)
63 | }
64 | find := func(test func(bound AABBVec2d) bool) []int {
65 | var result []int
66 | bvh.Find(test, func(n *Node[float64, AABBVec2d, int]) bool {
67 | result = append(result, n.Value)
68 | return true
69 | })
70 | return result
71 | }
72 | t.Log(find(TouchPoint[Vec2d, AABBVec2d](Vec2d{0, 0})))
73 | t.Log(find(TouchPoint[Vec2d, AABBVec2d](Vec2d{1.5, 0})))
74 | t.Log(find(TouchPoint[Vec2d, AABBVec2d](Vec2d{1.5, 1.5})))
75 | t.Log(find(TouchPoint[Vec2d, AABBVec2d](Vec2d{-1.5, 0})))
76 |
77 | t.Log(find(TouchBound[AABBVec2d](AABBVec2d{Upper: Vec2d{1, 1}, Lower: Vec2d{-1, -1}})))
78 | t.Log(find(TouchBound[AABBVec2d](AABBVec2d{Upper: Vec2d{1, 1}, Lower: Vec2d{1.5, 1.5}})))
79 | t.Log(find(TouchBound[AABBVec2d](AABBVec2d{Upper: Vec2d{-1.5, 0.5}, Lower: Vec2d{-2.5, -0.5}})))
80 | }
81 |
82 | func BenchmarkTree_Insert(b *testing.B) {
83 | type Vec2d = Vec2[float64]
84 | type AABBVec2d = AABB[float64, Vec2d]
85 | type TreeAABBVec2da = Tree[float64, AABBVec2d, any]
86 |
87 | const size = 25
88 | // generate test cases
89 | aabbs := make([]AABBVec2d, b.N)
90 | poses := make([]Vec2d, b.N)
91 | for i := range aabbs {
92 | poses[i] = Vec2d{rand.Float64() * 1e4, rand.Float64() * 1e4}
93 | aabbs[i] = AABBVec2d{
94 | Upper: Vec2d{poses[i][0] + size, poses[i][0] + size},
95 | Lower: Vec2d{poses[i][0] - size, poses[i][0] - size},
96 | }
97 | }
98 | b.ResetTimer()
99 |
100 | var bvh TreeAABBVec2da
101 | for _, v := range aabbs {
102 | bvh.Insert(v, nil)
103 | }
104 | }
105 |
106 | func BenchmarkTree2_Find_random(b *testing.B) {
107 | type Vec2d = Vec2[float64]
108 | type AABBVec2d = AABB[float64, Vec2d]
109 | type TreeAABBVec2da = Tree[float64, AABBVec2d, any]
110 |
111 | const size = 25
112 | // generate test cases
113 | aabbs := make([]AABBVec2d, b.N)
114 | poses := make([]Vec2d, b.N)
115 | for i := range aabbs {
116 | poses[i] = Vec2d{rand.Float64() * 1e4, rand.Float64() * 1e4}
117 | aabbs[i] = AABBVec2d{
118 | Upper: Vec2d{poses[i][0] + size, poses[i][0] + size},
119 | Lower: Vec2d{poses[i][0] - size, poses[i][0] - size},
120 | }
121 | }
122 | var bvh TreeAABBVec2da
123 | for _, v := range aabbs {
124 | bvh.Insert(v, nil)
125 | }
126 | b.ResetTimer()
127 |
128 | for _, v := range poses {
129 | bvh.Find(TouchPoint[Vec2d, AABBVec2d](v), func(n *Node[float64, AABBVec2d, any]) bool { return true })
130 | }
131 | }
132 |
133 | func BenchmarkTree2_Delete_random(b *testing.B) {
134 | const size = 25
135 | // generate test cases
136 | aabbs := make([]AABB[float64, Vec2[float64]], b.N)
137 | poses := make([]Vec2[float64], b.N)
138 | nodes := make([]*Node[float64, AABB[float64, Vec2[float64]], any], b.N)
139 | for i := range aabbs {
140 | poses[i] = Vec2[float64]{rand.Float64() * 1e4, rand.Float64() * 1e4}
141 | aabbs[i] = AABB[float64, Vec2[float64]]{
142 | Upper: Vec2[float64]{poses[i][0] + size, poses[i][0] + size},
143 | Lower: Vec2[float64]{poses[i][0] - size, poses[i][0] - size},
144 | }
145 | }
146 | b.ResetTimer()
147 |
148 | var bvh Tree[float64, AABB[float64, Vec2[float64]], any]
149 | for i, v := range aabbs {
150 | nodes[i] = bvh.Insert(v, nil)
151 | }
152 |
153 | b.StopTimer()
154 | rand.Shuffle(b.N, func(i, j int) {
155 | nodes[i], nodes[j] = nodes[j], nodes[i]
156 | })
157 | b.StartTimer()
158 |
159 | for _, v := range nodes {
160 | bvh.Delete(v)
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/world/internal/bvh/vector.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package bvh
18 |
19 | import (
20 | "math"
21 |
22 | "golang.org/x/exp/constraints"
23 | )
24 |
25 | type Vec2[I constraints.Signed | constraints.Float] [2]I
26 |
27 | func (v Vec2[I]) Add(other Vec2[I]) Vec2[I] { return Vec2[I]{v[0] + other[0], v[1] + other[1]} }
28 | func (v Vec2[I]) Sub(other Vec2[I]) Vec2[I] { return Vec2[I]{v[0] - other[0], v[1] - other[1]} }
29 | func (v Vec2[I]) Mul(i I) Vec2[I] { return Vec2[I]{v[0] * i, v[1] * i} }
30 | func (v Vec2[I]) Max(other Vec2[I]) Vec2[I] { return Vec2[I]{max(v[0], other[0]), max(v[1], other[1])} }
31 | func (v Vec2[I]) Min(other Vec2[I]) Vec2[I] { return Vec2[I]{min(v[0], other[0]), min(v[1], other[1])} }
32 | func (v Vec2[I]) Less(other Vec2[I]) bool { return v[0] < other[0] && v[1] < other[1] }
33 | func (v Vec2[I]) More(other Vec2[I]) bool { return v[0] > other[0] && v[1] > other[1] }
34 | func (v Vec2[I]) Norm() float64 { return sqrt(v[0]*v[0] + v[1]*v[1]) }
35 | func (v Vec2[I]) Sum() I { return v[0] + v[1] }
36 |
37 | type Vec3[I constraints.Signed | constraints.Float] [3]I
38 |
39 | func (v Vec3[I]) Add(other Vec3[I]) Vec3[I] {
40 | return Vec3[I]{v[0] + other[0], v[1] + other[1], v[2] + other[2]}
41 | }
42 |
43 | func (v Vec3[I]) Sub(other Vec3[I]) Vec3[I] {
44 | return Vec3[I]{v[0] - other[0], v[1] - other[1], v[2] - other[2]}
45 | }
46 | func (v Vec3[I]) Mul(i I) Vec3[I] { return Vec3[I]{v[0] * i, v[1] * i, v[2] * i} }
47 | func (v Vec3[I]) Max(other Vec3[I]) Vec3[I] {
48 | return Vec3[I]{max(v[0], other[0]), max(v[1], other[1]), max(v[2], other[2])}
49 | }
50 |
51 | func (v Vec3[I]) Min(other Vec3[I]) Vec3[I] {
52 | return Vec3[I]{min(v[0], other[0]), min(v[1], other[1]), min(v[2], other[2])}
53 | }
54 | func (v Vec3[I]) Less(other Vec3[I]) bool { return v[0] < other[0] && v[1] < other[1] }
55 | func (v Vec3[I]) More(other Vec3[I]) bool { return v[0] > other[0] && v[1] > other[1] }
56 | func (v Vec3[I]) Norm() float64 { return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]) }
57 | func (v Vec3[I]) Sum() I { return v[0] + v[1] }
58 |
59 | func max[T constraints.Ordered](a, b T) T {
60 | if a > b {
61 | return a
62 | }
63 | return b
64 | }
65 |
66 | func min[T constraints.Ordered](a, b T) T {
67 | if a < b {
68 | return a
69 | }
70 | return b
71 | }
72 |
73 | func sqrt[T constraints.Signed | constraints.Float](v T) float64 {
74 | return math.Sqrt(float64(v))
75 | }
76 |
--------------------------------------------------------------------------------
/world/loader.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package world
18 |
19 | import (
20 | "math"
21 | "sort"
22 |
23 | "golang.org/x/time/rate"
24 | )
25 |
26 | // loader takes part in chunk loading,each loader contains a position 'pos' and a radius 'r'
27 | // chunks pointed by the position, and the radius of loader will be load。
28 | type loader struct {
29 | loaderSource
30 | loaded map[[2]int32]struct{}
31 | loadQueue [][2]int32
32 | unloadQueue [][2]int32
33 | limiter *rate.Limiter
34 | }
35 |
36 | type loaderSource interface {
37 | chunkPosition() [2]int32
38 | chunkRadius() int32
39 | }
40 |
41 | func newLoader(source loaderSource, limiter *rate.Limiter) (l *loader) {
42 | l = &loader{
43 | loaderSource: source,
44 | loaded: make(map[[2]int32]struct{}),
45 | limiter: limiter,
46 | }
47 | l.calcLoadingQueue()
48 | return
49 | }
50 |
51 | // calcLoadingQueue calculate the chunks which loader point.
52 | // The result is stored in l.loadQueue and the previous will be removed.
53 | func (l *loader) calcLoadingQueue() {
54 | l.loadQueue = l.loadQueue[:0]
55 | for _, v := range loadList[:radiusIdx[l.chunkRadius()]] {
56 | pos := l.chunkPosition()
57 | pos[0], pos[1] = pos[0]+v[0], pos[1]+v[1]
58 | if _, ok := l.loaded[pos]; !ok {
59 | l.loadQueue = append(l.loadQueue, pos)
60 | }
61 | }
62 | }
63 |
64 | // calcUnusedChunks calculate the chunks the loader wants to remove.
65 | // Behaviour is same with calcLoadingQueue.
66 | func (l *loader) calcUnusedChunks() {
67 | l.unloadQueue = l.unloadQueue[:0]
68 | for chunk := range l.loaded {
69 | player := l.chunkPosition()
70 | r := l.chunkRadius()
71 | if distance2i([2]int32{chunk[0] - player[0], chunk[1] - player[1]}) > float64(r) {
72 | l.unloadQueue = append(l.unloadQueue, chunk)
73 | }
74 | }
75 | }
76 |
77 | // loadList is chunks in a certain distance of (0, 0), order by Euclidean distance
78 | // the more forward the chunk is, the closer it to (0, 0)
79 | var loadList [][2]int32
80 |
81 | // radiusIdx[i] is the count of chunks in loadList and the distance of i
82 | var radiusIdx []int
83 |
84 | func init() {
85 | const maxR int32 = 32
86 |
87 | // calculate loadList
88 | for x := -maxR; x <= maxR; x++ {
89 | for z := -maxR; z <= maxR; z++ {
90 | pos := [2]int32{x, z}
91 | if distance2i(pos) < float64(maxR) {
92 | loadList = append(loadList, pos)
93 | }
94 | }
95 | }
96 | sort.Slice(loadList, func(i, j int) bool {
97 | return distance2i(loadList[i]) < distance2i(loadList[j])
98 | })
99 |
100 | // calculate radiusIdx
101 | radiusIdx = make([]int, maxR+1)
102 | for i, v := range loadList {
103 | r := int32(math.Ceil(distance2i(v)))
104 | if r > maxR {
105 | break
106 | }
107 | radiusIdx[r] = i
108 | }
109 | }
110 |
111 | // distance calculates the Euclidean distance that a position to the origin point
112 | func distance2i(pos [2]int32) float64 {
113 | return math.Sqrt(float64(pos[0]*pos[0]) + float64(pos[1]*pos[1]))
114 | }
115 |
--------------------------------------------------------------------------------
/world/player.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package world
18 |
19 | import (
20 | "io"
21 | "sync"
22 | "time"
23 |
24 | "github.com/google/uuid"
25 |
26 | pk "github.com/Tnze/go-mc/net/packet"
27 | "github.com/Tnze/go-mc/yggdrasil/user"
28 | )
29 |
30 | func (i *ClientInfo) ReadFrom(r io.Reader) (n int64, err error) {
31 | return pk.Tuple{
32 | (*pk.String)(&i.Locale),
33 | (*pk.Byte)(&i.ViewDistance),
34 | (*pk.VarInt)(&i.ChatMode),
35 | (*pk.Boolean)(&i.ChatColors),
36 | (*pk.UnsignedByte)(&i.DisplayedSkinParts),
37 | (*pk.VarInt)(&i.MainHand),
38 | (*pk.Boolean)(&i.EnableTextFiltering),
39 | (*pk.Boolean)(&i.AllowServerListings),
40 | }.ReadFrom(r)
41 | }
42 |
43 | type Player struct {
44 | Entity
45 | Name string
46 | UUID uuid.UUID
47 | PubKey *user.PublicKey
48 | Properties []user.Property
49 | Latency time.Duration
50 |
51 | lastChatTimestamp time.Time
52 | lastChatSignature []byte
53 |
54 | ChunkPos [3]int32
55 | ViewDistance int32
56 |
57 | Gamemode int32
58 | EntitiesInView map[int32]*Entity
59 | view *playerViewNode
60 | teleport *TeleportRequest
61 |
62 | Inputs Inputs
63 | }
64 |
65 | func (p *Player) chunkPosition() [2]int32 { return [2]int32{p.ChunkPos[0], p.ChunkPos[2]} }
66 | func (p *Player) chunkRadius() int32 { return p.ViewDistance }
67 |
68 | // getView calculate the visual range enclosure with Position and ViewDistance of a player.
69 | func (p *Player) getView() aabb3d {
70 | viewDistance := float64(p.ViewDistance) * 16 // the unit of ViewDistance is 1 Chunk(16 Block)
71 | return aabb3d{
72 | Upper: vec3d{p.Position[0] + viewDistance, p.Position[1] + viewDistance, p.Position[2] + viewDistance},
73 | Lower: vec3d{p.Position[0] - viewDistance, p.Position[1] - viewDistance, p.Position[2] - viewDistance},
74 | }
75 | }
76 |
77 | type TeleportRequest struct {
78 | ID int32
79 | Position
80 | Rotation
81 | }
82 |
83 | type Inputs struct {
84 | sync.Mutex
85 | ClientInfo
86 | Position
87 | Rotation
88 | OnGround
89 | Latency time.Duration
90 | TeleportID int32
91 | }
92 |
93 | type ClientInfo struct {
94 | Locale string
95 | ViewDistance int8
96 | ChatMode int32
97 | ChatColors bool
98 | DisplayedSkinParts byte
99 | MainHand int32
100 | EnableTextFiltering bool
101 | AllowServerListings bool
102 | }
103 |
--------------------------------------------------------------------------------
/world/provider.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package world
18 |
19 | import (
20 | "compress/gzip"
21 | "errors"
22 | "fmt"
23 | "io/fs"
24 | "os"
25 | "path/filepath"
26 |
27 | "github.com/google/uuid"
28 | "golang.org/x/time/rate"
29 |
30 | "github.com/Tnze/go-mc/level"
31 | "github.com/Tnze/go-mc/save"
32 | "github.com/Tnze/go-mc/save/region"
33 | "github.com/Tnze/go-mc/yggdrasil/user"
34 | )
35 |
36 | // ChunkProvider implements chunk storage
37 | type ChunkProvider struct {
38 | dir string
39 | limiter *rate.Limiter
40 | }
41 |
42 | func NewProvider(dir string, limiter *rate.Limiter) ChunkProvider {
43 | return ChunkProvider{dir: dir, limiter: limiter}
44 | }
45 |
46 | var ErrReachRateLimit = errors.New("reach rate limit")
47 |
48 | func (p *ChunkProvider) GetChunk(pos [2]int32) (c *level.Chunk, errRet error) {
49 | if !p.limiter.Allow() {
50 | return nil, ErrReachRateLimit
51 | }
52 | r, err := p.getRegion(region.At(int(pos[0]), int(pos[1])))
53 | if err != nil {
54 | return nil, fmt.Errorf("open region fail: %w", err)
55 | }
56 | defer func(r *region.Region) {
57 | err2 := r.Close()
58 | if errRet == nil && err2 != nil {
59 | errRet = fmt.Errorf("close region fail: %w", err2)
60 | }
61 | }(r)
62 |
63 | x, z := region.In(int(pos[0]), int(pos[1]))
64 | if !r.ExistSector(x, z) {
65 | return nil, errChunkNotExist
66 | }
67 |
68 | data, err := r.ReadSector(x, z)
69 | if err != nil {
70 | return nil, fmt.Errorf("read sector fail: %w", err)
71 | }
72 |
73 | var chunk save.Chunk
74 | if err := chunk.Load(data); err != nil {
75 | return nil, fmt.Errorf("parse chunk data fail: %w", err)
76 | }
77 |
78 | c, err = level.ChunkFromSave(&chunk)
79 | if err != nil {
80 | return nil, fmt.Errorf("load chunk data fail: %w", err)
81 | }
82 | return c, nil
83 | }
84 |
85 | func (p *ChunkProvider) getRegion(rx, rz int) (*region.Region, error) {
86 | filename := fmt.Sprintf("r.%d.%d.mca", rx, rz)
87 | path := filepath.Join(p.dir, filename)
88 | r, err := region.Open(path)
89 | if errors.Is(err, fs.ErrNotExist) {
90 | r, err = region.Create(path)
91 | }
92 | return r, err
93 | }
94 |
95 | func (p *ChunkProvider) PutChunk(pos [2]int32, c *level.Chunk) (err error) {
96 | //var chunk save.Chunk
97 | //err = level.ChunkToSave(c, &chunk)
98 | //if err != nil {
99 | // return fmt.Errorf("encode chunk data fail: %w", err)
100 | //}
101 | //
102 | //data, err := chunk.Data(1)
103 | //if err != nil {
104 | // return fmt.Errorf("record chunk data fail: %w", err)
105 | //}
106 | //
107 | //r, err := p.getRegion(region.At(int(pos[0]), int(pos[1])))
108 | //if err != nil {
109 | // return fmt.Errorf("open region fail: %w", err)
110 | //}
111 | //defer func(r *region.Region) {
112 | // err2 := r.Close()
113 | // if err == nil && err2 != nil {
114 | // err = fmt.Errorf("open region fail: %w", err)
115 | // }
116 | //}(r)
117 | //
118 | //x, z := region.In(int(pos[0]), int(pos[1]))
119 | //err = r.WriteSector(x, z, data)
120 | //if err != nil {
121 | // return fmt.Errorf("write sector fail: %w", err)
122 | //}
123 |
124 | return nil
125 | }
126 |
127 | var errChunkNotExist = errors.New("ErrChunkNotExist")
128 |
129 | type PlayerProvider struct {
130 | dir string
131 | }
132 |
133 | func NewPlayerProvider(dir string) PlayerProvider {
134 | return PlayerProvider{dir: dir}
135 | }
136 |
137 | func (p *PlayerProvider) GetPlayer(name string, id uuid.UUID, pubKey *user.PublicKey, properties []user.Property) (player *Player, errRet error) {
138 | f, err := os.Open(filepath.Join(p.dir, id.String()+".dat"))
139 | if err != nil {
140 | return nil, err
141 | }
142 | defer func(f *os.File) {
143 | err2 := f.Close()
144 | if errRet == nil && err2 != nil {
145 | errRet = fmt.Errorf("close player data fail: %w", err2)
146 | }
147 | }(f)
148 | r, err := gzip.NewReader(f)
149 | if err != nil {
150 | return nil, fmt.Errorf("open gzip reader fail: %w", err)
151 | }
152 | data, err := save.ReadPlayerData(r)
153 | if err != nil {
154 | return nil, fmt.Errorf("read player data fail: %w", err)
155 | }
156 | if err := r.Close(); err != nil {
157 | return nil, fmt.Errorf("close gzip reader fail: %w", err)
158 | }
159 | player = &Player{
160 | Entity: Entity{
161 | EntityID: NewEntityID(),
162 | Position: data.Pos,
163 | Rotation: data.Rotation,
164 | },
165 | Name: name,
166 | UUID: id,
167 | PubKey: pubKey,
168 | Properties: properties,
169 | ChunkPos: [3]int32{
170 | int32(data.Pos[0]) >> 5,
171 | int32(data.Pos[1]) >> 5,
172 | int32(data.Pos[2]) >> 5,
173 | },
174 | Gamemode: data.PlayerGameType,
175 | EntitiesInView: make(map[int32]*Entity),
176 | ViewDistance: 10,
177 | }
178 | return
179 | }
180 |
--------------------------------------------------------------------------------
/world/registrycodec.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package world
18 |
19 | import (
20 | "bytes"
21 | _ "embed"
22 |
23 | "github.com/Tnze/go-mc/registry"
24 |
25 | "github.com/Tnze/go-mc/nbt"
26 | )
27 |
28 | //go:embed RegistryCodec.nbt
29 | var networkCodecBytes []byte
30 | var NetworkCodec registry.NetworkCodec
31 |
32 | func init() {
33 | r := bytes.NewReader(networkCodecBytes)
34 | d := nbt.NewDecoder(r)
35 | _, err := d.Decode(&NetworkCodec)
36 | if err != nil {
37 | panic(err)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/world/tick.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package world
18 |
19 | import (
20 | "math"
21 | "time"
22 |
23 | "go.uber.org/zap"
24 |
25 | "github.com/Tnze/go-mc/chat"
26 | "github.com/go-mc/server/world/internal/bvh"
27 | )
28 |
29 | func (w *World) tickLoop() {
30 | var n uint
31 | for range time.Tick(time.Microsecond * 20) {
32 | w.tick(n)
33 | n++
34 | }
35 | }
36 |
37 | func (w *World) tick(n uint) {
38 | w.tickLock.Lock()
39 | defer w.tickLock.Unlock()
40 |
41 | if n%8 == 0 {
42 | w.subtickChunkLoad()
43 | }
44 | w.subtickUpdatePlayers()
45 | w.subtickUpdateEntities()
46 | }
47 |
48 | func (w *World) subtickChunkLoad() {
49 | for c, p := range w.players {
50 | x := int32(p.Position[0]) >> 4
51 | y := int32(p.Position[1]) >> 4
52 | z := int32(p.Position[2]) >> 4
53 | if newChunkPos := [3]int32{x, y, z}; newChunkPos != p.ChunkPos {
54 | p.ChunkPos = newChunkPos
55 | c.SendSetChunkCacheCenter([2]int32{x, z})
56 | }
57 | }
58 | // because of the random traversal order of w.loaders, every loader has the same opportunity, so it's relatively fair.
59 | LoadChunk:
60 | for viewer, loader := range w.loaders {
61 | loader.calcLoadingQueue()
62 | for _, pos := range loader.loadQueue {
63 | if !loader.limiter.Allow() { // We reach the player limit. Skip
64 | break
65 | }
66 | if _, ok := w.chunks[pos]; !ok {
67 | if !w.loadChunk(pos) {
68 | break LoadChunk // We reach the global limit. skip
69 | }
70 | }
71 | loader.loaded[pos] = struct{}{}
72 | lc := w.chunks[pos]
73 | lc.AddViewer(viewer)
74 | lc.Lock()
75 | viewer.ViewChunkLoad(pos, lc.Chunk)
76 | lc.Unlock()
77 | }
78 | }
79 | for viewer, loader := range w.loaders {
80 | loader.calcUnusedChunks()
81 | for _, pos := range loader.unloadQueue {
82 | delete(loader.loaded, pos)
83 | if !w.chunks[pos].RemoveViewer(viewer) {
84 | w.log.Panic("viewer is not found in the loaded chunk")
85 | }
86 | viewer.ViewChunkUnload(pos)
87 | }
88 | }
89 | var unloadQueue [][2]int32
90 | for pos, chunk := range w.chunks {
91 | if len(chunk.viewers) == 0 {
92 | unloadQueue = append(unloadQueue, pos)
93 | }
94 | }
95 | for i := range unloadQueue {
96 | w.unloadChunk(unloadQueue[i])
97 | }
98 | }
99 |
100 | func (w *World) subtickUpdatePlayers() {
101 | for c, p := range w.players {
102 | if !p.Inputs.TryLock() {
103 | continue
104 | }
105 | inputs := &p.Inputs
106 | // update the range of visual.
107 | if p.ViewDistance != int32(inputs.ViewDistance) {
108 | p.ViewDistance = int32(inputs.ViewDistance)
109 | p.view = w.playerViews.Insert(p.getView(), w.playerViews.Delete(p.view))
110 | }
111 | // delete entities that not in range from entities lists of each player.
112 | for id, e := range p.EntitiesInView {
113 | if !p.view.Box.WithIn(vec3d(e.Position)) {
114 | delete(p.EntitiesInView, id) // it should be safe to delete element from a map being traversed.
115 | p.view.Value.ViewRemoveEntities([]int32{id})
116 | }
117 | }
118 | if p.teleport != nil {
119 | if inputs.TeleportID == p.teleport.ID {
120 | p.pos0 = p.teleport.Position
121 | p.rot0 = p.teleport.Rotation
122 | p.teleport = nil
123 | }
124 | } else {
125 | delta := [3]float64{
126 | inputs.Position[0] - p.Position[0],
127 | inputs.Position[1] - p.Position[1],
128 | inputs.Position[2] - p.Position[2],
129 | }
130 | distance := math.Sqrt(delta[0]*delta[0] + delta[1]*delta[1] + delta[2]*delta[2])
131 | if distance > 100 {
132 | // You moved too quickly :( (Hacking?)
133 | teleportID := c.SendPlayerPosition(p.Position, p.Rotation)
134 | p.teleport = &TeleportRequest{
135 | ID: teleportID,
136 | Position: p.Position,
137 | Rotation: p.Rotation,
138 | }
139 | } else if inputs.Position.IsValid() {
140 | p.pos0 = inputs.Position
141 | p.rot0 = inputs.Rotation
142 | p.OnGround = inputs.OnGround
143 | } else {
144 | w.log.Info("Player move invalid",
145 | zap.Float64("x", inputs.Position[0]),
146 | zap.Float64("y", inputs.Position[1]),
147 | zap.Float64("z", inputs.Position[2]),
148 | )
149 | c.SendDisconnect(chat.TranslateMsg("multiplayer.disconnect.invalid_player_movement"))
150 | }
151 | }
152 | p.Inputs.Unlock()
153 | }
154 | }
155 |
156 | func (w *World) subtickUpdateEntities() {
157 | // TODO: entity list should be traversed here, but players are the only entities now.
158 | for _, e := range w.players {
159 | // sending Update Entity Position pack to every player who can see it, when it moves.
160 | var delta [3]int16
161 | var rot [2]int8
162 | if e.Position != e.pos0 { // TODO: send Teleport Entity pack instead when moving distance is greater than 8.
163 | delta = [3]int16{
164 | int16((e.pos0[0] - e.Position[0]) * 32 * 128),
165 | int16((e.pos0[1] - e.Position[1]) * 32 * 128),
166 | int16((e.pos0[2] - e.Position[2]) * 32 * 128),
167 | }
168 | }
169 | if e.Rotation != e.rot0 {
170 | rot = [2]int8{
171 | int8(e.rot0[0] * 256 / 360),
172 | int8(e.rot0[1] * 256 / 360),
173 | }
174 | }
175 | cond := bvh.TouchPoint[vec3d, aabb3d](vec3d(e.Position))
176 | w.playerViews.Find(cond,
177 | func(n *playerViewNode) bool {
178 | if n.Value.Player == e {
179 | return true // don't send the player self to the player
180 | }
181 | // check if the current entity is in range of player visual. if so, moving data will be forwarded.
182 | if _, ok := n.Value.EntitiesInView[e.EntityID]; !ok {
183 | // add the entity to the entity list of the player
184 | n.Value.ViewAddPlayer(e)
185 | n.Value.EntitiesInView[e.EntityID] = &e.Entity
186 | }
187 | return true
188 | },
189 | )
190 | var sendMove func(v EntityViewer)
191 | switch {
192 | case e.Position != e.pos0 && e.Rotation != e.rot0:
193 | sendMove = func(v EntityViewer) {
194 | v.ViewMoveEntityPosAndRot(e.EntityID, delta, rot, bool(e.OnGround))
195 | v.ViewRotateHead(e.EntityID, rot[0])
196 | }
197 | case e.Position != e.pos0:
198 | sendMove = func(v EntityViewer) {
199 | v.ViewMoveEntityPos(e.EntityID, delta, bool(e.OnGround))
200 | }
201 | case e.Rotation != e.rot0:
202 | sendMove = func(v EntityViewer) {
203 | v.ViewMoveEntityRot(e.EntityID, rot, bool(e.OnGround))
204 | v.ViewRotateHead(e.EntityID, rot[0])
205 | }
206 | default:
207 | continue
208 | }
209 | e.Position = e.pos0
210 | e.Rotation = e.rot0
211 | w.playerViews.Find(cond,
212 | func(n *playerViewNode) bool {
213 | if n.Value.Player == e {
214 | return true // not sending self movements to player self.
215 | }
216 | // check if the current entity is in the player visual entities list. if so, moving data will be forwarded.
217 | if _, ok := n.Value.EntitiesInView[e.EntityID]; ok {
218 | sendMove(n.Value.EntityViewer)
219 | } else {
220 | // or the entity will be add to the entities list of the player
221 | // TODO: deal with the situation that the entity is not a player
222 | n.Value.ViewAddPlayer(e)
223 | n.Value.EntitiesInView[e.EntityID] = &e.Entity
224 | }
225 | return true
226 | },
227 | )
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/world/viewer.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package world
18 |
19 | import (
20 | "github.com/Tnze/go-mc/chat"
21 | "github.com/Tnze/go-mc/level"
22 | )
23 |
24 | type Client interface {
25 | ChunkViewer
26 | EntityViewer
27 | SendDisconnect(reason chat.Message)
28 | SendPlayerPosition(pos [3]float64, rot [2]float32) (teleportID int32)
29 | SendSetChunkCacheCenter(chunkPos [2]int32)
30 | }
31 |
32 | type ChunkViewer interface {
33 | ViewChunkLoad(pos level.ChunkPos, c *level.Chunk)
34 | ViewChunkUnload(pos level.ChunkPos)
35 | }
36 |
37 | type EntityViewer interface {
38 | ViewAddPlayer(p *Player)
39 | ViewRemoveEntities(entityIDs []int32)
40 | ViewMoveEntityPos(id int32, delta [3]int16, onGround bool)
41 | ViewMoveEntityPosAndRot(id int32, delta [3]int16, rot [2]int8, onGround bool)
42 | ViewMoveEntityRot(id int32, rot [2]int8, onGround bool)
43 | ViewRotateHead(id int32, yaw int8)
44 | ViewTeleportEntity(id int32, pos [3]float64, rot [2]int8, onGround bool)
45 | }
46 |
--------------------------------------------------------------------------------
/world/world.go:
--------------------------------------------------------------------------------
1 | // This file is part of go-mc/server project.
2 | // Copyright (C) 2023. Tnze
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published
6 | // by the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package world
18 |
19 | import (
20 | "errors"
21 | "sync"
22 |
23 | "go.uber.org/zap"
24 | "golang.org/x/time/rate"
25 |
26 | "github.com/Tnze/go-mc/level"
27 | "github.com/Tnze/go-mc/level/block"
28 | "github.com/go-mc/server/world/internal/bvh"
29 | )
30 |
31 | type World struct {
32 | log *zap.Logger
33 | config Config
34 | chunkProvider ChunkProvider
35 |
36 | chunks map[[2]int32]*LoadedChunk
37 | loaders map[ChunkViewer]*loader
38 | tickLock sync.Mutex
39 |
40 | // playerViews is a BVH tree,storing the visual range collision boxes of each player.
41 | // the data structure is used to determine quickly which players to send notify when entity moves.
42 | playerViews playerViewTree
43 | players map[Client]*Player
44 | }
45 |
46 | type Config struct {
47 | ViewDistance int32
48 | SpawnAngle float32
49 | SpawnPosition [3]int32
50 | }
51 |
52 | type playerView struct {
53 | EntityViewer
54 | *Player
55 | }
56 |
57 | type (
58 | vec3d = bvh.Vec3[float64]
59 | aabb3d = bvh.AABB[float64, vec3d]
60 | playerViewNode = bvh.Node[float64, aabb3d, playerView]
61 | playerViewTree = bvh.Tree[float64, aabb3d, playerView]
62 | )
63 |
64 | func New(logger *zap.Logger, provider ChunkProvider, config Config) (w *World) {
65 | w = &World{
66 | log: logger,
67 | config: config,
68 | chunks: make(map[[2]int32]*LoadedChunk),
69 | loaders: make(map[ChunkViewer]*loader),
70 | players: make(map[Client]*Player),
71 | chunkProvider: provider,
72 | }
73 | go w.tickLoop()
74 | return
75 | }
76 |
77 | func (w *World) Name() string {
78 | return "minecraft:overworld"
79 | }
80 |
81 | func (w *World) SpawnPositionAndAngle() ([3]int32, float32) {
82 | return w.config.SpawnPosition, w.config.SpawnAngle
83 | }
84 |
85 | func (w *World) HashedSeed() [8]byte {
86 | return [8]byte{}
87 | }
88 |
89 | func (w *World) AddPlayer(c Client, p *Player, limiter *rate.Limiter) {
90 | w.tickLock.Lock()
91 | defer w.tickLock.Unlock()
92 | w.loaders[c] = newLoader(p, limiter)
93 | w.players[c] = p
94 | p.view = w.playerViews.Insert(p.getView(), playerView{c, p})
95 | }
96 |
97 | func (w *World) RemovePlayer(c Client, p *Player) {
98 | w.tickLock.Lock()
99 | defer w.tickLock.Unlock()
100 | w.log.Debug("Remove Player",
101 | zap.Int("loader count", len(w.loaders[c].loaded)),
102 | zap.Int("world count", len(w.chunks)),
103 | )
104 | // delete the player from all chunks which load the player.
105 | for pos := range w.loaders[c].loaded {
106 | if !w.chunks[pos].RemoveViewer(c) {
107 | w.log.Panic("viewer is not found in the loaded chunk")
108 | }
109 | }
110 | delete(w.loaders, c)
111 | delete(w.players, c)
112 | // delete the player from entity system.
113 | w.playerViews.Delete(p.view)
114 | w.playerViews.Find(
115 | bvh.TouchPoint[vec3d, aabb3d](bvh.Vec3[float64](p.Position)),
116 | func(n *playerViewNode) bool {
117 | n.Value.ViewRemoveEntities([]int32{p.EntityID})
118 | delete(n.Value.EntitiesInView, p.EntityID)
119 | return true
120 | },
121 | )
122 | }
123 |
124 | func (w *World) loadChunk(pos [2]int32) bool {
125 | logger := w.log.With(zap.Int32("x", pos[0]), zap.Int32("z", pos[1]))
126 | logger.Debug("Loading chunk")
127 | c, err := w.chunkProvider.GetChunk(pos)
128 | if err != nil {
129 | if errors.Is(err, errChunkNotExist) {
130 | logger.Debug("Generate chunk")
131 | // TODO: because there is no chunk generator,generate an empty chunk and mark it as generated
132 | c = level.EmptyChunk(24)
133 | stone := block.ToStateID[block.Stone{}]
134 | for s := range c.Sections {
135 | for i := 0; i < 16*16*16; i++ {
136 | c.Sections[s].SetBlock(i, stone)
137 | }
138 | }
139 | c.Status = level.StatusFull
140 | } else if !errors.Is(err, ErrReachRateLimit) {
141 | logger.Error("GetChunk error", zap.Error(err))
142 | return false
143 | }
144 | }
145 | w.chunks[pos] = &LoadedChunk{Chunk: c}
146 | return true
147 | }
148 |
149 | func (w *World) unloadChunk(pos [2]int32) {
150 | logger := w.log.With(zap.Int32("x", pos[0]), zap.Int32("z", pos[1]))
151 | logger.Debug("Unloading chunk")
152 | c, ok := w.chunks[pos]
153 | if !ok {
154 | logger.Panic("Unloading an non-exist chunk")
155 | }
156 | // notify all viewers who are watching the chunk to unload the chunk
157 | for _, viewer := range c.viewers {
158 | viewer.ViewChunkUnload(pos)
159 | }
160 | // move the chunk to provider and save
161 | err := w.chunkProvider.PutChunk(pos, c.Chunk)
162 | if err != nil {
163 | logger.Error("Store chunk data error", zap.Error(err))
164 | }
165 | delete(w.chunks, pos)
166 | }
167 |
168 | type LoadedChunk struct {
169 | sync.Mutex
170 | viewers []ChunkViewer
171 | *level.Chunk
172 | }
173 |
174 | func (lc *LoadedChunk) AddViewer(v ChunkViewer) {
175 | lc.Lock()
176 | defer lc.Unlock()
177 | for _, v2 := range lc.viewers {
178 | if v2 == v {
179 | panic("append an exist viewer")
180 | }
181 | }
182 | lc.viewers = append(lc.viewers, v)
183 | }
184 |
185 | func (lc *LoadedChunk) RemoveViewer(v ChunkViewer) bool {
186 | lc.Lock()
187 | defer lc.Unlock()
188 | for i, v2 := range lc.viewers {
189 | if v2 == v {
190 | last := len(lc.viewers) - 1
191 | lc.viewers[i] = lc.viewers[last]
192 | lc.viewers = lc.viewers[:last]
193 | return true
194 | }
195 | }
196 | return false
197 | }
198 |
--------------------------------------------------------------------------------