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