├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets.go ├── audio.go ├── audio └── audio.go ├── block.go ├── blockentity.go ├── blockimpl.go ├── blockliquid.go ├── blockreg.go ├── blocks.go ├── blocksign.go ├── blocksimple.go ├── blockskull.go ├── blocksnapshot.go ├── blocktint.go ├── blocktree.go ├── chat.go ├── chunk.go ├── chunkbuilder.go ├── client.go ├── clientdebug.go ├── clouds.go ├── cmd ├── protocol_builder │ ├── builder.go │ ├── condition.go │ ├── helpers.go │ ├── reading.go │ └── writing.go └── steven │ └── steven.go ├── config.go ├── console.go ├── console ├── command.go ├── command_test.go ├── conf.go ├── console.go ├── cvar.go ├── doc.go ├── types.go └── types_test.go ├── desktop.go ├── encoding ├── json │ └── json.go └── nbt │ └── nbt.go ├── entities.go ├── entity.go ├── entityhelditem.go ├── entitymodels.go ├── entityparts.go ├── entitysys ├── desc.go ├── system.go └── system_test.go ├── entitysystems.go ├── fakegen.go ├── format ├── build.go ├── components.go ├── legacy.go ├── reflectmap.go ├── string.go └── type_string.go ├── handler.go ├── inventory.go ├── item.go ├── itemlist.go ├── key.go ├── model.go ├── modelrender.go ├── modelui.go ├── mojangverify.go ├── native └── native.go ├── network.go ├── playerlist.go ├── pluginmessage.go ├── pluginmessage_proto.go ├── position.go ├── progress.go ├── protocol ├── connection.go ├── connection_test.go ├── crypto.go ├── handshaking.go ├── handshaking_proto.go ├── io.go ├── item.go ├── login.go ├── login_clientbound.go ├── login_clientbound_proto.go ├── login_serverbound.go ├── login_serverbound_proto.go ├── metadata.go ├── mojang │ ├── auth.go │ ├── login.go │ └── profile.go ├── play_clientbound.go ├── play_clientbound_proto.go ├── play_serverbound.go ├── play_serverbound_proto.go ├── state_string.go ├── status.go ├── status_clientbound.go ├── status_clientbound_proto.go ├── status_serverbound.go ├── status_serverbound_proto.go └── types.go ├── render ├── atlas │ └── atlas.go ├── builder │ ├── builder.go │ └── reflect.go ├── camera.go ├── chunk.go ├── chunkshader.go ├── clouds.go ├── debug.go ├── font.go ├── gl │ ├── array.go │ ├── buffer.go │ ├── framebuffer.go │ ├── gl.go │ ├── shader.go │ ├── texture.go │ └── types.go ├── glsl │ └── glsl.go ├── line.go ├── lineshader.go ├── model.go ├── pool.go ├── position.go ├── queue.go ├── render.go ├── shader.go ├── shader_snippets.go ├── shader_sun.go ├── skin.go ├── texture.go ├── trans.go ├── ui.go └── uishader.go ├── resource ├── builtin │ ├── assets.go │ ├── files │ │ └── assets │ │ │ ├── minecraft │ │ │ ├── texts │ │ │ │ └── splashes.txt │ │ │ └── textures │ │ │ │ └── font │ │ │ │ └── ascii.png │ │ │ └── steven │ │ │ ├── blockstates │ │ │ └── missing_block.json │ │ │ ├── logo │ │ │ ├── logo.txt │ │ │ └── textures.txt │ │ │ ├── models │ │ │ └── block │ │ │ │ └── missing_block.json │ │ │ └── textures │ │ │ ├── environment │ │ │ └── clouds.png │ │ │ └── gui │ │ │ └── cog.png │ └── gen.go ├── locale │ └── locale.go └── resource.go ├── resourcepacks.go ├── steven.go ├── type ├── bit │ ├── map.go │ ├── map_test.go │ ├── set.go │ └── set_test.go ├── direction │ └── direction.go ├── nibble │ └── nibble.go └── vmath │ ├── aabb.go │ ├── frustum.go │ └── vector.go ├── ui.go ├── ui ├── button.go ├── container.go ├── formatted.go ├── image.go ├── input.go ├── model.go ├── scene │ └── scene.go ├── text.go ├── textbox.go ├── types.go └── ui.go ├── uieditserver.go ├── uigamemenu.go ├── uilogin.go ├── uilogo.go ├── uioptions.go ├── uiresourcelist.go ├── uirespawn.go ├── uiscreen.go ├── uiserverlist.go ├── uivolume.go ├── world.go └── world └── biome └── biome.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | /.idea 3 | /bin 4 | /jni/armeabi 5 | /libs 6 | /obj 7 | 8 | 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide Lines 2 | 3 | ## Code Submission 4 | 5 | * All code must be run through `go fmt` before commiting 6 | * There isn't a strict line length limit but try to not to have crazy long lines (120ish chars) 7 | * A single commit is prefered to multiple unless they are clearly seperate 8 | (touching two different packages for example) 9 | * Commit messages should be in the form `packagename: commit message` where the commit message 10 | should be in lower case (exluding acronyms like HTTP and JSON) 11 | * For the commit messages the root package `github.com/thinkofdeath/steven` is referred to as `steven` 12 | * In the case where multiple packages are touched and it doesn't make sense to split the commits 13 | the package names can be seperated in a list via a comma e.g. `render,steven: commit message` 14 | * The code must work on Linux, Windows and Mac unless its in a platform specific area file 15 | (with a build tag or file extension) 16 | 17 | ## Issue Submission 18 | 19 | * steven-log.txt should be submitted with all bug reports. 20 | * Logs/Crash logs should be wrapped in \`\`\` to keep it readable. 21 | * In the case the log is too large use https://gist.github.com 22 | * The title of the issue should clearly state the issue and the package (if known) that the issue occurs in 23 | e.g. `steven: missing model for red flowers` 24 | * Please include details about your operating system and graphics card in the issue to help with 25 | tracking down issues -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Steven 2 | 3 | # Not actively maintained anymore 4 | 5 | A work in progress Minecraft client in Go. 6 | Don't expect it to go anywhere, just doing this for fun. 7 | 8 | ## Images 9 | 10 | ![Steven](http://i.imgur.com/VVnmbkV.png) 11 | 12 | ![Steven with Nether style background and a resource pack](https://i.imgur.com/QjBb1UT.png) 13 | 14 | ![Steven's server list after disconnecting from a server](https://i.imgur.com/JRFXt0e.png) 15 | 16 | ## Building 17 | 18 | To build: 19 | 20 | ``` 21 | export GOPATH=your/install/directory 22 | go get github.com/thinkofdeath/steven/cmd/steven 23 | ``` 24 | 25 | To update, run `go get` with the `-u` option. 26 | 27 | Requires `csfml` libraries and headers to build. 28 | 29 | ## What works 30 | 31 | * Connecting to servers 32 | * Online mode 33 | * Rendering most blocks 34 | * Block model support 35 | 36 | ## What doesn't work 37 | 38 | * 99% of Minecraft's features 39 | 40 | ## Chat 41 | 42 | I generally am on the `irc.spi.gt` irc network in the `#think` channel. 43 | Feel free to pop in to say hi, [Webchat can be found here](https://irc.spi.gt/iris/?channels=think) 44 | 45 | ## Builds 46 | 47 | **Latest:** 48 | 49 | | # | Linux | OS X | Windows | 50 | |:---:|:---------------:|:----:|:-----------------:| 51 | | x64 | [linux_amd64.zip](http://ci.thinkofdeath.uk/guestAuth/repository/download/Steven_Client/.lastSuccessful/linux_amd64.zip) | [Issue](https://github.com/thinkofdeath/steven/issues/27) | [windows_amd64.zip](http://ci.thinkofdeath.uk/guestAuth/repository/download/Steven_Client/.lastSuccessful/windows_amd64.zip) | 52 | | x32 | [Issue](https://github.com/thinkofdeath/steven/issues/28) | [Issue](https://github.com/thinkofdeath/steven/issues/27) | [windows_386.zip](http://ci.thinkofdeath.uk/guestAuth/repository/download/Steven_Client/.lastSuccessful/windows_386.zip) | 53 | 54 | Older builds can be found [here](http://ci.thinkofdeath.co.uk/viewType.html?buildTypeId=Steven_Client&guest=1) 55 | 56 | ## Running 57 | 58 | ### Via the Offical Minecraft launcher 59 | 60 | ![Profile example](http://i.imgur.com/NBMGhPL.png) 61 | 62 | You need to create a new profile (or edit an existing one) on the Minecraft 63 | launcher and modify the profile to look like the above but replace the path 64 | to steven with the location you built it at or downloaded it too and change the 65 | `server` parameter to the target server. Currently only works in online mode 66 | (with no plans for offline mode currently). If the `server` parameter isn't 67 | passed then a server list will be displayed. 68 | 69 | ### Standalone 70 | 71 | Just running steven via a double click (Windows) or `./steven` (everything else) 72 | will bring up a login screen followed by a server list which you can select a server 73 | from. 74 | 75 | Providing a username, uuid and access token via the command line as followed: 76 | `--username --uuid --accessToken ` 77 | will skip the login screen and jump straight to the server list. Providing a 78 | server address via `--server :` will skip the server list and 79 | connect straight to the server. As it currently stands providing all the arguments 80 | allows for the client to parallelise connecting to the server and loading the 81 | textures/models/other assets as a 'quick connect'. 82 | 83 | -------------------------------------------------------------------------------- /blockentity.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/go-gl/mathgl/mgl32" 21 | "github.com/thinkofdeath/steven/encoding/nbt" 22 | "github.com/thinkofdeath/steven/entitysys" 23 | "github.com/thinkofdeath/steven/render" 24 | ) 25 | 26 | func init() { 27 | addSystem(entitysys.Add, esSkullAdd) 28 | addSystem(entitysys.Remove, esSkullRemove) 29 | addSystem(entitysys.Add, esSignAdd) 30 | addSystem(entitysys.Tick, lightBlockModel) 31 | } 32 | 33 | // updates the Colors of the model to fake lighting 34 | func lightBlockModel(m interface { 35 | Model() *render.Model 36 | }, be BlockEntity) { 37 | model := m.Model() 38 | bp := be.Position() 39 | bx, by, bz := bp.X, bp.Y, bp.Z 40 | bl := float32(chunkMap.BlockLight(bx, by, bz)) 41 | sl := float32(chunkMap.SkyLight(bx, by, bz)) 42 | model.BlockLight, model.SkyLight = bl, sl 43 | } 44 | 45 | // BlockEntity is the interface for which all block entities 46 | // must implement 47 | type BlockEntity interface { 48 | BlockComponent 49 | } 50 | 51 | type blockComponent struct { 52 | Location Position 53 | } 54 | 55 | func (bc *blockComponent) Position() Position { 56 | return bc.Location 57 | } 58 | 59 | func (bc *blockComponent) SetPosition(p Position) { 60 | bc.Location = p 61 | } 62 | 63 | // BlockComponent is a component that defines the location 64 | // of an entity when attached to a block. 65 | type BlockComponent interface { 66 | Position() Position 67 | SetPosition(p Position) 68 | } 69 | 70 | // BlockNBTComponent is implemented by block entities that 71 | // load information from nbt. 72 | type BlockNBTComponent interface { 73 | Deserilize(tag *nbt.Compound) 74 | CanHandleAction(action int) bool 75 | } 76 | 77 | type blockBreakComponent struct { 78 | blockComponent 79 | stage int 80 | model *render.Model 81 | } 82 | 83 | func (b *blockBreakComponent) SetStage(stage int) { b.stage = stage } 84 | func (b *blockBreakComponent) Stage() int { return b.stage } 85 | func (b *blockBreakComponent) Model() *render.Model { return b.model } 86 | func (b *blockBreakComponent) Update() { 87 | if b.model != nil { 88 | b.model.Free() 89 | } 90 | bounds := chunkMap.Block(b.Location.X, b.Location.Y, b.Location.Z).CollisionBounds() 91 | tex := render.GetTexture(fmt.Sprintf("blocks/destroy_stage_%d", b.stage)) 92 | 93 | var verts []*render.ModelVertex 94 | for _, bo := range bounds { 95 | // Slightly bigger than the block to prevent clipping 96 | bo = bo.Grow(0.01, 0.01, 0.01) 97 | verts = appendBox(verts, 98 | bo.Min.X(), bo.Min.Y(), bo.Min.Z(), 99 | bo.Max.X()-bo.Min.X(), bo.Max.Y()-bo.Min.Y(), bo.Max.Z()-bo.Min.Z(), 100 | [6]render.TextureInfo{ 101 | tex, tex, tex, tex, tex, tex, 102 | }) 103 | } 104 | b.model = render.NewModel([][]*render.ModelVertex{verts}) 105 | 106 | b.model.Matrix[0] = mgl32.Translate3D( 107 | float32(b.Location.X), 108 | -float32(b.Location.Y), 109 | float32(b.Location.Z), 110 | ) 111 | } 112 | 113 | // BlockBreakComponent is implemented by the block break animation 114 | // entity 115 | type BlockBreakComponent interface { 116 | SetStage(stage int) 117 | Stage() int 118 | Update() 119 | } 120 | 121 | func newBlockBreakEntity() BlockEntity { 122 | type blockBreak struct { 123 | networkComponent 124 | blockBreakComponent 125 | } 126 | b := &blockBreak{} 127 | return b 128 | } 129 | -------------------------------------------------------------------------------- /blockreg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | func init() { 18 | registerBlockType("default", &blockSimple{}) 19 | registerBlockType("stone", &blockStone{}) 20 | registerBlockType("grass", &blockGrass{}) 21 | registerBlockType("planks", &blockPlanks{}) 22 | registerBlockType("sapling", &blockSapling{}) 23 | registerBlockType("liquid", &blockLiquid{}) 24 | registerBlockType("log", &blockLog{}) 25 | registerBlockType("leaves", &blockLeaves{}) 26 | registerBlockType("sponge", &blockSponge{}) 27 | registerBlockType("dispenser", &blockDispenser{}) 28 | registerBlockType("bed", &blockBed{}) 29 | registerBlockType("rail", &blockRail{}) 30 | registerBlockType("poweredRail", &blockPoweredRail{}) 31 | registerBlockType("piston", &blockPiston{}) 32 | registerBlockType("pistonHead", &blockPistonHead{}) 33 | registerBlockType("tallGrass", &blockTallGrass{}) 34 | registerBlockType("deadBush", &blockDeadBush{}) 35 | registerBlockType("wool", &blockWool{}) 36 | registerBlockType("stairs", &blockStairs{}) 37 | registerBlockType("door", &blockDoor{}) 38 | registerBlockType("fence", &blockFence{}) 39 | registerBlockType("fenceGate", &blockFenceGate{}) 40 | registerBlockType("stainedGlass", &blockStainedGlass{}) 41 | registerBlockType("stainedGlassPane", &blockStainedGlassPane{}) 42 | registerBlockType("stainedClay", &blockStainedClay{}) 43 | registerBlockType("connectable", &blockConnectable{}) 44 | registerBlockType("vines", &blockVines{}) 45 | registerBlockType("wall", &blockWall{}) 46 | registerBlockType("slab", &blockSlab{}) 47 | registerBlockType("slabDouble", &blockSlabDouble{}) 48 | registerBlockType("slabDoubleSeamless", &blockSlabDoubleSeamless{}) 49 | registerBlockType("carpet", &blockCarpet{}) 50 | registerBlockType("torch", &blockTorch{}) 51 | registerBlockType("wallSign", &blockWallSign{}) 52 | registerBlockType("floorSign", &blockFloorSign{}) 53 | registerBlockType("skull", &blockSkull{}) 54 | registerBlockType("crop", &blockCrop{}) 55 | registerBlockType("farmland", &blockFarmland{}) 56 | registerBlockType("portal", &blockPortal{}) 57 | registerBlockType("lilypad", &blockLilypad{}) 58 | registerBlockType("stonebrick", &blockStoneBrick{}) 59 | registerBlockType("yellowFlower", &blockYellowFlower{}) 60 | registerBlockType("redFlower", &blockRedFlower{}) 61 | registerBlockType("fire", &blockFire{}) 62 | registerBlockType("redstone", &blockRedstone{}) 63 | registerBlockType("cactus", &blockCactus{}) 64 | registerBlockType("quartzBlock", &blockQuartzBlock{}) 65 | registerBlockType("snowLayer", &blockSnowLayer{}) 66 | registerBlockType("doublePlant", &blockDoublePlant{}) 67 | } 68 | -------------------------------------------------------------------------------- /blocksimple.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "reflect" 19 | "strconv" 20 | ) 21 | 22 | type blockSimple struct { 23 | baseBlock 24 | } 25 | 26 | func (b *blockSimple) load(tag reflect.StructTag) { 27 | getBool := wrapTagBool(tag) 28 | b.cullAgainst = getBool("cullAgainst", true) 29 | b.collidable = getBool("collidable", true) 30 | b.renderable = getBool("renderable", true) 31 | hardness, err := strconv.ParseFloat(tag.Get("hardness"), 64) 32 | if err == nil { 33 | b.hardness = hardness 34 | } 35 | b.translucent = getBool("translucent", false) 36 | } 37 | 38 | func (b *blockSimple) toData() int { 39 | if b == b.Parent.Base { 40 | return 0 41 | } 42 | return -1 43 | } 44 | -------------------------------------------------------------------------------- /blocktint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "fmt" 19 | "image" 20 | icolor "image/color" 21 | "image/png" 22 | 23 | "github.com/thinkofdeath/steven/console" 24 | "github.com/thinkofdeath/steven/resource" 25 | ) 26 | 27 | var ( 28 | grassBiomeColors *image.NRGBA 29 | foliageBiomeColors *image.NRGBA 30 | ) 31 | 32 | func loadBiomes() { 33 | grassBiomeColors = loadBiomeColors("grass") 34 | foliageBiomeColors = loadBiomeColors("foliage") 35 | } 36 | 37 | func loadBiomeColors(name string) *image.NRGBA { 38 | f, err := resource.Open("minecraft", fmt.Sprintf("textures/colormap/%s.png", name)) 39 | if err != nil { 40 | console.Text("loading biome colors: %s", err) 41 | return image.NewNRGBA(image.Rect(0, 0, 256, 256)) 42 | } 43 | defer f.Close() 44 | img, err := png.Decode(f) 45 | if err != nil { 46 | panic(err) 47 | } 48 | i, ok := img.(*image.NRGBA) 49 | if !ok { 50 | i = convertImage(img) 51 | } 52 | return i 53 | } 54 | 55 | func convertImage(img image.Image) *image.NRGBA { 56 | width, height := img.Bounds().Dx(), img.Bounds().Dy() 57 | pix := make([]byte, width*height*4) 58 | for y := 0; y < height; y++ { 59 | for x := 0; x < width; x++ { 60 | col := icolor.NRGBAModel.Convert(img.At(x, y)).(icolor.NRGBA) 61 | index := (y*width + x) * 4 62 | pix[index] = col.R 63 | pix[index+1] = col.G 64 | pix[index+2] = col.B 65 | pix[index+3] = col.A 66 | } 67 | } 68 | i := image.NewNRGBA(img.Bounds()) 69 | i.Pix = pix 70 | return i 71 | } 72 | -------------------------------------------------------------------------------- /clouds.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "math" 19 | 20 | "github.com/go-gl/mathgl/mgl32" 21 | "github.com/thinkofdeath/steven/render" 22 | ) 23 | 24 | var ( 25 | sunModel *render.Model 26 | moonModel *render.Model 27 | moonPhase int = -1 28 | ) 29 | 30 | func tickClouds(delta float64) { 31 | if Client != nil && Client.WorldType != wtOverworld { 32 | render.DrawClouds = false 33 | if sunModel != nil { 34 | sunModel.Free() 35 | moonModel.Free() 36 | sunModel = nil 37 | moonModel = nil 38 | } 39 | return 40 | } 41 | render.DrawClouds = true 42 | if Client == nil && Client.entity != nil { 43 | return 44 | } 45 | if sunModel == nil { 46 | genSunModel() 47 | } 48 | 49 | phase := int((Client.WorldAge / 24000) % 8) 50 | if phase != moonPhase { 51 | moonPhase = phase 52 | genSunModel() 53 | } 54 | 55 | x, y, z := Client.entity.Position() 56 | 57 | time := Client.WorldTime / 12000 58 | ox := math.Cos(time*math.Pi) * 300 59 | oy := math.Sin(time*math.Pi) * 300 60 | 61 | sunModel.Matrix[0] = mgl32.Translate3D( 62 | float32(x+ox), 63 | -float32(y+oy), 64 | float32(z), 65 | ).Mul4(mgl32.Rotate3DZ(-float32(time * math.Pi)).Mat4()) 66 | 67 | moonModel.Matrix[0] = mgl32.Translate3D( 68 | float32(x-ox), 69 | -float32(y-oy), 70 | float32(z), 71 | ).Mul4(mgl32.Rotate3DZ(math.Pi - float32(time*math.Pi)).Mat4()) 72 | } 73 | 74 | func genSunModel() { 75 | if sunModel != nil { 76 | sunModel.Free() 77 | moonModel.Free() 78 | sunModel = nil 79 | moonModel = nil 80 | } 81 | const size = 50 82 | tex := render.GetTexture("environment/sun") 83 | sunModel = render.NewModelCollection([][]*render.ModelVertex{ 84 | { 85 | {X: 0, Y: -size, Z: -size, TextureX: 0, TextureY: 1, Texture: tex, R: 255, G: 255, B: 255, A: 255}, 86 | {X: 0, Y: size, Z: -size, TextureX: 0, TextureY: 0, Texture: tex, R: 255, G: 255, B: 255, A: 255}, 87 | {X: 0, Y: -size, Z: size, TextureX: 1, TextureY: 1, Texture: tex, R: 255, G: 255, B: 255, A: 255}, 88 | {X: 0, Y: size, Z: size, TextureX: 1, TextureY: 0, Texture: tex, R: 255, G: 255, B: 255, A: 255}, 89 | }, 90 | }, render.SunModels) 91 | 92 | moon := render.GetTexture("environment/moon_phases") 93 | mpx, mpy := float64(moonPhase%4)*(1/4.0), float64(moonPhase/4)*(1/2.0) 94 | moonModel = render.NewModelCollection([][]*render.ModelVertex{ 95 | { 96 | {X: 0, Y: -size, Z: -size, TextureX: mpx, TextureY: mpy + (1 / 2.0), Texture: moon, R: 255, G: 255, B: 255, A: 255}, 97 | {X: 0, Y: size, Z: -size, TextureX: mpx, TextureY: mpy, Texture: moon, R: 255, G: 255, B: 255, A: 255}, 98 | {X: 0, Y: -size, Z: size, TextureX: mpx + (1 / 4.0), TextureY: mpy + (1 / 2.0), Texture: moon, R: 255, G: 255, B: 255, A: 255}, 99 | {X: 0, Y: size, Z: size, TextureX: mpx + (1 / 4.0), TextureY: mpy, Texture: moon, R: 255, G: 255, B: 255, A: 255}, 100 | }, 101 | }, render.SunModels) 102 | } 103 | -------------------------------------------------------------------------------- /cmd/protocol_builder/condition.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "strings" 21 | "unicode" 22 | ) 23 | 24 | type conditions []condition 25 | 26 | type condition struct { 27 | l conditionVar 28 | cond string 29 | r conditionVar 30 | } 31 | 32 | type conditionVar struct { 33 | name string 34 | structField int 35 | } 36 | 37 | func parseCondition(con string) conditions { 38 | var conds conditions 39 | for len(con) > 0 { 40 | v := conditionVar{} 41 | for con[0] == '.' { 42 | con = con[1:] 43 | v.structField++ 44 | } 45 | pos := strings.IndexFunc(con, func(r rune) bool { return !unicode.In(r, unicode.Letter, unicode.Digit) }) 46 | if pos == -1 { 47 | panic("invalid condition") 48 | } 49 | v.name = con[:pos] 50 | con = con[pos:] 51 | con = strings.TrimSpace(con) 52 | 53 | c := condition{l: v} 54 | 55 | pos = strings.IndexFunc(con, func(r rune) bool { return !unicode.IsSymbol(r) && r != '!' }) 56 | c.cond = con[:pos] 57 | con = con[pos:] 58 | con = strings.TrimSpace(con) 59 | 60 | r := conditionVar{} 61 | for con[0] == '.' { 62 | r.structField++ 63 | con = con[1:] 64 | } 65 | 66 | pos = strings.IndexFunc(con, func(r rune) bool { return !unicode.In(r, unicode.Letter, unicode.Digit) && r != '"' }) 67 | if pos == -1 { 68 | pos = len(con) 69 | } 70 | r.name = con[:pos] 71 | c.r = r 72 | conds = append(conds, c) 73 | con = con[pos:] 74 | con = strings.TrimSpace(con) 75 | } 76 | return conds 77 | } 78 | 79 | func (c *condition) print(base string, buf *bytes.Buffer) { 80 | l := c.l.name 81 | if c.l.structField > 0 { 82 | l = base 83 | i := c.l.structField - 1 84 | for i > 0 { 85 | p := strings.LastIndex(l, ".") 86 | l = l[:p] 87 | i-- 88 | } 89 | l += "." + c.l.name 90 | } 91 | r := c.r.name 92 | if c.r.structField > 0 { 93 | r = base 94 | i := c.r.structField - 1 95 | for i > 0 { 96 | p := strings.LastIndex(r, ".") 97 | r = r[:p] 98 | i-- 99 | } 100 | r += "." + c.r.name 101 | } 102 | fmt.Fprintf(buf, "%s %s %s", l, c.cond, r) 103 | } 104 | 105 | func (c *condition) equals(o *condition) bool { 106 | if c == o { 107 | return true 108 | } 109 | if c == nil || o == nil { 110 | return false 111 | } 112 | return *c == *o 113 | } 114 | 115 | func (c conditions) equals(o conditions) bool { 116 | if c == nil || o == nil { 117 | return false 118 | } 119 | if len(c) != len(o) { 120 | return false 121 | } 122 | for i, cond := range c { 123 | if !cond.equals(&o[i]) { 124 | return false 125 | } 126 | } 127 | return true 128 | } 129 | 130 | func (c conditions) print(base string, buf *bytes.Buffer) { 131 | buf.WriteString("if ") 132 | for i, cond := range c { 133 | cond.print(base, buf) 134 | if i != len(c)-1 { 135 | buf.WriteString(" || ") 136 | } 137 | } 138 | buf.WriteString(" {\n") 139 | } 140 | -------------------------------------------------------------------------------- /cmd/protocol_builder/helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | ) 21 | 22 | func generateNumberWrite(w *bytes.Buffer, name string, t string, size int, unsigned bool) { 23 | for i := 0; i < size; i++ { 24 | fmt.Fprintf(w, "tmp[%d] = byte(%s >> %d)\n", i, name, (size-1-i)*8) 25 | } 26 | fmt.Fprintf(w, "if _, err = ww.Write(tmp[:%d]); err != nil { return }\n", size) 27 | } 28 | 29 | func generateNumberRead(w *bytes.Buffer, name, t string, size int, unsigned bool) { 30 | origT := t 31 | if !unsigned { 32 | t = "u" + t 33 | } 34 | fmt.Fprintf(w, "if _, err = rr.Read(tmp[:%d]); err != nil { return }\n", size) 35 | fmt.Fprintf(w, "%s = ", name) 36 | if !unsigned { 37 | fmt.Fprintf(w, "%s(", origT) 38 | } 39 | for i := 0; i < size; i++ { 40 | fmt.Fprintf(w, "(%s(tmp[%d]) << %d)", t, size-1-i, i*8) 41 | if i != size-1 { 42 | w.WriteString("|") 43 | } 44 | } 45 | if !unsigned { 46 | w.WriteString(")") 47 | } 48 | w.WriteString("\n") 49 | } 50 | -------------------------------------------------------------------------------- /cmd/steven/steven.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "io" 19 | "os" 20 | "os/exec" 21 | 22 | "github.com/thinkofdeath/steven" 23 | ) 24 | 25 | func main() { 26 | // Can't use flags as we need to support a weird flag 27 | // format 28 | var username, uuid, accessToken string 29 | var noFork bool 30 | 31 | for i, arg := range os.Args { 32 | switch arg { 33 | case "--username": 34 | username = os.Args[i+1] 35 | case "--uuid": 36 | uuid = os.Args[i+1] 37 | case "--accessToken": 38 | accessToken = os.Args[i+1] 39 | case "--no-fork": 40 | noFork = true 41 | } 42 | } 43 | if noFork { 44 | steven.Main(username, uuid, accessToken) 45 | return 46 | } 47 | 48 | cmd := exec.Command(os.Args[0], append(os.Args[1:], "--no-fork")...) 49 | l, err := os.Create("steven.log") 50 | if err != nil { 51 | panic(err) 52 | } 53 | defer l.Close() 54 | cmd.Stdout = io.MultiWriter(os.Stdout, l) 55 | f, err := os.Create("steven-error.log") 56 | if err != nil { 57 | panic(err) 58 | } 59 | defer f.Close() 60 | cmd.Stderr = io.MultiWriter(f, os.Stderr) 61 | cmd.Run() 62 | } 63 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "encoding/json" 19 | "os" 20 | ) 21 | 22 | var Config ConfigData 23 | 24 | type ConfigData struct { 25 | Servers []ConfigServer 26 | } 27 | 28 | type ConfigServer struct { 29 | Name string 30 | Address string 31 | } 32 | 33 | func init() { 34 | f, err := os.Open("servers.json") 35 | if err != nil { 36 | return 37 | } 38 | defer f.Close() 39 | err = json.NewDecoder(f).Decode(&Config) 40 | if err != nil { 41 | panic(err) 42 | } 43 | saveServers() 44 | } 45 | 46 | func saveServers() { 47 | f, err := os.Create("servers.json") 48 | if err != nil { 49 | panic(err) 50 | } 51 | defer f.Close() 52 | data, err := json.MarshalIndent(&Config, "", " ") 53 | if err != nil { 54 | panic(err) 55 | } 56 | _, err = f.Write(data) 57 | if err != nil { 58 | panic(err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /console/command_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package console 16 | 17 | import "testing" 18 | 19 | func TestBasic(t *testing.T) { 20 | r := registry{} 21 | 22 | called := false 23 | r.Register("test", func() { 24 | called = true 25 | }) 26 | checkError(t, r.Execute("test")) 27 | if !called { 28 | t.FailNow() 29 | } 30 | } 31 | 32 | func TestSubCommands(t *testing.T) { 33 | r := registry{} 34 | 35 | called := 0 36 | r.Register("test a", func() { 37 | called++ 38 | }) 39 | r.Register("test b", func() { 40 | called++ 41 | }) 42 | r.Register("test c a", func() { 43 | called++ 44 | }) 45 | r.Register("test c b", func() { 46 | called++ 47 | }) 48 | r.Register("test d e f g", func() { 49 | called++ 50 | }) 51 | 52 | checkError(t, r.Execute("test a")) 53 | checkError(t, r.Execute("test b")) 54 | checkError(t, r.Execute("test c a")) 55 | checkError(t, r.Execute("test c b")) 56 | checkError(t, r.Execute("test d e f g")) 57 | 58 | if called != 5 { 59 | t.FailNow() 60 | } 61 | } 62 | 63 | func TestNonFunction(t *testing.T) { 64 | shouldPanic(t, func() { 65 | r := registry{} 66 | r.Register("test a", "") 67 | }) 68 | } 69 | 70 | func TestInvalidDesc(t *testing.T) { 71 | shouldPanic(t, func() { 72 | r := registry{} 73 | r.Register("", func() { 74 | 75 | }) 76 | }) 77 | } 78 | 79 | func TestDoubleAdd(t *testing.T) { 80 | shouldPanic(t, func() { 81 | r := registry{} 82 | r.Register("test", func() { 83 | 84 | }) 85 | r.Register("test", func() { 86 | 87 | }) 88 | }) 89 | } 90 | 91 | func TestExtraParams(t *testing.T) { 92 | r := registry{ 93 | ExtraParameters: 2, 94 | } 95 | r.Register("extra", func(a, b string) { 96 | if a != "a" || b != "b" { 97 | t.FailNow() 98 | } 99 | }) 100 | checkError(t, r.Execute("extra", "a", "b")) 101 | } 102 | 103 | func TestExtraParamsFail(t *testing.T) { 104 | shouldPanic(t, func() { 105 | r := registry{ 106 | ExtraParameters: 2, 107 | } 108 | r.Register("extra", func(a, b string) { 109 | t.FailNow() 110 | }) 111 | r.Execute("extra", "a", "b", "c") 112 | }) 113 | } 114 | 115 | func TestEmpty(t *testing.T) { 116 | r := registry{} 117 | err := r.Execute("test") 118 | if err != ErrCommandNotFound { 119 | t.FailNow() 120 | } 121 | } 122 | 123 | func TestMissing(t *testing.T) { 124 | r := registry{} 125 | r.Register("hello world", func() { 126 | t.FailNow() 127 | }) 128 | err := r.Execute("test") 129 | if err != ErrCommandNotFound { 130 | t.FailNow() 131 | } 132 | } 133 | 134 | func TestMissing2(t *testing.T) { 135 | r := registry{} 136 | r.Register("hello world", func() { 137 | t.FailNow() 138 | }) 139 | err := r.Execute("hello") 140 | if err != ErrCommandNotFound { 141 | t.FailNow() 142 | } 143 | } 144 | 145 | func TestQuoted(t *testing.T) { 146 | r := registry{} 147 | called := false 148 | r.Register("hello world", func() { 149 | called = true 150 | }) 151 | checkError(t, r.Execute("hello \"world\"")) 152 | if !called { 153 | t.FailNow() 154 | } 155 | } 156 | 157 | func TestCommandPanic(t *testing.T) { 158 | r := registry{} 159 | r.Register("hello world", func() { 160 | panic("test panic") 161 | }) 162 | err := r.Execute("hello \"world\"") 163 | if err == nil || err.Error() != "test panic" { 164 | t.FailNow() 165 | } 166 | } 167 | 168 | func checkError(t *testing.T, err error) { 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | } 173 | 174 | func shouldPanic(t *testing.T, f func()) { 175 | defer func() { 176 | if err := recover(); err == nil { 177 | t.FailNow() 178 | } 179 | }() 180 | f() 181 | } 182 | -------------------------------------------------------------------------------- /console/conf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package console 16 | 17 | import ( 18 | "bufio" 19 | "fmt" 20 | "os" 21 | "sort" 22 | "strings" 23 | 24 | "github.com/thinkofdeath/steven/format" 25 | ) 26 | 27 | // ExecConf executes/loads the passed config file 28 | func ExecConf(path string) { 29 | // The config file needs special rights 30 | if path == "conf.cfg" { 31 | configOverride = true 32 | } 33 | defer func() { 34 | if path == "conf.cfg" { 35 | configOverride = false 36 | saveConf() 37 | } 38 | }() 39 | f, err := os.Open(path) 40 | if err != nil { 41 | Component(format.Build("Failed to open "). 42 | Append(path). 43 | Append(": "). 44 | Append(err.Error()). 45 | Create(), 46 | ) 47 | return 48 | } 49 | defer f.Close() 50 | s := bufio.NewScanner(f) 51 | for s.Scan() { 52 | txt := strings.TrimSpace(s.Text()) 53 | if strings.HasPrefix(txt, "#") || txt == "" { 54 | continue 55 | } 56 | if err := Execute(txt); err != nil { 57 | Component(format.Build("Error: " + err.Error()).Color(format.Red).Create()) 58 | } 59 | } 60 | } 61 | 62 | func saveConf() { 63 | f, err := os.Create("conf.cfg") 64 | if err != nil { 65 | panic(err) 66 | } 67 | defer f.Close() 68 | sort.Sort(cvars) 69 | for _, c := range cvars { 70 | if c.props().is(Serializable) { 71 | fmt.Fprintln(f, c) 72 | fmt.Fprintln(f) 73 | } 74 | } 75 | } 76 | 77 | type cvarList []cvarI 78 | 79 | func (c cvarList) Len() int { return len(c) } 80 | func (c cvarList) Swap(a, b int) { c[a], c[b] = c[b], c[a] } 81 | func (c cvarList) Less(a, b int) bool { return c[a].Name() < c[b].Name() } 82 | -------------------------------------------------------------------------------- /console/console.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package console 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "os" 21 | "runtime" 22 | "strings" 23 | "sync" 24 | 25 | "github.com/thinkofdeath/steven/format" 26 | ) 27 | 28 | var ( 29 | // stdout and a log file combind 30 | w io.Writer 31 | 32 | // For the possible option of scrolling in the 33 | // future 34 | historyBuffer [200]format.AnyComponent 35 | lock sync.Mutex 36 | 37 | defaultRegistry registry 38 | ) 39 | 40 | func checkInit() { 41 | if w != nil { 42 | return 43 | } 44 | w = os.Stdout 45 | } 46 | 47 | // Text appends the passed formatted string plus a new line to 48 | // the log buffer. The formatting uses the same rules as fmt. 49 | func Text(f string, args ...interface{}) { 50 | _, file, line, ok := runtime.Caller(1) 51 | if !ok { 52 | file = "unknown" 53 | line = 0 54 | } 55 | file = file[strings.LastIndex(file, "/")+1:] 56 | 57 | msg := &format.TextComponent{ 58 | Text: fmt.Sprintf("[%s:%d] ", file, line), 59 | Component: format.Component{ 60 | Color: format.Aqua, 61 | }, 62 | } 63 | msg.Extra = append(msg.Extra, format.Wrap(&format.TextComponent{ 64 | Text: fmt.Sprintf(f, args...), 65 | Component: format.Component{ 66 | Color: format.White, 67 | }, 68 | })) 69 | Component(format.Wrap(msg)) 70 | } 71 | 72 | // Component appends the component to the log buffer. 73 | func Component(c format.AnyComponent) { 74 | lock.Lock() 75 | defer lock.Unlock() 76 | checkInit() 77 | io.WriteString(w, c.String()+"\n") 78 | copy(historyBuffer[1:], historyBuffer[:]) 79 | historyBuffer[0] = c 80 | } 81 | 82 | // History returns up to the requested number of lines from the log 83 | // buffer. 84 | // 85 | // As a special case -1 will return the whole buffer. 86 | func History(lines int) []format.AnyComponent { 87 | lock.Lock() 88 | defer lock.Unlock() 89 | if lines == -1 || lines > len(historyBuffer) { 90 | return historyBuffer[:] 91 | } 92 | return historyBuffer[:lines] 93 | } 94 | -------------------------------------------------------------------------------- /console/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package console implements a system for parsing and executing 16 | // commands + logging. 17 | // 18 | // When registering commands a description of the command is 19 | // required. For basic commands the format of this is simple: 20 | // 21 | // commandname sub1 sub2 sub... 22 | // 23 | // Complex commands can be created by using % to specify 24 | // arguments. The type of the argument will be inferred 25 | // from the type over the passed function pointer. Extra 26 | // constraints can be added after the % to gain finer control 27 | // over the argument. 28 | // 29 | // Built-in types: 30 | // string any string, a length limit may be added 31 | // after the % to enforce a max length 32 | // 33 | // Executing commands works by treating whitespace at delimiters 34 | // between arguments with the exception of whitespace contained 35 | // within quotes (") as that will be treated as a single argument 36 | package console 37 | -------------------------------------------------------------------------------- /console/types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package console 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | ) 21 | 22 | func TestTypeString(t *testing.T) { 23 | r := ®istry{} 24 | 25 | called := false 26 | r.Register("testing %", func(arg string) { 27 | if arg != "hello" { 28 | t.FailNow() 29 | } 30 | called = true 31 | }) 32 | 33 | checkError(t, r.Execute("testing hello")) 34 | if !called { 35 | t.FailNow() 36 | } 37 | } 38 | 39 | func TestTypeLimit(t *testing.T) { 40 | r := ®istry{} 41 | 42 | r.Register("testing %3", func(arg string) { 43 | t.FailNow() 44 | }) 45 | 46 | err := r.Execute("testing hello") 47 | if err == nil || !strings.Contains(err.Error(), "too long") { 48 | t.FailNow() 49 | } 50 | } 51 | 52 | func TestTypeSub(t *testing.T) { 53 | r := ®istry{} 54 | 55 | called := 0 56 | r.Register("testing % a", func(arg string) { 57 | if arg != "hello" { 58 | t.FailNow() 59 | } 60 | if called != 0 { 61 | t.FailNow() 62 | } 63 | called++ 64 | }) 65 | r.Register("testing % b", func(arg string) { 66 | if arg != "hello" { 67 | t.FailNow() 68 | } 69 | if called != 1 { 70 | t.FailNow() 71 | } 72 | called++ 73 | }) 74 | 75 | checkError(t, r.Execute("testing hello a")) 76 | checkError(t, r.Execute("testing hello b")) 77 | if called != 2 { 78 | t.FailNow() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /entity.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import "github.com/thinkofdeath/steven/entitysys" 18 | 19 | var entityTypes = map[int]func() Entity{ 20 | 50: newCreeper, 21 | 51: newSkeleton, 22 | 52: newSpider, 23 | // 53: Giant Zombie, TODO: Do we need this? 24 | 54: newZombie, 25 | 55: newSlime, 26 | 56: newGhast, 27 | 57: newZombiePigman, 28 | 58: newEnderman, 29 | 59: newCaveSpider, 30 | 60: newSilverfish, 31 | 61: newBlaze, 32 | 62: newMagmaCube, 33 | 63: newEnderDragon, 34 | 64: newWither, 35 | 65: newBat, 36 | 66: newWitch, 37 | 67: newEndermite, 38 | 68: newGuardian, 39 | 40 | 90: newPig, 41 | 91: newSheep, 42 | 92: newCow, 43 | 93: newChicken, 44 | 94: newSquid, 45 | 95: newWolf, 46 | 96: newMooshroom, 47 | 97: newSnowman, 48 | 98: newOcelot, 49 | 99: newIronGolem, 50 | 100: newHorse, 51 | 101: newRabbit, 52 | 120: newVillager, 53 | } 54 | 55 | var globalSystems []globalSystem 56 | 57 | type globalSystem struct { 58 | Stage entitysys.Stage 59 | F interface{} 60 | Matchers []entitysys.Matcher 61 | } 62 | 63 | func addSystem(stage entitysys.Stage, f interface{}, matchers ...entitysys.Matcher) { 64 | globalSystems = append(globalSystems, globalSystem{ 65 | Stage: stage, 66 | F: f, 67 | Matchers: matchers, 68 | }) 69 | } 70 | 71 | type clientEntities struct { 72 | entities map[int]Entity 73 | container *entitysys.Container 74 | } 75 | 76 | func (ce *clientEntities) init() { 77 | ce.container = entitysys.NewContainer() 78 | ce.entities = map[int]Entity{} 79 | for _, g := range globalSystems { 80 | ce.container.AddSystem(g.Stage, g.F, g.Matchers...) 81 | } 82 | } 83 | 84 | func (ce *clientEntities) add(id int, e Entity) { 85 | ce.entities[id] = e 86 | ce.container.AddEntity(e) 87 | } 88 | 89 | func (ce *clientEntities) remove(id int) { 90 | e, ok := ce.entities[id] 91 | if !ok { 92 | return 93 | } 94 | delete(ce.entities, id) 95 | ce.container.RemoveEntity(e) 96 | } 97 | 98 | func (ce *clientEntities) tick() { 99 | ce.container.Tick() 100 | } 101 | 102 | type Entity interface{} 103 | -------------------------------------------------------------------------------- /entitysys/desc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package entitysys 16 | 17 | import "reflect" 18 | 19 | // Matcher is used to select when an entity is used for a 20 | // system. 21 | type Matcher interface { 22 | Match(e interface{}) bool 23 | } 24 | 25 | type typeMatcher struct { 26 | Type reflect.Type 27 | } 28 | 29 | func (t typeMatcher) Match(e interface{}) bool { 30 | if t.Type.Kind() == reflect.Interface { 31 | return reflect.TypeOf(e).Implements(t.Type) 32 | } 33 | _, ok := reflect.TypeOf(e).Elem().FieldByName(t.Type.Elem().Name()) 34 | return ok 35 | } 36 | 37 | // Type returns a Matcher that matches when the type of element 38 | // of the passed to this is implemented by the entity. 39 | // 40 | // This method should be used as followed for interfaces 41 | // Type((*MyInterface)(nil)) 42 | func Type(t interface{}) Matcher { 43 | return typeMatcher{ 44 | Type: reflect.TypeOf(t).Elem(), 45 | } 46 | } 47 | 48 | type notMatcher struct { 49 | child Matcher 50 | } 51 | 52 | func (n notMatcher) Match(e interface{}) bool { return !n.child.Match(e) } 53 | 54 | // Not inverts the passed Matcher. 55 | func Not(d Matcher) Matcher { 56 | return notMatcher{child: d} 57 | } 58 | -------------------------------------------------------------------------------- /fakegen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "math/rand" 19 | "time" 20 | 21 | "github.com/thinkofdeath/steven/render" 22 | ) 23 | 24 | var fakeGenDistance = 7 25 | 26 | func fakeGen() { 27 | render.Camera.X = 0.5 28 | render.Camera.Z = 0.5 29 | render.Camera.Y = 70 30 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 31 | 32 | liquid := Blocks.Water 33 | top := Blocks.Grass 34 | mid := Blocks.Dirt 35 | bot := Blocks.Stone 36 | ra := r.Intn(3) 37 | if ra == 1 { 38 | liquid = Blocks.Lava 39 | render.LightLevel = 0.9 40 | render.ClearColour.R, render.ClearColour.G, render.ClearColour.B = 52/255.0, 8/255.0, 8/255.0 41 | top = Blocks.Netherrack 42 | mid = Blocks.Netherrack 43 | bot = Blocks.SoulSand 44 | } else if ra == 2 { 45 | liquid = Blocks.Air 46 | render.LightLevel = 0.8 47 | render.ClearColour.R, render.ClearColour.G, render.ClearColour.B = 23/255.0, 0, 23/255.0 48 | top = Blocks.EndStone 49 | mid = Blocks.EndStone 50 | bot = Blocks.EndStone 51 | } 52 | go func() { 53 | randGrid := make([]int, (fakeGenDistance*2+1)*(fakeGenDistance*2+1)) 54 | for i := range randGrid { 55 | randGrid[i] = r.Intn(10) + 54 56 | } 57 | get := func(cx, cz int) int { 58 | if cx < -fakeGenDistance || cz < -fakeGenDistance || cx > fakeGenDistance || cz > fakeGenDistance { 59 | return 63 60 | } 61 | cx += fakeGenDistance 62 | cz += fakeGenDistance 63 | return randGrid[cx+cz*(fakeGenDistance*2+1)] 64 | } 65 | smooth := func(cx, cz, x, y, z int) int { 66 | tl := float64(get(cx, cz)) 67 | tr := float64(get(cx+1, cz)) 68 | bl := float64(get(cx, cz+1)) 69 | br := float64(get(cx+1, cz+1)) 70 | t := tl*((15-float64(x))/15.0) + tr*(float64(x)/15.0) 71 | b := bl*((15-float64(x))/15.0) + br*(float64(x)/15.0) 72 | return int(t*((15-float64(z))/15.0) + b*(float64(z)/15.0)) 73 | } 74 | 75 | for cx := -fakeGenDistance; cx <= fakeGenDistance; cx++ { 76 | for cz := -fakeGenDistance; cz <= fakeGenDistance; cz++ { 77 | c := &chunk{ 78 | chunkPosition: chunkPosition{ 79 | X: cx, Z: cz, 80 | }, 81 | } 82 | 83 | for i := 0; i < 4; i++ { 84 | cs := newChunkSection(c, i) 85 | c.Sections[i] = cs 86 | for y := 0; y < 16; y++ { 87 | for z := 0; z < 16; z++ { 88 | for x := 0; x < 16; x++ { 89 | height := smooth(cx, cz, x, y, z) 90 | ry := y + i<<4 91 | var block Block 92 | cs.setSkyLight(0, x, y, z) 93 | switch { 94 | case ry <= height-5: 95 | block = bot.Base 96 | case ry <= height-1: 97 | block = mid.Base 98 | case ry == height: 99 | block = top.Base 100 | default: 101 | level := 0xF 102 | if ry >= 60 { 103 | block = Blocks.Air.Base 104 | } else { 105 | block = liquid.Base 106 | if liquid == Blocks.Water { 107 | level = 13 - (60-ry)*2 108 | } 109 | } 110 | if level < 0 { 111 | level = 0 112 | } 113 | sky := (16*16*16*2 + 16*16*8) * 4 114 | sky += 16 * 16 * 8 * i 115 | cs.setSkyLight(byte(level), x, y, z) 116 | } 117 | cs.setBlock(block, x, y, z) 118 | } 119 | } 120 | } 121 | } 122 | c.calcHeightmap() 123 | syncChan <- c.postLoad 124 | } 125 | } 126 | }() 127 | } 128 | -------------------------------------------------------------------------------- /format/build.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package format 16 | 17 | // Builder allows for easy creation of simple messages. 18 | type Builder struct { 19 | main *TextComponent 20 | 21 | cur *TextComponent 22 | } 23 | 24 | // Build creates a builder starting with the initial message. 25 | func Build(msg string) *Builder { 26 | return &Builder{ 27 | main: &TextComponent{}, 28 | cur: &TextComponent{Text: msg}, 29 | } 30 | } 31 | 32 | // Append adds the message to the builder and sets it as the current 33 | // target. 34 | func (b *Builder) Append(msg string) *Builder { 35 | b.main.Extra = append(b.main.Extra, Wrap(b.cur)) 36 | old := b.cur 37 | b.cur = &TextComponent{Text: msg} 38 | b.cur.Color = old.Color 39 | return b 40 | } 41 | 42 | // Color changes the color of current message 43 | func (b *Builder) Color(c Color) *Builder { 44 | b.cur.Color = c 45 | return b 46 | } 47 | 48 | // Create returns the component created by this builder 49 | func (b *Builder) Create() AnyComponent { 50 | b.main.Extra = append(b.main.Extra, Wrap(b.cur)) 51 | b.cur = nil 52 | return Wrap(b.main) 53 | } 54 | -------------------------------------------------------------------------------- /format/legacy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package format 16 | 17 | import ( 18 | "strings" 19 | "unicode" 20 | ) 21 | 22 | const legacyChar = '§' 23 | 24 | func ConvertLegacy(c AnyComponent) { 25 | switch c := c.Value.(type) { 26 | case *TextComponent: 27 | for _, e := range c.Extra { 28 | ConvertLegacy(e) 29 | } 30 | if strings.ContainsRune(c.Text, legacyChar) { 31 | text := []rune(c.Text) 32 | c.Text = "" 33 | var parts []AnyComponent 34 | 35 | last := 0 36 | cur := &TextComponent{} 37 | for i := 0; i < len(text); i++ { 38 | if text[i] == legacyChar && i+1 < len(text) { 39 | i++ 40 | r := unicode.ToLower(text[i]) 41 | cur.Text = string(text[last : i-1]) 42 | last = i + 1 43 | prev := cur 44 | parts = append(parts, AnyComponent{cur}) 45 | cur = &TextComponent{} 46 | if !((r >= 'a' && r <= 'f') || (r >= '0' && r <= '9')) { 47 | cur.Component = prev.Component 48 | } 49 | switch r { 50 | case '0': 51 | cur.Color = Black 52 | case '1': 53 | cur.Color = DarkBlue 54 | case '2': 55 | cur.Color = DarkGreen 56 | case '3': 57 | cur.Color = DarkAqua 58 | case '4': 59 | cur.Color = DarkRed 60 | case '5': 61 | cur.Color = DarkPurple 62 | case '6': 63 | cur.Color = Gold 64 | case '7': 65 | cur.Color = Gray 66 | case '8': 67 | cur.Color = DarkGray 68 | case '9': 69 | cur.Color = Blue 70 | case 'a': 71 | cur.Color = Green 72 | case 'b': 73 | cur.Color = Aqua 74 | case 'c': 75 | cur.Color = Red 76 | case 'd': 77 | cur.Color = LightPurple 78 | case 'e': 79 | cur.Color = Yellow 80 | case 'f': 81 | cur.Color = White 82 | case 'k': 83 | cur.Obfuscated = True 84 | case 'l': 85 | cur.Bold = True 86 | case 'm': 87 | cur.Strikethrough = True 88 | case 'n': 89 | cur.Underlined = True 90 | case 'o': 91 | cur.Italic = True 92 | case 'r': 93 | } 94 | } 95 | } 96 | if len(text[last:]) > 0 { 97 | cur.Text = string(text[last:]) 98 | parts = append(parts, AnyComponent{cur}) 99 | } 100 | 101 | c.Extra = append(parts, c.Extra...) 102 | } 103 | case *TranslateComponent: 104 | for _, w := range c.With { 105 | ConvertLegacy(w) 106 | } 107 | for _, e := range c.Extra { 108 | ConvertLegacy(e) 109 | } 110 | default: 111 | panic("unhandled component") 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /format/reflectmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package format 16 | 17 | import ( 18 | "encoding/json" 19 | "reflect" 20 | "strings" 21 | ) 22 | 23 | func mapStruct(val interface{}, m map[string]json.RawMessage) error { 24 | v := reflect.ValueOf(val).Elem() 25 | t := v.Type() 26 | 27 | for i := 0; i < t.NumField(); i++ { 28 | f := t.Field(i) 29 | if f.Type.Kind() == reflect.Struct { 30 | mapStruct(v.Field(i).Addr().Interface(), m) 31 | continue 32 | } 33 | name := f.Tag.Get("json") 34 | if name == "" { 35 | name = f.Name 36 | } 37 | if strings.ContainsRune(name, ',') { 38 | name = name[:strings.IndexRune(name, ',')] 39 | } 40 | mv, ok := m[name] 41 | if !ok { 42 | continue 43 | } 44 | json.Unmarshal([]byte(mv), v.Field(i).Addr().Interface()) 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /format/string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package format 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | ) 21 | 22 | // String provides a string version of the component without 23 | // formatting. 24 | func (a AnyComponent) String() string { 25 | return fmt.Sprint(a.Value) 26 | } 27 | 28 | // String provides a string version of the component without 29 | // formatting. 30 | func (c *Component) String() string { 31 | var buf bytes.Buffer 32 | for _, e := range c.Extra { 33 | fmt.Fprint(&buf, e) 34 | } 35 | return buf.String() 36 | } 37 | 38 | // String provides a string version of the component without 39 | // formatting. 40 | func (t *TextComponent) String() string { 41 | return t.Text + t.Component.String() 42 | } 43 | -------------------------------------------------------------------------------- /format/type_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type=Type; DO NOT EDIT 2 | 3 | package format 4 | 5 | import "fmt" 6 | 7 | const _Type_name = "InvalidTextTranslateScoreSelector" 8 | 9 | var _Type_index = [...]uint8{0, 7, 11, 20, 25, 33} 10 | 11 | func (i Type) String() string { 12 | if i < 0 || i+1 >= Type(len(_Type_index)) { 13 | return fmt.Sprintf("Type(%d)", i) 14 | } 15 | return _Type_name[_Type_index[i]:_Type_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /item.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "github.com/thinkofdeath/steven/encoding/nbt" 19 | "github.com/thinkofdeath/steven/protocol" 20 | ) 21 | 22 | type ItemStack struct { 23 | Type ItemType 24 | Count int 25 | 26 | rawID int16 27 | rawDamage int16 28 | rawTag *nbt.Compound 29 | } 30 | 31 | func ItemStackFromProtocol(p protocol.ItemStack) *ItemStack { 32 | it := ItemById(int(p.ID)) 33 | if it == nil { 34 | return nil 35 | } 36 | i := &ItemStack{ 37 | Type: it, 38 | Count: int(p.Count), 39 | rawID: p.ID, 40 | rawDamage: p.Damage, 41 | rawTag: p.NBT, 42 | } 43 | i.Type.ParseDamage(p.Damage) 44 | if p.NBT != nil { 45 | i.Type.ParseTag(p.NBT) 46 | } 47 | return i 48 | } 49 | func ItemStackToProtocol(i *ItemStack) protocol.ItemStack { 50 | if i == nil { 51 | return protocol.ItemStack{ID: -1} 52 | } 53 | return protocol.ItemStack{ 54 | ID: i.rawID, 55 | Count: byte(i.Count), 56 | Damage: i.rawDamage, 57 | NBT: i.rawTag, 58 | } 59 | } 60 | 61 | type ItemType interface { 62 | Name() string 63 | NameLocaleKey() string 64 | 65 | ParseDamage(d int16) 66 | ParseTag(tag *nbt.Compound) 67 | } 68 | 69 | func ItemById(id int) (ty ItemType) { 70 | if id == -1 { 71 | return nil 72 | } 73 | if id < 256 { 74 | ty = ItemOfBlock(blockSetsByID[id].Base) 75 | } else { 76 | if f, ok := itemsByID[id]; ok { 77 | ty = f() 78 | } 79 | } 80 | if ty == nil { 81 | ty = ItemOfBlock(Blocks.Stone.Base) 82 | } 83 | return ty 84 | } 85 | 86 | type displayTag struct { 87 | name string 88 | lore []string 89 | } 90 | 91 | func (d *displayTag) ParseTag(tag *nbt.Compound) { 92 | display, ok := tag.Items["display"].(*nbt.Compound) 93 | if !ok { 94 | return 95 | } 96 | d.name, _ = display.Items["Name"].(string) 97 | lore, ok := display.Items["Lore"].([]interface{}) 98 | if !ok { 99 | return 100 | } 101 | d.lore = make([]string, len(lore)) 102 | for i := range lore { 103 | d.lore[i], _ = lore[i].(string) 104 | } 105 | } 106 | func (d *displayTag) DisplayName() string { return d.name } 107 | func (d *displayTag) Lore() []string { return d.lore } 108 | 109 | type DisplayTag interface { 110 | DisplayName() string 111 | Lore() []string 112 | } 113 | 114 | type blockItem struct { 115 | itemNamed 116 | block Block 117 | displayTag 118 | } 119 | 120 | func ItemOfBlock(b Block) ItemType { 121 | return &blockItem{ 122 | block: b, 123 | itemNamed: itemNamed{ 124 | name: b.ModelName(), 125 | }, 126 | } 127 | } 128 | 129 | func (b *blockItem) NameLocaleKey() string { 130 | return b.block.NameLocaleKey() 131 | } 132 | 133 | func (b *blockItem) ParseDamage(d int16) { 134 | d &= 0xF 135 | nb := GetBlockByCombinedID(uint16(b.block.BlockSet().ID<<4) | uint16(d)) 136 | if nb.Is(b.block.BlockSet()) { 137 | b.block = nb 138 | b.itemNamed.name = nb.ModelName() 139 | } 140 | } 141 | func (b *blockItem) ParseTag(tag *nbt.Compound) { 142 | b.displayTag.ParseTag(tag) 143 | } 144 | 145 | type itemSimpleLocale struct { 146 | locale string 147 | } 148 | 149 | func (i *itemSimpleLocale) NameLocaleKey() string { 150 | return i.locale 151 | } 152 | 153 | type itemDamagable struct { 154 | damage, maxDamage int16 155 | } 156 | 157 | func (i *itemDamagable) ParseDamage(d int16) { 158 | i.damage = d 159 | } 160 | func (i *itemDamagable) Damage() int16 { return i.damage } 161 | func (i *itemDamagable) MaxDamage() int16 { return i.maxDamage } 162 | 163 | type ItemDamagable interface { 164 | Damage() int16 165 | MaxDamage() int16 166 | } 167 | 168 | type itemNamed struct { 169 | name string 170 | } 171 | 172 | func (i *itemNamed) Name() string { 173 | return i.name 174 | } 175 | -------------------------------------------------------------------------------- /key.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | type pluginKey struct { 18 | Plugin, Name string 19 | } 20 | 21 | func (pk pluginKey) String() string { 22 | return pk.Plugin + ":" + pk.Name 23 | } 24 | -------------------------------------------------------------------------------- /modelui.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "math" 19 | 20 | "github.com/go-gl/mathgl/mgl32" 21 | "github.com/thinkofdeath/steven/type/direction" 22 | "github.com/thinkofdeath/steven/ui" 23 | "github.com/thinkofdeath/steven/world/biome" 24 | ) 25 | 26 | var modelCache = map[string]*model{} 27 | 28 | func getModel(name string) *model { 29 | if mdl, ok := modelCache[name]; ok { 30 | return mdl 31 | } 32 | js := &jsModel{} 33 | err := loadJSON("minecraft", "models/item/"+name+".json", js) 34 | if err != nil { 35 | modelCache[name] = nil 36 | return nil 37 | } 38 | mdl := parseModel("minecraft", js) 39 | modelCache[name] = mdl 40 | return mdl 41 | } 42 | 43 | func modelToUI(mdl *model, block Block) *ui.Model { 44 | mat := mgl32.Ident4() 45 | 46 | if gui, ok := mdl.display["gui"]; ok { 47 | if gui.Scale != nil { 48 | mat = mat.Mul4(mgl32.Scale3D( 49 | float32(gui.Scale[0]), 50 | float32(gui.Scale[1]), 51 | float32(gui.Scale[2]), 52 | )) 53 | } 54 | if gui.Translation != nil { 55 | mat = mat.Mul4(mgl32.Translate3D( 56 | float32(gui.Translation[0]/16), 57 | float32(gui.Translation[1]/16), 58 | float32(gui.Translation[2]/16), 59 | )) 60 | } 61 | if gui.Rotation != nil { 62 | mat = mat.Mul4(mgl32.Rotate3DX(float32(gui.Rotation[0]/180) * math.Pi).Mat4()) 63 | mat = mat.Mul4(mgl32.Rotate3DZ(float32(gui.Rotation[2]/180) * math.Pi).Mat4()) 64 | mat = mat.Mul4(mgl32.Rotate3DY(float32(gui.Rotation[1]/180) * math.Pi).Mat4()) 65 | } 66 | } 67 | 68 | var verts []*ui.ModelVertex 69 | 70 | p := precomputeModel(mdl) 71 | for _, f := range p.faces { 72 | var cr, cg, cb byte 73 | cr = 255 74 | cg = 255 75 | cb = 255 76 | if block != nil && block.TintImage() != nil { 77 | switch f.tintIndex { 78 | case 0: 79 | bi := biome.Plains 80 | ix := bi.ColorIndex & 0xFF 81 | iy := bi.ColorIndex >> 8 82 | col := block.TintImage().NRGBAAt(ix, iy) 83 | cr = byte(col.R) 84 | cg = byte(col.G) 85 | cb = byte(col.B) 86 | } 87 | } 88 | if f.facing == direction.East || f.facing == direction.West { 89 | cr = byte(float64(cr) * 0.8) 90 | cg = byte(float64(cg) * 0.8) 91 | cb = byte(float64(cb) * 0.8) 92 | } 93 | if f.facing == direction.North || f.facing == direction.South { 94 | cr = byte(float64(cr) * 0.6) 95 | cg = byte(float64(cg) * 0.6) 96 | cb = byte(float64(cb) * 0.6) 97 | } 98 | 99 | for _, vert := range f.vertices { 100 | vert := &ui.ModelVertex{ 101 | X: vert.X, 102 | Y: vert.Y, 103 | Z: vert.Z, 104 | TOffsetX: vert.TOffsetX, 105 | TOffsetY: vert.TOffsetY, 106 | R: cr, 107 | G: cg, 108 | B: cb, 109 | A: 255, 110 | TX: vert.TX, 111 | TY: vert.TY, 112 | TW: vert.TW, 113 | TH: vert.TH, 114 | TAtlas: vert.TAtlas, 115 | } 116 | verts = append(verts, vert) 117 | } 118 | } 119 | return ui.NewModel(0, 0, verts, mat) 120 | } 121 | -------------------------------------------------------------------------------- /mojangverify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "archive/zip" 19 | "crypto" 20 | "crypto/rsa" 21 | "crypto/sha1" 22 | "crypto/x509" 23 | "fmt" 24 | "io" 25 | "io/ioutil" 26 | "net/http" 27 | "os" 28 | "sync" 29 | ) 30 | 31 | const ( 32 | authlibURL = "https://libraries.minecraft.net/com/mojang/authlib/%[1]s/authlib-%[1]s-sources.jar" 33 | authlibVersion = "1.5.21" 34 | authlibKeyPath = "yggdrasil_session_pubkey.der" 35 | ) 36 | 37 | var ( 38 | hasAuthlib bool 39 | authlibLock sync.Mutex 40 | authlibKey *rsa.PublicKey 41 | ) 42 | 43 | func init() { 44 | if _, err := os.Stat(authlibKeyPath); os.IsNotExist(err) { 45 | authlibLock.Lock() 46 | syncChan <- func() { 47 | go func() { 48 | getAuthlib() 49 | parseAuthlibKey() 50 | hasAuthlib = true 51 | authlibLock.Unlock() 52 | }() 53 | } 54 | } else { 55 | parseAuthlibKey() 56 | hasAuthlib = true 57 | } 58 | } 59 | 60 | func verifySkinSignature(data, sig []byte) error { 61 | s := sha1.New() 62 | s.Write(data) 63 | return rsa.VerifyPKCS1v15(getAuthlibKey(), crypto.SHA1, s.Sum(nil), sig) 64 | } 65 | 66 | func getAuthlibKey() *rsa.PublicKey { 67 | if hasAuthlib { 68 | return authlibKey 69 | } 70 | authlibLock.Lock() 71 | defer authlibLock.Unlock() 72 | if !hasAuthlib { 73 | panic("Invalid state, missing authlib key") 74 | } 75 | return authlibKey 76 | } 77 | 78 | func parseAuthlibKey() { 79 | f, err := os.Open(authlibKeyPath) 80 | if err != nil { 81 | panic(err) 82 | } 83 | defer f.Close() 84 | bytes, err := ioutil.ReadAll(f) 85 | if err != nil { 86 | panic(err) 87 | } 88 | p, err := x509.ParsePKIXPublicKey(bytes) 89 | if err != nil { 90 | panic(err) 91 | } 92 | authlibKey = p.(*rsa.PublicKey) 93 | } 94 | 95 | func getAuthlib() { 96 | target := fmt.Sprintf("authlib-%[1]s-sources.jar", authlibVersion) 97 | fmt.Printf("Obtaining authlib %s\n", authlibVersion) 98 | resp, err := http.Get(fmt.Sprintf(authlibURL, authlibVersion)) 99 | if err != nil { 100 | panic(err) 101 | } 102 | defer resp.Body.Close() 103 | os.MkdirAll("./", 0777) 104 | f, err := os.Create(target + ".tmp") 105 | if err != nil { 106 | panic(err) 107 | } 108 | defer os.Remove(target + ".tmp") 109 | defer f.Close() 110 | 111 | size, err := io.Copy(f, resp.Body) 112 | if err != nil { 113 | panic(err) 114 | } 115 | 116 | f.Seek(0, 0) // Go back to the start 117 | fr, err := zip.NewReader(f, size) 118 | 119 | for _, f := range fr.File { 120 | if f.Name != authlibKeyPath { 121 | continue 122 | } 123 | func() { 124 | path := authlibKeyPath 125 | w, err := os.Create(path) 126 | if err != nil { 127 | panic(err) 128 | } 129 | defer w.Close() 130 | r, err := f.Open() 131 | if err != nil { 132 | panic(err) 133 | } 134 | defer r.Close() 135 | _, err = io.Copy(w, r) 136 | if err != nil { 137 | panic(err) 138 | } 139 | }() 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /native/native.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package native 16 | 17 | import ( 18 | "encoding/binary" 19 | "unsafe" 20 | ) 21 | 22 | // Order is the native byte order of the system 23 | var Order binary.ByteOrder 24 | 25 | func init() { 26 | check := uint32(1) 27 | c := (*[4]byte)(unsafe.Pointer(&check)) 28 | Order = binary.BigEndian 29 | if binary.LittleEndian.Uint32(c[:]) == 1 { 30 | Order = binary.LittleEndian 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pluginmessage.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "reflect" 21 | 22 | "github.com/thinkofdeath/steven/console" 23 | "github.com/thinkofdeath/steven/protocol" 24 | ) 25 | 26 | var ( 27 | pluginMessagesServerbound = map[string]reflect.Type{} 28 | pluginMessagesClientbound = map[string]reflect.Type{} 29 | ) 30 | 31 | func registerPluginMessage(pm pluginMessage, serverbound bool) { 32 | t := reflect.TypeOf(pm).Elem() 33 | if serverbound { 34 | pluginMessagesServerbound[pm.channel()] = t 35 | } else { 36 | pluginMessagesClientbound[pm.channel()] = t 37 | } 38 | } 39 | 40 | func (h handler) handlePluginMessage(channel string, r io.Reader, serverbound bool) { 41 | var pm reflect.Type 42 | var ok bool 43 | if serverbound { 44 | pm, ok = pluginMessagesServerbound[channel] 45 | } else { 46 | pm, ok = pluginMessagesClientbound[channel] 47 | } 48 | if !ok { 49 | console.Text("Unhandled plugin message %s", channel) 50 | return 51 | } 52 | p := reflect.New(pm).Interface().(pluginMessage) 53 | err := p.read(r) 54 | if err != nil { 55 | console.Text("Failed to handle plugin message %s: %s", channel, err) 56 | return 57 | } 58 | h.Handle(p) 59 | } 60 | 61 | func sendPluginMessage(pm pluginMessage) { 62 | var buf bytes.Buffer 63 | pm.write(&buf) 64 | Client.network.Write(&protocol.PluginMessageServerbound{ 65 | Channel: pm.channel(), 66 | Data: buf.Bytes(), 67 | }) 68 | } 69 | 70 | //go:generate protocol_builder $GOFILE 71 | 72 | type pluginMessage interface { 73 | write(io.Writer) error 74 | read(io.Reader) error 75 | channel() string 76 | } 77 | 78 | // This is a packet 79 | type pmMinecraftBrand struct { 80 | Brand string 81 | } 82 | 83 | func (*pmMinecraftBrand) channel() string { 84 | return "MC|Brand" 85 | } 86 | 87 | func init() { 88 | registerPluginMessage((*pmMinecraftBrand)(nil), true) 89 | registerPluginMessage((*pmMinecraftBrand)(nil), false) 90 | } 91 | -------------------------------------------------------------------------------- /pluginmessage_proto.go: -------------------------------------------------------------------------------- 1 | // Generated by protocol_builder 2 | // Do not edit 3 | 4 | package steven 5 | 6 | import ( 7 | "io" 8 | 9 | "github.com/thinkofdeath/steven/protocol" 10 | ) 11 | 12 | func (p *pmMinecraftBrand) write(ww io.Writer) (err error) { 13 | if err = protocol.WriteString(ww, p.Brand); err != nil { 14 | return 15 | } 16 | return 17 | } 18 | func (p *pmMinecraftBrand) read(rr io.Reader) (err error) { 19 | if p.Brand, err = protocol.ReadString(rr); err != nil { 20 | return 21 | } 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /position.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "github.com/go-gl/mathgl/mgl32" 19 | "github.com/thinkofdeath/steven/type/direction" 20 | ) 21 | 22 | type Position struct { 23 | X, Y, Z int 24 | } 25 | 26 | func (p Position) Shift(x, y, z int) Position { 27 | return Position{X: p.X + x, Y: p.Y + y, Z: p.Z + z} 28 | } 29 | 30 | func (p Position) ShiftDir(d direction.Type) Position { 31 | return p.Shift(d.Offset()) 32 | } 33 | 34 | func (p Position) Get() (int, int, int) { 35 | return p.X, p.Y, p.Z 36 | } 37 | 38 | func (p Position) Vec() mgl32.Vec3 { 39 | return mgl32.Vec3{float32(p.X), float32(p.Y), float32(p.Z)} 40 | } 41 | -------------------------------------------------------------------------------- /progress.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "github.com/thinkofdeath/steven/render" 22 | "github.com/thinkofdeath/steven/ui" 23 | ) 24 | 25 | var progressBars []*progressBar 26 | 27 | type progressBar struct { 28 | id int 29 | bar *ui.Image 30 | text *ui.Text 31 | } 32 | 33 | func newProgressBar() *progressBar { 34 | p := &progressBar{ 35 | id: len(progressBars), 36 | } 37 | p.bar = ui.NewImage(render.GetTexture("solid"), 0, 21*float64(p.id), 854, 21, 0, 0, 1, 1, 0, 125, 0) 38 | ui.AddDrawable(p.bar.Attach(ui.Top, ui.Left)) 39 | p.text = ui.NewText("", 1, 21*float64(p.id)+1, 255, 255, 255) 40 | ui.AddDrawable(p.text.Attach(ui.Top, ui.Left)) 41 | progressBars = append(progressBars, p) 42 | p.bar.SetLayer(-241) 43 | p.text.SetLayer(-240) 44 | return p 45 | } 46 | 47 | func (p *progressBar) update(progress float64, text string) { 48 | p.text.Update(text) 49 | width, _ := window.GetFramebufferSize() 50 | sw := 854 / float64(width) 51 | if ui.DrawMode == ui.Unscaled { 52 | sw = ui.Scale 53 | p.bar.SetWidth((854 / sw) * progress) 54 | } else { 55 | p.bar.SetWidth(float64(width) * progress) 56 | } 57 | } 58 | 59 | func (p *progressBar) remove() { 60 | p.bar.Remove() 61 | p.text.Remove() 62 | for i, po := range progressBars { 63 | if po == p { 64 | progressBars = append(progressBars[:i], progressBars[i+1:]...) 65 | break 66 | } 67 | } 68 | for i, po := range progressBars { 69 | po.id = i 70 | po.bar.SetY(21 * float64(po.id)) 71 | po.text.SetY(21*float64(po.id) + 1) 72 | } 73 | } 74 | 75 | func (p *progressBar) watchCopy(dst io.Writer, src io.Reader, size int64, text string) (n int64, err error) { 76 | buf := make([]byte, 4906) 77 | for { 78 | nn, err := src.Read(buf) 79 | if nn > 0 { 80 | _, err := dst.Write(buf[:nn]) 81 | n += int64(nn) 82 | progress := float64(n) / float64(size) 83 | syncChan <- func() { 84 | if size == -1 { 85 | p.update(0.5, fmt.Sprintf(text, "??")) 86 | } else { 87 | p.update(progress, fmt.Sprintf(text, int(100*progress))) 88 | } 89 | } 90 | if err != nil { 91 | return n, err 92 | } 93 | } 94 | if err == io.EOF { 95 | break 96 | } 97 | if err != nil { 98 | return n, err 99 | } 100 | } 101 | return 102 | } 103 | -------------------------------------------------------------------------------- /protocol/connection_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package protocol 16 | 17 | import ( 18 | "bytes" 19 | "reflect" 20 | "strings" 21 | "testing" 22 | ) 23 | 24 | func TestBasic(t *testing.T) { 25 | h := &Handshake{ 26 | ProtocolVersion: 4, 27 | Host: "", 28 | Port: 25565, 29 | Next: 1, 30 | } 31 | buf := &bytes.Buffer{} 32 | h.write(buf) 33 | 34 | h2 := &Handshake{} 35 | h2.read(bytes.NewReader(buf.Bytes())) 36 | 37 | if !reflect.DeepEqual(h, h2) { 38 | t.Fail() 39 | } 40 | } 41 | 42 | // Created by fuzzing 43 | var testData = []string{ 44 | "\xff\xfd\x80\xc0(", 45 | "00000000000\xc000000000" + 46 | "00000000000000000000" + 47 | "00000000000", 48 | "0d000000000000000000" + 49 | "00000000000000000000" + 50 | "00000000000", 51 | "0 000000000000000000" + 52 | "00000000000000000000" + 53 | "00000000000000000000" + 54 | "0000", 55 | "000\x800000000000000000" + 56 | "00000000000000000000" + 57 | "000000000000", 58 | "0 000000000000000000" + 59 | "0000000000\x1e\v00000000" + 60 | "00000000000000000000" + 61 | "000", 62 | "0 0000000000000ee000" + 63 | "00000000000000000000" + 64 | "00000000000000000000" + 65 | "0000", 66 | "00000000000\xc000000000" + 67 | "00000000000000000000" + 68 | "000000000", 69 | "\x13\x1c0\x80\xc0\xfd\x80\xc0(00000000000", 70 | } 71 | 72 | func TestCrashers(t *testing.T) { 73 | for _, str := range testData { 74 | c := &Conn{ 75 | r: strings.NewReader(str), 76 | w: nil, 77 | net: nil, 78 | direction: serverbound, 79 | State: Play, 80 | compressionThreshold: -1, 81 | } 82 | c.readPacket() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /protocol/crypto.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package protocol 16 | 17 | import ( 18 | "crypto/cipher" 19 | ) 20 | 21 | type cfb8Stream struct { 22 | block cipher.Block 23 | iv, orig []byte 24 | offset int 25 | decrypt bool 26 | } 27 | 28 | func newCFB8(block cipher.Block, key []byte, decrypt bool) *cfb8Stream { 29 | cp := make([]byte, 16) 30 | copy(cp, key) 31 | orig := make([]byte, 32) 32 | copy(orig, key) 33 | copy(orig[16:], key) 34 | return &cfb8Stream{ 35 | block: block, 36 | iv: cp, 37 | orig: orig, 38 | decrypt: decrypt, 39 | } 40 | } 41 | 42 | func (c *cfb8Stream) XORKeyStream(dst, src []byte) { 43 | for i, b := range src { 44 | c.block.Encrypt(c.iv, c.iv) 45 | b ^= c.iv[0] 46 | 47 | if c.decrypt { 48 | c.orig[c.offset] = src[i] 49 | c.orig[c.offset+16] = src[i] 50 | } else { 51 | c.orig[c.offset] = b 52 | c.orig[c.offset+16] = b 53 | } 54 | c.offset = (c.offset + 1) & 0xF 55 | copy(c.iv, c.orig[c.offset:]) 56 | dst[i] = b 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /protocol/handshaking.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:generate protocol_builder $GOFILE Handshaking serverbound 16 | 17 | package protocol 18 | 19 | // Handshake is the first packet sent in the protocol. 20 | // Its used for deciding if the request is a client 21 | // is requesting status information about the server 22 | // (MOTD, players etc) or trying to login to the server. 23 | // 24 | // The host and port fields are not used by the vanilla 25 | // server but are there for virtual server hosting to 26 | // be able to redirect a client to a target server with 27 | // a single address + port. 28 | // 29 | // Some modified servers/proxies use the handshake field 30 | // differently, packing information into the field other 31 | // than the hostname due to the protocol not providing 32 | // any system for custom information to be transfered 33 | // by the client to the server until after login. 34 | // 35 | // This is a Minecraft packet 36 | type Handshake struct { 37 | // The protocol version of the connecting client 38 | ProtocolVersion VarInt 39 | // The hostname the client connected to 40 | Host string 41 | // The port the client connected to 42 | Port uint16 43 | // The next protocol state the client wants 44 | Next VarInt 45 | } 46 | -------------------------------------------------------------------------------- /protocol/handshaking_proto.go: -------------------------------------------------------------------------------- 1 | // Generated by protocol_builder 2 | // Do not edit 3 | 4 | package protocol 5 | 6 | import ( 7 | "io" 8 | ) 9 | 10 | func (h *Handshake) id() int { return 0 } 11 | func (h *Handshake) write(ww io.Writer) (err error) { 12 | var tmp [2]byte 13 | if err = WriteVarInt(ww, h.ProtocolVersion); err != nil { 14 | return 15 | } 16 | if err = WriteString(ww, h.Host); err != nil { 17 | return 18 | } 19 | tmp[0] = byte(h.Port >> 8) 20 | tmp[1] = byte(h.Port >> 0) 21 | if _, err = ww.Write(tmp[:2]); err != nil { 22 | return 23 | } 24 | if err = WriteVarInt(ww, h.Next); err != nil { 25 | return 26 | } 27 | return 28 | } 29 | func (h *Handshake) read(rr io.Reader) (err error) { 30 | var tmp [2]byte 31 | if h.ProtocolVersion, err = ReadVarInt(rr); err != nil { 32 | return 33 | } 34 | if h.Host, err = ReadString(rr); err != nil { 35 | return 36 | } 37 | if _, err = rr.Read(tmp[:2]); err != nil { 38 | return 39 | } 40 | h.Port = (uint16(tmp[1]) << 0) | (uint16(tmp[0]) << 8) 41 | if h.Next, err = ReadVarInt(rr); err != nil { 42 | return 43 | } 44 | return 45 | } 46 | 47 | func init() { 48 | packetCreator[Handshaking][serverbound][0] = func() Packet { return &Handshake{} } 49 | } 50 | -------------------------------------------------------------------------------- /protocol/item.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package protocol 16 | 17 | import ( 18 | "encoding/binary" 19 | "io" 20 | 21 | "github.com/thinkofdeath/steven/encoding/nbt" 22 | ) 23 | 24 | // ItemStack is a stack of items of a single type that can be serilized 25 | // in the protocol. 26 | type ItemStack struct { 27 | ID int16 28 | Count byte 29 | Damage int16 30 | NBT *nbt.Compound 31 | } 32 | 33 | // Serialize writes the item stack into the writer. 34 | func (i *ItemStack) Serialize(w io.Writer) error { 35 | if err := binary.Write(w, binary.BigEndian, i.ID); err != nil { 36 | return err 37 | } 38 | if i.ID == -1 { 39 | return nil 40 | } 41 | if err := binary.Write(w, binary.BigEndian, i.Count); err != nil { 42 | return err 43 | } 44 | if err := binary.Write(w, binary.BigEndian, i.Damage); err != nil { 45 | return err 46 | } 47 | return WriteNBT(w, i.NBT) 48 | } 49 | 50 | // Deserialize reads an item stack from the reader into this item stack. 51 | func (i *ItemStack) Deserialize(r io.Reader) error { 52 | if err := binary.Read(r, binary.BigEndian, &i.ID); err != nil { 53 | return err 54 | } 55 | if i.ID == -1 { 56 | return nil 57 | } 58 | if err := binary.Read(r, binary.BigEndian, &i.Count); err != nil { 59 | return err 60 | } 61 | if err := binary.Read(r, binary.BigEndian, &i.Damage); err != nil { 62 | return err 63 | } 64 | var err error 65 | i.NBT, err = ReadNBT(r) 66 | return err 67 | } 68 | -------------------------------------------------------------------------------- /protocol/login.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package protocol 16 | 17 | import ( 18 | "crypto/rand" 19 | "crypto/rsa" 20 | "crypto/x509" 21 | "errors" 22 | "fmt" 23 | 24 | "github.com/thinkofdeath/steven/protocol/mojang" 25 | ) 26 | 27 | // BUG(Think) LoginToServer doesn't support offline mode. Call it a feature? 28 | 29 | // LoginToServer sends the necessary packets to join a server. This 30 | // also authenticates the request with mojang for online mode connections. 31 | // This stops before LoginSuccess (or any other preceding packets). 32 | func (c *Conn) LoginToServer(profile mojang.Profile) (err error) { 33 | err = c.WritePacket(&Handshake{ 34 | ProtocolVersion: SupportedProtocolVersion, 35 | Host: c.host, 36 | Port: c.port, 37 | Next: VarInt(Login - 1), 38 | }) 39 | if err != nil { 40 | return 41 | } 42 | c.State = Login 43 | if err = c.WritePacket(&LoginStart{ 44 | Username: profile.Username, 45 | }); err != nil { 46 | return 47 | } 48 | 49 | var packet Packet 50 | if packet, err = c.ReadPacket(); err != nil { 51 | return 52 | } 53 | 54 | req, err := checkLoginPacket(c, packet) 55 | if err != nil { 56 | return err 57 | } 58 | var p interface{} 59 | if p, err = x509.ParsePKIXPublicKey(req.PublicKey); err != nil { 60 | return 61 | } 62 | pub := p.(*rsa.PublicKey) 63 | 64 | key := make([]byte, 16) 65 | n, err := rand.Read(key) 66 | if n != 16 || err != nil { 67 | return errors.New("crypto error") 68 | } 69 | 70 | sharedKey, err := rsa.EncryptPKCS1v15(rand.Reader, pub, key) 71 | if err != nil { 72 | return 73 | } 74 | verifyToken, err := rsa.EncryptPKCS1v15(rand.Reader, pub, req.VerifyToken) 75 | if err != nil { 76 | return 77 | } 78 | 79 | err = mojang.JoinServer(profile, []byte(req.ServerID), key, req.PublicKey) 80 | if err != nil { 81 | return 82 | } 83 | 84 | err = c.WritePacket(&EncryptionResponse{ 85 | SharedSecret: sharedKey, 86 | VerifyToken: verifyToken, 87 | }) 88 | if err != nil { 89 | return 90 | } 91 | 92 | err = c.EnableEncryption(key) 93 | return 94 | } 95 | 96 | func checkLoginPacket(c *Conn, p Packet) (*EncryptionRequest, error) { 97 | switch p := p.(type) { 98 | case *EncryptionRequest: 99 | return p, nil 100 | case *LoginDisconnect: 101 | return nil, errors.New(p.Reason.String()) 102 | case *LoginSuccess: 103 | return nil, errors.New("server is in offline mode which is currently unsupported") 104 | case *SetInitialCompression: 105 | c.SetCompression(int(p.Threshold)) 106 | p2, err := c.ReadPacket() 107 | if err != nil { 108 | return nil, err 109 | } 110 | return checkLoginPacket(c, p2) 111 | default: 112 | return nil, fmt.Errorf("unexpected packet %#v", p) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /protocol/login_clientbound.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:generate protocol_builder $GOFILE Login clientbound 16 | 17 | package protocol 18 | 19 | import ( 20 | "github.com/thinkofdeath/steven/format" 21 | ) 22 | 23 | // LoginDisconnect is sent by the server if there was any issues 24 | // authenticating the player during login or the general server 25 | // issues (e.g. too many players). 26 | // 27 | // This is a Minecraft packet 28 | type LoginDisconnect struct { 29 | Reason format.AnyComponent `as:"json"` 30 | } 31 | 32 | // EncryptionRequest is sent by the server if the server is in 33 | // online mode. If it is not sent then its assumed the server is 34 | // in offline mode. 35 | // 36 | // This is a Minecraft packet 37 | type EncryptionRequest struct { 38 | // Generally empty, left in from legacy auth 39 | // but is still used by the client if provided 40 | ServerID string 41 | // A RSA Public key serialized in x.509 PRIX format 42 | PublicKey []byte `length:"VarInt"` 43 | // Token used by the server to verify encryption is working 44 | // correctly 45 | VerifyToken []byte `length:"VarInt"` 46 | } 47 | 48 | // LoginSuccess is sent by the server if the player successfully 49 | // authenicates with the session servers (online mode) or straight 50 | // after LoginStart (offline mode). 51 | // 52 | // This is a Minecraft packet 53 | type LoginSuccess struct { 54 | // String encoding of a uuid (with hyphens) 55 | UUID string 56 | Username string 57 | } 58 | 59 | // SetInitialCompression sets the compression threshold during the 60 | // login state. 61 | // 62 | // This is a Minecraft packet 63 | type SetInitialCompression struct { 64 | // Threshold where a packet should be sent compressed 65 | Threshold VarInt 66 | } 67 | -------------------------------------------------------------------------------- /protocol/login_clientbound_proto.go: -------------------------------------------------------------------------------- 1 | // Generated by protocol_builder 2 | // Do not edit 3 | 4 | package protocol 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "math" 11 | ) 12 | 13 | func (l *LoginDisconnect) id() int { return 0 } 14 | func (l *LoginDisconnect) write(ww io.Writer) (err error) { 15 | var tmp0 []byte 16 | if tmp0, err = json.Marshal(&l.Reason); err != nil { 17 | return 18 | } 19 | tmp1 := string(tmp0) 20 | if err = WriteString(ww, tmp1); err != nil { 21 | return 22 | } 23 | return 24 | } 25 | func (l *LoginDisconnect) read(rr io.Reader) (err error) { 26 | var tmp0 string 27 | if tmp0, err = ReadString(rr); err != nil { 28 | return err 29 | } 30 | if err = json.Unmarshal([]byte(tmp0), &l.Reason); err != nil { 31 | return 32 | } 33 | return 34 | } 35 | 36 | func (e *EncryptionRequest) id() int { return 1 } 37 | func (e *EncryptionRequest) write(ww io.Writer) (err error) { 38 | if err = WriteString(ww, e.ServerID); err != nil { 39 | return 40 | } 41 | if err = WriteVarInt(ww, VarInt(len(e.PublicKey))); err != nil { 42 | return 43 | } 44 | if _, err = ww.Write(e.PublicKey); err != nil { 45 | return 46 | } 47 | if err = WriteVarInt(ww, VarInt(len(e.VerifyToken))); err != nil { 48 | return 49 | } 50 | if _, err = ww.Write(e.VerifyToken); err != nil { 51 | return 52 | } 53 | return 54 | } 55 | func (e *EncryptionRequest) read(rr io.Reader) (err error) { 56 | if e.ServerID, err = ReadString(rr); err != nil { 57 | return 58 | } 59 | var tmp0 VarInt 60 | if tmp0, err = ReadVarInt(rr); err != nil { 61 | return 62 | } 63 | if tmp0 > math.MaxInt16 { 64 | return fmt.Errorf("array larger than max value: %d > %d", tmp0, math.MaxInt16) 65 | } 66 | if tmp0 < 0 { 67 | return fmt.Errorf("negative array size: %d < 0", tmp0) 68 | } 69 | e.PublicKey = make([]byte, tmp0) 70 | if _, err = rr.Read(e.PublicKey); err != nil { 71 | return 72 | } 73 | var tmp1 VarInt 74 | if tmp1, err = ReadVarInt(rr); err != nil { 75 | return 76 | } 77 | if tmp1 > math.MaxInt16 { 78 | return fmt.Errorf("array larger than max value: %d > %d", tmp1, math.MaxInt16) 79 | } 80 | if tmp1 < 0 { 81 | return fmt.Errorf("negative array size: %d < 0", tmp1) 82 | } 83 | e.VerifyToken = make([]byte, tmp1) 84 | if _, err = rr.Read(e.VerifyToken); err != nil { 85 | return 86 | } 87 | return 88 | } 89 | 90 | func (l *LoginSuccess) id() int { return 2 } 91 | func (l *LoginSuccess) write(ww io.Writer) (err error) { 92 | if err = WriteString(ww, l.UUID); err != nil { 93 | return 94 | } 95 | if err = WriteString(ww, l.Username); err != nil { 96 | return 97 | } 98 | return 99 | } 100 | func (l *LoginSuccess) read(rr io.Reader) (err error) { 101 | if l.UUID, err = ReadString(rr); err != nil { 102 | return 103 | } 104 | if l.Username, err = ReadString(rr); err != nil { 105 | return 106 | } 107 | return 108 | } 109 | 110 | func (s *SetInitialCompression) id() int { return 3 } 111 | func (s *SetInitialCompression) write(ww io.Writer) (err error) { 112 | if err = WriteVarInt(ww, s.Threshold); err != nil { 113 | return 114 | } 115 | return 116 | } 117 | func (s *SetInitialCompression) read(rr io.Reader) (err error) { 118 | if s.Threshold, err = ReadVarInt(rr); err != nil { 119 | return 120 | } 121 | return 122 | } 123 | 124 | func init() { 125 | packetCreator[Login][clientbound][0] = func() Packet { return &LoginDisconnect{} } 126 | packetCreator[Login][clientbound][1] = func() Packet { return &EncryptionRequest{} } 127 | packetCreator[Login][clientbound][2] = func() Packet { return &LoginSuccess{} } 128 | packetCreator[Login][clientbound][3] = func() Packet { return &SetInitialCompression{} } 129 | } 130 | -------------------------------------------------------------------------------- /protocol/login_serverbound.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:generate protocol_builder $GOFILE Login serverbound 16 | 17 | package protocol 18 | 19 | // LoginStart is sent immeditately after switching into the login 20 | // state. The passed username is used by the server to authenticate 21 | // the player in online mode. 22 | // 23 | // This is a Minecraft packet 24 | type LoginStart struct { 25 | Username string 26 | } 27 | 28 | // EncryptionResponse is sent as a reply to EncryptionRequest. All 29 | // packets following this one must be encrypted with AES/CFB8 30 | // encryption. 31 | // 32 | // This is a Minecraft packet 33 | type EncryptionResponse struct { 34 | // The key for the AES/CFB8 cipher encrypted with the 35 | // public key 36 | SharedSecret []byte `length:"VarInt"` 37 | // The verify token from the request encrypted with the 38 | // public key 39 | VerifyToken []byte `length:"VarInt"` 40 | } 41 | -------------------------------------------------------------------------------- /protocol/login_serverbound_proto.go: -------------------------------------------------------------------------------- 1 | // Generated by protocol_builder 2 | // Do not edit 3 | 4 | package protocol 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "math" 10 | ) 11 | 12 | func (l *LoginStart) id() int { return 0 } 13 | func (l *LoginStart) write(ww io.Writer) (err error) { 14 | if err = WriteString(ww, l.Username); err != nil { 15 | return 16 | } 17 | return 18 | } 19 | func (l *LoginStart) read(rr io.Reader) (err error) { 20 | if l.Username, err = ReadString(rr); err != nil { 21 | return 22 | } 23 | return 24 | } 25 | 26 | func (e *EncryptionResponse) id() int { return 1 } 27 | func (e *EncryptionResponse) write(ww io.Writer) (err error) { 28 | if err = WriteVarInt(ww, VarInt(len(e.SharedSecret))); err != nil { 29 | return 30 | } 31 | if _, err = ww.Write(e.SharedSecret); err != nil { 32 | return 33 | } 34 | if err = WriteVarInt(ww, VarInt(len(e.VerifyToken))); err != nil { 35 | return 36 | } 37 | if _, err = ww.Write(e.VerifyToken); err != nil { 38 | return 39 | } 40 | return 41 | } 42 | func (e *EncryptionResponse) read(rr io.Reader) (err error) { 43 | var tmp0 VarInt 44 | if tmp0, err = ReadVarInt(rr); err != nil { 45 | return 46 | } 47 | if tmp0 > math.MaxInt16 { 48 | return fmt.Errorf("array larger than max value: %d > %d", tmp0, math.MaxInt16) 49 | } 50 | if tmp0 < 0 { 51 | return fmt.Errorf("negative array size: %d < 0", tmp0) 52 | } 53 | e.SharedSecret = make([]byte, tmp0) 54 | if _, err = rr.Read(e.SharedSecret); err != nil { 55 | return 56 | } 57 | var tmp1 VarInt 58 | if tmp1, err = ReadVarInt(rr); err != nil { 59 | return 60 | } 61 | if tmp1 > math.MaxInt16 { 62 | return fmt.Errorf("array larger than max value: %d > %d", tmp1, math.MaxInt16) 63 | } 64 | if tmp1 < 0 { 65 | return fmt.Errorf("negative array size: %d < 0", tmp1) 66 | } 67 | e.VerifyToken = make([]byte, tmp1) 68 | if _, err = rr.Read(e.VerifyToken); err != nil { 69 | return 70 | } 71 | return 72 | } 73 | 74 | func init() { 75 | packetCreator[Login][serverbound][0] = func() Packet { return &LoginStart{} } 76 | packetCreator[Login][serverbound][1] = func() Packet { return &EncryptionResponse{} } 77 | } 78 | -------------------------------------------------------------------------------- /protocol/mojang/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mojang 16 | 17 | import ( 18 | "bytes" 19 | "crypto/sha1" 20 | "encoding/hex" 21 | "encoding/json" 22 | "fmt" 23 | "io/ioutil" 24 | "net/http" 25 | "strings" 26 | ) 27 | 28 | const joinURL = "https://sessionserver.mojang.com/session/minecraft/join" 29 | 30 | type joinData struct { 31 | AccessToken string `json:"accessToken"` 32 | SelectedProfile string `json:"selectedProfile"` 33 | ServerID string `json:"serverId"` 34 | } 35 | 36 | type Error struct { 37 | Message string `json:"errorMessage"` 38 | Type string `json:"error"` 39 | } 40 | 41 | func (m Error) Error() string { 42 | return fmt.Sprintf("%s: %s", m.Type, m.Message) 43 | } 44 | 45 | // JoinServer tries to mark the server has joined on mojang's session servers 46 | // using the passed profile and bytes (as the server hash). The hash is normally 47 | // the serverID + secret key + public key. 48 | func JoinServer(profile Profile, serverHash ...[]byte) error { 49 | h := sha1.New() 50 | for _, sh := range serverHash { 51 | h.Write(sh) 52 | } 53 | hash := h.Sum(nil) 54 | 55 | // Mojang uses a hex method which allows for 56 | // negatives so we have to account for that. 57 | negative := (hash[0] & 0x80) == 0x80 58 | if negative { 59 | twosCompliment(hash) 60 | } 61 | serverID := hex.EncodeToString(hash) 62 | serverID = strings.TrimLeft(serverID, "0") 63 | if negative { 64 | serverID = "-" + serverID 65 | } 66 | 67 | b, err := json.Marshal(joinData{ 68 | AccessToken: profile.AccessToken, 69 | SelectedProfile: profile.ID, 70 | ServerID: serverID, 71 | }) 72 | if err != nil { 73 | return err 74 | } 75 | r := bytes.NewReader(b) 76 | resp, err := http.Post(joinURL, "application/json", r) 77 | if err != nil { 78 | return err 79 | } 80 | defer resp.Body.Close() 81 | 82 | reply, err := ioutil.ReadAll(resp.Body) 83 | if err != nil { 84 | return err 85 | } 86 | if len(reply) != 0 { 87 | var e Error 88 | json.Unmarshal(reply, &e) 89 | return e 90 | } 91 | 92 | return nil 93 | } 94 | 95 | func twosCompliment(p []byte) { 96 | carry := true 97 | for i := len(p) - 1; i >= 0; i-- { 98 | p[i] = ^p[i] 99 | if carry { 100 | carry = p[i] == 0xFF 101 | p[i]++ 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /protocol/mojang/login.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mojang 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "io/ioutil" 21 | "net/http" 22 | ) 23 | 24 | const ( 25 | loginURL = "https://authserver.mojang.com/authenticate" 26 | refreshURL = "https://authserver.mojang.com/refresh" 27 | validateURL = "https://authserver.mojang.com/validate" 28 | ) 29 | 30 | type loginRequest struct { 31 | Agent struct { 32 | Name string `json:"name"` 33 | Version int `json:"version"` 34 | } `json:"agent"` 35 | Username string `json:"username"` 36 | Password string `json:"password"` 37 | ClientToken string `json:"clientToken"` 38 | } 39 | 40 | type loginReply struct { 41 | AccessToken string `json:"accessToken"` 42 | SelectedProfile struct { 43 | ID string `json:"id"` 44 | Name string `json:"name"` 45 | } 46 | } 47 | 48 | // Login tries to login using the passed username (or email) and password 49 | // and returns the complete profile. error is non-nil if the login 50 | // fails. 51 | func Login(username, password, token string) (Profile, error) { 52 | req := loginRequest{ 53 | Username: username, 54 | Password: password, 55 | ClientToken: token, 56 | } 57 | req.Agent.Name = "Minecraft" 58 | req.Agent.Version = 1 59 | b, err := json.Marshal(req) 60 | if err != nil { 61 | return Profile{}, err 62 | } 63 | r := bytes.NewReader(b) 64 | resp, err := http.Post(loginURL, "application/json", r) 65 | if err != nil { 66 | return Profile{}, err 67 | } 68 | defer resp.Body.Close() 69 | 70 | reply, err := ioutil.ReadAll(resp.Body) 71 | if err != nil { 72 | return Profile{}, err 73 | } 74 | var me Error 75 | err = json.Unmarshal(reply, &me) 76 | if err == nil && me.Type != "" { 77 | return Profile{}, me 78 | } 79 | var lr loginReply 80 | err = json.Unmarshal(reply, &lr) 81 | return Profile{ 82 | AccessToken: lr.AccessToken, 83 | Username: lr.SelectedProfile.Name, 84 | ID: lr.SelectedProfile.ID, 85 | }, err 86 | } 87 | 88 | type refreshRequest struct { 89 | AccessToken string `json:"accessToken"` 90 | ClientToken string `json:"clientToken"` 91 | } 92 | 93 | // Refresh attempts to refresh the passed profile's accessToken 94 | // for futher use. The passed token should be the same as the 95 | // one passed to Login. 96 | func Refresh(profile Profile, token string) (Profile, error) { 97 | req := refreshRequest{ 98 | AccessToken: profile.AccessToken, 99 | ClientToken: token, 100 | } 101 | b, err := json.Marshal(req) 102 | if err != nil { 103 | return Profile{}, err 104 | } 105 | // Try to reuse old token 106 | r := bytes.NewReader(b) 107 | resp, err := http.Post(validateURL, "application/json", r) 108 | if err == nil { 109 | defer resp.Body.Close() 110 | reply, err := ioutil.ReadAll(resp.Body) 111 | if err == nil { 112 | var me Error 113 | err = json.Unmarshal(reply, &me) 114 | if err != nil || me.Type == "" { 115 | return profile, nil 116 | } 117 | } 118 | } 119 | r = bytes.NewReader(b) 120 | 121 | // Try and get a updated one 122 | resp, err = http.Post(refreshURL, "application/json", r) 123 | if err != nil { 124 | return Profile{}, err 125 | } 126 | defer resp.Body.Close() 127 | 128 | reply, err := ioutil.ReadAll(resp.Body) 129 | if err != nil { 130 | return Profile{}, err 131 | } 132 | var me Error 133 | err = json.Unmarshal(reply, &me) 134 | if err == nil && me.Type != "" { 135 | return Profile{}, me 136 | } 137 | var lr loginReply 138 | err = json.Unmarshal(reply, &lr) 139 | return Profile{ 140 | AccessToken: lr.AccessToken, 141 | Username: lr.SelectedProfile.Name, 142 | ID: lr.SelectedProfile.ID, 143 | }, err 144 | } 145 | -------------------------------------------------------------------------------- /protocol/mojang/profile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mojang 16 | 17 | // Profile contains information about the player required 18 | // to connect to a server 19 | type Profile struct { 20 | Username string 21 | ID string 22 | AccessToken string 23 | } 24 | 25 | // IsComplete returns whether the profile is enough to connect 26 | // with. 27 | func (p Profile) IsComplete() bool { 28 | return p.Username != "" && p.ID != "" && p.AccessToken != "" 29 | } 30 | -------------------------------------------------------------------------------- /protocol/state_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type=State; DO NOT EDIT 2 | 3 | package protocol 4 | 5 | import "fmt" 6 | 7 | const _State_name = "HandshakingPlayStatusLogin" 8 | 9 | var _State_index = [...]uint8{0, 11, 15, 21, 26} 10 | 11 | func (i State) String() string { 12 | if i < 0 || i >= State(len(_State_index)-1) { 13 | return fmt.Sprintf("State(%d)", i) 14 | } 15 | return _State_name[_State_index[i]:_State_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /protocol/status.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package protocol 16 | 17 | import ( 18 | "fmt" 19 | "time" 20 | 21 | "github.com/thinkofdeath/steven/format" 22 | ) 23 | 24 | // StatusReply is the reply retrieved from a server when pinging 25 | // it. 26 | type StatusReply struct { 27 | Version struct { 28 | Name string `json:"name"` 29 | Protocol int `json:"protocol"` 30 | } `json:"version"` 31 | Players struct { 32 | Max int `json:"max"` 33 | Online int `json:"online"` 34 | Sample []StatusPlayer `json:"sample,omitempty"` 35 | } `json:"players"` 36 | Description format.AnyComponent `json:"description"` 37 | Favicon string `json:"favicon"` 38 | } 39 | 40 | // StatusPlayer is one of the sample players in a StatusReply 41 | type StatusPlayer struct { 42 | Name string `json:"name"` 43 | ID string `json:"id"` 44 | } 45 | 46 | // RequestStatus starts a status request to the server and 47 | // returns the results of the request. The connection will 48 | // be closed after this request. 49 | func (c *Conn) RequestStatus() (response StatusReply, ping time.Duration, err error) { 50 | defer c.Close() 51 | 52 | err = c.WritePacket(&Handshake{ 53 | ProtocolVersion: SupportedProtocolVersion, 54 | Host: c.host, 55 | Port: c.port, 56 | Next: VarInt(Status - 1), 57 | }) 58 | if err != nil { 59 | return 60 | } 61 | c.State = Status 62 | if err = c.WritePacket(&StatusRequest{}); err != nil { 63 | return 64 | } 65 | 66 | // Get the reply 67 | var packet Packet 68 | if packet, err = c.ReadPacket(); err != nil { 69 | return 70 | } 71 | 72 | resp, ok := packet.(*StatusResponse) 73 | if !ok { 74 | err = fmt.Errorf("unexpected packet %#v", packet) 75 | return 76 | } 77 | response = resp.Status 78 | 79 | t := time.Now() 80 | if err = c.WritePacket(&StatusPing{ 81 | Time: t.UnixNano(), 82 | }); err != nil { 83 | return 84 | } 85 | 86 | // Get the pong reply 87 | packet, err = c.ReadPacket() 88 | if err != nil { 89 | return 90 | } 91 | 92 | _, ok = packet.(*StatusPong) 93 | if !ok { 94 | err = fmt.Errorf("unexpected packet %#v", packet) 95 | } 96 | ping = time.Now().Sub(t) 97 | return 98 | } 99 | -------------------------------------------------------------------------------- /protocol/status_clientbound.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:generate protocol_builder $GOFILE Status clientbound 16 | 17 | package protocol 18 | 19 | // StatusResponse is sent as a reply to a StatusRequest. 20 | // The Status should contain a json encoded structure with 21 | // version information, a player sample, a description/MOTD 22 | // and optionally a favicon. 23 | // 24 | // The structure is as follows 25 | // { 26 | // "version": { 27 | // "name": "1.8.3", 28 | // "protocol": 47, 29 | // }, 30 | // "players": { 31 | // "max": 20, 32 | // "online": 1, 33 | // "sample": [ 34 | // {"name": "Thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"} 35 | // ] 36 | // }, 37 | // "description": "Hello world", 38 | // "favicon": "data:image/png;base64," 39 | // } 40 | // 41 | // This is a Minecraft packet 42 | type StatusResponse struct { 43 | Status StatusReply `as:"json"` 44 | } 45 | 46 | // StatusPong is sent as a reply to a StatusPing. 47 | // The Time field should be exactly the same as the 48 | // one sent by the client. 49 | // 50 | // This is a Minecraft packet 51 | type StatusPong struct { 52 | Time int64 53 | } 54 | -------------------------------------------------------------------------------- /protocol/status_clientbound_proto.go: -------------------------------------------------------------------------------- 1 | // Generated by protocol_builder 2 | // Do not edit 3 | 4 | package protocol 5 | 6 | import ( 7 | "encoding/json" 8 | "io" 9 | ) 10 | 11 | func (s *StatusResponse) id() int { return 0 } 12 | func (s *StatusResponse) write(ww io.Writer) (err error) { 13 | var tmp0 []byte 14 | if tmp0, err = json.Marshal(&s.Status); err != nil { 15 | return 16 | } 17 | tmp1 := string(tmp0) 18 | if err = WriteString(ww, tmp1); err != nil { 19 | return 20 | } 21 | return 22 | } 23 | func (s *StatusResponse) read(rr io.Reader) (err error) { 24 | var tmp0 string 25 | if tmp0, err = ReadString(rr); err != nil { 26 | return err 27 | } 28 | if err = json.Unmarshal([]byte(tmp0), &s.Status); err != nil { 29 | return 30 | } 31 | return 32 | } 33 | 34 | func (s *StatusPong) id() int { return 1 } 35 | func (s *StatusPong) write(ww io.Writer) (err error) { 36 | var tmp [8]byte 37 | tmp[0] = byte(s.Time >> 56) 38 | tmp[1] = byte(s.Time >> 48) 39 | tmp[2] = byte(s.Time >> 40) 40 | tmp[3] = byte(s.Time >> 32) 41 | tmp[4] = byte(s.Time >> 24) 42 | tmp[5] = byte(s.Time >> 16) 43 | tmp[6] = byte(s.Time >> 8) 44 | tmp[7] = byte(s.Time >> 0) 45 | if _, err = ww.Write(tmp[:8]); err != nil { 46 | return 47 | } 48 | return 49 | } 50 | func (s *StatusPong) read(rr io.Reader) (err error) { 51 | var tmp [8]byte 52 | if _, err = rr.Read(tmp[:8]); err != nil { 53 | return 54 | } 55 | s.Time = int64((uint64(tmp[7]) << 0) | (uint64(tmp[6]) << 8) | (uint64(tmp[5]) << 16) | (uint64(tmp[4]) << 24) | (uint64(tmp[3]) << 32) | (uint64(tmp[2]) << 40) | (uint64(tmp[1]) << 48) | (uint64(tmp[0]) << 56)) 56 | return 57 | } 58 | 59 | func init() { 60 | packetCreator[Status][clientbound][0] = func() Packet { return &StatusResponse{} } 61 | packetCreator[Status][clientbound][1] = func() Packet { return &StatusPong{} } 62 | } 63 | -------------------------------------------------------------------------------- /protocol/status_serverbound.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:generate protocol_builder $GOFILE Status serverbound 16 | 17 | package protocol 18 | 19 | // StatusRequest is sent by the client instantly after 20 | // switching to the Status protocol state and is used 21 | // to signal the server to send a StatusResponse to the 22 | // client 23 | // 24 | // This is a Minecraft packet 25 | type StatusRequest struct { 26 | } 27 | 28 | // StatusPing is sent by the client after recieving a 29 | // StatusResponse. The client uses the time from sending 30 | // the ping until the time of recieving a pong to measure 31 | // the latency between the client and the server. 32 | // 33 | // This is a Minecraft packet 34 | type StatusPing struct { 35 | // The time when the ping was sent 36 | Time int64 37 | } 38 | -------------------------------------------------------------------------------- /protocol/status_serverbound_proto.go: -------------------------------------------------------------------------------- 1 | // Generated by protocol_builder 2 | // Do not edit 3 | 4 | package protocol 5 | 6 | import ( 7 | "io" 8 | ) 9 | 10 | func (s *StatusRequest) id() int { return 0 } 11 | func (s *StatusRequest) write(ww io.Writer) (err error) { 12 | return 13 | } 14 | func (s *StatusRequest) read(rr io.Reader) (err error) { 15 | return 16 | } 17 | 18 | func (s *StatusPing) id() int { return 1 } 19 | func (s *StatusPing) write(ww io.Writer) (err error) { 20 | var tmp [8]byte 21 | tmp[0] = byte(s.Time >> 56) 22 | tmp[1] = byte(s.Time >> 48) 23 | tmp[2] = byte(s.Time >> 40) 24 | tmp[3] = byte(s.Time >> 32) 25 | tmp[4] = byte(s.Time >> 24) 26 | tmp[5] = byte(s.Time >> 16) 27 | tmp[6] = byte(s.Time >> 8) 28 | tmp[7] = byte(s.Time >> 0) 29 | if _, err = ww.Write(tmp[:8]); err != nil { 30 | return 31 | } 32 | return 33 | } 34 | func (s *StatusPing) read(rr io.Reader) (err error) { 35 | var tmp [8]byte 36 | if _, err = rr.Read(tmp[:8]); err != nil { 37 | return 38 | } 39 | s.Time = int64((uint64(tmp[7]) << 0) | (uint64(tmp[6]) << 8) | (uint64(tmp[5]) << 16) | (uint64(tmp[4]) << 24) | (uint64(tmp[3]) << 32) | (uint64(tmp[2]) << 40) | (uint64(tmp[1]) << 48) | (uint64(tmp[0]) << 56)) 40 | return 41 | } 42 | 43 | func init() { 44 | packetCreator[Status][serverbound][0] = func() Packet { return &StatusRequest{} } 45 | packetCreator[Status][serverbound][1] = func() Packet { return &StatusPing{} } 46 | } 47 | -------------------------------------------------------------------------------- /protocol/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package protocol 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | ) 21 | 22 | // Serializable is a type which can be serialized into a packet. 23 | // This is used by protocol_builder when the struct tag 'as' is set 24 | // to "raw". 25 | type Serializable interface { 26 | Serialize(w io.Writer) error 27 | Deserialize(r io.Reader) error 28 | } 29 | 30 | //go:generate stringer -type=State 31 | 32 | // State defined which state the protocol is in. 33 | type State int 34 | 35 | // States of the protocol. 36 | // Handshaking is default. 37 | const ( 38 | Handshaking State = 0 39 | Play State = 1 40 | Status State = 2 41 | Login State = 3 42 | ) 43 | 44 | const ( 45 | // SupportedProtocolVersion is current protocol version this package defines 46 | SupportedProtocolVersion = 71 47 | ) 48 | 49 | const ( 50 | clientbound = iota 51 | serverbound 52 | ) 53 | const maxPacketCount = 100 54 | 55 | var packetCreator [4][2][maxPacketCount]func() Packet 56 | 57 | // VarInt is a variable length integer with a cap of 58 | // 32 bits 59 | type VarInt int32 60 | 61 | // VarLong is a variable length integer with a cap of 62 | // 64 bits 63 | type VarLong int64 64 | 65 | // Position is a location in the world packed into a 64 bit integer 66 | type Position uint64 67 | 68 | // NewPosition creates a Position for the given location. 69 | func NewPosition(x, y, z int) Position { 70 | return ((Position(x) & 0x3FFFFFF) << 38) | 71 | ((Position(y) & 0xFFF) << 26) | 72 | (Position(z) & 0x3FFFFFF) 73 | } 74 | 75 | // X returns the X component of the position 76 | func (p Position) X() int { 77 | return int(int64(p) >> 38) 78 | } 79 | 80 | // Y returns the Y component of the position 81 | func (p Position) Y() int { 82 | return int((int64(p) >> 26) & 0xFFF) 83 | } 84 | 85 | // Z returns the Z component of the position 86 | func (p Position) Z() int { 87 | return int(int64(p) << 38 >> 38) 88 | } 89 | 90 | // String returns a string representation of the position 91 | func (p Position) String() string { 92 | return fmt.Sprintf("%d,%d,%d", p.X(), p.Y(), p.Z()) 93 | } 94 | 95 | // UUID is an unique identifier 96 | type UUID [16]byte 97 | 98 | // Serialize serializes the uuid into the writer 99 | func (u *UUID) Serialize(w io.Writer) error { 100 | _, err := w.Write(u[:]) 101 | return err 102 | } 103 | 104 | // Deserialize deserializes the uuid from the reader 105 | func (u *UUID) Deserialize(r io.Reader) error { 106 | _, err := io.ReadFull(r, u[:]) 107 | return err 108 | } 109 | 110 | // Packet is a structure that can be serialized or deserialized from 111 | // Minecraft connection 112 | type Packet interface { 113 | write(io.Writer) error 114 | read(io.Reader) error 115 | id() int 116 | } 117 | -------------------------------------------------------------------------------- /render/builder/builder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package builder provides a simple way to create buffers to upload to the gpu 16 | package builder 17 | 18 | import ( 19 | "bytes" 20 | "math" 21 | 22 | "github.com/thinkofdeath/steven/native" 23 | ) 24 | 25 | // Types allowed to be used in a buffer. 26 | const ( 27 | UnsignedByte Type = 1 28 | Byte Type = 1 29 | UnsignedShort Type = 2 30 | Short Type = 2 31 | Float Type = 4 32 | ) 33 | 34 | // Type is a type that is allowed in a buffer. 35 | type Type int 36 | 37 | // Buffer is a dynamically sized byte buffer 38 | // for creating data to upload to the gpu. 39 | type Buffer struct { 40 | buf bytes.Buffer 41 | elemSize int 42 | 43 | scratch [8]byte 44 | } 45 | 46 | // New creates a new buffer containing the passed 47 | // types. 48 | func New(types ...Type) *Buffer { 49 | elemSize := 0 50 | for _, t := range types { 51 | elemSize += int(t) 52 | } 53 | b := &Buffer{ 54 | elemSize: elemSize, 55 | } 56 | b.buf.Grow(elemSize * 3000) 57 | return b 58 | } 59 | 60 | // UnsignedByte appends an unsigned byte to the 61 | // buffer. 62 | func (b *Buffer) UnsignedByte(i byte) { 63 | b.buf.WriteByte(i) 64 | } 65 | 66 | // Byte appends a signed byte to the buffer. 67 | func (b *Buffer) Byte(i int8) { 68 | b.UnsignedByte(byte(i)) 69 | } 70 | 71 | // UnsignedShort writes an unsigned short to the 72 | // buffer 73 | func (b *Buffer) UnsignedShort(i uint16) { 74 | d := b.scratch[:2] 75 | native.Order.PutUint16(d, i) 76 | b.buf.Write(d) 77 | } 78 | 79 | // Short writes a short to the buffer. 80 | func (b *Buffer) Short(i int16) { 81 | b.UnsignedShort(uint16(i)) 82 | } 83 | 84 | func (b *Buffer) UnsignedInt(i uint32) { 85 | d := b.scratch[:4] 86 | native.Order.PutUint32(d, i) 87 | b.buf.Write(d) 88 | } 89 | 90 | // Float writes a float to the buffer 91 | func (b *Buffer) Float(f float32) { 92 | d := b.scratch[:4] 93 | i := math.Float32bits(f) 94 | native.Order.PutUint32(d, i) 95 | b.buf.Write(d) 96 | } 97 | 98 | // Write appends the passed bytes to this buffer 99 | func (b *Buffer) Write(p []byte) { 100 | b.buf.Write(p) 101 | } 102 | 103 | // WriteBuffer copies the passed buffer to this buffer 104 | func (b *Buffer) WriteBuffer(o *Buffer) { 105 | o.buf.WriteTo(&b.buf) 106 | } 107 | 108 | // Count returns the number of vertices in the buffer 109 | func (b *Buffer) Count() int { 110 | return b.buf.Len() / b.elemSize 111 | } 112 | 113 | // Data returns a byte slice of the buffer 114 | func (b *Buffer) Data() []byte { 115 | return b.buf.Bytes() 116 | } 117 | 118 | // ElementSize returns the size of a single vertex in the 119 | // buffer 120 | func (b *Buffer) ElementSize() int { 121 | return b.elemSize 122 | } 123 | 124 | // Reset resets the internal buffer ready for reuse 125 | func (b *Buffer) Reset() { 126 | b.buf.Reset() 127 | } 128 | -------------------------------------------------------------------------------- /render/builder/reflect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package builder 16 | 17 | import "reflect" 18 | 19 | // Struct returns a function that will serialize 20 | // structs of the type passed to Struct originally. 21 | // It also returns an array of types that can be 22 | // passed to New 23 | func Struct(i interface{}) (func(*Buffer, interface{}), []Type) { 24 | t := reflect.TypeOf(i) 25 | isPtr := false 26 | if t.Kind() == reflect.Ptr { 27 | t = t.Elem() 28 | isPtr = true 29 | } 30 | l := t.NumField() 31 | 32 | var funcs []func(buf *Buffer, v reflect.Value) 33 | var types []Type 34 | 35 | for j := 0; j < l; j++ { 36 | f := t.Field(j) 37 | 38 | var fu func(buf *Buffer, v reflect.Value) 39 | var t Type 40 | 41 | ii := j 42 | switch f.Type.Kind() { 43 | case reflect.Float32: 44 | fu = func(buf *Buffer, v reflect.Value) { 45 | buf.Float(float32(v.Field(ii).Float())) 46 | } 47 | t = Float 48 | case reflect.Uint16: 49 | fu = func(buf *Buffer, v reflect.Value) { 50 | buf.UnsignedShort(uint16(v.Field(ii).Uint())) 51 | } 52 | t = UnsignedShort 53 | case reflect.Int16: 54 | fu = func(buf *Buffer, v reflect.Value) { 55 | buf.Short(int16(v.Field(ii).Int())) 56 | } 57 | t = Short 58 | case reflect.Uint8: 59 | fu = func(buf *Buffer, v reflect.Value) { 60 | buf.UnsignedByte(uint8(v.Field(ii).Uint())) 61 | } 62 | t = UnsignedByte 63 | case reflect.Int8: 64 | fu = func(buf *Buffer, v reflect.Value) { 65 | buf.Byte(int8(v.Field(ii).Int())) 66 | } 67 | t = Byte 68 | default: 69 | panic("unsupported type " + f.Type.String()) 70 | } 71 | funcs = append(funcs, fu) 72 | types = append(types, t) 73 | } 74 | 75 | return func(buf *Buffer, i interface{}) { 76 | v := reflect.ValueOf(i) 77 | if isPtr { 78 | v = v.Elem() 79 | } 80 | 81 | for _, f := range funcs { 82 | f(buf, v) 83 | } 84 | }, types 85 | } 86 | -------------------------------------------------------------------------------- /render/camera.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | import "math" 18 | 19 | // Camera is the main camera for the renderer 20 | var Camera = &ClientCamera{} 21 | 22 | func init() { 23 | Camera.Pitch = math.Pi 24 | } 25 | 26 | // ClientCamera is a camera within the world 27 | type ClientCamera struct { 28 | X, Y, Z float64 29 | Yaw, Pitch float64 30 | } 31 | -------------------------------------------------------------------------------- /render/chunkshader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | import ( 18 | "github.com/thinkofdeath/steven/render/gl" 19 | "github.com/thinkofdeath/steven/render/glsl" 20 | ) 21 | 22 | type chunkShader struct { 23 | Position gl.Attribute `gl:"aPosition"` 24 | TextureInfo gl.Attribute `gl:"aTextureInfo"` 25 | TextureOffset gl.Attribute `gl:"aTextureOffset"` 26 | Color gl.Attribute `gl:"aColor"` 27 | Lighting gl.Attribute `gl:"aLighting"` 28 | PerspectiveMatrix gl.Uniform `gl:"perspectiveMatrix"` 29 | CameraMatrix gl.Uniform `gl:"cameraMatrix"` 30 | Offset gl.Uniform `gl:"offset"` 31 | Texture gl.Uniform `gl:"textures"` 32 | LightLevel gl.Uniform `gl:"lightLevel"` 33 | SkyOffset gl.Uniform `gl:"skyOffset"` 34 | } 35 | 36 | func init() { 37 | glsl.Register("chunk_vertex", ` 38 | in vec3 aPosition; 39 | in vec4 aTextureInfo; 40 | in vec3 aTextureOffset; 41 | in vec3 aColor; 42 | in vec2 aLighting; 43 | 44 | uniform mat4 perspectiveMatrix; 45 | uniform mat4 cameraMatrix; 46 | uniform ivec3 offset; 47 | uniform float lightLevel; 48 | uniform float skyOffset; 49 | 50 | out vec3 vColor; 51 | out vec4 vTextureInfo; 52 | out vec2 vTextureOffset; 53 | out float vAtlas; 54 | out vec3 vLighting; 55 | 56 | #include get_light 57 | 58 | void main() { 59 | vec3 pos = vec3(aPosition.x, -aPosition.y, aPosition.z); 60 | vec3 o = vec3(offset.x, -offset.y / 4096.0, offset.z); 61 | gl_Position = perspectiveMatrix * cameraMatrix * vec4(pos + o * 16.0, 1.0); 62 | 63 | vColor = aColor; 64 | vTextureInfo = aTextureInfo; 65 | vTextureOffset = aTextureOffset.xy / 16.0; 66 | vAtlas = aTextureOffset.z; 67 | 68 | vLighting = getLight(aLighting / (4000.0)); 69 | } 70 | `) 71 | glsl.Register("chunk_frag", ` 72 | uniform sampler2DArray textures; 73 | 74 | in vec3 vColor; 75 | in vec4 vTextureInfo; 76 | in vec2 vTextureOffset; 77 | in float vAtlas; 78 | in vec3 vLighting; 79 | 80 | #ifndef alpha 81 | out vec4 fragColor; 82 | #else 83 | out vec4 accum; 84 | out float revealage; 85 | #endif 86 | 87 | #include lookup_texture 88 | 89 | void main() { 90 | vec4 col = atlasTexture(); 91 | #ifndef alpha 92 | if (col.a < 0.5) discard; 93 | #endif 94 | col *= vec4(vColor, 1.0); 95 | col.rgb *= vLighting; 96 | 97 | #ifndef alpha 98 | fragColor = col; 99 | #else 100 | float z = gl_FragCoord.z; 101 | float al = col.a; 102 | float weight = pow(al + 0.01f, 4.0f) + 103 | max(0.01f, min(3000.0f, 0.3f / (0.00001f + pow(abs(z) / 800.0f, 4.0f)))); 104 | accum = vec4(col.rgb * al * weight, al); 105 | revealage = weight * al; 106 | #endif 107 | } 108 | `) 109 | } 110 | -------------------------------------------------------------------------------- /render/debug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | import ( 18 | "github.com/thinkofdeath/steven/render/gl" 19 | ) 20 | 21 | type debugBuffer struct { 22 | fb *gl.Framebuffer 23 | a gl.Attachment 24 | } 25 | 26 | var debugBuffers = []debugBuffer{ 27 | {&transFramebuffer, gl.ColorAttachment0}, 28 | {&transFramebuffer, gl.ColorAttachment1}, 29 | } 30 | 31 | func blitBuffers() { 32 | // Screen 33 | gl.Framebuffer{}.BindDraw() 34 | 35 | const width, height = 300, 150 36 | 37 | ox, oy := 5, 5 38 | for _, d := range debugBuffers { 39 | d.fb.BindRead() 40 | d.fb.ReadBuffer(d.a) 41 | gl.BlitFramebuffer( 42 | 0, 0, lastWidth, lastHeight, 43 | ox, lastHeight-height-oy, ox+width, lastHeight-oy, 44 | gl.ColorBufferBit, gl.Nearest, 45 | ) 46 | ox += width 47 | if ox+width >= lastWidth { 48 | ox = 5 49 | oy += height 50 | } 51 | } 52 | 53 | gl.UnbindFramebufferDraw() 54 | gl.UnbindFramebufferRead() 55 | } 56 | -------------------------------------------------------------------------------- /render/gl/array.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gl 16 | 17 | import ( 18 | "github.com/thinkofdeath/gl/v3.2-core/gl" 19 | ) 20 | 21 | // VertexArray is used to store state needed to render vertices. 22 | // This includes buffers, the format of the buffers and enabled 23 | // attributes. 24 | type VertexArray struct { 25 | internal uint32 26 | } 27 | 28 | var currentVertexArray VertexArray 29 | 30 | // CreateVertexArray allocates a new VertexArray. If the allocation 31 | // fails then IsValid will return false. 32 | func CreateVertexArray() VertexArray { 33 | var va VertexArray 34 | gl.GenVertexArrays(1, &va.internal) 35 | return va 36 | } 37 | 38 | // Bind marks the VertexArray as the currently active one, this 39 | // means buffers/the format of the buffers etc will be bound to 40 | // this VertexArray. If this vertex array is already bound then 41 | // this will do nothing. 42 | func (va VertexArray) Bind() { 43 | if currentVertexArray == va { 44 | return 45 | } 46 | gl.BindVertexArray(va.internal) 47 | currentVertexArray = va 48 | } 49 | 50 | // Delete deallocates the VertexArray. This does not free any 51 | // attached buffers. IsValid will return false after this call. 52 | func (va *VertexArray) Delete() { 53 | gl.DeleteVertexArrays(1, &va.internal) 54 | if currentVertexArray == *va { 55 | currentVertexArray = VertexArray{} 56 | } 57 | va.internal = 0 58 | } 59 | 60 | // IsValid returns whether this VertexArray is still valid. A 61 | // VertexArray will become invalid after Delete is called. 62 | func (va VertexArray) IsValid() bool { 63 | return va.internal != 0 64 | } 65 | -------------------------------------------------------------------------------- /render/gl/framebuffer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gl 16 | 17 | import ( 18 | "github.com/thinkofdeath/gl/v3.2-core/gl" 19 | "github.com/thinkofdeath/steven/console" 20 | ) 21 | 22 | type Attachment uint32 23 | 24 | const ( 25 | ColorAttachment0 Attachment = gl.COLOR_ATTACHMENT0 26 | ColorAttachment1 Attachment = gl.COLOR_ATTACHMENT1 27 | ColorAttachment2 Attachment = gl.COLOR_ATTACHMENT2 28 | DepthAttachment Attachment = gl.DEPTH_ATTACHMENT 29 | ) 30 | 31 | type Framebuffer struct { 32 | internal uint32 33 | } 34 | 35 | func NewFramebuffer() Framebuffer { 36 | f := Framebuffer{} 37 | gl.GenFramebuffers(1, &f.internal) 38 | return f 39 | } 40 | 41 | func BindFragDataLocation(p Program, cn int, name string) { 42 | n := gl.Str(name + "\x00") 43 | gl.BindFragDataLocation(uint32(p), uint32(cn), n) 44 | } 45 | 46 | func (f Framebuffer) Bind() { 47 | gl.BindFramebuffer(gl.FRAMEBUFFER, f.internal) 48 | } 49 | 50 | func (f Framebuffer) BindRead() { 51 | gl.BindFramebuffer(gl.READ_FRAMEBUFFER, f.internal) 52 | } 53 | 54 | func (f Framebuffer) BindDraw() { 55 | gl.BindFramebuffer(gl.DRAW_FRAMEBUFFER, f.internal) 56 | } 57 | 58 | func UnbindFramebuffer() { 59 | gl.BindFramebuffer(gl.FRAMEBUFFER, 0) 60 | } 61 | 62 | func UnbindFramebufferRead() { 63 | gl.BindFramebuffer(gl.READ_FRAMEBUFFER, 0) 64 | } 65 | 66 | func UnbindFramebufferDraw() { 67 | gl.BindFramebuffer(gl.DRAW_FRAMEBUFFER, 0) 68 | } 69 | 70 | func DrawBuffers(bufs []Attachment) { 71 | gl.DrawBuffers(int32(len(bufs)), (*uint32)(gl.Ptr(bufs))) 72 | } 73 | 74 | func (f Framebuffer) Texture2D(attachment Attachment, texTarget TextureTarget, tex Texture, level int) { 75 | gl.FramebufferTexture2D(gl.FRAMEBUFFER, uint32(attachment), uint32(texTarget), tex.internal, int32(level)) 76 | } 77 | 78 | func (f Framebuffer) Delete() { 79 | gl.DeleteFramebuffers(1, &f.internal) 80 | } 81 | 82 | func (f Framebuffer) Check() { 83 | console.Text("%04X", gl.CheckFramebufferStatus(gl.FRAMEBUFFER)) 84 | } 85 | 86 | func BlitFramebuffer(sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1 int, mask ClearFlags, filter TextureValue) { 87 | gl.BlitFramebuffer( 88 | int32(sx0), int32(sy0), int32(sx1), int32(sy1), 89 | int32(dx0), int32(dy0), int32(dx1), int32(dy1), 90 | uint32(mask), uint32(filter), 91 | ) 92 | } 93 | 94 | func (f Framebuffer) ReadBuffer(a Attachment) { 95 | gl.ReadBuffer(uint32(a)) 96 | } 97 | 98 | type TargetBuffer uint32 99 | 100 | const ( 101 | Color TargetBuffer = gl.COLOR 102 | ) 103 | 104 | func ClearBuffer(buffer TargetBuffer, drawBuffer int, values []float32) { 105 | gl.ClearBufferfv(uint32(buffer), int32(drawBuffer), (*float32)(gl.Ptr(values))) 106 | } 107 | -------------------------------------------------------------------------------- /render/gl/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gl 16 | 17 | import ( 18 | "github.com/thinkofdeath/gl/v3.2-core/gl" 19 | ) 20 | 21 | // Type is a type of data used by various operations. 22 | type Type uint32 23 | 24 | // Valid types. 25 | const ( 26 | UnsignedByte Type = gl.UNSIGNED_BYTE 27 | UnsignedShort Type = gl.UNSIGNED_SHORT 28 | UnsignedInt Type = gl.UNSIGNED_INT 29 | Short Type = gl.SHORT 30 | Float Type = gl.FLOAT 31 | ) 32 | -------------------------------------------------------------------------------- /render/glsl/glsl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package glsl handles glsl shaders and applies some preprocessing to them 16 | package glsl 17 | 18 | import ( 19 | "bufio" 20 | "bytes" 21 | "fmt" 22 | "os" 23 | "strings" 24 | ) 25 | 26 | var shaders = map[string]string{} 27 | 28 | // Register adds the named shader to the database. 29 | // Once included they may be obtained from Get or 30 | // used within other shaders via #include 31 | func Register(name, source string) { 32 | _, ok := shaders[name] 33 | if ok { 34 | panic("shader " + name + " is already defined") 35 | } 36 | shaders[name] = strings.TrimSpace(source) 37 | } 38 | 39 | // Get returns the named shader optionally adding 40 | // the passed defines. The output from this will be 41 | // processed finding and expanding #include statements. 42 | // This also adds debugging information to the shaders 43 | // (line number and name). 44 | func Get(name string, defines ...string) string { 45 | var out bytes.Buffer 46 | out.WriteString("#version 150\n") 47 | for _, def := range defines { 48 | fmt.Fprintf(&out, "#define %s\n", def) 49 | } 50 | get(&out, name) 51 | return out.String() 52 | } 53 | 54 | func get(out *bytes.Buffer, name string) { 55 | src, ok := shaders[name] 56 | if !ok { 57 | panic("unknown shader " + name) 58 | } 59 | s := bufio.NewScanner(strings.NewReader(src)) 60 | lineNo := 0 61 | for s.Scan() { 62 | lineNo++ 63 | line := s.Text() 64 | if strings.HasPrefix(line, "#include ") { 65 | inc := strings.TrimSpace(line[len("#include "):]) 66 | get(out, inc) 67 | continue 68 | } 69 | out.WriteString(line) 70 | out.WriteRune('\n') 71 | if os.Getenv("STEVEN_DEBUG") == "true" { 72 | fmt.Fprintf(out, "#line %d \"%s\"\n", lineNo, name) 73 | } 74 | } 75 | if err := s.Err(); err != nil { 76 | panic(err) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /render/line.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | import "github.com/thinkofdeath/steven/render/gl" 18 | 19 | var lineState = struct { 20 | program gl.Program 21 | shader *lineShader 22 | array gl.VertexArray 23 | buffer gl.Buffer 24 | count int 25 | data []byte 26 | prevSize int 27 | }{ 28 | prevSize: -1, 29 | } 30 | 31 | func initLineDraw() { 32 | lineState.program = CreateProgram(vertexLine, fragmentLine) 33 | lineState.shader = &lineShader{} 34 | InitStruct(lineState.shader, lineState.program) 35 | 36 | lineState.array = gl.CreateVertexArray() 37 | lineState.array.Bind() 38 | lineState.buffer = gl.CreateBuffer() 39 | lineState.buffer.Bind(gl.ArrayBuffer) 40 | lineState.shader.Position.Enable() 41 | lineState.shader.Color.Enable() 42 | lineState.shader.Position.Pointer(3, gl.Float, false, 16, 0) 43 | lineState.shader.Color.Pointer(4, gl.UnsignedByte, true, 16, 12) 44 | } 45 | 46 | func drawLines() { 47 | if lineState.count > 0 { 48 | gl.Enable(gl.Blend) 49 | lineState.program.Use() 50 | lineState.shader.PerspectiveMatrix.Matrix4(&perspectiveMatrix) 51 | lineState.shader.CameraMatrix.Matrix4(&cameraMatrix) 52 | lineState.array.Bind() 53 | lineState.buffer.Bind(gl.ArrayBuffer) 54 | if len(lineState.data) > lineState.prevSize { 55 | lineState.prevSize = len(lineState.data) 56 | lineState.buffer.Data(lineState.data, gl.DynamicDraw) 57 | } else { 58 | target := lineState.buffer.Map(gl.WriteOnly, len(lineState.data)) 59 | copy(target, lineState.data) 60 | lineState.buffer.Unmap() 61 | } 62 | gl.DrawArrays(gl.Triangles, 0, lineState.count) 63 | lineState.count = 0 64 | lineState.data = lineState.data[:0] 65 | gl.Disable(gl.Blend) 66 | } 67 | } 68 | 69 | func DrawBox(x1, y1, z1, x2, y2, z2 float64, r, g, b, a byte) { 70 | for _, f := range faceVertices { 71 | for _, v := range f { 72 | val := v[0]*x2 + (1.0-v[0])*x1 73 | lineState.data = appendFloat(lineState.data, float32(val)) 74 | val = v[1]*y2 + (1.0-v[1])*y1 75 | lineState.data = appendFloat(lineState.data, float32(val)) 76 | val = v[2]*z2 + (1.0-v[2])*z1 77 | lineState.data = appendFloat(lineState.data, float32(val)) 78 | lineState.data = append(lineState.data, r, g, b, a) 79 | lineState.count++ 80 | } 81 | } 82 | } 83 | 84 | // Precomputed face vertices 85 | var faceVertices = [6][6][3]float64{ 86 | { // Up 87 | {0, 1, 0}, 88 | {1, 1, 0}, 89 | {0, 1, 1}, 90 | 91 | {1, 1, 1}, 92 | {0, 1, 1}, 93 | {1, 1, 0}, 94 | }, 95 | { // Down 96 | {0, 0, 0}, 97 | {0, 0, 1}, 98 | {1, 0, 0}, 99 | 100 | {1, 0, 1}, 101 | {1, 0, 0}, 102 | {0, 0, 1}, 103 | }, 104 | { // North 105 | {0, 0, 0}, 106 | {1, 0, 0}, 107 | {0, 1, 0}, 108 | 109 | {1, 1, 0}, 110 | {0, 1, 0}, 111 | {1, 0, 0}, 112 | }, 113 | { // South 114 | {0, 0, 1}, 115 | {0, 1, 1}, 116 | {1, 0, 1}, 117 | 118 | {1, 1, 1}, 119 | {1, 0, 1}, 120 | {0, 1, 1}, 121 | }, 122 | { // West 123 | {0, 0, 0}, 124 | {0, 1, 0}, 125 | {0, 0, 1}, 126 | 127 | {0, 1, 1}, 128 | {0, 0, 1}, 129 | {0, 1, 0}, 130 | }, 131 | { // East 132 | {1, 0, 0}, 133 | {1, 0, 1}, 134 | {1, 1, 0}, 135 | 136 | {1, 1, 1}, 137 | {1, 1, 0}, 138 | {1, 0, 1}, 139 | }, 140 | } 141 | -------------------------------------------------------------------------------- /render/lineshader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | import "github.com/thinkofdeath/steven/render/gl" 18 | 19 | type lineShader struct { 20 | Position gl.Attribute `gl:"aPosition"` 21 | Color gl.Attribute `gl:"aColor"` 22 | PerspectiveMatrix gl.Uniform `gl:"perspectiveMatrix"` 23 | CameraMatrix gl.Uniform `gl:"cameraMatrix"` 24 | } 25 | 26 | const ( 27 | vertexLine = ` 28 | #version 150 29 | in vec3 aPosition; 30 | in vec4 aColor; 31 | 32 | uniform mat4 perspectiveMatrix; 33 | uniform mat4 cameraMatrix; 34 | 35 | out vec4 vColor; 36 | 37 | void main() { 38 | vec3 pos = vec3(aPosition.x, -aPosition.y, aPosition.z); 39 | gl_Position = perspectiveMatrix * cameraMatrix * vec4(pos, 1.0); 40 | 41 | vColor = aColor; 42 | } 43 | ` 44 | fragmentLine = ` 45 | #version 150 46 | 47 | const float atlasSize = ` + atlasSizeStr + `; 48 | 49 | in vec4 vColor; 50 | 51 | out vec4 fragColor; 52 | 53 | void main() { 54 | fragColor = vec4(vColor); 55 | } 56 | ` 57 | ) 58 | -------------------------------------------------------------------------------- /render/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | var ( 18 | freeTextures []*reusableTexture 19 | ) 20 | 21 | type reusableTexture struct { 22 | info *textureInfo 23 | W, H int 24 | } 25 | 26 | func getFreeTexture(w, h int) *reusableTexture { 27 | for i, f := range freeTextures { 28 | if f.H == h && f.W == w { 29 | freeTextures = append(freeTextures[:i], freeTextures[i+1:]...) 30 | return f 31 | } 32 | } 33 | return &reusableTexture{ 34 | info: addTexture(make([]byte, w*h*4), w, h), 35 | W: w, H: h, 36 | } 37 | } 38 | 39 | func freeTexture(t *reusableTexture) { 40 | freeTextures = append(freeTextures, t) 41 | } 42 | -------------------------------------------------------------------------------- /render/position.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | type position struct { 18 | X, Y, Z int 19 | } 20 | 21 | type positionC struct { 22 | X, Z int 23 | } 24 | -------------------------------------------------------------------------------- /render/queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | const ( 18 | maxQueueSize = 1 << 12 19 | queueMask = maxQueueSize - 1 20 | ) 21 | 22 | type renderQueue struct { 23 | queue [maxQueueSize]renderRequest 24 | startPointer int 25 | endPointer int 26 | } 27 | 28 | func (rq *renderQueue) Append(r renderRequest) { 29 | rq.queue[rq.endPointer] = r 30 | rq.endPointer = (rq.endPointer + 1) & queueMask 31 | } 32 | 33 | func (rq *renderQueue) Take() renderRequest { 34 | val := rq.queue[rq.startPointer] 35 | rq.startPointer = (rq.startPointer + 1) & queueMask 36 | return val 37 | } 38 | 39 | func (rq *renderQueue) Empty() bool { 40 | return rq.startPointer == rq.endPointer 41 | } 42 | -------------------------------------------------------------------------------- /render/shader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | 21 | "github.com/thinkofdeath/steven/render/gl" 22 | ) 23 | 24 | // CreateProgram creates an OpenGL shader program from the 25 | // passed shader sources. Panics if the shader is invalid. 26 | func CreateProgram(vertex, fragment string) gl.Program { 27 | program := gl.CreateProgram() 28 | 29 | v := gl.CreateShader(gl.VertexShader) 30 | v.Source(vertex) 31 | v.Compile() 32 | 33 | if v.Parameter(gl.CompileStatus) == 0 { 34 | panic(v.InfoLog()) 35 | } else { 36 | log := v.InfoLog() 37 | if len(log) > 0 { 38 | fmt.Println(log) 39 | } 40 | } 41 | 42 | f := gl.CreateShader(gl.FragmentShader) 43 | f.Source(fragment) 44 | f.Compile() 45 | 46 | if f.Parameter(gl.CompileStatus) == 0 { 47 | panic(f.InfoLog()) 48 | } else { 49 | log := f.InfoLog() 50 | if len(log) > 0 { 51 | fmt.Println(log) 52 | } 53 | } 54 | 55 | program.AttachShader(v) 56 | program.AttachShader(f) 57 | program.Link() 58 | program.Use() 59 | return program 60 | } 61 | 62 | // InitStruct loads the Uniform and Attribute fields 63 | // of a struct with values from the program using 64 | // the name from the 'gl' struct tag. 65 | func InitStruct(inter interface{}, program gl.Program) { 66 | v := reflect.ValueOf(inter).Elem() 67 | t := v.Type() 68 | l := t.NumField() 69 | 70 | var a gl.Attribute 71 | var u gl.Uniform 72 | gla := reflect.TypeOf(a) 73 | glu := reflect.TypeOf(u) 74 | 75 | for i := 0; i < l; i++ { 76 | f := t.Field(i) 77 | name := f.Tag.Get("gl") 78 | if f.Type == gla { 79 | v.Field(i).Set(reflect.ValueOf( 80 | program.AttributeLocation(name), 81 | )) 82 | } else if f.Type == glu { 83 | v.Field(i).Set(reflect.ValueOf( 84 | program.UniformLocation(name), 85 | )) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /render/shader_snippets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | import "github.com/thinkofdeath/steven/render/glsl" 18 | 19 | func init() { 20 | glsl.Register("lookup_texture", ` 21 | const float invAtlasSize = 1.0 / `+atlasSizeStr+`; 22 | vec4 atlasTexture() { 23 | vec2 tPos = vTextureOffset; 24 | tPos = mod(tPos, vTextureInfo.zw); 25 | tPos += vTextureInfo.xy; 26 | tPos *= invAtlasSize; 27 | return texture(textures, vec3(tPos, vAtlas)); 28 | } 29 | `) 30 | glsl.Register("get_light", ` 31 | vec3 getLight(vec2 light) { 32 | vec2 li = pow(vec2(lightLevel), 15.0 - light); 33 | float skyTint = skyOffset * 0.95 + 0.05; 34 | float bl = li.x; 35 | float sk = li.y * skyTint; 36 | 37 | float skyRed = sk * (skyOffset * 0.65 + 0.35); 38 | float skyGreen = sk * (skyOffset * 0.65 + 0.35); 39 | float blockGreen = bl * ((bl * 0.6 + 0.4) * 0.6 + 0.4); 40 | float blockBlue = bl * (bl * bl * 0.6 + 0.4); 41 | 42 | vec3 col = vec3( 43 | skyRed + bl, 44 | skyGreen + blockGreen, 45 | sk + blockBlue 46 | ); 47 | 48 | col = col * 0.96 + 0.03; 49 | 50 | float gamma = 0.0; 51 | vec3 invCol = 1.0 - col; 52 | invCol = 1.0 - invCol * invCol * invCol * invCol; 53 | col = col * (1.0 - gamma) + invCol * gamma; 54 | col = col * 0.96 + 0.03; 55 | 56 | return clamp(col, 0.0, 1.0); 57 | } 58 | `) 59 | } 60 | -------------------------------------------------------------------------------- /render/shader_sun.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | import "github.com/thinkofdeath/steven/render/glsl" 18 | 19 | func init() { 20 | glsl.Register("sun_vertex", ` 21 | in vec3 aPosition; 22 | in vec4 aTextureInfo; 23 | in ivec3 aTextureOffset; 24 | in vec4 aColor; 25 | in int id; 26 | 27 | uniform mat4 perspectiveMatrix; 28 | uniform mat4 cameraMatrix; 29 | uniform mat4 modelMatrix[10]; 30 | uniform float lightLevel; 31 | uniform float skyOffset; 32 | uniform vec2 lighting; 33 | 34 | out vec4 vTextureInfo; 35 | out vec2 vTextureOffset; 36 | out float vAtlas; 37 | out float vID; 38 | 39 | void main() { 40 | vec3 pos = vec3(aPosition.x, -aPosition.y, aPosition.z); 41 | gl_Position = perspectiveMatrix * cameraMatrix * modelMatrix[id] * vec4(pos, 1.0); 42 | 43 | vTextureInfo = aTextureInfo; 44 | vTextureOffset = aTextureOffset.xy / 16.0; 45 | vAtlas = aTextureOffset.z; 46 | vID = float(id); 47 | } 48 | `) 49 | glsl.Register("sun_frag", ` 50 | 51 | uniform sampler2DArray textures; 52 | uniform vec4 colorMul[10]; 53 | 54 | in vec4 vTextureInfo; 55 | in vec2 vTextureOffset; 56 | in float vAtlas; 57 | in float vID; 58 | 59 | out vec4 fragColor; 60 | 61 | #include lookup_texture 62 | 63 | void main() { 64 | vec4 col = atlasTexture(); 65 | if (col.a <= 0.05) discard; 66 | fragColor = col * colorMul[int(vID)]; 67 | } 68 | `) 69 | } 70 | -------------------------------------------------------------------------------- /render/uishader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package render 16 | 17 | import ( 18 | "github.com/thinkofdeath/steven/render/gl" 19 | "github.com/thinkofdeath/steven/render/glsl" 20 | ) 21 | 22 | type uiShader struct { 23 | Position gl.Attribute `gl:"aPosition"` 24 | TextureInfo gl.Attribute `gl:"aTextureInfo"` 25 | TextureOffset gl.Attribute `gl:"aTextureOffset"` 26 | Color gl.Attribute `gl:"aColor"` 27 | Texture gl.Uniform `gl:"textures"` 28 | ScreenSize gl.Uniform `gl:"screenSize"` 29 | } 30 | 31 | func init() { 32 | glsl.Register("ui_vertex", ` 33 | in ivec3 aPosition; 34 | in vec4 aTextureInfo; 35 | in ivec3 aTextureOffset; 36 | in vec4 aColor; 37 | 38 | out vec4 vColor; 39 | out vec4 vTextureInfo; 40 | out vec2 vTextureOffset; 41 | out float vAtlas; 42 | 43 | uniform vec2 screenSize; 44 | 45 | void main() { 46 | vec2 pos = aPosition.xy / screenSize; 47 | gl_Position = vec4((pos.x-0.5)*2.0, -(pos.y-0.5)*2.0, float(-aPosition.z) / float(0xFFFF-1), 1.0); 48 | vColor = aColor; 49 | vTextureInfo = aTextureInfo; 50 | vTextureOffset = aTextureOffset.xy / 16.0; 51 | vAtlas = aTextureOffset.z; 52 | } 53 | `) 54 | glsl.Register("ui_frag", ` 55 | uniform sampler2DArray textures; 56 | 57 | in vec4 vColor; 58 | in vec4 vTextureInfo; 59 | in vec2 vTextureOffset; 60 | in float vAtlas; 61 | 62 | out vec4 fragColor; 63 | 64 | #include lookup_texture 65 | 66 | void main() { 67 | vec4 col = atlasTexture(); 68 | col *= vColor; 69 | if (col.a == 0.0) discard; 70 | fragColor = col; 71 | } 72 | `) 73 | } 74 | -------------------------------------------------------------------------------- /resource/builtin/files/assets/minecraft/texts/splashes.txt: -------------------------------------------------------------------------------- 1 | I blame Xor 2 | Its not a bug its a feature! 3 | Don't go to #think, tis a silly place 4 | Tested! (In production) 5 | Not in scala! 6 | Its steven not phteven! 7 | Now webscale! 8 | Meow 9 | I bet one of cindy's cats broke it! 10 | =^.^= 11 | ಠ_ಠ 12 | Commit reverted in 5..4..3... 13 | Latest is greatest! 14 | [This space is intentionally left blank] 15 | ThinkBot: .... *Thinkofdeath damn it 16 | Now with more bugs! 17 | I blame Mojang 18 | The logo is totally not ascii art rendered as textures 19 | Look, it works on my machine. 20 | Open Source! https://github.com/thinkofdeath/steven 21 | Built with Go! 22 | try { } catch (Exception e) { } 23 | panic(recover()) 24 | // Abandon hope all ye who enter here 25 | Its like I'm racing vanilla to see who can have the most bugs 26 | Using ascii art for the logo seemed like a bad idea at first 27 | ... and still does. 28 | Help! I'm trapped in the splash text! 29 | Linux support! 30 | Windows support! 31 | Mac support! (in theory) 32 | Come chat on IRC! 33 | Knowing Murphy's Law doesn't help 34 | Minecraft Multi-processing: breaking three things at once 35 | Silly Mortal... 36 | Software isn't released. It's allowed to escape. 37 | General System Error: Please sacrifice a cow and two chickens to continue 38 | Do you want to build a client? 39 | sudo rm -rf --no-preserve-root / 40 | Thinkofdeath.getClass().getField("sanity").set(Thinkofdeath, null); 41 | Now with potatoes! 42 | And then you cleanse them in a ball of atomic fire! 43 | I'm a little matrix, square and stout, 44 | this is my transpose, this is my count. 45 | var f = function() { return f; }; 46 | unsafe.allocateObject(Unsafe.class); 47 | :(){ :|:& };: 48 | You must pay to view this content. 49 | No touchy the topic 50 | Ceci n'est pas un splash. 51 | Xor is not actually a cat. 52 | The MD5 of md_5 is e14cfacdd442a953343ebd8529138680 53 | -------------------------------------------------------------------------------- /resource/builtin/files/assets/minecraft/textures/font/ascii.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thinkofname/steven-go/03afc55cd2c69a1e913526b9767fce1470f107f1/resource/builtin/files/assets/minecraft/textures/font/ascii.png -------------------------------------------------------------------------------- /resource/builtin/files/assets/steven/blockstates/missing_block.json: -------------------------------------------------------------------------------- 1 | { 2 | "variants": { 3 | "normal": { "model": "missing_block" } 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /resource/builtin/files/assets/steven/logo/logo.txt: -------------------------------------------------------------------------------- 1 | SSSSSSSSSSSSSSS tttt 2 | SS:::::::::::::::S ttt:::t 3 | S:::::SSSSSS::::::S t:::::t 4 | S:::::S SSSSSSS t:::::t 5 | S:::::S ttttttt:::::ttttttt eeeeeeeeeeee vvvvvvv vvvvvvv eeeeeeeeeeee nnnn nnnnnnnn 6 | S:::::S t:::::::::::::::::t ee::::::::::::ee v:::::v v:::::v ee::::::::::::ee n:::nn::::::::nn 7 | S::::SSSS t:::::::::::::::::t e::::::eeeee:::::ee v:::::v v:::::v e::::::eeeee:::::een::::::::::::::nn 8 | SS::::::SSSSS tttttt:::::::tttttt e::::::e e:::::e v:::::v v:::::v e::::::e e:::::enn:::::::::::::::n 9 | SSS::::::::SS t:::::t e:::::::eeeee::::::e v:::::v v:::::v e:::::::eeeee::::::e n:::::nnnn:::::n 10 | SSSSSS::::S t:::::t e:::::::::::::::::e v:::::v v:::::v e:::::::::::::::::e n::::n n::::n 11 | S:::::S t:::::t e::::::eeeeeeeeeee v:::::v:::::v e::::::eeeeeeeeeee n::::n n::::n 12 | S:::::S t:::::t tttttte:::::::e v:::::::::v e:::::::e n::::n n::::n 13 | SSSSSSS S:::::S t::::::tttt:::::te::::::::e v:::::::v e::::::::e n::::n n::::n 14 | S::::::SSSSSS:::::S tt::::::::::::::t e::::::::eeeeeeee v:::::v e::::::::eeeeeeee n::::n n::::n 15 | S:::::::::::::::SS tt:::::::::::tt ee:::::::::::::e v:::v ee:::::::::::::e n::::n n::::n 16 | SSSSSSSSSSSSSSS ttttttttttt eeeeeeeeeeeeee vvv eeeeeeeeeeeeee nnnnnn nnnnnn -------------------------------------------------------------------------------- /resource/builtin/files/assets/steven/logo/textures.txt: -------------------------------------------------------------------------------- 1 | blocks/cobblestone 2 | blocks/netherrack 3 | blocks/dirt 4 | blocks/planks_oak 5 | blocks/brick 6 | blocks/snow 7 | blocks/sand 8 | blocks/gravel 9 | blocks/hardened_clay 10 | blocks/clay 11 | blocks/bedrock 12 | blocks/obsidian 13 | blocks/end_stone 14 | blocks/stone_andesite 15 | blocks/dirt_podzol_top 16 | blocks/portal 17 | blocks/prismarine_rough 18 | blocks/soul_sand 19 | blocks/lava_still 20 | blocks/hay_block_top 21 | blocks/log_acacia 22 | blocks/red_sandstone_carved -------------------------------------------------------------------------------- /resource/builtin/files/assets/steven/models/block/missing_block.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": { 3 | "all": "missing_texture" 4 | }, 5 | "elements": [ 6 | { "from": [ 0, 0, 0 ], 7 | "to": [ 16, 16, 16 ], 8 | "faces": { 9 | "down": { "texture": "#all", "cullface": "down" }, 10 | "up": { "texture": "#all", "cullface": "up" }, 11 | "north": { "texture": "#all", "cullface": "north" }, 12 | "south": { "texture": "#all", "cullface": "south" }, 13 | "west": { "texture": "#all", "cullface": "west" }, 14 | "east": { "texture": "#all", "cullface": "east" } 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /resource/builtin/files/assets/steven/textures/environment/clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thinkofname/steven-go/03afc55cd2c69a1e913526b9767fce1470f107f1/resource/builtin/files/assets/steven/textures/environment/clouds.png -------------------------------------------------------------------------------- /resource/builtin/files/assets/steven/textures/gui/cog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thinkofname/steven-go/03afc55cd2c69a1e913526b9767fce1470f107f1/resource/builtin/files/assets/steven/textures/gui/cog.png -------------------------------------------------------------------------------- /resource/builtin/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package builtin 16 | 17 | //go:generate go-bindata -pkg builtin -nomemcopy -o assets.go -prefix files files/... 18 | //go:generate gofmt -w -s assets.go 19 | -------------------------------------------------------------------------------- /resource/locale/locale.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package locale 16 | 17 | import ( 18 | "bufio" 19 | "fmt" 20 | "strings" 21 | "sync" 22 | 23 | "github.com/thinkofdeath/steven/resource" 24 | ) 25 | 26 | var ( 27 | values = map[string]string{} 28 | lock sync.RWMutex 29 | ) 30 | 31 | func init() { 32 | LoadLocale("en_US") 33 | } 34 | 35 | // Clear clears loaded strings and reloads the default locale. 36 | func Clear() { 37 | lock.Lock() 38 | values = map[string]string{} 39 | lock.Unlock() 40 | LoadLocale("en_US") 41 | } 42 | 43 | // LoadLocale loads the named locale if it exists. 44 | func LoadLocale(name string) { 45 | lock.Lock() 46 | defer lock.Unlock() 47 | r, err := resource.Open("minecraft", fmt.Sprintf("lang/%s.lang", name)) 48 | if err != nil { 49 | return 50 | } 51 | defer r.Close() 52 | b := bufio.NewScanner(r) 53 | for b.Scan() { 54 | line := b.Text() 55 | line = strings.TrimSpace(line) 56 | if len(line) == 0 { 57 | continue 58 | } 59 | parts := strings.SplitN(line, "=", 2) 60 | if len(parts) != 2 { 61 | continue 62 | } 63 | values[parts[0]] = parts[1] 64 | } 65 | if err := b.Err(); err != nil { 66 | panic(err) 67 | } 68 | } 69 | 70 | // GetRaw returns the unparsed value with the given name. 71 | func GetRaw(key string) string { 72 | lock.RLock() 73 | defer lock.RUnlock() 74 | if val, ok := values[key]; ok { 75 | return val 76 | } 77 | return "!" + key + "!" 78 | } 79 | 80 | // Get returns a list of parts for the given key. It will be either 81 | // a string (just text to be printed) or an int (substitution index). 82 | func Get(key string) (parts []interface{}) { 83 | val := GetRaw(key) 84 | last := 0 85 | index := 0 86 | curIndex := -1 87 | inSub := false 88 | skipNext := false 89 | for i, r := range val { 90 | if skipNext { 91 | skipNext = false 92 | last = i + 1 93 | continue 94 | } 95 | if inSub { 96 | if r == '%' { 97 | parts = append(parts, "%") 98 | inSub = false 99 | last = i + 1 100 | curIndex = -1 101 | continue 102 | } 103 | if r >= '0' && r <= '9' { 104 | if curIndex == -1 { 105 | curIndex = 0 106 | } 107 | curIndex *= 10 108 | curIndex += int(r - '0') 109 | continue 110 | } 111 | if curIndex == -1 { 112 | curIndex = index + 1 113 | index++ 114 | } 115 | parts = append(parts, curIndex-1) 116 | inSub = false 117 | curIndex = -1 118 | last = i + 1 119 | if r == '$' { 120 | skipNext = true 121 | } 122 | } 123 | if r == '%' { 124 | inSub = true 125 | parts = append(parts, val[last:i]) 126 | continue 127 | } 128 | } 129 | if last != len(val) { 130 | parts = append(parts, val[last:]) 131 | } 132 | return 133 | } 134 | -------------------------------------------------------------------------------- /resourcepacks.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "strings" 21 | 22 | "github.com/thinkofdeath/steven/console" 23 | "github.com/thinkofdeath/steven/render" 24 | "github.com/thinkofdeath/steven/resource" 25 | "github.com/thinkofdeath/steven/resource/locale" 26 | "github.com/thinkofdeath/steven/ui" 27 | ) 28 | 29 | var resourcePacks = console.NewStringVar("cl_resource_packs", "", console.Serializable).Doc(` 30 | cl_resource_packs is a comma seperated list of resource packs 31 | that are currently enabled. 32 | `) 33 | 34 | func initResources() { 35 | var pBar *progressBar 36 | resource.Init(func(progress float64, done bool) { 37 | if !done { 38 | if pBar == nil { 39 | pBar = newProgressBar() 40 | } 41 | pBar.update(progress, fmt.Sprintf("Downloading textures: %v/100", int(100*progress))) 42 | } else { 43 | if pBar != nil { 44 | pBar.remove() 45 | } 46 | reloadResources() 47 | } 48 | }, syncChan) 49 | 50 | for _, pck := range strings.Split(resourcePacks.Value(), ",") { 51 | if pck == "" { 52 | continue 53 | } 54 | resource.LoadZip(pck) 55 | } 56 | locale.Clear() 57 | loadBiomes() 58 | render.LoadSkinBuffer() 59 | } 60 | 61 | func AddPack(path string) { 62 | console.Text("Adding pack " + path) 63 | if err := resource.LoadZip(path); err != nil { 64 | fmt.Println("Failed to load pack", path) 65 | return 66 | } 67 | if resourcePacks.Value() != "" { 68 | resourcePacks.SetValue(resourcePacks.Value() + "," + path) 69 | } else { 70 | resourcePacks.SetValue(path) 71 | } 72 | reloadResources() 73 | } 74 | 75 | func RemovePack(path string) { 76 | console.Text("Removing pack " + path) 77 | resource.RemovePack(path) 78 | var buf bytes.Buffer 79 | for _, pck := range strings.Split(resourcePacks.Value(), ",") { 80 | if pck != path { 81 | buf.WriteString(pck) 82 | buf.WriteRune(',') 83 | } 84 | } 85 | val := buf.String() 86 | if strings.HasPrefix(val, ",") { 87 | val = val[:len(val)-1] 88 | } 89 | resourcePacks.SetValue(val) 90 | reloadResources() 91 | } 92 | 93 | func reloadResources() { 94 | console.Text("Bringing everything to a stop") 95 | for freeBuilders < maxBuilders { 96 | select { 97 | case pos := <-completeBuilders: 98 | freeBuilders++ 99 | if c := chunkMap[chunkPosition{pos.X, pos.Z}]; c != nil { 100 | if s := c.Sections[pos.Y]; s != nil { 101 | s.building = false 102 | } 103 | } 104 | } 105 | } 106 | locale.Clear() 107 | render.LoadSkinBuffer() 108 | modelCache = map[string]*model{} 109 | console.Text("Reloading textures") 110 | render.LoadTextures() 111 | console.Text("Reloading biomes") 112 | loadBiomes() 113 | ui.ForceDraw() 114 | console.Text("Reloading blocks") 115 | reinitBlocks() 116 | console.Text("Marking chunks for rebuild") 117 | for _, c := range chunkMap { 118 | for _, s := range c.Sections { 119 | if s != nil { 120 | s.dirty = true 121 | } 122 | } 123 | } 124 | console.Text("Rebuilding static models") 125 | render.RefreshModels() 126 | console.Text("Reloading inventory") 127 | Client.playerInventory.Update() 128 | } 129 | -------------------------------------------------------------------------------- /type/bit/map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bit 16 | 17 | import "fmt" 18 | 19 | type Map struct { 20 | bits []uint64 21 | BitSize int 22 | Length int 23 | } 24 | 25 | func NewMap(l, size int) *Map { 26 | return &Map{ 27 | BitSize: size, 28 | bits: make([]uint64, (l*size)/64), 29 | Length: l, 30 | } 31 | } 32 | 33 | func NewMapFromRaw(bits []uint64, size int) *Map { 34 | return &Map{ 35 | BitSize: size, 36 | bits: bits, 37 | Length: (len(bits)*64 + 63) / size, 38 | } 39 | } 40 | 41 | func (m *Map) ResizeBits(size int) *Map { 42 | n := NewMap(m.Length, size) 43 | for i := 0; i < m.Length; i++ { 44 | n.Set(i, m.Get(i)) 45 | } 46 | return n 47 | } 48 | 49 | func (m *Map) Set(i, val int) { 50 | if val < 0 || val >= (int(1)<> uint(rem) << uint(rem)) | (uint64(val) >> uint64(used)) 63 | } 64 | } 65 | 66 | func (m *Map) Get(i int) int { 67 | i *= m.BitSize 68 | pos := i / 64 69 | mask := (uint64(1) << uint(m.BitSize)) - 1 70 | ii := i % 64 71 | pos2 := (i + m.BitSize - 1) / 64 72 | if pos2 != pos { 73 | used := 64 - ii 74 | return int(((m.bits[pos] >> uint64(ii)) | (m.bits[pos2])<> uint64(ii)) & mask) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /type/bit/map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bit 16 | 17 | import "testing" 18 | 19 | func TestMap(t *testing.T) { 20 | m := NewMap(4096, 4) 21 | for i := 0; i < 4096; i++ { 22 | for j := 0; j < 16; j++ { 23 | m.Set(i, j) 24 | if m.Get(i) != j { 25 | t.Fatalf("Index(%d) wanted %d and got %d", i, j, m.Get(i)) 26 | } 27 | } 28 | } 29 | } 30 | 31 | func TestMapOdd(t *testing.T) { 32 | for size := 1; size <= 16; size++ { 33 | m := NewMap(64*3, size) 34 | max := (1 << uint(size)) - 1 35 | for i := 0; i < 64*3; i++ { 36 | for j := 0; j < max; j++ { 37 | m.Set(i, j) 38 | if m.Get(i) != j { 39 | t.Fatalf("Index(%d) wanted %d and got %d", i, j, m.Get(i)) 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /type/bit/set.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bit 16 | 17 | // Set is a collection of booleans stored as bits 18 | type Set []uint64 19 | 20 | // NewSet allocates a new bit set that can store up to the 21 | // passed number of bits. 22 | func NewSet(size int) Set { 23 | return make(Set, (size+63)>>6) 24 | } 25 | 26 | // Set changes the value of the bit at the location. 27 | func (s Set) Set(i int, v bool) { 28 | if v { 29 | s[i>>6] |= 1 << uint(i&0x3F) 30 | } else { 31 | s[i>>6] &= ^(1 << uint(i&0x3F)) 32 | } 33 | } 34 | 35 | // Get returns the value of the bit at the location 36 | func (s Set) Get(i int) bool { 37 | v := s[i>>6] & (1 << uint(i&0x3F)) 38 | return v != 0 39 | } 40 | -------------------------------------------------------------------------------- /type/bit/set_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bit 16 | 17 | import "testing" 18 | 19 | func TestSet(t *testing.T) { 20 | s := NewSet(200) 21 | for i := 0; i < 200; i++ { 22 | if i%3 == 0 { 23 | s.Set(i, true) 24 | } 25 | } 26 | for i := 0; i < 200; i++ { 27 | if s.Get(i) != (i%3 == 0) { 28 | t.Fail() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /type/direction/direction.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package direction 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/go-gl/mathgl/mgl32" 21 | ) 22 | 23 | // Type is a direction in the minecraft world. 24 | type Type uint 25 | 26 | // Possible direction values. 27 | const ( 28 | Up Type = iota 29 | Down 30 | North 31 | South 32 | West 33 | East 34 | Invalid 35 | ) 36 | 37 | // Values is all valid directions. 38 | var Values = []Type{ 39 | Up, 40 | Down, 41 | North, 42 | South, 43 | West, 44 | East, 45 | } 46 | 47 | // FromString returns the direction that matches the passed 48 | // string if possible otherwise it will return Invalid. 49 | func FromString(str string) Type { 50 | switch str { 51 | case "up": 52 | return Up 53 | case "down": 54 | return Down 55 | case "north": 56 | return North 57 | case "south": 58 | return South 59 | case "west": 60 | return West 61 | case "east": 62 | return East 63 | } 64 | // ¯\_(ツ)_/¯ 65 | return Invalid 66 | } 67 | 68 | // Offset returns the x, y and z offset this direction points in. 69 | func (d Type) Offset() (x, y, z int) { 70 | switch d { 71 | case Up: 72 | return 0, 1, 0 73 | case Down: 74 | return 0, -1, 0 75 | case North: 76 | return 0, 0, -1 77 | case South: 78 | return 0, 0, 1 79 | case West: 80 | return -1, 0, 0 81 | case East: 82 | return 1, 0, 0 83 | } 84 | return 0, 0, 0 85 | 86 | } 87 | 88 | // AsVec returns a vector of the direction's offset. 89 | func (d Type) AsVec() mgl32.Vec3 { 90 | x, y, z := d.Offset() 91 | return mgl32.Vec3{float32(x), float32(y), float32(z)} 92 | } 93 | 94 | // Opposite returns the direction directly opposite to this 95 | // direction. 96 | func (d Type) Opposite() Type { 97 | switch d { 98 | case Up: 99 | return Down 100 | case Down: 101 | return Up 102 | case East: 103 | return West 104 | case West: 105 | return East 106 | case North: 107 | return South 108 | case South: 109 | return North 110 | } 111 | return Invalid 112 | } 113 | 114 | // Clockwise returns the direction directly in a clockwise rotation to 115 | // this direction. 116 | func (d Type) Clockwise() Type { 117 | switch d { 118 | case Up: 119 | return Up 120 | case Down: 121 | return Down 122 | case East: 123 | return South 124 | case West: 125 | return North 126 | case North: 127 | return East 128 | case South: 129 | return West 130 | } 131 | return Invalid 132 | } 133 | 134 | // CounterClockwise returns the direction directly in a counter clockwise 135 | // rotation to this direction. 136 | func (d Type) CounterClockwise() Type { 137 | switch d { 138 | case Up: 139 | return Up 140 | case Down: 141 | return Down 142 | case East: 143 | return North 144 | case West: 145 | return South 146 | case North: 147 | return West 148 | case South: 149 | return East 150 | } 151 | return Invalid 152 | } 153 | 154 | // String returns a string representation of the direction. 155 | func (d Type) String() string { 156 | switch d { 157 | case Up: 158 | return "up" 159 | case Down: 160 | return "down" 161 | case North: 162 | return "north" 163 | case South: 164 | return "south" 165 | case West: 166 | return "west" 167 | case East: 168 | return "east" 169 | case Invalid: 170 | return "invalid" 171 | } 172 | return fmt.Sprintf("direction.Type(%d)", d) 173 | } 174 | -------------------------------------------------------------------------------- /type/nibble/nibble.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nibble 16 | 17 | // Array is an array of 4 bit values. 18 | type Array []byte 19 | 20 | // New creates a new nibble array that can store at least 21 | // the requested number of values. 22 | func New(size int) []byte { 23 | return make(Array, (size+1)>>1) 24 | } 25 | 26 | // Get returns the value at the index. 27 | func (a Array) Get(idx int) byte { 28 | val := a[idx>>1] 29 | if idx&1 == 0 { 30 | return val & 0xF 31 | } 32 | return val >> 4 33 | } 34 | 35 | // Set sets the value a the index. 36 | func (a Array) Set(idx int, val byte) { 37 | i := idx >> 1 38 | o := a[i] 39 | if idx&1 == 0 { 40 | a[i] = (o & 0xF0) | (val & 0xF) 41 | } else { 42 | a[i] = (o & 0x0F) | ((val & 0xF) << 4) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /type/vmath/frustum.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package vmath 16 | 17 | import ( 18 | "math" 19 | 20 | "github.com/go-gl/mathgl/mgl32" 21 | ) 22 | 23 | type Frustum struct { 24 | planes [6]fPlane 25 | fovy, aspect, near, far float32 26 | tang, nh, nw, fh, fw float32 27 | } 28 | 29 | type fPlane struct { 30 | N, P mgl32.Vec3 31 | D float32 32 | } 33 | 34 | func (f *fPlane) setPoints(v1, v2, v3 mgl32.Vec3) { 35 | aux1 := v1.Sub(v2) 36 | aux2 := v3.Sub(v2) 37 | 38 | f.N = aux2.Cross(aux1) 39 | f.N = safeNormalize(f.N) 40 | f.P = v2 41 | f.D = -(f.N.Dot(f.P)) 42 | } 43 | 44 | func NewFrustum() *Frustum { 45 | return &Frustum{} 46 | } 47 | 48 | func safeNormalize(v mgl32.Vec3) mgl32.Vec3 { 49 | v = v.Normalize() 50 | if math.IsInf(float64(v[0]), 0) || math.IsNaN(float64(v[0])) { 51 | return mgl32.Vec3{} 52 | } 53 | return v 54 | } 55 | 56 | func (f *Frustum) SetPerspective(fovy, aspect, near, far float32) { 57 | f.fovy = fovy 58 | f.aspect = aspect 59 | f.near = near 60 | f.far = far 61 | 62 | f.tang = float32(math.Tan(float64(fovy * 0.5))) 63 | f.nh = near * f.tang 64 | f.nw = f.nh * aspect 65 | f.fh = far * f.tang 66 | f.fw = f.fh * aspect 67 | } 68 | 69 | func (f *Frustum) SetCamera(p, l, u mgl32.Vec3) { 70 | Z := p.Sub(l) 71 | Z = safeNormalize(Z) 72 | 73 | X := u.Cross(Z) 74 | X = safeNormalize(X) 75 | 76 | Y := Z.Cross(X) 77 | 78 | nc := p.Sub(Z.Mul(f.near)) 79 | fc := p.Sub(Z.Mul(f.far)) 80 | 81 | ntl := nc.Add(Y.Mul(f.nh)).Sub(X.Mul(f.nw)) 82 | ntr := nc.Add(Y.Mul(f.nh)).Add(X.Mul(f.nw)) 83 | nbl := nc.Sub(Y.Mul(f.nh)).Sub(X.Mul(f.nw)) 84 | nbr := nc.Sub(Y.Mul(f.nh)).Add(X.Mul(f.nw)) 85 | 86 | ftl := fc.Add(Y.Mul(f.fh)).Sub(X.Mul(f.fw)) 87 | ftr := fc.Add(Y.Mul(f.fh)).Add(X.Mul(f.fw)) 88 | fbl := fc.Sub(Y.Mul(f.fh)).Sub(X.Mul(f.fw)) 89 | fbr := fc.Sub(Y.Mul(f.fh)).Add(X.Mul(f.fw)) 90 | 91 | const ( 92 | top = iota 93 | bottom 94 | left 95 | right 96 | nearP 97 | farP 98 | ) 99 | f.planes[top].setPoints(ntr, ntl, ftl) 100 | f.planes[bottom].setPoints(nbl, nbr, fbr) 101 | f.planes[left].setPoints(ntl, nbl, fbl) 102 | f.planes[right].setPoints(nbr, ntr, fbr) 103 | f.planes[nearP].setPoints(ntl, ntr, nbr) 104 | f.planes[farP].setPoints(ftr, ftl, fbl) 105 | } 106 | 107 | func (f *Frustum) IsSphereInside(x, y, z, radius float32) bool { 108 | p := mgl32.Vec3{x, y, z} 109 | for i := range f.planes { 110 | dist := f.planes[i].D + f.planes[i].N.Dot(p) 111 | if dist < -radius { 112 | return false 113 | } 114 | } 115 | return true 116 | } 117 | 118 | func (f *Frustum) IsAABBInside(aabb AABB) bool { 119 | for i := range f.planes { 120 | v := aabb.Min 121 | for j := 0; j < 3; j++ { 122 | if f.planes[i].N[j] >= 0 { 123 | v[j] = aabb.Max[j] 124 | } 125 | } 126 | if f.planes[i].N.Dot(v)+f.planes[i].D < 0 { 127 | return false 128 | } 129 | } 130 | return true 131 | } 132 | -------------------------------------------------------------------------------- /type/vmath/vector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package vmath 16 | 17 | import "github.com/go-gl/mathgl/mgl32" 18 | 19 | func LengthSquared(v mgl32.Vec3) float32 { 20 | return v[0]*v[0] + v[1]*v[1] + v[2]*v[2] 21 | } 22 | -------------------------------------------------------------------------------- /ui.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "math" 19 | 20 | "github.com/thinkofdeath/steven/console" 21 | "github.com/thinkofdeath/steven/ui" 22 | "github.com/thinkofdeath/steven/ui/scene" 23 | ) 24 | 25 | const ( 26 | uiAuto = "auto" 27 | uiSmall = "small" 28 | uiMedium = "medium" 29 | uiLarge = "large" 30 | ) 31 | 32 | var uiScale = console.NewStringVar("cl_ui_scale", "auto", console.Mutable, console.Serializable). 33 | Doc(` 34 | cl_ui_scale sets the scaling used for the user interface. 35 | Valid values are: 36 | - auto 37 | - small 38 | - medium 39 | - large 40 | `) 41 | 42 | func init() { 43 | uiScale.Callback(func() { 44 | setUIScale() 45 | }) 46 | ui.ClickSound = func() { 47 | PlaySound("random.click") 48 | } 49 | } 50 | 51 | func setUIScale() { 52 | switch uiScale.Value() { 53 | case uiAuto: 54 | ui.DrawMode = ui.Scaled 55 | ui.Scale = 1.0 56 | case uiSmall: 57 | ui.DrawMode = ui.Unscaled 58 | ui.Scale = 0.4 59 | case uiMedium: 60 | ui.DrawMode = ui.Unscaled 61 | ui.Scale = 0.6 62 | case uiLarge: 63 | ui.DrawMode = ui.Unscaled 64 | ui.Scale = 1.0 65 | } 66 | ui.ForceDraw() 67 | } 68 | 69 | func uiFooter(scene *scene.Type) { 70 | scene.AddDrawable( 71 | ui.NewText("Steven - "+stevenVersion(), 5, 5, 255, 255, 255).Attach(ui.Bottom, ui.Left), 72 | ) 73 | scene.AddDrawable( 74 | ui.NewText("Not affiliated with Mojang/Minecraft", 5, 5, 255, 200, 200).Attach(ui.Bottom, ui.Right), 75 | ) 76 | } 77 | 78 | type baseUI struct{} 79 | 80 | func (b *baseUI) init() {} 81 | func (b *baseUI) hover(x, y float64, w, h int) { ui.Hover(x, y, w, h) } 82 | func (b *baseUI) click(down bool, x, y float64, w, h int) { 83 | if down { 84 | return 85 | } 86 | ui.Click(x, y, w, h) 87 | } 88 | 89 | func newButtonText(str string, x, y, w, h float64) (*ui.Button, *ui.Text) { 90 | btn := ui.NewButton(x, y, w, h) 91 | text := ui.NewText(str, 0, 0, 255, 255, 255).Attach(ui.Middle, ui.Center) 92 | text.AttachTo(btn) 93 | btn.AddHover(func(over bool) { 94 | if over && !btn.Disabled() { 95 | text.SetB(160) 96 | } else { 97 | text.SetB(255) 98 | } 99 | }) 100 | return btn, text 101 | } 102 | 103 | type slider struct { 104 | back *ui.Button 105 | slider *ui.Button 106 | Value float64 107 | sliding bool 108 | UpdateFunc func() 109 | } 110 | 111 | func newSlider(x, y, w, h float64) *slider { 112 | btn := ui.NewButton(x, y, w, h) 113 | btn.SetDisabled(true) 114 | sl := ui.NewButton(0, 0, 20, h).Attach(ui.Left, ui.Top) 115 | sl.AttachTo(btn) 116 | return &slider{ 117 | back: btn, 118 | slider: sl, 119 | } 120 | } 121 | 122 | func (sl *slider) update() { 123 | ww, _ := sl.back.Size() 124 | sl.slider.SetX(sl.Value * (ww - 20)) 125 | if sl.UpdateFunc != nil { 126 | sl.UpdateFunc() 127 | } 128 | } 129 | 130 | func (sl *slider) click(down bool, x, y float64, w, h int) { 131 | if !down { 132 | if sl.sliding { 133 | sl.sliding = false 134 | } 135 | return 136 | } 137 | _, _, ok := ui.Intersects(sl.slider, x, y, w, h) 138 | if ok { 139 | sl.sliding = true 140 | } else { 141 | ox, _, ok := ui.Intersects(sl.back, x, y, w, h) 142 | if ok { 143 | ww, _ := sl.back.Size() 144 | v := math.Min(ww-10, math.Max(10, ox)) - 10 145 | sl.Value = v / (ww - 20) 146 | sl.update() 147 | } 148 | } 149 | } 150 | func (sl *slider) hover(x, y float64, w, h int) { 151 | if sl.sliding { 152 | ox, _, ok := ui.Intersects(sl.back, x, y, w, h) 153 | if ok { 154 | ww, _ := sl.back.Size() 155 | v := math.Min(ww-10, math.Max(10, ox)) - 10 156 | sl.Value = v / (ww - 20) 157 | sl.update() 158 | } 159 | } 160 | } 161 | 162 | func (sl *slider) add(s *scene.Type) { 163 | s.AddDrawable(sl.back) 164 | s.AddDrawable(sl.slider) 165 | } 166 | -------------------------------------------------------------------------------- /ui/container.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ui 16 | 17 | // Container is a drawable that is used for aligning other drawables. 18 | // Should never be drawn. 19 | type Container struct { 20 | baseElement 21 | x, y, h, w float64 22 | hovered bool 23 | HoverFunc func(over bool) 24 | ClickFunc func() 25 | } 26 | 27 | func NewContainer(x, y, w, h float64) *Container { 28 | return &Container{ 29 | x: x, y: y, w: w, h: h, 30 | baseElement: baseElement{ 31 | visible: false, 32 | isNew: false, 33 | }, 34 | } 35 | } 36 | 37 | func (c *Container) X() float64 { return c.x } 38 | func (c *Container) SetX(x float64) { 39 | if c.x != x { 40 | c.x = x 41 | c.dirty = true 42 | } 43 | } 44 | func (c *Container) Y() float64 { return c.y } 45 | func (c *Container) SetY(y float64) { 46 | if c.y != y { 47 | c.y = y 48 | c.dirty = true 49 | } 50 | } 51 | func (c *Container) Width() float64 { return c.w } 52 | func (c *Container) SetWidth(w float64) { 53 | if c.w != w { 54 | c.w = w 55 | c.dirty = true 56 | } 57 | } 58 | func (c *Container) Height() float64 { return c.h } 59 | func (c *Container) SetHeight(h float64) { 60 | if c.h != h { 61 | c.h = h 62 | c.dirty = true 63 | } 64 | } 65 | 66 | // Attach changes the location where this is attached to. 67 | func (c *Container) Attach(vAttach, hAttach AttachPoint) *Container { 68 | c.vAttach, c.hAttach = vAttach, hAttach 69 | return c 70 | } 71 | 72 | // Draw draws this to the target region. 73 | func (c *Container) Draw(r Region, delta float64) { 74 | } 75 | 76 | // Offset returns the offset of this drawable from the attachment 77 | // point. 78 | func (c *Container) Offset() (float64, float64) { 79 | return c.x, c.y 80 | } 81 | 82 | // Size returns the size of this drawable. 83 | func (c *Container) Size() (float64, float64) { 84 | return c.w, c.h 85 | } 86 | func (c *Container) Click(r Region, x, y float64) { 87 | if c.ClickFunc != nil { 88 | c.ClickFunc() 89 | } 90 | } 91 | func (c *Container) Hover(r Region, x, y float64, over bool) { 92 | c.hovered = over 93 | if c.HoverFunc != nil { 94 | c.HoverFunc(over) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /ui/input.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ui 16 | 17 | import "github.com/go-gl/glfw/v3.1/glfw" 18 | 19 | // focusable is a drawable that can be focsued for keyboard input 20 | type focusable interface { 21 | Drawable 22 | setFocused(bool) 23 | handleKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) 24 | handleChar(w *glfw.Window, char rune) 25 | } 26 | 27 | // HandleKey passes the input to the focused drawable 28 | func HandleKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 29 | if key == glfw.KeyTab && action == glfw.Release { 30 | CycleFocus() 31 | return 32 | } 33 | if f := getFocused(); f != nil { 34 | f.handleKey(w, key, scancode, action, mods) 35 | } 36 | } 37 | 38 | // HandleChar passes the input to the focused drawable 39 | func HandleChar(w *glfw.Window, char rune) { 40 | if f := getFocused(); f != nil { 41 | f.handleChar(w, char) 42 | } 43 | } 44 | 45 | // Currently focused drawable 46 | var focused focusable 47 | 48 | // Returns the currently focused drawable or nil 49 | // Tries to focus an drawable if one isn't currently focused 50 | func getFocused() focusable { 51 | if focused != nil { 52 | // Ensure the drawable is focused 53 | for _, d := range drawables { 54 | if d.Drawable == focused { 55 | return d.Drawable.(focusable) 56 | } 57 | } 58 | } 59 | // Try to focus another drawable 60 | for _, d := range drawables { 61 | if f, ok := d.Drawable.(focusable); ok { 62 | focus(f) 63 | return focused 64 | } 65 | } 66 | // Clear the focus if one isn't found incase this 67 | // fell through from the first check 68 | focus(nil) 69 | return nil 70 | } 71 | 72 | // CycleFocus changes the focus to the next drawable 73 | func CycleFocus() { 74 | pos := 0 75 | l := len(drawables) 76 | // Find our drawable 77 | for i, d := range drawables { 78 | if d.Drawable == focused { 79 | pos = (i + 1) % l 80 | break 81 | } 82 | } 83 | // Make sure we don't loop forever on scenes without focusable drawables 84 | max := l 85 | for ; max >= 0; max-- { 86 | dr := drawables[pos] 87 | if f, ok := dr.Drawable.(focusable); ok { 88 | focus(f) 89 | break 90 | } 91 | pos = (pos + 1) % l 92 | } 93 | } 94 | 95 | // Changes the focused drawable 96 | func focus(f focusable) { 97 | if focused != nil { 98 | focused.setFocused(false) 99 | } 100 | focused = f 101 | if focused != nil { 102 | focused.setFocused(true) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /ui/scene/scene.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package scene provides methods to manage and load multiple ui scenes. 16 | package scene 17 | 18 | import "github.com/thinkofdeath/steven/ui" 19 | 20 | // Type stores a scene that can be removed and shown at any time. 21 | type Type struct { 22 | visible bool 23 | 24 | drawables []ui.Drawable 25 | hidding bool 26 | } 27 | 28 | // New creates a new scene. 29 | func New(visible bool) *Type { 30 | return &Type{ 31 | visible: visible, 32 | } 33 | } 34 | 35 | // Show shows all the drawables in the scene 36 | func (t *Type) Show() { 37 | if t.visible { 38 | return 39 | } 40 | t.visible = true 41 | for _, d := range t.drawables { 42 | ui.AddDrawable(d) 43 | } 44 | } 45 | 46 | // Hide hides all the drawables in the scene 47 | func (t *Type) Hide() { 48 | if !t.visible { 49 | return 50 | } 51 | t.visible = false 52 | t.hidding = true 53 | for _, d := range t.drawables { 54 | ui.Remove(d) 55 | } 56 | t.hidding = false 57 | } 58 | 59 | // AddDrawable adds the drawable to the draw list. 60 | func (t *Type) AddDrawable(d ui.Drawable) { 61 | t.drawables = append(t.drawables, d) 62 | if t.visible { 63 | ui.AddDrawable(d) 64 | } 65 | d.SetRemoveHook(t.removeHook) 66 | } 67 | 68 | func (t *Type) removeHook(d ui.Drawable) { 69 | if t.hidding { 70 | return 71 | } 72 | for i, dd := range t.drawables { 73 | if dd == d { 74 | t.drawables = append(t.drawables[:i], t.drawables[i+1:]...) 75 | return 76 | } 77 | } 78 | 79 | } 80 | 81 | // IsVisible returns whether the scene is currently visible. 82 | func (t *Type) IsVisible() bool { 83 | return t.visible 84 | } 85 | -------------------------------------------------------------------------------- /ui/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ui 16 | 17 | // Mode defines the way the ui is rendered. 18 | type Mode int 19 | 20 | // Valid modes 21 | const ( 22 | Scaled Mode = iota 23 | Unscaled 24 | ) 25 | 26 | // AttachPoint is a side of the screen that an element 27 | // can be attached to 28 | type AttachPoint int 29 | 30 | // Attachment points 31 | // VAlign, HAlign 32 | const ( 33 | Top, Left AttachPoint = iota, iota 34 | Middle, Center 35 | Bottom, Right 36 | ) 37 | -------------------------------------------------------------------------------- /uieditserver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | "github.com/thinkofdeath/steven/ui" 19 | "github.com/thinkofdeath/steven/ui/scene" 20 | ) 21 | 22 | type editServer struct { 23 | baseUI 24 | scene *scene.Type 25 | logo uiLogo 26 | 27 | name *ui.TextBox 28 | address *ui.TextBox 29 | 30 | index int 31 | } 32 | 33 | func newEditServer(index int) *editServer { 34 | se := &editServer{ 35 | scene: scene.New(true), 36 | index: index, 37 | } 38 | 39 | se.logo.init(se.scene) 40 | 41 | uiFooter(se.scene) 42 | 43 | done, txt := newButtonText("Done", 110, 100, 200, 40) 44 | se.scene.AddDrawable(done.Attach(ui.Center, ui.Middle)) 45 | se.scene.AddDrawable(txt) 46 | done.AddClick(func() { 47 | se.save() 48 | }) 49 | 50 | cancel, txt := newButtonText("Cancel", -110, 100, 200, 40) 51 | se.scene.AddDrawable(cancel.Attach(ui.Center, ui.Middle)) 52 | se.scene.AddDrawable(txt) 53 | cancel.AddClick(func() { 54 | setScreen(newServerList()) 55 | }) 56 | 57 | se.name = ui.NewTextBox(0, -20, 400, 40) 58 | se.scene.AddDrawable(se.name.Attach(ui.Middle, ui.Center)) 59 | label := ui.NewText("Name:", 0, -18, 255, 255, 255).Attach(ui.Top, ui.Left) 60 | label.AttachTo(se.name) 61 | se.scene.AddDrawable(label) 62 | 63 | se.address = ui.NewTextBox(0, 40, 400, 40) 64 | se.scene.AddDrawable(se.address.Attach(ui.Middle, ui.Center)) 65 | label = ui.NewText("Address:", 0, -18, 255, 255, 255).Attach(ui.Top, ui.Left) 66 | label.AttachTo(se.address) 67 | se.scene.AddDrawable(label) 68 | 69 | if index != -1 { 70 | server := Config.Servers[index] 71 | se.name.Update(server.Name) 72 | se.address.Update(server.Address) 73 | } 74 | 75 | return se 76 | } 77 | 78 | func (se *editServer) save() { 79 | if se.index == -1 { 80 | Config.Servers = append(Config.Servers, ConfigServer{ 81 | Name: se.name.Value(), 82 | Address: se.address.Value(), 83 | }) 84 | } else { 85 | Config.Servers[se.index].Name = se.name.Value() 86 | Config.Servers[se.index].Address = se.address.Value() 87 | } 88 | saveServers() 89 | setScreen(newServerList()) 90 | } 91 | 92 | func (se *editServer) tick(delta float64) { 93 | se.logo.tick(delta) 94 | } 95 | 96 | func (se *editServer) remove() { 97 | se.scene.Hide() 98 | } 99 | -------------------------------------------------------------------------------- /uigamemenu.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licengmd under the Apache Licengm, Version 2.0 (the "Licengm"); 4 | // you may not ugm this file except in compliance with the Licengm. 5 | // You may obtain a copy of the Licengm at 6 | // 7 | // http://www.apache.org/licengms/LICENgm-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the Licengm is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // gme the Licengm for the specific language governing permissions and 13 | // limitations under the Licengm. 14 | 15 | package steven 16 | 17 | import ( 18 | "github.com/go-gl/glfw/v3.1/glfw" 19 | "github.com/thinkofdeath/steven/render" 20 | "github.com/thinkofdeath/steven/ui" 21 | "github.com/thinkofdeath/steven/ui/scene" 22 | ) 23 | 24 | type gameMenu struct { 25 | baseUI 26 | scene *scene.Type 27 | 28 | background *ui.Image 29 | } 30 | 31 | func newGameMenu() screen { 32 | gm := &gameMenu{ 33 | scene: scene.New(true), 34 | } 35 | 36 | gm.background = ui.NewImage(render.GetTexture("solid"), 0, 0, 854, 480, 0, 0, 1, 1, 0, 0, 0) 37 | gm.background.SetA(160) 38 | gm.scene.AddDrawable(gm.background.Attach(ui.Top, ui.Left)) 39 | 40 | disconnect, txt := newButtonText("Disconnect", 0, 50, 400, 40) 41 | gm.scene.AddDrawable(disconnect.Attach(ui.Center, ui.Middle)) 42 | gm.scene.AddDrawable(txt) 43 | disconnect.AddClick(func() { Client.network.SignalClose(errManualDisconnect) }) 44 | 45 | rtg, txt := newButtonText("Return to game", 0, -50, 400, 40) 46 | gm.scene.AddDrawable(rtg.Attach(ui.Center, ui.Middle)) 47 | gm.scene.AddDrawable(txt) 48 | rtg.AddClick(func() { setScreen(nil) }) 49 | 50 | option, txt := newButtonText("Options", 0, 0, 400, 40) 51 | gm.scene.AddDrawable(option.Attach(ui.Center, ui.Middle)) 52 | gm.scene.AddDrawable(txt) 53 | option.AddClick(func() { setScreen(newOptionMenu(newGameMenu)) }) 54 | 55 | uiFooter(gm.scene) 56 | return gm 57 | } 58 | 59 | func (gm *gameMenu) init() { 60 | window.SetKeyCallback(gm.handleKey) 61 | } 62 | 63 | func (gm *gameMenu) tick(delta float64) { 64 | width, height := window.GetFramebufferSize() 65 | gm.background.SetWidth(float64(width) / ui.Scale) 66 | gm.background.SetHeight(float64(height) / ui.Scale) 67 | } 68 | 69 | func (gm *gameMenu) handleKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 70 | if key == glfw.KeyEscape && action == glfw.Release { 71 | setScreen(nil) 72 | } 73 | } 74 | 75 | func (gm *gameMenu) remove() { 76 | gm.scene.Hide() 77 | window.SetKeyCallback(onKey) 78 | } 79 | -------------------------------------------------------------------------------- /uilogin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import ( 18 | crand "crypto/rand" 19 | "encoding/hex" 20 | 21 | "github.com/thinkofdeath/steven/console" 22 | "github.com/thinkofdeath/steven/protocol/mojang" 23 | "github.com/thinkofdeath/steven/ui" 24 | "github.com/thinkofdeath/steven/ui/scene" 25 | ) 26 | 27 | type loginScreen struct { 28 | baseUI 29 | scene *scene.Type 30 | logo uiLogo 31 | 32 | User *ui.TextBox 33 | Pass *ui.TextBox 34 | LoginBtn *ui.Button 35 | LoginTxt *ui.Text 36 | LoginError *ui.Text 37 | } 38 | 39 | func newLoginScreen() *loginScreen { 40 | ls := &loginScreen{ 41 | scene: scene.New(false), 42 | } 43 | if clientToken.Value() == "" { 44 | data := make([]byte, 16) 45 | crand.Read(data) 46 | clientToken.SetValue(hex.EncodeToString(data)) 47 | } 48 | 49 | ls.logo.init(ls.scene) 50 | 51 | ls.LoginBtn, ls.LoginTxt = newButtonText("Login", 0, 100, 400, 40) 52 | ls.scene.AddDrawable(ls.LoginBtn.Attach(ui.Middle, ui.Center)) 53 | ls.scene.AddDrawable(ls.LoginTxt) 54 | ls.LoginBtn.AddClick(ls.Login) 55 | 56 | ls.LoginError = ui.NewText("", 0, 150, 255, 50, 50).Attach(ui.Middle, ui.Center) 57 | ls.scene.AddDrawable(ls.LoginError) 58 | 59 | { 60 | ls.User = ui.NewTextBox(0, -20, 400, 40).Attach(ui.Middle, ui.Center) 61 | ls.scene.AddDrawable(ls.User) 62 | label := ui.NewText("Username/Email:", 0, -18, 255, 255, 255) 63 | label.AttachTo(ls.User) 64 | ls.scene.AddDrawable(label) 65 | } 66 | 67 | { 68 | ls.Pass = ui.NewTextBox(0, 40, 400, 40).Attach(ui.Middle, ui.Center) 69 | ls.Pass.SubmitFunc = ls.Login 70 | ls.Pass.SetPassword(true) 71 | ls.scene.AddDrawable(ls.Pass) 72 | label := ui.NewText("Password:", 0, -18, 255, 255, 255) 73 | label.AttachTo(ls.Pass) 74 | ls.scene.AddDrawable(label) 75 | } 76 | 77 | uiFooter(ls.scene) 78 | 79 | ls.scene.Show() 80 | if getProfile().IsComplete() { 81 | ls.refresh() 82 | } 83 | 84 | return ls 85 | } 86 | 87 | func (ls *loginScreen) postLogin(p mojang.Profile, err error) { 88 | if err != nil { 89 | if me, ok := err.(mojang.Error); ok { 90 | ls.LoginError.Update(me.Message) 91 | } else { 92 | ls.LoginError.Update(err.Error()) 93 | } 94 | ls.LoginBtn.SetDisabled(false) 95 | ls.LoginTxt.Update("Login") 96 | return 97 | } 98 | clientUsername.SetValue(p.Username) 99 | clientUUID.SetValue(p.ID) 100 | clientAccessToken.SetValue(p.AccessToken) 101 | 102 | setScreen(newServerList()) 103 | console.ExecConf("autoexec.cfg") 104 | } 105 | 106 | func (ls *loginScreen) refresh() { 107 | ls.LoginError.Update("") 108 | ls.LoginBtn.SetDisabled(true) 109 | ls.LoginTxt.Update("Logging in...") 110 | go func() { 111 | p, err := mojang.Refresh(getProfile(), clientToken.Value()) 112 | syncChan <- func() { ls.postLogin(p, err) } 113 | }() 114 | } 115 | 116 | func (ls *loginScreen) Login() { 117 | ls.LoginError.Update("") 118 | ls.LoginBtn.SetDisabled(true) 119 | ls.LoginTxt.Update("Logging in...") 120 | go func() { 121 | p, err := mojang.Login(ls.User.Value(), ls.Pass.Value(), clientToken.Value()) 122 | syncChan <- func() { ls.postLogin(p, err) } 123 | }() 124 | } 125 | 126 | func (ls *loginScreen) tick(delta float64) { 127 | ls.logo.tick(delta) 128 | } 129 | 130 | func (ls *loginScreen) remove() { 131 | ls.scene.Hide() 132 | } 133 | -------------------------------------------------------------------------------- /uirespawn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licenrsd under the Apache Licenrs, Version 2.0 (the "Licenrs"); 4 | // you may not urs this file except in compliance with the Licenrs. 5 | // You may obtain a copy of the Licenrs at 6 | // 7 | // http://www.apache.org/licenrss/LICENrs-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the Licenrs is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // rse the Licenrs for the specific language governing permissions and 13 | // limitations under the Licenrs. 14 | 15 | package steven 16 | 17 | import ( 18 | "github.com/thinkofdeath/steven/protocol" 19 | "github.com/thinkofdeath/steven/render" 20 | "github.com/thinkofdeath/steven/ui" 21 | "github.com/thinkofdeath/steven/ui/scene" 22 | ) 23 | 24 | type respawnScreen struct { 25 | baseUI 26 | scene *scene.Type 27 | 28 | background *ui.Image 29 | } 30 | 31 | func newRespawnScreen() *respawnScreen { 32 | rs := &respawnScreen{ 33 | scene: scene.New(true), 34 | } 35 | 36 | rs.background = ui.NewImage(render.GetTexture("solid"), 0, 0, 854, 480, 0, 0, 1, 1, 0, 0, 0) 37 | rs.background.SetA(160) 38 | rs.scene.AddDrawable(rs.background.Attach(ui.Top, ui.Left)) 39 | 40 | rs.scene.AddDrawable( 41 | ui.NewText("Respawn:", 0, -20, 255, 255, 255).Attach(ui.Center, ui.Middle), 42 | ) 43 | 44 | respawn, txt := newButtonText("Respawn", -205, 20, 400, 40) 45 | rs.scene.AddDrawable(respawn.Attach(ui.Center, ui.Middle)) 46 | rs.scene.AddDrawable(txt) 47 | respawn.AddClick(func() { 48 | setScreen(nil) 49 | Client.network.Write(&protocol.ClientStatus{ActionID: 0}) 50 | }) 51 | 52 | disconnect, txt := newButtonText("Disconnect", 205, 20, 400, 40) 53 | rs.scene.AddDrawable(disconnect.Attach(ui.Center, ui.Middle)) 54 | rs.scene.AddDrawable(txt) 55 | disconnect.AddClick(func() { Client.network.SignalClose(errManualDisconnect) }) 56 | 57 | uiFooter(rs.scene) 58 | return rs 59 | } 60 | 61 | func (rs *respawnScreen) tick(delta float64) { 62 | width, height := window.GetFramebufferSize() 63 | rs.background.SetWidth(float64(width) / ui.Scale) 64 | rs.background.SetHeight(float64(height) / ui.Scale) 65 | } 66 | 67 | func (rs *respawnScreen) remove() { 68 | rs.scene.Hide() 69 | } 70 | -------------------------------------------------------------------------------- /uiscreen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | import "github.com/go-gl/glfw/v3.1/glfw" 18 | 19 | var currentScreen screen 20 | 21 | type screen interface { 22 | init() 23 | tick(delta float64) 24 | hover(x, y float64, w, h int) 25 | click(down bool, x, y float64, w, h int) 26 | remove() 27 | } 28 | 29 | func setScreen(s screen) { 30 | if currentScreen != nil { 31 | currentScreen.remove() 32 | } 33 | currentScreen = s 34 | if s != nil { 35 | Client.playerList.set(false) 36 | Client.scene.Hide() 37 | Client.hotbarScene.Hide() 38 | lockMouse = false 39 | window.SetInputMode(glfw.CursorMode, glfw.CursorNormal) 40 | for i := range Client.KeyState { 41 | Client.KeyState[i] = false 42 | } 43 | s.init() 44 | } else { 45 | Client.scene.Show() 46 | Client.hotbarScene.Show() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /uivolume.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licenvmd under the Apache Licenvm, Version 2.0 (the "Licenvm"); 4 | // you may not uvm this file except in cvmpliance with the Licenvm. 5 | // You may obtain a copy of the Licenvm at 6 | // 7 | // http://www.apache.org/licenvms/LICENvm-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the Licenvm is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // vme the Licenvm for the specific language governing permissions and 13 | // limitations under the Licenvm. 14 | 15 | package steven 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/go-gl/glfw/v3.1/glfw" 22 | "github.com/thinkofdeath/steven/render" 23 | "github.com/thinkofdeath/steven/ui" 24 | "github.com/thinkofdeath/steven/ui/scene" 25 | ) 26 | 27 | type volumeMenu struct { 28 | baseUI 29 | scene *scene.Type 30 | 31 | background *ui.Image 32 | sliders []*slider 33 | 34 | ret func() screen 35 | } 36 | 37 | func newVolumeMenu(ret func() screen) *volumeMenu { 38 | vm := &volumeMenu{ 39 | scene: scene.New(true), 40 | ret: ret, 41 | } 42 | 43 | vm.background = ui.NewImage(render.GetTexture("solid"), 0, 0, 854, 480, 0, 0, 1, 1, 0, 0, 0) 44 | vm.background.SetA(160) 45 | vm.scene.AddDrawable(vm.background.Attach(ui.Top, ui.Left)) 46 | 47 | done, txt := newButtonText("Done", 0, 50, 400, 40) 48 | vm.scene.AddDrawable(done.Attach(ui.Bottom, ui.Middle)) 49 | vm.scene.AddDrawable(txt) 50 | done.AddClick(func() { setScreen(newOptionMenu(vm.ret)) }) 51 | 52 | master := newSlider(0, -100, 620, 40) 53 | master.back.Attach(ui.Center, ui.Middle) 54 | master.add(vm.scene) 55 | mtxt := ui.NewText("", 0, 0, 255, 255, 255).Attach(ui.Center, ui.Middle) 56 | mtxt.AttachTo(master.back) 57 | vm.scene.AddDrawable(mtxt) 58 | master.UpdateFunc = func() { 59 | muVolMaster.SetValue(round(master.Value * 100)) 60 | if muVolMaster.Value() == 0 { 61 | mtxt.Update("Master: OFF") 62 | return 63 | } 64 | mtxt.Update(fmt.Sprintf("Master: %d%%", muVolMaster.Value())) 65 | } 66 | master.Value = float64(muVolMaster.Value()) / 100.0 67 | master.update() 68 | vm.sliders = append(vm.sliders, master) 69 | 70 | for i, cat := range soundCategories { 71 | cat := cat 72 | x := 160.0 73 | if i&1 == 0 { 74 | x = -x 75 | } 76 | y := 50 * float64(i/2) 77 | snd := newSlider(x, -50+y, 300, 40) 78 | snd.back.Attach(ui.Center, ui.Middle) 79 | snd.add(vm.scene) 80 | stxt := ui.NewText("", 0, 0, 255, 255, 255).Attach(ui.Center, ui.Middle) 81 | stxt.AttachTo(snd.back) 82 | vm.scene.AddDrawable(stxt) 83 | 84 | v := volVars[cat] 85 | 86 | snd.UpdateFunc = func() { 87 | v.SetValue(round(snd.Value * 100)) 88 | if val := v.Value(); val != 0 { 89 | stxt.Update(fmt.Sprintf("%s: %d%%", strings.Title(string(cat)), val)) 90 | return 91 | } 92 | stxt.Update(fmt.Sprintf("%s: OFF", strings.Title(string(cat)))) 93 | } 94 | snd.Value = float64(v.Value()) / 100.0 95 | snd.update() 96 | vm.sliders = append(vm.sliders, snd) 97 | } 98 | 99 | return vm 100 | } 101 | 102 | func (vm *volumeMenu) init() { 103 | window.SetKeyCallback(vm.handleKey) 104 | } 105 | 106 | func (vm *volumeMenu) hover(x, y float64, w, h int) { 107 | for _, s := range vm.sliders { 108 | s.hover(x, y, w, h) 109 | } 110 | ui.Hover(x, y, w, h) 111 | } 112 | func (vm *volumeMenu) click(down bool, x, y float64, w, h int) { 113 | for _, s := range vm.sliders { 114 | s.click(down, x, y, w, h) 115 | } 116 | if down { 117 | return 118 | } 119 | ui.Click(x, y, w, h) 120 | } 121 | func (vm *volumeMenu) tick(delta float64) { 122 | width, height := window.GetFramebufferSize() 123 | vm.background.SetWidth(float64(width) / ui.Scale) 124 | vm.background.SetHeight(float64(height) / ui.Scale) 125 | } 126 | 127 | func (vm *volumeMenu) handleKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 128 | if key == glfw.KeyEscape && action == glfw.Release { 129 | setScreen(newOptionMenu(vm.ret)) 130 | } 131 | } 132 | 133 | func (vm *volumeMenu) remove() { 134 | vm.scene.Hide() 135 | window.SetKeyCallback(onKey) 136 | } 137 | -------------------------------------------------------------------------------- /world.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Matthew Collins 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package steven 16 | 17 | type worldType int 18 | 19 | const ( 20 | wtNether worldType = iota - 1 21 | wtOverworld 22 | wtEnd 23 | ) 24 | --------------------------------------------------------------------------------